NestJS 集成 Winston 日志
本指引详细介绍如何在 NestJS 项目中集成 Winston 日志系统,并通过中间件记录每一次接口调用日志,便于后续项目快速复用。
目录结构建议
bash
src/
├── common/
│ └── middleware/
│ └── logger.middleware.ts # 接口调用日志中间件
├── config/
│ └── winston.config.ts # winston 日志配置
├── app.module.ts
└── main.ts1. 安装依赖
bash
pnpm add chalk winston winston-daily-rotate-file nest-winston提示
chalk 是一个用于在终端中输出彩色文本的库。winston 是一个功能强大的日志库,支持多种传输方式和格式化选项。winston-daily-rotate-file 是 Winston 的一个传输器,用于将日志输出到文件,并按天轮转。nest-winston 是 NestJS 的 Winston 集成包,提供了更好的集成和配置方式。
2. 配置 Winston 日志
在 src/config/winston.config.ts 中配置 winston:
typescript
import chalk from "chalk";
import { createLogger, format, transports } from "winston";
import * as DailyRotateFile from "winston-daily-rotate-file";
const winstonLogger = createLogger({
format: format.combine(format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" })),
transports: [
new DailyRotateFile({
filename: "logs/errors/error-%DATE%.log", // 日志名称,占位符 %DATE% 取值为 datePattern 值。
datePattern: "YYYY-MM-DD-HH", // 日志轮换的频率,此处表示 1 小时轮换一次。
zippedArchive: true, // 是否通过压缩的方式归档被轮换的日志文件。
maxSize: "20m", // 设置日志文件的最大大小,m 表示 mb 。
level: "error", // 日志类型,此处表示只记录错误日志。
format: format.combine(
format.printf((info) => {
return `${String(info.timestamp)}\n${"*".repeat(
100
)}\n错误信息: ${String(info.message)}\n堆栈信息: ${String(
info.stack
)}\n${"*".repeat(100)}\n`;
})
),
}),
new DailyRotateFile({
filename: "logs/warnings/warning-%DATE%.log",
datePattern: "YYYY-MM-DD-HH",
zippedArchive: true,
maxSize: "20m",
level: "warn",
format: format.combine(
format.printf((info) => {
return `${String(info.timestamp)}\n${"*".repeat(
100
)}\n警告信息: ${String(info.message)}\n${"*".repeat(100)}\n`;
})
),
}),
new DailyRotateFile({
filename: "logs/info/info-%DATE%.log",
datePattern: "YYYY-MM-DD-HH",
zippedArchive: true,
maxSize: "20m",
level: "info",
format: format.combine(
format.printf((info) => {
if (info.level === "error") {
return `${String(info.timestamp)}\n${"*".repeat(100)}\n${"=".repeat(
20
)}>错误信息: ${String(info.message)}\n${"=".repeat(
20
)}>堆栈信息: ${String(info.stack)}\n${"*".repeat(100)}\n`;
} else if (info.level === "warn") {
return `${String(info.timestamp)}\n${"*".repeat(100)}\n${"=".repeat(
20
)}>警告信息: ${String(info.message)}\n${"*".repeat(100)}\n`;
} else {
return `${String(info.timestamp)} ${String(info.message)}`;
}
})
),
}),
new transports.Console({
format: format.combine(
format.printf((info) => {
const chalkColor = getChalkColor(info.level);
return `${chalkColor(info.timestamp)} ${chalkColor(info.message)}`;
})
),
level: "debug",
}),
],
});
const getChalkColor = (level: string) => {
switch (level) {
case "error":
return chalk.red;
case "warn":
return chalk.yellow;
case "info":
return chalk.green;
case "debug":
return chalk.blue;
case "verbose":
return chalk.cyan;
default:
return chalk.white;
}
};
export default winstonLogger;3. 在 AppModule 中集成 Winston
在 src/app.module.ts 中引入并注册 Winston 日志模块:
typescript
import { WinstonModule } from 'nest-winston';
import winstonLogger from './config/winston.config';
@Module({
imports: [
WinstonModule.forRoot({
transports: winstonLogger.transports,
format: winstonLogger.format,
defaultMeta: winstonLogger.defaultMeta,
exitOnError: false,
}),
],
// ...
})4. 在 main.ts 中设置全局 Logger
typescript
import { WINSTON_MODULE_NEST_PROVIDER } from "nest-winston";
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useLogger(app.get(WINSTON_MODULE_NEST_PROVIDER));
await app.listen(process.env.PORT ?? 3000);
}5. 编写接口调用日志中间件
在 src/common/middleware/logger.middleware.ts:
typescript
import { Injectable, Logger, NestMiddleware } from "@nestjs/common";
import { NextFunction, Request, Response } from "express";
@Injectable()
export class LoggerMiddleware implements NestMiddleware {
private logger = new Logger();
use(req: Request, res: Response, next: NextFunction) {
const { method, originalUrl, ip, httpVersion, headers } = req;
const { statusCode } = res;
const logFormat = `接口调用 - 调用地址: [${originalUrl}] 调用方法: [${method}] HTTP 协议版本: [HTTP/${httpVersion}] 客户端IP: [${ip}] 状态码: [${statusCode}] User-Agent: [${headers["user-agent"]}]`;
if (statusCode >= 500) {
this.logger.error(logFormat, originalUrl);
} else if (statusCode >= 400) {
this.logger.warn(logFormat, originalUrl);
} else {
this.logger.log(logFormat, originalUrl);
}
next();
}
}如需将日志直接写入 winston,可将
Logger替换为自定义的 winstonLogger。
6. 注册中间件
在 app.module.ts 中注册中间件:
typescript
import { LoggerMiddleware } from "./common/middleware/logger.middleware";
import { MiddlewareConsumer, Module, NestModule } from "@nestjs/common";
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(LoggerMiddleware).forRoutes("*");
}
}7. 日志文件输出说明
- 错误日志:
logs/errors/error-YYYY-MM-DD-HH.log - 警告日志:
logs/warnings/warning-YYYY-MM-DD-HH.log - 普通日志:
logs/info/info-YYYY-MM-DD-HH.log
8. 常见问题排查
- 日志文件为空:请确认有实际调用日志方法,且日志目录有写入权限。
- 日志级别未触发:如只调用 info,error/warn 文件不会有内容。
- 进程未正常退出可能导致日志未及时写入磁盘。
如需扩展日志内容或格式,可在 winston.config.ts 中自定义 format。
