在Nest中使用Winston记录日志,nest-winston-module使用指南

4,731 阅读4分钟

Nest Logo

Nestjs Winston模块,提供灵活的日志记录方式。

参考自 nest-winston

安装

npm install --save nest-winston-module winston

快速开始

导入 WinstonModule 至 nest应用的根module(通常是AppModule),并使用forRoot()方法来配置nest-winston-module. 创建参数与winston.createLogger方法所需的参数一致,可参考winston官方文档:createLogger()

import { Module } from '@nestjs/common';
import { WinstonModule } from 'nest-winston-module';
import * as winston from 'winston';

@Module({
  imports: [
    WinstonModule.forRoot({
      // options
    }),
  ],
})
export class AppModule {}

在此之后,我们便可以使用WinstontokenWinston实例注入我们的nest应用中去, nest-winston-module为应用的不同模块提供了一个token枚举,从而使处理不同类型的日志更加容易。看看下边这个示例:

enum WinstonProviderEnum {
  /**
   * @description 替换 nest core Logger
   */
  coreProvider = 'winstonCoreProvider',
  /**
   * @description 应用层级 level Logger
   */
  appProvider = 'winstonAppProvider',
  /**
   * @description controller Logger
   */
  controllerProvider = 'winstonControllerProvider',
  /**
   * @description graphQL resolver Logger
   */
  resolverProvider = 'winstonResolverProvider',
  /**
   * @description service Logger
   */
  serviceProvider = 'winstonServiceProvider',
  /**
   * @description 控制台 Logger
   */
  consoleProvider = 'winstonConsoleProvider',
  /**
   * @description  所有Loggers的集合 
   */
  loggersProvider = 'winstonLoggersProvider',
}

这是在Nest Contrroler中使用WinstonLogger的示例:

import { Controller, Inject } from '@nestjs/common';
import { WinstonProviderEnum, NestWinstonLogger } from 'nest-winston-module';

@Controller('cats')
export class CatsController {
  // 使用提供的WinstonProviderEnum枚举来注入Winstoncontroller Logger至Cats Controller中去
  constructor(@Inject(WinstonProviderEnum.controllerProvider) private readonly logger: NestWinstonLogger) { }
}

请注意,WinstonModule是一个全局模块,它将在所有功能模块中可用。

Nest Async configuration(异步配置注入)

注意事项⚠️: 因为Nest的工作方式,所以我们无法注入从根模块本身导出的依赖项(使用exports)。 当我们使用forRootAsync()并且需要注入服务,则必须使用imports选项导入该服务,或者必须从global module 导出该服务

有时我们需要异步传递options,例如,当服务的启动依赖于动态配置项的读取。 在这种情况下,请使用forRootAsync()方法,并从useFactory方法返回一个选项对象:

import { Module } from '@nestjs/common';
import { WinstonModule } from 'nest-winston-module';
import * as winston from 'winston';

@Module({
  imports: [
    WinstonModule.forRootAsync({
      useFactory: () => ({
        // options, 后续会有配置示例
      }),
      inject: [],
    }),
  ],
})
export class AppModule {}

useFactory创建模块的步骤可能是异步的,我们可以使用inject选项注入依赖项,并可以使用imports选项导入其他模块。

另外,我们可以使用useClass语法:

WinstonModule.forRootAsync({
  useClass: WinstonConfigService,
})

使用上述代码,Nest将创建一个新的WinstonConfigService实例,并调用其方法createWinstonModuleOptions以提供模块选项。

替换Nest Core Logger

除了应用程序层级的日志记录之外,nest-winston-module还提供了WinstonLogger定制实现,可与Nest自带的日志记录系统一起使用。 以下是示例main.ts文件:

import { WinstonProviderEnum } from 'nest-winston-module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useLogger(app.get(WinstonProviderEnum.coreProvider));
}
bootstrap();

NestApplication实例上的get()方法用于检索WinstonLogger类的实例,该实例仍使用WinstonModule.forRootWinstonModule.forRootAsync方法进行配置。

注意,当我们替换Nest Core Logger时,只能使用WinstonProviderEnum.coreProvider token来做替换。 因为WinstonNest所遵循的Logger接口并不完全相同,我们在框架内部对coreProvider做了微妙的适配。

替换Nest Core Logger (并且用作启动服务实例)

使用如上方式有一个小缺点。 Nest必须先引导应用程序(实例化模块和提供程序,注入依赖关系等),在此过程中,WinstonLogger实例尚不可用,这意味着Nest会退回到内部Logger

这里还有一种解决方案,是使用createLogger函数在应用程序生命周期之外创建Logger,并将其传递给NestFactory.create

import { WinstonModule } from 'nest-winston-module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule, {
    logger: WinstonModule.createLogger({
      // options (same as WinstonModule.forRoot() options)
    })
  });
}
bootstrap();

不过使用这种方式,我们便无法使用Nest的依赖注入特性了。这意味着WinstonModule.forRootWinstonModule.forRootAsync方法么得用了。

要在我们的应用程序中也使用core Logger,请看如下示例:

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

@Module({
    providers: [Logger],
})
export class AppModule {}
import { Controller, Inject, Logger, LoggerService } from '@nestjs/common';

@Controller('cats')
export class CatsController {
  constructor(@Inject(Logger) private readonly logger: LoggerService) { }
}

上述代码之所以可行,是因为Nest Logger包装了我们的WinstonLogger(由createLogger方法返回的相同实例)并将所有调用转发给它。

配置

我们可以使用nest-winston-module提供的默认配置项,或者根据业务的不同进行自定义操作:

import { Module } from '@nestjs/common';
import {
  coreOptions,
  appOptions,
  controllerOptions,
  resolverOptions,
  serviceOptions,
  consoleOptions,
} from 'nest-winston-module';
import * as winston from 'winston';

@Module({
  imports: [
    WinstonModule.forRoot({
        core: coreOptions,
        app: appOptions,
        resolver: resolverOptions,
        controller: controllerOptions,
        service: serviceOptions,
        console: consoleOptions,
        directory: process.cwd() + '/logs',
    }),
  ],
})
export class AppModule {}