使用中间件、拦截器和过滤器构建日志系统
中间件
概念
这里写出个人对中间件的理解:所谓的中间件,就是函数式编程中链式调用的一环。通过对纯函数的使用,对一个固定的输入值都会有一个固定的输出值,在保证功能扩展的同时可以最大程度的保证高度的独立性。
中间件函数的功能:
- 对请求和相应对象进行更改。
- 结束请求 - 响应周琦。
- 使用 next() 调用中间件函数。
跑题,一个标准的 redux 中间件写法:
state => next => action => { .... return next(action)}
(似曾相识。
使用中间件记录请求日志
- 创建中间件
nest g middleware logger middleware // 在 middleware 文件夹下创建名为 logger.middleware.ts 的中间件
- 修改
src/middleware/logger.middleware.ts
文件
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response } from 'express';
import { Logger } from 'src/utils/log4js';
// 这里是创建中间件时 nest 提供的标准写法
@Injectable()
export class LoggerMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: () => void) {
const code = res.statusCode;
next();
// 组装日志信息
const logFormat = `Method: ${req.method} \n Request original url: ${req.originalUrl} \n IP: ${req.ip} \n Status code: ${code} \n`;
// 根据状态码,进行日志类型区分
if (code >= 500) {
Logger.error(logFormat);
} else if (code >= 400) {
Logger.warn(logFormat);
} else {
Logger.access(logFormat);
Logger.log(logFormat);
}
}
}
// 这是函数式写法,二选一即可
export function logger(
req: Request,
// @Body() body,
res: Response,
next: () => any,
) {
const code = res.statusCode;
next();
const logFormat = ` >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
Request original url: ${req.originalUrl}
Method: ${req.method}
IP: ${req.ip}
Status code: ${code}
Parmas: ${JSON.stringify(req.params)}
Query: ${JSON.stringify(req.query)}
Body: ${JSON.stringify(
req.body,
)} \n >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
`;
if (code >= 500) {
Logger.error(logFormat);
} else if (code >= 400) {
Logger.warn(logFormat);
} else {
Logger.access(logFormat);
// Logger.log(logFormat);
}
}
- 在
main.ts
引入中间件
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { logger } from './middleware/logger.middleware';
import * as express from 'express';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.use(express.json()); // For parsing application/json
app.use(express.urlencoded({ extended: true }));
app.use(logger); // 引入日志中间件
await app.listen(3000);
}
bootstrap();
注:需要
app.use(express.json())
和app.use(express.json())
才能拿到req,body
和req.params
。 注2:body-parser
已被废弃而且毛用没有,别问我咋知道。
拦截器
概念
中间件针对 request,拦截器针对 response。
使用拦截器记录 response 日志
- 创建拦截器
nest g interceptor transform interceptor // 在 interceptor 文件下创建名为 transform.interceptor.ts 的拦截器
- 修改 transform.interceptor.ts 文件
import {
CallHandler,
ExecutionContext,
Injectable,
NestInterceptor,
} from '@nestjs/common';
import { Logger } from '../utils/log4js';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
@Injectable()
export class TransformInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const req = context.getArgByIndex(1).req;
return next.handle().pipe(
map((data) => {
const logFormat = ` <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
Request original url: ${req.originalUrl}
Method: ${req.method}
IP: ${req.ip}
User: ${JSON.stringify(req.user)}
Response data:\n ${JSON.stringify(data.data)}
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<`;
Logger.info(logFormat);
Logger.access(logFormat);
return data;
}),
);
}
}
此处用到了 pip 和 rxjs。
输出结果时 User 为 undefiend 是因为未将用户信息绑定在 req 上,如果使用 AuthGuards('jwt) 则会显示(解密后的) user 信息。
- 在 main.ts 中引入
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { logger } from './middleware/logger.middleware';
import * as express from 'express';
import { TransformInterceptor } from './interceptor/transform.interceptor';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.use(express.json()); // For parsing application/json
app.use(express.urlencoded({ extended: true }));
app.use(logger); // 引入日志中间件
app.useGlobalInterceptors(new TransformInterceptor());
await app.listen(3000);
}
bootstrap();
使用拦截器规范化返回值
- 创建
data.interceptor.ts
文件
nest g interceptor data interceptor
- 修改
data.interceptor.ts
文件
import {
CallHandler,
ExecutionContext,
Injectable,
NestInterceptor,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
@Injectable()
export class DataInterceptor implements NestInterceptor {
intercept(
context: ExecutionContext,
next: CallHandler<any>,
): Observable<any> {
return next.handle().pipe(map((data) => ({ code: 200, data })));
}
}
- 引入 main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { logger } from './middleware/logger.middleware';
import * as express from 'express';
import { TransformInterceptor } from './interceptor/transform.interceptor';
import { DataInterceptor } from './interceptor/data.interceptor';
import { AllExceptionFilter } from './filter/exception.filter';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.use(express.json()); // For parsing application/json
app.use(express.urlencoded({ extended: true }));
app.use(logger); // 引入日志中间件
app.useGlobalInterceptors(new TransformInterceptor()); // 日志记录
app.useGlobalInterceptors(new DataInterceptor()); // 返回值规范化
await app.listen(3000);
}
bootstrap();
过滤器
概念
nestjs 中的过滤器全称应该是异常过滤器,和传统服务器开发(spring、php)中的过滤器概念并不是完全相同。
事实上 nest 中的中间件相当于拦截器,拦截器相当于过滤器,而过滤器事实上是全局错误处理....
使用过滤器记录错误日志
- 创建过滤器
nest g filter all-exception filter
- 编写
all-exception.filter.ts
import {
ArgumentsHost,
Catch,
ExceptionFilter,
HttpException,
HttpStatus,
} from '@nestjs/common';
import { Request, Response } from 'express';
import { Logger } from 'src/utils/log4js';
@Catch()
export class AllExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
const status =
exception instanceof HttpException
? exception.getStatus()
: HttpStatus.INTERNAL_SERVER_ERROR;
const logFormat = ` <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
Request original url: ${request.originalUrl}
Method: ${request.method}
IP: ${request.ip}
Status code: ${status}
Response: ${exception.toString()} \n <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
`;
Logger.error(logFormat);
response.status(status).json({
statusCode: status,
error: exception.message,
msg: `${status >= 500 ? '访问出错' : '链接异常'}`,
});
}
}
- 在 main.ts 中引入
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { logger } from './middleware/logger.middleware';
import * as express from 'express';
import { TransformInterceptor } from './interceptor/transform.interceptor';
import { DataInterceptor } from './interceptor/data.interceptor';
import { AllExceptionFilter } from './filter/exception.filter';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.use(express.json()); // For parsing application/json
app.use(express.urlencoded({ extended: true }));
app.use(logger); // 引入日志中间件
app.useGlobalInterceptors(new TransformInterceptor()); // 引入日志拦截器
app.useGlobalInterceptors(new DataInterceptor()); // 引入数据处理拦截器
app.useGlobalFilters(new AllExceptionFilter()); // 引入异常过滤器
await app.listen(3000);
}
bootstrap();