From 1fd1a58a7ad0006cd6d1e7e90bd6399df50998da Mon Sep 17 00:00:00 2001 From: haruka <1628615876@qq.com> Date: Thu, 19 Mar 2026 11:15:02 +0800 Subject: [PATCH] fix: record original upstream status code when failover exhausted (#1128) When all failover accounts are exhausted, handleFailoverExhausted maps the upstream status code (e.g. 403) to a client-facing code (e.g. 502) but did not write the original code to the gin context. This caused ops error logs to show the mapped code instead of the real upstream code. Call SetOpsUpstreamError before mapUpstreamError in all failover- exhausted paths so that ops_error_logger captures the true upstream status code and message. Co-Authored-By: Claude Opus 4.6 (1M context) --- backend/internal/handler/gateway_handler.go | 5 +++++ backend/internal/handler/gemini_v1beta_handler.go | 4 ++++ backend/internal/handler/openai_gateway_handler.go | 5 +++++ backend/internal/handler/sora_gateway_handler.go | 3 +++ backend/internal/service/ops_upstream_context.go | 7 +++++++ 5 files changed, 24 insertions(+) diff --git a/backend/internal/handler/gateway_handler.go b/backend/internal/handler/gateway_handler.go index 831029c4..b5250aad 100644 --- a/backend/internal/handler/gateway_handler.go +++ b/backend/internal/handler/gateway_handler.go @@ -1219,6 +1219,10 @@ func (h *GatewayHandler) handleFailoverExhausted(c *gin.Context, failoverErr *se } } + // 记录原始上游状态码,以便 ops 错误日志捕获真实的上游错误 + upstreamMsg := service.ExtractUpstreamErrorMessage(responseBody) + service.SetOpsUpstreamError(c, statusCode, upstreamMsg, "") + // 使用默认的错误映射 status, errType, errMsg := h.mapUpstreamError(statusCode) h.handleStreamingAwareError(c, status, errType, errMsg, streamStarted) @@ -1227,6 +1231,7 @@ func (h *GatewayHandler) handleFailoverExhausted(c *gin.Context, failoverErr *se // handleFailoverExhaustedSimple 简化版本,用于没有响应体的情况 func (h *GatewayHandler) handleFailoverExhaustedSimple(c *gin.Context, statusCode int, streamStarted bool) { status, errType, errMsg := h.mapUpstreamError(statusCode) + service.SetOpsUpstreamError(c, statusCode, errMsg, "") h.handleStreamingAwareError(c, status, errType, errMsg, streamStarted) } diff --git a/backend/internal/handler/gemini_v1beta_handler.go b/backend/internal/handler/gemini_v1beta_handler.go index cfe80911..fb231898 100644 --- a/backend/internal/handler/gemini_v1beta_handler.go +++ b/backend/internal/handler/gemini_v1beta_handler.go @@ -593,6 +593,10 @@ func (h *GatewayHandler) handleGeminiFailoverExhausted(c *gin.Context, failoverE } } + // 记录原始上游状态码,以便 ops 错误日志捕获真实的上游错误 + upstreamMsg := service.ExtractUpstreamErrorMessage(responseBody) + service.SetOpsUpstreamError(c, statusCode, upstreamMsg, "") + // 使用默认的错误映射 status, message := mapGeminiUpstreamError(statusCode) googleError(c, status, message) diff --git a/backend/internal/handler/openai_gateway_handler.go b/backend/internal/handler/openai_gateway_handler.go index c681e61d..ec957feb 100644 --- a/backend/internal/handler/openai_gateway_handler.go +++ b/backend/internal/handler/openai_gateway_handler.go @@ -1435,6 +1435,10 @@ func (h *OpenAIGatewayHandler) handleFailoverExhausted(c *gin.Context, failoverE } } + // 记录原始上游状态码,以便 ops 错误日志捕获真实的上游错误 + upstreamMsg := service.ExtractUpstreamErrorMessage(responseBody) + service.SetOpsUpstreamError(c, statusCode, upstreamMsg, "") + // 使用默认的错误映射 status, errType, errMsg := h.mapUpstreamError(statusCode) h.handleStreamingAwareError(c, status, errType, errMsg, streamStarted) @@ -1443,6 +1447,7 @@ func (h *OpenAIGatewayHandler) handleFailoverExhausted(c *gin.Context, failoverE // handleFailoverExhaustedSimple 简化版本,用于没有响应体的情况 func (h *OpenAIGatewayHandler) handleFailoverExhaustedSimple(c *gin.Context, statusCode int, streamStarted bool) { status, errType, errMsg := h.mapUpstreamError(statusCode) + service.SetOpsUpstreamError(c, statusCode, errMsg, "") h.handleStreamingAwareError(c, status, errType, errMsg, streamStarted) } diff --git a/backend/internal/handler/sora_gateway_handler.go b/backend/internal/handler/sora_gateway_handler.go index dc301ce1..cc1b1c0b 100644 --- a/backend/internal/handler/sora_gateway_handler.go +++ b/backend/internal/handler/sora_gateway_handler.go @@ -484,6 +484,9 @@ func (h *SoraGatewayHandler) handleConcurrencyError(c *gin.Context, err error, s } func (h *SoraGatewayHandler) handleFailoverExhausted(c *gin.Context, statusCode int, responseHeaders http.Header, responseBody []byte, streamStarted bool) { + upstreamMsg := service.ExtractUpstreamErrorMessage(responseBody) + service.SetOpsUpstreamError(c, statusCode, upstreamMsg, "") + status, errType, errMsg := h.mapUpstreamError(statusCode, responseHeaders, responseBody) h.handleStreamingAwareError(c, status, errType, errMsg, streamStarted) } diff --git a/backend/internal/service/ops_upstream_context.go b/backend/internal/service/ops_upstream_context.go index 21e09c43..9adf5896 100644 --- a/backend/internal/service/ops_upstream_context.go +++ b/backend/internal/service/ops_upstream_context.go @@ -53,6 +53,13 @@ func SetOpsLatencyMs(c *gin.Context, key string, value int64) { c.Set(key, value) } +// SetOpsUpstreamError is the exported wrapper for setOpsUpstreamError, used by +// handler-layer code (e.g. failover-exhausted paths) that needs to record the +// original upstream status code before mapping it to a client-facing code. +func SetOpsUpstreamError(c *gin.Context, upstreamStatusCode int, upstreamMessage, upstreamDetail string) { + setOpsUpstreamError(c, upstreamStatusCode, upstreamMessage, upstreamDetail) +} + func setOpsUpstreamError(c *gin.Context, upstreamStatusCode int, upstreamMessage, upstreamDetail string) { if c == nil { return