Files
iios/packages/iios-service/src/observability/trace-context.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

44 lines
1.6 KiB
TypeScript

import { AsyncLocalStorage } from 'node:async_hooks';
import { randomBytes, randomUUID } from 'node:crypto';
/**
* Request-scoped trace context (P9 observability). A module-level AsyncLocalStorage
* so singleton services can read the current request's trace id via `currentTraceId()`
* WITHOUT threading it through every method or constructor. The middleware runs each
* request inside `runWithTrace(...)`; async projectors run outside a request and
* simply see `undefined` (their events already carry the trace from the emitting call).
*/
export interface TraceStore {
traceId: string;
correlationId?: string;
}
const als = new AsyncLocalStorage<TraceStore>();
export function runWithTrace<T>(traceId: string, fn: () => T): T {
return als.run({ traceId }, fn);
}
export function currentTraceId(): string | undefined {
return als.getStore()?.traceId;
}
/** A fresh trace id (used when no inbound trace context is present). */
export function newTraceId(): string {
return randomUUID();
}
/** W3C traceparent for a trace id: 00-<32 hex>-<16 hex span>-01. */
export function toTraceparent(traceId: string): string {
const trace = traceId.replace(/-/g, '').padEnd(32, '0').slice(0, 32);
const span = randomBytes(8).toString('hex');
return `00-${trace}-${span}-01`;
}
/** Extract a trace id from an inbound traceparent header (the 2nd field), else undefined. */
export function traceIdFromTraceparent(traceparent?: string): string | undefined {
if (!traceparent) return undefined;
const parts = traceparent.split('-');
return parts.length >= 3 && /^[0-9a-f]{32}$/i.test(parts[1]) ? parts[1] : undefined;
}