From 3bd3ebc12d4045a3b32d95a91dff43bbea9bb591 Mon Sep 17 00:00:00 2001 From: SpecialX <47072643+wangxiner55@users.noreply.github.com> Date: Tue, 7 Jul 2026 11:34:40 +0800 Subject: [PATCH] feat(logging): add request-context with AsyncLocalStorage --- src/shared/lib/request-context.test.ts | 45 ++++++++++++++++++++++++++ src/shared/lib/request-context.ts | 27 ++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 src/shared/lib/request-context.test.ts create mode 100644 src/shared/lib/request-context.ts diff --git a/src/shared/lib/request-context.test.ts b/src/shared/lib/request-context.test.ts new file mode 100644 index 0000000..f0bd509 --- /dev/null +++ b/src/shared/lib/request-context.test.ts @@ -0,0 +1,45 @@ +import { describe, it, expect } from "vitest" +import { + requestContextStorage, + getRequestContext, +} from "./request-context" + +describe("request-context", () => { + describe("getRequestContext (无上下文)", () => { + it("返回空对象", () => { + expect(getRequestContext()).toEqual({}) + }) + }) + + describe("requestContextStorage.run (有上下文)", () => { + it("在 run 回调内 getRequestContext 返回注入的上下文", () => { + const ctx = { requestId: "req-123", userId: "user-456" } + requestContextStorage.run(ctx, () => { + expect(getRequestContext()).toEqual(ctx) + }) + }) + + it("run 回调返回值正常透传", () => { + const result = requestContextStorage.run( + { requestId: "req-789" }, + () => "return-value" + ) + expect(result).toBe("return-value") + }) + + it("嵌套 run 内层覆盖外层", () => { + requestContextStorage.run({ requestId: "outer" }, () => { + expect(getRequestContext().requestId).toBe("outer") + requestContextStorage.run({ requestId: "inner" }, () => { + expect(getRequestContext().requestId).toBe("inner") + }) + expect(getRequestContext().requestId).toBe("outer") + }) + }) + + it("run 回调退出后 getRequestContext 恢复为空", () => { + requestContextStorage.run({ requestId: "temp" }, () => {}) + expect(getRequestContext()).toEqual({}) + }) + }) +}) diff --git a/src/shared/lib/request-context.ts b/src/shared/lib/request-context.ts new file mode 100644 index 0000000..103490e --- /dev/null +++ b/src/shared/lib/request-context.ts @@ -0,0 +1,27 @@ +import { AsyncLocalStorage } from "node:async_hooks" + +/** + * 请求上下文,贯穿整个请求生命周期。 + * + * 仅在 Node.js Runtime 中可用(proxy.ts 是 Edge Runtime,不导入此模块)。 + * 通过 withRequestContext 高阶函数注入。 + */ +export interface RequestContext { + requestId: string + userId?: string + module?: string +} + +export const requestContextStorage = new AsyncLocalStorage() + +/** + * 获取当前请求上下文(若存在)。 + * + * - 在 withRequestContext 包装的调用栈内:返回完整上下文 + * - 在调用栈外(如顶层模块初始化、定时任务):返回空对象 + * + * pino logger 的 mixin 配置会自动调用此函数混入 requestId。 + */ +export function getRequestContext(): Partial { + return requestContextStorage.getStore() ?? {} +}