NestJS 进阶:用 Winston 和日志轮转,告别无限膨胀的日志文件!
嘿,老铁们!我是老码农,今天咱们聊聊在 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 模块的核心,我们在这里配置了transports和exceptionHandlers。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();
}
}
在这个例子中,我们:
- 引入
LoggerModule: 在AppModule中引入我们创建的日志模块。 - 注入
WINSTON_MODULE_PROVIDER: 在AppController中,我们使用@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 {}
在这个例子中,我们在 DailyRotateFile 的 options 中配置了 queue: true 和 flushInterval,表示使用异步写入。flushInterval 指定了日志写入的间隔时间。
4.5 日志清理策略
除了控制日志文件的大小和数量,你还可以设置更精细的日志清理策略,例如:
- 定期清理: 使用定时任务,定期扫描日志目录,删除过期的日志文件。
- 基于磁盘空间: 监控磁盘空间,当磁盘空间不足时,自动删除旧的日志文件。
5. 总结
通过使用 Winston 和 winston-daily-rotate-file,我们可以轻松地在 NestJS 项目中实现日志轮转,有效地控制日志文件的大小和数量,避免磁盘空间耗尽的风险,提高开发和运维效率。希望这篇文章对你有所帮助!
6. 常见问题解答
- Q:为什么我的日志文件没有轮转?
- A:请检查你的配置是否正确,特别是
dirname,filename,datePattern,maxSize,maxFiles这些参数。确保你的项目有写入日志文件的权限,并且你的服务器时间是正确的。
- A:请检查你的配置是否正确,特别是
- Q:如何查看压缩后的日志文件?
- A:你可以使用
gzip命令解压缩.gz文件,或者使用支持gzip的文本编辑器(如 VS Code)打开压缩后的日志文件。
- A:你可以使用
- Q:日志轮转会影响性能吗?
- A:如果配置不当,日志轮转可能会影响性能。例如,频繁地创建和删除文件,或者写入大量日志时,可能会导致性能下降。你可以通过调整
maxSize,maxFiles等参数,以及使用异步日志来优化性能。
- A:如果配置不当,日志轮转可能会影响性能。例如,频繁地创建和删除文件,或者写入大量日志时,可能会导致性能下降。你可以通过调整
- Q:除了 Winston,还有其他日志库吗?
- A:是的,还有很多其他的 Node.js 日志库,例如
pino,bunyan,log4js等。它们各有优缺点,你可以根据自己的需求选择合适的日志库。
- A:是的,还有很多其他的 Node.js 日志库,例如
7. 更多资源
- Winston 官方文档:Winston 的官方文档,包含了详细的 API 文档和使用示例。
winston-daily-rotate-file官方文档:winston-daily-rotate-file的官方文档,介绍了如何使用这个包实现日志轮转。- NestJS Winston 模块:NestJS 的 Winston 模块,简化了在 NestJS 中使用 Winston 的过程。
好了,今天就到这里了。如果你还有其他问题,欢迎在评论区留言,我会尽力解答。咱们下次再见!