@@ -522,14 +522,58 @@ async function fetchWithHeaderTimeout(
522522 }
523523}
524524
525- const requestLog : { timestamp : number ; model : string ; provider : string ; status : number ; durationMs : number } [ ] = [ ] ;
525+ export interface RequestLogEntry {
526+ requestId : string ;
527+ timestamp : number ;
528+ model : string ;
529+ provider : string ;
530+ status : number ;
531+ durationMs : number ;
532+ errorCode ?: string ;
533+ }
534+
535+ const requestLog : RequestLogEntry [ ] = [ ] ;
526536const MAX_LOG_SIZE = 200 ;
537+ let requestLogSeq = 0 ;
527538
528- function addRequestLog ( entry : typeof requestLog [ number ] ) {
539+ function addRequestLog ( entry : RequestLogEntry ) {
529540 requestLog . push ( entry ) ;
530541 if ( requestLog . length > MAX_LOG_SIZE ) requestLog . shift ( ) ;
531542}
532543
544+ export function nextRequestLogId ( timestamp = Date . now ( ) ) : string {
545+ requestLogSeq = ( requestLogSeq % 1_000_000 ) + 1 ;
546+ return `ocx-${ timestamp . toString ( 36 ) } -${ requestLogSeq . toString ( 36 ) } ` ;
547+ }
548+
549+ export function requestLogErrorCode ( status : number ) : string | undefined {
550+ if ( status >= 200 && status < 400 ) return undefined ;
551+ if ( status === 400 || status === 409 ) return "invalid_request_error" ;
552+ if ( status === 401 || status === 403 ) return "invalid_api_key" ;
553+ if ( status === 429 ) return "rate_limit_exceeded" ;
554+ if ( status === 503 ) return "server_is_overloaded" ;
555+ if ( status >= 500 ) return "upstream_server_error" ;
556+ return `http_${ status } ` ;
557+ }
558+
559+ export function filterRequestLogs ( logs : RequestLogEntry [ ] , params : URLSearchParams ) : RequestLogEntry [ ] {
560+ let filtered = logs ;
561+ const provider = params . get ( "provider" ) ?. trim ( ) ;
562+ if ( provider ) filtered = filtered . filter ( entry => entry . provider === provider ) ;
563+ const status = params . get ( "status" ) ?. trim ( ) . toLowerCase ( ) ;
564+ if ( status ) {
565+ filtered = / ^ [ 1 - 5 ] x x $ / . test ( status )
566+ ? filtered . filter ( entry => Math . floor ( entry . status / 100 ) === Number ( status [ 0 ] ) )
567+ : filtered . filter ( entry => String ( entry . status ) === status ) ;
568+ }
569+ const tailRaw = params . get ( "tail" ) ?. trim ( ) ;
570+ if ( tailRaw ) {
571+ const tail = Number . parseInt ( tailRaw , 10 ) ;
572+ if ( Number . isFinite ( tail ) && tail > 0 ) filtered = filtered . slice ( - Math . min ( tail , MAX_LOG_SIZE ) ) ;
573+ }
574+ return filtered ;
575+ }
576+
533577/**
534578 * Relay an upstream body verbatim while wiring client-cancel -> upstream.abort(). A body returned
535579 * directly from fetch does NOT propagate the consumer's cancel to a signalled fetch, so a client
@@ -941,7 +985,7 @@ async function handleManagementAPI(req: Request, url: URL, config: OcxConfig): P
941985 }
942986
943987 if ( url . pathname === "/api/logs" && req . method === "GET" ) {
944- return jsonResponse ( requestLog ) ;
988+ return jsonResponse ( filterRequestLogs ( requestLog , url . searchParams ) ) ;
945989 }
946990
947991 if ( url . pathname === "/api/providers" && req . method === "GET" ) {
@@ -1228,14 +1272,18 @@ export function startServer(port?: number) {
12281272 return formatErrorResponse ( 403 , "origin_rejected" , "cross-origin data-plane request blocked" ) ;
12291273 }
12301274 const start = Date . now ( ) ;
1275+ const requestId = nextRequestLogId ( start ) ;
12311276 const logCtx = { model : "unknown" , provider : "unknown" } ;
12321277 const response = await handleResponses ( req , config , logCtx ) ;
1278+ const errorCode = requestLogErrorCode ( response . status ) ;
12331279 addRequestLog ( {
1280+ requestId,
12341281 timestamp : start ,
12351282 model : logCtx . model ,
12361283 provider : logCtx . provider ,
12371284 status : response . status ,
12381285 durationMs : Date . now ( ) - start ,
1286+ ...( errorCode ? { errorCode } : { } ) ,
12391287 } ) ;
12401288 return response ;
12411289 }
0 commit comments