FORMA

日志记录

内置 Logger 可替换为 Pino 等实现。

日志是监控和调试应用的重要手段。NestJS 内置了轻量级日志器,同时也支持与 pinowinston 等专业日志库集成,满足生产环境需求。

一、内置 Logger

NestJS 在 @nestjs/common 中提供了 Logger 类,支持多种日志级别,开箱即用。

1. 基本使用

ts
import { Logger } from "@nestjs/common";

@Injectable()
export class AppService {
  private readonly logger = new Logger(AppService.name);

  getHello(): string {
    this.logger.log("Returning hello");
    this.logger.debug("Debug info");
    this.logger.warn("Warning message");
    this.logger.error("Error occurred", "stack trace");
    this.logger.verbose("Verbose output");
    return "Hello World!";
  }
}

2. 日志级别

方法级别说明
log()log一般信息
error()error错误信息,可传堆栈
warn()warn警告信息
debug()debug调试信息
verbose()verbose详细输出

可通过设置 LOG_LEVELS 环境变量或启动参数控制哪些级别输出:

bash
# 只输出 error 和 warn
LOG_LEVELS=error,warn node dist/main

3. 全局替换日志器

你可以创建自定义日志器替换内置的 Logger。例如,将日志输出到文件:

ts
// main.ts
import { Logger } from "@nestjs/common";

class MyLogger extends Logger {
  error(message: any, trace?: string, context?: string) {
    // 自定义逻辑(保存到文件、发送到外部服务)
    super.error(message, trace, context);
  }
}

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

二、自定义日志器:实现 LoggerService 接口

如果你需要完全控制日志行为,可以实现 LoggerService 接口(来自 @nestjs/common)。

ts
import { LoggerService } from "@nestjs/common";

export class MyCustomLogger implements LoggerService {
  log(message: any, ...optionalParams: any[]) {
    /* 实现 */
  }
  error(message: any, ...optionalParams: any[]) {
    /* 实现 */
  }
  warn(message: any, ...optionalParams: any[]) {
    /* 实现 */
  }
  debug?(message: any, ...optionalParams: any[]) {
    /* 实现 */
  }
  verbose?(message: any, ...optionalParams: any[]) {
    /* 实现 */
  }
  setLogLevels?(levels: string[]) {
    /* 可选 */
  }
}

然后在创建应用时传入:

ts
app.useLogger(new MyCustomLogger());

三、生产级日志:pino 与 winston

1. nestjs-pino(推荐用于高性能)

pino 是极快的 JSON 日志库,nestjs-pino 提供 NestJS 集成(注意包名是 nestjs-pino,不是 nest-pino)。

安装:

bash
npm install nestjs-pino pino-http

在模块中导入:

ts
// app.module.ts
import { LoggerModule } from "nestjs-pino";

@Module({
  imports: [
    LoggerModule.forRoot({
      pinoHttp: {
        level: process.env.LOG_LEVEL || "info",
        transport:
          process.env.NODE_ENV !== "production"
            ? { target: "pino-pretty" } // 开发友好格式
            : undefined,
      },
    }),
  ],
})
export class AppModule {}

在服务中注入 Pino 日志器:

ts
import { PinoLogger, InjectPinoLogger } from "nestjs-pino";

@Injectable()
export class MyService {
  constructor(@InjectPinoLogger(MyService.name) private logger: PinoLogger) {}

  doSomething() {
    this.logger.info({ data: 123 }, "Info log");
  }
}

2. nest-winston

winston 功能丰富,支持多运输渠道(控制台、文件、远程服务)。

安装:

bash
npm install nest-winston winston

配置:

ts
import { WinstonModule } from "nest-winston";
import * as winston from "winston";

@Module({
  imports: [
    WinstonModule.forRoot({
      transports: [
        new winston.transports.Console({
          format: winston.format.simple(),
        }),
        new winston.transports.File({ filename: "logs/error.log", level: "error" }),
      ],
    }),
  ],
})
export class AppModule {}

在服务中注入(nest-winston 没有 InjectLogger 装饰器,需通过 WINSTON_MODULE_PROVIDER 令牌注入):

ts
import { Inject, Injectable } from "@nestjs/common";
import { WINSTON_MODULE_PROVIDER } from "nest-winston";
import { Logger } from "winston";

@Injectable()
export class MyService {
  constructor(@Inject(WINSTON_MODULE_PROVIDER) private logger: Logger) {}

  test() {
    this.logger.info("message");
  }
}

四、请求日志中间件

记录每个 HTTP 请求的详细信息(方法、URL、参数、响应状态、耗时等)对问题排查很有帮助。可以用中间件实现。

ts
// logger.middleware.ts
import { Injectable, NestMiddleware, Logger } from "@nestjs/common";
import { Request, Response, NextFunction } from "express";

@Injectable()
export class RequestLoggerMiddleware implements NestMiddleware {
  private logger = new Logger("HTTP");

  use(req: Request, res: Response, next: NextFunction) {
    const { method, originalUrl, ip, body, query } = req;
    const userAgent = req.get("user-agent") || "";
    const start = Date.now();

    res.on("finish", () => {
      const duration = Date.now() - start;
      this.logger.log(
        `${method} ${originalUrl} ${res.statusCode} ${duration}ms - ${userAgent} ${ip}`,
      );
      // 可选:记录 body、query(注意敏感信息脱敏)
    });

    next();
  }
}

注册中间件(例如在 AppModule 中全局使用):

ts
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(RequestLoggerMiddleware).forRoutes("*");
  }
}

对于更高级的请求追踪(如关联请求 ID),可以使用 cls-hookednestjs-cls

五、日志最佳实践

  1. 分级输出:开发环境使用 debug/verbose,生产环境使用 info/warn/error
  2. 结构化日志:使用 JSON 格式(pino/winston 默认),便于日志聚合工具分析。
  3. 敏感信息脱敏:在记录请求体或参数时,滤除密码、Token 等。
  4. 请求 ID 关联:为每个请求生成唯一 ID,贯穿所有日志,方便定位链路。
  5. 异步日志:使用非阻塞日志库(pino 自带低开销),避免日志影响性能。
  6. 集中收集:生产环境将日志输出到 stdout/stderr,由容器或进程管理器(如 PM2)转发到 ELK、Splunk 等。

总结

需求推荐方案
简单调试内置 Logger,轻量且够用
高性能生产nestjs-pino(JSON 日志、低开销)
丰富运输渠道nest-winston(文件、远程、多格式)
请求日志自定义中间件记录入参、耗时、状态码
多级别控制环境变量 LOG_LEVELS 或日志库的 level 配置

根据项目规模和性能要求,选择合适的日志方案,并在开发初期就引入规范的日志记录,便于后期维护和故障排查。

参考文献

以下链接在编写时均可正常访问:

资料说明
NestJS 文档官方
Logger本章主题
Request lifecycle执行顺序