记录器 Logger

导读

Nest 带有内置的基于文本的记录器,可用于应用程序引导期间和其他几种情况,例如显示捕获的异常(即系统日志记录)。此功能通过 @nestjs/common 包中的 Logger 类提供。您可以完全控制日志系统的行为,包括以下任何一项:

  • 完全禁用日志记录
  • 指定日志详细程度(例如,显示错误、警告、调试信息等)
  • 覆盖默认记录器中的时间戳(例如,使用 ISO8601 标准作为日期格式)
  • 完全覆盖默认记录器
  • 通过扩展默认记录器来自定义它
  • 利用依赖项注入来简化应用程序的编写和测试

您还可以使用内置记录器或创建自己的自定义实现来记录您自己的应用程序级事件和消息。

如需更高级的日志记录功能,您可以使用任何 Node.js 日志记录包(例如 Winston)来实现完全自定义的生产级日志记录系统。

基本自定义

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

ts
const app = await NestFactory.create(AppModule, {
  logger: false,
})
await app.listen(3000)

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

ts
const app = await NestFactory.create(AppModule, {
  logger: ['error', 'warn'],
})
await app.listen(3000)

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

Hint

要禁用默认记录器消息中的颜色,请将 NO_COLOR 环境变量设置为某个非空字符串。

自定义实现

通过将 logger 属性的值设置为满足 LoggerService 接口的对象,您可以为 Nest 提供自定义记录器实现,用于系统日志记录。例如,您可以告诉 Nest 使用内置的全局 JavaScript console 对象(实现 LoggerService 接口),如下所示:

ts
const app = await NestFactory.create(AppModule, {
  logger: console,
})
await app.listen(3000)

实现您自己的自定义记录器非常简单。只需实现 LoggerService 接口的每个方法,如下所示。

ts
import { Injectable, LoggerService } from '@nestjs/common'

@Injectable()
export class MyLogger implements LoggerService {
  /**
   * Write a 'log' level log.
   */
  log(message: any, ...optionalParams: any[]) {}

  /**
   * Write a 'fatal' level log.
   */
  fatal(message: any, ...optionalParams: any[]) {}

  /**
   * Write an 'error' level log.
   */
  error(message: any, ...optionalParams: any[]) {}

  /**
   * Write a 'warn' level log.
   */
  warn(message: any, ...optionalParams: any[]) {}

  /**
   * Write a 'debug' level log.
   */
  debug?(message: any, ...optionalParams: any[]) {}

  /**
   * Write a 'verbose' level log.
   */
  verbose?(message: any, ...optionalParams: any[]) {}
}

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

ts
const app = await NestFactory.create(AppModule, {
  logger: new MyLogger(),
})
await app.listen(3000)

此技术虽然简单,但不利用MyLogger类的依赖注入。这可能会带来一些挑战,特别是对于测试,并限制MyLogger的可重用性。有关更好的解决方案,请参阅下面的依赖注入(techniques/logger#dependency-injection)部分。

Extend built-in logger

您不需要从头开始编写记录器,而是可以通过扩展内置的ConsoleLogger类并覆盖默认实现的选定行为来满足您的需求。

ts
import { ConsoleLogger } from '@nestjs/common'

export class MyLogger extends ConsoleLogger {
  error(message: any, stack?: string, context?: string) {
    // add your tailored logic here
    super.error(...arguments)
  }
}

您可以在功能模块中使用此类扩展记录器,如下面的 使用记录器进行应用程序日志记录 部分所述。

您可以通过应用程序选项对象的 logger 属性传递它的一个实例(如上面的 自定义实现 部分所示),或使用下面 依赖注入 部分中所示的技术,告诉 Nest 使用扩展记录器进行系统日志记录。如果这样做,您应该注意调用 super,如上面的示例代码所示,将特定的日志方法调用委托给父(内置)类,以便 Nest 可以依赖它期望的内置功能。

依赖注入

对于更高级的日志记录功能,您需要利用依赖注入。例如,您可能希望将ConfigService注入到记录器中以对其进行自定义,然后将自定义记录器注入到其他控制器和/或提供程序中。要为自定义记录器启用依赖项注入,请创建一个实现LoggerService的类,并将该类注册为某个模块中的提供程序。例如,您可以

  1. 定义一个MyLogger类,该类可以扩展内置的ConsoleLogger或完全覆盖它,如前几节所示。请确保实现LoggerService接口。
  2. 创建一个如下所示的LoggerModule,并从该模块提供MyLogger
ts
import { Module } from '@nestjs/common'
import { MyLogger } from './my-logger.service'

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

使用此构造,您现在可以提供自定义记录器供任何其他模块使用。由于您的 MyLogger 类是模块的一部分,因此它可以使用依赖项注入(例如,注入 ConfigService)。还需要一种技术来提供此自定义记录器供 Nest 用于系统日志记录(例如,用于引导和错误处理)。

由于应用程序实例化(NestFactory.create())发生在任何模块的上下文之外,因此它不参与初始化的正常依赖项注入阶段。因此,我们必须确保至少一个应用程序模块导入 LoggerModule 以触发 Nest 实例化我们的 MyLogger 类的单例实例。

然后,我们可以指示 Nest 使用以下构造的相同 MyLogger 单例实例:

ts
const app = await NestFactory.create(AppModule, {
  bufferLogs: true,
})
app.useLogger(app.get(MyLogger))
await app.listen(3000)
注意

在上面的示例中,我们将 bufferLogs 设置为 true,以确保所有日志都将被缓冲,直到附加自定义记录器(在本例中为 MyLogger)并且应用程序初始化过程完成或失败。如果初始化过程失败,Nest 将回退到原始 ConsoleLogger 以打印出任何报告的错误消息。此外,您可以将 autoFlushLogs 设置为 false(默认为 true)以手动刷新日志(使用 Logger#flush() 方法)。

在这里,我们在 NestApplication 实例上使用 get() 方法来检索 MyLogger 对象的单例实例。这种技术本质上是一种注入记录器实例以供 Nest 使用的方法。app.get() 调用检索 MyLogger 的单例实例,并依赖于该实例首先被注入另一个模块,如上所述。

您还可以在功能类中注入此 MyLogger 提供程序,从而确保 Nest 系统日志记录和应用程序日志记录的日志记录行为一致。有关更多信息,请参阅下面的 使用记录器进行应用程序日志记录注入自定义记录器

使用记录器进行应用程序日志记录

我们可以结合上述几种技术,在 Nest 系统日志记录和我们自己的应用程序事件/消息日志记录中提供一致的行为和格式。

一个好的做法是在我们的每个服务中从 @nestjs/common 实例化 Logger 类。我们可以在 Logger 构造函数中将我们的服务名称作为 context 参数提供,如下所示:

ts
import { Injectable, Logger } from '@nestjs/common'

@Injectable()
class MyService {
  private readonly logger = new Logger(MyService.name)

  doSomething() {
    this.logger.log('Doing something...')
  }
}

在默认的记录器实现中,context 打印在方括号中,如下例中的 NestFactory

bash
[Nest] 19096 - 2019 年 12 月 8 日,上午 7:12:59 [NestFactory] 正在启动 Nest 应用程序...

如果我们通过 app.useLogger() 提供自定义记录器,它实际上将由 Nest 内部使用。这意味着我们的代码仍然与实现无关,而我们可以通过调用 app.useLogger() 轻松地将默认记录器替换为自定义记录器。

这样,如果我们按照上一节中的步骤并调用 app.useLogger(app.get(MyLogger)),则以下从 MyServicethis.logger.log() 的调用将导致从 MyLogger 实例调用方法 log

这应该适用于大多数情况。但是,如果您需要更多自定义(例如添加和调用自定义方法),请转到下一部分。

注入自定义记录器

首先,使用如下代码扩展内置记录器。我们为 ConsoleLogger 类提供 scope 选项作为配置元数据,指定 transient 范围,以确保我们在每个功能模块中都有 MyLogger 的唯一实例。在此示例中,我们没有扩展单个 ConsoleLogger 方法(例如 log()warn() 等),但您可以选择这样做。

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

@Injectable({ scope: Scope.TRANSIENT })
export class MyLogger extends ConsoleLogger {
  customLog() {
    this.log('Please feed the cat!')
  }
}

接下来,创建一个具有如下结构的LoggerModule

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

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

接下来,将LoggerModule导入到您的功能模块中。由于我们扩展了默认的Logger,因此我们可以方便地使用setContext方法。因此,我们可以开始使用上下文感知的自定义记录器,如下所示:

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

@Injectable()
export class CatsService {
  private readonly cats: Cat[] = []

  constructor(private myLogger: MyLogger) {
    // Due to transient scope, CatsService has its own unique instance of MyLogger,
    // so setting context here will not affect other instances in other services
    this.myLogger.setContext('CatsService')
  }

  findAll(): Cat[] {
    // You can call all the default methods
    this.myLogger.warn('About to return cats!')
    // And your custom methods
    this.myLogger.customLog()
    return this.cats
  }
}

最后,指示 Nest 在 main.ts 文件中使用自定义记录器的实例,如下所示。当然,在这个例子中,我们实际上并没有自定义记录器行为(通过扩展 Logger 方法,如 log()warn() 等),所以实际上不需要此步骤。但如果您向这些方法添加了自定义逻辑并希望 Nest 使用相同的实现,则需要此步骤。

ts
const app = await NestFactory.create(AppModule, {
  bufferLogs: true,
})
app.useLogger(new MyLogger())
await app.listen(3000)
提示

或者,您可以暂时使用 logger: false 指令禁用记录器,而不是将 bufferLogs 设置为 true。请注意,如果您向 NestFactory.create 提供 logger: false,则在调用 useLogger 之前不会记录任何内容,因此您可能会错过一些重要的初始化错误。如果您不介意使用默认记录器记录某些初始消息,则可以省略 logger: false 选项。

使用外部记录器

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