Skip to content

日志

Nest 内置了一个基于文本的日志器,在应用程序启动以及其他一些场景中使用,例如显示捕获的异常(即系统日志记录)。此功能通过 @nestjs/common 包中的 Logger 类提供。你可以完全控制日志系统的行为,包括以下任何方面:

  • 完全禁用日志
  • 指定日志详细级别(例如,显示错误、警告、调试信息等)
  • 配置日志消息的格式(原始文本、JSON、彩色输出等)
  • 覆盖默认日志器中的时间戳(例如,使用 ISO8601 标准作为日期格式)
  • 完全覆盖默认日志器
  • 通过扩展默认日志器来自定义它
  • 利用依赖注入来简化应用程序的组合和测试

你也可以使用内置日志器,或创建自己的自定义实现,来记录应用程序级别的事件和消息。

如果你的应用程序需要与外部日志系统集成、自动基于文件的日志记录,或将日志转发到集中式日志服务,你可以使用 Node.js 日志库实现完全自定义的日志解决方案。一个流行的选择是 Pino,以其高性能和灵活性著称。

基本自定义

要禁用日志,在传递给 NestFactory.create() 方法第二个参数的(可选的)Nest 应用程序选项对象中,将 logger 属性设置为 false

typescript
const app = await NestFactory.create(AppModule, {
  logger: false,
});
await app.listen(process.env.PORT ?? 3000);

要启用特定的日志级别,将 logger 属性设置为一个字符串数组,指定要显示的日志级别,如下所示:

typescript
const app = await NestFactory.create(AppModule, {
  logger: ['error', 'warn'],
});
await app.listen(process.env.PORT ?? 3000);

数组中的值可以是 'log''fatal''error''warn''debug''verbose' 的任意组合。

提示

Nest 中的日志级别是级联的(可继承的)。这意味着提供一个特定的日志级别(如 'log')将自动包含所有更高严重级别(例如 'warn''error''fatal')。

要禁用彩色输出,传递一个将 colors 属性设置为 falseConsoleLogger 对象作为 logger 属性的值。

typescript
const app = await NestFactory.create(AppModule, {
  logger: new ConsoleLogger({
    colors: false,
  }),
});

要为每条日志消息配置前缀,传递一个设置了 prefix 属性的 ConsoleLogger 对象:

typescript
const app = await NestFactory.create(AppModule, {
  logger: new ConsoleLogger({
    prefix: 'MyApp', // 默认值为 "Nest"
  }),
});

以下是所有可用选项的完整列表:

选项类型描述
logLevelsLogLevel[]启用的日志级别。将 undefined 设置为激活所有日志级别。
timestampboolean如果设置为 true,将在日志消息中包含当前日期和时间。
prefixstring在日志消息中作为前缀显示的文本。默认值为 "Nest"
jsonboolean如果设置为 true,将以 JSON 格式输出日志消息。
colorsboolean如果设置为 true,将在日志消息中启用颜色。
contextstring在日志消息中显示的上下文字符串。
compactboolean如果设置为 true,日志消息将以单行格式输出(仅在 json 模式下有效)。

JSON 日志

JSON 日志对于现代应用程序的可观测性以及与日志管理系统的集成至关重要。要在 NestJS 应用程序中启用 JSON 日志,配置 ConsoleLogger 对象并将其 json 属性设置为 true

typescript
const app = await NestFactory.create(AppModule, {
  logger: new ConsoleLogger({
    json: true,
  }),
});

启用后,每条日志消息将作为 JSON 对象输出,包含以下字段:

  • level - 日志级别(logerrorwarndebugverbosefatal
  • message - 日志消息
  • context - 上下文(如果提供)
  • timestamp - 时间戳(如果已启用)
  • pid - 进程 ID

这种格式非常适合与 Elasticsearch、Datadog、AWS CloudWatch 等日志聚合工具配合使用。

在应用日志中使用日志器

我们可以组合使用 Logger 类(来自 @nestjs/common 包)的几种技术来提供一致的日志行为。

要在整个应用程序中使用日志器,可以从 @nestjs/common 导入 Logger 类并在服务中实例化它。这种方式允许我们提供 context(上下文),如当前类名,使日志更易于跟踪:

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

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

  findAll() {
    this.logger.log('查找所有猫');
    // 业务逻辑
  }
}

Logger 类提供了以下方法来输出不同级别的日志消息:

typescript
this.logger.log('信息消息');
this.logger.error('错误消息', stackTrace);
this.logger.warn('警告消息');
this.logger.debug('调试消息');
this.logger.verbose('详细消息');
this.logger.fatal('致命错误消息');

在上面的示例中,我们向构造函数传递了 CatsService.name(通常是类名),作为每条日志消息的 context 参数。如果你将内置日志器或自定义日志器替换为其他实现,此上下文将在日志输出中显示,帮助你快速定位日志来源。

带时间戳的日志

要启用时间戳,在 ConsoleLogger 选项中将 timestamp 设置为 true

typescript
const app = await NestFactory.create(AppModule, {
  logger: new ConsoleLogger({
    timestamp: true,
  }),
});

自定义实现

你可以提供一个自定义日志器实现,Nest 会使用它进行系统日志记录(如启动信息、错误报告等)。要实现自定义日志器,你需要实现 LoggerService 接口中的所有方法,或者扩展 ConsoleLogger 类并覆盖相应方法。

typescript
// my-logger.service.ts
import { LoggerService } from '@nestjs/common';

export class MyLogger 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[]) {
    // 自定义逻辑
  }

  fatal?(message: any, ...optionalParams: any[]) {
    // 自定义逻辑
  }
}

然后,你可以通过 Nest 应用程序选项对象的 logger 属性提供 MyLogger 的实例。

typescript
const app = await NestFactory.create(AppModule, {
  logger: new MyLogger(),
});
await app.listen(process.env.PORT ?? 3000);

这种方式虽然简单,但没有为 MyLogger 类使用依赖注入。这可能会带来一些挑战,尤其是在测试方面,并且限制了 MyLogger 的可复用性。更好的解决方案请参阅下文的依赖注入部分。

扩展内置日志器

与其从头开始编写日志器,你可以通过扩展内置的 ConsoleLogger 类并覆盖默认实现的选定行为来满足需求。

typescript
// my-logger.service.ts
import { ConsoleLogger } from '@nestjs/common';

export class MyLogger extends ConsoleLogger {
  error(message: unknown, stack?: string, context?: string) {
    // 在此添加你的自定义逻辑
    super.error(message, stack, context);
  }

  warn(message: unknown, context?: string) {
    // 在此添加你的自定义逻辑
    super.warn(message, context);
  }
}

你可以在功能模块中使用扩展的日志器,如下面的在应用日志中使用日志器部分所述。

你可以通过 bufferLogs 选项让 Nest 使用你的扩展日志器来进行系统日志记录(如前面自定义实现部分所述),或使用以下技术:

typescript
const app = await NestFactory.create(AppModule, {
  bufferLogs: true,
});
app.useLogger(new MyLogger());
await app.listen(process.env.PORT ?? 3000);

提示

bufferLogs 属性设为 true 时,Nest 会在内部缓冲所有日志消息,直到自定义日志器被关联后再将它们输出。如果应用程序初始化过程失败或被中止,所有缓冲的日志消息将通过原始的 ConsoleLogger 输出。

要在 main.ts 中设置后替换 Logger(而非通过 NestFactory.create 选项),可使用 app.useLogger() 方法:

typescript
const app = await NestFactory.create(AppModule);
app.useLogger(new MyLogger());

依赖注入

对于更高级的日志功能,你可能希望利用依赖注入。例如,你可能想将 ConfigService 注入到日志器中以自定义它,或者将自定义日志器注入到其他服务中。

要启用依赖注入,将 MyLogger 类注册为提供者并创建模块:

typescript
// logger.module.ts
import { Module } from '@nestjs/common';
import { MyLogger } from './my-logger.service';

@Module({
  providers: [MyLogger],
  exports: [MyLogger],
})
export class LoggerModule {}

然后更新 MyLogger 以使用依赖注入(例如注入 ConfigService):

typescript
// my-logger.service.ts
import { Injectable, ConsoleLogger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';

@Injectable()
export class MyLogger extends ConsoleLogger {
  constructor(private configService: ConfigService) {
    super();
    // 使用 configService 来配置日志器
  }
}

接下来,使用如下构造来通知 Nest 使用你的自定义日志器。在此代码中,我们将 LoggerModule 导入到 AppModule 中,并使用 app.useLogger() 方法将 MyLogger 实例设置为全局日志器:

typescript
// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { MyLogger } from './my-logger/my-logger.service';

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

提示

在上面的示例中,我们将 bufferLogs 设置为 true,以确保所有日志在自定义日志器(此处为 MyLogger)附加之前都被缓冲,如果初始化过程失败,Nest 将回退到默认的 ConsoleLogger 并刷新所有缓冲的日志消息。

注入自定义日志器

要在应用程序的其他服务中注入自定义日志器,只需将其添加到构造函数中。确保 LoggerModule 已被导入到使用该日志器的模块中(或在 @Module 装饰器中将其设为全局模块)。

typescript
// cats.service.ts
import { Injectable } from '@nestjs/common';
import { MyLogger } from './my-logger/my-logger.service';

@Injectable()
export class CatsService {
  constructor(private myLogger: MyLogger) {
    this.myLogger.setContext('CatsService');
  }

  findAll() {
    this.myLogger.log('查找所有猫');
    // ...
  }
}

使用外部日志器

生产环境的应用程序通常有特定的日志需求,包括高级过滤、格式化和集中式日志管理。Nest 的内置日志器用于监控 Nest 系统行为,在开发阶段也可用于功能模块中的基本格式化文本日志记录,但生产应用程序通常会利用专门的日志模块,如 Winston。与任何标准的 Node.js 应用程序一样,你可以在 Nest 中充分利用这些模块。

基于 NestJS 官方文档翻译