如何无需传参在其他类中使用Fastify的req.logger
Fastify全链路获取请求级logger实现方案
核心思路:使用Node.js内置的AsyncLocalStorage异步上下文存储能力,在请求入口绑定当前请求带correlationId的logger实例,全链路任意位置可直接读取,无需逐层传参。
步骤1:创建上下文存储工具
新建logger-context.ts文件,封装存储、读取logger的工具方法:
// logger-context.ts import { AsyncLocalStorage } from 'async_hooks'; import type { FastifyBaseLogger } from 'fastify'; const loggerStorage = new AsyncLocalStorage<FastifyBaseLogger>(); /** * 包裹请求处理流程,将当前请求的logger注入异步上下文 */ export function runWithLogger<T>(logger: FastifyBaseLogger, callback: () => T): T { return loggerStorage.run(logger, callback); } /** * 从当前异步上下文获取绑定了correlationId的请求logger */ export function getLogger(): FastifyBaseLogger { const logger = loggerStorage.getStore(); if (!logger) { throw new Error('No logger found in current async context, please call this method in request lifecycle'); } return logger; }
步骤2:Fastify全局钩子绑定上下文
修改app.ts,添加onRequest钩子,每个请求进入时自动将req.logger注入异步上下文,后续所有该请求触发的异步逻辑都能读取到对应logger:
import pino from 'pino'; import fastify from 'fastify'; import { runWithLogger } from './logger-context'; declare module 'fastify' { interface FastifyInstance { config: Config; } } function build() { const app = fastify({ logger: pino({ name: process.env.NAME, level: process.env.LOG_LEVEL, }), disableRequestLogging: true, requestIdHeader: 'correlation-id', requestIdLogLabel: 'correlationId', }); // 新增请求钩子,绑定logger到异步上下文 app.addHook('onRequest', (req, reply, done) => { runWithLogger(req.logger, done); }); // 原有插件注册逻辑保持不变,UseCase、TestClass维持单例即可 app.register(apiRoutes, () => ({ getObjectUseCase: new GetObjectUseCase(new TestClass()), })); return app; } export { build };
步骤3:业务代码直接取用logger
不需要修改类构造函数,不需要给方法传logger参数,在任意业务方法中直接引入getLogger即可拿到当前请求绑定的logger实例:
GetObjectUseCase.ts 改造
import { getLogger } from './logger-context'; export class GetObjectUseCase { private anotherClass: TestClass; constructor(anotherClass: TestClass) { this.anotherClass = anotherClass; } async execute(id: string): Promise<StoredObject> { // 直接获取带correlationId的logger使用 const logger = getLogger(); logger.info(`Start executing getObject flow, target id: ${id}`); // 下游类方法无需传logger,内部自行获取即可 return this.anotherClass.getById(id); } }
TestClass.ts 改造
import { getLogger } from './logger-context'; export class TestClass { async getById(id: string): Promise<StoredObject> { const logger = getLogger(); logger.debug(`Start querying record by id: ${id}`); // 原有业务逻辑 } }
路由层原有代码无需改动,req.logger和下游业务层拿到的是同一个logger实例,所有日志都会自动携带当前请求的correlationId。
注意事项
AsyncLocalStorage是Node.js官方原生实现,性能开销极低,生产环境可用,不会出现请求间日志串号问题。- 禁止将
getLogger()取到的logger实例存为类的成员变量,类是单例的,存成员变量会被后续请求覆盖导致日志串号,每次使用时实时调用getLogger()获取即可。 - 非请求场景(比如定时任务、启动初始化)调用业务类时,可以手动传入全局logger,用
runWithLogger包裹执行逻辑,避免抛出找不到上下文的错误。
内容的提问来源于stack exchange,提问作者Nicolas García
相关产品推荐
相关产品推荐

