likes
comments
collection
share

Nest.js 从零到壹详细系列(四):异常过滤器&错误日志收集前言 对Nest不熟悉的,可先看我之前写的文章 前端想了

作者站长头像
站长
· 阅读数 48

前言

对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
评论
请登录