feat(logging): add request-context with AsyncLocalStorage

This commit is contained in:
SpecialX
2026-07-07 11:34:40 +08:00
parent c2575960eb
commit 3bd3ebc12d
2 changed files with 72 additions and 0 deletions

View File

@@ -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({})
})
})
})

View File

@@ -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<RequestContext>()
/**
* 获取当前请求上下文(若存在)。
*
* - 在 withRequestContext 包装的调用栈内:返回完整上下文
* - 在调用栈外(如顶层模块初始化、定时任务):返回空对象
*
* pino logger 的 mixin 配置会自动调用此函数混入 requestId。
*/
export function getRequestContext(): Partial<RequestContext> {
return requestContextStorage.getStore() ?? {}
}