diff --git a/test/api-server_test.ts b/test/api-server_test.ts index 2a7fd2f..6682f80 100644 --- a/test/api-server_test.ts +++ b/test/api-server_test.ts @@ -64,6 +64,7 @@ Deno.test('ApiServer serves health metrics and authenticated model listings', as assertEquals(healthResponse.status, 200); assertEquals(healthBody.status, 'ok'); assertEquals(healthBody.models, 1); + assertEquals(typeof healthResponse.headers.get('x-request-id'), 'string'); const metricsResponse = await fetch(`http://127.0.0.1:${port}/metrics`); const metricsBody = await metricsResponse.text(); @@ -79,12 +80,14 @@ Deno.test('ApiServer serves health metrics and authenticated model listings', as const authenticatedModels = await fetch(`http://127.0.0.1:${port}/v1/models`, { headers: { Authorization: 'Bearer valid-key', + 'X-Request-Id': 'req-test-models', }, }); const authenticatedBody = await authenticatedModels.json(); assertEquals(authenticatedModels.status, 200); assertEquals(authenticatedBody.object, 'list'); assertEquals(authenticatedBody.data[0].id, 'meta-llama/Llama-3.1-8B-Instruct'); + assertEquals(authenticatedModels.headers.get('x-request-id'), 'req-test-models'); const metricsAfterRequests = await fetch(`http://127.0.0.1:${port}/metrics`); const metricsAfterRequestsBody = await metricsAfterRequests.text(); diff --git a/ts/api/server.ts b/ts/api/server.ts index eefdfeb..520f89d 100644 --- a/ts/api/server.ts +++ b/ts/api/server.ts @@ -123,6 +123,7 @@ export class ApiServer { res: http.ServerResponse, ): Promise { const startTime = Date.now(); + const requestId = this.ensureRequestId(req, res); // Set CORS headers if enabled if (this.config.cors) { @@ -177,7 +178,7 @@ export class ApiServer { // Log request const duration = Date.now() - startTime; this.recordRequest(path, res.statusCode); - logger.dim(`${req.method} ${path} - ${res.statusCode} (${duration}ms)`); + logger.dim(`[${requestId}] ${req.method} ${path} - ${res.statusCode} (${duration}ms)`); } /** @@ -405,6 +406,20 @@ export class ApiServer { metric.set(path, (metric.get(path) || 0) + 1); } + private ensureRequestId(req: http.IncomingMessage, res: http.ServerResponse): string { + const existing = typeof req.headers['x-request-id'] === 'string' + ? req.headers['x-request-id'] + : undefined; + const requestId = existing || this.generateRequestId(); + req.headers['x-request-id'] = requestId; + res.setHeader('X-Request-Id', requestId); + return requestId; + } + + private generateRequestId(): string { + return `req-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`; + } + private escapeMetricLabel(value: string): string { return value.replaceAll('\\', '\\\\').replaceAll('"', '\\"'); }