NestJS 日志进阶:winston-daily-rotate-file 多环境配置与实践指南
前言:为什么日志管理如此重要?
一、 基础配置:让日志先跑起来
1.1 创建日志服务
1.2 在 AppModule 中引入
1.3 LoggerModule
1.4 使用日志服务
二、 进阶配置:多环境日志管理
2.1 创建配置文件
2.2 加载配置
2.3 在 AppModule 中引入 ConfigModule
三、 实践技巧:让日志更实用
3.1 自定义日志格式
3.2 使用日志上下文
3.3 异步日志
3.4 日志监控和告警
四、 总结与思考
常见问题解答(FAQ)
前言:为什么日志管理如此重要?
“哥们,你这代码又崩了?”
“啊?不能吧,我本地跑得好好的!”
“你自己看日志去!”
相信不少开发者都经历过类似的“灵魂拷问”。在软件开发的世界里,日志就像飞机的“黑匣子”,记录着应用程序运行过程中的每一个细节。无论是调试代码、排查线上问题,还是分析用户行为,日志都扮演着至关重要的角色。一个好的日志系统,能让你在问题发生时迅速定位,减少“背锅”的几率。
对于 Node.js 开发者来说,Winston 是一款非常流行的日志库,而 winston-daily-rotate-file
则是 Winston 的一个强大扩展,它能够按照日期自动分割日志文件,避免单个日志文件过大,方便日志的管理和归档。更重要的是,我们可以根据不同的环境(开发、测试、生产)配置不同的日志级别和输出格式,这对于构建健壮、可维护的应用程序至关重要。
今天,咱们就来聊聊如何在 NestJS 项目中玩转 winston-daily-rotate-file
,打造一个既实用又高效的日志系统。
一、 基础配置:让日志先跑起来
在开始之前,我们需要先安装必要的依赖:
npm install winston winston-daily-rotate-file @nestjs/config
@nestjs/config
是 NestJS 官方提供的配置模块,它可以帮助我们更方便地管理不同环境的配置。
1.1 创建日志服务
首先,我们创建一个 logger.service.ts
文件,用于封装 Winston 的相关配置:
// src/logger/logger.service.ts import { Injectable, LoggerService } from '@nestjs/common'; import * as winston from 'winston'; import 'winston-daily-rotate-file'; @Injectable() export class Logger implements LoggerService { private logger: winston.Logger; constructor() { this.logger = winston.createLogger({ level: 'info', // 默认日志级别 format: winston.format.combine( winston.format.timestamp(), winston.format.json() ), transports: [ new winston.transports.Console(), // 输出到控制台 new winston.transports.DailyRotateFile({ filename: 'logs/application-%DATE%.log', // 日志文件名 datePattern: 'YYYY-MM-DD', zippedArchive: true, // 是否压缩 maxSize: '20m', // 最大文件大小 maxFiles: '14d', // 最多保留天数 }), ], }); } log(message: string, context?: string) { this.logger.log('info', message, { context }); } error(message: string, trace?: string, context?: string) { this.logger.error(message, { trace, context }); } warn(message: string, context?: string) { this.logger.warn(message, { context }); } debug(message: string, context?: string) { this.logger.debug(message, { context }); } verbose(message: string, context?: string) { this.logger.verbose(message, { context }); } }
1.2 在 AppModule 中引入
接下来,我们需要在 app.module.ts
中引入 LoggerModule
:
// src/app.module.ts import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { LoggerModule } from './logger/logger.module'; // 引入 LoggerModule @Module({ imports: [LoggerModule], // 导入 LoggerModule controllers: [AppController], providers: [AppService], }) export class AppModule {}
1.3 LoggerModule
// src/logger/logger.module.ts import { Module } from '@nestjs/common'; import { Logger } from './logger.service'; @Module({ providers: [Logger], exports: [Logger], // 导出 Logger,以便其他模块使用 }) export class LoggerModule {}
1.4 使用日志服务
现在,我们就可以在任何需要的地方注入 Logger
服务,并使用它来记录日志了:
// src/app.service.ts import { Injectable } from '@nestjs/common'; import { Logger } from './logger/logger.service'; // 导入 Logger @Injectable() export class AppService { constructor(private readonly logger: Logger) {} getHello(): string { this.logger.log('访问了首页', AppService.name); // 记录日志 return 'Hello World!'; } }
二、 进阶配置:多环境日志管理
在实际开发中,我们通常需要根据不同的环境(开发、测试、生产)配置不同的日志级别和输出格式。例如,在开发环境中,我们希望看到详细的调试信息,而在生产环境中,我们只需要记录错误和警告信息。
2.1 创建配置文件
首先,我们在 src
目录下创建一个 config
文件夹,并在其中创建三个配置文件:development.yaml
、test.yaml
和 production.yaml
。
# src/config/development.yaml logger: level: debug format: simple # src/config/test.yaml logger: level: info format: json # src/config/production.yaml logger: level: warn format: json
2.2 加载配置
接下来,我们需要修改 logger.service.ts
,使用 @nestjs/config
加载配置:
// src/logger/logger.service.ts import { Injectable, LoggerService } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; // 导入 ConfigService import * as winston from 'winston'; import 'winston-daily-rotate-file'; @Injectable() export class Logger implements LoggerService { private logger: winston.Logger; constructor(private configService: ConfigService) { const loggerConfig = this.configService.get('logger'); const level = loggerConfig.level || 'info'; const format = loggerConfig.format === 'json' ? winston.format.json() : winston.format.simple(); this.logger = winston.createLogger({ level: level, format: winston.format.combine( winston.format.timestamp(), format ), transports: [ new winston.transports.Console(), new winston.transports.DailyRotateFile({ filename: 'logs/application-%DATE%.log', datePattern: 'YYYY-MM-DD', zippedArchive: true, maxSize: '20m', maxFiles: '14d', }), ], }); } log(message: string, context?: string) { this.logger.log('info', message, { context }); } error(message: string, trace?: string, context?: string) { this.logger.error(message, { trace, context }); } warn(message: string, context?: string) { this.logger.warn(message, { context }); } debug(message: string, context?: string) { this.logger.debug(message, { context }); } verbose(message: string, context?: string) { this.logger.verbose(message, { context }); } }
2.3 在 AppModule 中引入 ConfigModule
最后,我们需要在 app.module.ts
中引入 ConfigModule
,并配置配置文件的路径:
// src/app.module.ts import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; // 导入 ConfigModule import { AppController } from './app.controller'; import { AppService } from './app.service'; import { LoggerModule } from './logger/logger.module'; @Module({ imports: [ ConfigModule.forRoot({ envFilePath: [`src/config/.${process.env.NODE_ENV}.yaml`], // 配置文件路径 isGlobal: true, }), LoggerModule, ], controllers: [AppController], providers: [AppService], }) export class AppModule {}
现在,我们就可以通过设置 NODE_ENV
环境变量来切换不同的日志配置了。例如,在开发环境中,我们可以设置 NODE_ENV=development
,在生产环境中,我们可以设置 NODE_ENV=production
。
三、 实践技巧:让日志更实用
3.1 自定义日志格式
Winston 提供了多种内置的日志格式,例如 json
、simple
、printf
等。我们也可以通过 winston.format.combine
将多个格式组合起来使用。此外,我们还可以自定义日志格式,以满足特定的需求。
例如,我们可以创建一个自定义格式,将日志级别、时间戳、上下文和消息格式化为更易读的形式:
// src/logger/my-logger-format.ts import { format } from 'winston'; const myFormat = format.printf(({ level, message, timestamp, context }) => { return `${timestamp} [${context}] ${level}: ${message}`; }); export default myFormat;
然后在logger.service.ts
引入:
// src/logger/logger.service.ts // ...其他代码 format: winston.format.combine( winston.format.timestamp(), myFormat // 使用自定义格式 ), // ...其他代码
3.2 使用日志上下文
在记录日志时,我们可以通过 context
参数提供额外的上下文信息。这对于区分不同模块、不同请求的日志非常有帮助。例如:
this.logger.log('用户登录成功', 'AuthService'); this.logger.error('创建订单失败', 'OrderService', err.stack);
3.3 异步日志
如果你的应用程序需要处理大量的日志,同步写入日志文件可能会成为性能瓶颈。为了解决这个问题,我们可以使用异步日志。
Winston 默认情况下是同步写入日志的。要实现异步日志,我们可以使用 winston-transport
提供的 TransportStream
。
具体实现稍微复杂,这里只提供思路,感兴趣的同学可以自行研究。
3.4 日志监控和告警
对于生产环境的应用程序,我们需要对日志进行监控和告警。当出现错误或异常时,我们需要及时收到通知,以便快速响应。
我们可以使用一些第三方工具来实现日志监控和告警,例如 ELK Stack(Elasticsearch, Logstash, Kibana)、Sentry、Graylog 等。
四、 总结与思考
日志管理是软件开发中不可或缺的一部分。一个好的日志系统,可以帮助我们更快地定位问题,提高开发效率,降低维护成本。
在本文中,我们介绍了如何在 NestJS 项目中使用 winston-daily-rotate-file
实现日志的自动分割和多环境配置。我们还讨论了一些实践技巧,例如自定义日志格式、使用日志上下文、异步日志以及日志监控和告警。
当然,日志管理不仅仅是技术问题,更是一种思维方式。我们需要养成良好的日志记录习惯,思考哪些信息需要记录,如何记录,如何利用日志来改进我们的应用程序。
希望本文能够帮助你更好地理解和应用 NestJS 中的日志管理。如果你有任何问题或建议,欢迎在评论区留言。
常见问题解答(FAQ)
Q:winston-daily-rotate-file
会自动删除过期的日志文件吗?
A:是的,winston-daily-rotate-file
会根据你配置的 maxFiles
选项自动删除过期的日志文件。
Q:我可以在运行时动态修改日志级别吗?
A:可以,但是不推荐这样做。动态修改日志级别可能会导致日志记录不一致,增加排查问题的难度。如果确实需要动态调整日志级别,建议使用专门的日志管理工具或平台。
Q:除了 winston-daily-rotate-file
,还有其他推荐的 Winston 传输器吗?
A:除了 winston-daily-rotate-file
,还有一些常用的 Winston 传输器,例如:
winston-console
: 将日志输出到控制台。winston-file
: 将日志写入到单个文件。winston-syslog
: 将日志发送到 Syslog 服务器。winston-cloudwatch
: 将日志发送到 AWS CloudWatch。
你可以根据自己的需求选择合适的传输器。
Q:如何处理敏感信息,避免记录到日志中?
A:在记录日志时,一定要注意避免将敏感信息(例如密码、密钥、身份证号等)记录到日志中。可以使用脱敏处理或者占位符代替。也可以使用专门的日志安全工具来检测和过滤敏感信息。