如何为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
相关产品推荐
相关产品推荐

