1+ import type { Logger } from "../logger" ;
12import {
23 CIRCUIT_COOLDOWN_MS ,
34 CIRCUIT_FAILURE_THRESHOLD ,
@@ -28,22 +29,40 @@ export class TrafficCircuitBreaker {
2829 this . fallbackChains = new Map ( Object . entries ( chains ) ) ;
2930 }
3031
31- resolve ( next : QueuedRequest ) : DispatchDecision | null {
32+ resolve ( next : QueuedRequest , logger ?: Logger ) : DispatchDecision | null {
33+ const circuitLogger = logger ?. child ( { module : "circuit-breaker" } ) ;
3234 const visited = new Set < string > ( ) ;
3335
3436 while ( true ) {
3537 const key = this . buildRateLimitKey ( next . request . metadata ) ;
3638 next . circuitKey = key ;
39+ circuitLogger ?. trace ?.( "Circuit resolve step" , {
40+ circuitKey : key ,
41+ provider : next . request . metadata ?. provider ,
42+ model : next . request . metadata ?. model ,
43+ } ) ;
3744
3845 const model = next . request . metadata ?. model ;
3946 if ( model ) visited . add ( model ) ;
4047
41- const evaluation = this . evaluateCircuitState ( key ) ;
48+ const evaluation = this . evaluateCircuitState ( key , circuitLogger ) ;
4249 next . circuitStatus = evaluation . state ;
50+ circuitLogger ?. debug ?.( "Circuit evaluated" , {
51+ circuitKey : key ,
52+ state : evaluation . state ,
53+ allowRequest : evaluation . allowRequest ,
54+ retryAfterMs : evaluation . retryAfterMs ,
55+ } ) ;
4356
4457 if ( evaluation . allowRequest ) return null ;
4558
46- const fallback = this . findFallbackModel ( next . request . metadata , visited ) ;
59+ const fallback = this . findFallbackModel ( next . request . metadata , visited , circuitLogger ) ;
60+ circuitLogger ?. debug ?.( "Circuit open; attempting fallback" , {
61+ circuitKey : key ,
62+ currentModel : next . request . metadata ?. model ,
63+ fallback,
64+ visitedModels : Array . from ( visited ) ,
65+ } ) ;
4766 if ( ! fallback || ! next . request . createFallbackRequest ) {
4867 next . reject (
4968 new CircuitBreakerOpenError (
@@ -52,39 +71,76 @@ export class TrafficCircuitBreaker {
5271 evaluation . retryAfterMs ,
5372 ) ,
5473 ) ;
74+ circuitLogger ?. warn ?.( "No fallback available; rejecting request" , {
75+ circuitKey : key ,
76+ retryAfterMs : evaluation . retryAfterMs ,
77+ } ) ;
5578 return { kind : "skip" } ;
5679 }
5780
5881 const fallbackRequest = next . request . createFallbackRequest ( fallback ) ;
59- if ( ! fallbackRequest ) return { kind : "skip" } ;
82+ if ( ! fallbackRequest ) {
83+ circuitLogger ?. warn ?.( "createFallbackRequest returned undefined; skipping" , {
84+ circuitKey : key ,
85+ fallback,
86+ } ) ;
87+ return { kind : "skip" } ;
88+ }
6089
6190 next . request = fallbackRequest ;
6291 next . attempt = 1 ;
6392 next . rateLimitKey = undefined ;
6493 next . etaMs = undefined ;
6594 next . circuitKey = undefined ;
6695 next . circuitStatus = undefined ;
96+ circuitLogger ?. debug ?.( "Switched to fallback request" , {
97+ previousCircuitKey : key ,
98+ fallbackModel : fallback ,
99+ } ) ;
67100 }
68101 }
69102
70- markTrial ( item : QueuedRequest ) : void {
103+ markTrial ( item : QueuedRequest , logger ?: Logger ) : void {
104+ const circuitLogger = logger ?. child ( { module : "circuit-breaker" } ) ;
71105 const key = item . circuitKey ;
72106 if ( ! key ) return ;
73107 const state = this . circuitBreakers . get ( key ) ;
74108 if ( state && state . status === "half-open" && ! state . trialInFlight ) {
75109 state . trialInFlight = true ;
110+ circuitLogger ?. debug ?.( "Marked half-open trial in flight" , { circuitKey : key } ) ;
76111 }
77112 }
78113
79- recordSuccess ( metadata ?: TrafficRequestMetadata ) : void {
114+ recordSuccess ( metadata ?: TrafficRequestMetadata , logger ?: Logger ) : void {
115+ const circuitLogger = logger ?. child ( { module : "circuit-breaker" } ) ;
80116 const key = this . buildRateLimitKey ( metadata ) ;
81117 this . circuitBreakers . delete ( key ) ;
118+ circuitLogger ?. debug ?.( "Circuit success; cleared circuit state" , {
119+ circuitKey : key ,
120+ provider : metadata ?. provider ,
121+ model : metadata ?. model ,
122+ } ) ;
82123 }
83124
84- recordFailure ( metadata : TrafficRequestMetadata | undefined , error : unknown ) : void {
85- const status = extractStatusCode ( error ) ;
125+ recordFailure (
126+ metadata : TrafficRequestMetadata | undefined ,
127+ error : unknown ,
128+ logger ?: Logger ,
129+ ) : void {
130+ const circuitLogger = logger ?. child ( { module : "circuit-breaker" } ) ;
131+ const status = extractStatusCode ( error , logger ) ;
132+ circuitLogger ?. debug ?.( "Circuit failure observed" , {
133+ circuitKey : this . buildRateLimitKey ( metadata ) ,
134+ status,
135+ provider : metadata ?. provider ,
136+ model : metadata ?. model ,
137+ } ) ;
86138 if ( ! this . isCircuitBreakerStatus ( status ) ) {
87139 this . circuitBreakers . delete ( this . buildRateLimitKey ( metadata ) ) ;
140+ circuitLogger ?. debug ?.( "Failure not eligible for circuit breaker; cleared circuit state" , {
141+ circuitKey : this . buildRateLimitKey ( metadata ) ,
142+ status,
143+ } ) ;
88144 return ;
89145 }
90146
@@ -106,18 +162,36 @@ export class TrafficCircuitBreaker {
106162 state . status = "open" ;
107163 state . openedAt = now ;
108164 state . trialInFlight = false ;
165+ circuitLogger ?. warn ?.( "Circuit opened" , {
166+ circuitKey : key ,
167+ failureCount : state . failureTimestamps . length ,
168+ threshold : CIRCUIT_FAILURE_THRESHOLD ,
169+ openedAt : state . openedAt ,
170+ } ) ;
109171 }
110172
111173 this . circuitBreakers . set ( key , state ) ;
174+ circuitLogger ?. trace ?.( "Circuit state updated" , {
175+ circuitKey : key ,
176+ status : state . status ,
177+ failureCount : state . failureTimestamps . length ,
178+ windowMs : CIRCUIT_FAILURE_WINDOW_MS ,
179+ } ) ;
112180 }
113181
114- private evaluateCircuitState ( key : string ) : {
182+ private evaluateCircuitState (
183+ key : string ,
184+ logger ?: Logger ,
185+ ) : {
115186 allowRequest : boolean ;
116187 state : CircuitStateStatus ;
117188 retryAfterMs ?: number ;
118189 } {
119190 const state = this . circuitBreakers . get ( key ) ;
120- if ( ! state ) return { allowRequest : true , state : "closed" } ;
191+ if ( ! state ) {
192+ logger ?. trace ?.( "Circuit state missing; allow request" , { circuitKey : key } ) ;
193+ return { allowRequest : true , state : "closed" } ;
194+ }
121195
122196 const now = Date . now ( ) ;
123197
@@ -127,6 +201,7 @@ export class TrafficCircuitBreaker {
127201 state . status = "half-open" ;
128202 state . trialInFlight = false ;
129203 state . failureTimestamps = [ ] ;
204+ logger ?. debug ?.( "Circuit transitioned to half-open" , { circuitKey : key } ) ;
130205 return { allowRequest : true , state : "half-open" } ;
131206 }
132207 return {
@@ -146,14 +221,17 @@ export class TrafficCircuitBreaker {
146221 private findFallbackModel (
147222 metadata : TrafficRequestMetadata | undefined ,
148223 visitedModels : Set < string > ,
224+ logger ?: Logger ,
149225 ) : string | undefined {
150226 const currentModel = metadata ?. model ;
151227 if ( ! currentModel ) {
228+ logger ?. trace ?.( "No current model; no fallback" , { } ) ;
152229 return undefined ;
153230 }
154231
155232 const chain = this . fallbackChains . get ( currentModel ) ;
156233 if ( ! chain ) {
234+ logger ?. trace ?.( "No fallback chain for model" , { currentModel } ) ;
157235 return undefined ;
158236 }
159237
@@ -168,9 +246,14 @@ export class TrafficCircuitBreaker {
168246 model : candidate ,
169247 } ) ;
170248
171- const evaluation = this . evaluateCircuitState ( candidateKey ) ;
249+ const evaluation = this . evaluateCircuitState ( candidateKey , logger ) ;
172250 if ( evaluation . allowRequest ) {
173251 visitedModels . add ( candidate ) ;
252+ logger ?. debug ?.( "Selected fallback model" , {
253+ currentModel,
254+ fallbackModel : candidate ,
255+ fallbackCircuitKey : candidateKey ,
256+ } ) ;
174257 return candidate ;
175258 }
176259 }
0 commit comments