日志中使用 winston
安装
bash
pnpm install --save nest-winston winston
pnpm install winston-daily-rotate-file
快速入门
app.module.ts
ts
// 日志模块
import { WinstonModule, utilities } from "nest-winston";
import * as winston from "winston";
import "winston-daily-rotate-file";
@Module({
imports: [
// 其他 模块 LoginModule,
WinstonModule.forRoot({
transports: [
new winston.transports.Console({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
utilities.format.nestLike(),
),
}),
new winston.transports.DailyRotateFile({
level: 'error',
dirname: 'logs',
filename: 'error-%DATE%.log',
datePattern: 'YYYY-MM-DD',
zippedArchive: true,
maxSize: '10m',
maxFiles: '14d',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json(),
),
}),
new winston.transports.DailyRotateFile({
level: 'info',
dirname: 'logs',
filename: 'info-%DATE%.log',
datePattern: 'YYYY-MM-DD',
zippedArchive: true, // 是否通过压缩的方式归档被轮换的日志文件。
maxSize: '10m', // 设置日志文件的最大大小,m 表示 mb 。
maxFiles: '14d', // 保留日志文件的最大天数,此处表示自动删除超过 14 天的日志文件。
// 记录时添加时间戳信息
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json(),
),
}),
],
}),
],
//...
})
这样运行起来 就会在根目录生成一个 log 文件夹
升级版
利用 拦截器,中间件,过滤器 写的
(1) 创建工具
- 新建一个 utils 文件夹 下面 新建一个 utils.ts
ts
import { Request } from "express";
export const getReqMainInfo: (req: Request) => {
[prop: string]: any;
} = (req) => {
const { query, headers, url, method, body, connection } = req;
// 获取 IP
const xRealIp = headers["X-Real-IP"];
const xForwardedFor = headers["X-Forwarded-For"];
const { ip: cIp } = req;
const { remoteAddress } = connection || {};
const ip = xRealIp || xForwardedFor || cIp || remoteAddress;
return {
url,
host: headers.host,
headers,
ip,
method,
query,
body,
};
};
(2) 创建拦截器,中间件,过滤器
在 src 目录下面新建一个 common 文件夹
- 过滤器
在 common 文件夹下面创建 filter 文件夹
然后再 filter 文件夹下面新建 global.filter.ts
ts
import {
ExceptionFilter,
Catch,
ArgumentsHost,
HttpException,
HttpStatus,
Inject,
} from "@nestjs/common";
import { Request, Response } from "express";
import { WINSTON_MODULE_PROVIDER } from "nest-winston";
import { Logger } from "winston";
import { getReqMainInfo } from "../../utils/utils";
@Catch(HttpException)
export class GlobalExceptionFilter implements ExceptionFilter {
// 注入日志服务相关依赖
constructor(
@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger
) {}
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp(); // 获取当前执行上下文
const res = ctx.getResponse<Response>(); // 获取响应对象
const req = ctx.getRequest<Request>(); // 获取请求对象
const status =
exception instanceof HttpException
? exception.getStatus()
: HttpStatus.INTERNAL_SERVER_ERROR;
const response: any = exception.getResponse(); // 获取异常响应信息
let msg =
exception.message || (status >= 500 ? "Service Error" : "Client Error");
if (
Object.prototype.toString.call(response) === "[object Object]" &&
response.message
) {
msg = response.message;
}
const { query, headers, url, method, body } = req;
// 记录日志(错误消息,错误码,请求信息等)
this.logger.error(msg, {
status,
req: getReqMainInfo(req),
// stack: exception.stack,
});
res.status(status >= 500 ? status : 200).json({ code: status, msg });
}
}
- 拦截器
在 common 文件夹下面新建 interceptor 文件夹
在 interceptor 文件夹下面新建 global.interceptor.ts
ts
import {
CallHandler,
ExecutionContext,
Injectable,
NestInterceptor,
Inject,
} from "@nestjs/common";
import { Observable } from "rxjs";
import { map } from "rxjs/operators";
import { Request } from "express";
import { WINSTON_MODULE_PROVIDER } from "nest-winston";
import { Logger } from "winston";
import { getReqMainInfo } from "../../utils/utils";
@Injectable()
export class GlobalInterceptor implements NestInterceptor {
constructor(
@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger
) {}
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const ctx = context.switchToHttp();
const req = ctx.getRequest<Request>();
return next.handle().pipe(
map((data) => {
// 记录请求日志
this.logger.info("response", {
responseData: data,
req: getReqMainInfo(req),
});
return {
code: 200,
data,
msg: "success",
};
})
);
}
}
- 中间件
在 common 文件夹下面新建 middleware 文件夹
在 middleware 文件夹下面新建 global.middleware.ts
ts
import { Injectable, NestMiddleware, Inject } from "@nestjs/common";
import { WINSTON_MODULE_PROVIDER } from "nest-winston";
import { Logger } from "winston";
import { getReqMainInfo } from "../../utils/utils";
@Injectable()
export class GlobalMiddleware implements NestMiddleware {
// 注入日志服务相关依赖
constructor(
@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger
) {}
use(req: any, res: any, next: () => void) {
// 获取请求信息
const {
query,
headers: { host },
url,
method,
body,
} = req;
// 记录日志
this.logger.info("route", {
req: getReqMainInfo(req),
});
next();
}
}
(3)挂载到全局
在 app.module.ts 文件中挂载
ts
import { MiddlewareConsumer, Module, NestModule } from "@nestjs/common";
import { LoginModule } from "./Login/login.module";
import { APP_FILTER, APP_INTERCEPTOR } from "@nestjs/core";
// 日志模块
import { WinstonModule, utilities } from "nest-winston";
import * as winston from "winston";
import "winston-daily-rotate-file";
// 加载中间件
import { GlobalMiddleware } from "./common/middleware/global.middleware";
// 过滤器
import { GlobalExceptionFilter } from "./common/filter/global.filter";
// 拦截器
import { GlobalInterceptor } from "./common/interceptor/global.interceptor";
@Module({
imports: [
LoginModule,
WinstonModule.forRoot({
transports: [
new winston.transports.Console({
level: "info",
format: winston.format.combine(
winston.format.timestamp(),
utilities.format.nestLike()
),
}),
new winston.transports.DailyRotateFile({
level: "error",
dirname: "logs",
filename: "error-%DATE%.log",
datePattern: "YYYY-MM-DD",
zippedArchive: true,
maxSize: "10m",
maxFiles: "14d",
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
}),
new winston.transports.DailyRotateFile({
level: "info",
dirname: "logs",
filename: "info-%DATE%.log",
datePattern: "YYYY-MM-DD",
zippedArchive: true, // 是否通过压缩的方式归档被轮换的日志文件。
maxSize: "10m", // 设置日志文件的最大大小,m 表示 mb 。
maxFiles: "14d", // 保留日志文件的最大天数,此处表示自动删除超过 14 天的日志文件。
// 记录时添加时间戳信息
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
}),
],
}),
],
controllers: [],
providers: [
{
provide: APP_FILTER,
useClass: GlobalExceptionFilter,
},
{
provide: APP_INTERCEPTOR,
useClass: GlobalInterceptor,
},
],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(GlobalMiddleware).forRoutes("*");
}
}