ops_error_logger.go 70 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473
  1. package handler
  2. import (
  3. "bufio"
  4. "bytes"
  5. "context"
  6. "encoding/json"
  7. "errors"
  8. "log"
  9. "net"
  10. "net/http"
  11. "runtime"
  12. "runtime/debug"
  13. "strconv"
  14. "strings"
  15. "sync"
  16. "sync/atomic"
  17. "time"
  18. "unicode/utf8"
  19. "github.com/Wei-Shaw/sub2api/internal/pkg/ctxkey"
  20. "github.com/Wei-Shaw/sub2api/internal/pkg/ip"
  21. middleware2 "github.com/Wei-Shaw/sub2api/internal/server/middleware"
  22. "github.com/Wei-Shaw/sub2api/internal/service"
  23. "github.com/gin-gonic/gin"
  24. )
  25. const (
  26. opsModelKey = "ops_model"
  27. opsStreamKey = "ops_stream"
  28. opsAccountIDKey = "ops_account_id"
  29. opsRoutingCapacityLimitedKey = "ops_routing_capacity_limited"
  30. opsDedicatedErrorRecordedKey = "ops_dedicated_error_recorded"
  31. opsUpstreamModelKey = service.OpsUpstreamModelKey
  32. opsRequestTypeKey = "ops_request_type"
  33. // 错误过滤匹配常量 — shouldSkipOpsErrorLog 和错误分类共用
  34. opsErrContextCanceled = "context canceled"
  35. opsErrNoAvailableAccounts = "no available accounts"
  36. opsErrInvalidAPIKey = "invalid_api_key"
  37. opsErrAPIKeyRequired = "api_key_required"
  38. opsErrInsufficientBalance = "insufficient balance"
  39. opsErrInsufficientAccountBalance = "insufficient account balance"
  40. opsErrInsufficientQuota = "insufficient_quota"
  41. // 上游错误码常量 — 错误分类 (normalizeOpsErrorType / classifyOpsPhase / classifyOpsIsBusinessLimited)
  42. opsCodeInsufficientBalance = "INSUFFICIENT_BALANCE"
  43. opsCodeUsageLimitExceeded = "USAGE_LIMIT_EXCEEDED"
  44. opsCodeSubscriptionNotFound = "SUBSCRIPTION_NOT_FOUND"
  45. opsCodeSubscriptionInvalid = "SUBSCRIPTION_INVALID"
  46. opsCodeUserInactive = "USER_INACTIVE"
  47. opsCodeInvalidAPIKey = "INVALID_API_KEY"
  48. opsCodeAPIKeyRequired = "API_KEY_REQUIRED"
  49. opsCodeAPIKeyExpired = "API_KEY_EXPIRED"
  50. opsCodeAPIKeyDisabled = "API_KEY_DISABLED"
  51. opsCodeUserNotFound = "USER_NOT_FOUND"
  52. opsCodeAPIKeyQuotaExhausted = "API_KEY_QUOTA_EXHAUSTED"
  53. opsCodeAPIKeyQueryDeprecated = "api_key_in_query_deprecated"
  54. opsCodeGroupDeleted = "GROUP_DELETED"
  55. opsCodeGroupDisabled = "GROUP_DISABLED"
  56. )
  57. const (
  58. opsErrorLogTimeout = 5 * time.Second
  59. opsErrorLogDrainTimeout = 10 * time.Second
  60. opsErrorLogBatchWindow = 200 * time.Millisecond
  61. opsErrorLogMinWorkerCount = 4
  62. opsErrorLogMaxWorkerCount = 32
  63. opsErrorLogQueueSizePerWorker = 128
  64. opsErrorLogMinQueueSize = 256
  65. opsErrorLogMaxQueueSize = 8192
  66. opsErrorLogBatchSize = 32
  67. opsErrorLogMaxQueueBytes = 32 * 1024 * 1024
  68. opsErrorLogMaxUserAgentBytes = 512
  69. )
  70. // keyPrefix 返回脱敏前缀(前 n 个字符);不足 n 则原样返回。
  71. func keyPrefix(key string, n int) string {
  72. if len(key) <= n {
  73. return key
  74. }
  75. return key[:n]
  76. }
  77. type opsErrorLogJob struct {
  78. ops *service.OpsService
  79. entry *service.OpsInsertErrorLogInput
  80. queuedBytes int64
  81. }
  82. var (
  83. opsErrorLogOnce sync.Once
  84. opsErrorLogQueue chan opsErrorLogJob
  85. opsErrorLogStopOnce sync.Once
  86. opsErrorLogWorkersWg sync.WaitGroup
  87. opsErrorLogMu sync.RWMutex
  88. opsErrorLogStopping bool
  89. opsErrorLogQueueLen atomic.Int64
  90. opsErrorLogQueueBytes atomic.Int64
  91. opsErrorLogEnqueued atomic.Int64
  92. opsErrorLogDropped atomic.Int64
  93. opsErrorLogProcessed atomic.Int64
  94. opsErrorLogSanitized atomic.Int64
  95. opsErrorLogLastDropLogAt atomic.Int64
  96. opsErrorLogShutdownCh = make(chan struct{})
  97. opsErrorLogShutdownOnce sync.Once
  98. opsErrorLogDrained atomic.Bool
  99. )
  100. func startOpsErrorLogWorkers() {
  101. opsErrorLogMu.Lock()
  102. defer opsErrorLogMu.Unlock()
  103. if opsErrorLogStopping {
  104. return
  105. }
  106. workerCount, queueSize := opsErrorLogConfig()
  107. opsErrorLogQueue = make(chan opsErrorLogJob, queueSize)
  108. opsErrorLogQueueLen.Store(0)
  109. opsErrorLogQueueBytes.Store(0)
  110. opsErrorLogWorkersWg.Add(workerCount)
  111. for i := 0; i < workerCount; i++ {
  112. go func() {
  113. defer opsErrorLogWorkersWg.Done()
  114. for {
  115. job, ok := <-opsErrorLogQueue
  116. if !ok {
  117. return
  118. }
  119. opsErrorLogQueueLen.Add(-1)
  120. opsErrorLogQueueBytes.Add(-job.queuedBytes)
  121. batch := make([]opsErrorLogJob, 0, opsErrorLogBatchSize)
  122. batch = append(batch, job)
  123. timer := time.NewTimer(opsErrorLogBatchWindow)
  124. batchLoop:
  125. for len(batch) < opsErrorLogBatchSize {
  126. select {
  127. case nextJob, ok := <-opsErrorLogQueue:
  128. if !ok {
  129. if !timer.Stop() {
  130. select {
  131. case <-timer.C:
  132. default:
  133. }
  134. }
  135. flushOpsErrorLogBatch(batch)
  136. return
  137. }
  138. opsErrorLogQueueLen.Add(-1)
  139. opsErrorLogQueueBytes.Add(-nextJob.queuedBytes)
  140. batch = append(batch, nextJob)
  141. case <-timer.C:
  142. break batchLoop
  143. }
  144. }
  145. if !timer.Stop() {
  146. select {
  147. case <-timer.C:
  148. default:
  149. }
  150. }
  151. flushOpsErrorLogBatch(batch)
  152. }
  153. }()
  154. }
  155. }
  156. func flushOpsErrorLogBatch(batch []opsErrorLogJob) {
  157. if len(batch) == 0 {
  158. return
  159. }
  160. defer func() {
  161. if r := recover(); r != nil {
  162. log.Printf("[OpsErrorLogger] worker panic: %v\n%s", r, debug.Stack())
  163. }
  164. }()
  165. grouped := make(map[*service.OpsService][]*service.OpsInsertErrorLogInput, len(batch))
  166. var processed int64
  167. for _, job := range batch {
  168. if job.ops == nil || job.entry == nil {
  169. continue
  170. }
  171. grouped[job.ops] = append(grouped[job.ops], job.entry)
  172. processed++
  173. }
  174. if processed == 0 {
  175. return
  176. }
  177. for opsSvc, entries := range grouped {
  178. if opsSvc == nil || len(entries) == 0 {
  179. continue
  180. }
  181. ctx, cancel := context.WithTimeout(context.Background(), opsErrorLogTimeout)
  182. _ = opsSvc.RecordErrorBatch(ctx, entries)
  183. cancel()
  184. }
  185. opsErrorLogProcessed.Add(processed)
  186. }
  187. func enqueueOpsErrorLog(ops *service.OpsService, entry *service.OpsInsertErrorLogInput) {
  188. if ops == nil || entry == nil {
  189. return
  190. }
  191. entry.UserAgent = normalizeOpsPersistentUserAgent(entry.UserAgent)
  192. if entry.ErrorBody != "" {
  193. originalBody := entry.ErrorBody
  194. body, truncated := service.SanitizeOpsErrorBodyForQueue(originalBody)
  195. entry.ErrorBody = body
  196. if truncated || body != originalBody {
  197. opsErrorLogSanitized.Add(1)
  198. }
  199. }
  200. if err := service.SanitizeOpsUpstreamErrorsForQueue(entry); err != nil {
  201. opsErrorLogDropped.Add(1)
  202. maybeLogOpsErrorLogDrop()
  203. return
  204. }
  205. select {
  206. case <-opsErrorLogShutdownCh:
  207. return
  208. default:
  209. }
  210. opsErrorLogMu.RLock()
  211. stopping := opsErrorLogStopping
  212. opsErrorLogMu.RUnlock()
  213. if stopping {
  214. return
  215. }
  216. opsErrorLogOnce.Do(startOpsErrorLogWorkers)
  217. opsErrorLogMu.RLock()
  218. defer opsErrorLogMu.RUnlock()
  219. if opsErrorLogStopping || opsErrorLogQueue == nil {
  220. return
  221. }
  222. queuedBytes := estimateOpsErrorLogJobBytes(entry)
  223. if !reserveOpsErrorLogQueueBytes(queuedBytes) {
  224. opsErrorLogDropped.Add(1)
  225. maybeLogOpsErrorLogDrop()
  226. return
  227. }
  228. select {
  229. case opsErrorLogQueue <- opsErrorLogJob{ops: ops, entry: entry, queuedBytes: queuedBytes}:
  230. opsErrorLogEnqueued.Add(1)
  231. default:
  232. opsErrorLogQueueLen.Add(-1)
  233. opsErrorLogQueueBytes.Add(-queuedBytes)
  234. // Queue is full; drop to avoid blocking request handling.
  235. opsErrorLogDropped.Add(1)
  236. maybeLogOpsErrorLogDrop()
  237. }
  238. }
  239. func normalizeOpsPersistentUserAgent(value string) string {
  240. return truncateString(strings.TrimSpace(strings.ToValidUTF8(value, "")), opsErrorLogMaxUserAgentBytes)
  241. }
  242. func StopOpsErrorLogWorkers() bool {
  243. opsErrorLogStopOnce.Do(func() {
  244. opsErrorLogShutdownOnce.Do(func() {
  245. close(opsErrorLogShutdownCh)
  246. })
  247. opsErrorLogDrained.Store(stopOpsErrorLogWorkers())
  248. })
  249. return opsErrorLogDrained.Load()
  250. }
  251. func stopOpsErrorLogWorkers() bool {
  252. opsErrorLogMu.Lock()
  253. opsErrorLogStopping = true
  254. ch := opsErrorLogQueue
  255. if ch != nil {
  256. close(ch)
  257. }
  258. opsErrorLogQueue = nil
  259. opsErrorLogMu.Unlock()
  260. if ch == nil {
  261. opsErrorLogQueueLen.Store(0)
  262. opsErrorLogQueueBytes.Store(0)
  263. return true
  264. }
  265. done := make(chan struct{})
  266. go func() {
  267. opsErrorLogWorkersWg.Wait()
  268. close(done)
  269. }()
  270. select {
  271. case <-done:
  272. opsErrorLogQueueLen.Store(0)
  273. opsErrorLogQueueBytes.Store(0)
  274. return true
  275. case <-time.After(opsErrorLogDrainTimeout):
  276. return false
  277. }
  278. }
  279. func OpsErrorLogQueueLength() int64 {
  280. return opsErrorLogQueueLen.Load()
  281. }
  282. func OpsErrorLogQueueBytes() int64 {
  283. return opsErrorLogQueueBytes.Load()
  284. }
  285. func OpsErrorLogQueueBytesCapacity() int64 {
  286. return opsErrorLogMaxQueueBytes
  287. }
  288. func OpsErrorLogQueueCapacity() int {
  289. opsErrorLogMu.RLock()
  290. ch := opsErrorLogQueue
  291. opsErrorLogMu.RUnlock()
  292. if ch == nil {
  293. return 0
  294. }
  295. return cap(ch)
  296. }
  297. func OpsErrorLogDroppedTotal() int64 {
  298. return opsErrorLogDropped.Load()
  299. }
  300. func OpsErrorLogEnqueuedTotal() int64 {
  301. return opsErrorLogEnqueued.Load()
  302. }
  303. func OpsErrorLogProcessedTotal() int64 {
  304. return opsErrorLogProcessed.Load()
  305. }
  306. func OpsErrorLogSanitizedTotal() int64 {
  307. return opsErrorLogSanitized.Load()
  308. }
  309. func maybeLogOpsErrorLogDrop() {
  310. now := time.Now().Unix()
  311. for {
  312. last := opsErrorLogLastDropLogAt.Load()
  313. if last != 0 && now-last < 60 {
  314. return
  315. }
  316. if opsErrorLogLastDropLogAt.CompareAndSwap(last, now) {
  317. break
  318. }
  319. }
  320. queued := opsErrorLogQueueLen.Load()
  321. queuedBytes := opsErrorLogQueueBytes.Load()
  322. queueCap := OpsErrorLogQueueCapacity()
  323. log.Printf(
  324. "[OpsErrorLogger] queue is full; dropping logs (queued=%d cap=%d queued_bytes=%d bytes_cap=%d enqueued_total=%d dropped_total=%d processed_total=%d sanitized_total=%d)",
  325. queued,
  326. queueCap,
  327. queuedBytes,
  328. opsErrorLogMaxQueueBytes,
  329. opsErrorLogEnqueued.Load(),
  330. opsErrorLogDropped.Load(),
  331. opsErrorLogProcessed.Load(),
  332. opsErrorLogSanitized.Load(),
  333. )
  334. }
  335. func reserveOpsErrorLogQueueBytes(size int64) bool {
  336. if size < 1 {
  337. size = 1
  338. }
  339. for {
  340. current := opsErrorLogQueueBytes.Load()
  341. if current > opsErrorLogMaxQueueBytes-size {
  342. return false
  343. }
  344. if opsErrorLogQueueBytes.CompareAndSwap(current, current+size) {
  345. opsErrorLogQueueLen.Add(1)
  346. return true
  347. }
  348. }
  349. }
  350. func estimateOpsErrorLogJobBytes(entry *service.OpsInsertErrorLogInput) int64 {
  351. if entry == nil {
  352. return 1
  353. }
  354. const fixedOverhead = 512
  355. size := fixedOverhead + len(entry.RequestID) + len(entry.ClientRequestID) +
  356. len(entry.Platform) + len(entry.Model) + len(entry.RequestPath) +
  357. len(entry.InboundEndpoint) + len(entry.UpstreamEndpoint) +
  358. len(entry.RequestedModel) + len(entry.UpstreamModel) + len(entry.UserAgent) +
  359. len(entry.ErrorPhase) + len(entry.ErrorType) + len(entry.Severity) +
  360. len(entry.ErrorMessage) + len(entry.ErrorBody) + len(entry.ErrorSource) +
  361. len(entry.ErrorOwner) + len(entry.APIKeyPrefix)
  362. if entry.UpstreamErrorMessage != nil {
  363. size += len(*entry.UpstreamErrorMessage)
  364. }
  365. if entry.UpstreamErrorDetail != nil {
  366. size += len(*entry.UpstreamErrorDetail)
  367. }
  368. if entry.UpstreamErrorsJSON != nil {
  369. size += len(*entry.UpstreamErrorsJSON)
  370. }
  371. return int64(size)
  372. }
  373. func opsErrorLogConfig() (workerCount int, queueSize int) {
  374. workerCount = runtime.GOMAXPROCS(0) * 2
  375. if workerCount < opsErrorLogMinWorkerCount {
  376. workerCount = opsErrorLogMinWorkerCount
  377. }
  378. if workerCount > opsErrorLogMaxWorkerCount {
  379. workerCount = opsErrorLogMaxWorkerCount
  380. }
  381. queueSize = workerCount * opsErrorLogQueueSizePerWorker
  382. if queueSize < opsErrorLogMinQueueSize {
  383. queueSize = opsErrorLogMinQueueSize
  384. }
  385. if queueSize > opsErrorLogMaxQueueSize {
  386. queueSize = opsErrorLogMaxQueueSize
  387. }
  388. return workerCount, queueSize
  389. }
  390. func setOpsRequestContext(c *gin.Context, model string, stream bool) {
  391. if c == nil {
  392. return
  393. }
  394. model = strings.TrimSpace(model)
  395. c.Set(opsModelKey, model)
  396. c.Set(opsStreamKey, stream)
  397. if c.Request != nil && model != "" {
  398. ctx := context.WithValue(c.Request.Context(), ctxkey.Model, model)
  399. c.Request = c.Request.WithContext(ctx)
  400. }
  401. }
  402. // setOpsEndpointContext stores upstream model and request type for ops error logging.
  403. // Called by handlers after model mapping and request type determination.
  404. func setOpsEndpointContext(c *gin.Context, upstreamModel string, requestType int16) {
  405. if c == nil {
  406. return
  407. }
  408. if upstreamModel = strings.TrimSpace(upstreamModel); upstreamModel != "" {
  409. c.Set(opsUpstreamModelKey, upstreamModel)
  410. }
  411. c.Set(opsRequestTypeKey, requestType)
  412. }
  413. func setOpsSelectedAccount(c *gin.Context, accountID int64, platform ...string) {
  414. if c == nil || accountID <= 0 {
  415. return
  416. }
  417. service.ClearOpsUpstreamModel(c)
  418. c.Set(opsAccountIDKey, accountID)
  419. if c.Request != nil {
  420. ctx := context.WithValue(c.Request.Context(), ctxkey.AccountID, accountID)
  421. if len(platform) > 0 {
  422. p := strings.TrimSpace(platform[0])
  423. if p != "" {
  424. ctx = context.WithValue(ctx, ctxkey.Platform, p)
  425. }
  426. }
  427. c.Request = c.Request.WithContext(ctx)
  428. }
  429. }
  430. func markOpsRoutingCapacityLimited(c *gin.Context) {
  431. if c == nil {
  432. return
  433. }
  434. c.Set(opsRoutingCapacityLimitedKey, true)
  435. }
  436. func markOpsRoutingCapacityLimitedIfNoAvailable(c *gin.Context, err error) {
  437. if !isOpsNoAvailableAccountError(err) {
  438. return
  439. }
  440. markOpsRoutingCapacityLimited(c)
  441. }
  442. func isOpsRoutingCapacityLimited(c *gin.Context) bool {
  443. if c == nil {
  444. return false
  445. }
  446. v, ok := c.Get(opsRoutingCapacityLimitedKey)
  447. if !ok {
  448. return false
  449. }
  450. marked, _ := v.(bool)
  451. return marked
  452. }
  453. func isOpsNoAvailableAccountError(err error) bool {
  454. if err == nil {
  455. return false
  456. }
  457. if errors.Is(err, service.ErrNoAvailableAccounts) || errors.Is(err, service.ErrNoAvailableCompactAccounts) {
  458. return true
  459. }
  460. return isOpsNoAvailableAccountMessage(err.Error())
  461. }
  462. type opsCaptureWriter struct {
  463. // Handles are never pooled. A generation binds each handle to exactly one
  464. // pooled state lease, so a stale handle cannot reach a later request.
  465. state *opsCaptureWriterState
  466. generation uint64
  467. pool opsCaptureWriterStatePool
  468. }
  469. type opsCaptureWriterState struct {
  470. mu sync.RWMutex
  471. inFlight sync.WaitGroup
  472. generation uint64
  473. responseWriter gin.ResponseWriter
  474. limit int
  475. buf bytes.Buffer
  476. probe []byte
  477. lineProbe []byte
  478. frameLineLen int
  479. frameTruncated bool
  480. lineTruncated bool
  481. skipLF bool
  482. sseCapturing bool
  483. terminalError parsedOpsError
  484. terminalFound bool
  485. ctx *gin.Context
  486. }
  487. const (
  488. opsCaptureWriterLimit = service.OpsErrorLogQueueBodyMaxBytes
  489. opsTerminalSSEFrameProbeLimit = 16 * 1024
  490. )
  491. const opsCaptureWriterPoolMaxRetainedCapacity = service.OpsErrorLogQueueBodyMaxBytes
  492. type opsCaptureWriterStatePool interface {
  493. Get() any
  494. Put(any)
  495. }
  496. var opsCaptureWriterPool opsCaptureWriterStatePool = &sync.Pool{
  497. New: func() any {
  498. return &opsCaptureWriterState{limit: opsCaptureWriterLimit}
  499. },
  500. }
  501. func acquireOpsCaptureWriter(rw gin.ResponseWriter) *opsCaptureWriter {
  502. return acquireOpsCaptureWriterFromPool(opsCaptureWriterPool, rw)
  503. }
  504. func acquireOpsCaptureWriterFromPool(pool opsCaptureWriterStatePool, rw gin.ResponseWriter) *opsCaptureWriter {
  505. var pooled any
  506. if pool != nil {
  507. pooled = pool.Get()
  508. }
  509. state, ok := pooled.(*opsCaptureWriterState)
  510. if !ok || state == nil {
  511. state = &opsCaptureWriterState{}
  512. }
  513. state.mu.Lock()
  514. state.generation++
  515. state.responseWriter = rw
  516. state.limit = opsCaptureWriterLimit
  517. state.buf.Reset()
  518. state.probe = state.probe[:0]
  519. state.lineProbe = state.lineProbe[:0]
  520. state.frameLineLen = 0
  521. state.frameTruncated = false
  522. state.lineTruncated = false
  523. state.skipLF = false
  524. state.sseCapturing = false
  525. state.terminalError = parsedOpsError{}
  526. state.terminalFound = false
  527. state.ctx = nil
  528. generation := state.generation
  529. state.mu.Unlock()
  530. return &opsCaptureWriter{state: state, generation: generation, pool: pool}
  531. }
  532. func releaseOpsCaptureWriter(w *opsCaptureWriter) {
  533. if w == nil || w.state == nil {
  534. return
  535. }
  536. state := w.state
  537. state.mu.Lock()
  538. if state.generation != w.generation {
  539. state.mu.Unlock()
  540. return
  541. }
  542. // Invalidate the lease before waiting. No new delegated calls can start for
  543. // this handle, while calls that already copied the writer keep it alive via
  544. // inFlight until their network operation returns.
  545. state.generation++
  546. state.responseWriter = nil
  547. state.ctx = nil
  548. state.mu.Unlock()
  549. state.inFlight.Wait()
  550. state.mu.Lock()
  551. state.limit = opsCaptureWriterLimit
  552. state.probe = state.probe[:0]
  553. state.lineProbe = state.lineProbe[:0]
  554. state.frameLineLen = 0
  555. state.frameTruncated = false
  556. state.lineTruncated = false
  557. state.skipLF = false
  558. state.sseCapturing = false
  559. state.terminalError = parsedOpsError{}
  560. state.terminalFound = false
  561. poolable := shouldPoolOpsCaptureWriterState(state)
  562. state.buf.Reset()
  563. state.mu.Unlock()
  564. if poolable && w.pool != nil {
  565. w.pool.Put(state)
  566. }
  567. }
  568. func shouldPoolOpsCaptureWriterState(state *opsCaptureWriterState) bool {
  569. return state != nil && state.buf.Cap() <= opsCaptureWriterPoolMaxRetainedCapacity &&
  570. cap(state.probe) <= opsTerminalSSEFrameProbeLimit && cap(state.lineProbe) <= 256
  571. }
  572. func (w *opsCaptureWriter) lockActive() (*opsCaptureWriterState, gin.ResponseWriter) {
  573. if w == nil || w.state == nil {
  574. return nil, nil
  575. }
  576. state := w.state
  577. state.mu.RLock()
  578. if state.generation != w.generation || state.responseWriter == nil {
  579. state.mu.RUnlock()
  580. return nil, nil
  581. }
  582. return state, state.responseWriter
  583. }
  584. func (w *opsCaptureWriter) lockActiveWrite() (*opsCaptureWriterState, gin.ResponseWriter) {
  585. if w == nil || w.state == nil {
  586. return nil, nil
  587. }
  588. state := w.state
  589. state.mu.Lock()
  590. if state.generation != w.generation || state.responseWriter == nil {
  591. state.mu.Unlock()
  592. return nil, nil
  593. }
  594. return state, state.responseWriter
  595. }
  596. func (w *opsCaptureWriter) beginDelegatedCall() (*opsCaptureWriterState, gin.ResponseWriter) {
  597. if w == nil || w.state == nil {
  598. return nil, nil
  599. }
  600. state := w.state
  601. state.mu.Lock()
  602. if state.generation != w.generation || state.responseWriter == nil {
  603. state.mu.Unlock()
  604. return nil, nil
  605. }
  606. rw := state.responseWriter
  607. state.inFlight.Add(1)
  608. return state, rw
  609. }
  610. func finishDelegatedCall(state *opsCaptureWriterState) {
  611. if state != nil {
  612. state.inFlight.Done()
  613. }
  614. }
  615. func (w *opsCaptureWriter) setContext(ctx *gin.Context) {
  616. state, _ := w.lockActiveWrite()
  617. if state == nil {
  618. return
  619. }
  620. state.ctx = ctx
  621. state.mu.Unlock()
  622. }
  623. func (w *opsCaptureWriter) capturedBytes() []byte {
  624. state, _ := w.lockActive()
  625. if state == nil {
  626. return nil
  627. }
  628. defer state.mu.RUnlock()
  629. return append([]byte(nil), state.buf.Bytes()...)
  630. }
  631. func (w *opsCaptureWriter) capturedTerminalError() (parsedOpsError, bool) {
  632. state, _ := w.lockActive()
  633. if state == nil {
  634. return parsedOpsError{}, false
  635. }
  636. defer state.mu.RUnlock()
  637. return state.terminalError, state.terminalFound
  638. }
  639. func (w *opsCaptureWriter) finalizeCapture() {
  640. state, _ := w.lockActiveWrite()
  641. if state == nil {
  642. return
  643. }
  644. defer state.mu.Unlock()
  645. state.finalizeResponseCapture()
  646. }
  647. func (w *opsCaptureWriter) Header() http.Header {
  648. state, rw := w.lockActive()
  649. if state == nil {
  650. return http.Header{}
  651. }
  652. defer state.mu.RUnlock()
  653. return rw.Header()
  654. }
  655. func (w *opsCaptureWriter) WriteHeader(code int) {
  656. state, rw := w.beginDelegatedCall()
  657. if state == nil {
  658. return
  659. }
  660. state.mu.Unlock()
  661. defer finishDelegatedCall(state)
  662. rw.WriteHeader(code)
  663. }
  664. func (w *opsCaptureWriter) WriteHeaderNow() {
  665. state, rw := w.beginDelegatedCall()
  666. if state == nil {
  667. return
  668. }
  669. state.mu.Unlock()
  670. defer finishDelegatedCall(state)
  671. rw.WriteHeaderNow()
  672. }
  673. func (w *opsCaptureWriter) Status() int {
  674. state, rw := w.lockActive()
  675. if state == nil {
  676. return 0
  677. }
  678. defer state.mu.RUnlock()
  679. return rw.Status()
  680. }
  681. func (w *opsCaptureWriter) Size() int {
  682. state, rw := w.lockActive()
  683. if state == nil {
  684. return -1
  685. }
  686. defer state.mu.RUnlock()
  687. return rw.Size()
  688. }
  689. func (w *opsCaptureWriter) Written() bool {
  690. state, rw := w.lockActive()
  691. if state == nil {
  692. return false
  693. }
  694. defer state.mu.RUnlock()
  695. return rw.Written()
  696. }
  697. func (w *opsCaptureWriter) Flush() {
  698. state, rw := w.beginDelegatedCall()
  699. if state == nil {
  700. return
  701. }
  702. state.mu.Unlock()
  703. defer finishDelegatedCall(state)
  704. rw.Flush()
  705. }
  706. func (w *opsCaptureWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
  707. state, rw := w.beginDelegatedCall()
  708. if state == nil {
  709. return nil, nil, errors.New("response writer released")
  710. }
  711. state.mu.Unlock()
  712. defer finishDelegatedCall(state)
  713. return rw.Hijack()
  714. }
  715. func (w *opsCaptureWriter) CloseNotify() <-chan bool {
  716. state, rw := w.lockActive()
  717. if state == nil {
  718. ch := make(chan bool)
  719. close(ch)
  720. return ch
  721. }
  722. defer state.mu.RUnlock()
  723. return rw.CloseNotify()
  724. }
  725. func (w *opsCaptureWriter) Pusher() http.Pusher {
  726. state, rw := w.lockActive()
  727. if state == nil {
  728. return nil
  729. }
  730. defer state.mu.RUnlock()
  731. return rw.Pusher()
  732. }
  733. func (w *opsCaptureWriter) Write(b []byte) (int, error) {
  734. state, rw := w.beginDelegatedCall()
  735. if state == nil {
  736. return 0, nil
  737. }
  738. if state.shouldCapture() {
  739. state.captureResponseChunk(b, rw.Status())
  740. }
  741. state.mu.Unlock()
  742. defer finishDelegatedCall(state)
  743. return rw.Write(b)
  744. }
  745. func (w *opsCaptureWriter) WriteString(s string) (int, error) {
  746. state, rw := w.beginDelegatedCall()
  747. if state == nil {
  748. return 0, nil
  749. }
  750. if state.shouldCapture() {
  751. state.captureResponseChunk([]byte(s), rw.Status())
  752. }
  753. state.mu.Unlock()
  754. defer finishDelegatedCall(state)
  755. return rw.WriteString(s)
  756. }
  757. var _ gin.ResponseWriter = (*opsCaptureWriter)(nil)
  758. func isOpsTerminalSSEFrame(frame []byte) bool {
  759. eventType, payload := parseOpsSSEFrameEnvelope(frame)
  760. if bytes.Equal(eventType, []byte("response.failed")) || bytes.Equal(eventType, []byte("error")) {
  761. return true
  762. }
  763. if len(payload) == 0 {
  764. return false
  765. }
  766. // Most successful frames cannot be terminal. Avoid JSON decoding on this
  767. // hot path while still validating any plausible terminal payload below.
  768. if !bytes.Contains(payload, []byte("response.failed")) && !bytes.Contains(payload, []byte(`"error"`)) {
  769. return false
  770. }
  771. var event struct {
  772. Type string `json:"type"`
  773. }
  774. return json.Unmarshal(payload, &event) == nil &&
  775. (event.Type == "response.failed" || event.Type == "error")
  776. }
  777. func parseOpsSSEFrameEnvelope(frame []byte) ([]byte, []byte) {
  778. var eventType []byte
  779. var data []byte
  780. dataOwned := false
  781. dataSeen := false
  782. for len(frame) > 0 {
  783. line := frame
  784. lf := bytes.IndexByte(frame, '\n')
  785. cr := bytes.IndexByte(frame, '\r')
  786. idx := lf
  787. if idx < 0 || (cr >= 0 && cr < idx) {
  788. idx = cr
  789. }
  790. if idx >= 0 {
  791. line = frame[:idx]
  792. consume := idx + 1
  793. if frame[idx] == '\r' && consume < len(frame) && frame[consume] == '\n' {
  794. consume++
  795. }
  796. frame = frame[consume:]
  797. } else {
  798. frame = nil
  799. }
  800. if len(line) == 0 || line[0] == ':' {
  801. continue
  802. }
  803. field, value, found := bytes.Cut(line, []byte{':'})
  804. if !found {
  805. value = nil
  806. }
  807. field = bytes.TrimSpace(field)
  808. value = bytes.TrimSpace(value)
  809. switch {
  810. case bytes.Equal(field, []byte("event")):
  811. eventType = value
  812. case bytes.Equal(field, []byte("data")):
  813. if !dataSeen {
  814. data = value
  815. dataSeen = true
  816. continue
  817. }
  818. if !dataOwned {
  819. data = append([]byte(nil), data...)
  820. dataOwned = true
  821. }
  822. data = append(data, '\n')
  823. data = append(data, value...)
  824. }
  825. }
  826. return bytes.TrimSpace(eventType), data
  827. }
  828. func (state *opsCaptureWriterState) captureResponseChunk(chunk []byte, status int) {
  829. if state == nil || state.limit <= 0 || len(chunk) == 0 {
  830. return
  831. }
  832. if status >= 400 {
  833. state.appendCapturedResponse(chunk)
  834. return
  835. }
  836. if state.sseCapturing {
  837. state.appendTerminalProbe(chunk)
  838. state.appendCapturedResponse(chunk)
  839. return
  840. }
  841. // Most stream writes contain one or more complete successful SSE frames.
  842. // Skip the byte-wise frame parser when the chunk cannot contain a terminal
  843. // event and leaves no split frame to carry into the next write.
  844. if len(state.probe) == 0 && len(state.lineProbe) == 0 && endsAtOpsSSEFrameBoundary(chunk) &&
  845. !mayContainOpsTerminalSSE(chunk) {
  846. return
  847. }
  848. for i, b := range chunk {
  849. if state.skipLF {
  850. state.skipLF = false
  851. if b == '\n' {
  852. continue
  853. }
  854. }
  855. if !state.lineTruncated {
  856. if len(state.lineProbe) < 256 {
  857. state.lineProbe = append(state.lineProbe, b)
  858. } else {
  859. state.lineTruncated = true
  860. }
  861. }
  862. if !state.frameTruncated {
  863. if len(state.probe) < opsTerminalSSEFrameProbeLimit {
  864. state.probe = append(state.probe, b)
  865. } else {
  866. state.frameTruncated = true
  867. }
  868. }
  869. if b != '\n' && b != '\r' {
  870. state.frameLineLen++
  871. continue
  872. }
  873. if b == '\r' {
  874. state.skipLF = true
  875. }
  876. if !state.lineTruncated && isOpsTerminalSSEEventLine(state.lineProbe) {
  877. state.sseCapturing = true
  878. state.terminalError = parsedOpsError{ErrorType: "upstream_error", StreamFailure: true}
  879. state.terminalFound = true
  880. state.appendCapturedResponse(state.lineProbe)
  881. state.probe = state.probe[:0]
  882. state.appendTerminalProbe(state.lineProbe)
  883. state.lineProbe = state.lineProbe[:0]
  884. state.frameLineLen = 0
  885. state.frameTruncated = false
  886. state.lineTruncated = false
  887. state.skipLF = false
  888. state.appendTerminalProbe(chunk[i+1:])
  889. state.appendCapturedResponse(chunk[i+1:])
  890. return
  891. }
  892. if state.frameLineLen != 0 {
  893. state.frameLineLen = 0
  894. state.lineProbe = state.lineProbe[:0]
  895. state.lineTruncated = false
  896. continue
  897. }
  898. if !state.frameTruncated && isOpsTerminalSSEFrame(state.probe) {
  899. state.sseCapturing = true
  900. state.terminalError, state.terminalFound = parseOpsSSEFailure(state.probe)
  901. state.appendCapturedResponse(state.probe)
  902. state.probe = state.probe[:0]
  903. state.appendCapturedResponse(chunk[i+1:])
  904. return
  905. }
  906. state.probe = state.probe[:0]
  907. state.lineProbe = state.lineProbe[:0]
  908. state.frameTruncated = false
  909. state.lineTruncated = false
  910. }
  911. }
  912. func endsAtOpsSSEFrameBoundary(chunk []byte) bool {
  913. return bytes.HasSuffix(chunk, []byte("\n\n")) ||
  914. bytes.HasSuffix(chunk, []byte("\r\n\r\n")) ||
  915. bytes.HasSuffix(chunk, []byte("\r\r"))
  916. }
  917. func mayContainOpsTerminalSSE(chunk []byte) bool {
  918. if bytes.Contains(chunk, []byte("response.failed")) {
  919. return true
  920. }
  921. return bytes.Contains(chunk, []byte("error")) &&
  922. (bytes.Contains(chunk, []byte("event")) || bytes.Contains(chunk, []byte(`"type"`)))
  923. }
  924. func isOpsTerminalSSEEventLine(line []byte) bool {
  925. line = bytes.TrimSpace(line)
  926. field, value, found := bytes.Cut(line, []byte{':'})
  927. return found && bytes.Equal(bytes.TrimSpace(field), []byte("event")) &&
  928. (bytes.Equal(bytes.TrimSpace(value), []byte("response.failed")) ||
  929. bytes.Equal(bytes.TrimSpace(value), []byte("error")))
  930. }
  931. func (state *opsCaptureWriterState) appendTerminalProbe(chunk []byte) {
  932. remaining := opsTerminalSSEFrameProbeLimit - len(state.probe)
  933. if remaining <= 0 {
  934. state.frameTruncated = true
  935. return
  936. }
  937. if len(chunk) > remaining {
  938. chunk = chunk[:remaining]
  939. state.frameTruncated = true
  940. }
  941. state.probe = append(state.probe, chunk...)
  942. }
  943. func (state *opsCaptureWriterState) finalizeResponseCapture() {
  944. if state == nil {
  945. return
  946. }
  947. if state.terminalFound {
  948. if parsed, ok := parseOpsSSEFailure(state.probe); ok {
  949. state.terminalError = parsed
  950. }
  951. return
  952. }
  953. if state.frameTruncated || len(state.probe) == 0 || !isOpsTerminalSSEFrame(state.probe) {
  954. return
  955. }
  956. state.sseCapturing = true
  957. state.appendCapturedResponse(state.probe)
  958. state.terminalError, state.terminalFound = parseOpsSSEFailure(state.probe)
  959. if !state.terminalFound {
  960. state.terminalError = parsedOpsError{ErrorType: "upstream_error", StreamFailure: true}
  961. state.terminalFound = true
  962. }
  963. }
  964. func (state *opsCaptureWriterState) appendCapturedResponse(chunk []byte) {
  965. remaining := state.limit - state.buf.Len()
  966. if remaining <= 0 {
  967. return
  968. }
  969. if len(chunk) > remaining {
  970. chunk = chunk[:remaining]
  971. }
  972. _, _ = state.buf.Write(chunk)
  973. }
  974. func (state *opsCaptureWriterState) shouldCapture() bool {
  975. if state.ctx == nil {
  976. return true
  977. }
  978. _, rejected := middleware2.GetIngressRejectReason(state.ctx)
  979. return !rejected
  980. }
  981. // OpsErrorLoggerMiddleware records error responses (status >= 400) into ops_error_logs.
  982. //
  983. // Notes:
  984. // - It buffers response bodies only for status >= 400 or terminal SSE frames.
  985. // - Streaming errors after the response has started (SSE) may still need explicit logging.
  986. func OpsErrorLoggerMiddleware(ops *service.OpsService) gin.HandlerFunc {
  987. return func(c *gin.Context) {
  988. originalWriter := c.Writer
  989. w := acquireOpsCaptureWriter(originalWriter)
  990. w.setContext(c)
  991. defer func() {
  992. // Restore the original writer before returning so outer middlewares
  993. // don't observe a pooled wrapper that has been released.
  994. if c.Writer == w {
  995. c.Writer = originalWriter
  996. }
  997. releaseOpsCaptureWriter(w)
  998. }()
  999. c.Writer = w
  1000. c.Next()
  1001. w.finalizeCapture()
  1002. if _, rejected := middleware2.GetIngressRejectReason(c); rejected {
  1003. return
  1004. }
  1005. if ops == nil {
  1006. return
  1007. }
  1008. if !ops.IsMonitoringEnabled(c.Request.Context()) {
  1009. return
  1010. }
  1011. if c.GetBool(opsDedicatedErrorRecordedKey) {
  1012. return
  1013. }
  1014. if shouldSkipOpsErrorLogForCyber(c) {
  1015. return
  1016. }
  1017. status := c.Writer.Status()
  1018. body := w.capturedBytes()
  1019. parsed := parseOpsErrorResponse(body)
  1020. if !parsed.StreamFailure {
  1021. if terminal, ok := w.capturedTerminalError(); ok {
  1022. parsed = terminal
  1023. }
  1024. }
  1025. if status < 400 {
  1026. if parsed.StreamFailure {
  1027. status = inferStreamFailureStatus(c, parsed)
  1028. } else {
  1029. // A marked in-band error is a visible request failure even though its
  1030. // wire status is already 200. Otherwise retain recovered attempts as a
  1031. // provider-health row whose 2xx status keeps it outside request SLA.
  1032. if len(service.GetOpsStreamErrors(c)) > 0 {
  1033. logOpsStreamError(c, ops, status)
  1034. } else {
  1035. logOpsRecoveredUpstream(c, ops, status)
  1036. }
  1037. return
  1038. }
  1039. }
  1040. // Skip logging if a passthrough rule with skip_monitoring=true matched.
  1041. if shouldSkipFinalOpsFailure(c) {
  1042. return
  1043. }
  1044. // Skip logging if the error should be filtered based on settings
  1045. if shouldSkipOpsErrorLog(c.Request.Context(), ops, parsed.Message, string(body), c.Request.URL.Path) {
  1046. return
  1047. }
  1048. apiKey := getOpsAPIKey(c)
  1049. clientRequestID, _ := c.Request.Context().Value(ctxkey.ClientRequestID).(string)
  1050. model, _ := c.Get(opsModelKey)
  1051. streamV, _ := c.Get(opsStreamKey)
  1052. accountIDV, _ := c.Get(opsAccountIDKey)
  1053. var modelName string
  1054. if s, ok := model.(string); ok {
  1055. modelName = s
  1056. }
  1057. stream := false
  1058. if b, ok := streamV.(bool); ok {
  1059. stream = b
  1060. }
  1061. var accountID *int64
  1062. if v, ok := accountIDV.(int64); ok && v > 0 {
  1063. accountID = &v
  1064. }
  1065. fallbackPlatform := guessPlatformFromPath(c.Request.URL.Path)
  1066. platform := resolveOpsPlatform(c.Request.Context(), apiKey, fallbackPlatform)
  1067. requestID, _ := c.Request.Context().Value(ctxkey.RequestID).(string)
  1068. requestID = strings.TrimSpace(requestID)
  1069. if requestID == "" {
  1070. requestID = c.Writer.Header().Get("X-Request-Id")
  1071. if requestID == "" {
  1072. requestID = c.Writer.Header().Get("x-request-id")
  1073. }
  1074. }
  1075. normalizedType := normalizeOpsErrorType(parsed.ErrorType, parsed.Code)
  1076. phase, isBusinessLimited, errorOwner, errorSource := classifyOpsErrorLog(c, normalizedType, parsed.Message, parsed.Code, status)
  1077. entry := &service.OpsInsertErrorLogInput{
  1078. RequestID: requestID,
  1079. ClientRequestID: clientRequestID,
  1080. AccountID: accountID,
  1081. Platform: platform,
  1082. Model: modelName,
  1083. RequestPath: func() string {
  1084. if c.Request != nil && c.Request.URL != nil {
  1085. return c.Request.URL.Path
  1086. }
  1087. return ""
  1088. }(),
  1089. Stream: stream,
  1090. InboundEndpoint: GetInboundEndpoint(c),
  1091. UpstreamEndpoint: GetUpstreamEndpoint(c, platform),
  1092. RequestedModel: modelName,
  1093. UpstreamModel: func() string {
  1094. if v, ok := c.Get(opsUpstreamModelKey); ok {
  1095. if s, ok := v.(string); ok {
  1096. return strings.TrimSpace(s)
  1097. }
  1098. }
  1099. return ""
  1100. }(),
  1101. RequestType: func() *int16 {
  1102. if v, ok := c.Get(opsRequestTypeKey); ok {
  1103. switch t := v.(type) {
  1104. case int16:
  1105. return &t
  1106. case int:
  1107. v16 := int16(t)
  1108. return &v16
  1109. }
  1110. }
  1111. return nil
  1112. }(),
  1113. UserAgent: c.GetHeader("User-Agent"),
  1114. ErrorPhase: phase,
  1115. ErrorType: normalizedType,
  1116. Severity: classifyOpsSeverity(normalizedType, status),
  1117. StatusCode: status,
  1118. IsBusinessLimited: isBusinessLimited,
  1119. IsCountTokens: isCountTokensRequest(c),
  1120. ErrorMessage: parsed.Message,
  1121. // Sanitize each SSE data payload before the body enters the async queue.
  1122. ErrorBody: sanitizeOpsSSEDataForPersistence(body),
  1123. ErrorSource: errorSource,
  1124. ErrorOwner: errorOwner,
  1125. CreatedAt: time.Now(),
  1126. }
  1127. applyOpsLatencyFieldsFromContext(c, entry)
  1128. applyOpsUpstreamFieldsFromContext(c, entry)
  1129. if parsed.StreamFailure {
  1130. if message := strings.TrimSpace(parsed.Message); message != "" {
  1131. entry.UpstreamErrorMessage = &message
  1132. }
  1133. if status >= 400 {
  1134. finalStatus := status
  1135. entry.UpstreamStatusCode = &finalStatus
  1136. }
  1137. }
  1138. suppressOpsUpstreamAttributionForLocalModelConfiguration(c, entry)
  1139. if apiKey != nil {
  1140. entry.APIKeyID = &apiKey.ID
  1141. // 有效 key 报错时快照前缀,key 之后被删也保留。
  1142. entry.APIKeyPrefix = keyPrefix(apiKey.Key, 8)
  1143. if apiKey.User != nil {
  1144. entry.UserID = &apiKey.User.ID
  1145. }
  1146. if apiKey.GroupID != nil {
  1147. entry.GroupID = apiKey.GroupID
  1148. }
  1149. // Prefer group platform if present (more stable than inferring from path).
  1150. if apiKey.Group != nil && apiKey.Group.Platform != "" {
  1151. entry.Platform = apiKey.Group.Platform
  1152. }
  1153. }
  1154. var clientIP string
  1155. if ip := strings.TrimSpace(ip.GetClientIP(c)); ip != "" {
  1156. clientIP = ip
  1157. entry.ClientIP = &clientIP
  1158. }
  1159. enqueueOpsErrorLog(ops, entry)
  1160. }
  1161. }
  1162. func logOpsRecoveredUpstream(c *gin.Context, ops *service.OpsService, finalStatus int) {
  1163. if c == nil || ops == nil || finalStatus >= 400 {
  1164. return
  1165. }
  1166. entry := &service.OpsInsertErrorLogInput{StatusCode: finalStatus}
  1167. applyOpsUpstreamFieldsFromContext(c, entry)
  1168. if len(entry.UpstreamErrors) > 0 {
  1169. visibleEvents := make([]*service.OpsUpstreamErrorEvent, 0, len(entry.UpstreamErrors))
  1170. for _, event := range entry.UpstreamErrors {
  1171. if event != nil && !event.SkipMonitoring {
  1172. visibleEvents = append(visibleEvents, event)
  1173. }
  1174. }
  1175. if len(visibleEvents) == 0 {
  1176. return
  1177. }
  1178. applyOpsUpstreamErrorEvents(entry, visibleEvents)
  1179. }
  1180. if entry.UpstreamStatusCode == nil && entry.UpstreamErrorMessage == nil &&
  1181. entry.UpstreamErrorDetail == nil && len(entry.UpstreamErrors) == 0 {
  1182. return
  1183. }
  1184. lastStatus := 0
  1185. if entry.UpstreamStatusCode != nil {
  1186. lastStatus = *entry.UpstreamStatusCode
  1187. }
  1188. lastStage := ""
  1189. for i := len(entry.UpstreamErrors) - 1; i >= 0; i-- {
  1190. if event := entry.UpstreamErrors[i]; event != nil {
  1191. lastStage = event.Stage
  1192. if event.AccountID > 0 {
  1193. accountID := event.AccountID
  1194. entry.AccountID = &accountID
  1195. }
  1196. break
  1197. }
  1198. }
  1199. if entry.AccountID == nil {
  1200. if accountID, ok := c.Get(opsAccountIDKey); ok {
  1201. if value, ok := accountID.(int64); ok && value > 0 {
  1202. entry.AccountID = &value
  1203. }
  1204. }
  1205. }
  1206. entry.ErrorPhase = "upstream"
  1207. entry.ErrorType = "upstream_error"
  1208. entry.ErrorSource = "upstream_http"
  1209. entry.ErrorOwner = "provider"
  1210. entry.Severity = classifyOpsSeverity(entry.ErrorType, lastStatus)
  1211. entry.IsCountTokens = isCountTokensRequest(c)
  1212. entry.CreatedAt = time.Now()
  1213. entry.ErrorMessage = "Recovered upstream error"
  1214. if lastStage == string(service.GatewayFailureStageAccountAuth) {
  1215. entry.ErrorPhase = string(service.GatewayFailureStageAccountAuth)
  1216. entry.ErrorMessage = "Recovered account authentication failure"
  1217. } else if lastStatus > 0 {
  1218. entry.ErrorMessage += " " + strconv.Itoa(lastStatus)
  1219. }
  1220. if entry.UpstreamErrorMessage != nil && strings.TrimSpace(*entry.UpstreamErrorMessage) != "" {
  1221. entry.ErrorMessage += ": " + strings.TrimSpace(*entry.UpstreamErrorMessage)
  1222. }
  1223. entry.ErrorMessage = truncateString(entry.ErrorMessage, 2048)
  1224. if c.Request != nil {
  1225. entry.UserAgent = c.GetHeader("User-Agent")
  1226. if c.Request.URL != nil {
  1227. entry.RequestPath = c.Request.URL.Path
  1228. }
  1229. if c.Request.Context() != nil {
  1230. entry.ClientRequestID, _ = c.Request.Context().Value(ctxkey.ClientRequestID).(string)
  1231. entry.RequestID, _ = c.Request.Context().Value(ctxkey.RequestID).(string)
  1232. }
  1233. }
  1234. entry.RequestID = strings.TrimSpace(entry.RequestID)
  1235. if entry.RequestID == "" {
  1236. entry.RequestID = c.Writer.Header().Get("X-Request-Id")
  1237. }
  1238. entry.Model = c.GetString(opsModelKey)
  1239. entry.RequestedModel = entry.Model
  1240. entry.Stream = c.GetBool(opsStreamKey)
  1241. entry.InboundEndpoint = GetInboundEndpoint(c)
  1242. entry.UpstreamModel = c.GetString(opsUpstreamModelKey)
  1243. entry.RequestType = opsRequestTypeFromContext(c)
  1244. apiKey := getOpsAPIKey(c)
  1245. fallbackPlatform := guessPlatformFromPath(entry.RequestPath)
  1246. requestContext := context.Background()
  1247. if c.Request != nil {
  1248. requestContext = c.Request.Context()
  1249. }
  1250. entry.Platform = resolveOpsPlatform(requestContext, apiKey, fallbackPlatform)
  1251. entry.UpstreamEndpoint = GetUpstreamEndpoint(c, entry.Platform)
  1252. if apiKey != nil {
  1253. entry.APIKeyID = &apiKey.ID
  1254. entry.APIKeyPrefix = keyPrefix(apiKey.Key, 8)
  1255. if apiKey.User != nil {
  1256. entry.UserID = &apiKey.User.ID
  1257. }
  1258. if apiKey.GroupID != nil {
  1259. entry.GroupID = apiKey.GroupID
  1260. }
  1261. if apiKey.Group != nil && apiKey.Group.Platform != "" {
  1262. entry.Platform = apiKey.Group.Platform
  1263. }
  1264. }
  1265. if clientIP := strings.TrimSpace(ip.GetClientIP(c)); clientIP != "" {
  1266. entry.ClientIP = &clientIP
  1267. }
  1268. applyOpsLatencyFieldsFromContext(c, entry)
  1269. enqueueOpsErrorLog(ops, entry)
  1270. }
  1271. func opsRequestTypeFromContext(c *gin.Context) *int16 {
  1272. if c == nil {
  1273. return nil
  1274. }
  1275. if value, ok := c.Get(opsRequestTypeKey); ok {
  1276. switch typed := value.(type) {
  1277. case int16:
  1278. result := typed
  1279. return &result
  1280. case int:
  1281. result := int16(typed)
  1282. return &result
  1283. }
  1284. }
  1285. return nil
  1286. }
  1287. // logOpsStreamError 记录一次挂在已固化 HTTP 200 SSE 流上的就地错误。
  1288. // 由于 wire 状态码停留在 200,常规的 status>=400 捕获路径永远不会触发;
  1289. // handleStreamingAwareError 通过 service.MarkOpsStreamError 标记这类错误,
  1290. // 此函数据此补记一条错误日志,让并发限流/流内失败在错误看板里可见。
  1291. //
  1292. // 仅在 status<400 且不存在上游错误上下文时调用:上游透传错误已由中间件的
  1293. // upstream-context 分支落库,无需在此重复记录。
  1294. func logOpsStreamError(c *gin.Context, ops *service.OpsService, wireStatus int) {
  1295. for _, streamErr := range service.GetOpsStreamErrors(c) {
  1296. logOpsStreamErrorValue(c, ops, wireStatus, streamErr)
  1297. }
  1298. }
  1299. func logOpsStreamErrorValue(c *gin.Context, ops *service.OpsService, wireStatus int, streamErr service.OpsStreamError) {
  1300. // 命中 skip_monitoring=true 透传规则的请求跳过落库,与其它分支一致。
  1301. if streamErr.SkipMonitoring || (streamErr.Turn == 0 && shouldSkipFinalOpsFailure(c)) {
  1302. return
  1303. }
  1304. // 复用与 status>=400 分支相同的设置过滤(context canceled / 无可用账号等)。
  1305. if shouldSkipOpsErrorLog(c.Request.Context(), ops, streamErr.Message, streamErr.Message, c.Request.URL.Path) {
  1306. return
  1307. }
  1308. // 分级用「本应返回的状态码」(如并发限流 429),wire 状态码缺省时回退。
  1309. classifyStatus := streamErr.IntendedStatus
  1310. if classifyStatus <= 0 {
  1311. classifyStatus = wireStatus
  1312. }
  1313. normalizedType := normalizeOpsErrorType(streamErr.ErrType, streamErr.Code)
  1314. phase, isBusinessLimited, errorOwner, errorSource := classifyOpsErrorLog(c, normalizedType, streamErr.Message, streamErr.Code, classifyStatus)
  1315. recordedStatus := wireStatus
  1316. if streamErr.CountTowardsSLA && streamErr.IntendedStatus >= 400 {
  1317. recordedStatus = streamErr.IntendedStatus
  1318. }
  1319. errorBody := ""
  1320. if streamErr.Code != "" {
  1321. if payload, err := json.Marshal(gin.H{"error": gin.H{
  1322. "type": normalizedType, "code": streamErr.Code, "message": streamErr.Message,
  1323. }}); err == nil {
  1324. errorBody = string(payload)
  1325. }
  1326. }
  1327. apiKey := getOpsAPIKey(c)
  1328. clientRequestID, _ := c.Request.Context().Value(ctxkey.ClientRequestID).(string)
  1329. model, _ := c.Get(opsModelKey)
  1330. var modelName string
  1331. if s, ok := model.(string); ok {
  1332. modelName = s
  1333. }
  1334. accountIDV, _ := c.Get(opsAccountIDKey)
  1335. var accountID *int64
  1336. if v, ok := accountIDV.(int64); ok && v > 0 {
  1337. accountID = &v
  1338. }
  1339. fallbackPlatform := guessPlatformFromPath(c.Request.URL.Path)
  1340. platform := resolveOpsPlatform(c.Request.Context(), apiKey, fallbackPlatform)
  1341. requestID, _ := c.Request.Context().Value(ctxkey.RequestID).(string)
  1342. requestID = strings.TrimSpace(requestID)
  1343. if requestID == "" {
  1344. requestID = c.Writer.Header().Get("X-Request-Id")
  1345. if requestID == "" {
  1346. requestID = c.Writer.Header().Get("x-request-id")
  1347. }
  1348. }
  1349. entry := &service.OpsInsertErrorLogInput{
  1350. RequestID: requestID,
  1351. ClientRequestID: clientRequestID,
  1352. AccountID: accountID,
  1353. Platform: platform,
  1354. Model: modelName,
  1355. RequestPath: func() string {
  1356. if c.Request != nil && c.Request.URL != nil {
  1357. return c.Request.URL.Path
  1358. }
  1359. return ""
  1360. }(),
  1361. // 就地 SSE 错误只出现在流式请求上。
  1362. Stream: true,
  1363. InboundEndpoint: GetInboundEndpoint(c),
  1364. UpstreamEndpoint: GetUpstreamEndpoint(c, platform),
  1365. RequestedModel: modelName,
  1366. UpstreamModel: func() string {
  1367. if v, ok := c.Get(opsUpstreamModelKey); ok {
  1368. if s, ok := v.(string); ok {
  1369. return strings.TrimSpace(s)
  1370. }
  1371. }
  1372. return ""
  1373. }(),
  1374. RequestType: func() *int16 {
  1375. if v, ok := c.Get(opsRequestTypeKey); ok {
  1376. switch t := v.(type) {
  1377. case int16:
  1378. return &t
  1379. case int:
  1380. v16 := int16(t)
  1381. return &v16
  1382. }
  1383. }
  1384. return nil
  1385. }(),
  1386. UserAgent: c.GetHeader("User-Agent"),
  1387. ErrorPhase: phase,
  1388. ErrorType: normalizedType,
  1389. Severity: classifyOpsSeverity(normalizedType, classifyStatus),
  1390. StatusCode: recordedStatus,
  1391. IsBusinessLimited: isBusinessLimited,
  1392. IsCountTokens: isCountTokensRequest(c),
  1393. ErrorMessage: streamErr.Message,
  1394. ErrorBody: errorBody,
  1395. ErrorSource: errorSource,
  1396. ErrorOwner: errorOwner,
  1397. CreatedAt: time.Now(),
  1398. }
  1399. applyOpsLatencyFieldsFromContext(c, entry)
  1400. applyOpsUpstreamFieldsFromContext(c, entry)
  1401. if streamErr.Turn > 0 {
  1402. applyOpsStreamErrorSnapshot(entry, streamErr)
  1403. }
  1404. if apiKey != nil {
  1405. entry.APIKeyID = &apiKey.ID
  1406. entry.APIKeyPrefix = keyPrefix(apiKey.Key, 8)
  1407. if apiKey.User != nil {
  1408. entry.UserID = &apiKey.User.ID
  1409. }
  1410. if apiKey.GroupID != nil {
  1411. entry.GroupID = apiKey.GroupID
  1412. }
  1413. if apiKey.Group != nil && apiKey.Group.Platform != "" {
  1414. entry.Platform = apiKey.Group.Platform
  1415. }
  1416. }
  1417. if clientIP := strings.TrimSpace(ip.GetClientIP(c)); clientIP != "" {
  1418. entry.ClientIP = &clientIP
  1419. }
  1420. enqueueOpsErrorLog(ops, entry)
  1421. }
  1422. func applyOpsStreamErrorSnapshot(entry *service.OpsInsertErrorLogInput, streamErr service.OpsStreamError) {
  1423. if entry == nil {
  1424. return
  1425. }
  1426. if streamErr.AccountID > 0 {
  1427. accountID := streamErr.AccountID
  1428. entry.AccountID = &accountID
  1429. }
  1430. entry.UpstreamModel = strings.TrimSpace(streamErr.UpstreamModel)
  1431. entry.UpstreamStatusCode = nil
  1432. if streamErr.UpstreamStatus > 0 {
  1433. status := streamErr.UpstreamStatus
  1434. entry.UpstreamStatusCode = &status
  1435. }
  1436. entry.UpstreamErrorMessage = nil
  1437. if message := strings.TrimSpace(streamErr.UpstreamMessage); message != "" {
  1438. entry.UpstreamErrorMessage = &message
  1439. }
  1440. entry.UpstreamErrorDetail = nil
  1441. if detail := strings.TrimSpace(streamErr.UpstreamDetail); detail != "" {
  1442. entry.UpstreamErrorDetail = &detail
  1443. }
  1444. entry.UpstreamErrors = streamErr.UpstreamErrors
  1445. lastStage := ""
  1446. for i := len(streamErr.UpstreamErrors) - 1; i >= 0; i-- {
  1447. if streamErr.UpstreamErrors[i] != nil {
  1448. lastStage = streamErr.UpstreamErrors[i].Stage
  1449. break
  1450. }
  1451. }
  1452. if lastStage == string(service.GatewayFailureStageAccountAuth) {
  1453. entry.ErrorPhase = string(service.GatewayFailureStageAccountAuth)
  1454. entry.ErrorOwner = "provider"
  1455. entry.ErrorSource = "gateway"
  1456. entry.IsBusinessLimited = false
  1457. } else if streamErr.UpstreamStatus > 0 || len(streamErr.UpstreamErrors) > 0 {
  1458. entry.ErrorPhase = "upstream"
  1459. entry.ErrorOwner = "provider"
  1460. entry.ErrorSource = "upstream_http"
  1461. entry.IsBusinessLimited = false
  1462. }
  1463. }
  1464. func shouldSkipFinalOpsFailure(c *gin.Context) bool {
  1465. if c == nil {
  1466. return false
  1467. }
  1468. if v, ok := c.Get(service.OpsSkipPassthroughKey); ok {
  1469. if skip, _ := v.(bool); skip {
  1470. return true
  1471. }
  1472. }
  1473. if v, ok := c.Get(service.OpsUpstreamErrorsKey); ok {
  1474. if events, ok := v.([]*service.OpsUpstreamErrorEvent); ok {
  1475. for i := len(events) - 1; i >= 0; i-- {
  1476. if events[i] != nil {
  1477. return events[i].SkipMonitoring
  1478. }
  1479. }
  1480. }
  1481. }
  1482. return false
  1483. }
  1484. // isCountTokensRequest checks if the request is a count_tokens request
  1485. func isCountTokensRequest(c *gin.Context) bool {
  1486. if c == nil || c.Request == nil || c.Request.URL == nil {
  1487. return false
  1488. }
  1489. return isTokenCountRequestPath(c.Request.URL.Path)
  1490. }
  1491. func isTokenCountRequestPath(path string) bool {
  1492. return strings.Contains(path, "/count_tokens") || strings.Contains(path, "/responses/input_tokens")
  1493. }
  1494. func applyOpsLatencyFieldsFromContext(c *gin.Context, entry *service.OpsInsertErrorLogInput) {
  1495. if c == nil || entry == nil {
  1496. return
  1497. }
  1498. entry.AuthLatencyMs = getContextLatencyMs(c, service.OpsAuthLatencyMsKey)
  1499. entry.RoutingLatencyMs = getContextLatencyMs(c, service.OpsRoutingLatencyMsKey)
  1500. entry.UpstreamLatencyMs = getContextLatencyMs(c, service.OpsUpstreamLatencyMsKey)
  1501. entry.ResponseLatencyMs = getContextLatencyMs(c, service.OpsResponseLatencyMsKey)
  1502. entry.TimeToFirstTokenMs = getContextLatencyMs(c, service.OpsTimeToFirstTokenMsKey)
  1503. }
  1504. // applyOpsUpstreamFieldsFromContext captures attempt-level upstream context.
  1505. // A final account_auth event owns the top-level status and forces it to zero;
  1506. // prior inference statuses remain available in UpstreamErrors.
  1507. func applyOpsUpstreamFieldsFromContext(c *gin.Context, entry *service.OpsInsertErrorLogInput) {
  1508. if c == nil || entry == nil {
  1509. return
  1510. }
  1511. if v, ok := c.Get(service.OpsUpstreamStatusCodeKey); ok {
  1512. switch t := v.(type) {
  1513. case int:
  1514. if t > 0 {
  1515. code := t
  1516. entry.UpstreamStatusCode = &code
  1517. }
  1518. case int64:
  1519. if t > 0 {
  1520. code := int(t)
  1521. entry.UpstreamStatusCode = &code
  1522. }
  1523. }
  1524. }
  1525. if v, ok := c.Get(service.OpsUpstreamErrorMessageKey); ok {
  1526. if value, ok := v.(string); ok {
  1527. if message := strings.TrimSpace(value); message != "" {
  1528. entry.UpstreamErrorMessage = &message
  1529. }
  1530. }
  1531. }
  1532. if v, ok := c.Get(service.OpsUpstreamErrorDetailKey); ok {
  1533. if value, ok := v.(string); ok {
  1534. if detail := strings.TrimSpace(value); detail != "" {
  1535. entry.UpstreamErrorDetail = &detail
  1536. }
  1537. }
  1538. }
  1539. if v, ok := c.Get(service.OpsUpstreamErrorsKey); ok {
  1540. if events, ok := v.([]*service.OpsUpstreamErrorEvent); ok && len(events) > 0 {
  1541. applyOpsUpstreamErrorEvents(entry, events)
  1542. }
  1543. }
  1544. }
  1545. func applyOpsUpstreamErrorEvents(entry *service.OpsInsertErrorLogInput, events []*service.OpsUpstreamErrorEvent) {
  1546. entry.UpstreamErrors = events
  1547. var last *service.OpsUpstreamErrorEvent
  1548. for i := len(events) - 1; i >= 0; i-- {
  1549. if events[i] != nil {
  1550. last = events[i]
  1551. break
  1552. }
  1553. }
  1554. if last == nil {
  1555. return
  1556. }
  1557. entry.UpstreamStatusCode = nil
  1558. entry.UpstreamErrorMessage = nil
  1559. entry.UpstreamErrorDetail = nil
  1560. if last.Stage == string(service.GatewayFailureStageAccountAuth) {
  1561. code := 0
  1562. entry.UpstreamStatusCode = &code
  1563. } else if last.UpstreamStatusCode > 0 {
  1564. code := last.UpstreamStatusCode
  1565. entry.UpstreamStatusCode = &code
  1566. }
  1567. if message := strings.TrimSpace(last.Message); message != "" {
  1568. entry.UpstreamErrorMessage = &message
  1569. }
  1570. if detail := strings.TrimSpace(last.Detail); detail != "" {
  1571. entry.UpstreamErrorDetail = &detail
  1572. }
  1573. }
  1574. func suppressOpsUpstreamAttributionForLocalModelConfiguration(c *gin.Context, entry *service.OpsInsertErrorLogInput) {
  1575. if entry == nil || !service.HasOpsClientBusinessLimited(c) || service.OpsClientBusinessLimitedReason(c) != service.OpsClientBusinessLimitedReasonLocalModelConfiguration {
  1576. return
  1577. }
  1578. entry.AccountID = nil
  1579. entry.UpstreamEndpoint = ""
  1580. entry.UpstreamModel = ""
  1581. entry.UpstreamStatusCode = nil
  1582. entry.UpstreamErrorMessage = nil
  1583. entry.UpstreamErrorDetail = nil
  1584. entry.UpstreamErrors = nil
  1585. }
  1586. func getContextLatencyMs(c *gin.Context, key string) *int64 {
  1587. if c == nil || strings.TrimSpace(key) == "" {
  1588. return nil
  1589. }
  1590. v, ok := c.Get(key)
  1591. if !ok {
  1592. return nil
  1593. }
  1594. var ms int64
  1595. switch t := v.(type) {
  1596. case int:
  1597. ms = int64(t)
  1598. case int32:
  1599. ms = int64(t)
  1600. case int64:
  1601. ms = t
  1602. case float64:
  1603. ms = int64(t)
  1604. default:
  1605. return nil
  1606. }
  1607. if ms < 0 {
  1608. return nil
  1609. }
  1610. return &ms
  1611. }
  1612. type parsedOpsError struct {
  1613. ErrorType string
  1614. Message string
  1615. Code string
  1616. StatusCode int
  1617. StreamFailure bool
  1618. }
  1619. func parseOpsErrorResponse(body []byte) parsedOpsError {
  1620. if len(body) == 0 {
  1621. return parsedOpsError{}
  1622. }
  1623. if parsed, ok := parseOpsSSEFailure(body); ok {
  1624. return parsed
  1625. }
  1626. // Fast path: attempt to decode into a generic map.
  1627. var m map[string]any
  1628. if err := json.Unmarshal(body, &m); err != nil {
  1629. return parsedOpsError{Message: truncateString(string(body), 1024)}
  1630. }
  1631. // Claude/OpenAI-style gateway error: { type:"error", error:{ type, message } }
  1632. if errObj, ok := m["error"].(map[string]any); ok {
  1633. t, _ := errObj["type"].(string)
  1634. msg, _ := errObj["message"].(string)
  1635. if t == "" {
  1636. t = "api_error"
  1637. }
  1638. code := opsJSONScalarString(errObj["code"])
  1639. return parsedOpsError{ErrorType: t, Message: msg, Code: code}
  1640. }
  1641. if errMessage, ok := m["error"].(string); ok && strings.TrimSpace(errMessage) != "" {
  1642. t, _ := m["type"].(string)
  1643. if t == "" || t == "error" {
  1644. t = "api_error"
  1645. }
  1646. return parsedOpsError{ErrorType: t, Message: strings.TrimSpace(errMessage), Code: opsJSONScalarString(m["code"])}
  1647. }
  1648. // APIKeyAuth-style: { code:"INSUFFICIENT_BALANCE", message:"..." }
  1649. code := opsJSONScalarString(m["code"])
  1650. msg, _ := m["message"].(string)
  1651. if code != "" || msg != "" {
  1652. t, _ := m["type"].(string)
  1653. if t == "" || t == "error" {
  1654. t = "api_error"
  1655. }
  1656. return parsedOpsError{ErrorType: t, Message: msg, Code: code}
  1657. }
  1658. return parsedOpsError{Message: truncateString(string(body), 1024)}
  1659. }
  1660. func opsJSONScalarString(value any) string {
  1661. switch value := value.(type) {
  1662. case string:
  1663. return strings.TrimSpace(value)
  1664. case float64:
  1665. return strconvItoa(int(value))
  1666. case json.Number:
  1667. return strings.TrimSpace(value.String())
  1668. case int:
  1669. return strconvItoa(value)
  1670. case int64:
  1671. return strconv.FormatInt(value, 10)
  1672. default:
  1673. return ""
  1674. }
  1675. }
  1676. func opsJSONInt(value any) int {
  1677. switch value := value.(type) {
  1678. case float64:
  1679. return int(value)
  1680. case json.Number:
  1681. parsed, _ := strconv.Atoi(value.String())
  1682. return parsed
  1683. case string:
  1684. parsed, _ := strconv.Atoi(strings.TrimSpace(value))
  1685. return parsed
  1686. case int:
  1687. return value
  1688. case int64:
  1689. return int(value)
  1690. default:
  1691. return 0
  1692. }
  1693. }
  1694. func parseOpsSSEFailure(body []byte) (parsedOpsError, bool) {
  1695. normalized := strings.ReplaceAll(string(body), "\r\n", "\n")
  1696. normalized = strings.ReplaceAll(normalized, "\r", "\n")
  1697. var errorCandidate *parsedOpsError
  1698. for _, frame := range strings.Split(normalized, "\n\n") {
  1699. frame = strings.TrimSpace(frame)
  1700. if frame == "" {
  1701. continue
  1702. }
  1703. eventTypeBytes, payloadBytes := parseOpsSSEFrameEnvelope([]byte(frame))
  1704. eventType := string(eventTypeBytes)
  1705. if eventType != "response.failed" && eventType != "error" && len(payloadBytes) == 0 {
  1706. continue
  1707. }
  1708. payload := string(payloadBytes)
  1709. var event map[string]any
  1710. if err := json.Unmarshal(payloadBytes, &event); err == nil {
  1711. if eventType == "" {
  1712. eventType, _ = event["type"].(string)
  1713. }
  1714. }
  1715. if eventType != "response.failed" && eventType != "error" {
  1716. continue
  1717. }
  1718. parsed := parsedOpsError{ErrorType: "upstream_error", StreamFailure: true}
  1719. if eventType == "error" {
  1720. parsed.ErrorType = "api_error"
  1721. }
  1722. errObj := opsSSEErrorObject(event)
  1723. if errObj == nil && event != nil && (event["message"] != nil || event["code"] != nil) {
  1724. errObj = event
  1725. }
  1726. if errObj != nil {
  1727. parsed.ErrorType, _ = errObj["type"].(string)
  1728. if parsed.ErrorType == "error" || parsed.ErrorType == "response.failed" {
  1729. parsed.ErrorType = ""
  1730. }
  1731. parsed.Message, _ = errObj["message"].(string)
  1732. switch code := errObj["code"].(type) {
  1733. case string:
  1734. parsed.Code = strings.TrimSpace(code)
  1735. case float64:
  1736. parsed.Code = strconvItoa(int(code))
  1737. }
  1738. parsed.StatusCode = opsJSONInt(errObj["status_code"])
  1739. if parsed.StatusCode == 0 {
  1740. parsed.StatusCode = opsJSONInt(errObj["status"])
  1741. }
  1742. if parsed.StatusCode == 0 {
  1743. parsed.StatusCode = opsJSONInt(event["status_code"])
  1744. }
  1745. if parsed.StatusCode == 0 {
  1746. parsed.StatusCode = opsJSONInt(event["status"])
  1747. }
  1748. if parsed.ErrorType == "" {
  1749. parsed.ErrorType = inferResponsesFailedOpsErrorType(parsed.Code)
  1750. }
  1751. if parsed.ErrorType == "" {
  1752. if eventType == "error" {
  1753. parsed.ErrorType = "api_error"
  1754. } else {
  1755. parsed.ErrorType = "upstream_error"
  1756. }
  1757. }
  1758. }
  1759. if strings.TrimSpace(parsed.Message) == "" && payload != "" {
  1760. trimmedPayload := strings.TrimSpace(payload)
  1761. if strings.HasPrefix(trimmedPayload, "{") || strings.HasPrefix(trimmedPayload, "[") {
  1762. parsed.Message = "upstream stream failed"
  1763. } else {
  1764. parsed.Message = truncateString(trimmedPayload, 1024)
  1765. }
  1766. }
  1767. if eventType == "response.failed" {
  1768. return parsed, true
  1769. }
  1770. candidate := parsed
  1771. errorCandidate = &candidate
  1772. }
  1773. if errorCandidate != nil {
  1774. return *errorCandidate, true
  1775. }
  1776. return parsedOpsError{}, false
  1777. }
  1778. func opsSSEErrorObject(event map[string]any) map[string]any {
  1779. if event == nil {
  1780. return nil
  1781. }
  1782. if errObj, ok := event["error"].(map[string]any); ok {
  1783. return errObj
  1784. }
  1785. if response, ok := event["response"].(map[string]any); ok {
  1786. if errObj, ok := response["error"].(map[string]any); ok {
  1787. return errObj
  1788. }
  1789. }
  1790. // Some providers flatten error fields onto the terminal event itself:
  1791. // {"type":"error","code":"service_unavailable","message":"..."}.
  1792. if eventType, _ := event["type"].(string); eventType == "error" {
  1793. return event
  1794. }
  1795. return nil
  1796. }
  1797. func sanitizeOpsSSEDataForPersistence(body []byte) string {
  1798. if len(body) == 0 || !bytes.Contains(body, []byte("data")) {
  1799. return string(body)
  1800. }
  1801. normalized := bytes.ReplaceAll(body, []byte("\r\n"), []byte{'\n'})
  1802. normalized = bytes.ReplaceAll(normalized, []byte{'\r'}, []byte{'\n'})
  1803. frames := bytes.Split(normalized, []byte("\n\n"))
  1804. var out bytes.Buffer
  1805. out.Grow(len(body))
  1806. for frameIndex, frame := range frames {
  1807. if frameIndex > 0 {
  1808. _, _ = out.WriteString("\n\n")
  1809. }
  1810. _, payload := parseOpsSSEFrameEnvelope(frame)
  1811. trimmedPayload := bytes.TrimSpace(payload)
  1812. replacement := ""
  1813. if json.Valid(trimmedPayload) {
  1814. replacement, _ = service.SanitizeOpsErrorBodyForQueue(string(trimmedPayload))
  1815. } else if len(trimmedPayload) > 0 && (trimmedPayload[0] == '{' || trimmedPayload[0] == '[') {
  1816. // Captured terminal frames can be truncated at the queue bound. Never
  1817. // persist a JSON-looking fragment that could contain an unredacted key.
  1818. replacement = `{"payload_truncated":true}`
  1819. }
  1820. if replacement == "" {
  1821. _, _ = out.Write(frame)
  1822. continue
  1823. }
  1824. wroteData := false
  1825. emittedLine := false
  1826. for _, line := range bytes.Split(frame, []byte{'\n'}) {
  1827. field, _, found := bytes.Cut(line, []byte{':'})
  1828. if found && bytes.Equal(bytes.TrimSpace(field), []byte("data")) {
  1829. if wroteData {
  1830. continue
  1831. }
  1832. line = append([]byte("data: "), replacement...)
  1833. wroteData = true
  1834. }
  1835. if emittedLine {
  1836. _ = out.WriteByte('\n')
  1837. }
  1838. _, _ = out.Write(line)
  1839. emittedLine = true
  1840. }
  1841. }
  1842. return out.String()
  1843. }
  1844. func inferResponsesFailedOpsErrorType(code string) string {
  1845. switch strings.TrimSpace(code) {
  1846. case "rate_limit_exceeded":
  1847. return "rate_limit_error"
  1848. case "permission_denied", "permission_error", "insufficient_permissions", "cyber_policy", "content_policy":
  1849. return "permission_error"
  1850. case "invalid_request", "context_length_exceeded":
  1851. return "invalid_request_error"
  1852. case "server_is_overloaded":
  1853. return "overloaded_error"
  1854. case "service_unavailable", "service_unavailable_error", "server_error":
  1855. return "service_unavailable_error"
  1856. case "authentication_failed":
  1857. return "authentication_error"
  1858. default:
  1859. return ""
  1860. }
  1861. }
  1862. func inferStreamFailureStatus(_ *gin.Context, parsed parsedOpsError) int {
  1863. if parsed.StatusCode >= 400 && parsed.StatusCode <= 599 {
  1864. return parsed.StatusCode
  1865. }
  1866. switch strings.TrimSpace(parsed.Code) {
  1867. case "rate_limit_exceeded":
  1868. return http.StatusTooManyRequests
  1869. case "permission_denied", "permission_error", "insufficient_permissions", "cyber_policy", "content_policy":
  1870. return http.StatusForbidden
  1871. case "invalid_request", "context_length_exceeded":
  1872. return http.StatusBadRequest
  1873. case "server_is_overloaded":
  1874. return http.StatusServiceUnavailable
  1875. case "service_unavailable", "service_unavailable_error", "server_error":
  1876. return http.StatusServiceUnavailable
  1877. case "authentication_failed":
  1878. return http.StatusUnauthorized
  1879. }
  1880. switch strings.TrimSpace(parsed.ErrorType) {
  1881. case "rate_limit_error":
  1882. return http.StatusTooManyRequests
  1883. case "permission_error", "forbidden_error":
  1884. return http.StatusForbidden
  1885. case "authentication_error":
  1886. return http.StatusUnauthorized
  1887. case "invalid_request_error":
  1888. return http.StatusBadRequest
  1889. case "overloaded_error", "service_unavailable_error":
  1890. return http.StatusServiceUnavailable
  1891. }
  1892. return http.StatusBadGateway
  1893. }
  1894. // getOpsAPIKey 返回用于 Ops 错误日志的 API Key:优先取已鉴权写入的正式 key;
  1895. // 鉴权早退(分组停用/删除、Key 停用/过期/额度、用户停用、IP 限制等)时,
  1896. // 正式 key 尚未写入,回退到 middleware 写入的 ops fallback key
  1897. // (含 User/Group/Platform),从而让日志能展示 用户/分组/平台。
  1898. func getOpsAPIKey(c *gin.Context) *service.APIKey {
  1899. if apiKey, ok := middleware2.GetAPIKeyFromContext(c); ok && apiKey != nil {
  1900. return apiKey
  1901. }
  1902. if apiKey, ok := middleware2.GetOpsFallbackAPIKey(c); ok && apiKey != nil {
  1903. return apiKey
  1904. }
  1905. return nil
  1906. }
  1907. func resolveOpsPlatform(ctx context.Context, apiKey *service.APIKey, fallback string) string {
  1908. if platform, ok := service.ResolvedTargetPlatformFromContext(ctx); ok {
  1909. return platform
  1910. }
  1911. if apiKey != nil && apiKey.Group != nil && apiKey.Group.Platform != "" {
  1912. return apiKey.Group.Platform
  1913. }
  1914. return fallback
  1915. }
  1916. func guessPlatformFromPath(path string) string {
  1917. p := strings.ToLower(path)
  1918. switch {
  1919. case strings.HasPrefix(p, "/antigravity/"):
  1920. return service.PlatformAntigravity
  1921. case strings.HasPrefix(p, "/v1beta/"):
  1922. return service.PlatformGemini
  1923. case strings.Contains(p, "/responses"), strings.Contains(p, "/images/"):
  1924. return service.PlatformOpenAI
  1925. default:
  1926. return ""
  1927. }
  1928. }
  1929. // isKnownOpsErrorType returns true if t is a recognized error type used by the
  1930. // ops classification pipeline. Upstream proxies sometimes return garbage values
  1931. // (e.g. the Go-serialized literal "<nil>") which would pollute phase/severity
  1932. // classification if accepted blindly.
  1933. func isKnownOpsErrorType(t string) bool {
  1934. switch t {
  1935. case "invalid_request_error",
  1936. "authentication_error",
  1937. "permission_error",
  1938. "model_not_found",
  1939. "service_unavailable",
  1940. "rate_limit_error",
  1941. "billing_error",
  1942. "subscription_error",
  1943. "upstream_error",
  1944. "overloaded_error",
  1945. "service_unavailable_error",
  1946. "api_error",
  1947. "not_found_error",
  1948. "forbidden_error":
  1949. return true
  1950. }
  1951. return false
  1952. }
  1953. func normalizeOpsErrorType(errType string, code string) string {
  1954. if errType != "" && isKnownOpsErrorType(errType) {
  1955. return errType
  1956. }
  1957. switch strings.TrimSpace(code) {
  1958. case opsCodeInsufficientBalance:
  1959. return "billing_error"
  1960. case opsCodeUsageLimitExceeded, opsCodeSubscriptionNotFound, opsCodeSubscriptionInvalid:
  1961. return "subscription_error"
  1962. default:
  1963. return "api_error"
  1964. }
  1965. }
  1966. func classifyOpsPhase(errType, message, code string) string {
  1967. msg := strings.ToLower(message)
  1968. // Standardized phases: request|auth|account_auth|routing|upstream|network|internal
  1969. // Map billing/concurrency/response => request; scheduling => routing.
  1970. if isOpsClientAuthError(code, msg) {
  1971. return "auth"
  1972. }
  1973. if isOpsLocalBusinessLimitError(code, msg) {
  1974. return "request"
  1975. }
  1976. switch errType {
  1977. case "authentication_error":
  1978. return "auth"
  1979. case "billing_error", "subscription_error":
  1980. return "request"
  1981. case "rate_limit_error":
  1982. if strings.Contains(msg, "concurrency") || strings.Contains(msg, "pending") || strings.Contains(msg, "queue") {
  1983. return "request"
  1984. }
  1985. return "upstream"
  1986. case "invalid_request_error", "permission_error", "forbidden_error", "not_found_error", "model_not_found":
  1987. return "request"
  1988. case "upstream_error", "overloaded_error":
  1989. return "upstream"
  1990. case "api_error":
  1991. if isOpsNoAvailableAccountMessage(msg) {
  1992. return "routing"
  1993. }
  1994. return "internal"
  1995. default:
  1996. return "internal"
  1997. }
  1998. }
  1999. func classifyOpsSeverity(errType string, status int) string {
  2000. switch errType {
  2001. case "invalid_request_error", "authentication_error", "permission_error", "forbidden_error", "not_found_error", "model_not_found", "billing_error", "subscription_error":
  2002. return "P3"
  2003. }
  2004. if status >= 500 {
  2005. return "P1"
  2006. }
  2007. if status == 429 {
  2008. return "P1"
  2009. }
  2010. if status >= 400 {
  2011. return "P2"
  2012. }
  2013. return "P3"
  2014. }
  2015. func classifyOpsErrorLog(c *gin.Context, errType, message, code string, status int) (phase string, isBusinessLimited bool, errorOwner string, errorSource string) {
  2016. phase = classifyOpsPhase(errType, message, code)
  2017. routingCapacityLimited := isOpsRoutingCapacityLimited(c)
  2018. clientBusinessLimited := service.HasOpsClientBusinessLimited(c)
  2019. localModelConfiguration := clientBusinessLimited && service.OpsClientBusinessLimitedReason(c) == service.OpsClientBusinessLimitedReasonLocalModelConfiguration
  2020. // 分组模型白名单的入口拒绝发生在调度之前(本地面请求策略,业务限流原因与
  2021. // 账号模型映射复用 local_model_configuration):保持 classifyOpsPhase 的
  2022. // 自然分类(not_found/invalid_request → request,owner=client),不落到
  2023. // routing;上游归因仍由 suppressOpsUpstreamAttributionForLocalModelConfiguration 清空。
  2024. ingressModelNotAllowed := false
  2025. if reason, rejected := middleware2.GetIngressRejectReason(c); rejected && reason == middleware2.IngressRejectModelNotAllowed {
  2026. ingressModelNotAllowed = true
  2027. }
  2028. upstreamError := hasOpsUpstreamErrorContext(c)
  2029. accountAuthFailure := hasOpsAccountAuthFailure(c)
  2030. if localModelConfiguration && !ingressModelNotAllowed {
  2031. phase = "routing"
  2032. } else if accountAuthFailure && !routingCapacityLimited {
  2033. phase = "account_auth"
  2034. } else if upstreamError && !routingCapacityLimited {
  2035. phase = "upstream"
  2036. }
  2037. if clientBusinessLimited && !upstreamError && !routingCapacityLimited && !localModelConfiguration {
  2038. phase = "auth"
  2039. }
  2040. if routingCapacityLimited {
  2041. phase = "routing"
  2042. }
  2043. msg := strings.ToLower(message)
  2044. effectiveUpstreamError := upstreamError && !localModelConfiguration
  2045. localClientAuthError := !effectiveUpstreamError && phase == "auth" && isOpsClientAuthError(code, msg)
  2046. localBusinessLimited := !effectiveUpstreamError && classifyOpsIsBusinessLimited(errType, phase, code, status, message, localClientAuthError)
  2047. isBusinessLimited = localModelConfiguration || routingCapacityLimited || (clientBusinessLimited && !effectiveUpstreamError) || localBusinessLimited
  2048. errorOwner = classifyOpsErrorOwner(phase, message)
  2049. errorSource = classifyOpsErrorSource(phase, message)
  2050. return phase, isBusinessLimited, errorOwner, errorSource
  2051. }
  2052. func classifyOpsIsBusinessLimited(errType, phase, code string, status int, message string, localClientAuthError ...bool) bool {
  2053. if len(localClientAuthError) > 0 && localClientAuthError[0] {
  2054. return true
  2055. }
  2056. if isOpsLocalBusinessLimitError(code, strings.ToLower(message)) {
  2057. return true
  2058. }
  2059. if phase == "billing" || phase == "concurrency" {
  2060. // SLA/错误率排除“用户级业务限制”
  2061. return true
  2062. }
  2063. // Avoid treating upstream rate limits as business-limited.
  2064. if errType == "rate_limit_error" && strings.Contains(strings.ToLower(message), "upstream") {
  2065. return false
  2066. }
  2067. _ = status
  2068. return false
  2069. }
  2070. func isOpsClientAuthError(code string, msg string) bool {
  2071. switch strings.TrimSpace(code) {
  2072. case opsCodeInvalidAPIKey,
  2073. opsCodeAPIKeyRequired,
  2074. opsCodeAPIKeyExpired,
  2075. opsCodeAPIKeyDisabled,
  2076. opsCodeUserNotFound,
  2077. opsCodeUserInactive,
  2078. opsCodeGroupDeleted,
  2079. opsCodeGroupDisabled:
  2080. return true
  2081. }
  2082. return strings.Contains(msg, "invalid api key") ||
  2083. strings.Contains(msg, "api key is required") ||
  2084. strings.Contains(msg, "api key is disabled") ||
  2085. strings.Contains(msg, "user associated with api key not found") ||
  2086. strings.Contains(msg, "user account is not active") ||
  2087. strings.Contains(msg, "api key 所属分组已删除") ||
  2088. strings.Contains(msg, "api key 所属分组已停用") ||
  2089. strings.Contains(msg, "api key is not assigned to any group")
  2090. }
  2091. func isOpsLocalBusinessLimitError(code string, msg string) bool {
  2092. switch strings.TrimSpace(code) {
  2093. case opsCodeInsufficientBalance,
  2094. opsCodeUsageLimitExceeded,
  2095. opsCodeSubscriptionNotFound,
  2096. opsCodeSubscriptionInvalid,
  2097. opsCodeAPIKeyQuotaExhausted,
  2098. opsCodeAPIKeyQueryDeprecated:
  2099. return true
  2100. }
  2101. return strings.Contains(msg, "api key in query parameter is deprecated") ||
  2102. strings.Contains(msg, "query parameter api_key is deprecated") ||
  2103. strings.Contains(msg, "no active subscription found for this group") ||
  2104. strings.Contains(msg, "subscription is invalid or expired") ||
  2105. strings.Contains(msg, opsErrInsufficientBalance) ||
  2106. strings.Contains(msg, "insufficient account balance") ||
  2107. strings.Contains(msg, "api key group platform is not gemini") ||
  2108. strings.Contains(msg, "api key 额度已用完") ||
  2109. strings.Contains(msg, "api key 5小时限额已用完") ||
  2110. strings.Contains(msg, "api key 日限额已用完") ||
  2111. strings.Contains(msg, "api key 7天限额已用完") ||
  2112. strings.Contains(msg, "daily usage limit exceeded") ||
  2113. strings.Contains(msg, "weekly usage limit exceeded") ||
  2114. strings.Contains(msg, "monthly usage limit exceeded") ||
  2115. strings.Contains(msg, "usage quota exhausted for this platform") ||
  2116. strings.Contains(msg, "requests-per-minute limit exceeded") ||
  2117. strings.Contains(msg, "too many pending requests") ||
  2118. strings.Contains(msg, "concurrency limit exceeded") ||
  2119. strings.Contains(msg, "image generation concurrency limit exceeded") ||
  2120. strings.Contains(msg, "this group is restricted to claude code clients") ||
  2121. strings.Contains(msg, "this group does not allow /v1/messages dispatch") ||
  2122. strings.Contains(msg, "image generation is not enabled for this group") ||
  2123. (strings.Contains(msg, "reasoning effort") && strings.Contains(msg, "exceeds this group's limit")) ||
  2124. strings.Contains(msg, "token counting is not supported for this platform") ||
  2125. strings.Contains(msg, "images api is not supported for this platform") ||
  2126. (strings.Contains(msg, "model ") && strings.Contains(msg, " not in whitelist")) ||
  2127. (strings.Contains(msg, "beta feature ") && strings.Contains(msg, " is not allowed")) ||
  2128. (strings.Contains(msg, "openai service_tier=") && strings.Contains(msg, " is not allowed for model")) ||
  2129. strings.Contains(msg, "this account only allows codex official clients") ||
  2130. strings.Contains(msg, "openai wsv1 is temporarily unsupported") ||
  2131. strings.Contains(msg, "openai codex passthrough requires a non-empty instructions field")
  2132. }
  2133. func hasOpsUpstreamErrorContext(c *gin.Context) bool {
  2134. if c == nil {
  2135. return false
  2136. }
  2137. if v, ok := c.Get(service.OpsUpstreamStatusCodeKey); ok {
  2138. switch code := v.(type) {
  2139. case int:
  2140. if code > 0 {
  2141. return true
  2142. }
  2143. case int64:
  2144. if code > 0 {
  2145. return true
  2146. }
  2147. }
  2148. }
  2149. if v, ok := c.Get(service.OpsUpstreamErrorsKey); ok {
  2150. if events, ok := v.([]*service.OpsUpstreamErrorEvent); ok && len(events) > 0 {
  2151. return true
  2152. }
  2153. }
  2154. return false
  2155. }
  2156. func hasOpsAccountAuthFailure(c *gin.Context) bool {
  2157. if c == nil {
  2158. return false
  2159. }
  2160. if v, ok := c.Get(service.OpsUpstreamErrorsKey); ok {
  2161. if events, ok := v.([]*service.OpsUpstreamErrorEvent); ok {
  2162. for i := len(events) - 1; i >= 0; i-- {
  2163. if events[i] != nil {
  2164. return events[i].Stage == string(service.GatewayFailureStageAccountAuth)
  2165. }
  2166. }
  2167. }
  2168. }
  2169. return false
  2170. }
  2171. func isOpsNoAvailableAccountMessage(message string) bool {
  2172. msg := strings.ToLower(message)
  2173. return strings.Contains(msg, opsErrNoAvailableAccounts) ||
  2174. strings.Contains(msg, "no available account") ||
  2175. strings.Contains(msg, "no available gemini accounts") ||
  2176. strings.Contains(msg, "no available openai accounts") ||
  2177. strings.Contains(msg, "no available compatible accounts")
  2178. }
  2179. func classifyOpsErrorOwner(phase string, message string) string {
  2180. // Standardized owners: client|provider|platform
  2181. switch phase {
  2182. case "upstream", "network":
  2183. return "provider"
  2184. case "account_auth":
  2185. return "provider"
  2186. case "request", "auth":
  2187. return "client"
  2188. case "routing", "internal":
  2189. return "platform"
  2190. default:
  2191. if strings.Contains(strings.ToLower(message), "upstream") {
  2192. return "provider"
  2193. }
  2194. return "platform"
  2195. }
  2196. }
  2197. func classifyOpsErrorSource(phase string, message string) string {
  2198. // Standardized sources: client_request|upstream_http|gateway
  2199. switch phase {
  2200. case "upstream":
  2201. return "upstream_http"
  2202. case "account_auth":
  2203. return "gateway"
  2204. case "network":
  2205. return "gateway"
  2206. case "request", "auth":
  2207. return "client_request"
  2208. case "routing", "internal":
  2209. return "gateway"
  2210. default:
  2211. if strings.Contains(strings.ToLower(message), "upstream") {
  2212. return "upstream_http"
  2213. }
  2214. return "gateway"
  2215. }
  2216. }
  2217. func truncateString(s string, max int) string {
  2218. if max <= 0 {
  2219. return ""
  2220. }
  2221. if len(s) <= max {
  2222. return s
  2223. }
  2224. cut := s[:max]
  2225. // Ensure truncation does not split multi-byte characters.
  2226. for len(cut) > 0 && !utf8.ValidString(cut) {
  2227. cut = cut[:len(cut)-1]
  2228. }
  2229. return cut
  2230. }
  2231. func strconvItoa(v int) string {
  2232. return strconv.Itoa(v)
  2233. }
  2234. // shouldSkipOpsErrorLog determines if an error should be skipped from logging based on settings.
  2235. // Returns true for errors that should be filtered according to OpsAdvancedSettings.
  2236. func shouldSkipOpsErrorLog(ctx context.Context, ops *service.OpsService, message, body, requestPath string) bool {
  2237. if ops == nil {
  2238. return false
  2239. }
  2240. // Get advanced settings to check filter configuration
  2241. _ = ctx
  2242. settings := ops.OpsAdvancedSettingsSnapshot()
  2243. msgLower := strings.ToLower(message)
  2244. bodyLower := strings.ToLower(body)
  2245. // Check if count_tokens errors should be ignored
  2246. if settings.IgnoreCountTokensErrors && isTokenCountRequestPath(requestPath) {
  2247. return true
  2248. }
  2249. // Check if context canceled errors should be ignored (client disconnects)
  2250. if settings.IgnoreContextCanceled {
  2251. if strings.Contains(msgLower, opsErrContextCanceled) || strings.Contains(bodyLower, opsErrContextCanceled) {
  2252. return true
  2253. }
  2254. }
  2255. // Check if "no available accounts" errors should be ignored
  2256. if settings.IgnoreNoAvailableAccounts {
  2257. if strings.Contains(msgLower, opsErrNoAvailableAccounts) || strings.Contains(bodyLower, opsErrNoAvailableAccounts) {
  2258. return true
  2259. }
  2260. }
  2261. // Check if invalid/missing API key errors should be ignored (user misconfiguration)
  2262. if settings.IgnoreInvalidApiKeyErrors {
  2263. if strings.Contains(bodyLower, opsErrInvalidAPIKey) || strings.Contains(bodyLower, opsErrAPIKeyRequired) {
  2264. return true
  2265. }
  2266. }
  2267. // Check if insufficient balance errors should be ignored
  2268. if settings.IgnoreInsufficientBalanceErrors {
  2269. if strings.Contains(bodyLower, opsErrInsufficientBalance) || strings.Contains(bodyLower, opsErrInsufficientAccountBalance) ||
  2270. strings.Contains(bodyLower, opsErrInsufficientQuota) ||
  2271. strings.Contains(msgLower, opsErrInsufficientBalance) || strings.Contains(msgLower, opsErrInsufficientAccountBalance) {
  2272. return true
  2273. }
  2274. }
  2275. return false
  2276. }
  2277. // shouldSkipOpsErrorLogForCyber:cyber_policy 命中的请求由 recordCyberPolicyIfMarked
  2278. // 统一落一条 status=403 的错误请求,故中间件跳过自身落库,避免双写。
  2279. func shouldSkipOpsErrorLogForCyber(c *gin.Context) bool {
  2280. return service.GetOpsCyberPolicy(c) != nil
  2281. }