From 31c32d99daae4e0b06825bcb81bacd5878c65c34 Mon Sep 17 00:00:00 2001 From: Daniel C Date: Wed, 12 Nov 2025 15:50:27 -0800 Subject: [PATCH 1/6] Add Request/Response logging --- src/middlewares/log/index.ts | 86 +++++++++++++++++++++++++++++++----- 1 file changed, 76 insertions(+), 10 deletions(-) diff --git a/src/middlewares/log/index.ts b/src/middlewares/log/index.ts index 57cbd9a2e..68962520e 100644 --- a/src/middlewares/log/index.ts +++ b/src/middlewares/log/index.ts @@ -4,6 +4,13 @@ import { getRuntimeKey } from 'hono/adapter'; let logId = 0; const MAX_RESPONSE_LENGTH = 100000; +// Log level control via environment variable +// Set LOG_LEVEL=verbose for detailed console logs +// Set LOG_LEVEL=minimal for basic logs only +// Set LOG_LEVEL=silent to disable console logs +// Default to verbose logging if not set or set to an invalid value +const LOG_LEVEL = process.env.LOG_LEVEL || 'verbose'; + // Map to store all connected log clients const logClients: Map = new Map(); @@ -72,16 +79,75 @@ async function processLog(c: Context, start: number) { console.error('Error processing log:', error); } - await broadcastLog( - JSON.stringify({ - time: new Date().toLocaleString(), - method: c.req.method, - endpoint: c.req.url.split(':8787')[1], - status: c.res.status, - duration: ms, - requestOptions: requestOptionsArray, - }) - ); + const now = new Date(); + const timestamp = now.toLocaleString('en-US', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + timeZoneName: 'short', + }); + + // Extract the endpoint path from the URL + const url = new URL(c.req.url); + const endpoint = url.pathname + url.search; + + const logData = { + time: timestamp, + method: c.req.method, + endpoint: endpoint, + status: c.res.status, + duration: ms, + requestOptions: requestOptionsArray, + }; + + // Log to console for STDOUT visibility based on LOG_LEVEL + if (LOG_LEVEL !== 'silent') { + if (LOG_LEVEL === 'minimal') { + // Minimal logging: just method, endpoint, status, duration + console.log( + `[${logData.time}] ${logData.method} ${logData.endpoint} - ${logData.status} (${ms}ms)` + ); + } else { + // Verbose logging: full details (default) + console.log('\n' + '='.repeat(80)); + console.log(`[${logData.time}] ${logData.method} ${logData.endpoint}`); + console.log(`Status: ${logData.status} | Duration: ${ms}ms`); + console.log('-'.repeat(80)); + + if (requestOptionsArray[0]) { + const option = requestOptionsArray[0]; + + // Log provider and model info + if (option.providerOptions) { + console.log('Provider:', option.providerOptions?.provider || 'N/A'); + console.log( + 'Request URL:', + option.providerOptions?.requestURL || 'N/A' + ); + } + + // Log request parameters + if (option.requestParams) { + console.log('\nRequest Parameters:'); + console.log(JSON.stringify(option.requestParams, null, 2)); + } + + // Log response + if (option.response) { + console.log('\nResponse:'); + console.log(JSON.stringify(option.response, null, 2)); + } + } + + console.log('='.repeat(80) + '\n'); + } + } + + // Broadcast to SSE clients (for web UI) + await broadcastLog(JSON.stringify(logData)); } export const logger = () => { From a764b09850dccdccc6f3a3b062c5fa7e06676826 Mon Sep 17 00:00:00 2001 From: Daniel C Date: Wed, 12 Nov 2025 15:55:59 -0800 Subject: [PATCH 2/6] Log all attempts --- src/middlewares/log/index.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/middlewares/log/index.ts b/src/middlewares/log/index.ts index 68962520e..1842a3821 100644 --- a/src/middlewares/log/index.ts +++ b/src/middlewares/log/index.ts @@ -117,8 +117,13 @@ async function processLog(c: Context, start: number) { console.log(`Status: ${logData.status} | Duration: ${ms}ms`); console.log('-'.repeat(80)); - if (requestOptionsArray[0]) { - const option = requestOptionsArray[0]; + // Log all attempts (useful for retries, fallbacks, load balancing) + requestOptionsArray.forEach((option: any, index: number) => { + if (requestOptionsArray.length > 1) { + console.log( + `\n--- Attempt ${index + 1} of ${requestOptionsArray.length} ---` + ); + } // Log provider and model info if (option.providerOptions) { @@ -140,7 +145,7 @@ async function processLog(c: Context, start: number) { console.log('\nResponse:'); console.log(JSON.stringify(option.response, null, 2)); } - } + }); console.log('='.repeat(80) + '\n'); } From e453c97e123476f5adc1fc201a455c9618db22f5 Mon Sep 17 00:00:00 2001 From: Daniel C Date: Wed, 12 Nov 2025 16:14:41 -0800 Subject: [PATCH 3/6] Also log client <-> gateway interaction --- src/middlewares/log/index.ts | 41 +++++++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/src/middlewares/log/index.ts b/src/middlewares/log/index.ts index 1842a3821..c8a08f33c 100644 --- a/src/middlewares/log/index.ts +++ b/src/middlewares/log/index.ts @@ -117,7 +117,31 @@ async function processLog(c: Context, start: number) { console.log(`Status: ${logData.status} | Duration: ${ms}ms`); console.log('-'.repeat(80)); + // Log incoming client request (Client -> Gateway) + console.log('\nINCOMING REQUEST (Client -> Gateway):'); + console.log('\nClient Headers:'); + const headers: Record = {}; + c.req.raw.headers.forEach((value, key) => { + headers[key] = value; + }); + console.log(JSON.stringify(headers, null, 2)); + + // Log original request body if available + if (requestOptionsArray[0]?.finalUntransformedRequest?.body) { + console.log('\nClient Request Body:'); + console.log( + JSON.stringify( + requestOptionsArray[0].finalUntransformedRequest.body, + null, + 2 + ) + ); + } + + console.log('\n' + '-'.repeat(80)); + // Log all attempts (useful for retries, fallbacks, load balancing) + console.log('\nOUTGOING REQUESTS (Gateway -> Provider):'); requestOptionsArray.forEach((option: any, index: number) => { if (requestOptionsArray.length > 1) { console.log( @@ -140,14 +164,25 @@ async function processLog(c: Context, start: number) { console.log(JSON.stringify(option.requestParams, null, 2)); } - // Log response + // Log response from provider if (option.response) { - console.log('\nResponse:'); + console.log('\nProvider Response:'); console.log(JSON.stringify(option.response, null, 2)); } }); - console.log('='.repeat(80) + '\n'); + // Log final response back to client (Gateway -> Client) + console.log('\n' + '-'.repeat(80)); + console.log('\nOUTGOING RESPONSE (Gateway -> Client):'); + console.log(`\nStatus: ${c.res.status}`); + console.log('\nResponse Headers:'); + const responseHeaders: Record = {}; + c.res.headers.forEach((value, key) => { + responseHeaders[key] = value; + }); + console.log(JSON.stringify(responseHeaders, null, 2)); + + console.log('\n' + '='.repeat(80) + '\n'); } } From 3d3c9c5e03623a235527021849780cab9dac93f8 Mon Sep 17 00:00:00 2001 From: Daniel C Date: Wed, 12 Nov 2025 16:22:04 -0800 Subject: [PATCH 4/6] Log final gateway -> client body response --- src/middlewares/log/index.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/middlewares/log/index.ts b/src/middlewares/log/index.ts index c8a08f33c..3d4d851ef 100644 --- a/src/middlewares/log/index.ts +++ b/src/middlewares/log/index.ts @@ -63,17 +63,19 @@ async function processLog(c: Context, start: number) { return; } + // Capture the final response body sent to the client + let finalClientResponse = null; try { - const response = requestOptionsArray[0].requestParams.stream + finalClientResponse = requestOptionsArray[0].requestParams.stream ? { message: 'The response was a stream.' } : await c.res.clone().json(); - const responseString = JSON.stringify(response); + const responseString = JSON.stringify(finalClientResponse); if (responseString.length > MAX_RESPONSE_LENGTH) { requestOptionsArray[0].response = responseString.substring(0, MAX_RESPONSE_LENGTH) + '...'; } else { - requestOptionsArray[0].response = response; + requestOptionsArray[0].response = finalClientResponse; } } catch (error) { console.error('Error processing log:', error); @@ -182,6 +184,12 @@ async function processLog(c: Context, start: number) { }); console.log(JSON.stringify(responseHeaders, null, 2)); + // Log the actual response body sent to client + if (finalClientResponse) { + console.log('\nResponse Body:'); + console.log(JSON.stringify(finalClientResponse, null, 2)); + } + console.log('\n' + '='.repeat(80) + '\n'); } } From 4e563b5fcde17f839d18b696138f064aeecbaf94 Mon Sep 17 00:00:00 2001 From: Daniel C Date: Wed, 12 Nov 2025 16:35:37 -0800 Subject: [PATCH 5/6] Fix bug where it is showing the first try instead of the last/returned try --- src/middlewares/log/index.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/middlewares/log/index.ts b/src/middlewares/log/index.ts index 3d4d851ef..04d4e571a 100644 --- a/src/middlewares/log/index.ts +++ b/src/middlewares/log/index.ts @@ -64,18 +64,22 @@ async function processLog(c: Context, start: number) { } // Capture the final response body sent to the client + // Note: requestOptionsArray is ordered chronologically (first attempt at [0], last at [-1]) + // The last element contains the final successful (or failed) response + const lastAttemptIndex = requestOptionsArray.length - 1; let finalClientResponse = null; try { - finalClientResponse = requestOptionsArray[0].requestParams.stream + finalClientResponse = requestOptionsArray[lastAttemptIndex].requestParams + .stream ? { message: 'The response was a stream.' } : await c.res.clone().json(); const responseString = JSON.stringify(finalClientResponse); if (responseString.length > MAX_RESPONSE_LENGTH) { - requestOptionsArray[0].response = + requestOptionsArray[lastAttemptIndex].response = responseString.substring(0, MAX_RESPONSE_LENGTH) + '...'; } else { - requestOptionsArray[0].response = finalClientResponse; + requestOptionsArray[lastAttemptIndex].response = finalClientResponse; } } catch (error) { console.error('Error processing log:', error); @@ -129,6 +133,7 @@ async function processLog(c: Context, start: number) { console.log(JSON.stringify(headers, null, 2)); // Log original request body if available + // Note: Client request body is the same across all attempts, so we can use any element if (requestOptionsArray[0]?.finalUntransformedRequest?.body) { console.log('\nClient Request Body:'); console.log( From bc7daf02604864297217d6689e9c2ff26921b10b Mon Sep 17 00:00:00 2001 From: Daniel C Date: Wed, 12 Nov 2025 16:36:52 -0800 Subject: [PATCH 6/6] Edit comment --- src/middlewares/log/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/middlewares/log/index.ts b/src/middlewares/log/index.ts index 04d4e571a..789b0976f 100644 --- a/src/middlewares/log/index.ts +++ b/src/middlewares/log/index.ts @@ -199,7 +199,7 @@ async function processLog(c: Context, start: number) { } } - // Broadcast to SSE clients (for web UI) + // Broadcast to SSE clients await broadcastLog(JSON.stringify(logData)); }