자바스크립트에서 직접 throw한 예외의 스택 트레이스를 얻는 방법

1. 문제 정의

자바스크립트에서 예외를 **직접 throw**할 때, 브라우저(Firebug 등) 콘솔에는 단순한 에러 메시지만 출력되고 스택 트레이스(함수가 어디서 어떻게 호출됐는지의 경로)가 보이지 않는 경우가 있습니다.

에러 원문은 다음과 같습니다.

If I throw a JavaScript exception myself (eg, throw "AArrggg"), how can I get the stack trace (in Firebug or otherwise)? Right now I just get the message.

즉, 아래처럼 재귀적으로 호출하는 함수에서 예외가 발생했을 때, 어떤 경로를 거쳐 이 예외가 던져졌는지를 알고 싶은 상황입니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
function foo() {
    bar(2);
}
function bar(n) {
    if (n < 2)
        throw "Oh no! 'n' is too small!"
    bar(n-1);
}

foo();

이 코드에서 bar(1)이 호출되는 순간 예외가 던져지지만, 브라우저는 던진 문자열("Oh no! 'n' is too small!")만 보여줄 뿐 foo → bar → bar로 이어진 호출 체인을 함께 보여주지 않습니다.

증상설명
출력 내용에러 메시지 텍스트만 표시
누락되는 정보호출한 함수 이름, 소스 URL, 호출 체인 (스택 트레이스)
발생 조건throw로 직접 예외를 던질 때

2. 원인 탐구

예외를 throw할 때 스택 트레이스가 안 보이는 이유는, 직접 던진 예외가 자바스크립트 엔진이 생성한 Error 객체가 아니기 때문입니다.

  • 엔진이 내부적으로 발생시킨 예외(예: TypeError, ReferenceError)는 언어 런타임이 예외를 만들 때 그 시점의 호출 스택을 함께 기록합니다.
  • 반면 throw "문자열"처럼 문자열을 던지면 해당 문자열은 단순한 값일 뿐, 스택 정보를 담고 있는 Error 객체가 아닙니다.
  • 따라서 브라우저는 화면에 표시할 메시지(문자열)는 확인할 수 있지만, 그 문자열에 스택 정보가 없어서 호출 경로를 알려줄 수 없습니다.

여기서 핵심은 “던져진 값에 스택 정보가 있느냐 없느냐"입니다.

3. 근본 원인 분석

근본 원인은 다음과 같이 정리할 수 있습니다.

직접 던진 예외 값에는 스택 트레이스 정보가 기본적으로 포함되어 있지 않다.

  • throw에 전달된 값(문자열, 숫자 등)은 그 자체로는 호출 스택을 알지 못합니다.
  • 스택 트레이스는 Error 객체가 생성될 때 캡처됩니다. 따라서 Error 객체(new Error())를 만들거나, 이미 만들어진 Errorstack 속성을 읽어야 호출 체인을 얻을 수 있습니다.
  • 또한 콘솔에 출력하려면 console.trace()처럼 호출 시점의 스택을 명시적으로 출력하는 메서드를 호출해야 합니다.

정리하면, “직접 던진 값에는 스택이 없다 → 스택을 얻으려면 스택을 제공하는 객체/메서드를 사용해야 한다"는 것이 곧 근본 원인입니다.

비교 대상스택 정보 포함 여부예시
throw "문자열"❌ 없음throw "AArrggg"
new Error().stack✅ 있음var err = new Error(); return err.stack;
console.trace()✅ 있음 (콘솔 출력)console.trace();

4. 코드 해결책

가장 간단한 두 가지 방법을 소개합니다.

방법 1: console.trace() 호출 (권장, 최신 브라우저)

모던 브라우저에서는 예외를 던지기 직전에 console.trace()를 호출하면 현재 호출 스택이 콘솔에 출력됩니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
function foo() {
    bar(2);
}
function bar(n) {
    if (n < 2) {
        console.trace();          // 여기서 호출 체인 출력
        throw "Oh no! 'n' is too small!"
    }
    bar(n-1);
}

foo();

실행 결과 콘솔에 foobarbar 순서의 호출 체인(함수 이름과 라인, URL)이 출력됩니다.

방법 2: new Error().stack 속성 사용

Error 객체는 생성되는 순간 stack 속성에 호출 스택을 기록합니다. 이 속성을 읽으면 함수 이름, URL, 호출 체인을 문자열로 얻을 수 있습니다.

1
2
3
4
function stackTrace() {
    var err = new Error();
    return err.stack;
}

이 함수를 호출하면 다음과 같은 형태의 출력이 생성됩니다.

1
2
3
4
5
6
7
DBX.Utils.stackTrace@http://localhost:49573/assets/js/scripts.js:44
DBX.Console.Debug@http://localhost:49573/assets/js/scripts.js:9
.success@http://localhost:49573/:462
x.Callbacks/c@http://localhost:49573/assets/js/jquery-1.10.2.min.js:4
x.Callbacks/p.fireWith@http://localhost:49573/assets/js/jquery-1.10.2.min.js:4
k@http://localhost:49573/assets/js/jquery-1.10.2.min.js:6
.send/r@http://localhost:49573/assets/js/jquery-1.10.2.min.js:6

각 줄은 호출한 함수의 이름소스 URL 및 라인 번호, 그리고 그 함수를 호출한 상위 함수로 이어지는 체인을 보여줍니다.

방법 비교

방법장점사용 예시
console.trace()한 줄로 즉시 콘솔 출력, 최신 브라우저에서 안정적디버깅 중 스택이 궁금할 때
new Error().stack문자열로 반환해 로그/전송 등에 활용 가능스택을 변수에 저장하거나 서버로 보낼 때

예외 처리(handling)와 함께 사용할 때 참고

예외를 catch한 뒤에도 Error 객체의 stack을 출력할 수 있습니다.

1
2
3
4
5
try {
    foo();
} catch (e) {
    console.error(e.stack);   // Error 객체면 stack 출력 가능
}

단, throw "문자열"처럼 문자열을 던진 경우에는 e.stack이 정의되어 있지 않을 수 있으므로, 스택을 활용하려면 throw new Error("메시지") 형태로 예외를 던지는 것이 좋습니다.

5. 향후 예방 조치

직접 예외를 던질 때 스택 트레이스를 잃지 않기 위한 예방 조치입니다.

  1. 문자열 대신 Error 객체를 throw하자.

    1
    2
    3
    4
    
    // 나쁜 예
    throw "Oh no! 'n' is too small!"
    // 좋은 예
    throw new Error("Oh no! 'n' is too small!")
    

    Error 객체는 stack 속성을 자동으로 갖기 때문에, 나중에 디버깅할 때 호출 경로를 복구할 수 있습니다.

  2. console.trace()를 디버깅 지점에 배치하자. 예외 직전 또는 의심스러운 분기점에서 한 줄만 추가하면 호출 체인을 즉시 확인할 수 있습니다.

  3. catch 블록에서 스택을 로그로 남기자.

    1
    2
    3
    4
    5
    
    try {
        // ...
    } catch (e) {
        console.error(e && e.stack ? e.stack : e);
    }
    

    e.stack이 있는지 먼저 확인하고, 없으면 원본 값을 출력하도록 방어적으로 작성합니다.

  4. arguments.callee.caller에 의존하지 말자. 과거에는 caller 속성으로 스택을 재구성하는 방식이 쓰였지만, 엄격 모드(strict mode)를 비롯한 현대 환경에서는 제한적입니다. console.trace() 또는 Error.stack이 더 안전하고 표준적인 방법입니다.

이렇게 하면 직접 던진 예외에서도 스택 트레이스를 놓치지 않고 효과적으로 디버깅할 수 있습니다.


출처