Javascript

JavaScript 디버깅의 숨겨진 보석: error.cause, 에러의 근본 원인을 쉽게 찾아볼까요?

드리프트2 2025. 3. 22. 17:12

 

안녕하세요!

개발하다 보면 에러 때문에 머리 싸맬 때가 많으시죠?

특히 에러의 원인을 찾는 건 정말 어려운 숙제인데요.

오늘은 JavaScript (자바스크립트) 디버깅을 훨씬 수월하게 만들어주는 숨겨진 보석 같은 기능, 바로 error.cause에 대해 알아보려고 합니다.

  1. 디버깅의 어려움

디버깅하면서 가장 힘든 점이 뭘까요?

저는 단연 에러의 근본 원인을 추적하는 거라고 생각하는데요.

이런 상황을 한번 상상해보세요.

const func = () => {
  doSth('A');
  doSth('B');
};

 

func 함수가 에러를 던졌을 때, 이 에러가 어느 단계에서 발생했는지 바로 알 수 있을까요?

doSth('A') 때문인지, doSth('B') 때문인지, 아니면 func 함수 자체의 문제인지… 에러 메시지만 봐서는 도대체 감이 안 잡힐 때가 많습니다.

에러에 맥락 정보가 부족하기 때문인데요.

  1. 일반적인 해결 방법

이 문제를 해결하려고 흔히 사용하는 방법이 있습니다.

바로 try...catch 구문을 사용해서 에러를 잡고, 새로운 에러를 던지는 건데요.

const func = () => {
  try {
    doSth('A');
  } catch (error) {
    throw new Error('A에서 에러 발생', error);
  }
  try {
    doSth('B');
  } catch (error) {
    throw new Error('B에서 에러 발생', error);
  }
};

 

이렇게 하면 에러가 어디에서 발생했는지 좀 더 쉽게 알 수 있습니다.

하지만 이 방법은 몇 가지 아쉬운 점이 있는데요.

  • 에러 세부 정보 손실: 에러에 자세한 정보 (예: payload (페이로드), HTTP (에이치티티피) 상태 코드, 에러 코드)가 담겨 있는 경우, 이 방법은 새로 만든 에러에 doSth의 에러 메시지만 추가할 뿐입니다. 원래 에러의 중요한 세부 정보들, 심지어 stack trace (스택 트레이스)까지 싹 다 날아가 버립니다.
  • 로그 가독성 저하: 에러가 발생할 수 있는 지점이 두 군데보다 많아지면, 로그가 너무 복잡해져서 읽기가 힘들어집니다.
  • 의도 표현의 모호함: 새로운 에러가 특정 doSth 함수에서 잡은 에러 때문에 발생했다는 것을 명확하게 코드로 표현하기 어렵습니다. 코드만 봐서는 의도가 제대로 전달되지 않을 수 있다는 거죠.
  1. error.cause, 문제 해결사 등장

이런 문제들을 해결하기 위해 ECMAScript 2022 (이에스6 2022)에서 드디어 error.cause라는 멋진 기능이 나왔습니다.

error.cause를 사용하면 새로운 error object (에러 객체)를 만들 때 에러의 근본 원인을 지정할 수 있습니다.

error.cause를 사용해서 에러들을 연결하면, 마치 에러 체인처럼 만들 수 있어서, 디버깅할 때 문제의 근본 원인을 더 쉽게 추적할 수 있습니다.

간단한 예시를 한번 볼까요?

try {
  // 에러가 발생할 수 있는 작업
} catch (error) {
  throw new Error('뭔가 잘못됐어요', { cause: error });
}

 

이렇게 하면 에러 사이에 인과 관계를 만들 수 있습니다.

앞서 봤던 func 예시를 error.cause를 사용해서 다시 한번 바꿔볼까요?

const func = () => {
  try {
    doSth('A');
  } catch (error) {
    throw new Error('doSth("A") 실행 중 에러 발생', { cause: error });
  }
  try {
    doSth('B');
  } catch (error) {
    throw new Error('doSth("B") 실행 중 에러 발생', { cause: error });
  }
};

 

이렇게 하면 하위 레벨 함수 (doSth('A'))에서 던져진 에러를 잡아서, 맥락 정보를 담은 새로운 에러 메시지 ("doSth('A') 실행 중 에러 발생")를 던지면서도, 원래 에러의 자세한 정보 ("A는 유효하지 않은 인수입니다.")는 그대로 유지할 수 있습니다.

  1. 에러 체인 만들기

error.cause의 또 다른 장점은 에러 체인을 만들 수 있다는 건데요.

에러 체인을 사용하면 애플리케이션의 여러 계층을 거쳐서 문제의 원인을 더 깊이 파고들 수 있습니다.

const func = () => {
  try {
    try {
      try {
        doSth('A');
      } catch (error) {
        throw new Error('깊이 3에서 에러 발생', { cause: error });
      }
    } catch (error) {
      throw new Error('깊이 2에서 에러 발생', { cause: error });
    }
  } catch (error) {
    throw new Error('깊이 1에서 에러 발생', { cause: error });
  }
};

console.log(error.cause.cause); // Error at depth 3 (깊이 3에서 에러 발생)

 

Node.js (노드js)에서는 cause 속성이 있는 에러를 console (콘솔)에 특별하게 처리해주는데요.

관련된 모든 에러 스택을 한꺼번에 출력해줍니다.

const cause = new Error('HTTP (에이치티티피) 서버가 500 상태 코드로 응답했습니다.');
const symptom = new Error('메시지 전송 실패', { cause });

console.log(symptom);
// 출력 결과:
//   Error: 메시지 전송 실패
//       at REPL2:1:17
//       at Script.runInThisContext (node:vm:130:12)
//       ... 7 lines matching cause stack trace ... (cause 스택 트레이스와 일치하는 7줄)
//       at [_line] [as _line] (node:internal/readline/interface:886:18) {
//     [cause]: Error: HTTP (에이치티티피) 서버가 500 상태 코드로 응답했습니다.
//         at REPL1:1:15
//         at Script.runInThisContext (node:vm:130:12)
//         at REPLServer.defaultEval (node:repl:574:29)
//         at bound (node:domain:426:15)
//         at REPLServer.runBound [as eval] (node:domain:437:12)
//         at REPLServer.onLine (node:repl:902:10)
//         at REPLServer.emit (node:events:549:35)
//         at REPLServer.emit (node:domain:482:12)
//         at [_onLine] [as _onLine] (node:internal/readline/interface:425:12)
//         at [_line] [as _line] (node:internal/readline/interface:886:18)
  1. 결론

에러 맥락과 세부 정보를 바로 확인할 수 있다면 디버깅이 훨씬 쉬워지겠죠?

error.cause 기능을 사용해서 "catch (잡기) + rethrow with context (맥락을 담아 다시 던지기)" 패턴을 적용하면, 디버깅 효율을 크게 높일 수 있습니다.

try {
  doSth();
} catch (error) {
  throw new Error('doSth 관련 맥락 정보', { cause: error });
}

 

error.cause를 활용하면 에러 추적도 쉬워지고, 코드 가독성과 유지보수성도 높일 수 있습니다.

JavaScript (자바스크립트) 디버깅, 이제 error.cause로 더 스마트하게 해보시는 건 어떠세요?