Nest中的日志模块

401 阅读5分钟

在前端开发过程中,通常会使用到控制台打印的方式对程序进行调试,以便记录错误以及定位问题。在企业级开发中我们需要在业务系统中集成日志模块,来及时发现问题产生的时间以及原因,这里将从两个方面来介绍Nest中的日志系统。

  • 常见日志以及记录方式
  • 第三方日志方案:pino(体积小、简单易用)、winston(官方推荐方案、日志信息更详细)

日志分类

按类别分类

在第三方的日志系统中,通常都会实现下列方法(名称可能有所不同),通过以下方法来输出不同等级的日志。

  • Log:通用日志,按需进行打印,用于日常开发调试
  • Warning:警告日志,比如某些包和库版本过低即将淘汰
  • Error:
  • Debug:调试日志
  • Verbose:详细日志,包括所有的操作和详细信息

按功能分类

  • 错误日志:方便开发者定位问题,给予用户友好的提示
  • 调试日志:方便开发
  • 请求日志:记录用户的敏感行为

nest内置日志模块--Logger

一般使用

首先介绍下nest官方内置的日志模块--Logger,我们可以在main.ts中设置日志的监控等级,为logger属性传入一个数组,在数组中填充想要开启的日志等级即可。由于nest中集成了ts,我们可以点到logger中来找到数组更多的可填充值即可用日志等级。同时,我们可以通过new创建的Logger实例对象来调用warn、verbose等方法手动输出日志信息。

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { Logger } from '@nestjs/common';

async function bootstrap() {
  const logger = new Logger();
  const app = await NestFactory.create(AppModule, {
    logger: ['error', 'warn'],
  });
  await app.listen(3000);
  logger.warn('应用已启动,端口:3000');
}
bootstrap();

下方控制台输出中黄色高亮的信息即代码第11行输出的信息。

image.png

在某一指定模块中使用

当在某一指定模块中使用Logger时,我们可以在模块的controller中实例化Logger。以user模块为例,与上一步不同的是这里我们需要额外为Logger构造器传入参数:UserController.name。之后,在构造器中通过this调用并输出相关日志信息时,我们可以看到在[ ]中显示出日志来自哪一模块,方便后续问题的定位。

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

@Controller('user')
export class UserController {
    private logger = new Logger(UserController.name);

    constructor() {
        this.logger.log('user模块实例化完成');
    }

    @Get()
    getUser(): any {
        return ' ';
    }

}

image.png

第三方日志模块--Pino

介绍完Logger之后,再来介绍一种在nodejs项目中常用的轻量级日志模块-pino。在pino的npm介绍中,我们可以看到通过pino完成的日志输出效率相较于其他日志模块来说更快。这里我们需要完成两步操作即可开始使用:

1.安装pino:npm install nestjs-pino

2.在模块中引入依赖并在模块中注册(以演示中的user模块为例)

在user模块中注册pino:

import { Module } from '@nestjs/common';
import { UserController } from './user.controller';
import { LoggerModule } from 'nestjs-pino';

@Module({
    imports: [LoggerModule.forRoot()],
  controllers: [UserController]
})
export class UserModule {
    
}

在controller中通过依赖注入的方式实例化Logger(这里要注意Logger是nestjs-pino包里面的):

import { Controller, Get } from '@nestjs/common';
import { Logger } from 'nestjs-pino';

@Controller('user')
export class UserController {

    constructor(
        private logger: Logger,
    ) {
        this.logger.log('user模块实例化完成');
    }

    @Get('getuser')
    getUser(): any {
        return ' ';
    }

}

可以看到当发送请求后,控制台输出了日志信息,包括请求的方法、url、参数等信息。 image.png

可以看到,pino原始输出的日志信息虽然详细,但是并没有格式化,导致日志的可读性较差。接下来我们可以使用pino-pretty来格式化输出信息。

首先,需要通过 npm i pino-pretty 安装依赖,之后在引入pino模块的module文件中配置中间件:

import { Module } from '@nestjs/common';
import { UserController } from './user.controller';
import { LoggerModule } from 'nestjs-pino';

@Module({
    imports: [LoggerModule.forRoot({
        pinoHttp: {
            transport: {
                target: 'pino-pretty',
                options: {
                    colorize: true,
                }
            }
        }
    })],
  controllers: [UserController]
})
export class UserModule {
    
}

之后,我们可以看到日志信息字体变成彩色,并且具有格式化的效果。

image.png

第三方日志模块--winston

winston是一个集成度较高的日志模块,拥有比pino更多的定制化功能以及更详细的日志信息输出。接下来介绍使用步骤:

  1. 安装依赖 npm i nest-winston winston
  2. 在main.ts中配置winston,替换掉nest官方内置的Logger系统
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { createLogger } from 'winston';
import * as winston from 'winston';
import { WinstonModule, utilities } from 'nest-winston';

async function bootstrap() {
  //  1.创建Logger实例
  const instance = createLogger({
    // 2.配置输出
    transports: [
      new winston.transports.Console({
        format: winston.format.combine(
          winston.format.timestamp(),
          utilities.format.nestLike(),
        ),
      }),
    ],
  });
  const app = await NestFactory.create(AppModule, {
    // 3.将第一步的instance传入
    logger: WinstonModule.createLogger({
      instance,
    }),
  });
  await app.listen(3000);
}
bootstrap();
  1. 其他模块怎么使用winston?这里有两种方式:

    ①.在app模块中将Logger模块暴露,以供其他模块引入使用,我们需要在其他需要使用到winston的模块module文件中手动imports 。

    ②.将app模块设置为全局模块,这样就不用在每个模块中分别import引入了,全局模块可以完成对provider数组中的模块方法的注册

import { Global, Logger, Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UserModule } from './user/user.module';

// 方式②: 将module声明为全局模块
@Global()
@Module({
  imports: [UserModule],
  controllers: [AppController],
  providers: [AppService],
  // 方式①: 暴露模块供其他模块引入,其他模块需要手动imports
  exports: [Logger],
  imports: [],
})
export class AppModule {}
  1. 在想要使用winston日志输出的模块,通过依赖注入的方式获取logger对象,并完成日志输出
import { Controller, Get } from '@nestjs/common';
import { Logger } from '@nestjs/common';

@Controller('user')
export class UserController {

    constructor(
        private logger: Logger,
    ) {
        this.logger.log('user finish');
    }

    @Get('getuser')
    getUser(): any {
        return ' ';
    }

}

image.png

这里我们需要注意的是,winston官方npm仓库下方的配置方法有误,并不能完成logger的依赖注入,可按照本文的方法进行配置。更多使用方法以及配置项请参考文档。