{T}

NestJS第三方日志模块Winston学习笔记

核心知识点

1. Winston vs Pino 对比

特性PinoWinston
定位高性能日志库(如 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-winstonWinston 与 NestJS 的集成桥接包
winstonWinston 核心库
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

utilitieswinston 中导入: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():将模块标记为全局作用域,其 providersexports 自动在所有模块中可用,无需重复导入。

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-file

5.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-rollwinston-daily-rotate-file
滚动方式frequency / sizedatePattern / 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-*.logwarn / error错误排查,信息精简
info-*.loginfo / 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 → 示例代码

学习要点总结

  1. Winston = 高集成度日志库:内置 format、transport、level,比 Pino 更全面
  2. nest-winston 替换内置 Logger:通过 WINSTON_MODULE_NEST_PROVIDERmain.ts 中替换
  3. @Global() 解决依赖注入问题:将 AppModule 标记为全局模块,Logger 在所有模块中可用
  4. 排错方法论:通读文档 → 查看 Issues → 参考示例代码 → 结合已有知识分析
  5. winston-daily-rotate-file:比 pino-roll 更完善,支持压缩、自动清理、事件回调

延伸学习资源

官方文档

后续课程预告

  • 日志代码抽离:将 Winston 配置提取为独立文件
  • ELK Stack 集成:Elasticsearch + Logstash + Kibana 日志可视化

Winston vs Pino 选型建议

code
选 Pino:
   性能要求极高
   日志量非常大
   只需要基础的文件滚动

选 Winston:
   需要开箱即用的丰富功能
   需要按等级分流输出
   需要文件压缩、自动清理
   需要 rotate 事件回调