← 中文资源

简体中文版本

关联 ID 异步上下文

用 AsyncLocalStorage 在异步调用间传播关联 ID,让每一行日志都带上请求身份。

阅读英文原文

功能说明

该示例解决分布式日志里“哪条日志属于哪个请求”的问题。入口用 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');
}