WEBKT

NestJS 进阶:用 Winston 和日志轮转,告别无限膨胀的日志文件!

176 0 0 0

嘿,老铁们!我是老码农,今天咱们聊聊在 NestJS 项目里,如何优雅地处理日志,避免日志文件越滚越大,最后把硬盘都塞满的情况。特别是对于那些高并发、需要大量日志输出的项目,一个好的日志方案至关重要。咱们用 Winston 和 winston-daily-rotate-file 这对黄金搭档,轻松实现日志轮转,让你的项目更健康,运维更省心。

1. 为什么需要日志轮转?

首先,咱们得明白为啥要搞日志轮转。试想一下,你的 NestJS 项目每天产生几百万甚至上千万条日志,如果都写到一个文件里,会发生什么?

  • 文件越来越大: 很快,你的日志文件就会变得巨大无比,打开、搜索、备份都会变得非常缓慢。这不仅影响开发效率,也给运维带来了挑战。
  • 磁盘空间耗尽: 如果没有限制,日志文件可能会吃掉你服务器的所有磁盘空间,导致系统崩溃。
  • 难以查找问题: 巨量的日志信息会让定位问题变得非常困难,你需要在海量数据中找到关键的错误信息,简直是海底捞针。

所以,我们需要一种机制,定期地创建新的日志文件,并将旧的日志文件进行归档或删除,这就是日志轮转。这样,我们可以控制单个日志文件的大小,保留一定时间范围内的日志,方便排查问题,也避免了磁盘空间被耗尽的风险。

2. Winston 和 winston-daily-rotate-file 简介

  • Winston: Winston 是一个功能强大、灵活的 Node.js 日志库。它支持多种日志级别(如 debug, info, warn, error),可以方便地将日志输出到控制台、文件、数据库等多种目标。Winston 的核心是 transports,你可以配置不同的 transports 来定义日志的输出方式。
  • winston-daily-rotate-file 这个包是 Winston 的一个 transport,专门用来实现日志轮转。它可以按照日期、大小等规则自动创建新的日志文件,并将旧的日志文件进行归档或删除。这样,我们就可以轻松地控制日志文件的大小和数量。

3. 在 NestJS 中配置 Winston 和日志轮转

下面,咱们一步一步来,看看如何在 NestJS 项目中使用 Winston 和 winston-daily-rotate-file

3.1 安装依赖

首先,你需要安装 Winston 和 winston-daily-rotate-file

npm install winston winston-daily-rotate-file --save

3.2 创建日志模块

接下来,咱们创建一个 NestJS 模块来管理 Winston 的配置。这可以让你更方便地在整个项目中引用和使用日志。

// logger.module.ts
import { Module } from '@nestjs/common';
import { WinstonModule } from 'nest-winston';
import * as winston from 'winston';
import * as DailyRotateFile from 'winston-daily-rotate-file';
import { utilities as nestWinstonModuleUtilities } from 'nest-winston';

@Module({
  imports: [
    WinstonModule.forRoot({
      transports: [
        // 每天创建一个新的日志文件
        new DailyRotateFile({
          dirname: 'logs', // 日志文件存放的目录
          filename: '%DATE%.log', // 日志文件名,%DATE% 会被替换为日期
          datePattern: 'YYYY-MM-DD', // 日期格式
          zippedArchive: true, // 是否压缩旧的日志文件
          maxSize: '20m', // 单个日志文件的最大大小,单位是 MB
          maxFiles: '14d', // 保留的日志文件数量,这里是 14 天
          format: winston.format.combine(
            winston.format.timestamp(), // 添加时间戳
            winston.format.printf(({ timestamp, level, message }) => {
              return `${timestamp} [${level.toUpperCase()}] ${message}`;
            }),
          ),
        }),
        // 在控制台输出日志
        new winston.transports.Console({
          format: winston.format.combine(
            winston.format.colorize(), // 颜色高亮
            winston.format.timestamp(), // 添加时间戳
            nestWinstonModuleUtilities.format.nestLike('MyApp', { // 美化 NestJS 的日志格式
              prettyPrint: true,
            }),
          ),
        }),
      ],
      // 异常处理
      exceptionHandlers: [
        new DailyRotateFile({
          dirname: 'logs', // 异常日志文件存放的目录
          filename: 'exceptions-%DATE%.log', // 异常日志文件名
          datePattern: 'YYYY-MM-DD', // 日期格式
          zippedArchive: true, // 是否压缩旧的异常日志文件
          maxSize: '20m', // 单个异常日志文件的最大大小
          maxFiles: '14d', // 保留的异常日志文件数量
          format: winston.format.combine(
            winston.format.timestamp(), // 添加时间戳
            winston.format.printf(({ timestamp, level, message }) => {
              return `${timestamp} [${level.toUpperCase()}] ${message}`;
            }),
          ),
        }),
      ],
    }),
  ],
  exports: [WinstonModule],
})
export class LoggerModule {}

在这个模块中,我们做了以下几件事:

  • 导入必要的模块: WinstonModule, winston, DailyRotateFile
  • 配置 forRoot 这是 Winston 模块的核心,我们在这里配置了 transportsexceptionHandlers
    • transports 定义了日志的输出方式。我们配置了两个 transports
      • DailyRotateFile:将日志写入文件,并按照日期进行轮转。dirname 指定日志文件的存放目录,filename 指定日志文件名,datePattern 指定日期格式,zippedArchive 表示是否压缩旧的日志文件,maxSize 指定单个日志文件的最大大小,maxFiles 指定保留的日志文件数量。
      • Console:将日志输出到控制台,方便开发调试。我们使用了 colorize 来给日志添加颜色,timestamp 添加时间戳,nestLike 美化 NestJS 的日志格式。
    • exceptionHandlers 定义了异常日志的输出方式。我们配置了一个 DailyRotateFile,将异常信息写入单独的日志文件,方便排查问题。
  • exports: [WinstonModule] 将 WinstonModule 导出,这样其他模块就可以通过 WinstonModule.forFeature() 来使用 Winston 了。

3.3 使用日志

现在,我们可以在任何需要使用日志的地方注入 LoggerService 并使用它。

// app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { LoggerModule } from './logger.module';

@Module({
  imports: [LoggerModule],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}
// app.controller.ts
import { Controller, Get, Inject } from '@nestjs/common';
import { AppService } from './app.service';
import { Logger } from 'winston';
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';

@Controller()
export class AppController {
  constructor(
    private readonly appService: AppService,
    @Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger,
  ) {}

  @Get()
  getHello(): string {
    this.logger.debug('This is a debug message.');
    this.logger.info('This is an info message.');
    this.logger.warn('This is a warn message.');
    this.logger.error('This is an error message.');
    return this.appService.getHello();
  }
}

在这个例子中,我们:

  • 引入 LoggerModuleAppModule 中引入我们创建的日志模块。
  • 注入 WINSTON_MODULE_PROVIDERAppController 中,我们使用 @Inject(WINSTON_MODULE_PROVIDER) 注入了 Winston 的 Logger 实例。WINSTON_MODULE_PROVIDER 是 NestJS 提供的用于访问 Winston 实例的令牌。
  • 使用日志: 我们可以通过 this.logger 来使用 Winston 的各种日志级别,例如 debug, info, warn, error

3.4 运行和测试

启动你的 NestJS 项目,访问你的 API 接口(例如 http://localhost:3000),你会在控制台看到日志输出,同时在 logs 目录下也会生成日志文件。你可以观察日志文件的大小和数量,看看是否符合你的预期。如果一切正常,那么恭喜你,你已经成功配置了 Winston 和日志轮转!

4. 进阶配置和优化

上面的例子是一个基本的配置,你可以根据自己的需求进行更高级的配置和优化。

4.1 日志级别

Winston 支持多种日志级别,从低到高分别是:silly, debug, verbose, info, warn, error。你可以根据不同的环境(如开发、测试、生产)设置不同的日志级别,来控制输出的日志信息量。

// logger.module.ts
import { Module } from '@nestjs/common';
import { WinstonModule } from 'nest-winston';
import * as winston from 'winston';
import * as DailyRotateFile from 'winston-daily-rotate-file';
import { utilities as nestWinstonModuleUtilities } from 'nest-winston';

@Module({
  imports: [
    WinstonModule.forRoot({
      level: process.env.NODE_ENV === 'production' ? 'info' : 'debug', // 根据环境设置日志级别
      transports: [
        // ... (其他配置)
      ],
    }),
  ],
  exports: [WinstonModule],
})
export class LoggerModule {}

在这个例子中,我们根据 NODE_ENV 环境变量来设置日志级别。在生产环境中,我们只输出 info 和更高级别的日志,而在开发环境中,我们输出 debug 和更低级别的日志。

4.2 日志格式

Winston 提供了丰富的日志格式化选项,你可以自定义日志的输出格式,例如添加时间戳、日志级别、文件名、行号等信息。

// logger.module.ts
import { Module } from '@nestjs/common';
import { WinstonModule } from 'nest-winston';
import * as winston from 'winston';
import * as DailyRotateFile from 'winston-daily-rotate-file';
import { utilities as nestWinstonModuleUtilities } from 'nest-winston';

@Module({
  imports: [
    WinstonModule.forRoot({
      transports: [
        new DailyRotateFile({
          // ... (其他配置)
          format: winston.format.combine(
            winston.format.timestamp({
              format: 'YYYY-MM-DD HH:mm:ss',
            }), // 自定义时间戳格式
            winston.format.printf(({ timestamp, level, message, stack }) => {
              // 包含堆栈信息
              return `${timestamp} [${level.toUpperCase()}] ${message} ${stack ? '\n${stack}' : ''}`;
            }),
          ),
        }),
        // ... (其他配置)
      ],
    }),
  ],
  exports: [WinstonModule],
})
export class LoggerModule {}

在这个例子中,我们自定义了时间戳的格式,并在日志中包含了堆栈信息,方便排查错误。

4.3 异常处理

除了普通的日志,你还可以配置 Winston 来处理未捕获的异常,将异常信息写入单独的日志文件,方便定位问题。

// logger.module.ts
import { Module } from '@nestjs/common';
import { WinstonModule } from 'nest-winston';
import * as winston from 'winston';
import * as DailyRotateFile from 'winston-daily-rotate-file';
import { utilities as nestWinstonModuleUtilities } from 'nest-winston';

@Module({
  imports: [
    WinstonModule.forRoot({
      // ... (其他配置)
      exceptionHandlers: [
        new DailyRotateFile({
          dirname: 'logs', // 异常日志文件存放的目录
          filename: 'exceptions-%DATE%.log', // 异常日志文件名
          datePattern: 'YYYY-MM-DD', // 日期格式
          zippedArchive: true, // 是否压缩旧的异常日志文件
          maxSize: '20m', // 单个异常日志文件的最大大小
          maxFiles: '14d', // 保留的异常日志文件数量
          format: winston.format.combine(
            winston.format.timestamp(),
            winston.format.printf(({ timestamp, level, message, stack }) => {
              return `${timestamp} [${level.toUpperCase()}] ${message} ${stack ? '\n${stack}' : ''}`;
            }),
          ),
        }),
      ],
    }),
  ],
  exports: [WinstonModule],
})
export class LoggerModule {}

在这个例子中,我们配置了 exceptionHandlers,将未捕获的异常信息写入 exceptions-%DATE%.log 文件。

4.4 异步日志

在某些情况下,同步写入日志可能会影响应用程序的性能。为了避免这种情况,你可以使用异步日志。

// logger.module.ts
import { Module } from '@nestjs/common';
import { WinstonModule } from 'nest-winston';
import * as winston from 'winston';
import * as DailyRotateFile from 'winston-daily-rotate-file';
import { utilities as nestWinstonModuleUtilities } from 'nest-winston';

@Module({
  imports: [
    WinstonModule.forRoot({
      transports: [
        new DailyRotateFile({
          // ... (其他配置)
          options: {
            // 使用异步写入
            maxsize: '20m',
            maxFiles: '14d',
            zippedArchive: true,
            // 异步写入配置
            queue: true,
            flushInterval: 2000,
          },
        }),
      ],
    }),
  ],
  exports: [WinstonModule],
})
export class LoggerModule {}

在这个例子中,我们在 DailyRotateFileoptions 中配置了 queue: trueflushInterval,表示使用异步写入。flushInterval 指定了日志写入的间隔时间。

4.5 日志清理策略

除了控制日志文件的大小和数量,你还可以设置更精细的日志清理策略,例如:

  • 定期清理: 使用定时任务,定期扫描日志目录,删除过期的日志文件。
  • 基于磁盘空间: 监控磁盘空间,当磁盘空间不足时,自动删除旧的日志文件。

5. 总结

通过使用 Winston 和 winston-daily-rotate-file,我们可以轻松地在 NestJS 项目中实现日志轮转,有效地控制日志文件的大小和数量,避免磁盘空间耗尽的风险,提高开发和运维效率。希望这篇文章对你有所帮助!

6. 常见问题解答

  • Q:为什么我的日志文件没有轮转?
    • A:请检查你的配置是否正确,特别是 dirname, filename, datePattern, maxSize, maxFiles 这些参数。确保你的项目有写入日志文件的权限,并且你的服务器时间是正确的。
  • Q:如何查看压缩后的日志文件?
    • A:你可以使用 gzip 命令解压缩 .gz 文件,或者使用支持 gzip 的文本编辑器(如 VS Code)打开压缩后的日志文件。
  • Q:日志轮转会影响性能吗?
    • A:如果配置不当,日志轮转可能会影响性能。例如,频繁地创建和删除文件,或者写入大量日志时,可能会导致性能下降。你可以通过调整 maxSize, maxFiles 等参数,以及使用异步日志来优化性能。
  • Q:除了 Winston,还有其他日志库吗?
    • A:是的,还有很多其他的 Node.js 日志库,例如 pino, bunyan, log4js 等。它们各有优缺点,你可以根据自己的需求选择合适的日志库。

7. 更多资源

好了,今天就到这里了。如果你还有其他问题,欢迎在评论区留言,我会尽力解答。咱们下次再见!

老码农 NestJS日志Winston日志轮转

评论点评