chore(debug): log Claude mimic fingerprint
This commit is contained in:
@@ -50,6 +50,11 @@ func (s *GatewayService) debugModelRoutingEnabled() bool {
|
||||
return v == "1" || v == "true" || v == "yes" || v == "on"
|
||||
}
|
||||
|
||||
func (s *GatewayService) debugClaudeMimicEnabled() bool {
|
||||
v := strings.ToLower(strings.TrimSpace(os.Getenv("SUB2API_DEBUG_CLAUDE_MIMIC")))
|
||||
return v == "1" || v == "true" || v == "yes" || v == "on"
|
||||
}
|
||||
|
||||
func shortSessionHash(sessionHash string) string {
|
||||
if sessionHash == "" {
|
||||
return ""
|
||||
@@ -60,6 +65,121 @@ func shortSessionHash(sessionHash string) string {
|
||||
return sessionHash[:8]
|
||||
}
|
||||
|
||||
func redactAuthHeaderValue(v string) string {
|
||||
v = strings.TrimSpace(v)
|
||||
if v == "" {
|
||||
return ""
|
||||
}
|
||||
// Keep scheme for debugging, redact secret.
|
||||
if strings.HasPrefix(strings.ToLower(v), "bearer ") {
|
||||
return "Bearer [redacted]"
|
||||
}
|
||||
return "[redacted]"
|
||||
}
|
||||
|
||||
func safeHeaderValueForLog(key string, v string) string {
|
||||
key = strings.ToLower(strings.TrimSpace(key))
|
||||
switch key {
|
||||
case "authorization", "x-api-key":
|
||||
return redactAuthHeaderValue(v)
|
||||
default:
|
||||
return strings.TrimSpace(v)
|
||||
}
|
||||
}
|
||||
|
||||
func extractSystemPreviewFromBody(body []byte) string {
|
||||
if len(body) == 0 {
|
||||
return ""
|
||||
}
|
||||
sys := gjson.GetBytes(body, "system")
|
||||
if !sys.Exists() {
|
||||
return ""
|
||||
}
|
||||
|
||||
switch {
|
||||
case sys.IsArray():
|
||||
for _, item := range sys.Array() {
|
||||
if !item.IsObject() {
|
||||
continue
|
||||
}
|
||||
if strings.EqualFold(item.Get("type").String(), "text") {
|
||||
if t := item.Get("text").String(); strings.TrimSpace(t) != "" {
|
||||
return t
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
case sys.Type == gjson.String:
|
||||
return sys.String()
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func logClaudeMimicDebug(req *http.Request, body []byte, account *Account, tokenType string, mimicClaudeCode bool) {
|
||||
if req == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Only log a minimal fingerprint to avoid leaking user content.
|
||||
interesting := []string{
|
||||
"user-agent",
|
||||
"x-app",
|
||||
"anthropic-dangerous-direct-browser-access",
|
||||
"anthropic-version",
|
||||
"anthropic-beta",
|
||||
"x-stainless-lang",
|
||||
"x-stainless-package-version",
|
||||
"x-stainless-os",
|
||||
"x-stainless-arch",
|
||||
"x-stainless-runtime",
|
||||
"x-stainless-runtime-version",
|
||||
"x-stainless-retry-count",
|
||||
"x-stainless-timeout",
|
||||
"authorization",
|
||||
"x-api-key",
|
||||
"content-type",
|
||||
"accept",
|
||||
"x-stainless-helper-method",
|
||||
}
|
||||
|
||||
h := make([]string, 0, len(interesting))
|
||||
for _, k := range interesting {
|
||||
if v := req.Header.Get(k); v != "" {
|
||||
h = append(h, fmt.Sprintf("%s=%q", k, safeHeaderValueForLog(k, v)))
|
||||
}
|
||||
}
|
||||
|
||||
metaUserID := strings.TrimSpace(gjson.GetBytes(body, "metadata.user_id").String())
|
||||
sysPreview := strings.TrimSpace(extractSystemPreviewFromBody(body))
|
||||
|
||||
// Truncate preview to keep logs sane.
|
||||
if len(sysPreview) > 300 {
|
||||
sysPreview = sysPreview[:300] + "..."
|
||||
}
|
||||
sysPreview = strings.ReplaceAll(sysPreview, "\n", "\\n")
|
||||
sysPreview = strings.ReplaceAll(sysPreview, "\r", "\\r")
|
||||
|
||||
aid := int64(0)
|
||||
aname := ""
|
||||
if account != nil {
|
||||
aid = account.ID
|
||||
aname = account.Name
|
||||
}
|
||||
|
||||
log.Printf(
|
||||
"[ClaudeMimicDebug] url=%s account=%d(%s) tokenType=%s mimic=%t meta.user_id=%q system.preview=%q headers={%s}",
|
||||
req.URL.String(),
|
||||
aid,
|
||||
aname,
|
||||
tokenType,
|
||||
mimicClaudeCode,
|
||||
metaUserID,
|
||||
sysPreview,
|
||||
strings.Join(h, " "),
|
||||
)
|
||||
}
|
||||
|
||||
// sseDataRe matches SSE data lines with optional whitespace after colon.
|
||||
// Some upstream APIs return non-standard "data:" without space (should be "data: ").
|
||||
var (
|
||||
@@ -3264,6 +3384,10 @@ func (s *GatewayService) buildUpstreamRequest(ctx context.Context, c *gin.Contex
|
||||
}
|
||||
}
|
||||
|
||||
if s.debugClaudeMimicEnabled() {
|
||||
logClaudeMimicDebug(req, body, account, tokenType, mimicClaudeCode)
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
@@ -4686,6 +4810,10 @@ func (s *GatewayService) buildCountTokensRequest(ctx context.Context, c *gin.Con
|
||||
}
|
||||
}
|
||||
|
||||
if s.debugClaudeMimicEnabled() {
|
||||
logClaudeMimicDebug(req, body, account, tokenType, mimicClaudeCode)
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user