Skip to content

当然!我们来探讨一个虽然基础但却至关重要的主题:日志 (Logger)。这就像是为你的应用安装一个全方位的“黑匣子”和“飞行记录仪”,在应用平稳运行时它默默无闻,但在出现问题时,它就是你排查故障、还原现场的唯一线索。

为你的应用安装“黑匣子”:NestJS 日志系统完全指南

想象你的 NestJS 应用是一架正在执行跨洋飞行的精密客机。

  • 在万米高空平稳飞行时(正常运行),一切安好。
  • 但如果突然遇到一阵强烈的气流(一个意外的错误),或者某个引擎的指示灯闪烁(性能瓶颈),飞行员(你,开发者)需要立刻知道:发生了什么?在哪里发生的?当时的情况是怎样的?

没有日志系统,就如同驾驶一架仪表盘全黑的飞机,你对飞机的内部状态一无所知,一旦出现问题便束手无策。

日志 (Logging) 就是记录应用在运行期间发生的关键事件的过程。一个好的日志系统应该能:

  • 分级记录:区分普通信息(log)、调试细节(debug)、警告(warn)和严重错误(error)。
  • 提供上下文:清晰地标明日志信息来自哪个模块([UsersService]),以及发生的时间。
  • 结构化输出:最好能输出为 JSON 格式,方便机器进行解析、搜索和分析。
  • 可定制化:允许你轻松地替换底层的日志库(比如换成更专业的 PinoWinston),并将日志输出到文件、远程日志服务等。

NestJS 提供了一个内置的、轻量级的日志系统,并设计了一套非常优雅的机制,让你可以在此基础上进行扩展和定制。

1. 基础用法:开箱即用的 Logger 服务

NestJS 内置了一个 Logger 类,你可以直接在任何服务或控制器中使用它,无需任何额外配置。

src/cats/cats.service.ts```typescript import { Injectable, Logger } from '@nestjs/common';

@Injectable() export class CatsService { // 1. 在类的属性中创建一个 Logger 实例 // 传入的上下文('CatsService')会自动显示在日志输出中 private readonly logger = new Logger(CatsService.name);

create(catDto: any) { // 2. 使用不同的方法记录不同级别的日志 this.logger.log('开始执行创建猫猫的操作...'); this.logger.debug(收到的数据是: ${JSON.stringify(catDto)}, 'create method'); // 可以传入第二个参数作为更详细的上下文

if (!catDto.name) {
  this.logger.warn('警告:创建的猫猫没有名字!');
}

// ... 执行创建逻辑 ...
this.logger.log('猫猫创建成功!');

} }


当你调用 `create()` 方法时,你的控制台会输出类似这样的彩色日志:

```bash
[Nest] 12345  - 07/16/2025, 5:00:00 PM     LOG [CatsService] 开始执行创建猫猫的操作...
[Nest] 12345  - 07/16/2025, 5:00:00 PM   DEBUG [CatsService] 收到的数据是: {"age":5} - create method
[Nest] 12345  - 07/16/2025, 5:00:00 PM    WARN [CatsService] 警告:创建的猫猫没有名字!
[Nest] 12345  - 07/16/2025, 5:00:00 PM     LOG [CatsService] 猫猫创建成功!

这种方式非常简单直观,对于开发和小项目来说已经足够了。

2. 更换“飞行记录仪”:自定义你的日志系统

内置的 Logger 只是一个简单的控制台输出工具。在生产环境中,我们通常有更复杂的需求:

  • 将日志写入文件以便长期保存。
  • 将日志输出为 JSON 格式,方便日志收集系统(如 ELK Stack, Datadog)进行处理。
  • 根据日志级别(error 级别的日志要发邮件告警)执行不同操作。

NestJS 允许你完全替换掉它的默认日志实现。你可以通过在 main.ts 中将一个自定义的 Logger 实例传递给 NestJS 应用来实现。

如何自定义? 你需要创建一个实现了 LoggerService 接口的类。

实战:使用强大的 Winston 库来接管日志

Winston 是 Node.js 生态中最流行、功能最强大的日志库之一。

第一步:安装依赖

bash
npm install winston nest-winston
````nest-winston` 是一个社区提供的辅助库,让集成过程更简单。

**第二步:创建我们的自定义 Logger**

**`src/logger/winston.logger.ts`**
```typescript
import { transports, format, createLogger } from 'winston';
import { WinstonModule } from 'nest-winston';

// 创建一个 Winston Logger 实例
const winstonLogger = createLogger({
  transports: [
    // 1. 在控制台输出
    new transports.Console({
      format: format.combine(
        format.timestamp(),
        format.ms(),
        format.colorize({ all: true }), // 为日志级别添加颜色
        format.printf(info => `${info.timestamp} [${info.level}] ${info.context}: ${info.message}`),
      ),
    }),

    // 2. 将错误日志写入文件
    new transports.File({
      filename: 'logs/error.log',
      level: 'error',
      format: format.combine(format.timestamp(), format.json()),
    }),

    // 3. 将所有日志写入另一个文件
    new transports.File({
      filename: 'logs/combined.log',
      format: format.combine(format.timestamp(), format.json()),
    }),
  ],
});

// 使用 nest-winston 的辅助函数创建一个 NestJS Logger
export const logger = WinstonModule.createLogger({
  instance: winstonLogger,
});

这里我们配置了三个“输出目的地”(Transports):一个彩色的控制台输出,一个只记录错误的 error.log 文件,以及一个记录所有信息的 combined.log 文件。

第三步:在 main.ts 中“注入”我们的新 Logger

typescript
// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { logger } from './logger/winston.logger'; // 导入我们的自定义 logger

async function bootstrap() {
  const app = await NestFactory.create(AppModule, {
    // 告诉 NestJS 使用我们的 Winston 实例作为默认 Logger
    logger: logger,
  });

  await app.listen(3000);
}
bootstrap();

现在,当你启动应用并再次调用 CatsServicecreate() 方法时,你会发现:

  • 控制台的日志格式变成了我们用 Winston 定义的新格式。
  • 项目根目录下会生成一个 logs 文件夹。
  • logs/combined.log 文件里会包含所有级别的日志(JSON 格式)。
  • 如果你触发了 warnerror 级别的日志,logs/error.log 文件里也会记录下来。

我们成功地、无缝地更换了整个应用的“飞行记录仪”核心!

3. 控制日志的“音量”

在生产环境中,为了性能,我们通常不希望打印大量的 debug 信息。你可以在 main.ts 中设置日志级别。

typescript
// main.ts
async function bootstrap() {
  const app = await NestFactory.create(AppModule, {
    // 只显示 'log', 'warn', 'error' 级别的日志
    logger: ['log', 'warn', 'error'],
  });
  // ...
}

这样设置后,所有通过 logger.debug()logger.verbose() 打印的日志都将被忽略。

总结

日志是保证应用可维护性和可观测性的生命线。

当你...你应该...核心思想
在开发中或进行快速原型设计时直接使用内置的 new Logger('Context')简单、开箱即用。快速地在代码中添加上下文感知的日志输出。
构建生产级应用时创建一个自定义 Logger (实现 LoggerService 接口)强大、可定制。允许你集成专业的日志库(如 Winston, Pino),实现日志分级、文件输出、JSON 格式化等高级功能。
需要替换整个应用的日志系统时NestFactory.create() 的第二个参数中传入 logger 实例全局替换。以一种非侵入式的方式,将整个框架和你的业务日志都交由新的日志系统管理。

不要等到飞机出事了才想起“黑匣子”的重要性。从项目一开始就养成良好的日志记录习惯,使用带有明确上下文和级别的日志,这会在未来的调试和维护工作中为你节省无数个小时。