创建日志模块
使用 NestJS CLI 创建模块骨架,避免手动创建文件:
nest g mo common/log --no-spec
bash
注意:模块目录名用
log而非logs,避免与.gitignore中的logs规则冲突。
安装依赖:
pnpm add winston nest-winston winston-daily-rotate-file
bash
使用 forRootAsync 异步加载
日志模块需要读取 ConfigService 中的环境变量来决定是否启用文件日志,因此必须使用 forRootAsync 而非 forRoot:
// common/log/log.module.ts
import { Module } from '@nestjs/common';
import { WinstonModule } from 'nest-winston';
import { ConfigService } from '@nestjs/config';
import * as winston from 'winston';
import { utilities } from 'nest-winston';
import { createDailyRotateTransport } from './create-daily-rotate-transport';
@Module({
imports: [
WinstonModule.forRootAsync({
inject: [ConfigService],
useFactory: (configService: ConfigService) => {
const logOn = configService.get<boolean>('LOG_ON', false);
const consoleTransport = new winston.transports.Console({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.ms(),
utilities.format.nestLike('MyApp', { colors: true, prettyPrint: true }),
),
});
const fileTransports = logOn
? [
createDailyRotateTransport('info', 'application'),
createDailyRotateTransport('warn', 'error'),
]
: [];
return {
transports: [consoleTransport, ...fileTransports],
};
},
}),
],
})
export class LogModule {}
typescript
为什么用 forRootAsync
forRoot 在模块注册时同步执行,此时 ConfigService 尚未初始化。forRootAsync 通过 inject + useFactory 延迟到 DI 容器就绪后执行,可以安全地注入和读取任何已注册的服务。
日志滚动传输工厂函数
// common/log/create-daily-rotate-transport.ts
import * as DailyRotateFile from 'winston-daily-rotate-file';
import * as winston from 'winston';
export function createDailyRotateTransport(
level: string,
filename: string,
): DailyRotateFile {
return new DailyRotateFile({
dirname: 'logs',
filename: `${filename}-%DATE%.log`,
datePattern: 'YYYY-MM-DD',
maxSize: '20m',
maxFiles: '14d',
level,
format: winston.format.combine(
winston.format.timestamp(),
winston.format.simple(),
),
});
}
typescript
| 参数 | 说明 | 示例 |
|---|---|---|
level | 日志等级阈值 | 'info'(全量)、'warn'(仅警告和错误) |
filename | 文件名前缀 | 'application'、'error' |
dirname | 日志目录 | 'logs'(生产环境建议绝对路径) |
maxSize | 单文件最大尺寸 | '20m' |
maxFiles | 保留天数 | '14d' |
两个文件分工明确:
application-*.log:记录 info 及以上全量日志error-*.log:仅记录 warn 和 error,方便快速定位问题
在 main.ts 中替换全局 Logger
// main.ts
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
async function bootstrap() {
const app = await NestFactory.create(AppModule, { bufferLogs: true });
app.useLogger(app.get(WINSTON_MODULE_PROVIDER));
const configService = app.get(ConfigService);
const port = configService.get<number>('PORT', 3000);
await app.listen(port);
}
bootstrap();
typescript
bufferLogs: true 确保应用启动期间的日志也被捕获,不会丢失。WINSTON_MODULE_PROVIDER 是 nest-winston 导出的 Logger 实例 token。
环境控制开关
在 .env 中通过 LOG_ON 控制是否启用文件日志:
# .env.development
LOG_ON=true # 开发环境启用文件日志(方便调试)
# .env.production
LOG_ON=true # 生产环境必须启用
# .env.test
LOG_ON=false # 测试环境关闭文件日志(加快测试速度)
bash
同时在配置模块的 Joi schema 中注册:
validationSchema: Joi.object({
// ... 其他配置
LOG_ON: Joi.boolean().default(false),
}),
typescript
格式化选项
nest-winston 的 utilities.format.nestLike() 提供了类似 NestJS 内置 Logger 的彩色输出,可自定义应用名和时间格式:
winston.format.combine(
winston.format.timestamp(), // ISO 时间戳
winston.format.ms(), // 毫秒精度
utilities.format.nestLike('MyApp', {
colors: true,
prettyPrint: true,
}),
),
typescript
.gitignore 注意事项
NestJS CLI 生成的 .gitignore 默认排除了 logs 目录。如果日志模块目录也叫 logs,Git 不会跟踪它。解决方案:
- 重命名模块目录(推荐):
common/logs→common/log - 修改 .gitignore:删除
logs规则,但要注意根目录下测试产生的日志文件也会被跟踪 - 精确排除:在
.gitignore中只排除根目录的logs/而非src/**/logs/
↑