功能说明
该示例解决分布式日志里“哪条日志属于哪个请求”的问题。入口用 withCorrelationId 建立上下文,缺少 id 时自动生成一个 UUID。此后在同一个异步调用链中的任何位置都能通过 currentCorrelationId 取到它,日志函数把它写入每条结构化记录,从而把同一请求散落在多个模块的日志串起来。
关键代码
AsyncLocalStorage 基于 async_hooks,能在 promise 与回调链中透传存储,无需层层传参。withCorrelationId 用 context.run 执行回调,存储只在该回调及其异步后代中可见。日志函数读取当前存储并把关联 ID 合并进记录,若不在上下文中则为 undefined,便于识别未纳入追踪的代码路径。
使用注意
若入口已从上游请求头(如 traceparent)获得追踪标识,应把该值作为 id 传入,实现跨服务串联,而不是每次另生成一个 UUID 造成断链。AsyncLocalStorage 有轻微的每异步操作开销,在高频路径上应避免在回调内频繁创建新的存储。进程外的边界不会自动透传,调用下游时必须显式把关联 ID 放进请求头,接收方再建立对应上下文。
代码示例
import { AsyncLocalStorage } from 'node:async_hooks';
import { randomUUID } from 'node:crypto';
export const context = new AsyncLocalStorage();
export function withCorrelationId(id, fn) {
const correlationId = id || randomUUID();
return context.run({ correlationId }, fn);
}
export function currentCorrelationId() {
const store = context.getStore();
return store ? store.correlationId : undefined;
}
export function log(level, message, extra = {}) {
const correlationId = currentCorrelationId();
const record = { level, message, correlationId, ...extra };
process.stdout.write(JSON.stringify(record) + '\n');
}