"backend/internal/git@web.lueluesay.top:chenxi/sub2api.git" did not exist on "539b41f42101486568b2594557c2cc95a2122478"
Commit 0bf162f6 authored by yangjianbo's avatar yangjianbo
Browse files

Merge branch 'dev' into release

parents 29191af8 64236361
...@@ -8,7 +8,6 @@ import ( ...@@ -8,7 +8,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"log"
"log/slog" "log/slog"
mathrand "math/rand" mathrand "math/rand"
"net" "net"
...@@ -21,6 +20,7 @@ import ( ...@@ -21,6 +20,7 @@ import (
"github.com/Wei-Shaw/sub2api/internal/pkg/antigravity" "github.com/Wei-Shaw/sub2api/internal/pkg/antigravity"
"github.com/Wei-Shaw/sub2api/internal/pkg/ctxkey" "github.com/Wei-Shaw/sub2api/internal/pkg/ctxkey"
"github.com/Wei-Shaw/sub2api/internal/pkg/logger"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/tidwall/gjson" "github.com/tidwall/gjson"
...@@ -154,7 +154,7 @@ type smartRetryResult struct { ...@@ -154,7 +154,7 @@ type smartRetryResult struct {
func (s *AntigravityGatewayService) handleSmartRetry(p antigravityRetryLoopParams, resp *http.Response, respBody []byte, baseURL string, urlIdx int, availableURLs []string) *smartRetryResult { func (s *AntigravityGatewayService) handleSmartRetry(p antigravityRetryLoopParams, resp *http.Response, respBody []byte, baseURL string, urlIdx int, availableURLs []string) *smartRetryResult {
// "Resource has been exhausted" 是 URL 级别限流,切换 URL(仅 429) // "Resource has been exhausted" 是 URL 级别限流,切换 URL(仅 429)
if resp.StatusCode == http.StatusTooManyRequests && isURLLevelRateLimit(respBody) && urlIdx < len(availableURLs)-1 { if resp.StatusCode == http.StatusTooManyRequests && isURLLevelRateLimit(respBody) && urlIdx < len(availableURLs)-1 {
log.Printf("%s URL fallback (429): %s -> %s", p.prefix, baseURL, availableURLs[urlIdx+1]) logger.LegacyPrintf("service.antigravity_gateway", "%s URL fallback (429): %s -> %s", p.prefix, baseURL, availableURLs[urlIdx+1])
return &smartRetryResult{action: smartRetryActionContinueURL} return &smartRetryResult{action: smartRetryActionContinueURL}
} }
...@@ -174,13 +174,13 @@ func (s *AntigravityGatewayService) handleSmartRetry(p antigravityRetryLoopParam ...@@ -174,13 +174,13 @@ func (s *AntigravityGatewayService) handleSmartRetry(p antigravityRetryLoopParam
if rateLimitDuration <= 0 { if rateLimitDuration <= 0 {
rateLimitDuration = antigravityDefaultRateLimitDuration rateLimitDuration = antigravityDefaultRateLimitDuration
} }
log.Printf("%s status=%d oauth_long_delay model=%s account=%d upstream_retry_delay=%v body=%s (model rate limit, switch account)", logger.LegacyPrintf("service.antigravity_gateway", "%s status=%d oauth_long_delay model=%s account=%d upstream_retry_delay=%v body=%s (model rate limit, switch account)",
p.prefix, resp.StatusCode, modelName, p.account.ID, rateLimitDuration, truncateForLog(respBody, 200)) p.prefix, resp.StatusCode, modelName, p.account.ID, rateLimitDuration, truncateForLog(respBody, 200))
resetAt := time.Now().Add(rateLimitDuration) resetAt := time.Now().Add(rateLimitDuration)
if !setModelRateLimitByModelName(p.ctx, p.accountRepo, p.account.ID, modelName, p.prefix, resp.StatusCode, resetAt, false) { if !setModelRateLimitByModelName(p.ctx, p.accountRepo, p.account.ID, modelName, p.prefix, resp.StatusCode, resetAt, false) {
p.handleError(p.ctx, p.prefix, p.account, resp.StatusCode, resp.Header, respBody, p.requestedModel, p.groupID, p.sessionHash, p.isStickySession) p.handleError(p.ctx, p.prefix, p.account, resp.StatusCode, resp.Header, respBody, p.requestedModel, p.groupID, p.sessionHash, p.isStickySession)
log.Printf("%s status=%d rate_limited account=%d (no model mapping)", p.prefix, resp.StatusCode, p.account.ID) logger.LegacyPrintf("service.antigravity_gateway", "%s status=%d rate_limited account=%d (no model mapping)", p.prefix, resp.StatusCode, p.account.ID)
} else { } else {
s.updateAccountModelRateLimitInCache(p.ctx, p.account, modelName, resetAt) s.updateAccountModelRateLimitInCache(p.ctx, p.account, modelName, resetAt)
} }
...@@ -202,12 +202,12 @@ func (s *AntigravityGatewayService) handleSmartRetry(p antigravityRetryLoopParam ...@@ -202,12 +202,12 @@ func (s *AntigravityGatewayService) handleSmartRetry(p antigravityRetryLoopParam
var lastRetryBody []byte var lastRetryBody []byte
for attempt := 1; attempt <= antigravitySmartRetryMaxAttempts; attempt++ { for attempt := 1; attempt <= antigravitySmartRetryMaxAttempts; attempt++ {
log.Printf("%s status=%d oauth_smart_retry attempt=%d/%d delay=%v model=%s account=%d", logger.LegacyPrintf("service.antigravity_gateway", "%s status=%d oauth_smart_retry attempt=%d/%d delay=%v model=%s account=%d",
p.prefix, resp.StatusCode, attempt, antigravitySmartRetryMaxAttempts, waitDuration, modelName, p.account.ID) p.prefix, resp.StatusCode, attempt, antigravitySmartRetryMaxAttempts, waitDuration, modelName, p.account.ID)
select { select {
case <-p.ctx.Done(): case <-p.ctx.Done():
log.Printf("%s status=context_canceled_during_smart_retry", p.prefix) logger.LegacyPrintf("service.antigravity_gateway", "%s status=context_canceled_during_smart_retry", p.prefix)
return &smartRetryResult{action: smartRetryActionBreakWithResp, err: p.ctx.Err()} return &smartRetryResult{action: smartRetryActionBreakWithResp, err: p.ctx.Err()}
case <-time.After(waitDuration): case <-time.After(waitDuration):
} }
...@@ -215,7 +215,7 @@ func (s *AntigravityGatewayService) handleSmartRetry(p antigravityRetryLoopParam ...@@ -215,7 +215,7 @@ func (s *AntigravityGatewayService) handleSmartRetry(p antigravityRetryLoopParam
// 智能重试:创建新请求 // 智能重试:创建新请求
retryReq, err := antigravity.NewAPIRequestWithURL(p.ctx, baseURL, p.action, p.accessToken, p.body) retryReq, err := antigravity.NewAPIRequestWithURL(p.ctx, baseURL, p.action, p.accessToken, p.body)
if err != nil { if err != nil {
log.Printf("%s status=smart_retry_request_build_failed error=%v", p.prefix, err) logger.LegacyPrintf("service.antigravity_gateway", "%s status=smart_retry_request_build_failed error=%v", p.prefix, err)
p.handleError(p.ctx, p.prefix, p.account, resp.StatusCode, resp.Header, respBody, p.requestedModel, p.groupID, p.sessionHash, p.isStickySession) p.handleError(p.ctx, p.prefix, p.account, resp.StatusCode, resp.Header, respBody, p.requestedModel, p.groupID, p.sessionHash, p.isStickySession)
return &smartRetryResult{ return &smartRetryResult{
action: smartRetryActionBreakWithResp, action: smartRetryActionBreakWithResp,
...@@ -229,13 +229,13 @@ func (s *AntigravityGatewayService) handleSmartRetry(p antigravityRetryLoopParam ...@@ -229,13 +229,13 @@ func (s *AntigravityGatewayService) handleSmartRetry(p antigravityRetryLoopParam
retryResp, retryErr := p.httpUpstream.Do(retryReq, p.proxyURL, p.account.ID, p.account.Concurrency) retryResp, retryErr := p.httpUpstream.Do(retryReq, p.proxyURL, p.account.ID, p.account.Concurrency)
if retryErr == nil && retryResp != nil && retryResp.StatusCode != http.StatusTooManyRequests && retryResp.StatusCode != http.StatusServiceUnavailable { if retryErr == nil && retryResp != nil && retryResp.StatusCode != http.StatusTooManyRequests && retryResp.StatusCode != http.StatusServiceUnavailable {
log.Printf("%s status=%d smart_retry_success attempt=%d/%d", p.prefix, retryResp.StatusCode, attempt, antigravitySmartRetryMaxAttempts) logger.LegacyPrintf("service.antigravity_gateway", "%s status=%d smart_retry_success attempt=%d/%d", p.prefix, retryResp.StatusCode, attempt, antigravitySmartRetryMaxAttempts)
return &smartRetryResult{action: smartRetryActionBreakWithResp, resp: retryResp} return &smartRetryResult{action: smartRetryActionBreakWithResp, resp: retryResp}
} }
// 网络错误时,继续重试 // 网络错误时,继续重试
if retryErr != nil || retryResp == nil { if retryErr != nil || retryResp == nil {
log.Printf("%s status=smart_retry_network_error attempt=%d/%d error=%v", p.prefix, attempt, antigravitySmartRetryMaxAttempts, retryErr) logger.LegacyPrintf("service.antigravity_gateway", "%s status=smart_retry_network_error attempt=%d/%d error=%v", p.prefix, attempt, antigravitySmartRetryMaxAttempts, retryErr)
continue continue
} }
...@@ -271,7 +271,7 @@ func (s *AntigravityGatewayService) handleSmartRetry(p antigravityRetryLoopParam ...@@ -271,7 +271,7 @@ func (s *AntigravityGatewayService) handleSmartRetry(p antigravityRetryLoopParam
// 单账号 503 退避重试模式:智能重试耗尽后不设限流、不切换账号, // 单账号 503 退避重试模式:智能重试耗尽后不设限流、不切换账号,
// 直接返回 503 让 Handler 层的单账号退避循环做最终处理。 // 直接返回 503 让 Handler 层的单账号退避循环做最终处理。
if resp.StatusCode == http.StatusServiceUnavailable && isSingleAccountRetry(p.ctx) { if resp.StatusCode == http.StatusServiceUnavailable && isSingleAccountRetry(p.ctx) {
log.Printf("%s status=%d smart_retry_exhausted_single_account attempts=%d model=%s account=%d body=%s (return 503 directly)", logger.LegacyPrintf("service.antigravity_gateway", "%s status=%d smart_retry_exhausted_single_account attempts=%d model=%s account=%d body=%s (return 503 directly)",
p.prefix, resp.StatusCode, antigravitySmartRetryMaxAttempts, modelName, p.account.ID, truncateForLog(retryBody, 200)) p.prefix, resp.StatusCode, antigravitySmartRetryMaxAttempts, modelName, p.account.ID, truncateForLog(retryBody, 200))
return &smartRetryResult{ return &smartRetryResult{
action: smartRetryActionBreakWithResp, action: smartRetryActionBreakWithResp,
...@@ -283,15 +283,15 @@ func (s *AntigravityGatewayService) handleSmartRetry(p antigravityRetryLoopParam ...@@ -283,15 +283,15 @@ func (s *AntigravityGatewayService) handleSmartRetry(p antigravityRetryLoopParam
} }
} }
log.Printf("%s status=%d smart_retry_exhausted attempts=%d model=%s account=%d upstream_retry_delay=%v body=%s (switch account)", logger.LegacyPrintf("service.antigravity_gateway", "%s status=%d smart_retry_exhausted attempts=%d model=%s account=%d upstream_retry_delay=%v body=%s (switch account)",
p.prefix, resp.StatusCode, antigravitySmartRetryMaxAttempts, modelName, p.account.ID, rateLimitDuration, truncateForLog(retryBody, 200)) p.prefix, resp.StatusCode, antigravitySmartRetryMaxAttempts, modelName, p.account.ID, rateLimitDuration, truncateForLog(retryBody, 200))
resetAt := time.Now().Add(rateLimitDuration) resetAt := time.Now().Add(rateLimitDuration)
if p.accountRepo != nil && modelName != "" { if p.accountRepo != nil && modelName != "" {
if err := p.accountRepo.SetModelRateLimit(p.ctx, p.account.ID, modelName, resetAt); err != nil { if err := p.accountRepo.SetModelRateLimit(p.ctx, p.account.ID, modelName, resetAt); err != nil {
log.Printf("%s status=%d model_rate_limit_failed model=%s error=%v", p.prefix, resp.StatusCode, modelName, err) logger.LegacyPrintf("service.antigravity_gateway", "%s status=%d model_rate_limit_failed model=%s error=%v", p.prefix, resp.StatusCode, modelName, err)
} else { } else {
log.Printf("%s status=%d model_rate_limited_after_smart_retry model=%s account=%d reset_in=%v", logger.LegacyPrintf("service.antigravity_gateway", "%s status=%d model_rate_limited_after_smart_retry model=%s account=%d reset_in=%v",
p.prefix, resp.StatusCode, modelName, p.account.ID, rateLimitDuration) p.prefix, resp.StatusCode, modelName, p.account.ID, rateLimitDuration)
s.updateAccountModelRateLimitInCache(p.ctx, p.account, modelName, resetAt) s.updateAccountModelRateLimitInCache(p.ctx, p.account, modelName, resetAt)
} }
...@@ -346,7 +346,7 @@ func (s *AntigravityGatewayService) handleSingleAccountRetryInPlace( ...@@ -346,7 +346,7 @@ func (s *AntigravityGatewayService) handleSingleAccountRetryInPlace(
waitDuration = antigravitySmartRetryMinWait waitDuration = antigravitySmartRetryMinWait
} }
log.Printf("%s status=%d single_account_503_retry_in_place model=%s account=%d upstream_retry_delay=%v (retrying in-place instead of rate-limiting)", logger.LegacyPrintf("service.antigravity_gateway", "%s status=%d single_account_503_retry_in_place model=%s account=%d upstream_retry_delay=%v (retrying in-place instead of rate-limiting)",
p.prefix, resp.StatusCode, modelName, p.account.ID, waitDuration) p.prefix, resp.StatusCode, modelName, p.account.ID, waitDuration)
var lastRetryResp *http.Response var lastRetryResp *http.Response
...@@ -358,19 +358,19 @@ func (s *AntigravityGatewayService) handleSingleAccountRetryInPlace( ...@@ -358,19 +358,19 @@ func (s *AntigravityGatewayService) handleSingleAccountRetryInPlace(
if totalWaited+waitDuration > antigravitySingleAccountSmartRetryTotalMaxWait { if totalWaited+waitDuration > antigravitySingleAccountSmartRetryTotalMaxWait {
remaining := antigravitySingleAccountSmartRetryTotalMaxWait - totalWaited remaining := antigravitySingleAccountSmartRetryTotalMaxWait - totalWaited
if remaining <= 0 { if remaining <= 0 {
log.Printf("%s single_account_503_retry: total_wait_exceeded total=%v max=%v, giving up", logger.LegacyPrintf("service.antigravity_gateway", "%s single_account_503_retry: total_wait_exceeded total=%v max=%v, giving up",
p.prefix, totalWaited, antigravitySingleAccountSmartRetryTotalMaxWait) p.prefix, totalWaited, antigravitySingleAccountSmartRetryTotalMaxWait)
break break
} }
waitDuration = remaining waitDuration = remaining
} }
log.Printf("%s status=%d single_account_503_retry attempt=%d/%d delay=%v total_waited=%v model=%s account=%d", logger.LegacyPrintf("service.antigravity_gateway", "%s status=%d single_account_503_retry attempt=%d/%d delay=%v total_waited=%v model=%s account=%d",
p.prefix, resp.StatusCode, attempt, antigravitySingleAccountSmartRetryMaxAttempts, waitDuration, totalWaited, modelName, p.account.ID) p.prefix, resp.StatusCode, attempt, antigravitySingleAccountSmartRetryMaxAttempts, waitDuration, totalWaited, modelName, p.account.ID)
select { select {
case <-p.ctx.Done(): case <-p.ctx.Done():
log.Printf("%s status=context_canceled_during_single_account_retry", p.prefix) logger.LegacyPrintf("service.antigravity_gateway", "%s status=context_canceled_during_single_account_retry", p.prefix)
return &smartRetryResult{action: smartRetryActionBreakWithResp, err: p.ctx.Err()} return &smartRetryResult{action: smartRetryActionBreakWithResp, err: p.ctx.Err()}
case <-time.After(waitDuration): case <-time.After(waitDuration):
} }
...@@ -379,13 +379,13 @@ func (s *AntigravityGatewayService) handleSingleAccountRetryInPlace( ...@@ -379,13 +379,13 @@ func (s *AntigravityGatewayService) handleSingleAccountRetryInPlace(
// 创建新请求 // 创建新请求
retryReq, err := antigravity.NewAPIRequestWithURL(p.ctx, baseURL, p.action, p.accessToken, p.body) retryReq, err := antigravity.NewAPIRequestWithURL(p.ctx, baseURL, p.action, p.accessToken, p.body)
if err != nil { if err != nil {
log.Printf("%s single_account_503_retry: request_build_failed error=%v", p.prefix, err) logger.LegacyPrintf("service.antigravity_gateway", "%s single_account_503_retry: request_build_failed error=%v", p.prefix, err)
break break
} }
retryResp, retryErr := p.httpUpstream.Do(retryReq, p.proxyURL, p.account.ID, p.account.Concurrency) retryResp, retryErr := p.httpUpstream.Do(retryReq, p.proxyURL, p.account.ID, p.account.Concurrency)
if retryErr == nil && retryResp != nil && retryResp.StatusCode != http.StatusTooManyRequests && retryResp.StatusCode != http.StatusServiceUnavailable { if retryErr == nil && retryResp != nil && retryResp.StatusCode != http.StatusTooManyRequests && retryResp.StatusCode != http.StatusServiceUnavailable {
log.Printf("%s status=%d single_account_503_retry_success attempt=%d/%d total_waited=%v", logger.LegacyPrintf("service.antigravity_gateway", "%s status=%d single_account_503_retry_success attempt=%d/%d total_waited=%v",
p.prefix, retryResp.StatusCode, attempt, antigravitySingleAccountSmartRetryMaxAttempts, totalWaited) p.prefix, retryResp.StatusCode, attempt, antigravitySingleAccountSmartRetryMaxAttempts, totalWaited)
// 关闭之前的响应 // 关闭之前的响应
if lastRetryResp != nil { if lastRetryResp != nil {
...@@ -396,7 +396,7 @@ func (s *AntigravityGatewayService) handleSingleAccountRetryInPlace( ...@@ -396,7 +396,7 @@ func (s *AntigravityGatewayService) handleSingleAccountRetryInPlace(
// 网络错误时继续重试 // 网络错误时继续重试
if retryErr != nil || retryResp == nil { if retryErr != nil || retryResp == nil {
log.Printf("%s single_account_503_retry: network_error attempt=%d/%d error=%v", logger.LegacyPrintf("service.antigravity_gateway", "%s single_account_503_retry: network_error attempt=%d/%d error=%v",
p.prefix, attempt, antigravitySingleAccountSmartRetryMaxAttempts, retryErr) p.prefix, attempt, antigravitySingleAccountSmartRetryMaxAttempts, retryErr)
continue continue
} }
...@@ -430,7 +430,7 @@ func (s *AntigravityGatewayService) handleSingleAccountRetryInPlace( ...@@ -430,7 +430,7 @@ func (s *AntigravityGatewayService) handleSingleAccountRetryInPlace(
if retryBody == nil { if retryBody == nil {
retryBody = respBody retryBody = respBody
} }
log.Printf("%s status=%d single_account_503_retry_exhausted attempts=%d total_waited=%v model=%s account=%d body=%s (return 503 directly)", logger.LegacyPrintf("service.antigravity_gateway", "%s status=%d single_account_503_retry_exhausted attempts=%d total_waited=%v model=%s account=%d body=%s (return 503 directly)",
p.prefix, resp.StatusCode, antigravitySingleAccountSmartRetryMaxAttempts, totalWaited, modelName, p.account.ID, truncateForLog(retryBody, 200)) p.prefix, resp.StatusCode, antigravitySingleAccountSmartRetryMaxAttempts, totalWaited, modelName, p.account.ID, truncateForLog(retryBody, 200))
return &smartRetryResult{ return &smartRetryResult{
...@@ -453,10 +453,10 @@ func (s *AntigravityGatewayService) antigravityRetryLoop(p antigravityRetryLoopP ...@@ -453,10 +453,10 @@ func (s *AntigravityGatewayService) antigravityRetryLoop(p antigravityRetryLoopP
// 如果上游确实还不可用,handleSmartRetry → handleSingleAccountRetryInPlace // 如果上游确实还不可用,handleSmartRetry → handleSingleAccountRetryInPlace
// 会在 Service 层原地等待+重试,不需要在预检查这里等。 // 会在 Service 层原地等待+重试,不需要在预检查这里等。
if isSingleAccountRetry(p.ctx) { if isSingleAccountRetry(p.ctx) {
log.Printf("%s pre_check: single_account_retry skipping rate_limit remaining=%v model=%s account=%d (will retry in-place if 503)", logger.LegacyPrintf("service.antigravity_gateway", "%s pre_check: single_account_retry skipping rate_limit remaining=%v model=%s account=%d (will retry in-place if 503)",
p.prefix, remaining.Truncate(time.Millisecond), p.requestedModel, p.account.ID) p.prefix, remaining.Truncate(time.Millisecond), p.requestedModel, p.account.ID)
} else { } else {
log.Printf("%s pre_check: rate_limit_switch remaining=%v model=%s account=%d", logger.LegacyPrintf("service.antigravity_gateway", "%s pre_check: rate_limit_switch remaining=%v model=%s account=%d",
p.prefix, remaining.Truncate(time.Millisecond), p.requestedModel, p.account.ID) p.prefix, remaining.Truncate(time.Millisecond), p.requestedModel, p.account.ID)
return nil, &AntigravityAccountSwitchError{ return nil, &AntigravityAccountSwitchError{
OriginalAccountID: p.account.ID, OriginalAccountID: p.account.ID,
...@@ -492,7 +492,7 @@ urlFallbackLoop: ...@@ -492,7 +492,7 @@ urlFallbackLoop:
for attempt := 1; attempt <= antigravityMaxRetries; attempt++ { for attempt := 1; attempt <= antigravityMaxRetries; attempt++ {
select { select {
case <-p.ctx.Done(): case <-p.ctx.Done():
log.Printf("%s status=context_canceled error=%v", p.prefix, p.ctx.Err()) logger.LegacyPrintf("service.antigravity_gateway", "%s status=context_canceled error=%v", p.prefix, p.ctx.Err())
return nil, p.ctx.Err() return nil, p.ctx.Err()
default: default:
} }
...@@ -522,18 +522,18 @@ urlFallbackLoop: ...@@ -522,18 +522,18 @@ urlFallbackLoop:
Message: safeErr, Message: safeErr,
}) })
if shouldAntigravityFallbackToNextURL(err, 0) && urlIdx < len(availableURLs)-1 { if shouldAntigravityFallbackToNextURL(err, 0) && urlIdx < len(availableURLs)-1 {
log.Printf("%s URL fallback (connection error): %s -> %s", p.prefix, baseURL, availableURLs[urlIdx+1]) logger.LegacyPrintf("service.antigravity_gateway", "%s URL fallback (connection error): %s -> %s", p.prefix, baseURL, availableURLs[urlIdx+1])
continue urlFallbackLoop continue urlFallbackLoop
} }
if attempt < antigravityMaxRetries { if attempt < antigravityMaxRetries {
log.Printf("%s status=request_failed retry=%d/%d error=%v", p.prefix, attempt, antigravityMaxRetries, err) logger.LegacyPrintf("service.antigravity_gateway", "%s status=request_failed retry=%d/%d error=%v", p.prefix, attempt, antigravityMaxRetries, err)
if !sleepAntigravityBackoffWithContext(p.ctx, attempt) { if !sleepAntigravityBackoffWithContext(p.ctx, attempt) {
log.Printf("%s status=context_canceled_during_backoff", p.prefix) logger.LegacyPrintf("service.antigravity_gateway", "%s status=context_canceled_during_backoff", p.prefix)
return nil, p.ctx.Err() return nil, p.ctx.Err()
} }
continue continue
} }
log.Printf("%s status=request_failed retries_exhausted error=%v", p.prefix, err) logger.LegacyPrintf("service.antigravity_gateway", "%s status=request_failed retries_exhausted error=%v", p.prefix, err)
setOpsUpstreamError(p.c, 0, safeErr, "") setOpsUpstreamError(p.c, 0, safeErr, "")
return nil, fmt.Errorf("upstream request failed after retries: %w", err) return nil, fmt.Errorf("upstream request failed after retries: %w", err)
} }
...@@ -590,9 +590,9 @@ urlFallbackLoop: ...@@ -590,9 +590,9 @@ urlFallbackLoop:
Message: upstreamMsg, Message: upstreamMsg,
Detail: getUpstreamDetail(respBody), Detail: getUpstreamDetail(respBody),
}) })
log.Printf("%s status=%d retry=%d/%d body=%s", p.prefix, resp.StatusCode, attempt, antigravityMaxRetries, truncateForLog(respBody, 200)) logger.LegacyPrintf("service.antigravity_gateway", "%s status=%d retry=%d/%d body=%s", p.prefix, resp.StatusCode, attempt, antigravityMaxRetries, truncateForLog(respBody, 200))
if !sleepAntigravityBackoffWithContext(p.ctx, attempt) { if !sleepAntigravityBackoffWithContext(p.ctx, attempt) {
log.Printf("%s status=context_canceled_during_backoff", p.prefix) logger.LegacyPrintf("service.antigravity_gateway", "%s status=context_canceled_during_backoff", p.prefix)
return nil, p.ctx.Err() return nil, p.ctx.Err()
} }
continue continue
...@@ -600,7 +600,7 @@ urlFallbackLoop: ...@@ -600,7 +600,7 @@ urlFallbackLoop:
// 重试用尽,标记账户限流 // 重试用尽,标记账户限流
p.handleError(p.ctx, p.prefix, p.account, resp.StatusCode, resp.Header, respBody, p.requestedModel, p.groupID, p.sessionHash, p.isStickySession) p.handleError(p.ctx, p.prefix, p.account, resp.StatusCode, resp.Header, respBody, p.requestedModel, p.groupID, p.sessionHash, p.isStickySession)
log.Printf("%s status=%d rate_limited base_url=%s body=%s", p.prefix, resp.StatusCode, baseURL, truncateForLog(respBody, 200)) logger.LegacyPrintf("service.antigravity_gateway", "%s status=%d rate_limited base_url=%s body=%s", p.prefix, resp.StatusCode, baseURL, truncateForLog(respBody, 200))
resp = &http.Response{ resp = &http.Response{
StatusCode: resp.StatusCode, StatusCode: resp.StatusCode,
Header: resp.Header.Clone(), Header: resp.Header.Clone(),
...@@ -624,9 +624,9 @@ urlFallbackLoop: ...@@ -624,9 +624,9 @@ urlFallbackLoop:
Message: upstreamMsg, Message: upstreamMsg,
Detail: getUpstreamDetail(respBody), Detail: getUpstreamDetail(respBody),
}) })
log.Printf("%s status=%d retry=%d/%d body=%s", p.prefix, resp.StatusCode, attempt, antigravityMaxRetries, truncateForLog(respBody, 500)) logger.LegacyPrintf("service.antigravity_gateway", "%s status=%d retry=%d/%d body=%s", p.prefix, resp.StatusCode, attempt, antigravityMaxRetries, truncateForLog(respBody, 500))
if !sleepAntigravityBackoffWithContext(p.ctx, attempt) { if !sleepAntigravityBackoffWithContext(p.ctx, attempt) {
log.Printf("%s status=context_canceled_during_backoff", p.prefix) logger.LegacyPrintf("service.antigravity_gateway", "%s status=context_canceled_during_backoff", p.prefix)
return nil, p.ctx.Err() return nil, p.ctx.Err()
} }
continue continue
...@@ -924,14 +924,14 @@ func (s *AntigravityGatewayService) TestConnection(ctx context.Context, account ...@@ -924,14 +924,14 @@ func (s *AntigravityGatewayService) TestConnection(ctx context.Context, account
} }
// 调试日志:Test 请求信息 // 调试日志:Test 请求信息
log.Printf("[antigravity-Test] account=%s request_size=%d url=%s", account.Name, len(requestBody), req.URL.String()) logger.LegacyPrintf("service.antigravity_gateway", "[antigravity-Test] account=%s request_size=%d url=%s", account.Name, len(requestBody), req.URL.String())
// 发送请求 // 发送请求
resp, err := s.httpUpstream.Do(req, proxyURL, account.ID, account.Concurrency) resp, err := s.httpUpstream.Do(req, proxyURL, account.ID, account.Concurrency)
if err != nil { if err != nil {
lastErr = fmt.Errorf("请求失败: %w", err) lastErr = fmt.Errorf("请求失败: %w", err)
if shouldAntigravityFallbackToNextURL(err, 0) && urlIdx < len(availableURLs)-1 { if shouldAntigravityFallbackToNextURL(err, 0) && urlIdx < len(availableURLs)-1 {
log.Printf("[antigravity-Test] URL fallback: %s -> %s", baseURL, availableURLs[urlIdx+1]) logger.LegacyPrintf("service.antigravity_gateway", "[antigravity-Test] URL fallback: %s -> %s", baseURL, availableURLs[urlIdx+1])
continue continue
} }
return nil, lastErr return nil, lastErr
...@@ -946,7 +946,7 @@ func (s *AntigravityGatewayService) TestConnection(ctx context.Context, account ...@@ -946,7 +946,7 @@ func (s *AntigravityGatewayService) TestConnection(ctx context.Context, account
// 检查是否需要 URL 降级 // 检查是否需要 URL 降级
if shouldAntigravityFallbackToNextURL(nil, resp.StatusCode) && urlIdx < len(availableURLs)-1 { if shouldAntigravityFallbackToNextURL(nil, resp.StatusCode) && urlIdx < len(availableURLs)-1 {
log.Printf("[antigravity-Test] URL fallback (HTTP %d): %s -> %s", resp.StatusCode, baseURL, availableURLs[urlIdx+1]) logger.LegacyPrintf("service.antigravity_gateway", "[antigravity-Test] URL fallback (HTTP %d): %s -> %s", resp.StatusCode, baseURL, availableURLs[urlIdx+1])
continue continue
} }
...@@ -1328,7 +1328,7 @@ func (s *AntigravityGatewayService) Forward(ctx context.Context, c *gin.Context, ...@@ -1328,7 +1328,7 @@ func (s *AntigravityGatewayService) Forward(ctx context.Context, c *gin.Context,
continue continue
} }
log.Printf("Antigravity account %d: detected signature-related 400, retrying once (%s)", account.ID, stage.name) logger.LegacyPrintf("service.antigravity_gateway", "Antigravity account %d: detected signature-related 400, retrying once (%s)", account.ID, stage.name)
retryGeminiBody, txErr := antigravity.TransformClaudeToGeminiWithOptions(&retryClaudeReq, projectID, mappedModel, s.getClaudeTransformOptions(ctx)) retryGeminiBody, txErr := antigravity.TransformClaudeToGeminiWithOptions(&retryClaudeReq, projectID, mappedModel, s.getClaudeTransformOptions(ctx))
if txErr != nil { if txErr != nil {
...@@ -1361,7 +1361,7 @@ func (s *AntigravityGatewayService) Forward(ctx context.Context, c *gin.Context, ...@@ -1361,7 +1361,7 @@ func (s *AntigravityGatewayService) Forward(ctx context.Context, c *gin.Context,
Kind: "signature_retry_request_error", Kind: "signature_retry_request_error",
Message: sanitizeUpstreamErrorMessage(retryErr.Error()), Message: sanitizeUpstreamErrorMessage(retryErr.Error()),
}) })
log.Printf("Antigravity account %d: signature retry request failed (%s): %v", account.ID, stage.name, retryErr) logger.LegacyPrintf("service.antigravity_gateway", "Antigravity account %d: signature retry request failed (%s): %v", account.ID, stage.name, retryErr)
continue continue
} }
...@@ -1380,7 +1380,7 @@ func (s *AntigravityGatewayService) Forward(ctx context.Context, c *gin.Context, ...@@ -1380,7 +1380,7 @@ func (s *AntigravityGatewayService) Forward(ctx context.Context, c *gin.Context,
if retryResp.Request != nil && retryResp.Request.URL != nil { if retryResp.Request != nil && retryResp.Request.URL != nil {
retryBaseURL = retryResp.Request.URL.Scheme + "://" + retryResp.Request.URL.Host retryBaseURL = retryResp.Request.URL.Scheme + "://" + retryResp.Request.URL.Host
} }
log.Printf("%s status=429 rate_limited base_url=%s retry_stage=%s body=%s", prefix, retryBaseURL, stage.name, truncateForLog(retryBody, 200)) logger.LegacyPrintf("service.antigravity_gateway", "%s status=429 rate_limited base_url=%s retry_stage=%s body=%s", prefix, retryBaseURL, stage.name, truncateForLog(retryBody, 200))
} }
kind := "signature_retry" kind := "signature_retry"
if strings.TrimSpace(stage.name) != "" { if strings.TrimSpace(stage.name) != "" {
...@@ -1433,7 +1433,7 @@ func (s *AntigravityGatewayService) Forward(ctx context.Context, c *gin.Context, ...@@ -1433,7 +1433,7 @@ func (s *AntigravityGatewayService) Forward(ctx context.Context, c *gin.Context,
upstreamDetail := s.getUpstreamErrorDetail(respBody) upstreamDetail := s.getUpstreamErrorDetail(respBody)
logBody, maxBytes := s.getLogConfig() logBody, maxBytes := s.getLogConfig()
if logBody { if logBody {
log.Printf("%s status=400 prompt_too_long=true upstream_message=%q request_id=%s body=%s", prefix, upstreamMsg, resp.Header.Get("x-request-id"), truncateForLog(respBody, maxBytes)) logger.LegacyPrintf("service.antigravity_gateway", "%s status=400 prompt_too_long=true upstream_message=%q request_id=%s body=%s", prefix, upstreamMsg, resp.Header.Get("x-request-id"), truncateForLog(respBody, maxBytes))
} }
appendOpsUpstreamError(c, OpsUpstreamErrorEvent{ appendOpsUpstreamError(c, OpsUpstreamErrorEvent{
Platform: account.Platform, Platform: account.Platform,
...@@ -1487,7 +1487,7 @@ func (s *AntigravityGatewayService) Forward(ctx context.Context, c *gin.Context, ...@@ -1487,7 +1487,7 @@ func (s *AntigravityGatewayService) Forward(ctx context.Context, c *gin.Context,
// 客户端要求流式,直接透传转换 // 客户端要求流式,直接透传转换
streamRes, err := s.handleClaudeStreamingResponse(c, resp, startTime, originalModel) streamRes, err := s.handleClaudeStreamingResponse(c, resp, startTime, originalModel)
if err != nil { if err != nil {
log.Printf("%s status=stream_error error=%v", prefix, err) logger.LegacyPrintf("service.antigravity_gateway", "%s status=stream_error error=%v", prefix, err)
return nil, err return nil, err
} }
usage = streamRes.usage usage = streamRes.usage
...@@ -1497,7 +1497,7 @@ func (s *AntigravityGatewayService) Forward(ctx context.Context, c *gin.Context, ...@@ -1497,7 +1497,7 @@ func (s *AntigravityGatewayService) Forward(ctx context.Context, c *gin.Context,
// 客户端要求非流式,收集流式响应后转换返回 // 客户端要求非流式,收集流式响应后转换返回
streamRes, err := s.handleClaudeStreamToNonStreaming(c, resp, startTime, originalModel) streamRes, err := s.handleClaudeStreamToNonStreaming(c, resp, startTime, originalModel)
if err != nil { if err != nil {
log.Printf("%s status=stream_collect_error error=%v", prefix, err) logger.LegacyPrintf("service.antigravity_gateway", "%s status=stream_collect_error error=%v", prefix, err)
return nil, err return nil, err
} }
usage = streamRes.usage usage = streamRes.usage
...@@ -1889,9 +1889,9 @@ func (s *AntigravityGatewayService) ForwardGemini(ctx context.Context, c *gin.Co ...@@ -1889,9 +1889,9 @@ func (s *AntigravityGatewayService) ForwardGemini(ctx context.Context, c *gin.Co
// 清理 Schema // 清理 Schema
if cleanedBody, err := cleanGeminiRequest(injectedBody); err == nil { if cleanedBody, err := cleanGeminiRequest(injectedBody); err == nil {
injectedBody = cleanedBody injectedBody = cleanedBody
log.Printf("[Antigravity] Cleaned request schema in forwarded request for account %s", account.Name) logger.LegacyPrintf("service.antigravity_gateway", "[Antigravity] Cleaned request schema in forwarded request for account %s", account.Name)
} else { } else {
log.Printf("[Antigravity] Failed to clean schema: %v", err) logger.LegacyPrintf("service.antigravity_gateway", "[Antigravity] Failed to clean schema: %v", err)
} }
// 包装请求 // 包装请求
...@@ -1953,7 +1953,7 @@ func (s *AntigravityGatewayService) ForwardGemini(ctx context.Context, c *gin.Co ...@@ -1953,7 +1953,7 @@ func (s *AntigravityGatewayService) ForwardGemini(ctx context.Context, c *gin.Co
isModelNotFoundError(resp.StatusCode, respBody) { isModelNotFoundError(resp.StatusCode, respBody) {
fallbackModel := s.settingService.GetFallbackModel(ctx, PlatformAntigravity) fallbackModel := s.settingService.GetFallbackModel(ctx, PlatformAntigravity)
if fallbackModel != "" && fallbackModel != mappedModel { if fallbackModel != "" && fallbackModel != mappedModel {
log.Printf("[Antigravity] Model not found (%s), retrying with fallback model %s (account: %s)", mappedModel, fallbackModel, account.Name) logger.LegacyPrintf("service.antigravity_gateway", "[Antigravity] Model not found (%s), retrying with fallback model %s (account: %s)", mappedModel, fallbackModel, account.Name)
fallbackWrapped, err := s.wrapV1InternalRequest(projectID, fallbackModel, injectedBody) fallbackWrapped, err := s.wrapV1InternalRequest(projectID, fallbackModel, injectedBody)
if err == nil { if err == nil {
...@@ -2020,7 +2020,7 @@ func (s *AntigravityGatewayService) ForwardGemini(ctx context.Context, c *gin.Co ...@@ -2020,7 +2020,7 @@ func (s *AntigravityGatewayService) ForwardGemini(ctx context.Context, c *gin.Co
Message: upstreamMsg, Message: upstreamMsg,
Detail: upstreamDetail, Detail: upstreamDetail,
}) })
log.Printf("[antigravity-Forward] upstream error status=%d body=%s", resp.StatusCode, truncateForLog(unwrappedForOps, 500)) logger.LegacyPrintf("service.antigravity_gateway", "[antigravity-Forward] upstream error status=%d body=%s", resp.StatusCode, truncateForLog(unwrappedForOps, 500))
c.Data(resp.StatusCode, contentType, unwrappedForOps) c.Data(resp.StatusCode, contentType, unwrappedForOps)
return nil, fmt.Errorf("antigravity upstream error: %d", resp.StatusCode) return nil, fmt.Errorf("antigravity upstream error: %d", resp.StatusCode)
} }
...@@ -2039,7 +2039,7 @@ handleSuccess: ...@@ -2039,7 +2039,7 @@ handleSuccess:
// 客户端要求流式,直接透传 // 客户端要求流式,直接透传
streamRes, err := s.handleGeminiStreamingResponse(c, resp, startTime) streamRes, err := s.handleGeminiStreamingResponse(c, resp, startTime)
if err != nil { if err != nil {
log.Printf("%s status=stream_error error=%v", prefix, err) logger.LegacyPrintf("service.antigravity_gateway", "%s status=stream_error error=%v", prefix, err)
return nil, err return nil, err
} }
usage = streamRes.usage usage = streamRes.usage
...@@ -2049,7 +2049,7 @@ handleSuccess: ...@@ -2049,7 +2049,7 @@ handleSuccess:
// 客户端要求非流式,收集流式响应后返回 // 客户端要求非流式,收集流式响应后返回
streamRes, err := s.handleGeminiStreamToNonStreaming(c, resp, startTime) streamRes, err := s.handleGeminiStreamToNonStreaming(c, resp, startTime)
if err != nil { if err != nil {
log.Printf("%s status=stream_collect_error error=%v", prefix, err) logger.LegacyPrintf("service.antigravity_gateway", "%s status=stream_collect_error error=%v", prefix, err)
return nil, err return nil, err
} }
usage = streamRes.usage usage = streamRes.usage
...@@ -2128,13 +2128,13 @@ func setModelRateLimitByModelName(ctx context.Context, repo AccountRepository, a ...@@ -2128,13 +2128,13 @@ func setModelRateLimitByModelName(ctx context.Context, repo AccountRepository, a
} }
// 直接使用官方模型 ID 作为 key,不再转换为 scope // 直接使用官方模型 ID 作为 key,不再转换为 scope
if err := repo.SetModelRateLimit(ctx, accountID, modelName, resetAt); err != nil { if err := repo.SetModelRateLimit(ctx, accountID, modelName, resetAt); err != nil {
log.Printf("%s status=%d model_rate_limit_failed model=%s error=%v", prefix, statusCode, modelName, err) logger.LegacyPrintf("service.antigravity_gateway", "%s status=%d model_rate_limit_failed model=%s error=%v", prefix, statusCode, modelName, err)
return false return false
} }
if afterSmartRetry { if afterSmartRetry {
log.Printf("%s status=%d model_rate_limited_after_smart_retry model=%s account=%d reset_in=%v", prefix, statusCode, modelName, accountID, time.Until(resetAt).Truncate(time.Second)) logger.LegacyPrintf("service.antigravity_gateway", "%s status=%d model_rate_limited_after_smart_retry model=%s account=%d reset_in=%v", prefix, statusCode, modelName, accountID, time.Until(resetAt).Truncate(time.Second))
} else { } else {
log.Printf("%s status=%d model_rate_limited model=%s account=%d reset_in=%v", prefix, statusCode, modelName, accountID, time.Until(resetAt).Truncate(time.Second)) logger.LegacyPrintf("service.antigravity_gateway", "%s status=%d model_rate_limited model=%s account=%d reset_in=%v", prefix, statusCode, modelName, accountID, time.Until(resetAt).Truncate(time.Second))
} }
return true return true
} }
...@@ -2241,7 +2241,7 @@ func parseAntigravitySmartRetryInfo(body []byte) *antigravitySmartRetryInfo { ...@@ -2241,7 +2241,7 @@ func parseAntigravitySmartRetryInfo(body []byte) *antigravitySmartRetryInfo {
// 例如: "0.5s", "10s", "4m50s", "1h30m", "200ms" 等 // 例如: "0.5s", "10s", "4m50s", "1h30m", "200ms" 等
dur, err := time.ParseDuration(delay) dur, err := time.ParseDuration(delay)
if err != nil { if err != nil {
log.Printf("[Antigravity] failed to parse retryDelay: %s error=%v", delay, err) logger.LegacyPrintf("service.antigravity_gateway", "[Antigravity] failed to parse retryDelay: %s error=%v", delay, err)
continue continue
} }
retryDelay = dur retryDelay = dur
...@@ -2342,7 +2342,7 @@ func (s *AntigravityGatewayService) handleModelRateLimit(p *handleModelRateLimit ...@@ -2342,7 +2342,7 @@ func (s *AntigravityGatewayService) handleModelRateLimit(p *handleModelRateLimit
// < antigravityRateLimitThreshold: 等待后重试 // < antigravityRateLimitThreshold: 等待后重试
if info.RetryDelay < antigravityRateLimitThreshold { if info.RetryDelay < antigravityRateLimitThreshold {
log.Printf("%s status=%d model_rate_limit_wait model=%s wait=%v", logger.LegacyPrintf("service.antigravity_gateway", "%s status=%d model_rate_limit_wait model=%s wait=%v",
p.prefix, p.statusCode, info.ModelName, info.RetryDelay) p.prefix, p.statusCode, info.ModelName, info.RetryDelay)
return &handleModelRateLimitResult{ return &handleModelRateLimitResult{
Handled: true, Handled: true,
...@@ -2367,12 +2367,12 @@ func (s *AntigravityGatewayService) handleModelRateLimit(p *handleModelRateLimit ...@@ -2367,12 +2367,12 @@ func (s *AntigravityGatewayService) handleModelRateLimit(p *handleModelRateLimit
// setModelRateLimitAndClearSession 设置模型限流并清除粘性会话 // setModelRateLimitAndClearSession 设置模型限流并清除粘性会话
func (s *AntigravityGatewayService) setModelRateLimitAndClearSession(p *handleModelRateLimitParams, info *antigravitySmartRetryInfo) { func (s *AntigravityGatewayService) setModelRateLimitAndClearSession(p *handleModelRateLimitParams, info *antigravitySmartRetryInfo) {
resetAt := time.Now().Add(info.RetryDelay) resetAt := time.Now().Add(info.RetryDelay)
log.Printf("%s status=%d model_rate_limited model=%s account=%d reset_in=%v", logger.LegacyPrintf("service.antigravity_gateway", "%s status=%d model_rate_limited model=%s account=%d reset_in=%v",
p.prefix, p.statusCode, info.ModelName, p.account.ID, info.RetryDelay) p.prefix, p.statusCode, info.ModelName, p.account.ID, info.RetryDelay)
// 设置模型限流状态(数据库) // 设置模型限流状态(数据库)
if err := s.accountRepo.SetModelRateLimit(p.ctx, p.account.ID, info.ModelName, resetAt); err != nil { if err := s.accountRepo.SetModelRateLimit(p.ctx, p.account.ID, info.ModelName, resetAt); err != nil {
log.Printf("%s model_rate_limit_failed model=%s error=%v", p.prefix, info.ModelName, err) logger.LegacyPrintf("service.antigravity_gateway", "%s model_rate_limit_failed model=%s error=%v", p.prefix, info.ModelName, err)
} }
// 立即更新 Redis 快照中账号的限流状态,避免并发请求重复选中 // 立即更新 Redis 快照中账号的限流状态,避免并发请求重复选中
...@@ -2408,7 +2408,7 @@ func (s *AntigravityGatewayService) updateAccountModelRateLimitInCache(ctx conte ...@@ -2408,7 +2408,7 @@ func (s *AntigravityGatewayService) updateAccountModelRateLimitInCache(ctx conte
// 更新 Redis 快照 // 更新 Redis 快照
if err := s.schedulerSnapshot.UpdateAccountInCache(ctx, account); err != nil { if err := s.schedulerSnapshot.UpdateAccountInCache(ctx, account); err != nil {
log.Printf("[antigravity-Forward] cache_update_failed account=%d model=%s err=%v", account.ID, modelKey, err) logger.LegacyPrintf("service.antigravity_gateway", "[antigravity-Forward] cache_update_failed account=%d model=%s err=%v", account.ID, modelKey, err)
} }
} }
...@@ -2447,7 +2447,7 @@ func (s *AntigravityGatewayService) handleUpstreamError( ...@@ -2447,7 +2447,7 @@ func (s *AntigravityGatewayService) handleUpstreamError(
// 429:尝试解析模型级限流,解析失败时兜底为账号级限流 // 429:尝试解析模型级限流,解析失败时兜底为账号级限流
if statusCode == 429 { if statusCode == 429 {
if logBody, maxBytes := s.getLogConfig(); logBody { if logBody, maxBytes := s.getLogConfig(); logBody {
log.Printf("[Antigravity-Debug] 429 response body: %s", truncateString(string(body), maxBytes)) logger.LegacyPrintf("service.antigravity_gateway", "[Antigravity-Debug] 429 response body: %s", truncateString(string(body), maxBytes))
} }
resetAt := ParseGeminiRateLimitResetTime(body) resetAt := ParseGeminiRateLimitResetTime(body)
...@@ -2458,9 +2458,9 @@ func (s *AntigravityGatewayService) handleUpstreamError( ...@@ -2458,9 +2458,9 @@ func (s *AntigravityGatewayService) handleUpstreamError(
if modelKey != "" { if modelKey != "" {
ra := s.resolveResetTime(resetAt, defaultDur) ra := s.resolveResetTime(resetAt, defaultDur)
if err := s.accountRepo.SetModelRateLimit(ctx, account.ID, modelKey, ra); err != nil { if err := s.accountRepo.SetModelRateLimit(ctx, account.ID, modelKey, ra); err != nil {
log.Printf("%s status=429 model_rate_limit_set_failed model=%s error=%v", prefix, modelKey, err) logger.LegacyPrintf("service.antigravity_gateway", "%s status=429 model_rate_limit_set_failed model=%s error=%v", prefix, modelKey, err)
} else { } else {
log.Printf("%s status=429 model_rate_limited model=%s account=%d reset_at=%v reset_in=%v", logger.LegacyPrintf("service.antigravity_gateway", "%s status=429 model_rate_limited model=%s account=%d reset_at=%v reset_in=%v",
prefix, modelKey, account.ID, ra.Format("15:04:05"), time.Until(ra).Truncate(time.Second)) prefix, modelKey, account.ID, ra.Format("15:04:05"), time.Until(ra).Truncate(time.Second))
s.updateAccountModelRateLimitInCache(ctx, account, modelKey, ra) s.updateAccountModelRateLimitInCache(ctx, account, modelKey, ra)
} }
...@@ -2469,10 +2469,10 @@ func (s *AntigravityGatewayService) handleUpstreamError( ...@@ -2469,10 +2469,10 @@ func (s *AntigravityGatewayService) handleUpstreamError(
// 无法解析模型 key,兜底为账号级限流 // 无法解析模型 key,兜底为账号级限流
ra := s.resolveResetTime(resetAt, defaultDur) ra := s.resolveResetTime(resetAt, defaultDur)
log.Printf("%s status=429 rate_limited account=%d reset_at=%v reset_in=%v (fallback)", logger.LegacyPrintf("service.antigravity_gateway", "%s status=429 rate_limited account=%d reset_at=%v reset_in=%v (fallback)",
prefix, account.ID, ra.Format("15:04:05"), time.Until(ra).Truncate(time.Second)) prefix, account.ID, ra.Format("15:04:05"), time.Until(ra).Truncate(time.Second))
if err := s.accountRepo.SetRateLimited(ctx, account.ID, ra); err != nil { if err := s.accountRepo.SetRateLimited(ctx, account.ID, ra); err != nil {
log.Printf("%s status=429 rate_limit_set_failed account=%d error=%v", prefix, account.ID, err) logger.LegacyPrintf("service.antigravity_gateway", "%s status=429 rate_limit_set_failed account=%d error=%v", prefix, account.ID, err)
} }
return nil return nil
} }
...@@ -2482,7 +2482,7 @@ func (s *AntigravityGatewayService) handleUpstreamError( ...@@ -2482,7 +2482,7 @@ func (s *AntigravityGatewayService) handleUpstreamError(
} }
shouldDisable := s.rateLimitService.HandleUpstreamError(ctx, account, statusCode, headers, body) shouldDisable := s.rateLimitService.HandleUpstreamError(ctx, account, statusCode, headers, body)
if shouldDisable { if shouldDisable {
log.Printf("%s status=%d marked_error", prefix, statusCode) logger.LegacyPrintf("service.antigravity_gateway", "%s status=%d marked_error", prefix, statusCode)
} }
return nil return nil
} }
...@@ -2556,18 +2556,18 @@ func (cw *antigravityClientWriter) Disconnected() bool { return cw.disconnected ...@@ -2556,18 +2556,18 @@ func (cw *antigravityClientWriter) Disconnected() bool { return cw.disconnected
func (cw *antigravityClientWriter) markDisconnected() { func (cw *antigravityClientWriter) markDisconnected() {
cw.disconnected = true cw.disconnected = true
log.Printf("Client disconnected during streaming (%s), continuing to drain upstream for billing", cw.prefix) logger.LegacyPrintf("service.antigravity_gateway", "Client disconnected during streaming (%s), continuing to drain upstream for billing", cw.prefix)
} }
// handleStreamReadError 处理上游读取错误的通用逻辑。 // handleStreamReadError 处理上游读取错误的通用逻辑。
// 返回 (clientDisconnect, handled):handled=true 表示错误已处理,调用方应返回已收集的 usage。 // 返回 (clientDisconnect, handled):handled=true 表示错误已处理,调用方应返回已收集的 usage。
func handleStreamReadError(err error, clientDisconnected bool, prefix string) (disconnect bool, handled bool) { func handleStreamReadError(err error, clientDisconnected bool, prefix string) (disconnect bool, handled bool) {
if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
log.Printf("Context canceled during streaming (%s), returning collected usage", prefix) logger.LegacyPrintf("service.antigravity_gateway", "Context canceled during streaming (%s), returning collected usage", prefix)
return true, true return true, true
} }
if clientDisconnected { if clientDisconnected {
log.Printf("Upstream read error after client disconnect (%s): %v, returning collected usage", prefix, err) logger.LegacyPrintf("service.antigravity_gateway", "Upstream read error after client disconnect (%s): %v, returning collected usage", prefix, err)
return true, true return true, true
} }
return false, false return false, false
...@@ -2672,7 +2672,7 @@ func (s *AntigravityGatewayService) handleGeminiStreamingResponse(c *gin.Context ...@@ -2672,7 +2672,7 @@ func (s *AntigravityGatewayService) handleGeminiStreamingResponse(c *gin.Context
return &antigravityStreamResult{usage: usage, firstTokenMs: firstTokenMs, clientDisconnect: disconnect}, nil return &antigravityStreamResult{usage: usage, firstTokenMs: firstTokenMs, clientDisconnect: disconnect}, nil
} }
if errors.Is(ev.err, bufio.ErrTooLong) { if errors.Is(ev.err, bufio.ErrTooLong) {
log.Printf("SSE line too long (antigravity): max_size=%d error=%v", maxLineSize, ev.err) logger.LegacyPrintf("service.antigravity_gateway", "SSE line too long (antigravity): max_size=%d error=%v", maxLineSize, ev.err)
sendErrorEvent("response_too_large") sendErrorEvent("response_too_large")
return &antigravityStreamResult{usage: usage, firstTokenMs: firstTokenMs}, ev.err return &antigravityStreamResult{usage: usage, firstTokenMs: firstTokenMs}, ev.err
} }
...@@ -2705,10 +2705,10 @@ func (s *AntigravityGatewayService) handleGeminiStreamingResponse(c *gin.Context ...@@ -2705,10 +2705,10 @@ func (s *AntigravityGatewayService) handleGeminiStreamingResponse(c *gin.Context
if candidates, ok := parsed["candidates"].([]any); ok && len(candidates) > 0 { if candidates, ok := parsed["candidates"].([]any); ok && len(candidates) > 0 {
if cand, ok := candidates[0].(map[string]any); ok { if cand, ok := candidates[0].(map[string]any); ok {
if fr, ok := cand["finishReason"].(string); ok && fr == "MALFORMED_FUNCTION_CALL" { if fr, ok := cand["finishReason"].(string); ok && fr == "MALFORMED_FUNCTION_CALL" {
log.Printf("[Antigravity] MALFORMED_FUNCTION_CALL detected in forward stream") logger.LegacyPrintf("service.antigravity_gateway", "[Antigravity] MALFORMED_FUNCTION_CALL detected in forward stream")
if content, ok := cand["content"]; ok { if content, ok := cand["content"]; ok {
if b, err := json.Marshal(content); err == nil { if b, err := json.Marshal(content); err == nil {
log.Printf("[Antigravity] Malformed content: %s", string(b)) logger.LegacyPrintf("service.antigravity_gateway", "[Antigravity] Malformed content: %s", string(b))
} }
} }
} }
...@@ -2733,10 +2733,10 @@ func (s *AntigravityGatewayService) handleGeminiStreamingResponse(c *gin.Context ...@@ -2733,10 +2733,10 @@ func (s *AntigravityGatewayService) handleGeminiStreamingResponse(c *gin.Context
continue continue
} }
if cw.Disconnected() { if cw.Disconnected() {
log.Printf("Upstream timeout after client disconnect (antigravity gemini), returning collected usage") logger.LegacyPrintf("service.antigravity_gateway", "Upstream timeout after client disconnect (antigravity gemini), returning collected usage")
return &antigravityStreamResult{usage: usage, firstTokenMs: firstTokenMs, clientDisconnect: true}, nil return &antigravityStreamResult{usage: usage, firstTokenMs: firstTokenMs, clientDisconnect: true}, nil
} }
log.Printf("Stream data interval timeout (antigravity)") logger.LegacyPrintf("service.antigravity_gateway", "Stream data interval timeout (antigravity)")
sendErrorEvent("stream_timeout") sendErrorEvent("stream_timeout")
return &antigravityStreamResult{usage: usage, firstTokenMs: firstTokenMs}, fmt.Errorf("stream data interval timeout") return &antigravityStreamResult{usage: usage, firstTokenMs: firstTokenMs}, fmt.Errorf("stream data interval timeout")
} }
...@@ -2819,7 +2819,7 @@ func (s *AntigravityGatewayService) handleGeminiStreamToNonStreaming(c *gin.Cont ...@@ -2819,7 +2819,7 @@ func (s *AntigravityGatewayService) handleGeminiStreamToNonStreaming(c *gin.Cont
} }
if ev.err != nil { if ev.err != nil {
if errors.Is(ev.err, bufio.ErrTooLong) { if errors.Is(ev.err, bufio.ErrTooLong) {
log.Printf("SSE line too long (antigravity non-stream): max_size=%d error=%v", maxLineSize, ev.err) logger.LegacyPrintf("service.antigravity_gateway", "SSE line too long (antigravity non-stream): max_size=%d error=%v", maxLineSize, ev.err)
} }
return nil, ev.err return nil, ev.err
} }
...@@ -2864,10 +2864,10 @@ func (s *AntigravityGatewayService) handleGeminiStreamToNonStreaming(c *gin.Cont ...@@ -2864,10 +2864,10 @@ func (s *AntigravityGatewayService) handleGeminiStreamToNonStreaming(c *gin.Cont
if candidates, ok := parsed["candidates"].([]any); ok && len(candidates) > 0 { if candidates, ok := parsed["candidates"].([]any); ok && len(candidates) > 0 {
if cand, ok := candidates[0].(map[string]any); ok { if cand, ok := candidates[0].(map[string]any); ok {
if fr, ok := cand["finishReason"].(string); ok && fr == "MALFORMED_FUNCTION_CALL" { if fr, ok := cand["finishReason"].(string); ok && fr == "MALFORMED_FUNCTION_CALL" {
log.Printf("[Antigravity] MALFORMED_FUNCTION_CALL detected in forward non-stream collect") logger.LegacyPrintf("service.antigravity_gateway", "[Antigravity] MALFORMED_FUNCTION_CALL detected in forward non-stream collect")
if content, ok := cand["content"]; ok { if content, ok := cand["content"]; ok {
if b, err := json.Marshal(content); err == nil { if b, err := json.Marshal(content); err == nil {
log.Printf("[Antigravity] Malformed content: %s", string(b)) logger.LegacyPrintf("service.antigravity_gateway", "[Antigravity] Malformed content: %s", string(b))
} }
} }
} }
...@@ -2894,7 +2894,7 @@ func (s *AntigravityGatewayService) handleGeminiStreamToNonStreaming(c *gin.Cont ...@@ -2894,7 +2894,7 @@ func (s *AntigravityGatewayService) handleGeminiStreamToNonStreaming(c *gin.Cont
if time.Since(lastRead) < streamInterval { if time.Since(lastRead) < streamInterval {
continue continue
} }
log.Printf("Stream data interval timeout (antigravity non-stream)") logger.LegacyPrintf("service.antigravity_gateway", "Stream data interval timeout (antigravity non-stream)")
return nil, fmt.Errorf("stream data interval timeout") return nil, fmt.Errorf("stream data interval timeout")
} }
} }
...@@ -2905,7 +2905,7 @@ returnResponse: ...@@ -2905,7 +2905,7 @@ returnResponse:
// 处理空响应情况 // 处理空响应情况
if last == nil && lastWithParts == nil { if last == nil && lastWithParts == nil {
log.Printf("[antigravity-Forward] warning: empty stream response, no valid chunks received") logger.LegacyPrintf("service.antigravity_gateway", "[antigravity-Forward] warning: empty stream response, no valid chunks received")
} }
// 如果收集到了图片 parts,需要合并到最终响应中 // 如果收集到了图片 parts,需要合并到最终响应中
...@@ -3120,7 +3120,7 @@ func (s *AntigravityGatewayService) writeMappedClaudeError(c *gin.Context, accou ...@@ -3120,7 +3120,7 @@ func (s *AntigravityGatewayService) writeMappedClaudeError(c *gin.Context, accou
// 记录上游错误详情便于排障(可选:由配置控制;不回显到客户端) // 记录上游错误详情便于排障(可选:由配置控制;不回显到客户端)
if logBody { if logBody {
log.Printf("[antigravity-Forward] upstream_error status=%d body=%s", upstreamStatus, truncateForLog(body, maxBytes)) logger.LegacyPrintf("service.antigravity_gateway", "[antigravity-Forward] upstream_error status=%d body=%s", upstreamStatus, truncateForLog(body, maxBytes))
} }
var statusCode int var statusCode int
...@@ -3262,7 +3262,7 @@ func (s *AntigravityGatewayService) handleClaudeStreamToNonStreaming(c *gin.Cont ...@@ -3262,7 +3262,7 @@ func (s *AntigravityGatewayService) handleClaudeStreamToNonStreaming(c *gin.Cont
} }
if ev.err != nil { if ev.err != nil {
if errors.Is(ev.err, bufio.ErrTooLong) { if errors.Is(ev.err, bufio.ErrTooLong) {
log.Printf("SSE line too long (antigravity claude non-stream): max_size=%d error=%v", maxLineSize, ev.err) logger.LegacyPrintf("service.antigravity_gateway", "SSE line too long (antigravity claude non-stream): max_size=%d error=%v", maxLineSize, ev.err)
} }
return nil, ev.err return nil, ev.err
} }
...@@ -3311,7 +3311,7 @@ func (s *AntigravityGatewayService) handleClaudeStreamToNonStreaming(c *gin.Cont ...@@ -3311,7 +3311,7 @@ func (s *AntigravityGatewayService) handleClaudeStreamToNonStreaming(c *gin.Cont
if time.Since(lastRead) < streamInterval { if time.Since(lastRead) < streamInterval {
continue continue
} }
log.Printf("Stream data interval timeout (antigravity claude non-stream)") logger.LegacyPrintf("service.antigravity_gateway", "Stream data interval timeout (antigravity claude non-stream)")
return nil, fmt.Errorf("stream data interval timeout") return nil, fmt.Errorf("stream data interval timeout")
} }
} }
...@@ -3322,7 +3322,7 @@ returnResponse: ...@@ -3322,7 +3322,7 @@ returnResponse:
// 处理空响应情况 // 处理空响应情况
if last == nil && lastWithParts == nil { if last == nil && lastWithParts == nil {
log.Printf("[antigravity-Forward] warning: empty stream response, no valid chunks received") logger.LegacyPrintf("service.antigravity_gateway", "[antigravity-Forward] warning: empty stream response, no valid chunks received")
return nil, s.writeClaudeError(c, http.StatusBadGateway, "upstream_error", "Empty response from upstream") return nil, s.writeClaudeError(c, http.StatusBadGateway, "upstream_error", "Empty response from upstream")
} }
...@@ -3340,7 +3340,7 @@ returnResponse: ...@@ -3340,7 +3340,7 @@ returnResponse:
// 转换 Gemini 响应为 Claude 格式 // 转换 Gemini 响应为 Claude 格式
claudeResp, agUsage, err := antigravity.TransformGeminiToClaude(geminiBody, originalModel) claudeResp, agUsage, err := antigravity.TransformGeminiToClaude(geminiBody, originalModel)
if err != nil { if err != nil {
log.Printf("[antigravity-Forward] transform_error error=%v body=%s", err, string(geminiBody)) logger.LegacyPrintf("service.antigravity_gateway", "[antigravity-Forward] transform_error error=%v body=%s", err, string(geminiBody))
return nil, s.writeClaudeError(c, http.StatusBadGateway, "upstream_error", "Failed to parse upstream response") return nil, s.writeClaudeError(c, http.StatusBadGateway, "upstream_error", "Failed to parse upstream response")
} }
...@@ -3475,7 +3475,7 @@ func (s *AntigravityGatewayService) handleClaudeStreamingResponse(c *gin.Context ...@@ -3475,7 +3475,7 @@ func (s *AntigravityGatewayService) handleClaudeStreamingResponse(c *gin.Context
return &antigravityStreamResult{usage: finishUsage(), firstTokenMs: firstTokenMs, clientDisconnect: disconnect}, nil return &antigravityStreamResult{usage: finishUsage(), firstTokenMs: firstTokenMs, clientDisconnect: disconnect}, nil
} }
if errors.Is(ev.err, bufio.ErrTooLong) { if errors.Is(ev.err, bufio.ErrTooLong) {
log.Printf("SSE line too long (antigravity): max_size=%d error=%v", maxLineSize, ev.err) logger.LegacyPrintf("service.antigravity_gateway", "SSE line too long (antigravity): max_size=%d error=%v", maxLineSize, ev.err)
sendErrorEvent("response_too_large") sendErrorEvent("response_too_large")
return &antigravityStreamResult{usage: convertUsage(nil), firstTokenMs: firstTokenMs}, ev.err return &antigravityStreamResult{usage: convertUsage(nil), firstTokenMs: firstTokenMs}, ev.err
} }
...@@ -3499,10 +3499,10 @@ func (s *AntigravityGatewayService) handleClaudeStreamingResponse(c *gin.Context ...@@ -3499,10 +3499,10 @@ func (s *AntigravityGatewayService) handleClaudeStreamingResponse(c *gin.Context
continue continue
} }
if cw.Disconnected() { if cw.Disconnected() {
log.Printf("Upstream timeout after client disconnect (antigravity claude), returning collected usage") logger.LegacyPrintf("service.antigravity_gateway", "Upstream timeout after client disconnect (antigravity claude), returning collected usage")
return &antigravityStreamResult{usage: finishUsage(), firstTokenMs: firstTokenMs, clientDisconnect: true}, nil return &antigravityStreamResult{usage: finishUsage(), firstTokenMs: firstTokenMs, clientDisconnect: true}, nil
} }
log.Printf("Stream data interval timeout (antigravity)") logger.LegacyPrintf("service.antigravity_gateway", "Stream data interval timeout (antigravity)")
sendErrorEvent("stream_timeout") sendErrorEvent("stream_timeout")
return &antigravityStreamResult{usage: convertUsage(nil), firstTokenMs: firstTokenMs}, fmt.Errorf("stream data interval timeout") return &antigravityStreamResult{usage: convertUsage(nil), firstTokenMs: firstTokenMs}, fmt.Errorf("stream data interval timeout")
} }
...@@ -3702,7 +3702,7 @@ func (s *AntigravityGatewayService) ForwardUpstream(ctx context.Context, c *gin. ...@@ -3702,7 +3702,7 @@ func (s *AntigravityGatewayService) ForwardUpstream(ctx context.Context, c *gin.
// 发送请求 // 发送请求
resp, err := s.httpUpstream.Do(req, proxyURL, account.ID, account.Concurrency) resp, err := s.httpUpstream.Do(req, proxyURL, account.ID, account.Concurrency)
if err != nil { if err != nil {
log.Printf("%s upstream request failed: %v", prefix, err) logger.LegacyPrintf("service.antigravity_gateway", "%s upstream request failed: %v", prefix, err)
return nil, fmt.Errorf("upstream request failed: %w", err) return nil, fmt.Errorf("upstream request failed: %w", err)
} }
defer func() { _ = resp.Body.Close() }() defer func() { _ = resp.Body.Close() }()
...@@ -3760,7 +3760,7 @@ func (s *AntigravityGatewayService) ForwardUpstream(ctx context.Context, c *gin. ...@@ -3760,7 +3760,7 @@ func (s *AntigravityGatewayService) ForwardUpstream(ctx context.Context, c *gin.
// 构建计费结果 // 构建计费结果
duration := time.Since(startTime) duration := time.Since(startTime)
log.Printf("%s status=success duration_ms=%d", prefix, duration.Milliseconds()) logger.LegacyPrintf("service.antigravity_gateway", "%s status=success duration_ms=%d", prefix, duration.Milliseconds())
return &ForwardResult{ return &ForwardResult{
Model: billingModel, Model: billingModel,
...@@ -3846,7 +3846,7 @@ func (s *AntigravityGatewayService) streamUpstreamResponse(c *gin.Context, resp ...@@ -3846,7 +3846,7 @@ func (s *AntigravityGatewayService) streamUpstreamResponse(c *gin.Context, resp
if disconnect, handled := handleStreamReadError(ev.err, cw.Disconnected(), "antigravity upstream"); handled { if disconnect, handled := handleStreamReadError(ev.err, cw.Disconnected(), "antigravity upstream"); handled {
return &antigravityStreamResult{usage: usage, firstTokenMs: firstTokenMs, clientDisconnect: disconnect} return &antigravityStreamResult{usage: usage, firstTokenMs: firstTokenMs, clientDisconnect: disconnect}
} }
log.Printf("Stream read error (antigravity upstream): %v", ev.err) logger.LegacyPrintf("service.antigravity_gateway", "Stream read error (antigravity upstream): %v", ev.err)
return &antigravityStreamResult{usage: usage, firstTokenMs: firstTokenMs} return &antigravityStreamResult{usage: usage, firstTokenMs: firstTokenMs}
} }
...@@ -3870,10 +3870,10 @@ func (s *AntigravityGatewayService) streamUpstreamResponse(c *gin.Context, resp ...@@ -3870,10 +3870,10 @@ func (s *AntigravityGatewayService) streamUpstreamResponse(c *gin.Context, resp
continue continue
} }
if cw.Disconnected() { if cw.Disconnected() {
log.Printf("Upstream timeout after client disconnect (antigravity upstream), returning collected usage") logger.LegacyPrintf("service.antigravity_gateway", "Upstream timeout after client disconnect (antigravity upstream), returning collected usage")
return &antigravityStreamResult{usage: usage, firstTokenMs: firstTokenMs, clientDisconnect: true} return &antigravityStreamResult{usage: usage, firstTokenMs: firstTokenMs, clientDisconnect: true}
} }
log.Printf("Stream data interval timeout (antigravity upstream)") logger.LegacyPrintf("service.antigravity_gateway", "Stream data interval timeout (antigravity upstream)")
return &antigravityStreamResult{usage: usage, firstTokenMs: firstTokenMs} return &antigravityStreamResult{usage: usage, firstTokenMs: firstTokenMs}
} }
} }
......
...@@ -7,13 +7,13 @@ import ( ...@@ -7,13 +7,13 @@ import (
"encoding/hex" "encoding/hex"
"errors" "errors"
"fmt" "fmt"
"log"
"net/mail" "net/mail"
"strings" "strings"
"time" "time"
"github.com/Wei-Shaw/sub2api/internal/config" "github.com/Wei-Shaw/sub2api/internal/config"
infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors" infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
"github.com/Wei-Shaw/sub2api/internal/pkg/logger"
"github.com/golang-jwt/jwt/v5" "github.com/golang-jwt/jwt/v5"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
...@@ -118,12 +118,12 @@ func (s *AuthService) RegisterWithVerification(ctx context.Context, email, passw ...@@ -118,12 +118,12 @@ func (s *AuthService) RegisterWithVerification(ctx context.Context, email, passw
// 验证邀请码 // 验证邀请码
redeemCode, err := s.redeemRepo.GetByCode(ctx, invitationCode) redeemCode, err := s.redeemRepo.GetByCode(ctx, invitationCode)
if err != nil { if err != nil {
log.Printf("[Auth] Invalid invitation code: %s, error: %v", invitationCode, err) logger.LegacyPrintf("service.auth", "[Auth] Invalid invitation code: %s, error: %v", invitationCode, err)
return "", nil, ErrInvitationCodeInvalid return "", nil, ErrInvitationCodeInvalid
} }
// 检查类型和状态 // 检查类型和状态
if redeemCode.Type != RedeemTypeInvitation || redeemCode.Status != StatusUnused { if redeemCode.Type != RedeemTypeInvitation || redeemCode.Status != StatusUnused {
log.Printf("[Auth] Invitation code invalid: type=%s, status=%s", redeemCode.Type, redeemCode.Status) logger.LegacyPrintf("service.auth", "[Auth] Invitation code invalid: type=%s, status=%s", redeemCode.Type, redeemCode.Status)
return "", nil, ErrInvitationCodeInvalid return "", nil, ErrInvitationCodeInvalid
} }
invitationRedeemCode = redeemCode invitationRedeemCode = redeemCode
...@@ -134,7 +134,7 @@ func (s *AuthService) RegisterWithVerification(ctx context.Context, email, passw ...@@ -134,7 +134,7 @@ func (s *AuthService) RegisterWithVerification(ctx context.Context, email, passw
// 如果邮件验证已开启但邮件服务未配置,拒绝注册 // 如果邮件验证已开启但邮件服务未配置,拒绝注册
// 这是一个配置错误,不应该允许绕过验证 // 这是一个配置错误,不应该允许绕过验证
if s.emailService == nil { if s.emailService == nil {
log.Println("[Auth] Email verification enabled but email service not configured, rejecting registration") logger.LegacyPrintf("service.auth", "%s", "[Auth] Email verification enabled but email service not configured, rejecting registration")
return "", nil, ErrServiceUnavailable return "", nil, ErrServiceUnavailable
} }
if verifyCode == "" { if verifyCode == "" {
...@@ -149,7 +149,7 @@ func (s *AuthService) RegisterWithVerification(ctx context.Context, email, passw ...@@ -149,7 +149,7 @@ func (s *AuthService) RegisterWithVerification(ctx context.Context, email, passw
// 检查邮箱是否已存在 // 检查邮箱是否已存在
existsEmail, err := s.userRepo.ExistsByEmail(ctx, email) existsEmail, err := s.userRepo.ExistsByEmail(ctx, email)
if err != nil { if err != nil {
log.Printf("[Auth] Database error checking email exists: %v", err) logger.LegacyPrintf("service.auth", "[Auth] Database error checking email exists: %v", err)
return "", nil, ErrServiceUnavailable return "", nil, ErrServiceUnavailable
} }
if existsEmail { if existsEmail {
...@@ -185,7 +185,7 @@ func (s *AuthService) RegisterWithVerification(ctx context.Context, email, passw ...@@ -185,7 +185,7 @@ func (s *AuthService) RegisterWithVerification(ctx context.Context, email, passw
if errors.Is(err, ErrEmailExists) { if errors.Is(err, ErrEmailExists) {
return "", nil, ErrEmailExists return "", nil, ErrEmailExists
} }
log.Printf("[Auth] Database error creating user: %v", err) logger.LegacyPrintf("service.auth", "[Auth] Database error creating user: %v", err)
return "", nil, ErrServiceUnavailable return "", nil, ErrServiceUnavailable
} }
...@@ -193,14 +193,14 @@ func (s *AuthService) RegisterWithVerification(ctx context.Context, email, passw ...@@ -193,14 +193,14 @@ func (s *AuthService) RegisterWithVerification(ctx context.Context, email, passw
if invitationRedeemCode != nil { if invitationRedeemCode != nil {
if err := s.redeemRepo.Use(ctx, invitationRedeemCode.ID, user.ID); err != nil { if err := s.redeemRepo.Use(ctx, invitationRedeemCode.ID, user.ID); err != nil {
// 邀请码标记失败不影响注册,只记录日志 // 邀请码标记失败不影响注册,只记录日志
log.Printf("[Auth] Failed to mark invitation code as used for user %d: %v", user.ID, err) logger.LegacyPrintf("service.auth", "[Auth] Failed to mark invitation code as used for user %d: %v", user.ID, err)
} }
} }
// 应用优惠码(如果提供且功能已启用) // 应用优惠码(如果提供且功能已启用)
if promoCode != "" && s.promoService != nil && s.settingService != nil && s.settingService.IsPromoCodeEnabled(ctx) { if promoCode != "" && s.promoService != nil && s.settingService != nil && s.settingService.IsPromoCodeEnabled(ctx) {
if err := s.promoService.ApplyPromoCode(ctx, user.ID, promoCode); err != nil { if err := s.promoService.ApplyPromoCode(ctx, user.ID, promoCode); err != nil {
// 优惠码应用失败不影响注册,只记录日志 // 优惠码应用失败不影响注册,只记录日志
log.Printf("[Auth] Failed to apply promo code for user %d: %v", user.ID, err) logger.LegacyPrintf("service.auth", "[Auth] Failed to apply promo code for user %d: %v", user.ID, err)
} else { } else {
// 重新获取用户信息以获取更新后的余额 // 重新获取用户信息以获取更新后的余额
if updatedUser, err := s.userRepo.GetByID(ctx, user.ID); err == nil { if updatedUser, err := s.userRepo.GetByID(ctx, user.ID); err == nil {
...@@ -237,7 +237,7 @@ func (s *AuthService) SendVerifyCode(ctx context.Context, email string) error { ...@@ -237,7 +237,7 @@ func (s *AuthService) SendVerifyCode(ctx context.Context, email string) error {
// 检查邮箱是否已存在 // 检查邮箱是否已存在
existsEmail, err := s.userRepo.ExistsByEmail(ctx, email) existsEmail, err := s.userRepo.ExistsByEmail(ctx, email)
if err != nil { if err != nil {
log.Printf("[Auth] Database error checking email exists: %v", err) logger.LegacyPrintf("service.auth", "[Auth] Database error checking email exists: %v", err)
return ErrServiceUnavailable return ErrServiceUnavailable
} }
if existsEmail { if existsEmail {
...@@ -260,11 +260,11 @@ func (s *AuthService) SendVerifyCode(ctx context.Context, email string) error { ...@@ -260,11 +260,11 @@ func (s *AuthService) SendVerifyCode(ctx context.Context, email string) error {
// SendVerifyCodeAsync 异步发送邮箱验证码并返回倒计时 // SendVerifyCodeAsync 异步发送邮箱验证码并返回倒计时
func (s *AuthService) SendVerifyCodeAsync(ctx context.Context, email string) (*SendVerifyCodeResult, error) { func (s *AuthService) SendVerifyCodeAsync(ctx context.Context, email string) (*SendVerifyCodeResult, error) {
log.Printf("[Auth] SendVerifyCodeAsync called for email: %s", email) logger.LegacyPrintf("service.auth", "[Auth] SendVerifyCodeAsync called for email: %s", email)
// 检查是否开放注册(默认关闭) // 检查是否开放注册(默认关闭)
if s.settingService == nil || !s.settingService.IsRegistrationEnabled(ctx) { if s.settingService == nil || !s.settingService.IsRegistrationEnabled(ctx) {
log.Println("[Auth] Registration is disabled") logger.LegacyPrintf("service.auth", "%s", "[Auth] Registration is disabled")
return nil, ErrRegDisabled return nil, ErrRegDisabled
} }
...@@ -275,17 +275,17 @@ func (s *AuthService) SendVerifyCodeAsync(ctx context.Context, email string) (*S ...@@ -275,17 +275,17 @@ func (s *AuthService) SendVerifyCodeAsync(ctx context.Context, email string) (*S
// 检查邮箱是否已存在 // 检查邮箱是否已存在
existsEmail, err := s.userRepo.ExistsByEmail(ctx, email) existsEmail, err := s.userRepo.ExistsByEmail(ctx, email)
if err != nil { if err != nil {
log.Printf("[Auth] Database error checking email exists: %v", err) logger.LegacyPrintf("service.auth", "[Auth] Database error checking email exists: %v", err)
return nil, ErrServiceUnavailable return nil, ErrServiceUnavailable
} }
if existsEmail { if existsEmail {
log.Printf("[Auth] Email already exists: %s", email) logger.LegacyPrintf("service.auth", "[Auth] Email already exists: %s", email)
return nil, ErrEmailExists return nil, ErrEmailExists
} }
// 检查邮件队列服务是否配置 // 检查邮件队列服务是否配置
if s.emailQueueService == nil { if s.emailQueueService == nil {
log.Println("[Auth] Email queue service not configured") logger.LegacyPrintf("service.auth", "%s", "[Auth] Email queue service not configured")
return nil, errors.New("email queue service not configured") return nil, errors.New("email queue service not configured")
} }
...@@ -296,13 +296,13 @@ func (s *AuthService) SendVerifyCodeAsync(ctx context.Context, email string) (*S ...@@ -296,13 +296,13 @@ func (s *AuthService) SendVerifyCodeAsync(ctx context.Context, email string) (*S
} }
// 异步发送 // 异步发送
log.Printf("[Auth] Enqueueing verify code for: %s", email) logger.LegacyPrintf("service.auth", "[Auth] Enqueueing verify code for: %s", email)
if err := s.emailQueueService.EnqueueVerifyCode(email, siteName); err != nil { if err := s.emailQueueService.EnqueueVerifyCode(email, siteName); err != nil {
log.Printf("[Auth] Failed to enqueue: %v", err) logger.LegacyPrintf("service.auth", "[Auth] Failed to enqueue: %v", err)
return nil, fmt.Errorf("enqueue verify code: %w", err) return nil, fmt.Errorf("enqueue verify code: %w", err)
} }
log.Printf("[Auth] Verify code enqueued successfully for: %s", email) logger.LegacyPrintf("service.auth", "[Auth] Verify code enqueued successfully for: %s", email)
return &SendVerifyCodeResult{ return &SendVerifyCodeResult{
Countdown: 60, // 60秒倒计时 Countdown: 60, // 60秒倒计时
}, nil }, nil
...@@ -314,27 +314,27 @@ func (s *AuthService) VerifyTurnstile(ctx context.Context, token string, remoteI ...@@ -314,27 +314,27 @@ func (s *AuthService) VerifyTurnstile(ctx context.Context, token string, remoteI
if required { if required {
if s.settingService == nil { if s.settingService == nil {
log.Println("[Auth] Turnstile required but settings service is not configured") logger.LegacyPrintf("service.auth", "%s", "[Auth] Turnstile required but settings service is not configured")
return ErrTurnstileNotConfigured return ErrTurnstileNotConfigured
} }
enabled := s.settingService.IsTurnstileEnabled(ctx) enabled := s.settingService.IsTurnstileEnabled(ctx)
secretConfigured := s.settingService.GetTurnstileSecretKey(ctx) != "" secretConfigured := s.settingService.GetTurnstileSecretKey(ctx) != ""
if !enabled || !secretConfigured { if !enabled || !secretConfigured {
log.Printf("[Auth] Turnstile required but not configured (enabled=%v, secret_configured=%v)", enabled, secretConfigured) logger.LegacyPrintf("service.auth", "[Auth] Turnstile required but not configured (enabled=%v, secret_configured=%v)", enabled, secretConfigured)
return ErrTurnstileNotConfigured return ErrTurnstileNotConfigured
} }
} }
if s.turnstileService == nil { if s.turnstileService == nil {
if required { if required {
log.Println("[Auth] Turnstile required but service not configured") logger.LegacyPrintf("service.auth", "%s", "[Auth] Turnstile required but service not configured")
return ErrTurnstileNotConfigured return ErrTurnstileNotConfigured
} }
return nil // 服务未配置则跳过验证 return nil // 服务未配置则跳过验证
} }
if !required && s.settingService != nil && s.settingService.IsTurnstileEnabled(ctx) && s.settingService.GetTurnstileSecretKey(ctx) == "" { if !required && s.settingService != nil && s.settingService.IsTurnstileEnabled(ctx) && s.settingService.GetTurnstileSecretKey(ctx) == "" {
log.Println("[Auth] Turnstile enabled but secret key not configured") logger.LegacyPrintf("service.auth", "%s", "[Auth] Turnstile enabled but secret key not configured")
} }
return s.turnstileService.VerifyToken(ctx, token, remoteIP) return s.turnstileService.VerifyToken(ctx, token, remoteIP)
...@@ -373,7 +373,7 @@ func (s *AuthService) Login(ctx context.Context, email, password string) (string ...@@ -373,7 +373,7 @@ func (s *AuthService) Login(ctx context.Context, email, password string) (string
return "", nil, ErrInvalidCredentials return "", nil, ErrInvalidCredentials
} }
// 记录数据库错误但不暴露给用户 // 记录数据库错误但不暴露给用户
log.Printf("[Auth] Database error during login: %v", err) logger.LegacyPrintf("service.auth", "[Auth] Database error during login: %v", err)
return "", nil, ErrServiceUnavailable return "", nil, ErrServiceUnavailable
} }
...@@ -426,7 +426,7 @@ func (s *AuthService) LoginOrRegisterOAuth(ctx context.Context, email, username ...@@ -426,7 +426,7 @@ func (s *AuthService) LoginOrRegisterOAuth(ctx context.Context, email, username
randomPassword, err := randomHexString(32) randomPassword, err := randomHexString(32)
if err != nil { if err != nil {
log.Printf("[Auth] Failed to generate random password for oauth signup: %v", err) logger.LegacyPrintf("service.auth", "[Auth] Failed to generate random password for oauth signup: %v", err)
return "", nil, ErrServiceUnavailable return "", nil, ErrServiceUnavailable
} }
hashedPassword, err := s.HashPassword(randomPassword) hashedPassword, err := s.HashPassword(randomPassword)
...@@ -457,18 +457,18 @@ func (s *AuthService) LoginOrRegisterOAuth(ctx context.Context, email, username ...@@ -457,18 +457,18 @@ func (s *AuthService) LoginOrRegisterOAuth(ctx context.Context, email, username
// 并发场景:GetByEmail 与 Create 之间用户被创建。 // 并发场景:GetByEmail 与 Create 之间用户被创建。
user, err = s.userRepo.GetByEmail(ctx, email) user, err = s.userRepo.GetByEmail(ctx, email)
if err != nil { if err != nil {
log.Printf("[Auth] Database error getting user after conflict: %v", err) logger.LegacyPrintf("service.auth", "[Auth] Database error getting user after conflict: %v", err)
return "", nil, ErrServiceUnavailable return "", nil, ErrServiceUnavailable
} }
} else { } else {
log.Printf("[Auth] Database error creating oauth user: %v", err) logger.LegacyPrintf("service.auth", "[Auth] Database error creating oauth user: %v", err)
return "", nil, ErrServiceUnavailable return "", nil, ErrServiceUnavailable
} }
} else { } else {
user = newUser user = newUser
} }
} else { } else {
log.Printf("[Auth] Database error during oauth login: %v", err) logger.LegacyPrintf("service.auth", "[Auth] Database error during oauth login: %v", err)
return "", nil, ErrServiceUnavailable return "", nil, ErrServiceUnavailable
} }
} }
...@@ -481,7 +481,7 @@ func (s *AuthService) LoginOrRegisterOAuth(ctx context.Context, email, username ...@@ -481,7 +481,7 @@ func (s *AuthService) LoginOrRegisterOAuth(ctx context.Context, email, username
if user.Username == "" && username != "" { if user.Username == "" && username != "" {
user.Username = username user.Username = username
if err := s.userRepo.Update(ctx, user); err != nil { if err := s.userRepo.Update(ctx, user); err != nil {
log.Printf("[Auth] Failed to update username after oauth login: %v", err) logger.LegacyPrintf("service.auth", "[Auth] Failed to update username after oauth login: %v", err)
} }
} }
...@@ -523,7 +523,7 @@ func (s *AuthService) LoginOrRegisterOAuthWithTokenPair(ctx context.Context, ema ...@@ -523,7 +523,7 @@ func (s *AuthService) LoginOrRegisterOAuthWithTokenPair(ctx context.Context, ema
randomPassword, err := randomHexString(32) randomPassword, err := randomHexString(32)
if err != nil { if err != nil {
log.Printf("[Auth] Failed to generate random password for oauth signup: %v", err) logger.LegacyPrintf("service.auth", "[Auth] Failed to generate random password for oauth signup: %v", err)
return nil, nil, ErrServiceUnavailable return nil, nil, ErrServiceUnavailable
} }
hashedPassword, err := s.HashPassword(randomPassword) hashedPassword, err := s.HashPassword(randomPassword)
...@@ -552,18 +552,18 @@ func (s *AuthService) LoginOrRegisterOAuthWithTokenPair(ctx context.Context, ema ...@@ -552,18 +552,18 @@ func (s *AuthService) LoginOrRegisterOAuthWithTokenPair(ctx context.Context, ema
if errors.Is(err, ErrEmailExists) { if errors.Is(err, ErrEmailExists) {
user, err = s.userRepo.GetByEmail(ctx, email) user, err = s.userRepo.GetByEmail(ctx, email)
if err != nil { if err != nil {
log.Printf("[Auth] Database error getting user after conflict: %v", err) logger.LegacyPrintf("service.auth", "[Auth] Database error getting user after conflict: %v", err)
return nil, nil, ErrServiceUnavailable return nil, nil, ErrServiceUnavailable
} }
} else { } else {
log.Printf("[Auth] Database error creating oauth user: %v", err) logger.LegacyPrintf("service.auth", "[Auth] Database error creating oauth user: %v", err)
return nil, nil, ErrServiceUnavailable return nil, nil, ErrServiceUnavailable
} }
} else { } else {
user = newUser user = newUser
} }
} else { } else {
log.Printf("[Auth] Database error during oauth login: %v", err) logger.LegacyPrintf("service.auth", "[Auth] Database error during oauth login: %v", err)
return nil, nil, ErrServiceUnavailable return nil, nil, ErrServiceUnavailable
} }
} }
...@@ -575,7 +575,7 @@ func (s *AuthService) LoginOrRegisterOAuthWithTokenPair(ctx context.Context, ema ...@@ -575,7 +575,7 @@ func (s *AuthService) LoginOrRegisterOAuthWithTokenPair(ctx context.Context, ema
if user.Username == "" && username != "" { if user.Username == "" && username != "" {
user.Username = username user.Username = username
if err := s.userRepo.Update(ctx, user); err != nil { if err := s.userRepo.Update(ctx, user); err != nil {
log.Printf("[Auth] Failed to update username after oauth login: %v", err) logger.LegacyPrintf("service.auth", "[Auth] Failed to update username after oauth login: %v", err)
} }
} }
...@@ -715,7 +715,7 @@ func (s *AuthService) RefreshToken(ctx context.Context, oldTokenString string) ( ...@@ -715,7 +715,7 @@ func (s *AuthService) RefreshToken(ctx context.Context, oldTokenString string) (
if errors.Is(err, ErrUserNotFound) { if errors.Is(err, ErrUserNotFound) {
return "", ErrInvalidToken return "", ErrInvalidToken
} }
log.Printf("[Auth] Database error refreshing token: %v", err) logger.LegacyPrintf("service.auth", "[Auth] Database error refreshing token: %v", err)
return "", ErrServiceUnavailable return "", ErrServiceUnavailable
} }
...@@ -756,16 +756,16 @@ func (s *AuthService) preparePasswordReset(ctx context.Context, email, frontendB ...@@ -756,16 +756,16 @@ func (s *AuthService) preparePasswordReset(ctx context.Context, email, frontendB
if err != nil { if err != nil {
if errors.Is(err, ErrUserNotFound) { if errors.Is(err, ErrUserNotFound) {
// Security: Log but don't reveal that user doesn't exist // Security: Log but don't reveal that user doesn't exist
log.Printf("[Auth] Password reset requested for non-existent email: %s", email) logger.LegacyPrintf("service.auth", "[Auth] Password reset requested for non-existent email: %s", email)
return "", "", false return "", "", false
} }
log.Printf("[Auth] Database error checking email for password reset: %v", err) logger.LegacyPrintf("service.auth", "[Auth] Database error checking email for password reset: %v", err)
return "", "", false return "", "", false
} }
// Check if user is active // Check if user is active
if !user.IsActive() { if !user.IsActive() {
log.Printf("[Auth] Password reset requested for inactive user: %s", email) logger.LegacyPrintf("service.auth", "[Auth] Password reset requested for inactive user: %s", email)
return "", "", false return "", "", false
} }
...@@ -797,11 +797,11 @@ func (s *AuthService) RequestPasswordReset(ctx context.Context, email, frontendB ...@@ -797,11 +797,11 @@ func (s *AuthService) RequestPasswordReset(ctx context.Context, email, frontendB
} }
if err := s.emailService.SendPasswordResetEmail(ctx, email, siteName, resetURL); err != nil { if err := s.emailService.SendPasswordResetEmail(ctx, email, siteName, resetURL); err != nil {
log.Printf("[Auth] Failed to send password reset email to %s: %v", email, err) logger.LegacyPrintf("service.auth", "[Auth] Failed to send password reset email to %s: %v", email, err)
return nil // Silent success to prevent enumeration return nil // Silent success to prevent enumeration
} }
log.Printf("[Auth] Password reset email sent to: %s", email) logger.LegacyPrintf("service.auth", "[Auth] Password reset email sent to: %s", email)
return nil return nil
} }
...@@ -821,11 +821,11 @@ func (s *AuthService) RequestPasswordResetAsync(ctx context.Context, email, fron ...@@ -821,11 +821,11 @@ func (s *AuthService) RequestPasswordResetAsync(ctx context.Context, email, fron
} }
if err := s.emailQueueService.EnqueuePasswordReset(email, siteName, resetURL); err != nil { if err := s.emailQueueService.EnqueuePasswordReset(email, siteName, resetURL); err != nil {
log.Printf("[Auth] Failed to enqueue password reset email for %s: %v", email, err) logger.LegacyPrintf("service.auth", "[Auth] Failed to enqueue password reset email for %s: %v", email, err)
return nil // Silent success to prevent enumeration return nil // Silent success to prevent enumeration
} }
log.Printf("[Auth] Password reset email enqueued for: %s", email) logger.LegacyPrintf("service.auth", "[Auth] Password reset email enqueued for: %s", email)
return nil return nil
} }
...@@ -852,7 +852,7 @@ func (s *AuthService) ResetPassword(ctx context.Context, email, token, newPasswo ...@@ -852,7 +852,7 @@ func (s *AuthService) ResetPassword(ctx context.Context, email, token, newPasswo
if errors.Is(err, ErrUserNotFound) { if errors.Is(err, ErrUserNotFound) {
return ErrInvalidResetToken // Token was valid but user was deleted return ErrInvalidResetToken // Token was valid but user was deleted
} }
log.Printf("[Auth] Database error getting user for password reset: %v", err) logger.LegacyPrintf("service.auth", "[Auth] Database error getting user for password reset: %v", err)
return ErrServiceUnavailable return ErrServiceUnavailable
} }
...@@ -872,17 +872,17 @@ func (s *AuthService) ResetPassword(ctx context.Context, email, token, newPasswo ...@@ -872,17 +872,17 @@ func (s *AuthService) ResetPassword(ctx context.Context, email, token, newPasswo
user.TokenVersion++ // Invalidate all existing tokens user.TokenVersion++ // Invalidate all existing tokens
if err := s.userRepo.Update(ctx, user); err != nil { if err := s.userRepo.Update(ctx, user); err != nil {
log.Printf("[Auth] Database error updating password for user %d: %v", user.ID, err) logger.LegacyPrintf("service.auth", "[Auth] Database error updating password for user %d: %v", user.ID, err)
return ErrServiceUnavailable return ErrServiceUnavailable
} }
// Also revoke all refresh tokens for this user // Also revoke all refresh tokens for this user
if err := s.RevokeAllUserSessions(ctx, user.ID); err != nil { if err := s.RevokeAllUserSessions(ctx, user.ID); err != nil {
log.Printf("[Auth] Failed to revoke refresh tokens for user %d: %v", user.ID, err) logger.LegacyPrintf("service.auth", "[Auth] Failed to revoke refresh tokens for user %d: %v", user.ID, err)
// Don't return error - password was already changed successfully // Don't return error - password was already changed successfully
} }
log.Printf("[Auth] Password reset successful for user: %s", email) logger.LegacyPrintf("service.auth", "[Auth] Password reset successful for user: %s", email)
return nil return nil
} }
...@@ -961,13 +961,13 @@ func (s *AuthService) generateRefreshToken(ctx context.Context, user *User, fami ...@@ -961,13 +961,13 @@ func (s *AuthService) generateRefreshToken(ctx context.Context, user *User, fami
// 添加到用户Token集合 // 添加到用户Token集合
if err := s.refreshTokenCache.AddToUserTokenSet(ctx, user.ID, tokenHash, ttl); err != nil { if err := s.refreshTokenCache.AddToUserTokenSet(ctx, user.ID, tokenHash, ttl); err != nil {
log.Printf("[Auth] Failed to add token to user set: %v", err) logger.LegacyPrintf("service.auth", "[Auth] Failed to add token to user set: %v", err)
// 不影响主流程 // 不影响主流程
} }
// 添加到家族Token集合 // 添加到家族Token集合
if err := s.refreshTokenCache.AddToFamilyTokenSet(ctx, familyID, tokenHash, ttl); err != nil { if err := s.refreshTokenCache.AddToFamilyTokenSet(ctx, familyID, tokenHash, ttl); err != nil {
log.Printf("[Auth] Failed to add token to family set: %v", err) logger.LegacyPrintf("service.auth", "[Auth] Failed to add token to family set: %v", err)
// 不影响主流程 // 不影响主流程
} }
...@@ -994,10 +994,10 @@ func (s *AuthService) RefreshTokenPair(ctx context.Context, refreshToken string) ...@@ -994,10 +994,10 @@ func (s *AuthService) RefreshTokenPair(ctx context.Context, refreshToken string)
if err != nil { if err != nil {
if errors.Is(err, ErrRefreshTokenNotFound) { if errors.Is(err, ErrRefreshTokenNotFound) {
// Token不存在,可能是已被使用(Token轮转)或已过期 // Token不存在,可能是已被使用(Token轮转)或已过期
log.Printf("[Auth] Refresh token not found, possible reuse attack") logger.LegacyPrintf("service.auth", "[Auth] Refresh token not found, possible reuse attack")
return nil, ErrRefreshTokenInvalid return nil, ErrRefreshTokenInvalid
} }
log.Printf("[Auth] Error getting refresh token: %v", err) logger.LegacyPrintf("service.auth", "[Auth] Error getting refresh token: %v", err)
return nil, ErrServiceUnavailable return nil, ErrServiceUnavailable
} }
...@@ -1016,7 +1016,7 @@ func (s *AuthService) RefreshTokenPair(ctx context.Context, refreshToken string) ...@@ -1016,7 +1016,7 @@ func (s *AuthService) RefreshTokenPair(ctx context.Context, refreshToken string)
_ = s.refreshTokenCache.DeleteTokenFamily(ctx, data.FamilyID) _ = s.refreshTokenCache.DeleteTokenFamily(ctx, data.FamilyID)
return nil, ErrRefreshTokenInvalid return nil, ErrRefreshTokenInvalid
} }
log.Printf("[Auth] Database error getting user for token refresh: %v", err) logger.LegacyPrintf("service.auth", "[Auth] Database error getting user for token refresh: %v", err)
return nil, ErrServiceUnavailable return nil, ErrServiceUnavailable
} }
...@@ -1036,7 +1036,7 @@ func (s *AuthService) RefreshTokenPair(ctx context.Context, refreshToken string) ...@@ -1036,7 +1036,7 @@ func (s *AuthService) RefreshTokenPair(ctx context.Context, refreshToken string)
// Token轮转:立即使旧Token失效 // Token轮转:立即使旧Token失效
if err := s.refreshTokenCache.DeleteRefreshToken(ctx, tokenHash); err != nil { if err := s.refreshTokenCache.DeleteRefreshToken(ctx, tokenHash); err != nil {
log.Printf("[Auth] Failed to delete old refresh token: %v", err) logger.LegacyPrintf("service.auth", "[Auth] Failed to delete old refresh token: %v", err)
// 继续处理,不影响主流程 // 继续处理,不影响主流程
} }
......
...@@ -3,13 +3,13 @@ package service ...@@ -3,13 +3,13 @@ package service
import ( import (
"context" "context"
"fmt" "fmt"
"log"
"sync" "sync"
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/Wei-Shaw/sub2api/internal/config" "github.com/Wei-Shaw/sub2api/internal/config"
infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors" infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
"github.com/Wei-Shaw/sub2api/internal/pkg/logger"
) )
// 错误定义 // 错误定义
...@@ -156,13 +156,13 @@ func (s *BillingCacheService) cacheWriteWorker() { ...@@ -156,13 +156,13 @@ func (s *BillingCacheService) cacheWriteWorker() {
case cacheWriteUpdateSubscriptionUsage: case cacheWriteUpdateSubscriptionUsage:
if s.cache != nil { if s.cache != nil {
if err := s.cache.UpdateSubscriptionUsage(ctx, task.userID, task.groupID, task.amount); err != nil { if err := s.cache.UpdateSubscriptionUsage(ctx, task.userID, task.groupID, task.amount); err != nil {
log.Printf("Warning: update subscription cache failed for user %d group %d: %v", task.userID, task.groupID, err) logger.LegacyPrintf("service.billing_cache", "Warning: update subscription cache failed for user %d group %d: %v", task.userID, task.groupID, err)
} }
} }
case cacheWriteDeductBalance: case cacheWriteDeductBalance:
if s.cache != nil { if s.cache != nil {
if err := s.cache.DeductUserBalance(ctx, task.userID, task.amount); err != nil { if err := s.cache.DeductUserBalance(ctx, task.userID, task.amount); err != nil {
log.Printf("Warning: deduct balance cache failed for user %d: %v", task.userID, err) logger.LegacyPrintf("service.billing_cache", "Warning: deduct balance cache failed for user %d: %v", task.userID, err)
} }
} }
} }
...@@ -216,7 +216,7 @@ func (s *BillingCacheService) logCacheWriteDrop(task cacheWriteTask, reason stri ...@@ -216,7 +216,7 @@ func (s *BillingCacheService) logCacheWriteDrop(task cacheWriteTask, reason stri
if dropped == 0 { if dropped == 0 {
return return
} }
log.Printf("Warning: cache write queue %s, dropped %d tasks in last %s (latest kind=%s user %d group %d)", logger.LegacyPrintf("service.billing_cache", "Warning: cache write queue %s, dropped %d tasks in last %s (latest kind=%s user %d group %d)",
reason, reason,
dropped, dropped,
cacheWriteDropLogInterval, cacheWriteDropLogInterval,
...@@ -274,7 +274,7 @@ func (s *BillingCacheService) setBalanceCache(ctx context.Context, userID int64, ...@@ -274,7 +274,7 @@ func (s *BillingCacheService) setBalanceCache(ctx context.Context, userID int64,
return return
} }
if err := s.cache.SetUserBalance(ctx, userID, balance); err != nil { if err := s.cache.SetUserBalance(ctx, userID, balance); err != nil {
log.Printf("Warning: set balance cache failed for user %d: %v", userID, err) logger.LegacyPrintf("service.billing_cache", "Warning: set balance cache failed for user %d: %v", userID, err)
} }
} }
...@@ -302,7 +302,7 @@ func (s *BillingCacheService) QueueDeductBalance(userID int64, amount float64) { ...@@ -302,7 +302,7 @@ func (s *BillingCacheService) QueueDeductBalance(userID int64, amount float64) {
ctx, cancel := context.WithTimeout(context.Background(), cacheWriteTimeout) ctx, cancel := context.WithTimeout(context.Background(), cacheWriteTimeout)
defer cancel() defer cancel()
if err := s.DeductBalanceCache(ctx, userID, amount); err != nil { if err := s.DeductBalanceCache(ctx, userID, amount); err != nil {
log.Printf("Warning: deduct balance cache fallback failed for user %d: %v", userID, err) logger.LegacyPrintf("service.billing_cache", "Warning: deduct balance cache fallback failed for user %d: %v", userID, err)
} }
} }
...@@ -312,7 +312,7 @@ func (s *BillingCacheService) InvalidateUserBalance(ctx context.Context, userID ...@@ -312,7 +312,7 @@ func (s *BillingCacheService) InvalidateUserBalance(ctx context.Context, userID
return nil return nil
} }
if err := s.cache.InvalidateUserBalance(ctx, userID); err != nil { if err := s.cache.InvalidateUserBalance(ctx, userID); err != nil {
log.Printf("Warning: invalidate balance cache failed for user %d: %v", userID, err) logger.LegacyPrintf("service.billing_cache", "Warning: invalidate balance cache failed for user %d: %v", userID, err)
return err return err
} }
return nil return nil
...@@ -396,7 +396,7 @@ func (s *BillingCacheService) setSubscriptionCache(ctx context.Context, userID, ...@@ -396,7 +396,7 @@ func (s *BillingCacheService) setSubscriptionCache(ctx context.Context, userID,
return return
} }
if err := s.cache.SetSubscriptionCache(ctx, userID, groupID, s.convertToPortsData(data)); err != nil { if err := s.cache.SetSubscriptionCache(ctx, userID, groupID, s.convertToPortsData(data)); err != nil {
log.Printf("Warning: set subscription cache failed for user %d group %d: %v", userID, groupID, err) logger.LegacyPrintf("service.billing_cache", "Warning: set subscription cache failed for user %d group %d: %v", userID, groupID, err)
} }
} }
...@@ -425,7 +425,7 @@ func (s *BillingCacheService) QueueUpdateSubscriptionUsage(userID, groupID int64 ...@@ -425,7 +425,7 @@ func (s *BillingCacheService) QueueUpdateSubscriptionUsage(userID, groupID int64
ctx, cancel := context.WithTimeout(context.Background(), cacheWriteTimeout) ctx, cancel := context.WithTimeout(context.Background(), cacheWriteTimeout)
defer cancel() defer cancel()
if err := s.UpdateSubscriptionUsage(ctx, userID, groupID, costUSD); err != nil { if err := s.UpdateSubscriptionUsage(ctx, userID, groupID, costUSD); err != nil {
log.Printf("Warning: update subscription cache fallback failed for user %d group %d: %v", userID, groupID, err) logger.LegacyPrintf("service.billing_cache", "Warning: update subscription cache fallback failed for user %d group %d: %v", userID, groupID, err)
} }
} }
...@@ -435,7 +435,7 @@ func (s *BillingCacheService) InvalidateSubscription(ctx context.Context, userID ...@@ -435,7 +435,7 @@ func (s *BillingCacheService) InvalidateSubscription(ctx context.Context, userID
return nil return nil
} }
if err := s.cache.InvalidateSubscriptionCache(ctx, userID, groupID); err != nil { if err := s.cache.InvalidateSubscriptionCache(ctx, userID, groupID); err != nil {
log.Printf("Warning: invalidate subscription cache failed for user %d group %d: %v", userID, groupID, err) logger.LegacyPrintf("service.billing_cache", "Warning: invalidate subscription cache failed for user %d group %d: %v", userID, groupID, err)
return err return err
} }
return nil return nil
...@@ -474,7 +474,7 @@ func (s *BillingCacheService) checkBalanceEligibility(ctx context.Context, userI ...@@ -474,7 +474,7 @@ func (s *BillingCacheService) checkBalanceEligibility(ctx context.Context, userI
if s.circuitBreaker != nil { if s.circuitBreaker != nil {
s.circuitBreaker.OnFailure(err) s.circuitBreaker.OnFailure(err)
} }
log.Printf("ALERT: billing balance check failed for user %d: %v", userID, err) logger.LegacyPrintf("service.billing_cache", "ALERT: billing balance check failed for user %d: %v", userID, err)
return ErrBillingServiceUnavailable.WithCause(err) return ErrBillingServiceUnavailable.WithCause(err)
} }
if s.circuitBreaker != nil { if s.circuitBreaker != nil {
...@@ -496,7 +496,7 @@ func (s *BillingCacheService) checkSubscriptionEligibility(ctx context.Context, ...@@ -496,7 +496,7 @@ func (s *BillingCacheService) checkSubscriptionEligibility(ctx context.Context,
if s.circuitBreaker != nil { if s.circuitBreaker != nil {
s.circuitBreaker.OnFailure(err) s.circuitBreaker.OnFailure(err)
} }
log.Printf("ALERT: billing subscription check failed for user %d group %d: %v", userID, group.ID, err) logger.LegacyPrintf("service.billing_cache", "ALERT: billing subscription check failed for user %d group %d: %v", userID, group.ID, err)
return ErrBillingServiceUnavailable.WithCause(err) return ErrBillingServiceUnavailable.WithCause(err)
} }
if s.circuitBreaker != nil { if s.circuitBreaker != nil {
...@@ -585,7 +585,7 @@ func (b *billingCircuitBreaker) Allow() bool { ...@@ -585,7 +585,7 @@ func (b *billingCircuitBreaker) Allow() bool {
} }
b.state = billingCircuitHalfOpen b.state = billingCircuitHalfOpen
b.halfOpenRemaining = b.halfOpenRequests b.halfOpenRemaining = b.halfOpenRequests
log.Printf("ALERT: billing circuit breaker entering half-open state") logger.LegacyPrintf("service.billing_cache", "ALERT: billing circuit breaker entering half-open state")
fallthrough fallthrough
case billingCircuitHalfOpen: case billingCircuitHalfOpen:
if b.halfOpenRemaining <= 0 { if b.halfOpenRemaining <= 0 {
...@@ -612,7 +612,7 @@ func (b *billingCircuitBreaker) OnFailure(err error) { ...@@ -612,7 +612,7 @@ func (b *billingCircuitBreaker) OnFailure(err error) {
b.state = billingCircuitOpen b.state = billingCircuitOpen
b.openedAt = time.Now() b.openedAt = time.Now()
b.halfOpenRemaining = 0 b.halfOpenRemaining = 0
log.Printf("ALERT: billing circuit breaker opened after half-open failure: %v", err) logger.LegacyPrintf("service.billing_cache", "ALERT: billing circuit breaker opened after half-open failure: %v", err)
return return
default: default:
b.failures++ b.failures++
...@@ -620,7 +620,7 @@ func (b *billingCircuitBreaker) OnFailure(err error) { ...@@ -620,7 +620,7 @@ func (b *billingCircuitBreaker) OnFailure(err error) {
b.state = billingCircuitOpen b.state = billingCircuitOpen
b.openedAt = time.Now() b.openedAt = time.Now()
b.halfOpenRemaining = 0 b.halfOpenRemaining = 0
log.Printf("ALERT: billing circuit breaker opened after %d failures: %v", b.failures, err) logger.LegacyPrintf("service.billing_cache", "ALERT: billing circuit breaker opened after %d failures: %v", b.failures, err)
} }
} }
} }
...@@ -641,9 +641,9 @@ func (b *billingCircuitBreaker) OnSuccess() { ...@@ -641,9 +641,9 @@ func (b *billingCircuitBreaker) OnSuccess() {
// 只有状态真正发生变化时才记录日志 // 只有状态真正发生变化时才记录日志
if previousState != billingCircuitClosed { if previousState != billingCircuitClosed {
log.Printf("ALERT: billing circuit breaker closed (was %s)", circuitStateString(previousState)) logger.LegacyPrintf("service.billing_cache", "ALERT: billing circuit breaker closed (was %s)", circuitStateString(previousState))
} else if previousFailures > 0 { } else if previousFailures > 0 {
log.Printf("INFO: billing circuit breaker failures reset from %d", previousFailures) logger.LegacyPrintf("service.billing_cache", "INFO: billing circuit breaker failures reset from %d", previousFailures)
} }
} }
......
...@@ -5,8 +5,9 @@ import ( ...@@ -5,8 +5,9 @@ import (
"crypto/rand" "crypto/rand"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"log"
"time" "time"
"github.com/Wei-Shaw/sub2api/internal/pkg/logger"
) )
// ConcurrencyCache 定义并发控制的缓存接口 // ConcurrencyCache 定义并发控制的缓存接口
...@@ -124,7 +125,7 @@ func (s *ConcurrencyService) AcquireAccountSlot(ctx context.Context, accountID i ...@@ -124,7 +125,7 @@ func (s *ConcurrencyService) AcquireAccountSlot(ctx context.Context, accountID i
bgCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) bgCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel() defer cancel()
if err := s.cache.ReleaseAccountSlot(bgCtx, accountID, requestID); err != nil { if err := s.cache.ReleaseAccountSlot(bgCtx, accountID, requestID); err != nil {
log.Printf("Warning: failed to release account slot for %d (req=%s): %v", accountID, requestID, err) logger.LegacyPrintf("service.concurrency", "Warning: failed to release account slot for %d (req=%s): %v", accountID, requestID, err)
} }
}, },
}, nil }, nil
...@@ -163,7 +164,7 @@ func (s *ConcurrencyService) AcquireUserSlot(ctx context.Context, userID int64, ...@@ -163,7 +164,7 @@ func (s *ConcurrencyService) AcquireUserSlot(ctx context.Context, userID int64,
bgCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) bgCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel() defer cancel()
if err := s.cache.ReleaseUserSlot(bgCtx, userID, requestID); err != nil { if err := s.cache.ReleaseUserSlot(bgCtx, userID, requestID); err != nil {
log.Printf("Warning: failed to release user slot for %d (req=%s): %v", userID, requestID, err) logger.LegacyPrintf("service.concurrency", "Warning: failed to release user slot for %d (req=%s): %v", userID, requestID, err)
} }
}, },
}, nil }, nil
...@@ -191,7 +192,7 @@ func (s *ConcurrencyService) IncrementWaitCount(ctx context.Context, userID int6 ...@@ -191,7 +192,7 @@ func (s *ConcurrencyService) IncrementWaitCount(ctx context.Context, userID int6
result, err := s.cache.IncrementWaitCount(ctx, userID, maxWait) result, err := s.cache.IncrementWaitCount(ctx, userID, maxWait)
if err != nil { if err != nil {
// On error, allow the request to proceed (fail open) // On error, allow the request to proceed (fail open)
log.Printf("Warning: increment wait count failed for user %d: %v", userID, err) logger.LegacyPrintf("service.concurrency", "Warning: increment wait count failed for user %d: %v", userID, err)
return true, nil return true, nil
} }
return result, nil return result, nil
...@@ -209,7 +210,7 @@ func (s *ConcurrencyService) DecrementWaitCount(ctx context.Context, userID int6 ...@@ -209,7 +210,7 @@ func (s *ConcurrencyService) DecrementWaitCount(ctx context.Context, userID int6
defer cancel() defer cancel()
if err := s.cache.DecrementWaitCount(bgCtx, userID); err != nil { if err := s.cache.DecrementWaitCount(bgCtx, userID); err != nil {
log.Printf("Warning: decrement wait count failed for user %d: %v", userID, err) logger.LegacyPrintf("service.concurrency", "Warning: decrement wait count failed for user %d: %v", userID, err)
} }
} }
...@@ -221,7 +222,7 @@ func (s *ConcurrencyService) IncrementAccountWaitCount(ctx context.Context, acco ...@@ -221,7 +222,7 @@ func (s *ConcurrencyService) IncrementAccountWaitCount(ctx context.Context, acco
result, err := s.cache.IncrementAccountWaitCount(ctx, accountID, maxWait) result, err := s.cache.IncrementAccountWaitCount(ctx, accountID, maxWait)
if err != nil { if err != nil {
log.Printf("Warning: increment wait count failed for account %d: %v", accountID, err) logger.LegacyPrintf("service.concurrency", "Warning: increment wait count failed for account %d: %v", accountID, err)
return true, nil return true, nil
} }
return result, nil return result, nil
...@@ -237,7 +238,7 @@ func (s *ConcurrencyService) DecrementAccountWaitCount(ctx context.Context, acco ...@@ -237,7 +238,7 @@ func (s *ConcurrencyService) DecrementAccountWaitCount(ctx context.Context, acco
defer cancel() defer cancel()
if err := s.cache.DecrementAccountWaitCount(bgCtx, accountID); err != nil { if err := s.cache.DecrementAccountWaitCount(bgCtx, accountID); err != nil {
log.Printf("Warning: decrement wait count failed for account %d: %v", accountID, err) logger.LegacyPrintf("service.concurrency", "Warning: decrement wait count failed for account %d: %v", accountID, err)
} }
} }
...@@ -293,7 +294,7 @@ func (s *ConcurrencyService) StartSlotCleanupWorker(accountRepo AccountRepositor ...@@ -293,7 +294,7 @@ func (s *ConcurrencyService) StartSlotCleanupWorker(accountRepo AccountRepositor
accounts, err := accountRepo.ListSchedulable(listCtx) accounts, err := accountRepo.ListSchedulable(listCtx)
cancel() cancel()
if err != nil { if err != nil {
log.Printf("Warning: list schedulable accounts failed: %v", err) logger.LegacyPrintf("service.concurrency", "Warning: list schedulable accounts failed: %v", err)
return return
} }
for _, account := range accounts { for _, account := range accounts {
...@@ -301,7 +302,7 @@ func (s *ConcurrencyService) StartSlotCleanupWorker(accountRepo AccountRepositor ...@@ -301,7 +302,7 @@ func (s *ConcurrencyService) StartSlotCleanupWorker(accountRepo AccountRepositor
err := s.cache.CleanupExpiredAccountSlots(accountCtx, account.ID) err := s.cache.CleanupExpiredAccountSlots(accountCtx, account.ID)
accountCancel() accountCancel()
if err != nil { if err != nil {
log.Printf("Warning: cleanup expired slots failed for account %d: %v", account.ID, err) logger.LegacyPrintf("service.concurrency", "Warning: cleanup expired slots failed for account %d: %v", account.ID, err)
} }
} }
} }
......
...@@ -3,12 +3,12 @@ package service ...@@ -3,12 +3,12 @@ package service
import ( import (
"context" "context"
"errors" "errors"
"log"
"log/slog" "log/slog"
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/Wei-Shaw/sub2api/internal/config" "github.com/Wei-Shaw/sub2api/internal/config"
"github.com/Wei-Shaw/sub2api/internal/pkg/logger"
) )
const ( const (
...@@ -66,7 +66,7 @@ func (s *DashboardAggregationService) Start() { ...@@ -66,7 +66,7 @@ func (s *DashboardAggregationService) Start() {
return return
} }
if !s.cfg.Enabled { if !s.cfg.Enabled {
log.Printf("[DashboardAggregation] 聚合作业已禁用") logger.LegacyPrintf("service.dashboard_aggregation", "[DashboardAggregation] 聚合作业已禁用")
return return
} }
...@@ -82,9 +82,9 @@ func (s *DashboardAggregationService) Start() { ...@@ -82,9 +82,9 @@ func (s *DashboardAggregationService) Start() {
s.timingWheel.ScheduleRecurring("dashboard:aggregation", interval, func() { s.timingWheel.ScheduleRecurring("dashboard:aggregation", interval, func() {
s.runScheduledAggregation() s.runScheduledAggregation()
}) })
log.Printf("[DashboardAggregation] 聚合作业启动 (interval=%v, lookback=%ds)", interval, s.cfg.LookbackSeconds) logger.LegacyPrintf("service.dashboard_aggregation", "[DashboardAggregation] 聚合作业启动 (interval=%v, lookback=%ds)", interval, s.cfg.LookbackSeconds)
if !s.cfg.BackfillEnabled { if !s.cfg.BackfillEnabled {
log.Printf("[DashboardAggregation] 回填已禁用,如需补齐保留窗口以外历史数据请手动回填") logger.LegacyPrintf("service.dashboard_aggregation", "[DashboardAggregation] 回填已禁用,如需补齐保留窗口以外历史数据请手动回填")
} }
} }
...@@ -94,7 +94,7 @@ func (s *DashboardAggregationService) TriggerBackfill(start, end time.Time) erro ...@@ -94,7 +94,7 @@ func (s *DashboardAggregationService) TriggerBackfill(start, end time.Time) erro
return errors.New("聚合服务未初始化") return errors.New("聚合服务未初始化")
} }
if !s.cfg.BackfillEnabled { if !s.cfg.BackfillEnabled {
log.Printf("[DashboardAggregation] 回填被拒绝: backfill_enabled=false") logger.LegacyPrintf("service.dashboard_aggregation", "[DashboardAggregation] 回填被拒绝: backfill_enabled=false")
return ErrDashboardBackfillDisabled return ErrDashboardBackfillDisabled
} }
if !end.After(start) { if !end.After(start) {
...@@ -111,7 +111,7 @@ func (s *DashboardAggregationService) TriggerBackfill(start, end time.Time) erro ...@@ -111,7 +111,7 @@ func (s *DashboardAggregationService) TriggerBackfill(start, end time.Time) erro
ctx, cancel := context.WithTimeout(context.Background(), defaultDashboardAggregationBackfillTimeout) ctx, cancel := context.WithTimeout(context.Background(), defaultDashboardAggregationBackfillTimeout)
defer cancel() defer cancel()
if err := s.backfillRange(ctx, start, end); err != nil { if err := s.backfillRange(ctx, start, end); err != nil {
log.Printf("[DashboardAggregation] 回填失败: %v", err) logger.LegacyPrintf("service.dashboard_aggregation", "[DashboardAggregation] 回填失败: %v", err)
} }
}() }()
return nil return nil
...@@ -142,12 +142,12 @@ func (s *DashboardAggregationService) TriggerRecomputeRange(start, end time.Time ...@@ -142,12 +142,12 @@ func (s *DashboardAggregationService) TriggerRecomputeRange(start, end time.Time
return return
} }
if !errors.Is(err, errDashboardAggregationRunning) { if !errors.Is(err, errDashboardAggregationRunning) {
log.Printf("[DashboardAggregation] 重新计算失败: %v", err) logger.LegacyPrintf("service.dashboard_aggregation", "[DashboardAggregation] 重新计算失败: %v", err)
return return
} }
time.Sleep(5 * time.Second) time.Sleep(5 * time.Second)
} }
log.Printf("[DashboardAggregation] 重新计算放弃: 聚合作业持续占用") logger.LegacyPrintf("service.dashboard_aggregation", "[DashboardAggregation] 重新计算放弃: 聚合作业持续占用")
}() }()
return nil return nil
} }
...@@ -163,7 +163,7 @@ func (s *DashboardAggregationService) recomputeRecentDays() { ...@@ -163,7 +163,7 @@ func (s *DashboardAggregationService) recomputeRecentDays() {
ctx, cancel := context.WithTimeout(context.Background(), defaultDashboardAggregationBackfillTimeout) ctx, cancel := context.WithTimeout(context.Background(), defaultDashboardAggregationBackfillTimeout)
defer cancel() defer cancel()
if err := s.backfillRange(ctx, start, now); err != nil { if err := s.backfillRange(ctx, start, now); err != nil {
log.Printf("[DashboardAggregation] 启动重算失败: %v", err) logger.LegacyPrintf("service.dashboard_aggregation", "[DashboardAggregation] 启动重算失败: %v", err)
return return
} }
} }
...@@ -178,7 +178,7 @@ func (s *DashboardAggregationService) recomputeRange(ctx context.Context, start, ...@@ -178,7 +178,7 @@ func (s *DashboardAggregationService) recomputeRange(ctx context.Context, start,
if err := s.repo.RecomputeRange(ctx, start, end); err != nil { if err := s.repo.RecomputeRange(ctx, start, end); err != nil {
return err return err
} }
log.Printf("[DashboardAggregation] 重新计算完成 (start=%s end=%s duration=%s)", logger.LegacyPrintf("service.dashboard_aggregation", "[DashboardAggregation] 重新计算完成 (start=%s end=%s duration=%s)",
start.UTC().Format(time.RFC3339), start.UTC().Format(time.RFC3339),
end.UTC().Format(time.RFC3339), end.UTC().Format(time.RFC3339),
time.Since(jobStart).String(), time.Since(jobStart).String(),
...@@ -199,7 +199,7 @@ func (s *DashboardAggregationService) runScheduledAggregation() { ...@@ -199,7 +199,7 @@ func (s *DashboardAggregationService) runScheduledAggregation() {
now := time.Now().UTC() now := time.Now().UTC()
last, err := s.repo.GetAggregationWatermark(ctx) last, err := s.repo.GetAggregationWatermark(ctx)
if err != nil { if err != nil {
log.Printf("[DashboardAggregation] 读取水位失败: %v", err) logger.LegacyPrintf("service.dashboard_aggregation", "[DashboardAggregation] 读取水位失败: %v", err)
last = time.Unix(0, 0).UTC() last = time.Unix(0, 0).UTC()
} }
...@@ -217,13 +217,13 @@ func (s *DashboardAggregationService) runScheduledAggregation() { ...@@ -217,13 +217,13 @@ func (s *DashboardAggregationService) runScheduledAggregation() {
} }
if err := s.aggregateRange(ctx, start, now); err != nil { if err := s.aggregateRange(ctx, start, now); err != nil {
log.Printf("[DashboardAggregation] 聚合失败: %v", err) logger.LegacyPrintf("service.dashboard_aggregation", "[DashboardAggregation] 聚合失败: %v", err)
return return
} }
updateErr := s.repo.UpdateAggregationWatermark(ctx, now) updateErr := s.repo.UpdateAggregationWatermark(ctx, now)
if updateErr != nil { if updateErr != nil {
log.Printf("[DashboardAggregation] 更新水位失败: %v", updateErr) logger.LegacyPrintf("service.dashboard_aggregation", "[DashboardAggregation] 更新水位失败: %v", updateErr)
} }
slog.Debug("[DashboardAggregation] 聚合完成", slog.Debug("[DashboardAggregation] 聚合完成",
"start", start.Format(time.RFC3339), "start", start.Format(time.RFC3339),
...@@ -262,9 +262,9 @@ func (s *DashboardAggregationService) backfillRange(ctx context.Context, start, ...@@ -262,9 +262,9 @@ func (s *DashboardAggregationService) backfillRange(ctx context.Context, start,
updateErr := s.repo.UpdateAggregationWatermark(ctx, endUTC) updateErr := s.repo.UpdateAggregationWatermark(ctx, endUTC)
if updateErr != nil { if updateErr != nil {
log.Printf("[DashboardAggregation] 更新水位失败: %v", updateErr) logger.LegacyPrintf("service.dashboard_aggregation", "[DashboardAggregation] 更新水位失败: %v", updateErr)
} }
log.Printf("[DashboardAggregation] 回填聚合完成 (start=%s end=%s duration=%s watermark_updated=%t)", logger.LegacyPrintf("service.dashboard_aggregation", "[DashboardAggregation] 回填聚合完成 (start=%s end=%s duration=%s watermark_updated=%t)",
startUTC.Format(time.RFC3339), startUTC.Format(time.RFC3339),
endUTC.Format(time.RFC3339), endUTC.Format(time.RFC3339),
time.Since(jobStart).String(), time.Since(jobStart).String(),
...@@ -280,7 +280,7 @@ func (s *DashboardAggregationService) aggregateRange(ctx context.Context, start, ...@@ -280,7 +280,7 @@ func (s *DashboardAggregationService) aggregateRange(ctx context.Context, start,
return nil return nil
} }
if err := s.repo.EnsureUsageLogsPartitions(ctx, end); err != nil { if err := s.repo.EnsureUsageLogsPartitions(ctx, end); err != nil {
log.Printf("[DashboardAggregation] 分区检查失败: %v", err) logger.LegacyPrintf("service.dashboard_aggregation", "[DashboardAggregation] 分区检查失败: %v", err)
} }
return s.repo.AggregateRange(ctx, start, end) return s.repo.AggregateRange(ctx, start, end)
} }
...@@ -299,11 +299,11 @@ func (s *DashboardAggregationService) maybeCleanupRetention(ctx context.Context, ...@@ -299,11 +299,11 @@ func (s *DashboardAggregationService) maybeCleanupRetention(ctx context.Context,
aggErr := s.repo.CleanupAggregates(ctx, hourlyCutoff, dailyCutoff) aggErr := s.repo.CleanupAggregates(ctx, hourlyCutoff, dailyCutoff)
if aggErr != nil { if aggErr != nil {
log.Printf("[DashboardAggregation] 聚合保留清理失败: %v", aggErr) logger.LegacyPrintf("service.dashboard_aggregation", "[DashboardAggregation] 聚合保留清理失败: %v", aggErr)
} }
usageErr := s.repo.CleanupUsageLogs(ctx, usageCutoff) usageErr := s.repo.CleanupUsageLogs(ctx, usageCutoff)
if usageErr != nil { if usageErr != nil {
log.Printf("[DashboardAggregation] usage_logs 保留清理失败: %v", usageErr) logger.LegacyPrintf("service.dashboard_aggregation", "[DashboardAggregation] usage_logs 保留清理失败: %v", usageErr)
} }
if aggErr == nil && usageErr == nil { if aggErr == nil && usageErr == nil {
s.lastRetentionCleanup.Store(now) s.lastRetentionCleanup.Store(now)
......
...@@ -5,11 +5,11 @@ import ( ...@@ -5,11 +5,11 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"log"
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/Wei-Shaw/sub2api/internal/config" "github.com/Wei-Shaw/sub2api/internal/config"
"github.com/Wei-Shaw/sub2api/internal/pkg/logger"
"github.com/Wei-Shaw/sub2api/internal/pkg/usagestats" "github.com/Wei-Shaw/sub2api/internal/pkg/usagestats"
) )
...@@ -113,7 +113,7 @@ func (s *DashboardService) GetDashboardStats(ctx context.Context) (*usagestats.D ...@@ -113,7 +113,7 @@ func (s *DashboardService) GetDashboardStats(ctx context.Context) (*usagestats.D
return cached, nil return cached, nil
} }
if err != nil && !errors.Is(err, ErrDashboardStatsCacheMiss) { if err != nil && !errors.Is(err, ErrDashboardStatsCacheMiss) {
log.Printf("[Dashboard] 仪表盘缓存读取失败: %v", err) logger.LegacyPrintf("service.dashboard", "[Dashboard] 仪表盘缓存读取失败: %v", err)
} }
} }
...@@ -188,7 +188,7 @@ func (s *DashboardService) refreshDashboardStatsAsync() { ...@@ -188,7 +188,7 @@ func (s *DashboardService) refreshDashboardStatsAsync() {
stats, err := s.fetchDashboardStats(ctx) stats, err := s.fetchDashboardStats(ctx)
if err != nil { if err != nil {
log.Printf("[Dashboard] 仪表盘缓存异步刷新失败: %v", err) logger.LegacyPrintf("service.dashboard", "[Dashboard] 仪表盘缓存异步刷新失败: %v", err)
return return
} }
s.applyAggregationStatus(ctx, stats) s.applyAggregationStatus(ctx, stats)
...@@ -220,12 +220,12 @@ func (s *DashboardService) saveDashboardStatsCache(ctx context.Context, stats *u ...@@ -220,12 +220,12 @@ func (s *DashboardService) saveDashboardStatsCache(ctx context.Context, stats *u
} }
data, err := json.Marshal(entry) data, err := json.Marshal(entry)
if err != nil { if err != nil {
log.Printf("[Dashboard] 仪表盘缓存序列化失败: %v", err) logger.LegacyPrintf("service.dashboard", "[Dashboard] 仪表盘缓存序列化失败: %v", err)
return return
} }
if err := s.cache.SetDashboardStats(ctx, string(data), s.cacheTTL); err != nil { if err := s.cache.SetDashboardStats(ctx, string(data), s.cacheTTL); err != nil {
log.Printf("[Dashboard] 仪表盘缓存写入失败: %v", err) logger.LegacyPrintf("service.dashboard", "[Dashboard] 仪表盘缓存写入失败: %v", err)
} }
} }
...@@ -237,10 +237,10 @@ func (s *DashboardService) evictDashboardStatsCache(reason error) { ...@@ -237,10 +237,10 @@ func (s *DashboardService) evictDashboardStatsCache(reason error) {
defer cancel() defer cancel()
if err := s.cache.DeleteDashboardStats(cacheCtx); err != nil { if err := s.cache.DeleteDashboardStats(cacheCtx); err != nil {
log.Printf("[Dashboard] 仪表盘缓存清理失败: %v", err) logger.LegacyPrintf("service.dashboard", "[Dashboard] 仪表盘缓存清理失败: %v", err)
} }
if reason != nil { if reason != nil {
log.Printf("[Dashboard] 仪表盘缓存异常,已清理: %v", reason) logger.LegacyPrintf("service.dashboard", "[Dashboard] 仪表盘缓存异常,已清理: %v", reason)
} }
} }
...@@ -271,7 +271,7 @@ func (s *DashboardService) fetchAggregationUpdatedAt(ctx context.Context) time.T ...@@ -271,7 +271,7 @@ func (s *DashboardService) fetchAggregationUpdatedAt(ctx context.Context) time.T
} }
updatedAt, err := s.aggRepo.GetAggregationWatermark(ctx) updatedAt, err := s.aggRepo.GetAggregationWatermark(ctx)
if err != nil { if err != nil {
log.Printf("[Dashboard] 读取聚合水位失败: %v", err) logger.LegacyPrintf("service.dashboard", "[Dashboard] 读取聚合水位失败: %v", err)
return time.Unix(0, 0).UTC() return time.Unix(0, 0).UTC()
} }
if updatedAt.IsZero() { if updatedAt.IsZero() {
......
...@@ -161,6 +161,9 @@ const ( ...@@ -161,6 +161,9 @@ const (
// SettingKeyOpsAdvancedSettings stores JSON config for ops advanced settings (data retention, aggregation). // SettingKeyOpsAdvancedSettings stores JSON config for ops advanced settings (data retention, aggregation).
SettingKeyOpsAdvancedSettings = "ops_advanced_settings" SettingKeyOpsAdvancedSettings = "ops_advanced_settings"
// SettingKeyOpsRuntimeLogConfig stores JSON config for runtime log settings.
SettingKeyOpsRuntimeLogConfig = "ops_runtime_log_config"
// ========================= // =========================
// Stream Timeout Handling // Stream Timeout Handling
// ========================= // =========================
......
...@@ -3,9 +3,10 @@ package service ...@@ -3,9 +3,10 @@ package service
import ( import (
"context" "context"
"fmt" "fmt"
"log"
"sync" "sync"
"time" "time"
"github.com/Wei-Shaw/sub2api/internal/pkg/logger"
) )
// Task type constants // Task type constants
...@@ -56,7 +57,7 @@ func (s *EmailQueueService) start() { ...@@ -56,7 +57,7 @@ func (s *EmailQueueService) start() {
s.wg.Add(1) s.wg.Add(1)
go s.worker(i) go s.worker(i)
} }
log.Printf("[EmailQueue] Started %d workers", s.workers) logger.LegacyPrintf("service.email_queue", "[EmailQueue] Started %d workers", s.workers)
} }
// worker 工作协程 // worker 工作协程
...@@ -68,7 +69,7 @@ func (s *EmailQueueService) worker(id int) { ...@@ -68,7 +69,7 @@ func (s *EmailQueueService) worker(id int) {
case task := <-s.taskChan: case task := <-s.taskChan:
s.processTask(id, task) s.processTask(id, task)
case <-s.stopChan: case <-s.stopChan:
log.Printf("[EmailQueue] Worker %d stopping", id) logger.LegacyPrintf("service.email_queue", "[EmailQueue] Worker %d stopping", id)
return return
} }
} }
...@@ -82,18 +83,18 @@ func (s *EmailQueueService) processTask(workerID int, task EmailTask) { ...@@ -82,18 +83,18 @@ func (s *EmailQueueService) processTask(workerID int, task EmailTask) {
switch task.TaskType { switch task.TaskType {
case TaskTypeVerifyCode: case TaskTypeVerifyCode:
if err := s.emailService.SendVerifyCode(ctx, task.Email, task.SiteName); err != nil { if err := s.emailService.SendVerifyCode(ctx, task.Email, task.SiteName); err != nil {
log.Printf("[EmailQueue] Worker %d failed to send verify code to %s: %v", workerID, task.Email, err) logger.LegacyPrintf("service.email_queue", "[EmailQueue] Worker %d failed to send verify code to %s: %v", workerID, task.Email, err)
} else { } else {
log.Printf("[EmailQueue] Worker %d sent verify code to %s", workerID, task.Email) logger.LegacyPrintf("service.email_queue", "[EmailQueue] Worker %d sent verify code to %s", workerID, task.Email)
} }
case TaskTypePasswordReset: case TaskTypePasswordReset:
if err := s.emailService.SendPasswordResetEmailWithCooldown(ctx, task.Email, task.SiteName, task.ResetURL); err != nil { if err := s.emailService.SendPasswordResetEmailWithCooldown(ctx, task.Email, task.SiteName, task.ResetURL); err != nil {
log.Printf("[EmailQueue] Worker %d failed to send password reset to %s: %v", workerID, task.Email, err) logger.LegacyPrintf("service.email_queue", "[EmailQueue] Worker %d failed to send password reset to %s: %v", workerID, task.Email, err)
} else { } else {
log.Printf("[EmailQueue] Worker %d sent password reset to %s", workerID, task.Email) logger.LegacyPrintf("service.email_queue", "[EmailQueue] Worker %d sent password reset to %s", workerID, task.Email)
} }
default: default:
log.Printf("[EmailQueue] Worker %d unknown task type: %s", workerID, task.TaskType) logger.LegacyPrintf("service.email_queue", "[EmailQueue] Worker %d unknown task type: %s", workerID, task.TaskType)
} }
} }
...@@ -107,7 +108,7 @@ func (s *EmailQueueService) EnqueueVerifyCode(email, siteName string) error { ...@@ -107,7 +108,7 @@ func (s *EmailQueueService) EnqueueVerifyCode(email, siteName string) error {
select { select {
case s.taskChan <- task: case s.taskChan <- task:
log.Printf("[EmailQueue] Enqueued verify code task for %s", email) logger.LegacyPrintf("service.email_queue", "[EmailQueue] Enqueued verify code task for %s", email)
return nil return nil
default: default:
return fmt.Errorf("email queue is full") return fmt.Errorf("email queue is full")
...@@ -125,7 +126,7 @@ func (s *EmailQueueService) EnqueuePasswordReset(email, siteName, resetURL strin ...@@ -125,7 +126,7 @@ func (s *EmailQueueService) EnqueuePasswordReset(email, siteName, resetURL strin
select { select {
case s.taskChan <- task: case s.taskChan <- task:
log.Printf("[EmailQueue] Enqueued password reset task for %s", email) logger.LegacyPrintf("service.email_queue", "[EmailQueue] Enqueued password reset task for %s", email)
return nil return nil
default: default:
return fmt.Errorf("email queue is full") return fmt.Errorf("email queue is full")
...@@ -136,5 +137,5 @@ func (s *EmailQueueService) EnqueuePasswordReset(email, siteName, resetURL strin ...@@ -136,5 +137,5 @@ func (s *EmailQueueService) EnqueuePasswordReset(email, siteName, resetURL strin
func (s *EmailQueueService) Stop() { func (s *EmailQueueService) Stop() {
close(s.stopChan) close(s.stopChan)
s.wg.Wait() s.wg.Wait()
log.Println("[EmailQueue] All workers stopped") logger.LegacyPrintf("service.email_queue", "%s", "[EmailQueue] All workers stopped")
} }
...@@ -2,13 +2,13 @@ package service ...@@ -2,13 +2,13 @@ package service
import ( import (
"context" "context"
"log"
"sort" "sort"
"strings" "strings"
"sync" "sync"
"time" "time"
"github.com/Wei-Shaw/sub2api/internal/model" "github.com/Wei-Shaw/sub2api/internal/model"
"github.com/Wei-Shaw/sub2api/internal/pkg/logger"
) )
// ErrorPassthroughRepository 定义错误透传规则的数据访问接口 // ErrorPassthroughRepository 定义错误透传规则的数据访问接口
...@@ -62,9 +62,9 @@ func NewErrorPassthroughService( ...@@ -62,9 +62,9 @@ func NewErrorPassthroughService(
// 启动时加载规则到本地缓存 // 启动时加载规则到本地缓存
ctx := context.Background() ctx := context.Background()
if err := svc.reloadRulesFromDB(ctx); err != nil { if err := svc.reloadRulesFromDB(ctx); err != nil {
log.Printf("[ErrorPassthroughService] Failed to load rules from DB on startup: %v", err) logger.LegacyPrintf("service.error_passthrough", "[ErrorPassthroughService] Failed to load rules from DB on startup: %v", err)
if fallbackErr := svc.refreshLocalCache(ctx); fallbackErr != nil { if fallbackErr := svc.refreshLocalCache(ctx); fallbackErr != nil {
log.Printf("[ErrorPassthroughService] Failed to load rules from cache fallback on startup: %v", fallbackErr) logger.LegacyPrintf("service.error_passthrough", "[ErrorPassthroughService] Failed to load rules from cache fallback on startup: %v", fallbackErr)
} }
} }
...@@ -72,7 +72,7 @@ func NewErrorPassthroughService( ...@@ -72,7 +72,7 @@ func NewErrorPassthroughService(
if cache != nil { if cache != nil {
cache.SubscribeUpdates(ctx, func() { cache.SubscribeUpdates(ctx, func() {
if err := svc.refreshLocalCache(context.Background()); err != nil { if err := svc.refreshLocalCache(context.Background()); err != nil {
log.Printf("[ErrorPassthroughService] Failed to refresh cache on notification: %v", err) logger.LegacyPrintf("service.error_passthrough", "[ErrorPassthroughService] Failed to refresh cache on notification: %v", err)
} }
}) })
} }
...@@ -180,7 +180,7 @@ func (s *ErrorPassthroughService) getCachedRules() []*model.ErrorPassthroughRule ...@@ -180,7 +180,7 @@ func (s *ErrorPassthroughService) getCachedRules() []*model.ErrorPassthroughRule
// 如果本地缓存为空,尝试刷新 // 如果本地缓存为空,尝试刷新
ctx := context.Background() ctx := context.Background()
if err := s.refreshLocalCache(ctx); err != nil { if err := s.refreshLocalCache(ctx); err != nil {
log.Printf("[ErrorPassthroughService] Failed to refresh cache: %v", err) logger.LegacyPrintf("service.error_passthrough", "[ErrorPassthroughService] Failed to refresh cache: %v", err)
return nil return nil
} }
...@@ -213,7 +213,7 @@ func (s *ErrorPassthroughService) reloadRulesFromDB(ctx context.Context) error { ...@@ -213,7 +213,7 @@ func (s *ErrorPassthroughService) reloadRulesFromDB(ctx context.Context) error {
// 更新 Redis 缓存 // 更新 Redis 缓存
if s.cache != nil { if s.cache != nil {
if err := s.cache.Set(ctx, rules); err != nil { if err := s.cache.Set(ctx, rules); err != nil {
log.Printf("[ErrorPassthroughService] Failed to set cache: %v", err) logger.LegacyPrintf("service.error_passthrough", "[ErrorPassthroughService] Failed to set cache: %v", err)
} }
} }
...@@ -254,13 +254,13 @@ func (s *ErrorPassthroughService) invalidateAndNotify(ctx context.Context) { ...@@ -254,13 +254,13 @@ func (s *ErrorPassthroughService) invalidateAndNotify(ctx context.Context) {
// 先失效缓存,避免后续刷新读到陈旧规则。 // 先失效缓存,避免后续刷新读到陈旧规则。
if s.cache != nil { if s.cache != nil {
if err := s.cache.Invalidate(ctx); err != nil { if err := s.cache.Invalidate(ctx); err != nil {
log.Printf("[ErrorPassthroughService] Failed to invalidate cache: %v", err) logger.LegacyPrintf("service.error_passthrough", "[ErrorPassthroughService] Failed to invalidate cache: %v", err)
} }
} }
// 刷新本地缓存 // 刷新本地缓存
if err := s.reloadRulesFromDB(ctx); err != nil { if err := s.reloadRulesFromDB(ctx); err != nil {
log.Printf("[ErrorPassthroughService] Failed to refresh local cache: %v", err) logger.LegacyPrintf("service.error_passthrough", "[ErrorPassthroughService] Failed to refresh local cache: %v", err)
// 刷新失败时清空本地缓存,避免继续使用陈旧规则。 // 刷新失败时清空本地缓存,避免继续使用陈旧规则。
s.clearLocalCache() s.clearLocalCache()
} }
...@@ -268,7 +268,7 @@ func (s *ErrorPassthroughService) invalidateAndNotify(ctx context.Context) { ...@@ -268,7 +268,7 @@ func (s *ErrorPassthroughService) invalidateAndNotify(ctx context.Context) {
// 通知其他实例 // 通知其他实例
if s.cache != nil { if s.cache != nil {
if err := s.cache.NotifyUpdate(ctx); err != nil { if err := s.cache.NotifyUpdate(ctx); err != nil {
log.Printf("[ErrorPassthroughService] Failed to notify cache update: %v", err) logger.LegacyPrintf("service.error_passthrough", "[ErrorPassthroughService] Failed to notify cache update: %v", err)
} }
} }
} }
......
...@@ -9,7 +9,6 @@ import ( ...@@ -9,7 +9,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"log"
"log/slog" "log/slog"
mathrand "math/rand" mathrand "math/rand"
"net/http" "net/http"
...@@ -24,6 +23,7 @@ import ( ...@@ -24,6 +23,7 @@ import (
"github.com/Wei-Shaw/sub2api/internal/config" "github.com/Wei-Shaw/sub2api/internal/config"
"github.com/Wei-Shaw/sub2api/internal/pkg/claude" "github.com/Wei-Shaw/sub2api/internal/pkg/claude"
"github.com/Wei-Shaw/sub2api/internal/pkg/ctxkey" "github.com/Wei-Shaw/sub2api/internal/pkg/ctxkey"
"github.com/Wei-Shaw/sub2api/internal/pkg/logger"
"github.com/Wei-Shaw/sub2api/internal/util/responseheaders" "github.com/Wei-Shaw/sub2api/internal/util/responseheaders"
"github.com/Wei-Shaw/sub2api/internal/util/urlvalidator" "github.com/Wei-Shaw/sub2api/internal/util/urlvalidator"
"github.com/cespare/xxhash/v2" "github.com/cespare/xxhash/v2"
...@@ -213,7 +213,7 @@ func logClaudeMimicDebug(req *http.Request, body []byte, account *Account, token ...@@ -213,7 +213,7 @@ func logClaudeMimicDebug(req *http.Request, body []byte, account *Account, token
if line == "" { if line == "" {
return return
} }
log.Printf("[ClaudeMimicDebug] %s", line) logger.LegacyPrintf("service.gateway", "[ClaudeMimicDebug] %s", line)
} }
func isClaudeCodeCredentialScopeError(msg string) bool { func isClaudeCodeCredentialScopeError(msg string) bool {
...@@ -936,7 +936,7 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro ...@@ -936,7 +936,7 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro
if group != nil { if group != nil {
groupPlatform = group.Platform groupPlatform = group.Platform
} }
log.Printf("[ModelRoutingDebug] select entry: group_id=%v group_platform=%s model=%s session=%s sticky_account=%d load_batch=%v concurrency=%v", logger.LegacyPrintf("service.gateway", "[ModelRoutingDebug] select entry: group_id=%v group_platform=%s model=%s session=%s sticky_account=%d load_batch=%v concurrency=%v",
derefGroupID(groupID), groupPlatform, requestedModel, shortSessionHash(sessionHash), stickyAccountID, cfg.LoadBatchEnabled, s.concurrencyService != nil) derefGroupID(groupID), groupPlatform, requestedModel, shortSessionHash(sessionHash), stickyAccountID, cfg.LoadBatchEnabled, s.concurrencyService != nil)
} }
...@@ -1006,7 +1006,7 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro ...@@ -1006,7 +1006,7 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro
} }
preferOAuth := platform == PlatformGemini preferOAuth := platform == PlatformGemini
if s.debugModelRoutingEnabled() && platform == PlatformAnthropic && requestedModel != "" { if s.debugModelRoutingEnabled() && platform == PlatformAnthropic && requestedModel != "" {
log.Printf("[ModelRoutingDebug] load-aware enabled: group_id=%v model=%s session=%s platform=%s", derefGroupID(groupID), requestedModel, shortSessionHash(sessionHash), platform) logger.LegacyPrintf("service.gateway", "[ModelRoutingDebug] load-aware enabled: group_id=%v model=%s session=%s platform=%s", derefGroupID(groupID), requestedModel, shortSessionHash(sessionHash), platform)
} }
accounts, useMixed, err := s.listSchedulableAccounts(ctx, groupID, platform, hasForcePlatform) accounts, useMixed, err := s.listSchedulableAccounts(ctx, groupID, platform, hasForcePlatform)
...@@ -1036,7 +1036,7 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro ...@@ -1036,7 +1036,7 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro
if group != nil && requestedModel != "" && group.Platform == PlatformAnthropic { if group != nil && requestedModel != "" && group.Platform == PlatformAnthropic {
routingAccountIDs = group.GetRoutingAccountIDs(requestedModel) routingAccountIDs = group.GetRoutingAccountIDs(requestedModel)
if s.debugModelRoutingEnabled() { if s.debugModelRoutingEnabled() {
log.Printf("[ModelRoutingDebug] context group routing: group_id=%d model=%s enabled=%v rules=%d matched_ids=%v session=%s sticky_account=%d", logger.LegacyPrintf("service.gateway", "[ModelRoutingDebug] context group routing: group_id=%d model=%s enabled=%v rules=%d matched_ids=%v session=%s sticky_account=%d",
group.ID, requestedModel, group.ModelRoutingEnabled, len(group.ModelRouting), routingAccountIDs, shortSessionHash(sessionHash), stickyAccountID) group.ID, requestedModel, group.ModelRoutingEnabled, len(group.ModelRouting), routingAccountIDs, shortSessionHash(sessionHash), stickyAccountID)
if len(routingAccountIDs) == 0 && group.ModelRoutingEnabled && len(group.ModelRouting) > 0 { if len(routingAccountIDs) == 0 && group.ModelRoutingEnabled && len(group.ModelRouting) > 0 {
keys := make([]string, 0, len(group.ModelRouting)) keys := make([]string, 0, len(group.ModelRouting))
...@@ -1048,7 +1048,7 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro ...@@ -1048,7 +1048,7 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro
if len(keys) > maxKeys { if len(keys) > maxKeys {
keys = keys[:maxKeys] keys = keys[:maxKeys]
} }
log.Printf("[ModelRoutingDebug] context group routing miss: group_id=%d model=%s patterns(sample)=%v", group.ID, requestedModel, keys) logger.LegacyPrintf("service.gateway", "[ModelRoutingDebug] context group routing miss: group_id=%d model=%s patterns(sample)=%v", group.ID, requestedModel, keys)
} }
} }
} }
...@@ -1095,11 +1095,11 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro ...@@ -1095,11 +1095,11 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro
} }
if s.debugModelRoutingEnabled() { if s.debugModelRoutingEnabled() {
log.Printf("[ModelRoutingDebug] routed candidates: group_id=%v model=%s routed=%d candidates=%d filtered(excluded=%d missing=%d unsched=%d platform=%d model_scope=%d model_mapping=%d window_cost=%d)", logger.LegacyPrintf("service.gateway", "[ModelRoutingDebug] routed candidates: group_id=%v model=%s routed=%d candidates=%d filtered(excluded=%d missing=%d unsched=%d platform=%d model_scope=%d model_mapping=%d window_cost=%d)",
derefGroupID(groupID), requestedModel, len(routingAccountIDs), len(routingCandidates), derefGroupID(groupID), requestedModel, len(routingAccountIDs), len(routingCandidates),
filteredExcluded, filteredMissing, filteredUnsched, filteredPlatform, filteredModelScope, filteredModelMapping, filteredWindowCost) filteredExcluded, filteredMissing, filteredUnsched, filteredPlatform, filteredModelScope, filteredModelMapping, filteredWindowCost)
if len(modelScopeSkippedIDs) > 0 { if len(modelScopeSkippedIDs) > 0 {
log.Printf("[ModelRoutingDebug] model_rate_limited accounts skipped: group_id=%v model=%s account_ids=%v", logger.LegacyPrintf("service.gateway", "[ModelRoutingDebug] model_rate_limited accounts skipped: group_id=%v model=%s account_ids=%v",
derefGroupID(groupID), requestedModel, modelScopeSkippedIDs) derefGroupID(groupID), requestedModel, modelScopeSkippedIDs)
} }
} }
...@@ -1124,7 +1124,7 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro ...@@ -1124,7 +1124,7 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro
// 继续到负载感知选择 // 继续到负载感知选择
} else { } else {
if s.debugModelRoutingEnabled() { if s.debugModelRoutingEnabled() {
log.Printf("[ModelRoutingDebug] routed sticky hit: group_id=%v model=%s session=%s account=%d", derefGroupID(groupID), requestedModel, shortSessionHash(sessionHash), stickyAccountID) logger.LegacyPrintf("service.gateway", "[ModelRoutingDebug] routed sticky hit: group_id=%v model=%s session=%s account=%d", derefGroupID(groupID), requestedModel, shortSessionHash(sessionHash), stickyAccountID)
} }
return &AccountSelectionResult{ return &AccountSelectionResult{
Account: stickyAccount, Account: stickyAccount,
...@@ -1217,7 +1217,7 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro ...@@ -1217,7 +1217,7 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro
_ = s.cache.SetSessionAccountID(ctx, derefGroupID(groupID), sessionHash, item.account.ID, stickySessionTTL) _ = s.cache.SetSessionAccountID(ctx, derefGroupID(groupID), sessionHash, item.account.ID, stickySessionTTL)
} }
if s.debugModelRoutingEnabled() { if s.debugModelRoutingEnabled() {
log.Printf("[ModelRoutingDebug] routed select: group_id=%v model=%s session=%s account=%d", derefGroupID(groupID), requestedModel, shortSessionHash(sessionHash), item.account.ID) logger.LegacyPrintf("service.gateway", "[ModelRoutingDebug] routed select: group_id=%v model=%s session=%s account=%d", derefGroupID(groupID), requestedModel, shortSessionHash(sessionHash), item.account.ID)
} }
return &AccountSelectionResult{ return &AccountSelectionResult{
Account: item.account, Account: item.account,
...@@ -1234,7 +1234,7 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro ...@@ -1234,7 +1234,7 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro
continue // 会话限制已满,尝试下一个 continue // 会话限制已满,尝试下一个
} }
if s.debugModelRoutingEnabled() { if s.debugModelRoutingEnabled() {
log.Printf("[ModelRoutingDebug] routed wait: group_id=%v model=%s session=%s account=%d", derefGroupID(groupID), requestedModel, shortSessionHash(sessionHash), item.account.ID) logger.LegacyPrintf("service.gateway", "[ModelRoutingDebug] routed wait: group_id=%v model=%s session=%s account=%d", derefGroupID(groupID), requestedModel, shortSessionHash(sessionHash), item.account.ID)
} }
return &AccountSelectionResult{ return &AccountSelectionResult{
Account: item.account, Account: item.account,
...@@ -1249,7 +1249,7 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro ...@@ -1249,7 +1249,7 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro
// 所有路由账号会话限制都已满,继续到 Layer 2 回退 // 所有路由账号会话限制都已满,继续到 Layer 2 回退
} }
// 路由列表中的账号都不可用(负载率 >= 100),继续到 Layer 2 回退 // 路由列表中的账号都不可用(负载率 >= 100),继续到 Layer 2 回退
log.Printf("[ModelRouting] All routed accounts unavailable for model=%s, falling back to normal selection", requestedModel) logger.LegacyPrintf("service.gateway", "[ModelRouting] All routed accounts unavailable for model=%s, falling back to normal selection", requestedModel)
} }
} }
...@@ -1510,20 +1510,20 @@ func (s *GatewayService) routingAccountIDsForRequest(ctx context.Context, groupI ...@@ -1510,20 +1510,20 @@ func (s *GatewayService) routingAccountIDsForRequest(ctx context.Context, groupI
group, err := s.resolveGroupByID(ctx, *groupID) group, err := s.resolveGroupByID(ctx, *groupID)
if err != nil || group == nil { if err != nil || group == nil {
if s.debugModelRoutingEnabled() { if s.debugModelRoutingEnabled() {
log.Printf("[ModelRoutingDebug] resolve group failed: group_id=%v model=%s platform=%s err=%v", derefGroupID(groupID), requestedModel, platform, err) logger.LegacyPrintf("service.gateway", "[ModelRoutingDebug] resolve group failed: group_id=%v model=%s platform=%s err=%v", derefGroupID(groupID), requestedModel, platform, err)
} }
return nil return nil
} }
// Preserve existing behavior: model routing only applies to anthropic groups. // Preserve existing behavior: model routing only applies to anthropic groups.
if group.Platform != PlatformAnthropic { if group.Platform != PlatformAnthropic {
if s.debugModelRoutingEnabled() { if s.debugModelRoutingEnabled() {
log.Printf("[ModelRoutingDebug] skip: non-anthropic group platform: group_id=%d group_platform=%s model=%s", group.ID, group.Platform, requestedModel) logger.LegacyPrintf("service.gateway", "[ModelRoutingDebug] skip: non-anthropic group platform: group_id=%d group_platform=%s model=%s", group.ID, group.Platform, requestedModel)
} }
return nil return nil
} }
ids := group.GetRoutingAccountIDs(requestedModel) ids := group.GetRoutingAccountIDs(requestedModel)
if s.debugModelRoutingEnabled() { if s.debugModelRoutingEnabled() {
log.Printf("[ModelRoutingDebug] routing lookup: group_id=%d model=%s enabled=%v rules=%d matched_ids=%v", logger.LegacyPrintf("service.gateway", "[ModelRoutingDebug] routing lookup: group_id=%d model=%s enabled=%v rules=%d matched_ids=%v",
group.ID, requestedModel, group.ModelRoutingEnabled, len(group.ModelRouting), ids) group.ID, requestedModel, group.ModelRoutingEnabled, len(group.ModelRouting), ids)
} }
return ids return ids
...@@ -2117,7 +2117,7 @@ func (s *GatewayService) selectAccountForModelWithPlatform(ctx context.Context, ...@@ -2117,7 +2117,7 @@ func (s *GatewayService) selectAccountForModelWithPlatform(ctx context.Context,
// so switching model can switch upstream account within the same sticky session. // so switching model can switch upstream account within the same sticky session.
if len(routingAccountIDs) > 0 { if len(routingAccountIDs) > 0 {
if s.debugModelRoutingEnabled() { if s.debugModelRoutingEnabled() {
log.Printf("[ModelRoutingDebug] legacy routed begin: group_id=%v model=%s platform=%s session=%s routed_ids=%v", logger.LegacyPrintf("service.gateway", "[ModelRoutingDebug] legacy routed begin: group_id=%v model=%s platform=%s session=%s routed_ids=%v",
derefGroupID(groupID), requestedModel, platform, shortSessionHash(sessionHash), routingAccountIDs) derefGroupID(groupID), requestedModel, platform, shortSessionHash(sessionHash), routingAccountIDs)
} }
// 1) Sticky session only applies if the bound account is within the routing set. // 1) Sticky session only applies if the bound account is within the routing set.
...@@ -2134,7 +2134,7 @@ func (s *GatewayService) selectAccountForModelWithPlatform(ctx context.Context, ...@@ -2134,7 +2134,7 @@ func (s *GatewayService) selectAccountForModelWithPlatform(ctx context.Context,
} }
if !clearSticky && s.isAccountInGroup(account, groupID) && account.Platform == platform && (requestedModel == "" || s.isModelSupportedByAccountWithContext(ctx, account, requestedModel)) && account.IsSchedulableForModelWithContext(ctx, requestedModel) { if !clearSticky && s.isAccountInGroup(account, groupID) && account.Platform == platform && (requestedModel == "" || s.isModelSupportedByAccountWithContext(ctx, account, requestedModel)) && account.IsSchedulableForModelWithContext(ctx, requestedModel) {
if s.debugModelRoutingEnabled() { if s.debugModelRoutingEnabled() {
log.Printf("[ModelRoutingDebug] legacy routed sticky hit: group_id=%v model=%s session=%s account=%d", derefGroupID(groupID), requestedModel, shortSessionHash(sessionHash), accountID) logger.LegacyPrintf("service.gateway", "[ModelRoutingDebug] legacy routed sticky hit: group_id=%v model=%s session=%s account=%d", derefGroupID(groupID), requestedModel, shortSessionHash(sessionHash), accountID)
} }
return account, nil return account, nil
} }
...@@ -2209,15 +2209,15 @@ func (s *GatewayService) selectAccountForModelWithPlatform(ctx context.Context, ...@@ -2209,15 +2209,15 @@ func (s *GatewayService) selectAccountForModelWithPlatform(ctx context.Context,
if selected != nil { if selected != nil {
if sessionHash != "" && s.cache != nil { if sessionHash != "" && s.cache != nil {
if err := s.cache.SetSessionAccountID(ctx, derefGroupID(groupID), sessionHash, selected.ID, stickySessionTTL); err != nil { if err := s.cache.SetSessionAccountID(ctx, derefGroupID(groupID), sessionHash, selected.ID, stickySessionTTL); err != nil {
log.Printf("set session account failed: session=%s account_id=%d err=%v", sessionHash, selected.ID, err) logger.LegacyPrintf("service.gateway", "set session account failed: session=%s account_id=%d err=%v", sessionHash, selected.ID, err)
} }
} }
if s.debugModelRoutingEnabled() { if s.debugModelRoutingEnabled() {
log.Printf("[ModelRoutingDebug] legacy routed select: group_id=%v model=%s session=%s account=%d", derefGroupID(groupID), requestedModel, shortSessionHash(sessionHash), selected.ID) logger.LegacyPrintf("service.gateway", "[ModelRoutingDebug] legacy routed select: group_id=%v model=%s session=%s account=%d", derefGroupID(groupID), requestedModel, shortSessionHash(sessionHash), selected.ID)
} }
return selected, nil return selected, nil
} }
log.Printf("[ModelRouting] No routed accounts available for model=%s, falling back to normal selection", requestedModel) logger.LegacyPrintf("service.gateway", "[ModelRouting] No routed accounts available for model=%s, falling back to normal selection", requestedModel)
} }
// 1. 查询粘性会话 // 1. 查询粘性会话
...@@ -2305,7 +2305,7 @@ func (s *GatewayService) selectAccountForModelWithPlatform(ctx context.Context, ...@@ -2305,7 +2305,7 @@ func (s *GatewayService) selectAccountForModelWithPlatform(ctx context.Context,
// 4. 建立粘性绑定 // 4. 建立粘性绑定
if sessionHash != "" && s.cache != nil { if sessionHash != "" && s.cache != nil {
if err := s.cache.SetSessionAccountID(ctx, derefGroupID(groupID), sessionHash, selected.ID, stickySessionTTL); err != nil { if err := s.cache.SetSessionAccountID(ctx, derefGroupID(groupID), sessionHash, selected.ID, stickySessionTTL); err != nil {
log.Printf("set session account failed: session=%s account_id=%d err=%v", sessionHash, selected.ID, err) logger.LegacyPrintf("service.gateway", "set session account failed: session=%s account_id=%d err=%v", sessionHash, selected.ID, err)
} }
} }
...@@ -2324,7 +2324,7 @@ func (s *GatewayService) selectAccountWithMixedScheduling(ctx context.Context, g ...@@ -2324,7 +2324,7 @@ func (s *GatewayService) selectAccountWithMixedScheduling(ctx context.Context, g
// ============ Model Routing (legacy path): apply before sticky session ============ // ============ Model Routing (legacy path): apply before sticky session ============
if len(routingAccountIDs) > 0 { if len(routingAccountIDs) > 0 {
if s.debugModelRoutingEnabled() { if s.debugModelRoutingEnabled() {
log.Printf("[ModelRoutingDebug] legacy mixed routed begin: group_id=%v model=%s platform=%s session=%s routed_ids=%v", logger.LegacyPrintf("service.gateway", "[ModelRoutingDebug] legacy mixed routed begin: group_id=%v model=%s platform=%s session=%s routed_ids=%v",
derefGroupID(groupID), requestedModel, nativePlatform, shortSessionHash(sessionHash), routingAccountIDs) derefGroupID(groupID), requestedModel, nativePlatform, shortSessionHash(sessionHash), routingAccountIDs)
} }
// 1) Sticky session only applies if the bound account is within the routing set. // 1) Sticky session only applies if the bound account is within the routing set.
...@@ -2342,7 +2342,7 @@ func (s *GatewayService) selectAccountWithMixedScheduling(ctx context.Context, g ...@@ -2342,7 +2342,7 @@ func (s *GatewayService) selectAccountWithMixedScheduling(ctx context.Context, g
if !clearSticky && s.isAccountInGroup(account, groupID) && (requestedModel == "" || s.isModelSupportedByAccountWithContext(ctx, account, requestedModel)) && account.IsSchedulableForModelWithContext(ctx, requestedModel) { if !clearSticky && s.isAccountInGroup(account, groupID) && (requestedModel == "" || s.isModelSupportedByAccountWithContext(ctx, account, requestedModel)) && account.IsSchedulableForModelWithContext(ctx, requestedModel) {
if account.Platform == nativePlatform || (account.Platform == PlatformAntigravity && account.IsMixedSchedulingEnabled()) { if account.Platform == nativePlatform || (account.Platform == PlatformAntigravity && account.IsMixedSchedulingEnabled()) {
if s.debugModelRoutingEnabled() { if s.debugModelRoutingEnabled() {
log.Printf("[ModelRoutingDebug] legacy mixed routed sticky hit: group_id=%v model=%s session=%s account=%d", derefGroupID(groupID), requestedModel, shortSessionHash(sessionHash), accountID) logger.LegacyPrintf("service.gateway", "[ModelRoutingDebug] legacy mixed routed sticky hit: group_id=%v model=%s session=%s account=%d", derefGroupID(groupID), requestedModel, shortSessionHash(sessionHash), accountID)
} }
return account, nil return account, nil
} }
...@@ -2418,15 +2418,15 @@ func (s *GatewayService) selectAccountWithMixedScheduling(ctx context.Context, g ...@@ -2418,15 +2418,15 @@ func (s *GatewayService) selectAccountWithMixedScheduling(ctx context.Context, g
if selected != nil { if selected != nil {
if sessionHash != "" && s.cache != nil { if sessionHash != "" && s.cache != nil {
if err := s.cache.SetSessionAccountID(ctx, derefGroupID(groupID), sessionHash, selected.ID, stickySessionTTL); err != nil { if err := s.cache.SetSessionAccountID(ctx, derefGroupID(groupID), sessionHash, selected.ID, stickySessionTTL); err != nil {
log.Printf("set session account failed: session=%s account_id=%d err=%v", sessionHash, selected.ID, err) logger.LegacyPrintf("service.gateway", "set session account failed: session=%s account_id=%d err=%v", sessionHash, selected.ID, err)
} }
} }
if s.debugModelRoutingEnabled() { if s.debugModelRoutingEnabled() {
log.Printf("[ModelRoutingDebug] legacy mixed routed select: group_id=%v model=%s session=%s account=%d", derefGroupID(groupID), requestedModel, shortSessionHash(sessionHash), selected.ID) logger.LegacyPrintf("service.gateway", "[ModelRoutingDebug] legacy mixed routed select: group_id=%v model=%s session=%s account=%d", derefGroupID(groupID), requestedModel, shortSessionHash(sessionHash), selected.ID)
} }
return selected, nil return selected, nil
} }
log.Printf("[ModelRouting] No routed accounts available for model=%s, falling back to normal selection", requestedModel) logger.LegacyPrintf("service.gateway", "[ModelRouting] No routed accounts available for model=%s, falling back to normal selection", requestedModel)
} }
// 1. 查询粘性会话 // 1. 查询粘性会话
...@@ -2516,7 +2516,7 @@ func (s *GatewayService) selectAccountWithMixedScheduling(ctx context.Context, g ...@@ -2516,7 +2516,7 @@ func (s *GatewayService) selectAccountWithMixedScheduling(ctx context.Context, g
// 4. 建立粘性绑定 // 4. 建立粘性绑定
if sessionHash != "" && s.cache != nil { if sessionHash != "" && s.cache != nil {
if err := s.cache.SetSessionAccountID(ctx, derefGroupID(groupID), sessionHash, selected.ID, stickySessionTTL); err != nil { if err := s.cache.SetSessionAccountID(ctx, derefGroupID(groupID), sessionHash, selected.ID, stickySessionTTL); err != nil {
log.Printf("set session account failed: session=%s account_id=%d err=%v", sessionHash, selected.ID, err) logger.LegacyPrintf("service.gateway", "set session account failed: session=%s account_id=%d err=%v", sessionHash, selected.ID, err)
} }
} }
...@@ -2831,7 +2831,7 @@ func injectClaudeCodePrompt(body []byte, system any) []byte { ...@@ -2831,7 +2831,7 @@ func injectClaudeCodePrompt(body []byte, system any) []byte {
result, err := sjson.SetBytes(body, "system", newSystem) result, err := sjson.SetBytes(body, "system", newSystem)
if err != nil { if err != nil {
log.Printf("Warning: failed to inject Claude Code prompt: %v", err) logger.LegacyPrintf("service.gateway", "Warning: failed to inject Claude Code prompt: %v", err)
return body return body
} }
return result return result
...@@ -2987,7 +2987,7 @@ func removeCacheControlFromThinkingBlocks(data map[string]any) { ...@@ -2987,7 +2987,7 @@ func removeCacheControlFromThinkingBlocks(data map[string]any) {
if blockType, _ := m["type"].(string); blockType == "thinking" { if blockType, _ := m["type"].(string); blockType == "thinking" {
if _, has := m["cache_control"]; has { if _, has := m["cache_control"]; has {
delete(m, "cache_control") delete(m, "cache_control")
log.Printf("[Warning] Removed illegal cache_control from thinking block in system") logger.LegacyPrintf("service.gateway", "[Warning] Removed illegal cache_control from thinking block in system")
} }
} }
} }
...@@ -3004,7 +3004,7 @@ func removeCacheControlFromThinkingBlocks(data map[string]any) { ...@@ -3004,7 +3004,7 @@ func removeCacheControlFromThinkingBlocks(data map[string]any) {
if blockType, _ := m["type"].(string); blockType == "thinking" { if blockType, _ := m["type"].(string); blockType == "thinking" {
if _, has := m["cache_control"]; has { if _, has := m["cache_control"]; has {
delete(m, "cache_control") delete(m, "cache_control")
log.Printf("[Warning] Removed illegal cache_control from thinking block in messages[%d].content[%d]", msgIdx, contentIdx) logger.LegacyPrintf("service.gateway", "[Warning] Removed illegal cache_control from thinking block in messages[%d].content[%d]", msgIdx, contentIdx)
} }
} }
} }
...@@ -3083,7 +3083,7 @@ func (s *GatewayService) Forward(ctx context.Context, c *gin.Context, account *A ...@@ -3083,7 +3083,7 @@ func (s *GatewayService) Forward(ctx context.Context, c *gin.Context, account *A
// 替换请求体中的模型名 // 替换请求体中的模型名
body = s.replaceModelInBody(body, mappedModel) body = s.replaceModelInBody(body, mappedModel)
reqModel = mappedModel reqModel = mappedModel
log.Printf("Model mapping applied: %s -> %s (account: %s, source=%s)", originalModel, mappedModel, account.Name, mappingSource) logger.LegacyPrintf("service.gateway", "Model mapping applied: %s -> %s (account: %s, source=%s)", originalModel, mappedModel, account.Name, mappingSource)
} }
// 获取凭证 // 获取凭证
...@@ -3099,7 +3099,7 @@ func (s *GatewayService) Forward(ctx context.Context, c *gin.Context, account *A ...@@ -3099,7 +3099,7 @@ func (s *GatewayService) Forward(ctx context.Context, c *gin.Context, account *A
} }
// 调试日志:记录即将转发的账号信息 // 调试日志:记录即将转发的账号信息
log.Printf("[Forward] Using account: ID=%d Name=%s Platform=%s Type=%s TLSFingerprint=%v Proxy=%s", logger.LegacyPrintf("service.gateway", "[Forward] Using account: ID=%d Name=%s Platform=%s Type=%s TLSFingerprint=%v Proxy=%s",
account.ID, account.Name, account.Platform, account.Type, account.IsTLSFingerprintEnabled(), proxyURL) account.ID, account.Name, account.Platform, account.Type, account.IsTLSFingerprintEnabled(), proxyURL)
// 重试循环 // 重试循环
...@@ -3179,7 +3179,7 @@ func (s *GatewayService) Forward(ctx context.Context, c *gin.Context, account *A ...@@ -3179,7 +3179,7 @@ func (s *GatewayService) Forward(ctx context.Context, c *gin.Context, account *A
resp.Body = io.NopCloser(bytes.NewReader(respBody)) resp.Body = io.NopCloser(bytes.NewReader(respBody))
break break
} }
log.Printf("Account %d: detected thinking block signature error, retrying with filtered thinking blocks", account.ID) logger.LegacyPrintf("service.gateway", "Account %d: detected thinking block signature error, retrying with filtered thinking blocks", account.ID)
// Conservative two-stage fallback: // Conservative two-stage fallback:
// 1) Disable thinking + thinking->text (preserve content) // 1) Disable thinking + thinking->text (preserve content)
...@@ -3192,7 +3192,7 @@ func (s *GatewayService) Forward(ctx context.Context, c *gin.Context, account *A ...@@ -3192,7 +3192,7 @@ func (s *GatewayService) Forward(ctx context.Context, c *gin.Context, account *A
retryResp, retryErr := s.httpUpstream.DoWithTLS(retryReq, proxyURL, account.ID, account.Concurrency, account.IsTLSFingerprintEnabled()) retryResp, retryErr := s.httpUpstream.DoWithTLS(retryReq, proxyURL, account.ID, account.Concurrency, account.IsTLSFingerprintEnabled())
if retryErr == nil { if retryErr == nil {
if retryResp.StatusCode < 400 { if retryResp.StatusCode < 400 {
log.Printf("Account %d: signature error retry succeeded (thinking downgraded)", account.ID) logger.LegacyPrintf("service.gateway", "Account %d: signature error retry succeeded (thinking downgraded)", account.ID)
resp = retryResp resp = retryResp
break break
} }
...@@ -3217,7 +3217,7 @@ func (s *GatewayService) Forward(ctx context.Context, c *gin.Context, account *A ...@@ -3217,7 +3217,7 @@ func (s *GatewayService) Forward(ctx context.Context, c *gin.Context, account *A
}) })
msg2 := extractUpstreamErrorMessage(retryRespBody) msg2 := extractUpstreamErrorMessage(retryRespBody)
if looksLikeToolSignatureError(msg2) && time.Since(retryStart) < maxRetryElapsed { if looksLikeToolSignatureError(msg2) && time.Since(retryStart) < maxRetryElapsed {
log.Printf("Account %d: signature retry still failing and looks tool-related, retrying with tool blocks downgraded", account.ID) logger.LegacyPrintf("service.gateway", "Account %d: signature retry still failing and looks tool-related, retrying with tool blocks downgraded", account.ID)
filteredBody2 := FilterSignatureSensitiveBlocksForRetry(body) filteredBody2 := FilterSignatureSensitiveBlocksForRetry(body)
retryReq2, buildErr2 := s.buildUpstreamRequest(ctx, c, account, filteredBody2, token, tokenType, reqModel, reqStream, shouldMimicClaudeCode) retryReq2, buildErr2 := s.buildUpstreamRequest(ctx, c, account, filteredBody2, token, tokenType, reqModel, reqStream, shouldMimicClaudeCode)
if buildErr2 == nil { if buildErr2 == nil {
...@@ -3237,9 +3237,9 @@ func (s *GatewayService) Forward(ctx context.Context, c *gin.Context, account *A ...@@ -3237,9 +3237,9 @@ func (s *GatewayService) Forward(ctx context.Context, c *gin.Context, account *A
Kind: "signature_retry_tools_request_error", Kind: "signature_retry_tools_request_error",
Message: sanitizeUpstreamErrorMessage(retryErr2.Error()), Message: sanitizeUpstreamErrorMessage(retryErr2.Error()),
}) })
log.Printf("Account %d: tool-downgrade signature retry failed: %v", account.ID, retryErr2) logger.LegacyPrintf("service.gateway", "Account %d: tool-downgrade signature retry failed: %v", account.ID, retryErr2)
} else { } else {
log.Printf("Account %d: tool-downgrade signature retry build failed: %v", account.ID, buildErr2) logger.LegacyPrintf("service.gateway", "Account %d: tool-downgrade signature retry build failed: %v", account.ID, buildErr2)
} }
} }
} }
...@@ -3255,9 +3255,9 @@ func (s *GatewayService) Forward(ctx context.Context, c *gin.Context, account *A ...@@ -3255,9 +3255,9 @@ func (s *GatewayService) Forward(ctx context.Context, c *gin.Context, account *A
if retryResp != nil && retryResp.Body != nil { if retryResp != nil && retryResp.Body != nil {
_ = retryResp.Body.Close() _ = retryResp.Body.Close()
} }
log.Printf("Account %d: signature error retry failed: %v", account.ID, retryErr) logger.LegacyPrintf("service.gateway", "Account %d: signature error retry failed: %v", account.ID, retryErr)
} else { } else {
log.Printf("Account %d: signature error retry build request failed: %v", account.ID, buildErr) logger.LegacyPrintf("service.gateway", "Account %d: signature error retry build request failed: %v", account.ID, buildErr)
} }
// Retry failed: restore original response body and continue handling. // Retry failed: restore original response body and continue handling.
...@@ -3303,7 +3303,7 @@ func (s *GatewayService) Forward(ctx context.Context, c *gin.Context, account *A ...@@ -3303,7 +3303,7 @@ func (s *GatewayService) Forward(ctx context.Context, c *gin.Context, account *A
return "" return ""
}(), }(),
}) })
log.Printf("Account %d: upstream error %d, retry %d/%d after %v (elapsed=%v/%v)", logger.LegacyPrintf("service.gateway", "Account %d: upstream error %d, retry %d/%d after %v (elapsed=%v/%v)",
account.ID, resp.StatusCode, attempt, maxRetryAttempts, delay, elapsed, maxRetryElapsed) account.ID, resp.StatusCode, attempt, maxRetryAttempts, delay, elapsed, maxRetryElapsed)
if err := sleepWithContext(ctx, delay); err != nil { if err := sleepWithContext(ctx, delay); err != nil {
return nil, err return nil, err
...@@ -3317,9 +3317,9 @@ func (s *GatewayService) Forward(ctx context.Context, c *gin.Context, account *A ...@@ -3317,9 +3317,9 @@ func (s *GatewayService) Forward(ctx context.Context, c *gin.Context, account *A
// 不需要重试(成功或不可重试的错误),跳出循环 // 不需要重试(成功或不可重试的错误),跳出循环
// DEBUG: 输出响应 headers(用于检测 rate limit 信息) // DEBUG: 输出响应 headers(用于检测 rate limit 信息)
if account.Platform == PlatformGemini && resp.StatusCode < 400 { if account.Platform == PlatformGemini && resp.StatusCode < 400 {
log.Printf("[DEBUG] Gemini API Response Headers for account %d:", account.ID) logger.LegacyPrintf("service.gateway", "[DEBUG] Gemini API Response Headers for account %d:", account.ID)
for k, v := range resp.Header { for k, v := range resp.Header {
log.Printf("[DEBUG] %s: %v", k, v) logger.LegacyPrintf("service.gateway", "[DEBUG] %s: %v", k, v)
} }
} }
break break
...@@ -3337,7 +3337,7 @@ func (s *GatewayService) Forward(ctx context.Context, c *gin.Context, account *A ...@@ -3337,7 +3337,7 @@ func (s *GatewayService) Forward(ctx context.Context, c *gin.Context, account *A
resp.Body = io.NopCloser(bytes.NewReader(respBody)) resp.Body = io.NopCloser(bytes.NewReader(respBody))
// 调试日志:打印重试耗尽后的错误响应 // 调试日志:打印重试耗尽后的错误响应
log.Printf("[Forward] Upstream error (retry exhausted, failover): Account=%d(%s) Status=%d RequestID=%s Body=%s", logger.LegacyPrintf("service.gateway", "[Forward] Upstream error (retry exhausted, failover): Account=%d(%s) Status=%d RequestID=%s Body=%s",
account.ID, account.Name, resp.StatusCode, resp.Header.Get("x-request-id"), truncateString(string(respBody), 1000)) account.ID, account.Name, resp.StatusCode, resp.Header.Get("x-request-id"), truncateString(string(respBody), 1000))
s.handleRetryExhaustedSideEffects(ctx, resp, account) s.handleRetryExhaustedSideEffects(ctx, resp, account)
...@@ -3368,7 +3368,7 @@ func (s *GatewayService) Forward(ctx context.Context, c *gin.Context, account *A ...@@ -3368,7 +3368,7 @@ func (s *GatewayService) Forward(ctx context.Context, c *gin.Context, account *A
resp.Body = io.NopCloser(bytes.NewReader(respBody)) resp.Body = io.NopCloser(bytes.NewReader(respBody))
// 调试日志:打印上游错误响应 // 调试日志:打印上游错误响应
log.Printf("[Forward] Upstream error (failover): Account=%d(%s) Status=%d RequestID=%s Body=%s", logger.LegacyPrintf("service.gateway", "[Forward] Upstream error (failover): Account=%d(%s) Status=%d RequestID=%s Body=%s",
account.ID, account.Name, resp.StatusCode, resp.Header.Get("x-request-id"), truncateString(string(respBody), 1000)) account.ID, account.Name, resp.StatusCode, resp.Header.Get("x-request-id"), truncateString(string(respBody), 1000))
s.handleFailoverSideEffects(ctx, resp, account) s.handleFailoverSideEffects(ctx, resp, account)
...@@ -3422,13 +3422,13 @@ func (s *GatewayService) Forward(ctx context.Context, c *gin.Context, account *A ...@@ -3422,13 +3422,13 @@ func (s *GatewayService) Forward(ctx context.Context, c *gin.Context, account *A
}) })
if s.cfg.Gateway.LogUpstreamErrorBody { if s.cfg.Gateway.LogUpstreamErrorBody {
log.Printf( logger.LegacyPrintf("service.gateway",
"Account %d: 400 error, attempting failover: %s", "Account %d: 400 error, attempting failover: %s",
account.ID, account.ID,
truncateForLog(respBody, s.cfg.Gateway.LogUpstreamErrorBodyMaxBytes), truncateForLog(respBody, s.cfg.Gateway.LogUpstreamErrorBodyMaxBytes),
) )
} else { } else {
log.Printf("Account %d: 400 error, attempting failover", account.ID) logger.LegacyPrintf("service.gateway", "Account %d: 400 error, attempting failover", account.ID)
} }
s.handleFailoverSideEffects(ctx, resp, account) s.handleFailoverSideEffects(ctx, resp, account)
return nil, &UpstreamFailoverError{StatusCode: resp.StatusCode, ResponseBody: respBody} return nil, &UpstreamFailoverError{StatusCode: resp.StatusCode, ResponseBody: respBody}
...@@ -3497,7 +3497,7 @@ func (s *GatewayService) buildUpstreamRequest(ctx context.Context, c *gin.Contex ...@@ -3497,7 +3497,7 @@ func (s *GatewayService) buildUpstreamRequest(ctx context.Context, c *gin.Contex
// 1. 获取或创建指纹(包含随机生成的ClientID) // 1. 获取或创建指纹(包含随机生成的ClientID)
fp, err := s.identityService.GetOrCreateFingerprint(ctx, account.ID, clientHeaders) fp, err := s.identityService.GetOrCreateFingerprint(ctx, account.ID, clientHeaders)
if err != nil { if err != nil {
log.Printf("Warning: failed to get fingerprint for account %d: %v", account.ID, err) logger.LegacyPrintf("service.gateway", "Warning: failed to get fingerprint for account %d: %v", account.ID, err)
// 失败时降级为透传原始headers // 失败时降级为透传原始headers
} else { } else {
fingerprint = fp fingerprint = fp
...@@ -3768,33 +3768,33 @@ func (s *GatewayService) isThinkingBlockSignatureError(respBody []byte) bool { ...@@ -3768,33 +3768,33 @@ func (s *GatewayService) isThinkingBlockSignatureError(respBody []byte) bool {
} }
// Log for debugging // Log for debugging
log.Printf("[SignatureCheck] Checking error message: %s", msg) logger.LegacyPrintf("service.gateway", "[SignatureCheck] Checking error message: %s", msg)
// 检测signature相关的错误(更宽松的匹配) // 检测signature相关的错误(更宽松的匹配)
// 例如: "Invalid `signature` in `thinking` block", "***.signature" 等 // 例如: "Invalid `signature` in `thinking` block", "***.signature" 等
if strings.Contains(msg, "signature") { if strings.Contains(msg, "signature") {
log.Printf("[SignatureCheck] Detected signature error") logger.LegacyPrintf("service.gateway", "[SignatureCheck] Detected signature error")
return true return true
} }
// 检测 thinking block 顺序/类型错误 // 检测 thinking block 顺序/类型错误
// 例如: "Expected `thinking` or `redacted_thinking`, but found `text`" // 例如: "Expected `thinking` or `redacted_thinking`, but found `text`"
if strings.Contains(msg, "expected") && (strings.Contains(msg, "thinking") || strings.Contains(msg, "redacted_thinking")) { if strings.Contains(msg, "expected") && (strings.Contains(msg, "thinking") || strings.Contains(msg, "redacted_thinking")) {
log.Printf("[SignatureCheck] Detected thinking block type error") logger.LegacyPrintf("service.gateway", "[SignatureCheck] Detected thinking block type error")
return true return true
} }
// 检测 thinking block 被修改的错误 // 检测 thinking block 被修改的错误
// 例如: "thinking or redacted_thinking blocks in the latest assistant message cannot be modified" // 例如: "thinking or redacted_thinking blocks in the latest assistant message cannot be modified"
if strings.Contains(msg, "cannot be modified") && (strings.Contains(msg, "thinking") || strings.Contains(msg, "redacted_thinking")) { if strings.Contains(msg, "cannot be modified") && (strings.Contains(msg, "thinking") || strings.Contains(msg, "redacted_thinking")) {
log.Printf("[SignatureCheck] Detected thinking block modification error") logger.LegacyPrintf("service.gateway", "[SignatureCheck] Detected thinking block modification error")
return true return true
} }
// 检测空消息内容错误(可能是过滤 thinking blocks 后导致的) // 检测空消息内容错误(可能是过滤 thinking blocks 后导致的)
// 例如: "all messages must have non-empty content" // 例如: "all messages must have non-empty content"
if strings.Contains(msg, "non-empty content") || strings.Contains(msg, "empty content") { if strings.Contains(msg, "non-empty content") || strings.Contains(msg, "empty content") {
log.Printf("[SignatureCheck] Detected empty content error") logger.LegacyPrintf("service.gateway", "[SignatureCheck] Detected empty content error")
return true return true
} }
...@@ -3855,7 +3855,7 @@ func (s *GatewayService) handleErrorResponse(ctx context.Context, resp *http.Res ...@@ -3855,7 +3855,7 @@ func (s *GatewayService) handleErrorResponse(ctx context.Context, resp *http.Res
body, _ := io.ReadAll(io.LimitReader(resp.Body, 2<<20)) body, _ := io.ReadAll(io.LimitReader(resp.Body, 2<<20))
// 调试日志:打印上游错误响应 // 调试日志:打印上游错误响应
log.Printf("[Forward] Upstream error (non-retryable): Account=%d(%s) Status=%d RequestID=%s Body=%s", logger.LegacyPrintf("service.gateway", "[Forward] Upstream error (non-retryable): Account=%d(%s) Status=%d RequestID=%s Body=%s",
account.ID, account.Name, resp.StatusCode, resp.Header.Get("x-request-id"), truncateString(string(body), 1000)) account.ID, account.Name, resp.StatusCode, resp.Header.Get("x-request-id"), truncateString(string(body), 1000))
upstreamMsg := strings.TrimSpace(extractUpstreamErrorMessage(body)) upstreamMsg := strings.TrimSpace(extractUpstreamErrorMessage(body))
...@@ -3866,7 +3866,7 @@ func (s *GatewayService) handleErrorResponse(ctx context.Context, resp *http.Res ...@@ -3866,7 +3866,7 @@ func (s *GatewayService) handleErrorResponse(ctx context.Context, resp *http.Res
if isClaudeCodeCredentialScopeError(upstreamMsg) && c != nil { if isClaudeCodeCredentialScopeError(upstreamMsg) && c != nil {
if v, ok := c.Get(claudeMimicDebugInfoKey); ok { if v, ok := c.Get(claudeMimicDebugInfoKey); ok {
if line, ok := v.(string); ok && strings.TrimSpace(line) != "" { if line, ok := v.(string); ok && strings.TrimSpace(line) != "" {
log.Printf("[ClaudeMimicDebugOnError] status=%d request_id=%s %s", logger.LegacyPrintf("service.gateway", "[ClaudeMimicDebugOnError] status=%d request_id=%s %s",
resp.StatusCode, resp.StatusCode,
resp.Header.Get("x-request-id"), resp.Header.Get("x-request-id"),
line, line,
...@@ -3906,7 +3906,7 @@ func (s *GatewayService) handleErrorResponse(ctx context.Context, resp *http.Res ...@@ -3906,7 +3906,7 @@ func (s *GatewayService) handleErrorResponse(ctx context.Context, resp *http.Res
// 记录上游错误响应体摘要便于排障(可选:由配置控制;不回显到客户端) // 记录上游错误响应体摘要便于排障(可选:由配置控制;不回显到客户端)
if s.cfg != nil && s.cfg.Gateway.LogUpstreamErrorBody { if s.cfg != nil && s.cfg.Gateway.LogUpstreamErrorBody {
log.Printf( logger.LegacyPrintf("service.gateway",
"Upstream error %d (account=%d platform=%s type=%s): %s", "Upstream error %d (account=%d platform=%s type=%s): %s",
resp.StatusCode, resp.StatusCode,
account.ID, account.ID,
...@@ -4007,10 +4007,10 @@ func (s *GatewayService) handleRetryExhaustedSideEffects(ctx context.Context, re ...@@ -4007,10 +4007,10 @@ func (s *GatewayService) handleRetryExhaustedSideEffects(ctx context.Context, re
// OAuth/Setup Token 账号的 403:标记账号异常 // OAuth/Setup Token 账号的 403:标记账号异常
if account.IsOAuth() && statusCode == 403 { if account.IsOAuth() && statusCode == 403 {
s.rateLimitService.HandleUpstreamError(ctx, account, statusCode, resp.Header, body) s.rateLimitService.HandleUpstreamError(ctx, account, statusCode, resp.Header, body)
log.Printf("Account %d: marked as error after %d retries for status %d", account.ID, maxRetryAttempts, statusCode) logger.LegacyPrintf("service.gateway", "Account %d: marked as error after %d retries for status %d", account.ID, maxRetryAttempts, statusCode)
} else { } else {
// API Key 未配置错误码:不标记账号状态 // API Key 未配置错误码:不标记账号状态
log.Printf("Account %d: upstream error %d after %d retries (not marking account)", account.ID, statusCode, maxRetryAttempts) logger.LegacyPrintf("service.gateway", "Account %d: upstream error %d after %d retries (not marking account)", account.ID, statusCode, maxRetryAttempts)
} }
} }
...@@ -4036,7 +4036,7 @@ func (s *GatewayService) handleRetryExhaustedError(ctx context.Context, resp *ht ...@@ -4036,7 +4036,7 @@ func (s *GatewayService) handleRetryExhaustedError(ctx context.Context, resp *ht
if isClaudeCodeCredentialScopeError(upstreamMsg) && c != nil { if isClaudeCodeCredentialScopeError(upstreamMsg) && c != nil {
if v, ok := c.Get(claudeMimicDebugInfoKey); ok { if v, ok := c.Get(claudeMimicDebugInfoKey); ok {
if line, ok := v.(string); ok && strings.TrimSpace(line) != "" { if line, ok := v.(string); ok && strings.TrimSpace(line) != "" {
log.Printf("[ClaudeMimicDebugOnError] status=%d request_id=%s %s", logger.LegacyPrintf("service.gateway", "[ClaudeMimicDebugOnError] status=%d request_id=%s %s",
resp.StatusCode, resp.StatusCode,
resp.Header.Get("x-request-id"), resp.Header.Get("x-request-id"),
line, line,
...@@ -4065,7 +4065,7 @@ func (s *GatewayService) handleRetryExhaustedError(ctx context.Context, resp *ht ...@@ -4065,7 +4065,7 @@ func (s *GatewayService) handleRetryExhaustedError(ctx context.Context, resp *ht
}) })
if s.cfg != nil && s.cfg.Gateway.LogUpstreamErrorBody { if s.cfg != nil && s.cfg.Gateway.LogUpstreamErrorBody {
log.Printf( logger.LegacyPrintf("service.gateway",
"Upstream error %d retries_exhausted (account=%d platform=%s type=%s): %s", "Upstream error %d retries_exhausted (account=%d platform=%s type=%s): %s",
resp.StatusCode, resp.StatusCode,
account.ID, account.ID,
...@@ -4325,17 +4325,17 @@ func (s *GatewayService) handleStreamingResponse(ctx context.Context, resp *http ...@@ -4325,17 +4325,17 @@ func (s *GatewayService) handleStreamingResponse(ctx context.Context, resp *http
if ev.err != nil { if ev.err != nil {
// 检测 context 取消(客户端断开会导致 context 取消,进而影响上游读取) // 检测 context 取消(客户端断开会导致 context 取消,进而影响上游读取)
if errors.Is(ev.err, context.Canceled) || errors.Is(ev.err, context.DeadlineExceeded) { if errors.Is(ev.err, context.Canceled) || errors.Is(ev.err, context.DeadlineExceeded) {
log.Printf("Context canceled during streaming, returning collected usage") logger.LegacyPrintf("service.gateway", "Context canceled during streaming, returning collected usage")
return &streamingResult{usage: usage, firstTokenMs: firstTokenMs, clientDisconnect: true}, nil return &streamingResult{usage: usage, firstTokenMs: firstTokenMs, clientDisconnect: true}, nil
} }
// 客户端已通过写入失败检测到断开,上游也出错了,返回已收集的 usage // 客户端已通过写入失败检测到断开,上游也出错了,返回已收集的 usage
if clientDisconnected { if clientDisconnected {
log.Printf("Upstream read error after client disconnect: %v, returning collected usage", ev.err) logger.LegacyPrintf("service.gateway", "Upstream read error after client disconnect: %v, returning collected usage", ev.err)
return &streamingResult{usage: usage, firstTokenMs: firstTokenMs, clientDisconnect: true}, nil return &streamingResult{usage: usage, firstTokenMs: firstTokenMs, clientDisconnect: true}, nil
} }
// 客户端未断开,正常的错误处理 // 客户端未断开,正常的错误处理
if errors.Is(ev.err, bufio.ErrTooLong) { if errors.Is(ev.err, bufio.ErrTooLong) {
log.Printf("SSE line too long: account=%d max_size=%d error=%v", account.ID, maxLineSize, ev.err) logger.LegacyPrintf("service.gateway", "SSE line too long: account=%d max_size=%d error=%v", account.ID, maxLineSize, ev.err)
sendErrorEvent("response_too_large") sendErrorEvent("response_too_large")
return &streamingResult{usage: usage, firstTokenMs: firstTokenMs}, ev.err return &streamingResult{usage: usage, firstTokenMs: firstTokenMs}, ev.err
} }
...@@ -4363,7 +4363,7 @@ func (s *GatewayService) handleStreamingResponse(ctx context.Context, resp *http ...@@ -4363,7 +4363,7 @@ func (s *GatewayService) handleStreamingResponse(ctx context.Context, resp *http
if !clientDisconnected { if !clientDisconnected {
if _, werr := fmt.Fprint(w, block); werr != nil { if _, werr := fmt.Fprint(w, block); werr != nil {
clientDisconnected = true clientDisconnected = true
log.Printf("Client disconnected during streaming, continuing to drain upstream for billing") logger.LegacyPrintf("service.gateway", "Client disconnected during streaming, continuing to drain upstream for billing")
break break
} }
flusher.Flush() flusher.Flush()
...@@ -4388,10 +4388,10 @@ func (s *GatewayService) handleStreamingResponse(ctx context.Context, resp *http ...@@ -4388,10 +4388,10 @@ func (s *GatewayService) handleStreamingResponse(ctx context.Context, resp *http
} }
if clientDisconnected { if clientDisconnected {
// 客户端已断开,上游也超时了,返回已收集的 usage // 客户端已断开,上游也超时了,返回已收集的 usage
log.Printf("Upstream timeout after client disconnect, returning collected usage") logger.LegacyPrintf("service.gateway", "Upstream timeout after client disconnect, returning collected usage")
return &streamingResult{usage: usage, firstTokenMs: firstTokenMs, clientDisconnect: true}, nil return &streamingResult{usage: usage, firstTokenMs: firstTokenMs, clientDisconnect: true}, nil
} }
log.Printf("Stream data interval timeout: account=%d model=%s interval=%s", account.ID, originalModel, streamInterval) logger.LegacyPrintf("service.gateway", "Stream data interval timeout: account=%d model=%s interval=%s", account.ID, originalModel, streamInterval)
// 处理流超时,可能标记账户为临时不可调度或错误状态 // 处理流超时,可能标记账户为临时不可调度或错误状态
if s.rateLimitService != nil { if s.rateLimitService != nil {
s.rateLimitService.HandleStreamTimeout(ctx, account, originalModel) s.rateLimitService.HandleStreamTimeout(ctx, account, originalModel)
...@@ -4536,7 +4536,7 @@ func (s *GatewayService) RecordUsage(ctx context.Context, input *RecordUsageInpu ...@@ -4536,7 +4536,7 @@ func (s *GatewayService) RecordUsage(ctx context.Context, input *RecordUsageInpu
// 强制缓存计费:将 input_tokens 转为 cache_read_input_tokens // 强制缓存计费:将 input_tokens 转为 cache_read_input_tokens
// 用于粘性会话切换时的特殊计费处理 // 用于粘性会话切换时的特殊计费处理
if input.ForceCacheBilling && result.Usage.InputTokens > 0 { if input.ForceCacheBilling && result.Usage.InputTokens > 0 {
log.Printf("force_cache_billing: %d input_tokens → cache_read_input_tokens (account=%d)", logger.LegacyPrintf("service.gateway", "force_cache_billing: %d input_tokens → cache_read_input_tokens (account=%d)",
result.Usage.InputTokens, account.ID) result.Usage.InputTokens, account.ID)
result.Usage.CacheReadInputTokens += result.Usage.InputTokens result.Usage.CacheReadInputTokens += result.Usage.InputTokens
result.Usage.InputTokens = 0 result.Usage.InputTokens = 0
...@@ -4597,7 +4597,7 @@ func (s *GatewayService) RecordUsage(ctx context.Context, input *RecordUsageInpu ...@@ -4597,7 +4597,7 @@ func (s *GatewayService) RecordUsage(ctx context.Context, input *RecordUsageInpu
var err error var err error
cost, err = s.billingService.CalculateCost(result.Model, tokens, multiplier) cost, err = s.billingService.CalculateCost(result.Model, tokens, multiplier)
if err != nil { if err != nil {
log.Printf("Calculate cost failed: %v", err) logger.LegacyPrintf("service.gateway", "Calculate cost failed: %v", err)
cost = &CostBreakdown{ActualCost: 0} cost = &CostBreakdown{ActualCost: 0}
} }
} }
...@@ -4668,11 +4668,11 @@ func (s *GatewayService) RecordUsage(ctx context.Context, input *RecordUsageInpu ...@@ -4668,11 +4668,11 @@ func (s *GatewayService) RecordUsage(ctx context.Context, input *RecordUsageInpu
inserted, err := s.usageLogRepo.Create(ctx, usageLog) inserted, err := s.usageLogRepo.Create(ctx, usageLog)
if err != nil { if err != nil {
log.Printf("Create usage log failed: %v", err) logger.LegacyPrintf("service.gateway", "Create usage log failed: %v", err)
} }
if s.cfg != nil && s.cfg.RunMode == config.RunModeSimple { if s.cfg != nil && s.cfg.RunMode == config.RunModeSimple {
log.Printf("[SIMPLE MODE] Usage recorded (not billed): user=%d, tokens=%d", usageLog.UserID, usageLog.TotalTokens()) logger.LegacyPrintf("service.gateway", "[SIMPLE MODE] Usage recorded (not billed): user=%d, tokens=%d", usageLog.UserID, usageLog.TotalTokens())
s.deferredService.ScheduleLastUsedUpdate(account.ID) s.deferredService.ScheduleLastUsedUpdate(account.ID)
return nil return nil
} }
...@@ -4684,7 +4684,7 @@ func (s *GatewayService) RecordUsage(ctx context.Context, input *RecordUsageInpu ...@@ -4684,7 +4684,7 @@ func (s *GatewayService) RecordUsage(ctx context.Context, input *RecordUsageInpu
// 订阅模式:更新订阅用量(使用 TotalCost 原始费用,不考虑倍率) // 订阅模式:更新订阅用量(使用 TotalCost 原始费用,不考虑倍率)
if shouldBill && cost.TotalCost > 0 { if shouldBill && cost.TotalCost > 0 {
if err := s.userSubRepo.IncrementUsage(ctx, subscription.ID, cost.TotalCost); err != nil { if err := s.userSubRepo.IncrementUsage(ctx, subscription.ID, cost.TotalCost); err != nil {
log.Printf("Increment subscription usage failed: %v", err) logger.LegacyPrintf("service.gateway", "Increment subscription usage failed: %v", err)
} }
// 异步更新订阅缓存 // 异步更新订阅缓存
s.billingCacheService.QueueUpdateSubscriptionUsage(user.ID, *apiKey.GroupID, cost.TotalCost) s.billingCacheService.QueueUpdateSubscriptionUsage(user.ID, *apiKey.GroupID, cost.TotalCost)
...@@ -4693,7 +4693,7 @@ func (s *GatewayService) RecordUsage(ctx context.Context, input *RecordUsageInpu ...@@ -4693,7 +4693,7 @@ func (s *GatewayService) RecordUsage(ctx context.Context, input *RecordUsageInpu
// 余额模式:扣除用户余额(使用 ActualCost 考虑倍率后的费用) // 余额模式:扣除用户余额(使用 ActualCost 考虑倍率后的费用)
if shouldBill && cost.ActualCost > 0 { if shouldBill && cost.ActualCost > 0 {
if err := s.userRepo.DeductBalance(ctx, user.ID, cost.ActualCost); err != nil { if err := s.userRepo.DeductBalance(ctx, user.ID, cost.ActualCost); err != nil {
log.Printf("Deduct balance failed: %v", err) logger.LegacyPrintf("service.gateway", "Deduct balance failed: %v", err)
} }
// 异步更新余额缓存 // 异步更新余额缓存
s.billingCacheService.QueueDeductBalance(user.ID, cost.ActualCost) s.billingCacheService.QueueDeductBalance(user.ID, cost.ActualCost)
...@@ -4703,7 +4703,7 @@ func (s *GatewayService) RecordUsage(ctx context.Context, input *RecordUsageInpu ...@@ -4703,7 +4703,7 @@ func (s *GatewayService) RecordUsage(ctx context.Context, input *RecordUsageInpu
// 更新 API Key 配额(如果设置了配额限制) // 更新 API Key 配额(如果设置了配额限制)
if shouldBill && cost.ActualCost > 0 && apiKey.Quota > 0 && input.APIKeyService != nil { if shouldBill && cost.ActualCost > 0 && apiKey.Quota > 0 && input.APIKeyService != nil {
if err := input.APIKeyService.UpdateQuotaUsed(ctx, apiKey.ID, cost.ActualCost); err != nil { if err := input.APIKeyService.UpdateQuotaUsed(ctx, apiKey.ID, cost.ActualCost); err != nil {
log.Printf("Update API key quota failed: %v", err) logger.LegacyPrintf("service.gateway", "Update API key quota failed: %v", err)
} }
} }
...@@ -4739,7 +4739,7 @@ func (s *GatewayService) RecordUsageWithLongContext(ctx context.Context, input * ...@@ -4739,7 +4739,7 @@ func (s *GatewayService) RecordUsageWithLongContext(ctx context.Context, input *
// 强制缓存计费:将 input_tokens 转为 cache_read_input_tokens // 强制缓存计费:将 input_tokens 转为 cache_read_input_tokens
// 用于粘性会话切换时的特殊计费处理 // 用于粘性会话切换时的特殊计费处理
if input.ForceCacheBilling && result.Usage.InputTokens > 0 { if input.ForceCacheBilling && result.Usage.InputTokens > 0 {
log.Printf("force_cache_billing: %d input_tokens → cache_read_input_tokens (account=%d)", logger.LegacyPrintf("service.gateway", "force_cache_billing: %d input_tokens → cache_read_input_tokens (account=%d)",
result.Usage.InputTokens, account.ID) result.Usage.InputTokens, account.ID)
result.Usage.CacheReadInputTokens += result.Usage.InputTokens result.Usage.CacheReadInputTokens += result.Usage.InputTokens
result.Usage.InputTokens = 0 result.Usage.InputTokens = 0
...@@ -4783,7 +4783,7 @@ func (s *GatewayService) RecordUsageWithLongContext(ctx context.Context, input * ...@@ -4783,7 +4783,7 @@ func (s *GatewayService) RecordUsageWithLongContext(ctx context.Context, input *
var err error var err error
cost, err = s.billingService.CalculateCostWithLongContext(result.Model, tokens, multiplier, input.LongContextThreshold, input.LongContextMultiplier) cost, err = s.billingService.CalculateCostWithLongContext(result.Model, tokens, multiplier, input.LongContextThreshold, input.LongContextMultiplier)
if err != nil { if err != nil {
log.Printf("Calculate cost failed: %v", err) logger.LegacyPrintf("service.gateway", "Calculate cost failed: %v", err)
cost = &CostBreakdown{ActualCost: 0} cost = &CostBreakdown{ActualCost: 0}
} }
} }
...@@ -4849,11 +4849,11 @@ func (s *GatewayService) RecordUsageWithLongContext(ctx context.Context, input * ...@@ -4849,11 +4849,11 @@ func (s *GatewayService) RecordUsageWithLongContext(ctx context.Context, input *
inserted, err := s.usageLogRepo.Create(ctx, usageLog) inserted, err := s.usageLogRepo.Create(ctx, usageLog)
if err != nil { if err != nil {
log.Printf("Create usage log failed: %v", err) logger.LegacyPrintf("service.gateway", "Create usage log failed: %v", err)
} }
if s.cfg != nil && s.cfg.RunMode == config.RunModeSimple { if s.cfg != nil && s.cfg.RunMode == config.RunModeSimple {
log.Printf("[SIMPLE MODE] Usage recorded (not billed): user=%d, tokens=%d", usageLog.UserID, usageLog.TotalTokens()) logger.LegacyPrintf("service.gateway", "[SIMPLE MODE] Usage recorded (not billed): user=%d, tokens=%d", usageLog.UserID, usageLog.TotalTokens())
s.deferredService.ScheduleLastUsedUpdate(account.ID) s.deferredService.ScheduleLastUsedUpdate(account.ID)
return nil return nil
} }
...@@ -4865,7 +4865,7 @@ func (s *GatewayService) RecordUsageWithLongContext(ctx context.Context, input * ...@@ -4865,7 +4865,7 @@ func (s *GatewayService) RecordUsageWithLongContext(ctx context.Context, input *
// 订阅模式:更新订阅用量(使用 TotalCost 原始费用,不考虑倍率) // 订阅模式:更新订阅用量(使用 TotalCost 原始费用,不考虑倍率)
if shouldBill && cost.TotalCost > 0 { if shouldBill && cost.TotalCost > 0 {
if err := s.userSubRepo.IncrementUsage(ctx, subscription.ID, cost.TotalCost); err != nil { if err := s.userSubRepo.IncrementUsage(ctx, subscription.ID, cost.TotalCost); err != nil {
log.Printf("Increment subscription usage failed: %v", err) logger.LegacyPrintf("service.gateway", "Increment subscription usage failed: %v", err)
} }
// 异步更新订阅缓存 // 异步更新订阅缓存
s.billingCacheService.QueueUpdateSubscriptionUsage(user.ID, *apiKey.GroupID, cost.TotalCost) s.billingCacheService.QueueUpdateSubscriptionUsage(user.ID, *apiKey.GroupID, cost.TotalCost)
...@@ -4874,14 +4874,14 @@ func (s *GatewayService) RecordUsageWithLongContext(ctx context.Context, input * ...@@ -4874,14 +4874,14 @@ func (s *GatewayService) RecordUsageWithLongContext(ctx context.Context, input *
// 余额模式:扣除用户余额(使用 ActualCost 考虑倍率后的费用) // 余额模式:扣除用户余额(使用 ActualCost 考虑倍率后的费用)
if shouldBill && cost.ActualCost > 0 { if shouldBill && cost.ActualCost > 0 {
if err := s.userRepo.DeductBalance(ctx, user.ID, cost.ActualCost); err != nil { if err := s.userRepo.DeductBalance(ctx, user.ID, cost.ActualCost); err != nil {
log.Printf("Deduct balance failed: %v", err) logger.LegacyPrintf("service.gateway", "Deduct balance failed: %v", err)
} }
// 异步更新余额缓存 // 异步更新余额缓存
s.billingCacheService.QueueDeductBalance(user.ID, cost.ActualCost) s.billingCacheService.QueueDeductBalance(user.ID, cost.ActualCost)
// API Key 独立配额扣费 // API Key 独立配额扣费
if input.APIKeyService != nil && apiKey.Quota > 0 { if input.APIKeyService != nil && apiKey.Quota > 0 {
if err := input.APIKeyService.UpdateQuotaUsed(ctx, apiKey.ID, cost.ActualCost); err != nil { if err := input.APIKeyService.UpdateQuotaUsed(ctx, apiKey.ID, cost.ActualCost); err != nil {
log.Printf("Add API key quota used failed: %v", err) logger.LegacyPrintf("service.gateway", "Add API key quota used failed: %v", err)
} }
} }
} }
...@@ -4940,7 +4940,7 @@ func (s *GatewayService) ForwardCountTokens(ctx context.Context, c *gin.Context, ...@@ -4940,7 +4940,7 @@ func (s *GatewayService) ForwardCountTokens(ctx context.Context, c *gin.Context,
if mappedModel != reqModel { if mappedModel != reqModel {
body = s.replaceModelInBody(body, mappedModel) body = s.replaceModelInBody(body, mappedModel)
reqModel = mappedModel reqModel = mappedModel
log.Printf("CountTokens model mapping applied: %s -> %s (account: %s, source=%s)", parsed.Model, mappedModel, account.Name, mappingSource) logger.LegacyPrintf("service.gateway", "CountTokens model mapping applied: %s -> %s (account: %s, source=%s)", parsed.Model, mappedModel, account.Name, mappingSource)
} }
} }
...@@ -4982,7 +4982,7 @@ func (s *GatewayService) ForwardCountTokens(ctx context.Context, c *gin.Context, ...@@ -4982,7 +4982,7 @@ func (s *GatewayService) ForwardCountTokens(ctx context.Context, c *gin.Context,
// 检测 thinking block 签名错误(400)并重试一次(过滤 thinking blocks) // 检测 thinking block 签名错误(400)并重试一次(过滤 thinking blocks)
if resp.StatusCode == 400 && s.isThinkingBlockSignatureError(respBody) { if resp.StatusCode == 400 && s.isThinkingBlockSignatureError(respBody) {
log.Printf("Account %d: detected thinking block signature error on count_tokens, retrying with filtered thinking blocks", account.ID) logger.LegacyPrintf("service.gateway", "Account %d: detected thinking block signature error on count_tokens, retrying with filtered thinking blocks", account.ID)
filteredBody := FilterThinkingBlocksForRetry(body) filteredBody := FilterThinkingBlocksForRetry(body)
retryReq, buildErr := s.buildCountTokensRequest(ctx, c, account, filteredBody, token, tokenType, reqModel, shouldMimicClaudeCode) retryReq, buildErr := s.buildCountTokensRequest(ctx, c, account, filteredBody, token, tokenType, reqModel, shouldMimicClaudeCode)
...@@ -5019,7 +5019,7 @@ func (s *GatewayService) ForwardCountTokens(ctx context.Context, c *gin.Context, ...@@ -5019,7 +5019,7 @@ func (s *GatewayService) ForwardCountTokens(ctx context.Context, c *gin.Context,
// 记录上游错误摘要便于排障(不回显请求内容) // 记录上游错误摘要便于排障(不回显请求内容)
if s.cfg != nil && s.cfg.Gateway.LogUpstreamErrorBody { if s.cfg != nil && s.cfg.Gateway.LogUpstreamErrorBody {
log.Printf( logger.LegacyPrintf("service.gateway",
"count_tokens upstream error %d (account=%d platform=%s type=%s): %s", "count_tokens upstream error %d (account=%d platform=%s type=%s): %s",
resp.StatusCode, resp.StatusCode,
account.ID, account.ID,
......
...@@ -10,7 +10,6 @@ import ( ...@@ -10,7 +10,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"log"
"math" "math"
mathrand "math/rand" mathrand "math/rand"
"net/http" "net/http"
...@@ -22,6 +21,7 @@ import ( ...@@ -22,6 +21,7 @@ import (
"github.com/Wei-Shaw/sub2api/internal/pkg/ctxkey" "github.com/Wei-Shaw/sub2api/internal/pkg/ctxkey"
"github.com/Wei-Shaw/sub2api/internal/pkg/geminicli" "github.com/Wei-Shaw/sub2api/internal/pkg/geminicli"
"github.com/Wei-Shaw/sub2api/internal/pkg/googleapi" "github.com/Wei-Shaw/sub2api/internal/pkg/googleapi"
"github.com/Wei-Shaw/sub2api/internal/pkg/logger"
"github.com/Wei-Shaw/sub2api/internal/util/responseheaders" "github.com/Wei-Shaw/sub2api/internal/util/responseheaders"
"github.com/Wei-Shaw/sub2api/internal/util/urlvalidator" "github.com/Wei-Shaw/sub2api/internal/util/urlvalidator"
...@@ -282,7 +282,7 @@ func (s *GeminiMessagesCompatService) passesRateLimitPreCheck(ctx context.Contex ...@@ -282,7 +282,7 @@ func (s *GeminiMessagesCompatService) passesRateLimitPreCheck(ctx context.Contex
} }
ok, err := s.rateLimitService.PreCheckUsage(ctx, account, requestedModel) ok, err := s.rateLimitService.PreCheckUsage(ctx, account, requestedModel)
if err != nil { if err != nil {
log.Printf("[Gemini PreCheck] Account %d precheck error: %v", account.ID, err) logger.LegacyPrintf("service.gemini_messages_compat", "[Gemini PreCheck] Account %d precheck error: %v", account.ID, err)
} }
return ok return ok
} }
...@@ -698,7 +698,7 @@ func (s *GeminiMessagesCompatService) Forward(ctx context.Context, c *gin.Contex ...@@ -698,7 +698,7 @@ func (s *GeminiMessagesCompatService) Forward(ctx context.Context, c *gin.Contex
Message: safeErr, Message: safeErr,
}) })
if attempt < geminiMaxRetries { if attempt < geminiMaxRetries {
log.Printf("Gemini account %d: upstream request failed, retry %d/%d: %v", account.ID, attempt, geminiMaxRetries, err) logger.LegacyPrintf("service.gemini_messages_compat", "Gemini account %d: upstream request failed, retry %d/%d: %v", account.ID, attempt, geminiMaxRetries, err)
sleepGeminiBackoff(attempt) sleepGeminiBackoff(attempt)
continue continue
} }
...@@ -754,7 +754,7 @@ func (s *GeminiMessagesCompatService) Forward(ctx context.Context, c *gin.Contex ...@@ -754,7 +754,7 @@ func (s *GeminiMessagesCompatService) Forward(ctx context.Context, c *gin.Contex
} }
retryGeminiReq, txErr := convertClaudeMessagesToGeminiGenerateContent(strippedClaudeBody) retryGeminiReq, txErr := convertClaudeMessagesToGeminiGenerateContent(strippedClaudeBody)
if txErr == nil { if txErr == nil {
log.Printf("Gemini account %d: detected signature-related 400, retrying with downgraded Claude blocks (%s)", account.ID, stageName) logger.LegacyPrintf("service.gemini_messages_compat", "Gemini account %d: detected signature-related 400, retrying with downgraded Claude blocks (%s)", account.ID, stageName)
geminiReq = retryGeminiReq geminiReq = retryGeminiReq
// Consume one retry budget attempt and continue with the updated request payload. // Consume one retry budget attempt and continue with the updated request payload.
sleepGeminiBackoff(1) sleepGeminiBackoff(1)
...@@ -821,7 +821,7 @@ func (s *GeminiMessagesCompatService) Forward(ctx context.Context, c *gin.Contex ...@@ -821,7 +821,7 @@ func (s *GeminiMessagesCompatService) Forward(ctx context.Context, c *gin.Contex
Detail: upstreamDetail, Detail: upstreamDetail,
}) })
log.Printf("Gemini account %d: upstream status %d, retry %d/%d", account.ID, resp.StatusCode, attempt, geminiMaxRetries) logger.LegacyPrintf("service.gemini_messages_compat", "Gemini account %d: upstream status %d, retry %d/%d", account.ID, resp.StatusCode, attempt, geminiMaxRetries)
sleepGeminiBackoff(attempt) sleepGeminiBackoff(attempt)
continue continue
} }
...@@ -1166,7 +1166,7 @@ func (s *GeminiMessagesCompatService) ForwardNative(ctx context.Context, c *gin. ...@@ -1166,7 +1166,7 @@ func (s *GeminiMessagesCompatService) ForwardNative(ctx context.Context, c *gin.
Message: safeErr, Message: safeErr,
}) })
if attempt < geminiMaxRetries { if attempt < geminiMaxRetries {
log.Printf("Gemini account %d: upstream request failed, retry %d/%d: %v", account.ID, attempt, geminiMaxRetries, err) logger.LegacyPrintf("service.gemini_messages_compat", "Gemini account %d: upstream request failed, retry %d/%d: %v", account.ID, attempt, geminiMaxRetries, err)
sleepGeminiBackoff(attempt) sleepGeminiBackoff(attempt)
continue continue
} }
...@@ -1235,7 +1235,7 @@ func (s *GeminiMessagesCompatService) ForwardNative(ctx context.Context, c *gin. ...@@ -1235,7 +1235,7 @@ func (s *GeminiMessagesCompatService) ForwardNative(ctx context.Context, c *gin.
Detail: upstreamDetail, Detail: upstreamDetail,
}) })
log.Printf("Gemini account %d: upstream status %d, retry %d/%d", account.ID, resp.StatusCode, attempt, geminiMaxRetries) logger.LegacyPrintf("service.gemini_messages_compat", "Gemini account %d: upstream status %d, retry %d/%d", account.ID, resp.StatusCode, attempt, geminiMaxRetries)
sleepGeminiBackoff(attempt) sleepGeminiBackoff(attempt)
continue continue
} }
...@@ -1367,7 +1367,7 @@ func (s *GeminiMessagesCompatService) ForwardNative(ctx context.Context, c *gin. ...@@ -1367,7 +1367,7 @@ func (s *GeminiMessagesCompatService) ForwardNative(ctx context.Context, c *gin.
maxBytes = 2048 maxBytes = 2048
} }
upstreamDetail = truncateString(string(respBody), maxBytes) upstreamDetail = truncateString(string(respBody), maxBytes)
log.Printf("[Gemini] native upstream error %d: %s", resp.StatusCode, truncateForLog(respBody, s.cfg.Gateway.LogUpstreamErrorBodyMaxBytes)) logger.LegacyPrintf("service.gemini_messages_compat", "[Gemini] native upstream error %d: %s", resp.StatusCode, truncateForLog(respBody, s.cfg.Gateway.LogUpstreamErrorBodyMaxBytes))
} }
setOpsUpstreamError(c, resp.StatusCode, upstreamMsg, upstreamDetail) setOpsUpstreamError(c, resp.StatusCode, upstreamMsg, upstreamDetail)
appendOpsUpstreamError(c, OpsUpstreamErrorEvent{ appendOpsUpstreamError(c, OpsUpstreamErrorEvent{
...@@ -1544,7 +1544,7 @@ func (s *GeminiMessagesCompatService) writeGeminiMappedError(c *gin.Context, acc ...@@ -1544,7 +1544,7 @@ func (s *GeminiMessagesCompatService) writeGeminiMappedError(c *gin.Context, acc
}) })
if s.cfg != nil && s.cfg.Gateway.LogUpstreamErrorBody { if s.cfg != nil && s.cfg.Gateway.LogUpstreamErrorBody {
log.Printf("[Gemini] upstream error %d: %s", upstreamStatus, truncateForLog(body, s.cfg.Gateway.LogUpstreamErrorBodyMaxBytes)) logger.LegacyPrintf("service.gemini_messages_compat", "[Gemini] upstream error %d: %s", upstreamStatus, truncateForLog(body, s.cfg.Gateway.LogUpstreamErrorBodyMaxBytes))
} }
if status, errType, errMsg, matched := applyErrorPassthroughRule( if status, errType, errMsg, matched := applyErrorPassthroughRule(
...@@ -2299,13 +2299,13 @@ type UpstreamHTTPResult struct { ...@@ -2299,13 +2299,13 @@ type UpstreamHTTPResult struct {
func (s *GeminiMessagesCompatService) handleNativeNonStreamingResponse(c *gin.Context, resp *http.Response, isOAuth bool) (*ClaudeUsage, error) { func (s *GeminiMessagesCompatService) handleNativeNonStreamingResponse(c *gin.Context, resp *http.Response, isOAuth bool) (*ClaudeUsage, error) {
// Log response headers for debugging // Log response headers for debugging
log.Printf("[GeminiAPI] ========== Response Headers ==========") logger.LegacyPrintf("service.gemini_messages_compat", "[GeminiAPI] ========== Response Headers ==========")
for key, values := range resp.Header { for key, values := range resp.Header {
if strings.HasPrefix(strings.ToLower(key), "x-ratelimit") { if strings.HasPrefix(strings.ToLower(key), "x-ratelimit") {
log.Printf("[GeminiAPI] %s: %v", key, values) logger.LegacyPrintf("service.gemini_messages_compat", "[GeminiAPI] %s: %v", key, values)
} }
} }
log.Printf("[GeminiAPI] ========================================") logger.LegacyPrintf("service.gemini_messages_compat", "[GeminiAPI] ========================================")
respBody, err := io.ReadAll(resp.Body) respBody, err := io.ReadAll(resp.Body)
if err != nil { if err != nil {
...@@ -2339,13 +2339,13 @@ func (s *GeminiMessagesCompatService) handleNativeNonStreamingResponse(c *gin.Co ...@@ -2339,13 +2339,13 @@ func (s *GeminiMessagesCompatService) handleNativeNonStreamingResponse(c *gin.Co
func (s *GeminiMessagesCompatService) handleNativeStreamingResponse(c *gin.Context, resp *http.Response, startTime time.Time, isOAuth bool) (*geminiNativeStreamResult, error) { func (s *GeminiMessagesCompatService) handleNativeStreamingResponse(c *gin.Context, resp *http.Response, startTime time.Time, isOAuth bool) (*geminiNativeStreamResult, error) {
// Log response headers for debugging // Log response headers for debugging
log.Printf("[GeminiAPI] ========== Streaming Response Headers ==========") logger.LegacyPrintf("service.gemini_messages_compat", "[GeminiAPI] ========== Streaming Response Headers ==========")
for key, values := range resp.Header { for key, values := range resp.Header {
if strings.HasPrefix(strings.ToLower(key), "x-ratelimit") { if strings.HasPrefix(strings.ToLower(key), "x-ratelimit") {
log.Printf("[GeminiAPI] %s: %v", key, values) logger.LegacyPrintf("service.gemini_messages_compat", "[GeminiAPI] %s: %v", key, values)
} }
} }
log.Printf("[GeminiAPI] ====================================================") logger.LegacyPrintf("service.gemini_messages_compat", "[GeminiAPI] ====================================================")
if s.cfg != nil { if s.cfg != nil {
responseheaders.WriteFilteredHeaders(c.Writer.Header(), resp.Header, s.cfg.Security.ResponseHeaders) responseheaders.WriteFilteredHeaders(c.Writer.Header(), resp.Header, s.cfg.Security.ResponseHeaders)
...@@ -2640,16 +2640,16 @@ func (s *GeminiMessagesCompatService) handleGeminiUpstreamError(ctx context.Cont ...@@ -2640,16 +2640,16 @@ func (s *GeminiMessagesCompatService) handleGeminiUpstreamError(ctx context.Cont
cooldown = s.rateLimitService.GeminiCooldown(ctx, account) cooldown = s.rateLimitService.GeminiCooldown(ctx, account)
} }
ra = time.Now().Add(cooldown) ra = time.Now().Add(cooldown)
log.Printf("[Gemini 429] Account %d (Code Assist, tier=%s, project=%s) rate limited, cooldown=%v", account.ID, tierID, projectID, time.Until(ra).Truncate(time.Second)) logger.LegacyPrintf("service.gemini_messages_compat", "[Gemini 429] Account %d (Code Assist, tier=%s, project=%s) rate limited, cooldown=%v", account.ID, tierID, projectID, time.Until(ra).Truncate(time.Second))
} else { } else {
// API Key / AI Studio OAuth: PST 午夜 // API Key / AI Studio OAuth: PST 午夜
if ts := nextGeminiDailyResetUnix(); ts != nil { if ts := nextGeminiDailyResetUnix(); ts != nil {
ra = time.Unix(*ts, 0) ra = time.Unix(*ts, 0)
log.Printf("[Gemini 429] Account %d (API Key/AI Studio, type=%s) rate limited, reset at PST midnight (%v)", account.ID, account.Type, ra) logger.LegacyPrintf("service.gemini_messages_compat", "[Gemini 429] Account %d (API Key/AI Studio, type=%s) rate limited, reset at PST midnight (%v)", account.ID, account.Type, ra)
} else { } else {
// 兜底:5 分钟 // 兜底:5 分钟
ra = time.Now().Add(5 * time.Minute) ra = time.Now().Add(5 * time.Minute)
log.Printf("[Gemini 429] Account %d rate limited, fallback to 5min", account.ID) logger.LegacyPrintf("service.gemini_messages_compat", "[Gemini 429] Account %d rate limited, fallback to 5min", account.ID)
} }
} }
_ = s.accountRepo.SetRateLimited(ctx, account.ID, ra) _ = s.accountRepo.SetRateLimited(ctx, account.ID, ra)
...@@ -2659,7 +2659,7 @@ func (s *GeminiMessagesCompatService) handleGeminiUpstreamError(ctx context.Cont ...@@ -2659,7 +2659,7 @@ func (s *GeminiMessagesCompatService) handleGeminiUpstreamError(ctx context.Cont
// 使用解析到的重置时间 // 使用解析到的重置时间
resetTime := time.Unix(*resetAt, 0) resetTime := time.Unix(*resetAt, 0)
_ = s.accountRepo.SetRateLimited(ctx, account.ID, resetTime) _ = s.accountRepo.SetRateLimited(ctx, account.ID, resetTime)
log.Printf("[Gemini 429] Account %d rate limited until %v (oauth_type=%s, tier=%s)", logger.LegacyPrintf("service.gemini_messages_compat", "[Gemini 429] Account %d rate limited until %v (oauth_type=%s, tier=%s)",
account.ID, resetTime, oauthType, tierID) account.ID, resetTime, oauthType, tierID)
} }
......
...@@ -6,7 +6,6 @@ import ( ...@@ -6,7 +6,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"log"
"net/http" "net/http"
"regexp" "regexp"
"strconv" "strconv"
...@@ -16,6 +15,7 @@ import ( ...@@ -16,6 +15,7 @@ import (
"github.com/Wei-Shaw/sub2api/internal/config" "github.com/Wei-Shaw/sub2api/internal/config"
"github.com/Wei-Shaw/sub2api/internal/pkg/geminicli" "github.com/Wei-Shaw/sub2api/internal/pkg/geminicli"
"github.com/Wei-Shaw/sub2api/internal/pkg/httpclient" "github.com/Wei-Shaw/sub2api/internal/pkg/httpclient"
"github.com/Wei-Shaw/sub2api/internal/pkg/logger"
) )
const ( const (
...@@ -328,27 +328,27 @@ func extractTierIDFromAllowedTiers(allowedTiers []geminicli.AllowedTier) string ...@@ -328,27 +328,27 @@ func extractTierIDFromAllowedTiers(allowedTiers []geminicli.AllowedTier) string
// inferGoogleOneTier infers Google One tier from Drive storage limit // inferGoogleOneTier infers Google One tier from Drive storage limit
func inferGoogleOneTier(storageBytes int64) string { func inferGoogleOneTier(storageBytes int64) string {
log.Printf("[GeminiOAuth] inferGoogleOneTier - input: %d bytes (%.2f TB)", storageBytes, float64(storageBytes)/float64(TB)) logger.LegacyPrintf("service.gemini_oauth", "[GeminiOAuth] inferGoogleOneTier - input: %d bytes (%.2f TB)", storageBytes, float64(storageBytes)/float64(TB))
if storageBytes <= 0 { if storageBytes <= 0 {
log.Printf("[GeminiOAuth] inferGoogleOneTier - storageBytes <= 0, returning UNKNOWN") logger.LegacyPrintf("service.gemini_oauth", "[GeminiOAuth] inferGoogleOneTier - storageBytes <= 0, returning UNKNOWN")
return GeminiTierGoogleOneUnknown return GeminiTierGoogleOneUnknown
} }
if storageBytes > StorageTierUnlimited { if storageBytes > StorageTierUnlimited {
log.Printf("[GeminiOAuth] inferGoogleOneTier - > %d bytes (100TB), returning UNLIMITED", StorageTierUnlimited) logger.LegacyPrintf("service.gemini_oauth", "[GeminiOAuth] inferGoogleOneTier - > %d bytes (100TB), returning UNLIMITED", StorageTierUnlimited)
return GeminiTierGoogleAIUltra return GeminiTierGoogleAIUltra
} }
if storageBytes >= StorageTierAIPremium { if storageBytes >= StorageTierAIPremium {
log.Printf("[GeminiOAuth] inferGoogleOneTier - >= %d bytes (2TB), returning google_ai_pro", StorageTierAIPremium) logger.LegacyPrintf("service.gemini_oauth", "[GeminiOAuth] inferGoogleOneTier - >= %d bytes (2TB), returning google_ai_pro", StorageTierAIPremium)
return GeminiTierGoogleAIPro return GeminiTierGoogleAIPro
} }
if storageBytes >= StorageTierFree { if storageBytes >= StorageTierFree {
log.Printf("[GeminiOAuth] inferGoogleOneTier - >= %d bytes (15GB), returning FREE", StorageTierFree) logger.LegacyPrintf("service.gemini_oauth", "[GeminiOAuth] inferGoogleOneTier - >= %d bytes (15GB), returning FREE", StorageTierFree)
return GeminiTierGoogleOneFree return GeminiTierGoogleOneFree
} }
log.Printf("[GeminiOAuth] inferGoogleOneTier - < %d bytes (15GB), returning UNKNOWN", StorageTierFree) logger.LegacyPrintf("service.gemini_oauth", "[GeminiOAuth] inferGoogleOneTier - < %d bytes (15GB), returning UNKNOWN", StorageTierFree)
return GeminiTierGoogleOneUnknown return GeminiTierGoogleOneUnknown
} }
...@@ -358,30 +358,30 @@ func inferGoogleOneTier(storageBytes int64) string { ...@@ -358,30 +358,30 @@ func inferGoogleOneTier(storageBytes int64) string {
// 2. Personal accounts will get 403/404 from cloudaicompanion.googleapis.com // 2. Personal accounts will get 403/404 from cloudaicompanion.googleapis.com
// 3. Google consumer (Google One) and enterprise (GCP) systems are physically isolated // 3. Google consumer (Google One) and enterprise (GCP) systems are physically isolated
func (s *GeminiOAuthService) FetchGoogleOneTier(ctx context.Context, accessToken, proxyURL string) (string, *geminicli.DriveStorageInfo, error) { func (s *GeminiOAuthService) FetchGoogleOneTier(ctx context.Context, accessToken, proxyURL string) (string, *geminicli.DriveStorageInfo, error) {
log.Printf("[GeminiOAuth] Starting FetchGoogleOneTier (Google One personal account)") logger.LegacyPrintf("service.gemini_oauth", "[GeminiOAuth] Starting FetchGoogleOneTier (Google One personal account)")
// Use Drive API to infer tier from storage quota (requires drive.readonly scope) // Use Drive API to infer tier from storage quota (requires drive.readonly scope)
log.Printf("[GeminiOAuth] Calling Drive API for storage quota...") logger.LegacyPrintf("service.gemini_oauth", "[GeminiOAuth] Calling Drive API for storage quota...")
driveClient := geminicli.NewDriveClient() driveClient := geminicli.NewDriveClient()
storageInfo, err := driveClient.GetStorageQuota(ctx, accessToken, proxyURL) storageInfo, err := driveClient.GetStorageQuota(ctx, accessToken, proxyURL)
if err != nil { if err != nil {
// Check if it's a 403 (scope not granted) // Check if it's a 403 (scope not granted)
if strings.Contains(err.Error(), "status 403") { if strings.Contains(err.Error(), "status 403") {
log.Printf("[GeminiOAuth] Drive API scope not available (403): %v", err) logger.LegacyPrintf("service.gemini_oauth", "[GeminiOAuth] Drive API scope not available (403): %v", err)
return GeminiTierGoogleOneUnknown, nil, err return GeminiTierGoogleOneUnknown, nil, err
} }
// Other errors // Other errors
log.Printf("[GeminiOAuth] Failed to fetch Drive storage: %v", err) logger.LegacyPrintf("service.gemini_oauth", "[GeminiOAuth] Failed to fetch Drive storage: %v", err)
return GeminiTierGoogleOneUnknown, nil, err return GeminiTierGoogleOneUnknown, nil, err
} }
log.Printf("[GeminiOAuth] Drive API response - Limit: %d bytes (%.2f TB), Usage: %d bytes (%.2f GB)", logger.LegacyPrintf("service.gemini_oauth", "[GeminiOAuth] Drive API response - Limit: %d bytes (%.2f TB), Usage: %d bytes (%.2f GB)",
storageInfo.Limit, float64(storageInfo.Limit)/float64(TB), storageInfo.Limit, float64(storageInfo.Limit)/float64(TB),
storageInfo.Usage, float64(storageInfo.Usage)/float64(GB)) storageInfo.Usage, float64(storageInfo.Usage)/float64(GB))
tierID := inferGoogleOneTier(storageInfo.Limit) tierID := inferGoogleOneTier(storageInfo.Limit)
log.Printf("[GeminiOAuth] Inferred tier from storage: %s", tierID) logger.LegacyPrintf("service.gemini_oauth", "[GeminiOAuth] Inferred tier from storage: %s", tierID)
return tierID, storageInfo, nil return tierID, storageInfo, nil
} }
...@@ -441,16 +441,16 @@ func (s *GeminiOAuthService) RefreshAccountGoogleOneTier( ...@@ -441,16 +441,16 @@ func (s *GeminiOAuthService) RefreshAccountGoogleOneTier(
} }
func (s *GeminiOAuthService) ExchangeCode(ctx context.Context, input *GeminiExchangeCodeInput) (*GeminiTokenInfo, error) { func (s *GeminiOAuthService) ExchangeCode(ctx context.Context, input *GeminiExchangeCodeInput) (*GeminiTokenInfo, error) {
log.Printf("[GeminiOAuth] ========== ExchangeCode START ==========") logger.LegacyPrintf("service.gemini_oauth", "[GeminiOAuth] ========== ExchangeCode START ==========")
log.Printf("[GeminiOAuth] SessionID: %s", input.SessionID) logger.LegacyPrintf("service.gemini_oauth", "[GeminiOAuth] SessionID: %s", input.SessionID)
session, ok := s.sessionStore.Get(input.SessionID) session, ok := s.sessionStore.Get(input.SessionID)
if !ok { if !ok {
log.Printf("[GeminiOAuth] ERROR: Session not found or expired") logger.LegacyPrintf("service.gemini_oauth", "[GeminiOAuth] ERROR: Session not found or expired")
return nil, fmt.Errorf("session not found or expired") return nil, fmt.Errorf("session not found or expired")
} }
if strings.TrimSpace(input.State) == "" || input.State != session.State { if strings.TrimSpace(input.State) == "" || input.State != session.State {
log.Printf("[GeminiOAuth] ERROR: Invalid state") logger.LegacyPrintf("service.gemini_oauth", "[GeminiOAuth] ERROR: Invalid state")
return nil, fmt.Errorf("invalid state") return nil, fmt.Errorf("invalid state")
} }
...@@ -461,7 +461,7 @@ func (s *GeminiOAuthService) ExchangeCode(ctx context.Context, input *GeminiExch ...@@ -461,7 +461,7 @@ func (s *GeminiOAuthService) ExchangeCode(ctx context.Context, input *GeminiExch
proxyURL = proxy.URL() proxyURL = proxy.URL()
} }
} }
log.Printf("[GeminiOAuth] ProxyURL: %s", proxyURL) logger.LegacyPrintf("service.gemini_oauth", "[GeminiOAuth] ProxyURL: %s", proxyURL)
redirectURI := session.RedirectURI redirectURI := session.RedirectURI
...@@ -470,8 +470,8 @@ func (s *GeminiOAuthService) ExchangeCode(ctx context.Context, input *GeminiExch ...@@ -470,8 +470,8 @@ func (s *GeminiOAuthService) ExchangeCode(ctx context.Context, input *GeminiExch
if oauthType == "" { if oauthType == "" {
oauthType = "code_assist" oauthType = "code_assist"
} }
log.Printf("[GeminiOAuth] OAuth Type: %s", oauthType) logger.LegacyPrintf("service.gemini_oauth", "[GeminiOAuth] OAuth Type: %s", oauthType)
log.Printf("[GeminiOAuth] Project ID from session: %s", session.ProjectID) logger.LegacyPrintf("service.gemini_oauth", "[GeminiOAuth] Project ID from session: %s", session.ProjectID)
// If the session was created for AI Studio OAuth, ensure a custom OAuth client is configured. // If the session was created for AI Studio OAuth, ensure a custom OAuth client is configured.
if oauthType == "ai_studio" { if oauthType == "ai_studio" {
...@@ -496,12 +496,12 @@ func (s *GeminiOAuthService) ExchangeCode(ctx context.Context, input *GeminiExch ...@@ -496,12 +496,12 @@ func (s *GeminiOAuthService) ExchangeCode(ctx context.Context, input *GeminiExch
tokenResp, err := s.oauthClient.ExchangeCode(ctx, oauthType, input.Code, session.CodeVerifier, redirectURI, proxyURL) tokenResp, err := s.oauthClient.ExchangeCode(ctx, oauthType, input.Code, session.CodeVerifier, redirectURI, proxyURL)
if err != nil { if err != nil {
log.Printf("[GeminiOAuth] ERROR: Failed to exchange code: %v", err) logger.LegacyPrintf("service.gemini_oauth", "[GeminiOAuth] ERROR: Failed to exchange code: %v", err)
return nil, fmt.Errorf("failed to exchange code: %w", err) return nil, fmt.Errorf("failed to exchange code: %w", err)
} }
log.Printf("[GeminiOAuth] Token exchange successful") logger.LegacyPrintf("service.gemini_oauth", "[GeminiOAuth] Token exchange successful")
log.Printf("[GeminiOAuth] Token scope: %s", tokenResp.Scope) logger.LegacyPrintf("service.gemini_oauth", "[GeminiOAuth] Token scope: %s", tokenResp.Scope)
log.Printf("[GeminiOAuth] Token expires_in: %d seconds", tokenResp.ExpiresIn) logger.LegacyPrintf("service.gemini_oauth", "[GeminiOAuth] Token expires_in: %d seconds", tokenResp.ExpiresIn)
sessionProjectID := strings.TrimSpace(session.ProjectID) sessionProjectID := strings.TrimSpace(session.ProjectID)
s.sessionStore.Delete(input.SessionID) s.sessionStore.Delete(input.SessionID)
...@@ -523,40 +523,40 @@ func (s *GeminiOAuthService) ExchangeCode(ctx context.Context, input *GeminiExch ...@@ -523,40 +523,40 @@ func (s *GeminiOAuthService) ExchangeCode(ctx context.Context, input *GeminiExch
fallbackTierID = canonicalGeminiTierIDForOAuthType(oauthType, session.TierID) fallbackTierID = canonicalGeminiTierIDForOAuthType(oauthType, session.TierID)
} }
log.Printf("[GeminiOAuth] ========== Account Type Detection START ==========") logger.LegacyPrintf("service.gemini_oauth", "[GeminiOAuth] ========== Account Type Detection START ==========")
log.Printf("[GeminiOAuth] OAuth Type: %s", oauthType) logger.LegacyPrintf("service.gemini_oauth", "[GeminiOAuth] OAuth Type: %s", oauthType)
// 对于 code_assist 模式,project_id 是必需的,需要调用 Code Assist API // 对于 code_assist 模式,project_id 是必需的,需要调用 Code Assist API
// 对于 google_one 模式,使用个人 Google 账号,不需要 project_id,配额由 Google 网关自动识别 // 对于 google_one 模式,使用个人 Google 账号,不需要 project_id,配额由 Google 网关自动识别
// 对于 ai_studio 模式,project_id 是可选的(不影响使用 AI Studio API) // 对于 ai_studio 模式,project_id 是可选的(不影响使用 AI Studio API)
switch oauthType { switch oauthType {
case "code_assist": case "code_assist":
log.Printf("[GeminiOAuth] Processing code_assist OAuth type") logger.LegacyPrintf("service.gemini_oauth", "[GeminiOAuth] Processing code_assist OAuth type")
if projectID == "" { if projectID == "" {
log.Printf("[GeminiOAuth] No project_id provided, attempting to fetch from LoadCodeAssist API...") logger.LegacyPrintf("service.gemini_oauth", "[GeminiOAuth] No project_id provided, attempting to fetch from LoadCodeAssist API...")
var err error var err error
projectID, tierID, err = s.fetchProjectID(ctx, tokenResp.AccessToken, proxyURL) projectID, tierID, err = s.fetchProjectID(ctx, tokenResp.AccessToken, proxyURL)
if err != nil { if err != nil {
// 记录警告但不阻断流程,允许后续补充 project_id // 记录警告但不阻断流程,允许后续补充 project_id
fmt.Printf("[GeminiOAuth] Warning: Failed to fetch project_id during token exchange: %v\n", err) fmt.Printf("[GeminiOAuth] Warning: Failed to fetch project_id during token exchange: %v\n", err)
log.Printf("[GeminiOAuth] WARNING: Failed to fetch project_id: %v", err) logger.LegacyPrintf("service.gemini_oauth", "[GeminiOAuth] WARNING: Failed to fetch project_id: %v", err)
} else { } else {
log.Printf("[GeminiOAuth] Successfully fetched project_id: %s, tier_id: %s", projectID, tierID) logger.LegacyPrintf("service.gemini_oauth", "[GeminiOAuth] Successfully fetched project_id: %s, tier_id: %s", projectID, tierID)
} }
} else { } else {
log.Printf("[GeminiOAuth] User provided project_id: %s, fetching tier_id...", projectID) logger.LegacyPrintf("service.gemini_oauth", "[GeminiOAuth] User provided project_id: %s, fetching tier_id...", projectID)
// 用户手动填了 project_id,仍需调用 LoadCodeAssist 获取 tierID // 用户手动填了 project_id,仍需调用 LoadCodeAssist 获取 tierID
_, fetchedTierID, err := s.fetchProjectID(ctx, tokenResp.AccessToken, proxyURL) _, fetchedTierID, err := s.fetchProjectID(ctx, tokenResp.AccessToken, proxyURL)
if err != nil { if err != nil {
fmt.Printf("[GeminiOAuth] Warning: Failed to fetch tierID: %v\n", err) fmt.Printf("[GeminiOAuth] Warning: Failed to fetch tierID: %v\n", err)
log.Printf("[GeminiOAuth] WARNING: Failed to fetch tier_id: %v", err) logger.LegacyPrintf("service.gemini_oauth", "[GeminiOAuth] WARNING: Failed to fetch tier_id: %v", err)
} else { } else {
tierID = fetchedTierID tierID = fetchedTierID
log.Printf("[GeminiOAuth] Successfully fetched tier_id: %s", tierID) logger.LegacyPrintf("service.gemini_oauth", "[GeminiOAuth] Successfully fetched tier_id: %s", tierID)
} }
} }
if strings.TrimSpace(projectID) == "" { if strings.TrimSpace(projectID) == "" {
log.Printf("[GeminiOAuth] ERROR: Missing project_id for Code Assist OAuth") logger.LegacyPrintf("service.gemini_oauth", "[GeminiOAuth] ERROR: Missing project_id for Code Assist OAuth")
return nil, fmt.Errorf("missing project_id for Code Assist OAuth: please fill Project ID (optional field) and regenerate the auth URL, or ensure your Google account has an ACTIVE GCP project") return nil, fmt.Errorf("missing project_id for Code Assist OAuth: please fill Project ID (optional field) and regenerate the auth URL, or ensure your Google account has an ACTIVE GCP project")
} }
// Prefer auto-detected tier; fall back to user-selected tier. // Prefer auto-detected tier; fall back to user-selected tier.
...@@ -564,31 +564,31 @@ func (s *GeminiOAuthService) ExchangeCode(ctx context.Context, input *GeminiExch ...@@ -564,31 +564,31 @@ func (s *GeminiOAuthService) ExchangeCode(ctx context.Context, input *GeminiExch
if tierID == "" { if tierID == "" {
if fallbackTierID != "" { if fallbackTierID != "" {
tierID = fallbackTierID tierID = fallbackTierID
log.Printf("[GeminiOAuth] Using fallback tier_id from user/session: %s", tierID) logger.LegacyPrintf("service.gemini_oauth", "[GeminiOAuth] Using fallback tier_id from user/session: %s", tierID)
} else { } else {
tierID = GeminiTierGCPStandard tierID = GeminiTierGCPStandard
log.Printf("[GeminiOAuth] Using default tier_id: %s", tierID) logger.LegacyPrintf("service.gemini_oauth", "[GeminiOAuth] Using default tier_id: %s", tierID)
} }
} }
log.Printf("[GeminiOAuth] Final code_assist result - project_id: %s, tier_id: %s", projectID, tierID) logger.LegacyPrintf("service.gemini_oauth", "[GeminiOAuth] Final code_assist result - project_id: %s, tier_id: %s", projectID, tierID)
case "google_one": case "google_one":
log.Printf("[GeminiOAuth] Processing google_one OAuth type") logger.LegacyPrintf("service.gemini_oauth", "[GeminiOAuth] Processing google_one OAuth type")
// Google One accounts use cloudaicompanion API, which requires a project_id. // Google One accounts use cloudaicompanion API, which requires a project_id.
// For personal accounts, Google auto-assigns a project_id via the LoadCodeAssist API. // For personal accounts, Google auto-assigns a project_id via the LoadCodeAssist API.
if projectID == "" { if projectID == "" {
log.Printf("[GeminiOAuth] No project_id provided, attempting to fetch from LoadCodeAssist API...") logger.LegacyPrintf("service.gemini_oauth", "[GeminiOAuth] No project_id provided, attempting to fetch from LoadCodeAssist API...")
var err error var err error
projectID, _, err = s.fetchProjectID(ctx, tokenResp.AccessToken, proxyURL) projectID, _, err = s.fetchProjectID(ctx, tokenResp.AccessToken, proxyURL)
if err != nil { if err != nil {
log.Printf("[GeminiOAuth] ERROR: Failed to fetch project_id: %v", err) logger.LegacyPrintf("service.gemini_oauth", "[GeminiOAuth] ERROR: Failed to fetch project_id: %v", err)
return nil, fmt.Errorf("google One accounts require a project_id, failed to auto-detect: %w", err) return nil, fmt.Errorf("google One accounts require a project_id, failed to auto-detect: %w", err)
} }
log.Printf("[GeminiOAuth] Successfully fetched project_id: %s", projectID) logger.LegacyPrintf("service.gemini_oauth", "[GeminiOAuth] Successfully fetched project_id: %s", projectID)
} }
log.Printf("[GeminiOAuth] Attempting to fetch Google One tier from Drive API...") logger.LegacyPrintf("service.gemini_oauth", "[GeminiOAuth] Attempting to fetch Google One tier from Drive API...")
// Attempt to fetch Drive storage tier // Attempt to fetch Drive storage tier
var storageInfo *geminicli.DriveStorageInfo var storageInfo *geminicli.DriveStorageInfo
var err error var err error
...@@ -596,12 +596,12 @@ func (s *GeminiOAuthService) ExchangeCode(ctx context.Context, input *GeminiExch ...@@ -596,12 +596,12 @@ func (s *GeminiOAuthService) ExchangeCode(ctx context.Context, input *GeminiExch
if err != nil { if err != nil {
// Log warning but don't block - use fallback // Log warning but don't block - use fallback
fmt.Printf("[GeminiOAuth] Warning: Failed to fetch Drive tier: %v\n", err) fmt.Printf("[GeminiOAuth] Warning: Failed to fetch Drive tier: %v\n", err)
log.Printf("[GeminiOAuth] WARNING: Failed to fetch Drive tier: %v", err) logger.LegacyPrintf("service.gemini_oauth", "[GeminiOAuth] WARNING: Failed to fetch Drive tier: %v", err)
tierID = "" tierID = ""
} else { } else {
log.Printf("[GeminiOAuth] Successfully fetched Drive tier: %s", tierID) logger.LegacyPrintf("service.gemini_oauth", "[GeminiOAuth] Successfully fetched Drive tier: %s", tierID)
if storageInfo != nil { if storageInfo != nil {
log.Printf("[GeminiOAuth] Drive storage - Limit: %d bytes (%.2f TB), Usage: %d bytes (%.2f GB)", logger.LegacyPrintf("service.gemini_oauth", "[GeminiOAuth] Drive storage - Limit: %d bytes (%.2f TB), Usage: %d bytes (%.2f GB)",
storageInfo.Limit, float64(storageInfo.Limit)/float64(TB), storageInfo.Limit, float64(storageInfo.Limit)/float64(TB),
storageInfo.Usage, float64(storageInfo.Usage)/float64(GB)) storageInfo.Usage, float64(storageInfo.Usage)/float64(GB))
} }
...@@ -610,10 +610,10 @@ func (s *GeminiOAuthService) ExchangeCode(ctx context.Context, input *GeminiExch ...@@ -610,10 +610,10 @@ func (s *GeminiOAuthService) ExchangeCode(ctx context.Context, input *GeminiExch
if tierID == "" || tierID == GeminiTierGoogleOneUnknown { if tierID == "" || tierID == GeminiTierGoogleOneUnknown {
if fallbackTierID != "" { if fallbackTierID != "" {
tierID = fallbackTierID tierID = fallbackTierID
log.Printf("[GeminiOAuth] Using fallback tier_id from user/session: %s", tierID) logger.LegacyPrintf("service.gemini_oauth", "[GeminiOAuth] Using fallback tier_id from user/session: %s", tierID)
} else { } else {
tierID = GeminiTierGoogleOneFree tierID = GeminiTierGoogleOneFree
log.Printf("[GeminiOAuth] Using default tier_id: %s", tierID) logger.LegacyPrintf("service.gemini_oauth", "[GeminiOAuth] Using default tier_id: %s", tierID)
} }
} }
fmt.Printf("[GeminiOAuth] Google One tierID after normalization: %s\n", tierID) fmt.Printf("[GeminiOAuth] Google One tierID after normalization: %s\n", tierID)
...@@ -636,7 +636,7 @@ func (s *GeminiOAuthService) ExchangeCode(ctx context.Context, input *GeminiExch ...@@ -636,7 +636,7 @@ func (s *GeminiOAuthService) ExchangeCode(ctx context.Context, input *GeminiExch
"drive_tier_updated_at": time.Now().Format(time.RFC3339), "drive_tier_updated_at": time.Now().Format(time.RFC3339),
}, },
} }
log.Printf("[GeminiOAuth] ========== ExchangeCode END (google_one with storage info) ==========") logger.LegacyPrintf("service.gemini_oauth", "[GeminiOAuth] ========== ExchangeCode END (google_one with storage info) ==========")
return tokenInfo, nil return tokenInfo, nil
} }
...@@ -649,10 +649,10 @@ func (s *GeminiOAuthService) ExchangeCode(ctx context.Context, input *GeminiExch ...@@ -649,10 +649,10 @@ func (s *GeminiOAuthService) ExchangeCode(ctx context.Context, input *GeminiExch
} }
default: default:
log.Printf("[GeminiOAuth] Processing %s OAuth type (no tier detection)", oauthType) logger.LegacyPrintf("service.gemini_oauth", "[GeminiOAuth] Processing %s OAuth type (no tier detection)", oauthType)
} }
log.Printf("[GeminiOAuth] ========== Account Type Detection END ==========") logger.LegacyPrintf("service.gemini_oauth", "[GeminiOAuth] ========== Account Type Detection END ==========")
result := &GeminiTokenInfo{ result := &GeminiTokenInfo{
AccessToken: tokenResp.AccessToken, AccessToken: tokenResp.AccessToken,
...@@ -665,8 +665,8 @@ func (s *GeminiOAuthService) ExchangeCode(ctx context.Context, input *GeminiExch ...@@ -665,8 +665,8 @@ func (s *GeminiOAuthService) ExchangeCode(ctx context.Context, input *GeminiExch
TierID: tierID, TierID: tierID,
OAuthType: oauthType, OAuthType: oauthType,
} }
log.Printf("[GeminiOAuth] Final result - OAuth Type: %s, Project ID: %s, Tier ID: %s", result.OAuthType, result.ProjectID, result.TierID) logger.LegacyPrintf("service.gemini_oauth", "[GeminiOAuth] Final result - OAuth Type: %s, Project ID: %s, Tier ID: %s", result.OAuthType, result.ProjectID, result.TierID)
log.Printf("[GeminiOAuth] ========== ExchangeCode END ==========") logger.LegacyPrintf("service.gemini_oauth", "[GeminiOAuth] ========== ExchangeCode END ==========")
return result, nil return result, nil
} }
...@@ -949,23 +949,23 @@ func (s *GeminiOAuthService) fetchProjectID(ctx context.Context, accessToken, pr ...@@ -949,23 +949,23 @@ func (s *GeminiOAuthService) fetchProjectID(ctx context.Context, accessToken, pr
registeredTierID := strings.TrimSpace(loadResp.GetTier()) registeredTierID := strings.TrimSpace(loadResp.GetTier())
if registeredTierID != "" { if registeredTierID != "" {
// 已注册但未返回 cloudaicompanionProject,这在 Google One 用户中较常见:需要用户自行提供 project_id。 // 已注册但未返回 cloudaicompanionProject,这在 Google One 用户中较常见:需要用户自行提供 project_id。
log.Printf("[GeminiOAuth] User has tier (%s) but no cloudaicompanionProject, trying Cloud Resource Manager...", registeredTierID) logger.LegacyPrintf("service.gemini_oauth", "[GeminiOAuth] User has tier (%s) but no cloudaicompanionProject, trying Cloud Resource Manager...", registeredTierID)
// Try to get project from Cloud Resource Manager // Try to get project from Cloud Resource Manager
fallback, fbErr := fetchProjectIDFromResourceManager(ctx, accessToken, proxyURL) fallback, fbErr := fetchProjectIDFromResourceManager(ctx, accessToken, proxyURL)
if fbErr == nil && strings.TrimSpace(fallback) != "" { if fbErr == nil && strings.TrimSpace(fallback) != "" {
log.Printf("[GeminiOAuth] Found project from Cloud Resource Manager: %s", fallback) logger.LegacyPrintf("service.gemini_oauth", "[GeminiOAuth] Found project from Cloud Resource Manager: %s", fallback)
return strings.TrimSpace(fallback), tierID, nil return strings.TrimSpace(fallback), tierID, nil
} }
// No project found - user must provide project_id manually // No project found - user must provide project_id manually
log.Printf("[GeminiOAuth] No project found from Cloud Resource Manager, user must provide project_id manually") logger.LegacyPrintf("service.gemini_oauth", "[GeminiOAuth] No project found from Cloud Resource Manager, user must provide project_id manually")
return "", tierID, fmt.Errorf("user is registered (tier: %s) but no project_id available. Please provide Project ID manually in the authorization form, or create a project at https://console.cloud.google.com", registeredTierID) return "", tierID, fmt.Errorf("user is registered (tier: %s) but no project_id available. Please provide Project ID manually in the authorization form, or create a project at https://console.cloud.google.com", registeredTierID)
} }
} }
// 未检测到 currentTier/paidTier,视为新用户,继续调用 onboardUser // 未检测到 currentTier/paidTier,视为新用户,继续调用 onboardUser
log.Printf("[GeminiOAuth] No currentTier/paidTier found, proceeding with onboardUser (tierID: %s)", tierID) logger.LegacyPrintf("service.gemini_oauth", "[GeminiOAuth] No currentTier/paidTier found, proceeding with onboardUser (tierID: %s)", tierID)
req := &geminicli.OnboardUserRequest{ req := &geminicli.OnboardUserRequest{
TierID: tierID, TierID: tierID,
......
...@@ -7,13 +7,14 @@ import ( ...@@ -7,13 +7,14 @@ import (
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"fmt" "fmt"
"log"
"log/slog" "log/slog"
"net/http" "net/http"
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
"time" "time"
"github.com/Wei-Shaw/sub2api/internal/pkg/logger"
) )
// 预编译正则表达式(避免每次调用重新编译) // 预编译正则表达式(避免每次调用重新编译)
...@@ -84,7 +85,7 @@ func (s *IdentityService) GetOrCreateFingerprint(ctx context.Context, accountID ...@@ -84,7 +85,7 @@ func (s *IdentityService) GetOrCreateFingerprint(ctx context.Context, accountID
cached.UserAgent = clientUA cached.UserAgent = clientUA
// 保存更新后的指纹 // 保存更新后的指纹
_ = s.cache.SetFingerprint(ctx, accountID, cached) _ = s.cache.SetFingerprint(ctx, accountID, cached)
log.Printf("Updated fingerprint user-agent for account %d: %s", accountID, clientUA) logger.LegacyPrintf("service.identity", "Updated fingerprint user-agent for account %d: %s", accountID, clientUA)
} }
return cached, nil return cached, nil
} }
...@@ -97,10 +98,10 @@ func (s *IdentityService) GetOrCreateFingerprint(ctx context.Context, accountID ...@@ -97,10 +98,10 @@ func (s *IdentityService) GetOrCreateFingerprint(ctx context.Context, accountID
// 保存到缓存(永不过期) // 保存到缓存(永不过期)
if err := s.cache.SetFingerprint(ctx, accountID, fp); err != nil { if err := s.cache.SetFingerprint(ctx, accountID, fp); err != nil {
log.Printf("Warning: failed to cache fingerprint for account %d: %v", accountID, err) logger.LegacyPrintf("service.identity", "Warning: failed to cache fingerprint for account %d: %v", accountID, err)
} }
log.Printf("Created new fingerprint for account %d with client_id: %s", accountID, fp.ClientID) logger.LegacyPrintf("service.identity", "Created new fingerprint for account %d with client_id: %s", accountID, fp.ClientID)
return fp, nil return fp, nil
} }
...@@ -277,19 +278,19 @@ func (s *IdentityService) RewriteUserIDWithMasking(ctx context.Context, body []b ...@@ -277,19 +278,19 @@ func (s *IdentityService) RewriteUserIDWithMasking(ctx context.Context, body []b
// 获取或生成固定的伪装 session ID // 获取或生成固定的伪装 session ID
maskedSessionID, err := s.cache.GetMaskedSessionID(ctx, account.ID) maskedSessionID, err := s.cache.GetMaskedSessionID(ctx, account.ID)
if err != nil { if err != nil {
log.Printf("Warning: failed to get masked session ID for account %d: %v", account.ID, err) logger.LegacyPrintf("service.identity", "Warning: failed to get masked session ID for account %d: %v", account.ID, err)
return newBody, nil return newBody, nil
} }
if maskedSessionID == "" { if maskedSessionID == "" {
// 首次或已过期,生成新的伪装 session ID // 首次或已过期,生成新的伪装 session ID
maskedSessionID = generateRandomUUID() maskedSessionID = generateRandomUUID()
log.Printf("Generated new masked session ID for account %d: %s", account.ID, maskedSessionID) logger.LegacyPrintf("service.identity", "Generated new masked session ID for account %d: %s", account.ID, maskedSessionID)
} }
// 刷新 TTL(每次请求都刷新,保持 15 分钟有效期) // 刷新 TTL(每次请求都刷新,保持 15 分钟有效期)
if err := s.cache.SetMaskedSessionID(ctx, account.ID, maskedSessionID); err != nil { if err := s.cache.SetMaskedSessionID(ctx, account.ID, maskedSessionID); err != nil {
log.Printf("Warning: failed to set masked session ID for account %d: %v", account.ID, err) logger.LegacyPrintf("service.identity", "Warning: failed to set masked session ID for account %d: %v", account.ID, err)
} }
// 替换 session 部分:保留 _session_ 之前的内容,替换之后的内容 // 替换 session 部分:保留 _session_ 之前的内容,替换之后的内容
...@@ -335,7 +336,7 @@ func generateClientID() string { ...@@ -335,7 +336,7 @@ func generateClientID() string {
b := make([]byte, 32) b := make([]byte, 32)
if _, err := rand.Read(b); err != nil { if _, err := rand.Read(b); err != nil {
// 极罕见的情况,使用时间戳+固定值作为fallback // 极罕见的情况,使用时间戳+固定值作为fallback
log.Printf("Warning: crypto/rand.Read failed: %v, using fallback", err) logger.LegacyPrintf("service.identity", "Warning: crypto/rand.Read failed: %v, using fallback", err)
// 使用SHA256(当前纳秒时间)作为fallback // 使用SHA256(当前纳秒时间)作为fallback
h := sha256.Sum256([]byte(fmt.Sprintf("%d", time.Now().UnixNano()))) h := sha256.Sum256([]byte(fmt.Sprintf("%d", time.Now().UnixNano())))
return hex.EncodeToString(h[:]) return hex.EncodeToString(h[:])
......
...@@ -10,7 +10,6 @@ import ( ...@@ -10,7 +10,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"log"
"net/http" "net/http"
"sort" "sort"
"strconv" "strconv"
...@@ -19,12 +18,14 @@ import ( ...@@ -19,12 +18,14 @@ import (
"time" "time"
"github.com/Wei-Shaw/sub2api/internal/config" "github.com/Wei-Shaw/sub2api/internal/config"
"github.com/Wei-Shaw/sub2api/internal/pkg/logger"
"github.com/Wei-Shaw/sub2api/internal/pkg/openai" "github.com/Wei-Shaw/sub2api/internal/pkg/openai"
"github.com/Wei-Shaw/sub2api/internal/util/responseheaders" "github.com/Wei-Shaw/sub2api/internal/util/responseheaders"
"github.com/Wei-Shaw/sub2api/internal/util/urlvalidator" "github.com/Wei-Shaw/sub2api/internal/util/urlvalidator"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/tidwall/gjson" "github.com/tidwall/gjson"
"github.com/tidwall/sjson" "github.com/tidwall/sjson"
"go.uber.org/zap"
) )
const ( const (
...@@ -786,7 +787,7 @@ func (s *OpenAIGatewayService) Forward(ctx context.Context, c *gin.Context, acco ...@@ -786,7 +787,7 @@ func (s *OpenAIGatewayService) Forward(ctx context.Context, c *gin.Context, acco
// 对所有请求执行模型映射(包含 Codex CLI)。 // 对所有请求执行模型映射(包含 Codex CLI)。
mappedModel := account.GetMappedModel(reqModel) mappedModel := account.GetMappedModel(reqModel)
if mappedModel != reqModel { if mappedModel != reqModel {
log.Printf("[OpenAI] Model mapping applied: %s -> %s (account: %s, isCodexCLI: %v)", reqModel, mappedModel, account.Name, isCodexCLI) logger.LegacyPrintf("service.openai_gateway", "[OpenAI] Model mapping applied: %s -> %s (account: %s, isCodexCLI: %v)", reqModel, mappedModel, account.Name, isCodexCLI)
reqBody["model"] = mappedModel reqBody["model"] = mappedModel
bodyModified = true bodyModified = true
} }
...@@ -795,7 +796,7 @@ func (s *OpenAIGatewayService) Forward(ctx context.Context, c *gin.Context, acco ...@@ -795,7 +796,7 @@ func (s *OpenAIGatewayService) Forward(ctx context.Context, c *gin.Context, acco
if model, ok := reqBody["model"].(string); ok { if model, ok := reqBody["model"].(string); ok {
normalizedModel := normalizeCodexModel(model) normalizedModel := normalizeCodexModel(model)
if normalizedModel != "" && normalizedModel != model { if normalizedModel != "" && normalizedModel != model {
log.Printf("[OpenAI] Codex model normalization: %s -> %s (account: %s, type: %s, isCodexCLI: %v)", logger.LegacyPrintf("service.openai_gateway", "[OpenAI] Codex model normalization: %s -> %s (account: %s, type: %s, isCodexCLI: %v)",
model, normalizedModel, account.Name, account.Type, isCodexCLI) model, normalizedModel, account.Name, account.Type, isCodexCLI)
reqBody["model"] = normalizedModel reqBody["model"] = normalizedModel
mappedModel = normalizedModel mappedModel = normalizedModel
...@@ -808,7 +809,7 @@ func (s *OpenAIGatewayService) Forward(ctx context.Context, c *gin.Context, acco ...@@ -808,7 +809,7 @@ func (s *OpenAIGatewayService) Forward(ctx context.Context, c *gin.Context, acco
if effort, ok := reasoning["effort"].(string); ok && effort == "minimal" { if effort, ok := reasoning["effort"].(string); ok && effort == "minimal" {
reasoning["effort"] = "none" reasoning["effort"] = "none"
bodyModified = true bodyModified = true
log.Printf("[OpenAI] Normalized reasoning.effort: minimal -> none (account: %s)", account.Name) logger.LegacyPrintf("service.openai_gateway", "[OpenAI] Normalized reasoning.effort: minimal -> none (account: %s)", account.Name)
} }
} }
...@@ -1012,7 +1013,7 @@ func (s *OpenAIGatewayService) forwardOpenAIPassthrough( ...@@ -1012,7 +1013,7 @@ func (s *OpenAIGatewayService) forwardOpenAIPassthrough(
reqStream bool, reqStream bool,
startTime time.Time, startTime time.Time,
) (*OpenAIForwardResult, error) { ) (*OpenAIForwardResult, error) {
log.Printf( logger.LegacyPrintf("service.openai_gateway",
"[OpenAI 自动透传] 命中自动透传分支: account=%d name=%s type=%s model=%s stream=%v", "[OpenAI 自动透传] 命中自动透传分支: account=%d name=%s type=%s model=%s stream=%v",
account.ID, account.ID,
account.Name, account.Name,
...@@ -1022,18 +1023,15 @@ func (s *OpenAIGatewayService) forwardOpenAIPassthrough( ...@@ -1022,18 +1023,15 @@ func (s *OpenAIGatewayService) forwardOpenAIPassthrough(
) )
if reqStream && c != nil && c.Request != nil { if reqStream && c != nil && c.Request != nil {
if timeoutHeaders := collectOpenAIPassthroughTimeoutHeaders(c.Request.Header); len(timeoutHeaders) > 0 { if timeoutHeaders := collectOpenAIPassthroughTimeoutHeaders(c.Request.Header); len(timeoutHeaders) > 0 {
streamWarnLogger := logger.FromContext(ctx).With(
zap.String("component", "service.openai_gateway"),
zap.Int64("account_id", account.ID),
zap.Strings("timeout_headers", timeoutHeaders),
)
if s.isOpenAIPassthroughTimeoutHeadersAllowed() { if s.isOpenAIPassthroughTimeoutHeadersAllowed() {
log.Printf( streamWarnLogger.Warn("OpenAI passthrough 透传请求包含超时相关请求头,且当前配置为放行,可能导致上游提前断流")
"[WARN] [OpenAI passthrough] 透传请求包含超时相关请求头,且当前配置为放行,可能导致上游提前断流: account=%d headers=%s",
account.ID,
strings.Join(timeoutHeaders, ", "),
)
} else { } else {
log.Printf( streamWarnLogger.Warn("OpenAI passthrough 检测到超时相关请求头,将按配置过滤以降低断流风险")
"[WARN] [OpenAI passthrough] 检测到超时相关请求头,将按配置过滤以降低断流风险: account=%d headers=%s",
account.ID,
strings.Join(timeoutHeaders, ", "),
)
} }
} }
} }
...@@ -1347,7 +1345,7 @@ func (s *OpenAIGatewayService) handleStreamingResponsePassthrough( ...@@ -1347,7 +1345,7 @@ func (s *OpenAIGatewayService) handleStreamingResponsePassthrough(
if !clientDisconnected { if !clientDisconnected {
if _, err := fmt.Fprintln(w, line); err != nil { if _, err := fmt.Fprintln(w, line); err != nil {
clientDisconnected = true clientDisconnected = true
log.Printf("[OpenAI passthrough] Client disconnected during streaming, continue draining upstream for usage: account=%d", account.ID) logger.LegacyPrintf("service.openai_gateway", "[OpenAI passthrough] Client disconnected during streaming, continue draining upstream for usage: account=%d", account.ID)
} else { } else {
flusher.Flush() flusher.Flush()
} }
...@@ -1355,11 +1353,11 @@ func (s *OpenAIGatewayService) handleStreamingResponsePassthrough( ...@@ -1355,11 +1353,11 @@ func (s *OpenAIGatewayService) handleStreamingResponsePassthrough(
} }
if err := scanner.Err(); err != nil { if err := scanner.Err(); err != nil {
if clientDisconnected { if clientDisconnected {
log.Printf("[OpenAI passthrough] Upstream read error after client disconnect: account=%d err=%v", account.ID, err) logger.LegacyPrintf("service.openai_gateway", "[OpenAI passthrough] Upstream read error after client disconnect: account=%d err=%v", account.ID, err)
return &openaiStreamingResultPassthrough{usage: usage, firstTokenMs: firstTokenMs}, nil return &openaiStreamingResultPassthrough{usage: usage, firstTokenMs: firstTokenMs}, nil
} }
if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
log.Printf( logger.LegacyPrintf("service.openai_gateway",
"[WARN] [OpenAI passthrough] 流读取被取消,可能发生断流: account=%d request_id=%s err=%v ctx_err=%v", "[WARN] [OpenAI passthrough] 流读取被取消,可能发生断流: account=%d request_id=%s err=%v ctx_err=%v",
account.ID, account.ID,
upstreamRequestID, upstreamRequestID,
...@@ -1369,10 +1367,10 @@ func (s *OpenAIGatewayService) handleStreamingResponsePassthrough( ...@@ -1369,10 +1367,10 @@ func (s *OpenAIGatewayService) handleStreamingResponsePassthrough(
return &openaiStreamingResultPassthrough{usage: usage, firstTokenMs: firstTokenMs}, nil return &openaiStreamingResultPassthrough{usage: usage, firstTokenMs: firstTokenMs}, nil
} }
if errors.Is(err, bufio.ErrTooLong) { if errors.Is(err, bufio.ErrTooLong) {
log.Printf("[OpenAI passthrough] SSE line too long: account=%d max_size=%d error=%v", account.ID, maxLineSize, err) logger.LegacyPrintf("service.openai_gateway", "[OpenAI passthrough] SSE line too long: account=%d max_size=%d error=%v", account.ID, maxLineSize, err)
return &openaiStreamingResultPassthrough{usage: usage, firstTokenMs: firstTokenMs}, err return &openaiStreamingResultPassthrough{usage: usage, firstTokenMs: firstTokenMs}, err
} }
log.Printf( logger.LegacyPrintf("service.openai_gateway",
"[WARN] [OpenAI passthrough] 流读取异常中断: account=%d request_id=%s err=%v", "[WARN] [OpenAI passthrough] 流读取异常中断: account=%d request_id=%s err=%v",
account.ID, account.ID,
upstreamRequestID, upstreamRequestID,
...@@ -1381,11 +1379,11 @@ func (s *OpenAIGatewayService) handleStreamingResponsePassthrough( ...@@ -1381,11 +1379,11 @@ func (s *OpenAIGatewayService) handleStreamingResponsePassthrough(
return &openaiStreamingResultPassthrough{usage: usage, firstTokenMs: firstTokenMs}, fmt.Errorf("stream read error: %w", err) return &openaiStreamingResultPassthrough{usage: usage, firstTokenMs: firstTokenMs}, fmt.Errorf("stream read error: %w", err)
} }
if !clientDisconnected && !sawDone && ctx.Err() == nil { if !clientDisconnected && !sawDone && ctx.Err() == nil {
log.Printf( logger.FromContext(ctx).With(
"[WARN] [OpenAI passthrough] 上游流在未收到 [DONE] 时结束,疑似断流: account=%d request_id=%s", zap.String("component", "service.openai_gateway"),
account.ID, zap.Int64("account_id", account.ID),
upstreamRequestID, zap.String("upstream_request_id", upstreamRequestID),
) ).Warn("OpenAI passthrough 上游流在未收到 [DONE] 时结束,疑似断流")
} }
return &openaiStreamingResultPassthrough{usage: usage, firstTokenMs: firstTokenMs}, nil return &openaiStreamingResultPassthrough{usage: usage, firstTokenMs: firstTokenMs}, nil
...@@ -1584,7 +1582,7 @@ func (s *OpenAIGatewayService) handleErrorResponse(ctx context.Context, resp *ht ...@@ -1584,7 +1582,7 @@ func (s *OpenAIGatewayService) handleErrorResponse(ctx context.Context, resp *ht
setOpsUpstreamError(c, resp.StatusCode, upstreamMsg, upstreamDetail) setOpsUpstreamError(c, resp.StatusCode, upstreamMsg, upstreamDetail)
if s.cfg != nil && s.cfg.Gateway.LogUpstreamErrorBody { if s.cfg != nil && s.cfg.Gateway.LogUpstreamErrorBody {
log.Printf( logger.LegacyPrintf("service.openai_gateway",
"OpenAI upstream error %d (account=%d platform=%s type=%s): %s", "OpenAI upstream error %d (account=%d platform=%s type=%s): %s",
resp.StatusCode, resp.StatusCode,
account.ID, account.ID,
...@@ -1844,16 +1842,16 @@ func (s *OpenAIGatewayService) handleStreamingResponse(ctx context.Context, resp ...@@ -1844,16 +1842,16 @@ func (s *OpenAIGatewayService) handleStreamingResponse(ctx context.Context, resp
// 客户端断开/取消请求时,上游读取往往会返回 context canceled。 // 客户端断开/取消请求时,上游读取往往会返回 context canceled。
// /v1/responses 的 SSE 事件必须符合 OpenAI 协议;这里不注入自定义 error event,避免下游 SDK 解析失败。 // /v1/responses 的 SSE 事件必须符合 OpenAI 协议;这里不注入自定义 error event,避免下游 SDK 解析失败。
if errors.Is(ev.err, context.Canceled) || errors.Is(ev.err, context.DeadlineExceeded) { if errors.Is(ev.err, context.Canceled) || errors.Is(ev.err, context.DeadlineExceeded) {
log.Printf("Context canceled during streaming, returning collected usage") logger.LegacyPrintf("service.openai_gateway", "Context canceled during streaming, returning collected usage")
return &openaiStreamingResult{usage: usage, firstTokenMs: firstTokenMs}, nil return &openaiStreamingResult{usage: usage, firstTokenMs: firstTokenMs}, nil
} }
// 客户端已断开时,上游出错仅影响体验,不影响计费;返回已收集 usage // 客户端已断开时,上游出错仅影响体验,不影响计费;返回已收集 usage
if clientDisconnected { if clientDisconnected {
log.Printf("Upstream read error after client disconnect: %v, returning collected usage", ev.err) logger.LegacyPrintf("service.openai_gateway", "Upstream read error after client disconnect: %v, returning collected usage", ev.err)
return &openaiStreamingResult{usage: usage, firstTokenMs: firstTokenMs}, nil return &openaiStreamingResult{usage: usage, firstTokenMs: firstTokenMs}, nil
} }
if errors.Is(ev.err, bufio.ErrTooLong) { if errors.Is(ev.err, bufio.ErrTooLong) {
log.Printf("SSE line too long: account=%d max_size=%d error=%v", account.ID, maxLineSize, ev.err) logger.LegacyPrintf("service.openai_gateway", "SSE line too long: account=%d max_size=%d error=%v", account.ID, maxLineSize, ev.err)
sendErrorEvent("response_too_large") sendErrorEvent("response_too_large")
return &openaiStreamingResult{usage: usage, firstTokenMs: firstTokenMs}, ev.err return &openaiStreamingResult{usage: usage, firstTokenMs: firstTokenMs}, ev.err
} }
...@@ -1882,7 +1880,7 @@ func (s *OpenAIGatewayService) handleStreamingResponse(ctx context.Context, resp ...@@ -1882,7 +1880,7 @@ func (s *OpenAIGatewayService) handleStreamingResponse(ctx context.Context, resp
if !clientDisconnected { if !clientDisconnected {
if _, err := fmt.Fprintf(w, "%s\n", line); err != nil { if _, err := fmt.Fprintf(w, "%s\n", line); err != nil {
clientDisconnected = true clientDisconnected = true
log.Printf("Client disconnected during streaming, continuing to drain upstream for billing") logger.LegacyPrintf("service.openai_gateway", "Client disconnected during streaming, continuing to drain upstream for billing")
} else { } else {
flusher.Flush() flusher.Flush()
} }
...@@ -1899,7 +1897,7 @@ func (s *OpenAIGatewayService) handleStreamingResponse(ctx context.Context, resp ...@@ -1899,7 +1897,7 @@ func (s *OpenAIGatewayService) handleStreamingResponse(ctx context.Context, resp
if !clientDisconnected { if !clientDisconnected {
if _, err := fmt.Fprintf(w, "%s\n", line); err != nil { if _, err := fmt.Fprintf(w, "%s\n", line); err != nil {
clientDisconnected = true clientDisconnected = true
log.Printf("Client disconnected during streaming, continuing to drain upstream for billing") logger.LegacyPrintf("service.openai_gateway", "Client disconnected during streaming, continuing to drain upstream for billing")
} else { } else {
flusher.Flush() flusher.Flush()
} }
...@@ -1912,10 +1910,10 @@ func (s *OpenAIGatewayService) handleStreamingResponse(ctx context.Context, resp ...@@ -1912,10 +1910,10 @@ func (s *OpenAIGatewayService) handleStreamingResponse(ctx context.Context, resp
continue continue
} }
if clientDisconnected { if clientDisconnected {
log.Printf("Upstream timeout after client disconnect, returning collected usage") logger.LegacyPrintf("service.openai_gateway", "Upstream timeout after client disconnect, returning collected usage")
return &openaiStreamingResult{usage: usage, firstTokenMs: firstTokenMs}, nil return &openaiStreamingResult{usage: usage, firstTokenMs: firstTokenMs}, nil
} }
log.Printf("Stream data interval timeout: account=%d model=%s interval=%s", account.ID, originalModel, streamInterval) logger.LegacyPrintf("service.openai_gateway", "Stream data interval timeout: account=%d model=%s interval=%s", account.ID, originalModel, streamInterval)
// 处理流超时,可能标记账户为临时不可调度或错误状态 // 处理流超时,可能标记账户为临时不可调度或错误状态
if s.rateLimitService != nil { if s.rateLimitService != nil {
s.rateLimitService.HandleStreamTimeout(ctx, account, originalModel) s.rateLimitService.HandleStreamTimeout(ctx, account, originalModel)
...@@ -1932,7 +1930,7 @@ func (s *OpenAIGatewayService) handleStreamingResponse(ctx context.Context, resp ...@@ -1932,7 +1930,7 @@ func (s *OpenAIGatewayService) handleStreamingResponse(ctx context.Context, resp
} }
if _, err := fmt.Fprint(w, ":\n\n"); err != nil { if _, err := fmt.Fprint(w, ":\n\n"); err != nil {
clientDisconnected = true clientDisconnected = true
log.Printf("Client disconnected during streaming, continuing to drain upstream for billing") logger.LegacyPrintf("service.openai_gateway", "Client disconnected during streaming, continuing to drain upstream for billing")
continue continue
} }
flusher.Flush() flusher.Flush()
...@@ -2323,7 +2321,7 @@ func (s *OpenAIGatewayService) RecordUsage(ctx context.Context, input *OpenAIRec ...@@ -2323,7 +2321,7 @@ func (s *OpenAIGatewayService) RecordUsage(ctx context.Context, input *OpenAIRec
inserted, err := s.usageLogRepo.Create(ctx, usageLog) inserted, err := s.usageLogRepo.Create(ctx, usageLog)
if s.cfg != nil && s.cfg.RunMode == config.RunModeSimple { if s.cfg != nil && s.cfg.RunMode == config.RunModeSimple {
log.Printf("[SIMPLE MODE] Usage recorded (not billed): user=%d, tokens=%d", usageLog.UserID, usageLog.TotalTokens()) logger.LegacyPrintf("service.openai_gateway", "[SIMPLE MODE] Usage recorded (not billed): user=%d, tokens=%d", usageLog.UserID, usageLog.TotalTokens())
s.deferredService.ScheduleLastUsedUpdate(account.ID) s.deferredService.ScheduleLastUsedUpdate(account.ID)
return nil return nil
} }
...@@ -2346,7 +2344,7 @@ func (s *OpenAIGatewayService) RecordUsage(ctx context.Context, input *OpenAIRec ...@@ -2346,7 +2344,7 @@ func (s *OpenAIGatewayService) RecordUsage(ctx context.Context, input *OpenAIRec
// Update API key quota if applicable (only for balance mode with quota set) // Update API key quota if applicable (only for balance mode with quota set)
if shouldBill && cost.ActualCost > 0 && apiKey.Quota > 0 && input.APIKeyService != nil { if shouldBill && cost.ActualCost > 0 && apiKey.Quota > 0 && input.APIKeyService != nil {
if err := input.APIKeyService.UpdateQuotaUsed(ctx, apiKey.ID, cost.ActualCost); err != nil { if err := input.APIKeyService.UpdateQuotaUsed(ctx, apiKey.ID, cost.ActualCost); err != nil {
log.Printf("Update API key quota failed: %v", err) logger.LegacyPrintf("service.openai_gateway", "Update API key quota failed: %v", err)
} }
} }
......
...@@ -3,17 +3,17 @@ package service ...@@ -3,17 +3,17 @@ package service
import ( import (
"bytes" "bytes"
"context" "context"
"fmt"
"io" "io"
"log"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"os"
"strings" "strings"
"sync" "sync"
"testing" "testing"
"time" "time"
"github.com/Wei-Shaw/sub2api/internal/config" "github.com/Wei-Shaw/sub2api/internal/config"
"github.com/Wei-Shaw/sub2api/internal/pkg/logger"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
...@@ -46,24 +46,76 @@ func (u *httpUpstreamRecorder) DoWithTLS(req *http.Request, proxyURL string, acc ...@@ -46,24 +46,76 @@ func (u *httpUpstreamRecorder) DoWithTLS(req *http.Request, proxyURL string, acc
return u.Do(req, proxyURL, accountID, accountConcurrency) return u.Do(req, proxyURL, accountID, accountConcurrency)
} }
var stdLogCaptureMu sync.Mutex var structuredLogCaptureMu sync.Mutex
func captureStdLog(t *testing.T) (*bytes.Buffer, func()) { type inMemoryLogSink struct {
t.Helper() mu sync.Mutex
stdLogCaptureMu.Lock() events []*logger.LogEvent
buf := &bytes.Buffer{} }
prevWriter := log.Writer()
prevFlags := log.Flags() func (s *inMemoryLogSink) WriteLogEvent(event *logger.LogEvent) {
log.SetFlags(0) if event == nil {
log.SetOutput(buf) return
return buf, func() { }
log.SetOutput(prevWriter) cloned := *event
log.SetFlags(prevFlags) if event.Fields != nil {
// 防御性恢复,避免其他测试改动了底层 writer。 cloned.Fields = make(map[string]any, len(event.Fields))
if prevWriter == nil { for k, v := range event.Fields {
log.SetOutput(os.Stderr) cloned.Fields[k] = v
}
}
s.mu.Lock()
s.events = append(s.events, &cloned)
s.mu.Unlock()
}
func (s *inMemoryLogSink) ContainsMessage(substr string) bool {
s.mu.Lock()
defer s.mu.Unlock()
for _, ev := range s.events {
if ev != nil && strings.Contains(ev.Message, substr) {
return true
}
}
return false
}
func (s *inMemoryLogSink) ContainsFieldValue(field, substr string) bool {
s.mu.Lock()
defer s.mu.Unlock()
for _, ev := range s.events {
if ev == nil || ev.Fields == nil {
continue
}
if v, ok := ev.Fields[field]; ok && strings.Contains(fmt.Sprint(v), substr) {
return true
} }
stdLogCaptureMu.Unlock() }
return false
}
func captureStructuredLog(t *testing.T) (*inMemoryLogSink, func()) {
t.Helper()
structuredLogCaptureMu.Lock()
err := logger.Init(logger.InitOptions{
Level: "debug",
Format: "json",
ServiceName: "sub2api",
Environment: "test",
Output: logger.OutputOptions{
ToStdout: true,
ToFile: false,
},
Sampling: logger.SamplingOptions{Enabled: false},
})
require.NoError(t, err)
sink := &inMemoryLogSink{}
logger.SetSink(sink)
return sink, func() {
logger.SetSink(nil)
structuredLogCaptureMu.Unlock()
} }
} }
...@@ -486,7 +538,7 @@ func TestOpenAIGatewayService_APIKeyPassthrough_PreservesBodyAndUsesResponsesEnd ...@@ -486,7 +538,7 @@ func TestOpenAIGatewayService_APIKeyPassthrough_PreservesBodyAndUsesResponsesEnd
func TestOpenAIGatewayService_OAuthPassthrough_WarnOnTimeoutHeadersForStream(t *testing.T) { func TestOpenAIGatewayService_OAuthPassthrough_WarnOnTimeoutHeadersForStream(t *testing.T) {
gin.SetMode(gin.TestMode) gin.SetMode(gin.TestMode)
logBuf, restore := captureStdLog(t) logSink, restore := captureStructuredLog(t)
defer restore() defer restore()
rec := httptest.NewRecorder() rec := httptest.NewRecorder()
...@@ -521,13 +573,13 @@ func TestOpenAIGatewayService_OAuthPassthrough_WarnOnTimeoutHeadersForStream(t * ...@@ -521,13 +573,13 @@ func TestOpenAIGatewayService_OAuthPassthrough_WarnOnTimeoutHeadersForStream(t *
_, err := svc.Forward(context.Background(), c, account, originalBody) _, err := svc.Forward(context.Background(), c, account, originalBody)
require.NoError(t, err) require.NoError(t, err)
require.Contains(t, logBuf.String(), "检测到超时相关请求头,将按配置过滤以降低断流风险") require.True(t, logSink.ContainsMessage("检测到超时相关请求头,将按配置过滤以降低断流风险"))
require.Contains(t, logBuf.String(), "x-stainless-timeout=10000") require.True(t, logSink.ContainsFieldValue("timeout_headers", "x-stainless-timeout=10000"))
} }
func TestOpenAIGatewayService_OAuthPassthrough_WarnWhenStreamEndsWithoutDone(t *testing.T) { func TestOpenAIGatewayService_OAuthPassthrough_WarnWhenStreamEndsWithoutDone(t *testing.T) {
gin.SetMode(gin.TestMode) gin.SetMode(gin.TestMode)
logBuf, restore := captureStdLog(t) logSink, restore := captureStructuredLog(t)
defer restore() defer restore()
rec := httptest.NewRecorder() rec := httptest.NewRecorder()
...@@ -562,8 +614,8 @@ func TestOpenAIGatewayService_OAuthPassthrough_WarnWhenStreamEndsWithoutDone(t * ...@@ -562,8 +614,8 @@ func TestOpenAIGatewayService_OAuthPassthrough_WarnWhenStreamEndsWithoutDone(t *
_, err := svc.Forward(context.Background(), c, account, originalBody) _, err := svc.Forward(context.Background(), c, account, originalBody)
require.NoError(t, err) require.NoError(t, err)
require.Contains(t, logBuf.String(), "上游流在未收到 [DONE] 时结束,疑似断流") require.True(t, logSink.ContainsMessage("上游流在未收到 [DONE] 时结束,疑似断流"))
require.Contains(t, logBuf.String(), "rid-truncate") require.True(t, logSink.ContainsFieldValue("upstream_request_id", "rid-truncate"))
} }
func TestOpenAIGatewayService_OAuthPassthrough_DefaultFiltersTimeoutHeaders(t *testing.T) { func TestOpenAIGatewayService_OAuthPassthrough_DefaultFiltersTimeoutHeaders(t *testing.T) {
......
...@@ -3,8 +3,9 @@ package service ...@@ -3,8 +3,9 @@ package service
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"log"
"sync" "sync"
"github.com/Wei-Shaw/sub2api/internal/pkg/logger"
) )
// codexToolNameMapping 定义 Codex 原生工具名称到 OpenCode 工具名称的映射 // codexToolNameMapping 定义 Codex 原生工具名称到 OpenCode 工具名称的映射
...@@ -140,7 +141,7 @@ func (c *CodexToolCorrector) CorrectToolCallsInSSEData(data string) (string, boo ...@@ -140,7 +141,7 @@ func (c *CodexToolCorrector) CorrectToolCallsInSSEData(data string) (string, boo
// 序列化回 JSON // 序列化回 JSON
correctedBytes, err := json.Marshal(payload) correctedBytes, err := json.Marshal(payload)
if err != nil { if err != nil {
log.Printf("[CodexToolCorrector] Failed to marshal corrected data: %v", err) logger.LegacyPrintf("service.openai_tool_corrector", "[CodexToolCorrector] Failed to marshal corrected data: %v", err)
return data, false return data, false
} }
...@@ -219,13 +220,13 @@ func (c *CodexToolCorrector) correctToolParameters(toolName string, functionCall ...@@ -219,13 +220,13 @@ func (c *CodexToolCorrector) correctToolParameters(toolName string, functionCall
argsMap["workdir"] = workDir argsMap["workdir"] = workDir
delete(argsMap, "work_dir") delete(argsMap, "work_dir")
corrected = true corrected = true
log.Printf("[CodexToolCorrector] Renamed 'work_dir' to 'workdir' in bash tool") logger.LegacyPrintf("service.openai_tool_corrector", "[CodexToolCorrector] Renamed 'work_dir' to 'workdir' in bash tool")
} }
} else { } else {
if _, exists := argsMap["work_dir"]; exists { if _, exists := argsMap["work_dir"]; exists {
delete(argsMap, "work_dir") delete(argsMap, "work_dir")
corrected = true corrected = true
log.Printf("[CodexToolCorrector] Removed duplicate 'work_dir' parameter from bash tool") logger.LegacyPrintf("service.openai_tool_corrector", "[CodexToolCorrector] Removed duplicate 'work_dir' parameter from bash tool")
} }
} }
...@@ -236,17 +237,17 @@ func (c *CodexToolCorrector) correctToolParameters(toolName string, functionCall ...@@ -236,17 +237,17 @@ func (c *CodexToolCorrector) correctToolParameters(toolName string, functionCall
argsMap["filePath"] = filePath argsMap["filePath"] = filePath
delete(argsMap, "file_path") delete(argsMap, "file_path")
corrected = true corrected = true
log.Printf("[CodexToolCorrector] Renamed 'file_path' to 'filePath' in edit tool") logger.LegacyPrintf("service.openai_tool_corrector", "[CodexToolCorrector] Renamed 'file_path' to 'filePath' in edit tool")
} else if filePath, exists := argsMap["path"]; exists { } else if filePath, exists := argsMap["path"]; exists {
argsMap["filePath"] = filePath argsMap["filePath"] = filePath
delete(argsMap, "path") delete(argsMap, "path")
corrected = true corrected = true
log.Printf("[CodexToolCorrector] Renamed 'path' to 'filePath' in edit tool") logger.LegacyPrintf("service.openai_tool_corrector", "[CodexToolCorrector] Renamed 'path' to 'filePath' in edit tool")
} else if filePath, exists := argsMap["file"]; exists { } else if filePath, exists := argsMap["file"]; exists {
argsMap["filePath"] = filePath argsMap["filePath"] = filePath
delete(argsMap, "file") delete(argsMap, "file")
corrected = true corrected = true
log.Printf("[CodexToolCorrector] Renamed 'file' to 'filePath' in edit tool") logger.LegacyPrintf("service.openai_tool_corrector", "[CodexToolCorrector] Renamed 'file' to 'filePath' in edit tool")
} }
} }
...@@ -255,7 +256,7 @@ func (c *CodexToolCorrector) correctToolParameters(toolName string, functionCall ...@@ -255,7 +256,7 @@ func (c *CodexToolCorrector) correctToolParameters(toolName string, functionCall
argsMap["oldString"] = oldString argsMap["oldString"] = oldString
delete(argsMap, "old_string") delete(argsMap, "old_string")
corrected = true corrected = true
log.Printf("[CodexToolCorrector] Renamed 'old_string' to 'oldString' in edit tool") logger.LegacyPrintf("service.openai_tool_corrector", "[CodexToolCorrector] Renamed 'old_string' to 'oldString' in edit tool")
} }
} }
...@@ -264,7 +265,7 @@ func (c *CodexToolCorrector) correctToolParameters(toolName string, functionCall ...@@ -264,7 +265,7 @@ func (c *CodexToolCorrector) correctToolParameters(toolName string, functionCall
argsMap["newString"] = newString argsMap["newString"] = newString
delete(argsMap, "new_string") delete(argsMap, "new_string")
corrected = true corrected = true
log.Printf("[CodexToolCorrector] Renamed 'new_string' to 'newString' in edit tool") logger.LegacyPrintf("service.openai_tool_corrector", "[CodexToolCorrector] Renamed 'new_string' to 'newString' in edit tool")
} }
} }
...@@ -273,7 +274,7 @@ func (c *CodexToolCorrector) correctToolParameters(toolName string, functionCall ...@@ -273,7 +274,7 @@ func (c *CodexToolCorrector) correctToolParameters(toolName string, functionCall
argsMap["replaceAll"] = replaceAll argsMap["replaceAll"] = replaceAll
delete(argsMap, "replace_all") delete(argsMap, "replace_all")
corrected = true corrected = true
log.Printf("[CodexToolCorrector] Renamed 'replace_all' to 'replaceAll' in edit tool") logger.LegacyPrintf("service.openai_tool_corrector", "[CodexToolCorrector] Renamed 'replace_all' to 'replaceAll' in edit tool")
} }
} }
} }
...@@ -303,7 +304,7 @@ func (c *CodexToolCorrector) recordCorrection(from, to string) { ...@@ -303,7 +304,7 @@ func (c *CodexToolCorrector) recordCorrection(from, to string) {
key := fmt.Sprintf("%s->%s", from, to) key := fmt.Sprintf("%s->%s", from, to)
c.stats.CorrectionsByTool[key]++ c.stats.CorrectionsByTool[key]++
log.Printf("[CodexToolCorrector] Corrected tool call: %s -> %s (total: %d)", logger.LegacyPrintf("service.openai_tool_corrector", "[CodexToolCorrector] Corrected tool call: %s -> %s (total: %d)",
from, to, c.stats.TotalCorrected) from, to, c.stats.TotalCorrected)
} }
......
...@@ -5,12 +5,12 @@ import ( ...@@ -5,12 +5,12 @@ import (
"database/sql" "database/sql"
"errors" "errors"
"fmt" "fmt"
"log"
"strings" "strings"
"sync" "sync"
"time" "time"
"github.com/Wei-Shaw/sub2api/internal/config" "github.com/Wei-Shaw/sub2api/internal/config"
"github.com/Wei-Shaw/sub2api/internal/pkg/logger"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/redis/go-redis/v9" "github.com/redis/go-redis/v9"
) )
...@@ -190,7 +190,7 @@ func (s *OpsAggregationService) aggregateHourly() { ...@@ -190,7 +190,7 @@ func (s *OpsAggregationService) aggregateHourly() {
latest, ok, err := s.opsRepo.GetLatestHourlyBucketStart(ctxMax) latest, ok, err := s.opsRepo.GetLatestHourlyBucketStart(ctxMax)
cancelMax() cancelMax()
if err != nil { if err != nil {
log.Printf("[OpsAggregation][hourly] failed to read latest bucket: %v", err) logger.LegacyPrintf("service.ops_aggregation", "[OpsAggregation][hourly] failed to read latest bucket: %v", err)
} else if ok { } else if ok {
candidate := latest.Add(-opsAggHourlyOverlap) candidate := latest.Add(-opsAggHourlyOverlap)
if candidate.After(start) { if candidate.After(start) {
...@@ -209,7 +209,7 @@ func (s *OpsAggregationService) aggregateHourly() { ...@@ -209,7 +209,7 @@ func (s *OpsAggregationService) aggregateHourly() {
chunkEnd := minTime(cursor.Add(opsAggHourlyChunk), end) chunkEnd := minTime(cursor.Add(opsAggHourlyChunk), end)
if err := s.opsRepo.UpsertHourlyMetrics(ctx, cursor, chunkEnd); err != nil { if err := s.opsRepo.UpsertHourlyMetrics(ctx, cursor, chunkEnd); err != nil {
aggErr = err aggErr = err
log.Printf("[OpsAggregation][hourly] upsert failed (%s..%s): %v", cursor.Format(time.RFC3339), chunkEnd.Format(time.RFC3339), err) logger.LegacyPrintf("service.ops_aggregation", "[OpsAggregation][hourly] upsert failed (%s..%s): %v", cursor.Format(time.RFC3339), chunkEnd.Format(time.RFC3339), err)
break break
} }
} }
...@@ -288,7 +288,7 @@ func (s *OpsAggregationService) aggregateDaily() { ...@@ -288,7 +288,7 @@ func (s *OpsAggregationService) aggregateDaily() {
latest, ok, err := s.opsRepo.GetLatestDailyBucketDate(ctxMax) latest, ok, err := s.opsRepo.GetLatestDailyBucketDate(ctxMax)
cancelMax() cancelMax()
if err != nil { if err != nil {
log.Printf("[OpsAggregation][daily] failed to read latest bucket: %v", err) logger.LegacyPrintf("service.ops_aggregation", "[OpsAggregation][daily] failed to read latest bucket: %v", err)
} else if ok { } else if ok {
candidate := latest.Add(-opsAggDailyOverlap) candidate := latest.Add(-opsAggDailyOverlap)
if candidate.After(start) { if candidate.After(start) {
...@@ -307,7 +307,7 @@ func (s *OpsAggregationService) aggregateDaily() { ...@@ -307,7 +307,7 @@ func (s *OpsAggregationService) aggregateDaily() {
chunkEnd := minTime(cursor.Add(opsAggDailyChunk), end) chunkEnd := minTime(cursor.Add(opsAggDailyChunk), end)
if err := s.opsRepo.UpsertDailyMetrics(ctx, cursor, chunkEnd); err != nil { if err := s.opsRepo.UpsertDailyMetrics(ctx, cursor, chunkEnd); err != nil {
aggErr = err aggErr = err
log.Printf("[OpsAggregation][daily] upsert failed (%s..%s): %v", cursor.Format("2006-01-02"), chunkEnd.Format("2006-01-02"), err) logger.LegacyPrintf("service.ops_aggregation", "[OpsAggregation][daily] upsert failed (%s..%s): %v", cursor.Format("2006-01-02"), chunkEnd.Format("2006-01-02"), err)
break break
} }
} }
...@@ -427,7 +427,7 @@ func (s *OpsAggregationService) maybeLogSkip(prefix string) { ...@@ -427,7 +427,7 @@ func (s *OpsAggregationService) maybeLogSkip(prefix string) {
if prefix == "" { if prefix == "" {
prefix = "[OpsAggregation]" prefix = "[OpsAggregation]"
} }
log.Printf("%s leader lock held by another instance; skipping", prefix) logger.LegacyPrintf("service.ops_aggregation", "%s leader lock held by another instance; skipping", prefix)
} }
func utcFloorToHour(t time.Time) time.Time { func utcFloorToHour(t time.Time) time.Time {
......
...@@ -3,7 +3,6 @@ package service ...@@ -3,7 +3,6 @@ package service
import ( import (
"context" "context"
"fmt" "fmt"
"log"
"math" "math"
"strconv" "strconv"
"strings" "strings"
...@@ -11,6 +10,7 @@ import ( ...@@ -11,6 +10,7 @@ import (
"time" "time"
"github.com/Wei-Shaw/sub2api/internal/config" "github.com/Wei-Shaw/sub2api/internal/config"
"github.com/Wei-Shaw/sub2api/internal/pkg/logger"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/redis/go-redis/v9" "github.com/redis/go-redis/v9"
) )
...@@ -186,7 +186,7 @@ func (s *OpsAlertEvaluatorService) evaluateOnce(interval time.Duration) { ...@@ -186,7 +186,7 @@ func (s *OpsAlertEvaluatorService) evaluateOnce(interval time.Duration) {
rules, err := s.opsRepo.ListAlertRules(ctx) rules, err := s.opsRepo.ListAlertRules(ctx)
if err != nil { if err != nil {
s.recordHeartbeatError(runAt, time.Since(startedAt), err) s.recordHeartbeatError(runAt, time.Since(startedAt), err)
log.Printf("[OpsAlertEvaluator] list rules failed: %v", err) logger.LegacyPrintf("service.ops_alert_evaluator", "[OpsAlertEvaluator] list rules failed: %v", err)
return return
} }
...@@ -236,7 +236,7 @@ func (s *OpsAlertEvaluatorService) evaluateOnce(interval time.Duration) { ...@@ -236,7 +236,7 @@ func (s *OpsAlertEvaluatorService) evaluateOnce(interval time.Duration) {
activeEvent, err := s.opsRepo.GetActiveAlertEvent(ctx, rule.ID) activeEvent, err := s.opsRepo.GetActiveAlertEvent(ctx, rule.ID)
if err != nil { if err != nil {
log.Printf("[OpsAlertEvaluator] get active event failed (rule=%d): %v", rule.ID, err) logger.LegacyPrintf("service.ops_alert_evaluator", "[OpsAlertEvaluator] get active event failed (rule=%d): %v", rule.ID, err)
continue continue
} }
...@@ -258,7 +258,7 @@ func (s *OpsAlertEvaluatorService) evaluateOnce(interval time.Duration) { ...@@ -258,7 +258,7 @@ func (s *OpsAlertEvaluatorService) evaluateOnce(interval time.Duration) {
latestEvent, err := s.opsRepo.GetLatestAlertEvent(ctx, rule.ID) latestEvent, err := s.opsRepo.GetLatestAlertEvent(ctx, rule.ID)
if err != nil { if err != nil {
log.Printf("[OpsAlertEvaluator] get latest event failed (rule=%d): %v", rule.ID, err) logger.LegacyPrintf("service.ops_alert_evaluator", "[OpsAlertEvaluator] get latest event failed (rule=%d): %v", rule.ID, err)
continue continue
} }
if latestEvent != nil && rule.CooldownMinutes > 0 { if latestEvent != nil && rule.CooldownMinutes > 0 {
...@@ -283,7 +283,7 @@ func (s *OpsAlertEvaluatorService) evaluateOnce(interval time.Duration) { ...@@ -283,7 +283,7 @@ func (s *OpsAlertEvaluatorService) evaluateOnce(interval time.Duration) {
created, err := s.opsRepo.CreateAlertEvent(ctx, firedEvent) created, err := s.opsRepo.CreateAlertEvent(ctx, firedEvent)
if err != nil { if err != nil {
log.Printf("[OpsAlertEvaluator] create event failed (rule=%d): %v", rule.ID, err) logger.LegacyPrintf("service.ops_alert_evaluator", "[OpsAlertEvaluator] create event failed (rule=%d): %v", rule.ID, err)
continue continue
} }
...@@ -300,7 +300,7 @@ func (s *OpsAlertEvaluatorService) evaluateOnce(interval time.Duration) { ...@@ -300,7 +300,7 @@ func (s *OpsAlertEvaluatorService) evaluateOnce(interval time.Duration) {
if activeEvent != nil { if activeEvent != nil {
resolvedAt := now resolvedAt := now
if err := s.opsRepo.UpdateAlertEventStatus(ctx, activeEvent.ID, OpsAlertStatusResolved, &resolvedAt); err != nil { if err := s.opsRepo.UpdateAlertEventStatus(ctx, activeEvent.ID, OpsAlertStatusResolved, &resolvedAt); err != nil {
log.Printf("[OpsAlertEvaluator] resolve event failed (event=%d): %v", activeEvent.ID, err) logger.LegacyPrintf("service.ops_alert_evaluator", "[OpsAlertEvaluator] resolve event failed (event=%d): %v", activeEvent.ID, err)
} else { } else {
eventsResolved++ eventsResolved++
} }
...@@ -779,7 +779,7 @@ func (s *OpsAlertEvaluatorService) tryAcquireLeaderLock(ctx context.Context, loc ...@@ -779,7 +779,7 @@ func (s *OpsAlertEvaluatorService) tryAcquireLeaderLock(ctx context.Context, loc
} }
if s.redisClient == nil { if s.redisClient == nil {
s.warnNoRedisOnce.Do(func() { s.warnNoRedisOnce.Do(func() {
log.Printf("[OpsAlertEvaluator] redis not configured; running without distributed lock") logger.LegacyPrintf("service.ops_alert_evaluator", "[OpsAlertEvaluator] redis not configured; running without distributed lock")
}) })
return nil, true return nil, true
} }
...@@ -797,7 +797,7 @@ func (s *OpsAlertEvaluatorService) tryAcquireLeaderLock(ctx context.Context, loc ...@@ -797,7 +797,7 @@ func (s *OpsAlertEvaluatorService) tryAcquireLeaderLock(ctx context.Context, loc
// Prefer fail-closed to avoid duplicate evaluators stampeding the DB when Redis is flaky. // Prefer fail-closed to avoid duplicate evaluators stampeding the DB when Redis is flaky.
// Single-node deployments can disable the distributed lock via runtime settings. // Single-node deployments can disable the distributed lock via runtime settings.
s.warnNoRedisOnce.Do(func() { s.warnNoRedisOnce.Do(func() {
log.Printf("[OpsAlertEvaluator] leader lock SetNX failed; skipping this cycle: %v", err) logger.LegacyPrintf("service.ops_alert_evaluator", "[OpsAlertEvaluator] leader lock SetNX failed; skipping this cycle: %v", err)
}) })
return nil, false return nil, false
} }
...@@ -819,7 +819,7 @@ func (s *OpsAlertEvaluatorService) maybeLogSkip(key string) { ...@@ -819,7 +819,7 @@ func (s *OpsAlertEvaluatorService) maybeLogSkip(key string) {
return return
} }
s.skipLogAt = now s.skipLogAt = now
log.Printf("[OpsAlertEvaluator] leader lock held by another instance; skipping (key=%q)", key) logger.LegacyPrintf("service.ops_alert_evaluator", "[OpsAlertEvaluator] leader lock held by another instance; skipping (key=%q)", key)
} }
func (s *OpsAlertEvaluatorService) recordHeartbeatSuccess(runAt time.Time, duration time.Duration, result string) { func (s *OpsAlertEvaluatorService) recordHeartbeatSuccess(runAt time.Time, duration time.Duration, result string) {
......
...@@ -4,12 +4,12 @@ import ( ...@@ -4,12 +4,12 @@ import (
"context" "context"
"database/sql" "database/sql"
"fmt" "fmt"
"log"
"strings" "strings"
"sync" "sync"
"time" "time"
"github.com/Wei-Shaw/sub2api/internal/config" "github.com/Wei-Shaw/sub2api/internal/config"
"github.com/Wei-Shaw/sub2api/internal/pkg/logger"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/redis/go-redis/v9" "github.com/redis/go-redis/v9"
"github.com/robfig/cron/v3" "github.com/robfig/cron/v3"
...@@ -75,11 +75,11 @@ func (s *OpsCleanupService) Start() { ...@@ -75,11 +75,11 @@ func (s *OpsCleanupService) Start() {
return return
} }
if s.cfg != nil && !s.cfg.Ops.Cleanup.Enabled { if s.cfg != nil && !s.cfg.Ops.Cleanup.Enabled {
log.Printf("[OpsCleanup] not started (disabled)") logger.LegacyPrintf("service.ops_cleanup", "[OpsCleanup] not started (disabled)")
return return
} }
if s.opsRepo == nil || s.db == nil { if s.opsRepo == nil || s.db == nil {
log.Printf("[OpsCleanup] not started (missing deps)") logger.LegacyPrintf("service.ops_cleanup", "[OpsCleanup] not started (missing deps)")
return return
} }
...@@ -99,12 +99,12 @@ func (s *OpsCleanupService) Start() { ...@@ -99,12 +99,12 @@ func (s *OpsCleanupService) Start() {
c := cron.New(cron.WithParser(opsCleanupCronParser), cron.WithLocation(loc)) c := cron.New(cron.WithParser(opsCleanupCronParser), cron.WithLocation(loc))
_, err := c.AddFunc(schedule, func() { s.runScheduled() }) _, err := c.AddFunc(schedule, func() { s.runScheduled() })
if err != nil { if err != nil {
log.Printf("[OpsCleanup] not started (invalid schedule=%q): %v", schedule, err) logger.LegacyPrintf("service.ops_cleanup", "[OpsCleanup] not started (invalid schedule=%q): %v", schedule, err)
return return
} }
s.cron = c s.cron = c
s.cron.Start() s.cron.Start()
log.Printf("[OpsCleanup] started (schedule=%q tz=%s)", schedule, loc.String()) logger.LegacyPrintf("service.ops_cleanup", "[OpsCleanup] started (schedule=%q tz=%s)", schedule, loc.String())
}) })
} }
...@@ -118,7 +118,7 @@ func (s *OpsCleanupService) Stop() { ...@@ -118,7 +118,7 @@ func (s *OpsCleanupService) Stop() {
select { select {
case <-ctx.Done(): case <-ctx.Done():
case <-time.After(3 * time.Second): case <-time.After(3 * time.Second):
log.Printf("[OpsCleanup] cron stop timed out") logger.LegacyPrintf("service.ops_cleanup", "[OpsCleanup] cron stop timed out")
} }
} }
}) })
...@@ -146,17 +146,19 @@ func (s *OpsCleanupService) runScheduled() { ...@@ -146,17 +146,19 @@ func (s *OpsCleanupService) runScheduled() {
counts, err := s.runCleanupOnce(ctx) counts, err := s.runCleanupOnce(ctx)
if err != nil { if err != nil {
s.recordHeartbeatError(runAt, time.Since(startedAt), err) s.recordHeartbeatError(runAt, time.Since(startedAt), err)
log.Printf("[OpsCleanup] cleanup failed: %v", err) logger.LegacyPrintf("service.ops_cleanup", "[OpsCleanup] cleanup failed: %v", err)
return return
} }
s.recordHeartbeatSuccess(runAt, time.Since(startedAt), counts) s.recordHeartbeatSuccess(runAt, time.Since(startedAt), counts)
log.Printf("[OpsCleanup] cleanup complete: %s", counts) logger.LegacyPrintf("service.ops_cleanup", "[OpsCleanup] cleanup complete: %s", counts)
} }
type opsCleanupDeletedCounts struct { type opsCleanupDeletedCounts struct {
errorLogs int64 errorLogs int64
retryAttempts int64 retryAttempts int64
alertEvents int64 alertEvents int64
systemLogs int64
logAudits int64
systemMetrics int64 systemMetrics int64
hourlyPreagg int64 hourlyPreagg int64
dailyPreagg int64 dailyPreagg int64
...@@ -164,10 +166,12 @@ type opsCleanupDeletedCounts struct { ...@@ -164,10 +166,12 @@ type opsCleanupDeletedCounts struct {
func (c opsCleanupDeletedCounts) String() string { func (c opsCleanupDeletedCounts) String() string {
return fmt.Sprintf( return fmt.Sprintf(
"error_logs=%d retry_attempts=%d alert_events=%d system_metrics=%d hourly_preagg=%d daily_preagg=%d", "error_logs=%d retry_attempts=%d alert_events=%d system_logs=%d log_audits=%d system_metrics=%d hourly_preagg=%d daily_preagg=%d",
c.errorLogs, c.errorLogs,
c.retryAttempts, c.retryAttempts,
c.alertEvents, c.alertEvents,
c.systemLogs,
c.logAudits,
c.systemMetrics, c.systemMetrics,
c.hourlyPreagg, c.hourlyPreagg,
c.dailyPreagg, c.dailyPreagg,
...@@ -204,6 +208,18 @@ func (s *OpsCleanupService) runCleanupOnce(ctx context.Context) (opsCleanupDelet ...@@ -204,6 +208,18 @@ func (s *OpsCleanupService) runCleanupOnce(ctx context.Context) (opsCleanupDelet
return out, err return out, err
} }
out.alertEvents = n out.alertEvents = n
n, err = deleteOldRowsByID(ctx, s.db, "ops_system_logs", "created_at", cutoff, batchSize, false)
if err != nil {
return out, err
}
out.systemLogs = n
n, err = deleteOldRowsByID(ctx, s.db, "ops_system_log_cleanup_audits", "created_at", cutoff, batchSize, false)
if err != nil {
return out, err
}
out.logAudits = n
} }
// Minute-level metrics snapshots. // Minute-level metrics snapshots.
...@@ -315,11 +331,11 @@ func (s *OpsCleanupService) tryAcquireLeaderLock(ctx context.Context) (func(), b ...@@ -315,11 +331,11 @@ func (s *OpsCleanupService) tryAcquireLeaderLock(ctx context.Context) (func(), b
} }
// Redis error: fall back to DB advisory lock. // Redis error: fall back to DB advisory lock.
s.warnNoRedisOnce.Do(func() { s.warnNoRedisOnce.Do(func() {
log.Printf("[OpsCleanup] leader lock SetNX failed; falling back to DB advisory lock: %v", err) logger.LegacyPrintf("service.ops_cleanup", "[OpsCleanup] leader lock SetNX failed; falling back to DB advisory lock: %v", err)
}) })
} else { } else {
s.warnNoRedisOnce.Do(func() { s.warnNoRedisOnce.Do(func() {
log.Printf("[OpsCleanup] redis not configured; using DB advisory lock") logger.LegacyPrintf("service.ops_cleanup", "[OpsCleanup] redis not configured; using DB advisory lock")
}) })
} }
......
package service
import (
"context"
"encoding/json"
"errors"
"fmt"
"strings"
"time"
"github.com/Wei-Shaw/sub2api/internal/config"
"github.com/Wei-Shaw/sub2api/internal/pkg/logger"
"go.uber.org/zap"
)
func defaultOpsRuntimeLogConfig(cfg *config.Config) *OpsRuntimeLogConfig {
out := &OpsRuntimeLogConfig{
Level: "info",
EnableSampling: false,
SamplingInitial: 100,
SamplingNext: 100,
Caller: true,
StacktraceLevel: "error",
RetentionDays: 30,
}
if cfg == nil {
return out
}
out.Level = strings.ToLower(strings.TrimSpace(cfg.Log.Level))
out.EnableSampling = cfg.Log.Sampling.Enabled
out.SamplingInitial = cfg.Log.Sampling.Initial
out.SamplingNext = cfg.Log.Sampling.Thereafter
out.Caller = cfg.Log.Caller
out.StacktraceLevel = strings.ToLower(strings.TrimSpace(cfg.Log.StacktraceLevel))
if cfg.Ops.Cleanup.ErrorLogRetentionDays > 0 {
out.RetentionDays = cfg.Ops.Cleanup.ErrorLogRetentionDays
}
return out
}
func normalizeOpsRuntimeLogConfig(cfg *OpsRuntimeLogConfig, defaults *OpsRuntimeLogConfig) {
if cfg == nil || defaults == nil {
return
}
cfg.Level = strings.ToLower(strings.TrimSpace(cfg.Level))
if cfg.Level == "" {
cfg.Level = defaults.Level
}
cfg.StacktraceLevel = strings.ToLower(strings.TrimSpace(cfg.StacktraceLevel))
if cfg.StacktraceLevel == "" {
cfg.StacktraceLevel = defaults.StacktraceLevel
}
if cfg.SamplingInitial <= 0 {
cfg.SamplingInitial = defaults.SamplingInitial
}
if cfg.SamplingNext <= 0 {
cfg.SamplingNext = defaults.SamplingNext
}
if cfg.RetentionDays <= 0 {
cfg.RetentionDays = defaults.RetentionDays
}
}
func validateOpsRuntimeLogConfig(cfg *OpsRuntimeLogConfig) error {
if cfg == nil {
return errors.New("invalid config")
}
switch strings.ToLower(strings.TrimSpace(cfg.Level)) {
case "debug", "info", "warn", "error":
default:
return errors.New("level must be one of: debug/info/warn/error")
}
switch strings.ToLower(strings.TrimSpace(cfg.StacktraceLevel)) {
case "none", "error", "fatal":
default:
return errors.New("stacktrace_level must be one of: none/error/fatal")
}
if cfg.SamplingInitial <= 0 {
return errors.New("sampling_initial must be positive")
}
if cfg.SamplingNext <= 0 {
return errors.New("sampling_thereafter must be positive")
}
if cfg.RetentionDays < 1 || cfg.RetentionDays > 3650 {
return errors.New("retention_days must be between 1 and 3650")
}
return nil
}
func (s *OpsService) GetRuntimeLogConfig(ctx context.Context) (*OpsRuntimeLogConfig, error) {
if s == nil || s.settingRepo == nil {
var cfg *config.Config
if s != nil {
cfg = s.cfg
}
defaultCfg := defaultOpsRuntimeLogConfig(cfg)
return defaultCfg, nil
}
defaultCfg := defaultOpsRuntimeLogConfig(s.cfg)
if ctx == nil {
ctx = context.Background()
}
raw, err := s.settingRepo.GetValue(ctx, SettingKeyOpsRuntimeLogConfig)
if err != nil {
if errors.Is(err, ErrSettingNotFound) {
b, _ := json.Marshal(defaultCfg)
_ = s.settingRepo.Set(ctx, SettingKeyOpsRuntimeLogConfig, string(b))
return defaultCfg, nil
}
return nil, err
}
cfg := &OpsRuntimeLogConfig{}
if err := json.Unmarshal([]byte(raw), cfg); err != nil {
return defaultCfg, nil
}
normalizeOpsRuntimeLogConfig(cfg, defaultCfg)
return cfg, nil
}
func (s *OpsService) UpdateRuntimeLogConfig(ctx context.Context, req *OpsRuntimeLogConfig, operatorID int64) (*OpsRuntimeLogConfig, error) {
if s == nil || s.settingRepo == nil {
return nil, errors.New("setting repository not initialized")
}
if req == nil {
return nil, errors.New("invalid config")
}
if ctx == nil {
ctx = context.Background()
}
if operatorID <= 0 {
return nil, errors.New("invalid operator id")
}
oldCfg, err := s.GetRuntimeLogConfig(ctx)
if err != nil {
return nil, err
}
next := *req
normalizeOpsRuntimeLogConfig(&next, defaultOpsRuntimeLogConfig(s.cfg))
if err := validateOpsRuntimeLogConfig(&next); err != nil {
s.auditRuntimeLogConfigFailure(operatorID, oldCfg, &next, "validation_failed: "+err.Error())
return nil, err
}
if err := applyOpsRuntimeLogConfig(&next); err != nil {
s.auditRuntimeLogConfigFailure(operatorID, oldCfg, &next, "apply_failed: "+err.Error())
return nil, err
}
next.Source = "runtime_setting"
next.UpdatedAt = time.Now().UTC().Format(time.RFC3339Nano)
next.UpdatedByUserID = operatorID
encoded, err := json.Marshal(&next)
if err != nil {
return nil, err
}
if err := s.settingRepo.Set(ctx, SettingKeyOpsRuntimeLogConfig, string(encoded)); err != nil {
// 存储失败时回滚到旧配置,避免内存状态与持久化状态不一致。
_ = applyOpsRuntimeLogConfig(oldCfg)
s.auditRuntimeLogConfigFailure(operatorID, oldCfg, &next, "persist_failed: "+err.Error())
return nil, err
}
s.auditRuntimeLogConfigChange(operatorID, oldCfg, &next, "updated")
return &next, nil
}
func (s *OpsService) ResetRuntimeLogConfig(ctx context.Context, operatorID int64) (*OpsRuntimeLogConfig, error) {
if s == nil || s.settingRepo == nil {
return nil, errors.New("setting repository not initialized")
}
if ctx == nil {
ctx = context.Background()
}
if operatorID <= 0 {
return nil, errors.New("invalid operator id")
}
oldCfg, err := s.GetRuntimeLogConfig(ctx)
if err != nil {
return nil, err
}
resetCfg := defaultOpsRuntimeLogConfig(s.cfg)
normalizeOpsRuntimeLogConfig(resetCfg, defaultOpsRuntimeLogConfig(s.cfg))
if err := validateOpsRuntimeLogConfig(resetCfg); err != nil {
s.auditRuntimeLogConfigFailure(operatorID, oldCfg, resetCfg, "reset_validation_failed: "+err.Error())
return nil, err
}
if err := applyOpsRuntimeLogConfig(resetCfg); err != nil {
s.auditRuntimeLogConfigFailure(operatorID, oldCfg, resetCfg, "reset_apply_failed: "+err.Error())
return nil, err
}
// 清理 runtime 覆盖配置,回退到 env/yaml baseline。
if err := s.settingRepo.Delete(ctx, SettingKeyOpsRuntimeLogConfig); err != nil && !errors.Is(err, ErrSettingNotFound) {
_ = applyOpsRuntimeLogConfig(oldCfg)
s.auditRuntimeLogConfigFailure(operatorID, oldCfg, resetCfg, "reset_persist_failed: "+err.Error())
return nil, err
}
now := time.Now().UTC().Format(time.RFC3339Nano)
resetCfg.Source = "baseline"
resetCfg.UpdatedAt = now
resetCfg.UpdatedByUserID = operatorID
s.auditRuntimeLogConfigChange(operatorID, oldCfg, resetCfg, "reset")
return resetCfg, nil
}
func applyOpsRuntimeLogConfig(cfg *OpsRuntimeLogConfig) error {
if cfg == nil {
return fmt.Errorf("nil runtime log config")
}
if err := logger.Reconfigure(func(opts *logger.InitOptions) error {
opts.Level = strings.ToLower(strings.TrimSpace(cfg.Level))
opts.Caller = cfg.Caller
opts.StacktraceLevel = strings.ToLower(strings.TrimSpace(cfg.StacktraceLevel))
opts.Sampling.Enabled = cfg.EnableSampling
opts.Sampling.Initial = cfg.SamplingInitial
opts.Sampling.Thereafter = cfg.SamplingNext
return nil
}); err != nil {
return err
}
return nil
}
func (s *OpsService) applyRuntimeLogConfigOnStartup(ctx context.Context) {
if s == nil {
return
}
cfg, err := s.GetRuntimeLogConfig(ctx)
if err != nil {
return
}
_ = applyOpsRuntimeLogConfig(cfg)
}
func (s *OpsService) auditRuntimeLogConfigChange(operatorID int64, oldCfg *OpsRuntimeLogConfig, newCfg *OpsRuntimeLogConfig, action string) {
oldRaw, _ := json.Marshal(oldCfg)
newRaw, _ := json.Marshal(newCfg)
logger.With(
zap.String("component", "audit.log_config_change"),
zap.String("action", strings.TrimSpace(action)),
zap.Int64("operator_id", operatorID),
zap.String("old", string(oldRaw)),
zap.String("new", string(newRaw)),
).Info("runtime log config changed")
}
func (s *OpsService) auditRuntimeLogConfigFailure(operatorID int64, oldCfg *OpsRuntimeLogConfig, newCfg *OpsRuntimeLogConfig, reason string) {
oldRaw, _ := json.Marshal(oldCfg)
newRaw, _ := json.Marshal(newCfg)
logger.With(
zap.String("component", "audit.log_config_change"),
zap.String("action", "failed"),
zap.Int64("operator_id", operatorID),
zap.String("reason", strings.TrimSpace(reason)),
zap.String("old", string(oldRaw)),
zap.String("new", string(newRaw)),
).Warn("runtime log config change failed")
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment