일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- GraphQL
- 코딩테스트
- Interceptor
- MySQL
- css
- winston
- REST API
- LifeCycle
- Spring
- javascript
- 인접행렬
- typescript
- Linux
- JWT
- java
- node.js
- html
- bean
- OOP
- Deep Dive
- TIL
- Kubernetes
- 알고리즘
- 탐욕법
- dfs
- 자료구조
- 인접리스트
- puppeteer
- nestjs
- 프로그래머스
- Today
- Total
처음부터 차근차근
[NestJS] Custom Exception 구현 본문
NestJS에서는 Custom Exception을 구현할 수 있습니다.
Custom Exception을 구현하여 Production 환경에서는 물론 개발 환경에서도 더 쉽고 빠른 디버깅이 가능하도록 구현할 수 있습니다.
저는 Custom Exception을 구현하여 다음과 같은 기능을 구현했습니다.
- message가 아닌 errorCode를 구현하여 클라이언트에서 예외를 명확하게 구분할 수 있도록 수정
- 일반 예외처리(설문지 조사 실패, ID 로그인 실패 등)과 서버에서 발생한 치명적인 에러(ORM Error, Server 자체 에러)를 구분하고, 치명적인 에러의 경우 StatusCode를 500으로 일괄 처리하며, 서버 문제를 외부에 노출시키지 않도록 처리
- Exception을 직접 구현하여 코드 레벨에서 가독성이 좋게 하며, 재사용성을 보장
GraphQL의 Error handling
HTTP에서 에러가 발생한 경우에는, JSON을 통해 우리가 원하는 형식을 전달할 수 있습니다.
그러나 GraphQL에서 에러가 발생한 경우에는, 기본적인 형식이 지정되어 있습니다.
GraphQL에서 Error를 custom 하려면 extensions에 해당하는 내역을 추가할 수 있습니다.
GraphQL에서 Error handling을 할 경우, 기본적으로 message, locations, path는 고정이며, extensions을 통해 custom error에 해당하는 내역을 추가할 수 있습니다.
구현 방법
1. 새로운 Custom Exception Class 구현
우리가 원하는 새로운 Exception Class를 구현해야 합니다.
기본적으로 GraphQL의 Error에서는 Error 객체에서 상속받고 있으며, message를 인자로 넘겨줘야 합니다.
export declare class GraphQLError extends Error {
readonly locations: ReadonlyArray<SourceLocation> | undefined;
readonly path: ReadonlyArray<string | number> | undefined;
readonly nodes: ReadonlyArray<ASTNode> | undefined;
readonly source: Source | undefined;
readonly positions: ReadonlyArray<number> | undefined;
readonly originalError: Error | undefined;
readonly extensions: GraphQLErrorExtensions;
constructor(message: string, options?: GraphQLErrorOptions);
constructor(
message: string,
nodes?: ReadonlyArray<ASTNode> | ASTNode | null,
source?: Maybe<Source>,
positions?: Maybe<ReadonlyArray<number>>,
path?: Maybe<ReadonlyArray<string | number>>,
originalError?: Maybe<
Error & {
readonly extensions?: unknown;
}
>,
extensions?: Maybe<GraphQLErrorExtensions>,
);
get [Symbol.toStringTag](): string;
toString(): string;
toJSON(): GraphQLFormattedError;
}
하지만 이 코드에서는 기본적으로 errorCode와 timestamp 속성 값이 없으므로, Custom Exception Class를 구현해보겠습니다.
// common/interface/base.exception.interface.ts
import { GraphQLErrorExtensions } from 'graphql';
export interface IBaseException {
message: string;
// GraphQl의 extensions interface로 type 지정
extensions: GraphQLErrorExtensions;
}
// src/common/exceptions/base.exception.ts
import { IBaseException } from '../interfaces/base.exception.interface';
import { GraphQLError, GraphQLErrorExtensions } from 'graphql';
// GraphQLError를 상속받아 사용
export class BaseException extends GraphQLError implements IBaseException {
constructor(errorCode: string, message: string, statusCode: number) {
super(message);
// extension에 추가할 내용
// 에러 코드 추가
this.extensions.errorCode = errorCode;
// http 상태 코드 추가
this.extensions.code = statusCode;
// timestamp 추가
this.extensions.timestamp = new Date().toLocaleString();
}
message: string;
extensions: GraphQLErrorExtensions;
}
GraphQLError에서 상속받아와 BaseException을 구현하였습니다.
기본적으로 message를 상속받았으며, extension에 추가할 내용으로 errorCode와 StatusCode, timestamp를 추가시켰습니다.
2. errorCode와 Custom Exception Class 생성
// src/common/exception/codeError.enum.ts
export enum errorCode {
NotFoundSurvey = '0001',
NotFoundQuestion = '0002',
NotFoundAnswer = '0003',
NotEqualSelectAndScore = '0004',
NotEqualQuestionAndAnswer = '0005',
UnCatched = '9999',
}
먼저 Enum을 간단하게 생성하였습니다.
현재 정의한 사용자 에러 코드는 5개로, 간단하게 정의하였으며, 예상한 Exception이 아닌 경우 Uncatched를 통해 알 수 있도록 Enum에 추가하였습니다.
이제 이 에러코드를 사용하여 Custom Exception을 제작하겠습니다.
// src/common/exceptions/question.exception.ts
import { HttpStatus } from '@nestjs/common';
import { BaseException } from './base.exception';
import { errorCode } from './codeError.enum';
export class NotFoundQuestionException extends BaseException {
constructor() {
super(
errorCode.NotFoundQuestion,
'문항을 찾을 수 없습니다.',
HttpStatus.NOT_FOUND,
);
}
}
export class NotEqualSelectAndScoreException extends BaseException {
constructor() {
super(
errorCode.NotEqualSelectAndScore,
'선택지 수와 점수 개수가 다릅니다.',
HttpStatus.BAD_REQUEST,
);
}
}
설문지 문항에 필요한 Custom Exception을 제작했습니다.
기본적으로 errorCode와 message, StatusCode를 받습니다.
errorCode를 통해 동일한 statusCode, message를 받아도 할당된 Exception이 다르기 때문에 클라이언트에서도 예외를 명확하게 구분할 수 있습니다.
// 단일 문항 조회 메서드
async getQuestion(id: number) {
const question = await this.questionRepository.findOneBy({ id });
if (!question) throw new NotFoundQuestionException();
return question;
}
3. Exception Filter 구현
이제 예외 발생 시의 응답 메세지를 원하는 형태로 전달하기 위해 Filter를 새로 구현해야 합니다.
@Catch() 데코레이터의 인자에는 ExceptionFilter가 추적할 예외 클래스를 명시하며, 만약 내부 인자에 아무것도 넣지 않는다면 애플리케이션에서 처리하지 못한 예외까지도 모두 추적할 수 있습니다.
import { ArgumentsHost, Catch, LoggerService } from '@nestjs/common';
import { GqlArgumentsHost, GqlExceptionFilter } from '@nestjs/graphql';
import { BaseException } from '../exceptions/base.exception';
import { UnCatchedException } from '../exceptions/uncatched.exception';
@Catch()
export class AllExceptionFilter implements GqlExceptionFilter {
constructor(private readonly logger: LoggerService) {}
catch(exception: any, host: ArgumentsHost) {
// gql 전용 context로 변경하기.
// https://docs.nestjs.com/graphql/other-features
const gqlHost = GqlArgumentsHost.create(host);
// host에서 info 정보를 받아와 path와 typename을 받아올 수 있다.
const info = gqlHost.getInfo();
const ip = gqlHost.getContext().req.ip;
if (exception instanceof BaseException) {
// 우리가 지정한 예외처리의 경우에는
// customHandler로 들어가며, warn을 통해 명시한다.
this.logger.warn({
context: 'CustomHandler',
message: exception.message,
ip,
extensions: exception.extensions,
typename: info.path.typename,
path: info.fieldName,
});
return exception;
} else {
// 그 외의 에러처리의 경우
// log를 error로 남겨 심각한 상태의 로그라고 남기며,
// slack이나 다른 APM tool을 통해 알 수 있도록 할 수 있다.
const newException = new UnCatchedException(exception.message);
this.logger.error({
context: 'FatalError',
message: newException.message,
stack: newException.stack,
ip,
extensions: newException.extensions,
typename: info.path.typename,
path: info.fieldName,
});
return newException;
}
}
}
제가 작성한 코드는
- 애플리케이션에서 처리한 예외: warn 메세지로 남겨 표시 진행
- app에서 처리하지 못한 예외: error로 Log를 남기며, 이후 slack이나 다른 APM Tool을 통해 알람 수신 진행
이후 exception을 return하여 어떠한 예외처리가 발생했는지 구분하여 Logger를 작성할 수 있습니다.
이를 통해 Application에서 처리하지 못한 예외처리에 대하여 알람 처리를 받을 수 있으며, Logger에 남아있기 때문에 빠른 대응이 가능합니다.
이때 예외처리 Logger에는 Stack또한 포함되어 있어 손쉽게 문제가 발생한 곳에 접근이 가능합니다.
참고
https://velog.io/@cataiden/nestjs-custom-exception
https://docs.nestjs.com/graphql/other-features
https://docs.nestjs.com/exception-filters#exception-filters-1
https://spec.graphql.org/draft/#sec-Errors
'FrameWork > NestJS' 카테고리의 다른 글
[NestJS] Winston Logger Interceptor 구현 (0) | 2023.12.03 |
---|---|
[NestJS] Winston Logger 초기 설정 및 Logger 사용 (0) | 2023.12.01 |
[NestJS] GraphQL Mutation (1) | 2023.11.30 |
[NestJS] GraphQL Resolvers (0) | 2023.11.29 |
[NestJS] NestJS에서 GraphQL 초기 설정 (1) | 2023.11.29 |