import { randomUUID } from 'node:crypto' /** * @typedef {Object} RequestContext * @property {string} traceId 贯穿一次请求的 trace id * @property {Object} fields 累积的结构化字段(如 userId/route) * @property {(extraFields?: Object) => RequestContext} child * 派生子上下文:继承 `traceId`,merge 字段,返回**新** ctx(不可变派生,不污染父) */ /** * 创建请求上下文。入口处一次,或从入站 trace header 续传。 * 不传 `traceId` 时自动生成(`crypto.randomUUID`);传则续用(跨服务/入站续传)。 * * @param {Object} [opts] * @param {string} [opts.traceId] 续传已有 trace_id;省略则自动生成 * @param {Object} [opts.fields] 初始附加字段(如 route) * @returns {RequestContext} */ export function createRequestContext(opts) { const traceId = opts && opts.traceId ? opts.traceId : randomUUID() const fields = opts && opts.fields ? { ...opts.fields } : {} return makeContext(traceId, fields) } /** * 内部工厂:把 traceId + 已固化的 fields 包成不可变 RequestContext。 * @param {string} traceId * @param {Object} fields 调用方已复制好的字段对象,本函数不再外泄其可变引用 * @returns {RequestContext} */ function makeContext(traceId, fields) { /** @type {RequestContext} */ const ctx = { traceId, fields, child(extraFields) { // 不可变派生:合并出全新字段对象,父 ctx.fields 不被触碰。 const merged = { ...fields, ...(extraFields || {}) } return makeContext(traceId, merged) }, } // 冻结对外可见形态,防止下游意外篡改 traceId/child 造成 trace 漂移。 Object.freeze(ctx.fields) return Object.freeze(ctx) }