NestJS第三方日志模块Winston学习笔记
核心知识点
1. Winston vs Pino 对比
| 特性 | Pino | Winston |
|---|---|---|
| 定位 | 高性能日志库(如 webpack) | 高集成度日志库(如 yarn) |
| 描述 | 极速、低开销 | "A logger for just about everything" |
| 内置功能 | 需要多个包组合(pino-http / pretty / roll) | 内置 format、transport、level 等 |
| 文件滚动 | 需 pino-roll | 需 winston-daily-rotate-file |
| 社区生态 | 轻量、专注性能 | 生态丰富、集成度高 |
| 适用场景 | 对性能要求极高的场景 | 功能全面、开箱即用的场景 |
如果 Pino 是 webpack(快而精),那 Winston 就是 yarn(功能全而集成度高)。
2. 安装依赖
bash
pnpm install nest-winston winston winston-daily-rotate-file| 包名 | 作用 |
|---|---|
nest-winston | Winston 与 NestJS 的集成桥接包 |
winston | Winston 核心库 |
winston-daily-rotate-file | 日志文件按日期自动滚动 |
3. 替换 NestJS 内置 Logger
3.1 在 main.ts 中创建 Winston Logger 实例
typescript
// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston';
import * as winston from 'winston';
import { utilities } from 'winston';
async function bootstrap() {
const app = await NestFactory.create(AppModule, {
// 使用 Winston 替换内置 Logger
logger: WINSTON_MODULE_NEST_PROVIDER,
});
await app.listen(3000);
}
bootstrap();3.2 配置 Winston 格式(format)
typescript
// main.ts
import * as winston from 'winston';
import { utilities } from 'winston';
const app = await NestFactory.create(AppModule, {
logger: WinstonModule.createLogger({
transports: [
new winston.transports.Console({
// combine:组合多个格式
format: winston.format.combine(
winston.format.timestamp(), // 添加时间戳
utilities.format.nestLike(), // NestJS 风格格式化
),
}),
],
}),
});3.3 Winston 常用 format 速查
| format 方法 | 说明 | 输出示例 |
|---|---|---|
winston.format.timestamp() | 添加时间戳 | "2026-03-30T08:00:00.000Z" |
winston.format.simple() | 简洁输出 | info: message |
winston.format.prettyPrint() | 美化输出(JSON + 换行) | { level: "info", ... } |
winston.format.json() | JSON 格式输出 | {"level":"info","message":"..."} |
utilities.format.nestLike() | NestJS 风格 | [NestWinston] info message |
utilities从winston中导入:import { utilities } from 'winston';
4. 依赖注入报错与解决方案 了解(排错思路)
4.1 问题描述
按照 nest-winston 官方文档配置后,在 Controller 中通过依赖注入使用 Logger 时报错:
typescript
// 按官方文档配置后报错
constructor(@Inject(WINSTON_MODULE_NEST_PROVIDER) private logger: LoggerService) {}
// Error: Nest can't resolve dependencies of the UserController (LoggerService, ?)4.2 排错思路(重要方法论)
code
第一步:通读官方文档(以 GitHub 文档为准)
↓ 发现 "imports options" 和 "export from global module" 的提示
↓
第二步:查看 GitHub Issues
↓ 看是否有相同问题的 open issue
↓
第三步:查看官方示例代码
↓ 找到 nest-winston/examples/nest-log-bootstrap 示例
↓ 发现关键:缺少模块导出和全局注册
↓
第四步:结合 NestJS DI 知识分析
↓ 根因:Logger 在 AppModule 中注册,但其他模块无法访问
↓ 方案:将 AppModule 标记为全局模块4.3 根因分析
code
NestJS DI 系统:
AppModule 中 providers 了 Logger
↓
但其他 Module(如 UserModule)不知道 Logger 的存在
↓
需要:Export + 全局注册 或 在每个模块中重复提供4.4 解决方案
方案一(推荐):使用 @Global 装饰器
typescript
// app.module.ts
import { Global, Module } from '@nestjs/common';
import { Logger } from '@nestjs/common';
import { WinstonModule } from 'nest-winston';
import * as winston from 'winston';
import { utilities } from 'winston';
@Global // ← 关键:将 AppModule 标记为全局模块
@Module({
imports: [
WinstonModule.forRoot({
transports: [
new winston.transports.Console({
format: winston.format.combine(
winston.format.timestamp(),
utilities.format.nestLike(),
),
}),
],
}),
],
providers: [
{
provide: 'WINSTON_LOGGER', // 自定义 Provider
useValue: new Logger(),
},
],
exports: ['WINSTON_LOGGER'],
})
export class AppModule {}方案二(不推荐):在每个模块中重复提供
typescript
// user.module.ts
@Module({
providers: [
{ provide: 'WINSTON_LOGGER', useValue: new Logger() },
],
})
export class UserModule {}
@Global():将模块标记为全局作用域,其providers和exports自动在所有模块中可用,无需重复导入。
4.5 解决后的使用方式
typescript
// user.controller.ts
import { Controller, Get } from '@nestjs/common';
import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston';
@Controller('user')
export class UserController {
// 方式一:使用 @Inject 注入
constructor(@Inject(WINSTON_MODULE_NEST_PROVIDER) private logger: any) {}
// 方式二:使用 Logger 直接使用(需全局注册)
private logger = new Logger(UserController.name);
@Get()
getUsers() {
this.logger.log('请求 get users 成功');
this.logger.warn('This is a warning');
this.logger.error('This is an error');
this.logger.debug('This is debug info');
this.logger.verbose('This is verbose info');
return { users: [] };
}
}5. winston-daily-rotate-file 日志滚动
5.1 安装
bash
pnpm install winston-daily-rotate-file5.2 配置滚动文件
typescript
// main.ts
import DailyRotateFile from 'winston-daily-rotate-file';
const app = await NestFactory.create(AppModule, {
logger: WinstonModule.createLogger({
transports: [
// 控制台输出(info 级别)
new winston.transports.Console({
format: winston.format.combine(
winston.format.timestamp(),
utilities.format.nestLike(),
),
level: 'info',
}),
// 滚动文件输出(warn + error 级别)
new DailyRotateFile({
dirname: 'logs', // 日志目录
filename: 'application-%DATE%.log', // 文件名(%DATE% 为日期占位符)
datePattern: 'YYYY-MM-DD-HH', // 日期格式(精确到小时)
zippedArchive: true, // 压缩归档旧日志
maxSize: '20m', // 单个文件最大 20MB
maxFiles: '14d', // 最多保留 14 天
level: 'warn', // 只记录 warn 及以上
}),
// 信息文件输出(info 级别)
new DailyRotateFile({
dirname: 'logs',
filename: 'info-%DATE%.log',
datePattern: 'YYYY-MM-DD-HH',
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
utilities.format.nestLike(),
),
}),
],
}),
});5.3 DailyRotateFile 配置项详解
| 参数 | 说明 | 示例 |
|---|---|---|
dirname | 日志文件目录 | 'logs' 或绝对路径 |
filename | 文件名模板 | 'app-%DATE%.log' |
datePattern | 日期格式(占位符) | 'YYYY-MM-DD-HH' |
zippedArchive | 是否压缩旧日志文件 | true |
maxSize | 单个文件最大大小 | '20m' |
maxFiles | 日志保留时长 | '14d'(14 天后自动删除) |
level | 日志等级过滤 | 'info' / 'warn' / 'error' |
5.4 Winston vs Pino 文件滚动对比
| 特性 | pino-roll | winston-daily-rotate-file |
|---|---|---|
| 滚动方式 | frequency / size | datePattern / maxSize |
| 日期格式 | 'daily' / 'hourly' | 'YYYY-MM-DD-HH'(精确到分钟) |
| 文件压缩 | 不支持 | zippedArchive: true |
| 自动清理 | 需手动清理 | maxFiles: '14d' |
| 事件回调 | rotate / new / archive 等事件 | |
| 成熟度 | 相对简单 | 更成熟,功能更完善 |
winston-daily-rotate-file更完善:支持压缩、自动清理、事件回调(如 rotate 时发邮件通知)。
6. 按等级分流输出
6.1 不同等级写入不同文件
typescript
transports: [
// Console:输出 info 及以上
new winston.transports.Console({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
utilities.format.nestLike(),
),
}),
// application.log:只记录 warn + error
new DailyRotateFile({
dirname: 'logs',
filename: 'application-%DATE%.log',
datePattern: 'YYYY-MM-DD-HH',
zippedArchive: true,
maxSize: '20m',
maxFiles: '14d',
level: 'warn', // ← 只记录 warn 和 error
}),
// info.log:记录所有 info 及以上
new DailyRotateFile({
dirname: 'logs',
filename: 'info-%DATE%.log',
datePattern: 'YYYY-MM-DD-HH',
level: 'info', // ← 记录 info、warn、error
format: winston.format.combine(
winston.format.timestamp(),
winston.format.simple(),
),
}),
],6.2 文件分工
| 文件 | 等级 | 用途 |
|---|---|---|
| Console(终端) | info / warn / error | 开发实时监控 |
application-*.log | warn / error | 错误排查,信息精简 |
info-*.log | info / warn / error | 完整回溯,信息全面 |
代码实战案例
需求描述
集成 Winston 替换 NestJS 内置 Logger,实现控制台美化输出 + 按等级分流写入日志文件,支持日志滚动和自动清理。
完整实现
第一步:安装依赖
bash
pnpm install nest-winston winston winston-daily-rotate-file第二步:配置 main.ts
typescript
// main.ts
import { NestFactory } from '@nestjs/core';
import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston';
import * as winston from 'winston';
import { utilities } from 'winston';
import DailyRotateFile from 'winston-daily-rotate-file';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule, {
logger: WinstonModule.createLogger({
transports: [
// 控制台:美化输出
new winston.transports.Console({
format: winston.format.combine(
winston.format.timestamp(),
utilities.format.nestLike(),
),
level: 'info',
}),
// 错误文件:只记录 warn + error
new DailyRotateFile({
dirname: 'logs',
filename: 'application-%DATE%.log',
datePattern: 'YYYY-MM-DD-HH',
zippedArchive: true,
maxSize: '20m',
maxFiles: '14d',
level: 'warn',
}),
// 信息文件:记录所有 info 及以上
new DailyRotateFile({
dirname: 'logs',
filename: 'info-%DATE%.log',
datePattern: 'YYYY-MM-DD-HH',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.simple(),
),
level: 'info',
}),
],
}),
});
await app.listen(3000);
}
bootstrap();第三步:配置 AppModule(@Global 全局注册)
typescript
// app.module.ts
import { Global, Module } from '@nestjs/common';
import { WinstonModule } from 'nest-winston';
import { UserModule } from './user/user.module';
@Global // 全局模块
@Module({
imports: [
WinstonModule.forRoot({ /* ... 同 main.ts 中的配置 ... */ }),
UserModule,
],
providers: [],
exports: [],
})
export class AppModule {}第四步:在 Controller 中使用
typescript
// user.controller.ts
import { Controller, Get } from '@nestjs/common';
import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston';
import { Inject } from '@nestjs/common';
@Controller('user')
export class UserController {
constructor(
@Inject(WINSTON_MODULE_NEST_PROVIDER) private readonly logger: any,
) {}
@Get()
getUsers() {
this.logger.log('请求 get users 成功');
this.logger.warn('密码未加密存储');
this.logger.error('数据库连接超时');
return { users: [] };
}
}测试结果
控制台输出:
code
[2026-03-30 08:00:00] LOG [UserController] 请求 get users 成功
[2026-03-30 08:00:01] WARN [UserController] 密码未加密存储
[2026-03-30 08:00:02] ERROR [UserController] 数据库连接超时logs/application-2026-03-30-08.log(只有 warn + error):
code
2026-03-30T08:00:01.000Z warn: 密码未加密存储
2026-03-30T08:00:02.000Z error: 数据库连接超时logs/info-2026-03-30-08.log(info + warn + error):
code
2026-03-30T08:00:00.000Z info: 请求 get users 成功
2026-03-30T08:00:01.000Z warn: 密码未加密存储
2026-03-30T08:00:02.000Z error: 数据库连接超时常见问题与解决方案
| 问题 | 原因 | 解决方案 |
|---|---|---|
| Controller 注入 Logger 报错 | Logger 未注册为全局可用 | 在 AppModule 上添加 @Global() 装饰器 |
| 日志文件无时间戳 | 未配置 winston.format.timestamp() | 在 format.combine 中添加 timestamp() |
| info 文件没有内容 | Console 和 info 文件 level 不匹配 | 确认 info 文件的 level: 'info' |
| 日志文件一直增长 | 未配置滚动或清理 | 使用 maxSize: '20m' + maxFiles: '14d' |
| nest-winston 文档配置不生效 | 官方文档不完整,需看 GitHub 示例 | 参考 examples/nest-log-bootstrap |
| Winston 依赖注入问题 | 第三方包的 Bug 或文档不完善 | 按排错思路:文档 → Issues → 示例代码 |
学习要点总结
- Winston = 高集成度日志库:内置 format、transport、level,比 Pino 更全面
- nest-winston 替换内置 Logger:通过
WINSTON_MODULE_NEST_PROVIDER在main.ts中替换 - @Global() 解决依赖注入问题:将 AppModule 标记为全局模块,Logger 在所有模块中可用
- 排错方法论:通读文档 → 查看 Issues → 参考示例代码 → 结合已有知识分析
- winston-daily-rotate-file:比 pino-roll 更完善,支持压缩、自动清理、事件回调
延伸学习资源
官方文档
后续课程预告
- 日志代码抽离:将 Winston 配置提取为独立文件
- ELK Stack 集成:Elasticsearch + Logstash + Kibana 日志可视化
Winston vs Pino 选型建议
code
选 Pino:
性能要求极高
日志量非常大
只需要基础的文件滚动
选 Winston:
需要开箱即用的丰富功能
需要按等级分流输出
需要文件压缩、自动清理
需要 rotate 事件回调