填坑 算是比较重要的日志模块
我这里把日志文件分成两种,一种是request,记录请求的日志,无论是正常数据返回还是业务异常范围(比如参数、验签错误),其实也就是HTTP Code:200的日志。
另一种是应用层面异常的日志。
日志输出的库选择了log4js
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| const layout = { type: 'pattern', pattern: '[%d{yyyy-MM-dd hh:mm:ss}] [%p] %h %z %m' }
configure({ appenders: { console: { type: 'console' }, stdout: { type: 'dateFile', filename: `${loggerDir}request.log`, alwaysIncludePattern: true, keepFileExt: true, layout }, errorout: { type: 'dateFile', filename: `${loggerDir}error.log`, alwaysIncludePattern: true, keepFileExt: true, layout } }, categories: { default: { appenders: ['console'], level: 'all' }, request: { appenders: ['stdout'], level: 'info' }, error: { appenders: ['errorout'], level: 'error' } } })
|
LoggerModule
这里选择实现LoggerSerivce接口(其实最后才发现不实现也能用…)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| import { LoggerService, Injectable } from '@nestjs/common' import { configure, getLogger, Logger } from 'log4js' import { ConfigService } from '../config/config.service'
@Injectable() export class CustomLoggerService implements LoggerService { private readonly requestLogger: Logger private readonly errorLogger: Logger
constructor(private readonly configService: ConfigService) { this.requestLogger = getLogger('request') this.errorLogger = getLogger('error')
const loggerDir = this.configService.get('LOGS_DIR')
}
log(message: string): void { this.info(message) }
info(message: string): void { this.requestLogger.info(message) }
warn(message: string): void { this.errorLogger.warn(message) }
error(message: string): void { this.errorLogger.error(message) }
requestError(message: string): void { this.requestLogger.error(message) } }
|
1 2 3 4 5 6 7 8 9
| import { Module } from '@nestjs/common' import { CustomLoggerService } from './logger.service'
@Module({ providers: [CustomLoggerService], exports: [CustomLoggerService] }) export class LoggerModule {}
|
最后记得在app.module.ts
中import进来
使用
请求日志
请求的出口有两个,一个是包装正常数据的api.interceptor.ts
,另一个是捕获业务异常的http-exception.filter.ts
构造函数依赖注入LoggerService
1
| constructor(private readonly logger: CustomLoggerService) {}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| intercept( context: ExecutionContext, next: CallHandler ): Observable<Response<T>> { return next.handle().pipe( map(data => { const res = { code: ApiCode.SUCCESS, msg: 'success', data }
this.doLog(context, res)
return res }) ) }
doLog(context: ExecutionContext, res: Response<T>): void { const ctx = context.switchToHttp() const request: Request = ctx.getRequest() const { url, headers, method, body } = request const ua = headers['user-agent']
this.logger.info( `${method} ${url} ${ua} ${JSON.stringify(body)} ${JSON.stringify(res)}` ) }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| catch(exception, host: ArgumentsHost): void { const ctx = host.switchToHttp() const response: Response = ctx.getResponse() const request: Request = ctx.getRequest() const status = exception.getStatus()
const data = {} as ResponseData
data.msg = (exception as ApiException).getErrorMessage() data.code = (exception as ApiException).getErrorCode()
this.doLog(request, data)
response.status(status).json(data) }
doLog(request: Request, data): void { const { url, headers, method, body } = request const ua = headers['user-agent']
this.logger.requestError( `${method} ${url} ${ua} ${JSON.stringify(body)} ${JSON.stringify(data)}` ) }
|
应用异常日志
应用异常的日志默认走的是Nest内置的日志模块(因为没有捕获),也就是控制台里输出的那些,因此还需要一个捕获其他异常的filter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| import { Catch, ArgumentsHost, Injectable } from '@nestjs/common' import { BaseExceptionFilter } from '@nestjs/core' import { CustomLoggerService } from '../../extends/logger/logger.service' import { Request } from 'express'
@Injectable() @Catch() export class GlobalExceptionsFilter extends BaseExceptionFilter { constructor(private readonly logger: CustomLoggerService) { super() }
catch(exception: Error, host: ArgumentsHost): void { super.catch(exception, host)
const ctx = host.switchToHttp() const request: Request = ctx.getRequest()
this.doLog(request, exception) }
doLog(request: Request, exception: Error): void { const { url, headers, method, body } = request const ua = headers['user-agent']
this.logger.error( `${method} ${url} ${ua} ${JSON.stringify(body)} ${exception.stack}` ) } }
|
注册的时候需要注意先后顺序问题
1 2 3 4 5
| providers: [ { provide: APP_FILTER, useClass: GlobalExceptionsFilter }, { provide: APP_FILTER, useClass: HttpExceptionFilter }, ]
|
异常走的filter自下到上的,假如反过来,业务异常会先走到GlobalExceptionsFilter
里,HttpExceptionFilter
是捕获不到的。