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

如何为Nestjs Logger日志添加毫秒数及绑定请求/响应

为NestJS Logger添加毫秒数并实现请求级日志绑定

一、为日志添加毫秒数

NestJS默认的ConsoleLogger可通过自定义子类修改时间格式实现毫秒输出;若使用第三方日志库(如Pino、Winston),直接配置对应格式即可。

方案1:自定义ConsoleLogger

创建继承自ConsoleLogger的自定义日志类,重写formatTimestamp方法返回带毫秒的时间字符串:

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

@Injectable()
export class CustomLogger extends ConsoleLogger {
  // 重写时间格式化方法,返回带毫秒的时间格式
  formatTimestamp(): string {
    // 方式1:ISO标准格式(自带毫秒)
    return new Date().toISOString();
    
    // 方式2:自定义本地格式(如"2024-05-20 14:30:45.678")
    // return new Date().toLocaleString('zh-CN', {
    //   hour12: false,
    //   year: 'numeric',
    //   month: '2-digit',
    //   day: '2-digit',
    //   hour: '2-digit',
    //   minute: '2-digit',
    //   second: '2-digit',
    //   fractionalSecondDigits: 3,
    // });
  }
}

在main.ts中注册自定义日志:

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { CustomLogger } from './custom.logger';

async function bootstrap() {
  const app = await NestFactory.create(AppModule, {
    logger: new CustomLogger(),
  });
  await app.listen(3000);
}
bootstrap();

方案2:使用Pino日志库(推荐)

借助nestjs-pino可直接配置带毫秒的时间格式:

npm install nestjs-pino pino-http

在模块中配置:

import { Module } from '@nestjs/common';
import { LoggerModule } from 'nestjs-pino';

@Module({
  imports: [
    LoggerModule.forRoot({
      pinoHttp: {
        transport: {
          target: 'pino-pretty',
          options: {
            singleLine: true,
            // 配置带毫秒的时间格式
            translateTime: 'SYS:yyyy-mm-dd HH:MM:ss.l o',
          },
        },
      },
    }),
  ],
})
export class AppModule {}

在main.ts中启用日志:

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { Logger } from 'nestjs-pino';

async function bootstrap() {
  const app = await NestFactory.create(AppModule, { bufferLogs: true });
  app.useLogger(app.get(Logger));
  await app.listen(3000);
}
bootstrap();

二、实现Logger与请求/响应绑定

核心思路是为每个请求分配唯一traceId,通过异步上下文(AsyncLocalStorage)传递给Logger,让每条日志关联对应请求的标识。

方案1:自定义实现(基于AsyncLocalStorage)

1. 创建请求上下文服务

用于存储和获取当前请求的traceId:

import { Injectable, Scope } from '@nestjs/common';
import { AsyncLocalStorage } from 'async_hooks';

interface RequestContextData {
  traceId: string;
}

@Injectable({ scope: Scope.DEFAULT })
export class RequestContext {
  private readonly als = new AsyncLocalStorage<RequestContextData>();

  runWithTraceId(traceId: string, callback: () => void): void {
    this.als.run({ traceId }, callback);
  }

  getTraceId(): string | undefined {
    return this.als.getStore()?.traceId;
  }
}

2. 创建TraceId中间件

为每个请求生成或获取traceId,并注入到请求上下文:

import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import { v4 as uuidv4 } from 'uuid';
import { RequestContext } from './request-context.service';

@Injectable()
export class TraceIdMiddleware implements NestMiddleware {
  constructor(private readonly requestContext: RequestContext) {}

  use(req: Request, res: Response, next: NextFunction) {
    // 优先取请求头中的traceId,无则生成UUID
    const traceId = req.headers['x-trace-id'] as string || uuidv4();
    // 将traceId返回给客户端,便于排查
    res.setHeader('X-Trace-Id', traceId);
    // 启动异步上下文,传递traceId
    this.requestContext.runWithTraceId(traceId, next);
  }
}

3. 修改自定义Logger,添加traceId前缀

import { ConsoleLogger, Injectable } from '@nestjs/common';
import { RequestContext } from './request-context.service';

@Injectable()
export class CustomLogger extends ConsoleLogger {
  constructor(private readonly requestContext: RequestContext) {
    super();
  }

  formatTimestamp(): string {
    return new Date().toISOString();
  }

  // 获取请求traceId前缀
  private getTracePrefix(): string {
    const traceId = this.requestContext.getTraceId();
    return traceId ? `[traceId: ${traceId}]` : '';
  }

  // 重写所有日志方法,添加traceId前缀
  log(message: any, context?: string) {
    super.log(`${this.getTracePrefix()} ${message}`, context);
  }

  error(message: any, stack?: string, context?: string) {
    super.error(`${this.getTracePrefix()} ${message}`, stack, context);
  }

  warn(message: any, context?: string) {
    super.warn(`${this.getTracePrefix()} ${message}`, context);
  }

  debug(message: any, context?: string) {
    super.debug(`${this.getTracePrefix()} ${message}`, context);
  }

  verbose(message: any, context?: string) {
    super.verbose(`${this.getTracePrefix()} ${message}`, context);
  }
}

4. 注册中间件和服务

在AppModule中配置:

import { Module, MiddlewareConsumer } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { CustomLogger } from './custom.logger';
import { RequestContext } from './request-context.service';
import { TraceIdMiddleware } from './trace-id.middleware';

@Module({
  controllers: [AppController],
  providers: [AppService, CustomLogger, RequestContext],
})
export class AppModule {
  configure(consumer: MiddlewareConsumer) {
    // 为所有路由应用TraceId中间件
    consumer.apply(TraceIdMiddleware).forRoutes('*');
  }
}

方案2:使用nestjs-pino快速实现

nestjs-pino内置请求上下文支持,无需手动处理AsyncLocalStorage:
在之前的Pino配置中添加genReqId即可自动生成请求ID并关联日志:

LoggerModule.forRoot({
  pinoHttp: {
    transport: {
      target: 'pino-pretty',
      options: {
        singleLine: true,
        translateTime: 'SYS:yyyy-mm-dd HH:MM:ss.l o',
      },
    },
    // 生成请求ID(优先取请求头的x-trace-id,否则自动生成)
    genReqId: (req) => req.headers['x-trace-id'] || crypto.randomUUID(),
    // 序列化请求信息,保留请求ID
    serializers: {
      req(req) {
        req.id = req.id;
        return req;
      },
    },
  },
}),

所有请求相关日志会自动带上req.id(即traceId),实现请求级日志绑定。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 14:25:47