简介:
拦截器日志异常捕获
在前面两篇内容中已经对Nest的基础,日志,统一拦截,请求拦截有了一定的了解。但是如果需要将整个应用搭起来,我们需要将三者进行优化,以便后续开发
在这之前需要了解
我们仍旧需要对Nest所涉及到的几个基础知识进行一定的了解,当然我仍然推荐
阅读官方文档得到更全面的了解。
在 NestJS 中,filter、guard 和 interceptor 是三种不同的功能机制,它们各自有不同的用途和应用场景。每种机制都有其特定的应用场景,通过合理地使用它们,可以更好地管理和控制应用程序的行为。
- Filter(过滤器):用于异常处理,捕获并处理应用程序中的未处理异常。
- Guard(守卫):用于认证和授权,决定某个请求是否可以被处理。
- Interceptor(拦截器):用于在请求处理过程中进行额外的逻辑处理,修改请求或响应、添加日志、进行性能监控等。
以下是它们的具体区别和用途:
1. Filter(过滤器)官方文档传送门
就是用来捕获异常,然后对异常进行一系列处理(如日志,统一错误返回),然后返回一个Http的Response请求
用途:
主要用于处理异常(错误处理)。过滤器可以捕获应用程序中的未处理异常,并将它们转换为适当的 HTTP 响应。
工作方式: 当应用程序抛出异常时,过滤器会捕获该异常,并根据异常类型生成相应的 HTTP 响应。 过滤器可以是全局的,也可以是局部的(应用于特定的控制器或路由)。
示例:
/*这个示例是以express作为Http服务器,如果你使用的Fastify,你可根据Fastify的文档进行调整*/
import { ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus } from '@nestjs/common';
@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
catch(exception: unknown, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse();
const request = ctx.getRequest();
const status = exception instanceof HttpException ? exception.getStatus() : HttpStatus.INTERNAL_SERVER_ERROR;
/* 这里是Express 示例*/
response.status(status).json({
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
});
/* 下方为Fastify 示例, 其实区别不大*/
response.code(status).send({
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
});
}
}
2. Guard(守卫)官方文档传送门
用途:
主要用于认证和授权。守卫可以决定某个请求是否可以被处理(即是否允许访问特定的路由)。
工作方式: 守卫在请求到达控制器之前执行,可以基于请求中的信息(如 JWT 令牌、用户角色等)来决定是否允许访问。 守卫可以是全局的,也可以是局部的(应用于特定的控制器或路由)。
示例:
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const roles = this.reflector.get<string[]>('roles', context.getHandler());
if (!roles) {
return true;
}
const request = context.switchToHttp().getRequest();
const user = request.user;
return roles.some(role => user.roles?.includes(role));
}
}
3. Interceptor(拦截器)官方文档传送门
用途:
主要用于在请求处理过程中进行额外的逻辑处理。拦截器可以在请求处理的各个阶段(如请求前、请求后)执行逻辑。
工作方式: 拦截器可以在请求到达控制器之前、请求处理过程中以及响应发送之前执行。它们可以修改请求或响应、添加日志、进行性能监控等。 拦截器可以是全局的,也可以是局部的(应用于特定的控制器或路由)。
示例:
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
@Injectable()
export class TransformInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
// 这里返回的pipe(管道),如果你有python种使用过scrapy 你对这个一定再熟悉不过了
return next.handle().pipe(map(data => ({ data })));
}
}
Code it
理清所有逻辑后,就很明确了各个模块的职能
在我实际开发中,我是这样使用的
- 使用Guard(
auth.guard.ts)进行用户身份验证,可参考之前文章 - 使用另一个Interceptor(
request-logging.interceptor.ts) 进行请求日志的记录
import {
CallHandler,
ExecutionContext,
Injectable,
NestInterceptor,
} from '@nestjs/common';
import { Logger } from 'winston';
import { Inject } from '@nestjs/common';
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
@Injectable()
export class RequestLoggingInterceptor implements NestInterceptor {
constructor(
@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger,
) {}
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const request = context.switchToHttp().getRequest();
const method = request.method;
const url = request.url;
const now = Date.now();
return next.handle().pipe(
tap(() => {
const response = context.switchToHttp().getResponse();
const statusCode = response.statusCode;
const delay = Date.now() - now;
this.logger.info(`${method} ${url} ${statusCode} - ${delay}ms`, {
context: 'HTTP',
});
}),
);
}
}
- 使用Interceptor(
response.interceptor.ts)进行正常响应的处理,在异常响应时将异常抛出来给error.filter.ts进行处理
import {
CallHandler,
ExecutionContext,
Injectable,
NestInterceptor,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { catchError, map } from 'rxjs/operators';
import { IResponseData } from './index';
@Injectable()
export class ResponseInterceptor<T> implements NestInterceptor<T, any> {
intercept(
context: ExecutionContext,
next: CallHandler<T>,
): Observable<IResponseData> {
return next.handle().pipe(
map((data) => ({
success: true,
code: 200,
data,
})),
catchError((error) => {
// 直接抛出异常,用error.filter处理
throw error;
}),
);
}
}
- 使用Filter(
error.filter.ts) 处理所有的异常并调用logging记录日志,并统一返回错误内容
为了让所有接口都保持正常的Http200响应,我在处理错误时候,也会将HttpStatus改成200. 所以即便是后端报错,也会在Http请求上仍然是一个成功的请求。而在请求体中增加code和success来具体定义当前请求是否成功,且不成功的code,以便前端进行自定义处理
import { IResponseData } from '../response';
@Catch()
export class ErrorLoggingFilter implements ExceptionFilter {
constructor(
@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger,
) {}
catch(exception: unknown, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse();
// 统一返回格式为 IResponseData
const responseData: IResponseData = {
code: ErrorCode.SERVER_ERROR,
message: exception,
success: false,
};
let status = HttpStatus.INTERNAL_SERVER_ERROR;
if (exception instanceof CustomException) {
status = exception.getStatus();
responseData.message = exception.message;
responseData.code = exception.code;
responseData.data = exception.data;
} else if (exception instanceof HttpException) {
responseData.message = exception.message || exception.getResponse();
} else if (exception instanceof Error) {
responseData.message = exception.message;
}
const request = ctx.getRequest();
this.logger.error(
`${request.method} ${request.url} ${status} - ${JSON.stringify(responseData.message)}`,
{ context: 'Exception' },
);
// 强制将所有错误都使用200返回
response.code(200).send(responseData);
}
}
统一的返回格式定义:
export interface IResponseData {
success: boolean; // 是否成功
code: number; // 一个和前端对齐的code
data?: any; // 返回的数据
message?: string; // 当错误的时候会抛出消息
}