NestJS中如何为TypeORM实现带请求作用域的依赖注入日志器?
让TypeORM日志器支持NestJS请求作用域并关联correlationId
由于TypeORM日志器默认是启动时初始化的单例,无法直接注入NestJS的请求作用域依赖,我们可以通过AsyncLocalStorage(ALS) 传递请求上下文(包括correlationId),同时自定义TypeORM日志器读取ALS中的上下文信息,实现请求级别的日志追踪。
核心思路
- 用ALS存储每个请求的correlationId,确保异步操作中也能访问当前请求的上下文
- 自定义TypeORM日志器,输出日志时从ALS中获取correlationId并附加到日志内容
- 通过NestJS拦截器在请求入口处生成并注入correlationId到ALS
具体实现步骤
1. 实现AsyncLocalStorage服务
创建ALS服务管理请求上下文的存储与读取:
import { Injectable } from '@nestjs/common'; import { AsyncLocalStorage } from 'async_hooks'; @Injectable() export class AlsService { private readonly als = new AsyncLocalStorage<Map<string, any>>(); // 运行带上下文的回调 runWithContext<T>(context: Map<string, any>, callback: () => T): T { return this.als.run(context, callback); } // 获取当前上下文 getContext(): Map<string, any> | undefined { return this.als.getStore(); } // 从上下文获取指定键的值 get(key: string): any { const context = this.getContext(); return context?.get(key); } }
2. 创建请求拦截器注入correlationId
编写拦截器,在每个请求进来时生成correlationId并存入ALS:
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common'; import { Observable } from 'rxjs'; import { AlsService } from './als.service'; import { v4 as uuidv4 } from 'uuid'; @Injectable() export class CorrelationIdInterceptor implements NestInterceptor { constructor(private readonly alsService: AlsService) {} intercept(context: ExecutionContext, next: CallHandler): Observable<any> { // 生成唯一correlationId const correlationId = uuidv4(); const contextMap = new Map(); contextMap.set('correlationId', correlationId); // 若需将请求作用域日志器存入ALS,可在此获取并添加 // const requestLogger = context.switchToHttp().getRequest().logger; // contextMap.set('requestLogger', requestLogger); // 用ALS包裹请求处理流程 return this.alsService.runWithContext(contextMap, () => next.handle()); } }
3. 自定义TypeORM日志器
实现TypeORM的Logger接口,读取ALS中的correlationId并附加到日志:
import { Logger as TypeOrmLogger, QueryRunner } from 'typeorm'; import { Injectable, Logger as NestLogger } from '@nestjs/common'; import { AlsService } from './als.service'; @Injectable() export class TypeOrmCustomLogger implements TypeOrmLogger { constructor( private readonly alsService: AlsService, private readonly nestLogger: NestLogger, ) {} private getCorrelatedMessage(message: string): string { const correlationId = this.alsService.get('correlationId'); return correlationId ? `[${correlationId}] ${message}` : message; } log(message: string, queryRunner?: QueryRunner) { this.nestLogger.log(this.getCorrelatedMessage(message)); } error(message: string, trace?: string, queryRunner?: QueryRunner) { this.nestLogger.error(this.getCorrelatedMessage(message), trace); } warn(message: string, queryRunner?: QueryRunner) { this.nestLogger.warn(this.getCorrelatedMessage(message)); } debug(message: string, queryRunner?: QueryRunner) { this.nestLogger.debug(this.getCorrelatedMessage(message)); } logMigration(message: string, queryRunner?: QueryRunner) { this.log(message, queryRunner); } logQuery(query: string, parameters?: any[], queryRunner?: QueryRunner) { const message = `${query} | Parameters: ${JSON.stringify(parameters)}`; this.nestLogger.log(this.getCorrelatedMessage(message)); } logQueryError(error: string, query: string, parameters?: any[], queryRunner?: QueryRunner) { const message = `${query} | Parameters: ${JSON.stringify(parameters)} | Error: ${error}`; this.nestLogger.error(this.getCorrelatedMessage(message)); } logQuerySlow(time: number, query: string, parameters?: any[], queryRunner?: QueryRunner) { const message = `Slow query: ${time}ms | ${query} | Parameters: ${JSON.stringify(parameters)}`; this.nestLogger.warn(this.getCorrelatedMessage(message)); } logSchemaBuild(message: string, queryRunner?: QueryRunner) { this.log(message, queryRunner); } }
4. 配置NestJS模块
在根模块中注册ALS服务、拦截器和自定义TypeORM日志器:
import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { AlsService } from './als.service'; import { CorrelationIdInterceptor } from './correlation-id.interceptor'; import { TypeOrmCustomLogger } from './type-orm-custom.logger'; import { APP_INTERCEPTOR } from '@nestjs/core'; @Module({ imports: [ TypeOrmModule.forRootAsync({ useFactory: (customLogger: TypeOrmCustomLogger) => ({ type: 'postgres', // 替换为你的数据库类型 host: 'localhost', port: 5432, username: 'your-username', password: 'your-password', database: 'your-db', entities: [__dirname + '/**/*.entity{.ts,.js}'], synchronize: process.env.NODE_ENV !== 'production', logger: customLogger, // 使用自定义日志器 }), inject: [TypeOrmCustomLogger], }), ], providers: [ AlsService, TypeOrmCustomLogger, { provide: APP_INTERCEPTOR, useClass: CorrelationIdInterceptor, }, ], }) export class AppModule {}
注意事项
- 若使用NestJS集群模式,需额外处理ALS的跨worker上下文传递,因为每个worker有独立的ALS实例
- 迁移、Schema构建等非请求上下文的操作不会有correlationId,日志中不会显示该字段,属于正常情况
- 若你的请求作用域日志器有自定义逻辑,可将日志器实例存入ALS,在TypeORM日志器中直接调用该实例的方法,替代单例的
NestLogger
内容的提问来源于stack exchange,提问作者Philip Claren
相关产品推荐
相关产品推荐

