feat(logging): add request-context with AsyncLocalStorage
This commit is contained in:
45
src/shared/lib/request-context.test.ts
Normal file
45
src/shared/lib/request-context.test.ts
Normal 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({})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
27
src/shared/lib/request-context.ts
Normal file
27
src/shared/lib/request-context.ts
Normal 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() ?? {}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user