NestJS 日志

2023-09-08 17:41 更新

Nest 附带一个默认的内部日志记录器实现,它在实例化过程中以及在一些不同的情况下使用,比如发生异常等等(例如系统记录)。这由 @nestjs/common 包中的 Logger 类实现。你可以全面控制如下的日志系统的行为:

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

你也可以使用内置日志记录器,或者创建你自己的应用来记录你自己应用水平的事件和消息。

更多高级的日志功能,可以使用任何 Node.js 日志包,比如Winston,来生成一个完全自定义的生产环境水平的日志系统。

基础自定义

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

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

你也可以只启用特定日志级别,设置一个字符串形式的 logger 属性数组以确定要显示的日志水平,如下:

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

数组中的字符串可以是以下字符串的任意组合: log , error , warn , debug 和 verbose 。

你可以通过设置 NO_COLOR 环境变量为非空字符串来禁用默认日志信息的颜色

自定义应用

你可以提供一个自定义日志记录器应用,并由 Nest 作为系统记录使用,这需要设置logger 属性到一个满足 LoggerService 接口的对象。例如,你可以告诉 Nest 使用内置的全局 JavaScript console 对象(其实现了 LoggerService 接口),如下:

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

应用你的自定义记录器很简单。只要简单实现以下 LoggerService 接口中的每个方法就可以:

import { LoggerService } from '@nestjs/common';

export class MyLogger implements LoggerService {
  /**
   * Write a 'log' level log.
   */
  log(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[]) {}
}

你可以通过 logger 属性为 Nest 应用的选项对象提供一个 MyLogger 实例:

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

这个技术虽然很简单,但是没有为 MyLogger 类应用依赖注入。这会带来一些挑战,尤其在测试方面,同时也限制了 MyLogger 的重用性。更好的解决方案参见如下的依赖注入部分。

扩展内置的日志类

很多实例操作需要创建自己的日志。你不必完全重新发明轮子。只需继承内置 ConsoleLogger 类以部分覆盖默认实现,并使用 super 将调用委托给父类。

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

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

你可以按如下使用应用记录器来记录部分所述,从你的特征模块中使用扩展记录器。

你可以把你的扩展日志记录器的实例传递到应用选项对象的 logger 属性来让 Nest 使用你的日志记录器记录系统日志(如自定义应用所述),也可以按照如下的依赖注入部分。如果你这样做,你在调用 super 时要小心,如上述代码示例,要委托一个特定的日志方法,调用其父(内置)类,以便 Nest 可以依赖需要的内置特征。

依赖注入

你可能需要利用依赖注入的优势来使用高级的日志记录功能。例如,你可能想把 ConfigService 注入到你的记录器中来对它自定义,然后把自定义记录器注入到其他控制器和/或提供者中。要为你的自定义记录器启用依赖注入,创建一个实现 LoggerService 的类并将其作为提供者注册在某些模块中,例如,你可以:

  1. 定义一个 MyLogger 类来继承内置的 ConsoleLogger 或者完全覆盖它,如前节所述。注意一定要实现 LoggerService 接口。
  2. 创建一个 LoggerModule 如下所示,从该模块中提供 MyLogger 。
import { Module } from '@nestjs/common';
import { MyLogger } from './my-logger.service.ts';

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

通过这个结构,你现在可以提供你的自定义记录器供其他任何模块使用。因为你的 MyLogger 类是模块的一部分,它也可以使用依赖注入(例如,注入一个 ConfigService )。提供自定义记录器供使用还需要一个技术,即 Nest 的系统记录(例如,供 bootstrapping 和 error handling )。

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

我们可以在之后按照下列知道来告诉 Nest 使用同一个 MyLogger 实例。

const app = await NestFactory.create(ApplicationModule, {
  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 类。我们可以将提供者的名字当作 context 参数传入 Logger 的构造函数,就像这样:

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

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

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

在默认的日志记录器实现中, context 是包裹在方括号中被打印出来,就像下面例子中的 NestFactory :

[Nest] 19096   - 12/08/2019, 7:12:59 AM   [NestFactory] Starting Nest application...

如果我们通过 app.useLogger() 提供一个自定义日志记录器,那么它会在 Nest 内部被使用。这就意味着我们的代码可以保持与实现无关,因为我们可以简单地调用 app.useLogger() 用默认日志记录器来代替自定义的那个。

如果我们跟着前面章节一步步做下来并且调用了 app.useLogger(app.get(MyLogger)) ,那么接下来在 MyService 中对 this.logger.log() 的调用会造成对 MyLogger 实例中方法 log 的调用。

这个应该在大多数情况下都适用。但是你如果想要更深入的自定义(比如增加或者调用自定义方法),请看下一章节。

注入自定义日志记录器

通过像下面一样的扩展内置的日志记录器来开始这一章节。我们传入 scope 选项来配置 ConsoleLogger 的元数据,通过指定瞬态作用域来保证在每个模块内都有独一无二的 MyLogger 的实例。在下面的例子中,我们没有扩展每个单独的 ConsoleLogger 方法(比如 log() , warn() 之类),尽管你可能会选择去这么做。

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

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

接下来,我们采用如下结构创建一个 LoggerModule 。

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

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

然后,在你的模块中导入 LoggerModule 。因为我们继承了默认的 Logger ,所以我们可以很方便地调用 setContext 方法。之后我们就可以像下面一样开始使用这个包含了上下文的日志记录器了。

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

  constructor(private myLogger: MyLogger) {
    // 因为瞬态作用域的原因, CatService 有属于自己的独一无二的 MyLogger 实例,
    // 所以在这里设置上下文不会影响到其他提供者的实例
    this.myLogger.setContext('CatsService');
  }

  findAll(): Cat[] {
    // 你可以调用所有的默认方法
    this.myLogger.warn('About to return cats!');
    // 当然还有你的自定义方法
    this.myLogger.customLog();
    return this.cats;
  }
}

最后,和下面一样在你的 main.ts 文件中让 Nest 使用自定义日志记录器的实例。当然这只是个例子,我们还没有真正自定义日志记录器的行为(通过扩展像 log() , warn() 这些 Logger 的方法),所以这一步并不一定需要。但是如果你给这些方法增加了自定义的逻辑而且你想让 Nest 去使用这个实现,那么你还是会需要这一步。

const app = await NestFactory.create(ApplicationModule, {
  bufferLogs: true,
});
app.useLogger(new MyLogger());
await app.listen(3000);

除了把 bufferLogs 设置为true,你也可以声明 logger: false 来临时禁用日志记录器。需要注意的是如果你给 NestFactory.create 提供了 logger: false ,在你调用 useLogger 以前没有东西会被记录进日志,所以你可能会错过一些重要的初始化错误。如果你不在意一些初始化信息会使用默认日志记录器来记录,那你可以直接设置 logger: false 。

使用外部记录器

生产环境应用通常包括特定的记录需求,包括高级过滤器,格式化和中心化记录。Nest 的内置记录器用于监控 Nest 系统状态,在开发时也可以为你的特征模块提供实用的基础的文本格式的记录,但生产环境可能更倾向于使用类似Winston的模块,这是一个标准的 Node.js 应用,你可以在 Nest 中体验到类似模块的优势。


以上内容是否对您有帮助:
在线笔记
App下载
App下载

扫描二维码

下载编程狮App

公众号
微信公众号

编程狮公众号