引言
申请日志与谬误记录是后端服务中不可或缺的一环,对谬误排查和保障利用运行稳定性具备积极意义。综合 GitHub 活跃度和 Nest 官网举荐的因素,决定将 Winston 作为 Nest 利用的日志服务模块,本文将演示如何在 NestJS 中接入 Winston,实现日志记录性能。
引入与配置 Winston
相干依赖:winston、nest-winston、winston-daily-rotate-file
- 其中 winston-daily-rotate-file 用于实现日志文件的定期归档。因为利用日志量个别都十分大,因而须要定期主动对日志文件进行轮换、归档与删除。
app.module.ts(主模块)
import { // ... Module,} from '@nestjs/common';import { WinstonModule } from 'nest-winston';import * as winston from 'winston';import 'winston-daily-rotate-file';// ...@Module({ controllers: [], imports: [ // ... WinstonModule.forRoot({ transports: [ new winston.transports.DailyRotateFile({ dirname: `logs`, // 日志保留的目录 filename: '%DATE%.log', // 日志名称,占位符 %DATE% 取值为 datePattern 值。 datePattern: 'YYYY-MM-DD', // 日志轮换的频率,此处示意每天。 zippedArchive: true, // 是否通过压缩的形式归档被轮换的日志文件。 maxSize: '20m', // 设置日志文件的最大大小,m 示意 mb 。 maxFiles: '14d', // 保留日志文件的最大天数,此处示意主动删除超过 14 天的日志文件。 // 记录时增加工夫戳信息 format: winston.format.combine( winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss', }), winston.format.json(), ), }), ], }), ], // ...})export class AppModule { // ... }
在全局中间件、过滤器以及拦截器中记录日志
获取申请头信息的工具办法
utils.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, ip, method, query, body, };};
在全局中间件中记录日志
logger.middleware.ts
import { Inject, Injectable, NestMiddleware } from '@nestjs/common';import { Request, Response, NextFunction } from 'express';import { WINSTON_MODULE_PROVIDER } from 'nest-winston';import { Logger } from 'winston';import { getReqMainInfo } from './utils';@Injectable()export default class LoggerMiddleware implements NestMiddleware { // 注入日志服务相干依赖 constructor( @Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger, ) {} use(req: Request, res: Response, next: NextFunction) { // 获取申请信息 const { query, headers: { host }, url, method, body, } = req; // 记录日志 this.logger.info('route', { req: getReqMainInfo(req), }); next(); }}
在全局异样过滤器中记录日志
uinify-exception.filter.ts
import { ArgumentsHost, Catch, ExceptionFilter, HttpException, HttpStatus, Inject,} from '@nestjs/common';import { Response, Request } from 'express';import { WINSTON_MODULE_PROVIDER } from 'nest-winston';import { Logger } from 'winston';import { getReqMainInfo } from './utils';@Catch()export default class UnifyExceptionFilter 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 = 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: 1, msg }); }}
在响应拦截器中记录日志
unify-response.interceptor.ts
import { CallHandler, ExecutionContext, Inject, Injectable, NestInterceptor,} 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';@Injectable()export class UnifyResponseInterceptor 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: 0, data, msg: '胜利', }; }), ); }}
利用全局中间件、过滤器以及拦截器
import { MiddlewareConsumer, Module, NestModule, RequestMethod,} from '@nestjs/common';import { APP_FILTER } from '@nestjs/core';import { WinstonModule } from 'nest-winston';import * as winston from 'winston';import 'winston-daily-rotate-file';import UnifyExceptionFilter from './common/uinify-exception.filter';import logger from './common/logger.middleware';// ...@Module({ // ... imports: [ // ... WinstonModule.forRoot({ // ... }), ], providers: [ // ... // 利用全局过滤器 { provide: APP_FILTER, useClass: UnifyExceptionFilter, }, // 利用拦截器 { provide: APP_INTERCEPTOR, useClass: UnifyResponseInterceptor, }, ],})export class AppModule implements NestModule { // 利用全局中间件 configure(consumer: MiddlewareConsumer) { consumer.apply(logger).forRoutes({ path: '*', method: RequestMethod.ALL }); }}
实现以上配置后,我的项目目录下就会蕴含拜访及错误信息的日志文件。日志文件将每天主动归档压缩,超过 14 天的日志也将被主动删除。
更多参考
winston
winston-daily-rotate-file
nest-winston