Nest.js 从零到壹详细系列(四):异常过滤器&错误日志收集前言 对Nest不熟悉的,可先看我之前写的文章 前端想了
前言
对Nest不熟悉的,可先看我之前写的文章
1. 基本概念
Nest内置了异常层负责处理整个应用程序中的所有抛出的异常。
修改app.controller.ts
import { Controller, Get, HttpException, HttpStatus } from '@nestjs/common';
@Controller()
export class AppController {
@Get()
getHello(): string {
throw new HttpException('Forbidden', HttpStatus.FORBIDDEN);
}
}
使用throw抛出一个HTTP异常错误。HttpException构造函数接受两个参数:第一个参数,关于错误的简短描述;第二个参数,HTTP状态码;
启动服务后,访问localhost:3000,接口返回:
{
"statusCode": 403,
"message": "Forbidden"
}
HTTP错误可自定义,对于非HTTP错误,接口将统一返回:
{
"statusCode": 500,
"message": "Internal server error"
}
2. 内置HTTP异常
上述使用throw new HttpException('Forbidden', HttpStatus.FORBIDDEN);抛出403无权限错误。还可以使用Nest内置的,基于HttpException类的异常模块ForbiddenException。
修改app.controller.ts
import { Controller, Get, ForbiddenException } from '@nestjs/common';
@Controller()
export class AppController {
@Get()
getHello(): string {
throw new ForbiddenException();
}
}
除了ForbiddenException,Nest提供的模板还有:
BadRequestException
UnauthorizedException
NotFoundException
ForbiddenException
NotAcceptableException
RequestTimeoutException
ConflictException
GoneException
PayloadTooLargeException
UnsupportedMediaTypeException
UnprocessableException
InternalServerErrorException
NotImplementedException
BadGatewayException
ServiceUnavailableException
GatewayTimeoutException
3. HTTP错误捕获
Nest内置的异常层返回的数据结构比较简单,正如上述提到的接口返回:
{
"statusCode": 403,
"message": "Forbidden"
}
可自定义HTTP错误捕获,以增加返回信息。
1. 首先使用命令创建一个过滤器:
nest g filter core/filter/http-exception --no-spec --flat
将会在core/filter下生成http-exception.filter.ts
2. 过滤器代码实现,修改http-exception.filter.ts
import {
ArgumentsHost,
Catch,
ExceptionFilter,
HttpException,
} from '@nestjs/common';
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp(); // 获取请求上下文
const response = ctx.getResponse(); // 获取请求上下文中的 response 对象
const request = ctx.getRequest(); // 获取请求上下文中的 request 对象
const status = exception.getStatus(); // 获取异常状态码
// 设置错误信息
const message = exception.message
? exception.message
: `${status >= 500 ? 'Service Error' : 'Client Error'}`;
// 使用底层平台express的response,设置返回的状态码, 发送错误信息
response.status(status).json({
data: {},
message,
code: -1,
path: request.url,
});
}
}
@Catch(HttpException):过滤器捕获HttpException错误
catch方法接受两个参数:exception是当前正在处理的异常对象,因为是捕获HttpException错误,因此为HttpException类型;host是一个 ArgumentsHost 对象,ArgumentsHost 是一个程序对象,这里使用它来获取对 Request 和 Response 对象的引用。
3. 最后需要在main.ts中全局注册
//...
import { HttpExceptionFilter } from 'src/core/filter/http-exception.filter';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// 注册全局错误的过滤器
app.useGlobalFilters(new HttpExceptionFilter());
await app.listen(3000);
}
bootstrap();
这样对请求错误就可以统一的返回了,返回请求错误只需要抛出异常即可,比如之前在app.controller.ts中定义的错误:
@Get()
getHello(): string {
throw new NotImplementedException();
}
访问localhost:3000,接口返回:
{
"data": {},
"message": "Not Implemented",
"code": -1,
"path": "/",
"timestamp": "2024-08-21T05:47:44.929Z"
}
4. 全局错误捕获
为了捕获每一个未处理的异常(不管异常类型如何),将 @Catch() 装饰器的参数列表设为空。
1. 首先使用命令创建一个过滤器:
nest g filter core/filter/any-exception --no-spec --flat
2. 过滤器代码实现,修改any-exception.filter.ts
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;
response.status(status).json({
data: {},
message: `Service Error: ${exception}`,
code: -1,
path: request.url,
});
}
}
3. 最后需要在main.ts中全局注册
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { HttpExceptionFilter } from 'src/core/filter/http-exception.filter';
import { AllExceptionsFilter } from 'src/core/filter/any-exception.filter';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// 注册全局错误的过滤器
app.useGlobalFilters(new AllExceptionsFilter());
app.useGlobalFilters(new HttpExceptionFilter());
await app.listen(3000);
}
bootstrap();
提示:由于Nest的过滤器是后注册的先执行,因此 AllExceptionsFilter 要在 HttpExceptionFilter 的上面,否则 HttpExceptionFilter 就不生效了,全被 AllExceptionsFilter 捕获了。
手动抛出个代码异常,测试下,修改app.controller.ts
import { Controller, Get } from '@nestjs/common';
@Controller()
export class AppController {
@Get()
getHello(): string {
throw Error('222');
}
}
访问localhost:3000,接口返回:
{
"msg": "Service Error: Error: 222",
"statusCode": 500,
"path": "/"
}
5. 日志收集
Nest内置了日志类,但功能较少,这里使用 winston 进行日志收集。
1. 安装
yarn add winston@^3.14.2 yarn add winston-daily-rotate-file@^5.0.0
2. 新建lib/winston-logger.ts
import * as winston from 'winston';
import 'winston-daily-rotate-file';
export const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
//时间戳
winston.format.timestamp({
format: 'YYYY-MM-DD HH:mm:ss',
}),
//将错误对象转换为字符串
winston.format.errors({ stack: true }),
//处理日志条目中的占位符 %s 和 %o
winston.format.splat(),
//将整个日志条目转换为 JSON 格式
winston.format.json(),
),
transports: [
//错误日志会被写入logs/error.log文件中
new winston.transports.DailyRotateFile({
filename: '%DATE%.log',
dirname: 'logs',
datePattern: 'YYYY-MM-DD',
maxSize: '20m', //文件最大的大小
maxFiles: '60d', //文件存储最长的时间,过期会被删除
}),
],
});
3. 在全局错误捕获中添加日志,修改any-exception.filter.ts
import {
ExceptionFilter,
Catch,
ArgumentsHost,
HttpException,
HttpStatus,
} from '@nestjs/common';
//引入
import { logger } from 'src/lib/winston-logger';
@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;
const data = {
data: {},
message: `Service Error: ${exception}`,
code: -1,
path: request.url,
};
//记录日志
logger.error(data);
response.status(status).json(data);
}
}
重启启动项目后,访问localhost:3000,项目根目录生成logs文件夹,报错信息已在生成的日志文件中
转载自:https://juejin.cn/post/7411168497466032164