0f923303d3
Task O.1: AsyncLocalStorage trace context (currentTraceId/runWithTrace/toTraceparent) read by singleton services with no DI churn; Express traceMiddleware derives a trace id per request (x-trace-id → traceparent → generated), echoes x-trace-id on the response, runs the request in-context, and logs a structured http.request line on finish. Minimal JSON structured logger stamps traceId on every line. Wired via app.use in main.ts. 4 tests: scope, concurrency isolation, traceparent shape/parse. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
28 lines
1.2 KiB
TypeScript
28 lines
1.2 KiB
TypeScript
import type { Request, Response, NextFunction } from 'express';
|
|
import { runWithTrace, newTraceId, traceIdFromTraceparent } from './trace-context';
|
|
import { logJson } from './logger';
|
|
|
|
/**
|
|
* Express middleware (P9 observability). Derives a trace id per request (inbound
|
|
* x-trace-id → traceparent → generated), echoes it as `x-trace-id` on the response,
|
|
* runs the whole request inside the trace context, and logs a structured line when
|
|
* the response finishes (method, path, status, ms). Wired via app.use() in main.ts.
|
|
*/
|
|
export function traceMiddleware(req: Request, res: Response, next: NextFunction): void {
|
|
const headerTrace = (req.headers['x-trace-id'] as string | undefined)?.trim();
|
|
const traceId = headerTrace || traceIdFromTraceparent(req.headers['traceparent'] as string | undefined) || newTraceId();
|
|
res.setHeader('x-trace-id', traceId);
|
|
const startedAt = Date.now();
|
|
runWithTrace(traceId, () => {
|
|
res.on('finish', () => {
|
|
logJson('info', 'http.request', {
|
|
method: req.method,
|
|
path: (req.originalUrl || req.url || '').split('?')[0],
|
|
status: res.statusCode,
|
|
ms: Date.now() - startedAt,
|
|
});
|
|
});
|
|
next();
|
|
});
|
|
}
|