Files
iios/packages/iios-service/src/observability/trace.middleware.ts
T
maaz519 0f923303d3 feat(p9): request-scoped trace context + middleware + structured logger
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>
2026-07-03 00:50:15 +05:30

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();
});
}