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

如何在NestJS中便捷分配correlationId并让日志自动携带该ID?

问题

接手了一个NestJS项目,前任开发者写的中间件能生成correlationId并附加到请求里,功能正常。但开发和写测试时,每次都要用到扩展了correlationId的express.Request类型(也就是AppRequest),冗余代码多,想优化使用体验,把重复逻辑封装起来。

现在控制器代码是这样的:

async createUser(@Req() request: AppRequest,
                 @Body() payload: any,
                 @Res() response: Response)
{
   return this.users.create(request, payload);
}

必须把request传给service,才能让CloudWatch日志带上correlationId;要是直接用console.log,日志会因为时间差没法关联到对应请求。我希望在this.users.create()里调用logger.info()时,日志能自动带上correlationId,就像Spring Boot里的做法一样,想知道Node.js和NestJS能不能实现。

现有中间件代码如下:

const onHeaders = require('on-headers');

@Injectable()
export class LoggerMiddleware implements NestMiddleware {
  use(request: Request, response: Response, next: NextFunction) {
    httpContext.middleware(request, response, () => {
      const startTime = process.hrtime();

      const correlationId = (
        request.headers['request-id'] ||
        request.headers['x-request-id'] ||
        request.headers['x-correlation-id'] ||
        request.headers.correlationId ||
        uuidv4()
      ).toString();

      const { method, baseUrl, body } = request;
      const url = (baseUrl || '') + (request.url || '-');
      const route = `${method} ${request.route ? request.route.path : url}`;

      const logger = rootLogger.child({ correlationId, route, url, method });
      onHeaders(response, function onHeaders() {
        const diff = process.hrtime(startTime);
        const responseTime = diff[0] * 1e3 + diff[1] * 1e-6;
        response.setHeader('X-Response-Time', responseTime);
        logger.info(
          {
            responseTime,
            statusCode: response.statusCode,
          },
          'Responsed',
        );
      });

      request.logger = logger;
      request.correlationId = correlationId;
      response.setHeader('x-correlation-id', correlationId);
      next();
    });
  }
}
解决方案

当然可以实现,以下是几个能大幅优化开发体验的方案:

方案1:基于请求上下文封装全局请求作用域Logger

你已经在用httpContext处理请求上下文了,完全可以把带correlationId的Logger实例存在上下文里,不用每次手动传递request。

实现步骤:

  1. 封装请求作用域的Logger服务
    创建一个自定义Logger服务,从httpContext中获取当前请求的Logger实例:
import { Injectable } from '@nestjs/common';
import httpContext from 'express-http-context';

@Injectable()
export class RequestLogger {
  private readonly logger: any; // 替换成你实际的Logger类型

  constructor() {
    this.logger = httpContext.get('logger');
  }

  info(message: string, meta?: Record<string, any>) {
    this.logger.info(meta || {}, message);
  }

  error(message: string, meta?: Record<string, any>) {
    this.logger.error(meta || {}, message);
  }

  // 根据需要扩展warn、debug等方法
}
  1. 修改中间件,将Logger存入httpContext
    在现有中间件中,设置request.logger的同时,把Logger实例存入httpContext:
// 中间代码补充
httpContext.set('logger', logger); // 新增这一行
request.logger = logger;
request.correlationId = correlationId;
  1. Service中直接注入使用
    现在Service里无需再接收request参数,直接注入RequestLogger即可自动获取带correlationId的Logger:
import { Injectable } from '@nestjs/common';
import { RequestLogger } from './request.logger';

@Injectable()
export class UsersService {
  constructor(private readonly logger: RequestLogger) {}

  async create(payload: any) {
    // 打日志自动携带correlationId
    this.logger.info('开始创建用户', { payload });
    // 业务逻辑...
    return result;
  }
}
  1. 简化控制器代码
    控制器不再需要传递request给Service,代码更简洁:
async createUser(@Body() payload: any) {
   return this.users.create(payload);
}

方案2:自定义装饰器直接获取Logger/CorrelationId

如果不想依赖httpContext,可以通过自定义装饰器,直接从request中提取Logger或correlationId,注入到控制器或Service的参数中。

自定义Logger装饰器:

import { createParamDecorator, ExecutionContext } from '@nestjs/common';

export const RequestLogger = createParamDecorator(
  (_, ctx: ExecutionContext) => {
    const request = ctx.switchToHttp().getRequest();
    return request.logger;
  },
);

控制器中使用装饰器:

async createUser(@Body() payload: any, @RequestLogger() logger: any) {
   logger.info('收到创建用户请求');
   return this.users.create(payload, logger);
}

Service中结合自定义提供者注入:

要在Service中直接使用,可以通过自定义提供者将request.logger注入:

// 在对应模块的providers数组中配置
providers: [
  UsersService,
  {
    provide: 'REQUEST_LOGGER',
    useFactory: (request: any) => request.logger,
    inject: [REQUEST], // REQUEST是NestJS内置的请求对象令牌
  },
]

然后在Service中注入使用:

import { Inject, Injectable } from '@nestjs/common';

@Injectable()
export class UsersService {
  constructor(@Inject('REQUEST_LOGGER') private readonly logger: any) {}

  async create(payload: any) {
    this.logger.info('创建用户', { payload });
    // 业务逻辑...
  }
}

方案3:扩展NestJS全局Logger,利用AsyncLocalStorage跟踪上下文

这个方案类似Spring Boot的ThreadLocal,用Node.js原生的AsyncLocalStorage来跟踪请求上下文,扩展NestJS自带的Logger,实现全局自动携带correlationId。

实现步骤:

  1. 创建AsyncLocalStorage实例
import { AsyncLocalStorage } from 'async_hooks';

// 定义上下文类型
type RequestContext = {
  correlationId: string;
  logger: any;
};

export const requestContext = new AsyncLocalStorage<RequestContext>();
  1. 修改中间件,用AsyncLocalStorage包裹请求逻辑
    替换原来的httpContext,用requestContext.run来存储请求上下文:
use(request: Request, response: Response, next: NextFunction) {
  // ... 生成correlationId和logger的逻辑保持不变
  requestContext.run({ correlationId, logger }, () => {
    onHeaders(response, () => {
      // 响应日志逻辑不变
    });
    request.correlationId = correlationId;
    response.setHeader('x-correlation-id', correlationId);
    next();
  });
}
  1. 扩展NestJS自带Logger
import { Logger } from '@nestjs/common';
import { requestContext } from './request-context';

export class CorrelationLogger extends Logger {
  log(message: string, context?: string) {
    const ctx = requestContext.getStore();
    if (ctx) {
      super.log(`[${ctx.correlationId}] ${message}`, context);
    } else {
      super.log(message, context);
    }
  }

  error(message: string, trace?: string, context?: string) {
    const ctx = requestContext.getStore();
    if (ctx) {
      super.error(`[${ctx.correlationId}] ${message}`, trace, context);
    } else {
      super.error(message, trace, context);
    }
  }

  // 同理扩展warn、debug等方法
}
  1. 全局注册自定义Logger
    在main.ts中替换默认Logger:
async function bootstrap() {
  const app = await NestFactory.create(AppModule, {
    logger: new CorrelationLogger(),
  });
  // ... 其他配置
  await app.listen(3000);
}

之后在控制器或Service中直接注入NestJS的Logger,打日志时会自动带上correlationId。


内容的提问来源于stack exchange,提问作者tftd

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 08:30:58