Nest探索(八)Nest 集成日志框架 winston

475 阅读3分钟

💥 在掘金写技术好文,瓜分万元现金大奖 | 4月金石计划

前言

在上一篇文章 Nest探索(七)Nest 中打印日志 中,我们探索了 Nest 中常用的打印日志的方法,包括 Logger 类、LoggerService 接口、通过继承 ConsoleLogger 以实现自定义的 logger ,以及实现依赖注入等。不过,在日常的开发工作中,为了更方便、更高效地管理日志,可以集成 Node.js 的 winston 包。

winston 是什么?正如官方介绍所说:"winston is designed to be a simple and universal logging library with support for multiple transports." winston 是一个 Node.js 日志框架,目前有 22.1k star,支持设置多种 transports、format以及设置不同的日志级别等。

这里,我们将具体实践下。

新建 nest 项目

首先,新建一个 nest 项目。之前也操作了很多次了,没什么难度:

nest new nest-winston-demo-240505 -p pnpm

接着,在 src 目录下,我们添加一个 MyLogger.ts 文件,也就是自定义的日志打印器:

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

export class MyLogger implements LoggerService {
  log(message: string, context: string) {
    console.log(`---log---[${context}]---`, message);
  }

  error(message: string, context: string) {
    console.log(`---error---[${context}]---`, message);
  }

  warn(message: string, context: string) {
    console.log(`---warn---[${context}]---`, message);
  }
}

在 main.ts 文件中引入这个自定义的日志打印器:

import { MyLogger } from "./MyLogger";

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useLogger(new MyLogger());
  await app.listen(3000);
}

运行服务:

pnpm run start:dev

我们可以看到自定义 logger 生效了:

[Nest] 16084  - 2024/05/05 23:46:50     LOG [NestFactory] Starting Nest application...
[Nest] 16084  - 2024/05/05 23:46:50     LOG [InstanceLoader] AppModule dependencies initialized +25ms
---log---[RoutesResolver]--- AppController {/}:
---log---[RouterExplorer]--- Mapped {/, GET} route
---log---[NestApplication]--- Nest application successfully started

接着,在 AppController 里添加 logger:

import { Controller, Get, Logger } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  private logger = new Logger()

  @Get()
  getHello(): string {
    this.logger.log('hhh', AppController.name);
    return this.appService.getHello();
  }
}

浏览器访问 http://localhost:3000/ ,可以看到打印了

---log---[AppController]--- hhh

也就是说,自定义 logger 生效了。

集成 winston

在 Nest 应用中成功地引入自定义的 logger 后,我们接着集成 winston 日志框架,并进行相关的设置。

首先,安装 winston 包:

pnpm i winston

接着,在 MyLogger.ts 中引入 winston ,这里是使用 createLogger() 初始化 this.logger 属性,并替换原来的 console.log 为 this.logger.log() 等方法。

import { LoggerService, LogLevel } from '@nestjs/common';
import { createLogger, format, Logger, transports } from 'winston';

export class MyLogger implements LoggerService {
  private logger: Logger;

  constructor() {
    this.logger = createLogger({
      level: 'debug',
      format: format.combine(format.colorize(), format.simple()),
      transports: [new transports.Console()],
    });
  }

  log(message: string, context: string) {
    this.logger.log('info', `[${context}] ${message}`);
  }

  error(message: string, context: string) {
    this.logger.log('error', `[${context}] ${message}`);
  }

  warn(message: string, context: string) {
    this.logger.log('warn', `[${context}] ${message}`);
  }
}

可以看到有新的打印,这里打印的日志就是 winston 的日志记录了。

[Nest] 15600  - 2024/05/05 23:52:12     LOG [NestFactory] Starting Nest application...
[Nest] 15600  - 2024/05/05 23:52:12     LOG [InstanceLoader] AppModule dependencies initialized +10ms
info: [RoutesResolver] AppController {/}:
info: [RouterExplorer] Mapped {/, GET} route
info: [NestApplication] Nest application successfully started

但是,我们不难发现日志的样式有点不规范,如何进行自定义和美化呢?

这里,引入了 dayjs 用于格式化输出日期,引入 chalk@4 用于打印自定义的颜色。

pnpm i dayjs chalk@4

相关配置如下,具体是,设置 transports 数组:

  • new transports.Console() 设置打印日志的格式,最终输出内容设置为${appStr} ${time} ${level} ${contextStr} ${message}
  • new transports.File() 设置输出日志的格式,这里是通过format: format.combine(format.timestamp(), format.json()), 指定为 json 格式,并加上时间戳;
import { LoggerService, LogLevel } from '@nestjs/common';
import { createLogger, format, Logger, transports } from 'winston';
import * as chalk from 'chalk';
import * as dayjs from 'dayjs';

export class MyLogger implements LoggerService {
  private logger: Logger;

  constructor() {
    this.logger = createLogger({
      level: 'debug',
      transports: [
        // 设置打印日志的格式
        new transports.Console({
          format: format.combine(
            format.colorize(),
            format.printf(({ context, level, message, time }) => {
              const appStr = chalk.green(`[NEST]`);
              const contextStr = chalk.yellow(`[${context}]`);
              return `${appStr} ${time} ${level} ${contextStr} ${message} `;
            }),
          ),
        }),
        // 设置输出日志的格式
        new transports.File({
          // 指定为 json 格式,加上时间戳
          format: format.combine(
            format.timestamp(),
            format.json()
          ),
          filename: '111.log',
          dirname: 'log'
        })
      ],
    });
  }

  log(message: string, context: string) {
    const time = dayjs(Date.now()).format('YYYY-MM-DD HH:mm:ss');
    this.logger.log('info', message, { context, time });
  }

  error(message: string, context: string) {
    const time = dayjs(Date.now()).format('YYYY-MM-DD HH:mm:ss');
    this.logger.log('info', message, { context, time });
  }

  warn(message: string, context: string) {
    const time = dayjs(Date.now()).format('YYYY-MM-DD HH:mm:ss');
    this.logger.log('info', message, { context, time });
  }
}

最终,可以看到打印日志格式为:

[Nest] 11500  - 2024/05/05 23:53:07     LOG [NestFactory] Starting Nest application...
[Nest] 11500  - 2024/05/05 23:53:07     LOG [InstanceLoader] AppModule dependencies 
initialized +22ms
[NEST] 2024-05-05 23:53:08 info [RoutesResolver] AppController {/}: 
[NEST] 2024-05-05 23:53:08 info [RouterExplorer] Mapped {/, GET} route 
[NEST] 2024-05-05 23:53:08 info [NestApplication] Nest application successfully started

并在 log 目录下生成了日志文件 111.log ,每行的内容均为 json 格式的文本。

至此,我们就完成了 nest 中日志框架 winston 的集成。

后记

总的来说,在日常的开发工作中,我们可以通过集成 Node.js 的 winston 日志框架,实现更方便、更高效地管理 Nest 应用中的日志的需求。

我们可以设置 winston 的多种 transports ,实现设置不同的打印日志的格式;或是设置输出日志的格式,指定输出内容为 json 格式、指定输出文件的目录以及文件格式等等。

此外,我们可以借助 dayjs 和 chalk,规范和美化输出日志的格式,实现相应的定制化开发的需求。

参考