chore(debug): emit Claude mimic fingerprint on credential-scope error
This commit is contained in:
@@ -45,6 +45,10 @@ const (
|
|||||||
maxCacheControlBlocks = 4 // Anthropic API 允许的最大 cache_control 块数量
|
maxCacheControlBlocks = 4 // Anthropic API 允许的最大 cache_control 块数量
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
claudeMimicDebugInfoKey = "claude_mimic_debug_info"
|
||||||
|
)
|
||||||
|
|
||||||
func (s *GatewayService) debugModelRoutingEnabled() bool {
|
func (s *GatewayService) debugModelRoutingEnabled() bool {
|
||||||
v := strings.ToLower(strings.TrimSpace(os.Getenv("SUB2API_DEBUG_MODEL_ROUTING")))
|
v := strings.ToLower(strings.TrimSpace(os.Getenv("SUB2API_DEBUG_MODEL_ROUTING")))
|
||||||
return v == "1" || v == "true" || v == "yes" || v == "on"
|
return v == "1" || v == "true" || v == "yes" || v == "on"
|
||||||
@@ -116,9 +120,9 @@ func extractSystemPreviewFromBody(body []byte) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func logClaudeMimicDebug(req *http.Request, body []byte, account *Account, tokenType string, mimicClaudeCode bool) {
|
func buildClaudeMimicDebugLine(req *http.Request, body []byte, account *Account, tokenType string, mimicClaudeCode bool) string {
|
||||||
if req == nil {
|
if req == nil {
|
||||||
return
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only log a minimal fingerprint to avoid leaking user content.
|
// Only log a minimal fingerprint to avoid leaking user content.
|
||||||
@@ -167,8 +171,8 @@ func logClaudeMimicDebug(req *http.Request, body []byte, account *Account, token
|
|||||||
aname = account.Name
|
aname = account.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf(
|
return fmt.Sprintf(
|
||||||
"[ClaudeMimicDebug] url=%s account=%d(%s) tokenType=%s mimic=%t meta.user_id=%q system.preview=%q headers={%s}",
|
"url=%s account=%d(%s) tokenType=%s mimic=%t meta.user_id=%q system.preview=%q headers={%s}",
|
||||||
req.URL.String(),
|
req.URL.String(),
|
||||||
aid,
|
aid,
|
||||||
aname,
|
aname,
|
||||||
@@ -180,6 +184,23 @@ func logClaudeMimicDebug(req *http.Request, body []byte, account *Account, token
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func logClaudeMimicDebug(req *http.Request, body []byte, account *Account, tokenType string, mimicClaudeCode bool) {
|
||||||
|
line := buildClaudeMimicDebugLine(req, body, account, tokenType, mimicClaudeCode)
|
||||||
|
if line == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Printf("[ClaudeMimicDebug] %s", line)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isClaudeCodeCredentialScopeError(msg string) bool {
|
||||||
|
m := strings.ToLower(strings.TrimSpace(msg))
|
||||||
|
if m == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return strings.Contains(m, "only authorized for use with claude code") &&
|
||||||
|
strings.Contains(m, "cannot be used for other api requests")
|
||||||
|
}
|
||||||
|
|
||||||
// sseDataRe matches SSE data lines with optional whitespace after colon.
|
// sseDataRe matches SSE data lines with optional whitespace after colon.
|
||||||
// Some upstream APIs return non-standard "data:" without space (should be "data: ").
|
// Some upstream APIs return non-standard "data:" without space (should be "data: ").
|
||||||
var (
|
var (
|
||||||
@@ -3384,6 +3405,11 @@ func (s *GatewayService) buildUpstreamRequest(ctx context.Context, c *gin.Contex
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Always capture a compact fingerprint line for later error diagnostics.
|
||||||
|
// We only print it when needed (or when the explicit debug flag is enabled).
|
||||||
|
if c != nil && tokenType == "oauth" {
|
||||||
|
c.Set(claudeMimicDebugInfoKey, buildClaudeMimicDebugLine(req, body, account, tokenType, mimicClaudeCode))
|
||||||
|
}
|
||||||
if s.debugClaudeMimicEnabled() {
|
if s.debugClaudeMimicEnabled() {
|
||||||
logClaudeMimicDebug(req, body, account, tokenType, mimicClaudeCode)
|
logClaudeMimicDebug(req, body, account, tokenType, mimicClaudeCode)
|
||||||
}
|
}
|
||||||
@@ -3640,6 +3666,20 @@ func (s *GatewayService) handleErrorResponse(ctx context.Context, resp *http.Res
|
|||||||
upstreamMsg := strings.TrimSpace(extractUpstreamErrorMessage(body))
|
upstreamMsg := strings.TrimSpace(extractUpstreamErrorMessage(body))
|
||||||
upstreamMsg = sanitizeUpstreamErrorMessage(upstreamMsg)
|
upstreamMsg = sanitizeUpstreamErrorMessage(upstreamMsg)
|
||||||
|
|
||||||
|
// Print a compact upstream request fingerprint when we hit the Claude Code OAuth
|
||||||
|
// credential scope error. This avoids requiring env-var tweaks in a fixed deploy.
|
||||||
|
if isClaudeCodeCredentialScopeError(upstreamMsg) && c != nil {
|
||||||
|
if v, ok := c.Get(claudeMimicDebugInfoKey); ok {
|
||||||
|
if line, ok := v.(string); ok && strings.TrimSpace(line) != "" {
|
||||||
|
log.Printf("[ClaudeMimicDebugOnError] status=%d request_id=%s %s",
|
||||||
|
resp.StatusCode,
|
||||||
|
resp.Header.Get("x-request-id"),
|
||||||
|
line,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Enrich Ops error logs with upstream status + message, and optionally a truncated body snippet.
|
// Enrich Ops error logs with upstream status + message, and optionally a truncated body snippet.
|
||||||
upstreamDetail := ""
|
upstreamDetail := ""
|
||||||
if s.cfg != nil && s.cfg.Gateway.LogUpstreamErrorBody {
|
if s.cfg != nil && s.cfg.Gateway.LogUpstreamErrorBody {
|
||||||
@@ -3769,6 +3809,19 @@ func (s *GatewayService) handleRetryExhaustedError(ctx context.Context, resp *ht
|
|||||||
|
|
||||||
upstreamMsg := strings.TrimSpace(extractUpstreamErrorMessage(respBody))
|
upstreamMsg := strings.TrimSpace(extractUpstreamErrorMessage(respBody))
|
||||||
upstreamMsg = sanitizeUpstreamErrorMessage(upstreamMsg)
|
upstreamMsg = sanitizeUpstreamErrorMessage(upstreamMsg)
|
||||||
|
|
||||||
|
if isClaudeCodeCredentialScopeError(upstreamMsg) && c != nil {
|
||||||
|
if v, ok := c.Get(claudeMimicDebugInfoKey); ok {
|
||||||
|
if line, ok := v.(string); ok && strings.TrimSpace(line) != "" {
|
||||||
|
log.Printf("[ClaudeMimicDebugOnError] status=%d request_id=%s %s",
|
||||||
|
resp.StatusCode,
|
||||||
|
resp.Header.Get("x-request-id"),
|
||||||
|
line,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
upstreamDetail := ""
|
upstreamDetail := ""
|
||||||
if s.cfg != nil && s.cfg.Gateway.LogUpstreamErrorBody {
|
if s.cfg != nil && s.cfg.Gateway.LogUpstreamErrorBody {
|
||||||
maxBytes := s.cfg.Gateway.LogUpstreamErrorBodyMaxBytes
|
maxBytes := s.cfg.Gateway.LogUpstreamErrorBodyMaxBytes
|
||||||
@@ -4810,6 +4863,9 @@ func (s *GatewayService) buildCountTokensRequest(ctx context.Context, c *gin.Con
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c != nil && tokenType == "oauth" {
|
||||||
|
c.Set(claudeMimicDebugInfoKey, buildClaudeMimicDebugLine(req, body, account, tokenType, mimicClaudeCode))
|
||||||
|
}
|
||||||
if s.debugClaudeMimicEnabled() {
|
if s.debugClaudeMimicEnabled() {
|
||||||
logClaudeMimicDebug(req, body, account, tokenType, mimicClaudeCode)
|
logClaudeMimicDebug(req, body, account, tokenType, mimicClaudeCode)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user