95 lines
2.9 KiB
JavaScript
95 lines
2.9 KiB
JavaScript
/**
|
|
* @fileoverview Correlation / trace IDs for failure tracing across HTTP and NATS.
|
|
* @module debug/trace
|
|
*/
|
|
|
|
import { createDebugger } from './logger.js';
|
|
import {
|
|
traceStorage,
|
|
generateTraceId,
|
|
getTraceId,
|
|
getTraceContext,
|
|
} from './trace-context.js';
|
|
|
|
export { generateTraceId, getTraceId, getTraceContext };
|
|
|
|
const log = createDebugger('trace');
|
|
|
|
/**
|
|
* Run `fn` within a trace context. Nested calls inherit the same `traceId`
|
|
* unless `traceId` is explicitly overridden.
|
|
*
|
|
* @template T
|
|
* @param {object|(() => T|Promise<T>)} options - Options or the callback itself.
|
|
* @param {string} [options.traceId] - Existing id (e.g. from `X-Trace-Id` header).
|
|
* @param {string} [options.span] - Human label for this span (logged when debug on).
|
|
* @param {() => T|Promise<T>} [fn] - Work to execute inside the context when options is an object.
|
|
* @returns {T|Promise<T>} Return value of `fn`.
|
|
*
|
|
* @example
|
|
* await withTrace({ span: 'timestamp.wait', traceId: req.headers['x-trace-id'] }, async () => {
|
|
* // getTraceId() is stable here and in awaited children
|
|
* });
|
|
*/
|
|
export function withTrace(options, fn) {
|
|
const opts = typeof options === 'function' ? {} : options ?? {};
|
|
const callback = typeof options === 'function' ? options : fn;
|
|
|
|
if (typeof callback !== 'function') {
|
|
throw new TypeError('withTrace requires a function to execute');
|
|
}
|
|
|
|
const parent = traceStorage.getStore();
|
|
const traceId = opts.traceId || parent?.traceId || generateTraceId();
|
|
const span = opts.span || parent?.span || 'root';
|
|
|
|
const ctx = { traceId, span };
|
|
|
|
return traceStorage.run(ctx, () => {
|
|
log.debug('span enter', { span });
|
|
try {
|
|
const result = callback();
|
|
if (result && typeof result.then === 'function') {
|
|
return result.then(
|
|
(value) => {
|
|
log.debug('span exit', { span, ok: true });
|
|
return value;
|
|
},
|
|
(err) => {
|
|
log.debug('span exit', { span, ok: false, error: err?.message });
|
|
throw err;
|
|
},
|
|
);
|
|
}
|
|
log.debug('span exit', { span, ok: true });
|
|
return result;
|
|
} catch (err) {
|
|
log.debug('span exit', { span, ok: false, error: err?.message });
|
|
throw err;
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Express middleware that establishes a trace context per request.
|
|
*
|
|
* Reads `X-Trace-Id` when present; otherwise generates a new id.
|
|
* Sets `req.traceId` and response header `X-Trace-Id`.
|
|
*
|
|
* @param {import('express').Request} req
|
|
* @param {import('express').Response} res
|
|
* @param {import('express').NextFunction} next
|
|
* @returns {void}
|
|
*/
|
|
export function traceMiddleware(req, res, next) {
|
|
const incoming = req.headers['x-trace-id'];
|
|
const traceId =
|
|
typeof incoming === 'string' && incoming.trim() ? incoming.trim() : generateTraceId();
|
|
|
|
req.traceId = traceId;
|
|
res.setHeader('X-Trace-Id', traceId);
|
|
|
|
withTrace({ traceId, span: `${req.method} ${req.path}` }, () => {
|
|
next();
|
|
});
|
|
}
|