You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

NestJS中如何为TypeORM实现带请求作用域的依赖注入日志器?

让TypeORM日志器支持NestJS请求作用域并关联correlationId

由于TypeORM日志器默认是启动时初始化的单例,无法直接注入NestJS的请求作用域依赖,我们可以通过AsyncLocalStorage(ALS) 传递请求上下文(包括correlationId),同时自定义TypeORM日志器读取ALS中的上下文信息,实现请求级别的日志追踪。

核心思路

  1. 用ALS存储每个请求的correlationId,确保异步操作中也能访问当前请求的上下文
  2. 自定义TypeORM日志器,输出日志时从ALS中获取correlationId并附加到日志内容
  3. 通过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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 13:25:17