1. 에러 핸들링 디자인 - SaekKkanDa/OppaManyColorTone GitHub Wiki

프로젝트 규모가 커질수록 다양한 사람과 팀이 프로젝트에 기여하게 됩니다. 그리고 막대한 에러가 어떤 곳에 수집 될 것 입니다.

수집된 방대한 에러를 분석할 때 "이 에러는 뭐지?", "이 에러는 어느 팀이 가장 잘 알고 있을까?" 라는 질문들을 생각하곤 합니다.

프로젝트 규모가 커지게 되면 어떤 에러를 누군가에게 할당하는 것은 비용입니다.

아래 설계한 에러 핸들링 디자인은 에러를 필터링 할 수 있게 태그를 등록하여 이 비용을 줄이는 것을 목표로 하고 있습니다.

개요

OMCT 프로젝트에서는

  • Sentry를 통해서 에러 수집을 하고
  • React.ErrorBoundary를 활용하여 에러에 태그를 등록하여 Sentry에 효과적으로 에러 수집하는 것을 목표로 하고 있습니다.

Sentry에 수집된 로그를 빠르게 그룹핑을 하는 것을 목적으로 에러 핸들링을 디자인 했습니다.

플로우

설계한 에러 핸들링은 아래와 같은 플로우를 가지고 있습니다.

error number 등록 → custom error 생성 → 예외 throw → error boundary가 error capture → tag 등록(현재는 생략) → sentry 전송

위의 볼드체로 표시된 플로우를 개발자가 구현하게 됩니다.

에러 핸들링을 하는 방법은 단순 합니다.

  1. 본인의 Custom Error을 생성.
  2. error 발생 시 throw
  3. throw 된 error 핸들링 (선택)

Custom Error 생성

아래는 생성되는 custom error의 클래스 다이어 그램 입니다. (정확히 클래스 다이어그램은 아닙니다)

클래스 다이어그램

CustomError 클래스는 기본 내장 클래스 Error을 상속받고 있습니다.

CustomErrorerrNo 를 프로퍼티로 가지고 있습니다.

  • **errNo** : errorKeyValue.ts 파일에 등록된 에러 번호입니다. custom error instance를 생성하기 전에 err number를 등록하시거나 재활용 하시면 됩니다.

그래서 뭘 하면 되나요?

  1. errorKeyValue.ts에 에러번호 등록
  2. CustomError를 상속하는 custom error 생성

ErrorKeyValue에 err number 등록

에러값에 대한 정의를 한 곳에 모으기 위해서 하나의 파일에 등록을 했습니다.

./src/constants/errorKeyValue.ts 파일에 에러번호를 enum 값으로 등록하고 omctError Map 객체에 에러번호에 맞는 message를 등록합니다.

// ./src/constants/errorKeyValue.ts
export enum OmctErrorNo {
  /* 0 ~ 20 : Common Error */
  COMMON_ERROR_START = 0,
  COMMON_UNEXPECTED_CONDITION = COMMON_ERROR_START,
  COMMON_INVALID_PARAMETER,
  COMMON_ERROR_FINISH = 20,
}

const omctError = new Map<OmctErrorNo, string>();

// prettier-ignore
/* 0 ~ 20 : Common Error */
omctError.set(OmctErrorNo.COMMON_UNEXPECTED_CONDITION, '예기치 못한 에러가 발생하였습니다.');
omctError.set(OmctErrorNo.COMMON_INVALID_PARAMETER, "잘못된 파라미터 입니다.");

export default omctError;
  1. OmctErrorNo 에 에러 번호를 등록합니다. 에러 번호는 동적으로 변하지 않게 하기 위해서 범위를 지정해서 사용합니다. (예: 020: common error, 2130: Share Error)
  2. omctError Map 객체에 해당 에러 번호에 맞는 message를 등록합니다. 이 값은 Error.message 값이 됩니다.

Custom Error 생성

import CustomError, { CustomErrorConstructor } from "@Utils/customError";

class ShareError extends CustomError {
  constructor(props: CustomErrorConstructor) {
    super(props);
    this.name = "ShareError";
  }
}

new ShareError({ errorNo: OmctErrorNo.SHARE_CLIPBOARD_COPY_ERROR });

CustomError 를 상속하는 Custom Error를 생성하며 생성자는 CustomErrorConstructor 타입을 파라미터로 전달 받아야 합니다.

예외 throw

만든 custom error가 throw 될 떄의 로직 입니다.

에외 처리 플로우

  1. 유저가 예외를 throw
  2. Error Boundary가 해당 예외를 capture 하여 sentry에 들어갈 tag를 등록
  3. Sentry에 로그 전송

Error Boundary

algebraic effect를 위해 Error Boundary를 사용하며, 간단하게 책임을 부모한테 넘긴다는 개념으로 이해하시면 됩니다.

Error Boundary는 Sentry 로그 수집을 위해서 Sentry.ErrorBoundary를 사용하였습니다.

// ./src/common/ErrorBoundary.tsx
import React, { useCallback, useMemo } from "react";
import * as Sentry from "@sentry/react";

interface ErrorBoundaryProps {
  children: React.ReactNode;
}

function ErrorBoundary({ children }: ErrorBoundaryProps) {
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  const beforeCapture = (scope: Sentry.Scope, error: Error) => {
    // HJ TODO: capture 전 무언가
  };

  const fallback = useCallback(() => <>{children}</>, [children]);

  const sentryProps = useMemo(
    () => ({
      beforeCapture,
      fallback,
    }),
    [fallback]
  );

  return (
    <Sentry.ErrorBoundary {...sentryProps}>{children}</Sentry.ErrorBoundary>
  );
}

export default ErrorBoundary;

현재 만든 Error Boundary는 capture전에 핸들링하는 동작이 없습니다. 또한 fallback 은 등록하지 않았습니다. 디자인이 필요합니다.

그래서 뭘 하면 되나요?

예외를 throw 하시면 됩니다. 개발자가 예외 처리를 하지 않았다면 Sentry에 전송 됩니다. try ...catch 문 같은 방법으로 예외를 처리하셨다면 다시 throw 를 해야 합니다.

throw new CustomError({
  errorNo: OmctErrorNo.COMMON_INVALID_PARAMETER,
});

실제로 에러가 발생한다면 Sentry에서 아래와 같이 확인 하실 수 있습니다.

sentry 샘플

FAQ

Q) develop 환경에서 sentry가 동작하지 않습니다.

A) react development mode 에서 에러가 두번 발생할 수 있습니다. production mode에서는 잘 동작합니다.(https://docs.sentry.io/platforms/javascript/guides/react/features/error-boundary/)

Q) Error 값 말고 다른 정보도 Sentry 로그에 출력되게 할려면 어떻게 하나요?

A) error가 throw 될 때 까지의 breadcrumbs가 같이 수집이 되기 때문에 console 로그를 출력하시면 같이 수집 됩니다.