Seize the day

POST : Backend study

Nest.js 예외처리 ExceptionFilter 적용하기.

https://velog.io/@anjinwoong/Nest.js-Exception-ExceptionFilter 를 참고했고 자세한 구현 설명은 생락하고 코드 위주로..

ErrorCodeException

입력 파라미터 체크 실패이거나, 로직상 정상적인 응답이지만 API상의 에러코드를 응답해야 하는 경우를 위해서 ErrorCodeException을 정의했다. 예외가 발생하면 file logging과 discord notify를 같이 적용한다. 

export class ErrorCodeException extends Error {
    errorCode: number
    
    constructor(message: string, errorCode: number) {
        super(message)
        this.errorCode = errorCode
    }
}

@Catch(ErrorCodeException)
export class ErrorCodeExceptionFilter implements ExceptionFilter {

  private readonly logger = winstonLogger;

  catch(exception: ErrorCodeException, host: ArgumentsHost): void {
    const ctx = host.switchToHttp();
    const request = ctx.getRequest<Request>();
    const response = ctx.getResponse<Response>();
    const errorCode = exception.errorCode;
    const timestamp = new Date().toLocaleString();

    const content = {
      code: errorCode,
      message: exception.message,
      path: request.url,
      timestamp: timestamp,
    };

    response.status(200).send(content);

    if (IS_PRODUCTION) {
      axios.post(DISCORD_WEBHOOK_URL, {
        content: JSON.stringify(content),
      });
    }

    // write log file
    this.logger.error(`errorCode: ${errorCode} message: ${exception.message} path: ${request.url} ip: ${request.ip}`)
  }
}

 

HttpException이 발생하면 경고 log를 작성하고 역시 discord로 알린다. 

@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {

  private readonly logger = winstonLogger;

  catch(exception: HttpException, host: ArgumentsHost): void {
    const ctx = host.switchToHttp();
    const request = ctx.getRequest<Request>();
    const response = ctx.getResponse<Response>();
    const statusCode = exception.getStatus();
    const timestamp = new Date().toLocaleString();

    const content = {
      statusCode: statusCode,
      message: exception.message,
      path: request.url,
      timestamp: timestamp,
    };

    response.status(statusCode).send(content);

    if (IS_PRODUCTION) {
      axios.post(DISCORD_WEBHOOK_URL, {
        content: JSON.stringify(content),
      });
    }

    // write log file
    this.logger.warn(`statusCode: ${statusCode} message: ${exception.message} path: ${request.url} ip: ${request.ip}`)
  }
}

 

그외 new Error("fatal error")나 알 수 없는 오류가 발생할 경우 (발생하지 말아야 할 오류이다.)

@Catch()
export class AllExceptionsFilter extends BaseExceptionFilter {

  catch(exception: unknown, host: ArgumentsHost) {    
    super.catch(exception, host);
    
    if (IS_PRODUCTION) {
        const ctx = host.switchToHttp();
        const request = ctx.getRequest<Request>();    
        const message = exception?.toString();
        const timestamp = new Date().toLocaleString();
        
        const logMessage = `${timestamp} message: ${message} path: ${request.url} ip: ${request.ip}`;
        const stack =  super.isExceptionObject(exception)? exception.stack : null;
    
        axios.post(DISCORD_WEBHOOK_URL, {
            content: `${logMessage} ${stack}`,
        });        
    }    
  }
}

super.catch(exception, host); 에서 file로 log를 저장하므로 , discord로 알리는 코드만 구현한다.

 

ExceptionFilter 적용

ap.controller.ts

@Controller()
@UseFilters(ErrorCodeExceptionFilter)
export class AppController {
...

main.ts

const { httpAdapter } = app.get(HttpAdapterHost);
app.useGlobalFilters(new AllExceptionsFilter(httpAdapter), new HttpExceptionFilter());

HttpException은 HttpExceptionFilter에서 처리하고 그 외는  AllExceptionFilter 에서 처리하고 싶은데, HttpExceptionFilter를 모듈에 설치하면 제대로 동작하지 않는다. useGlobalFilters에 두 개다 추가해야하고, 심지어 추가하는 순서도 중요하다. HttpExceptionFilter가 앞에 있으면 제대로 동작하지 않는다. 

@Controller() @UseFilters(ErrorCodeExceptionFilter) 내보내기 클래스 AppController { ...
 
top

posted at

2024. 5. 2. 00:35


CONTENTS

Seize the day
BLOG main image
김대정의 앱 개발 노트와 사는 이야기
RSS 2.0Tattertools
공지
아카이브
최근 글 최근 댓글
카테고리 태그 구름사이트 링크