애플리케이션에서 오류가 발생하면 Ruby는 예외(exception)를 발생시키고 스택 트레이스(stack trace)를 로그에 출력합니다. 이 글에서는 스택 트레이스를 읽는 방법과, 이를 활용해 애플리케이션 내에서 예외의 발생 지점을 찾아내는 방법을 살펴보겠습니다.
호출 스택(Call Stack)
메서드를 호출할 때마다 Ruby는 호출 스택(call stack, '런타임 스택'이라고도 하며 흔히 그냥 '스택'이라 부릅니다)에 스택 프레임(stack frame)을 쌓습니다. 스택 프레임은 메서드의 인자, 내부 변수를 위한 공간, 그리고 호출자의 복귀 주소(return address)를 담고 있는 메모리 할당 영역입니다.
# divide.rb
def divide(a, b)
"Dividing #{a} by #{b} gives #{a / b}."
end
puts divide(8, 4)한 메서드(divide)가 다른 메서드(Fixnum#/, 줄여서 /)를 호출하면, 먼저 실행이 끝나야 하기 때문에 후자가 스택의 맨 위에 쌓입니다. 이 예제에서 divide(8, 4)를 호출하면 Ruby는 다음 작업을 순서대로 수행합니다.
8./(4)호출- 나눗셈 결과(
2)를 받아 문자열로 조합 - 완성된 문자열(
"Dividing 8 by 4 gives 2")을 받아puts로 콘솔에 출력
스택 트레이스(Stack Trace)
스택 트레이스(Ruby에서는 보통 '백트레이스(backtrace)'라고 부르며, '스택 백트레이스', '스택 트레이스백'이라고도 합니다)는 프로그램이 실행되는 특정 시점의 스택 상태를 사람이 읽기 쉬운 형태로 표현한 것입니다. / 메서드 호출에 대한 스택 트레이스는 아래와 같으며, 2번째 줄의 divide 메서드에서 호출되었고, 이 divide 메서드는 다시 5번째 줄의 <main> 메서드에서 호출되었음을 보여줍니다.
divide.rb:2:in `/'
divide.rb:2:in `divide'
divide.rb:5:in `<main>'
위 예제에서 divide 메서드의 인자 중 하나로 0을 넣어 호출하면 ZeroDivisionError 예외가 발생합니다.
# divide_by_zero.rb
def divide(a, b)
"Dividing #{a} by #{b} gives #{a / b}."
end
puts divide(8, 0)이런 일이 발생하면 Ruby는 예외와 함께 스택 트레이스를 콘솔에 출력합니다. 스택 트레이스는 각 메서드가 코드상 어디에 위치하는지 사람이 읽기 쉬운 형식으로 보여주어, 예외가 어디에서 비롯되었는지 정확하게 짚어낼 수 있도록 도와줍니다.
$ ruby divide_by_zero.rb
divide_by_zero.rb:2:in `/': divided by 0 (ZeroDivisionError)
from divide_by_zero.rb:2:in `divide'
from divide_by_zero.rb:5:in `<main>'
위 스택 트레이스의 첫 번째 줄에서 "divided by 0"이라는 메시지와 함께
ZeroDivisionError가 발생했음을 알 수 있습니다. 예외 자체 외에도divide_by_zero.rb:2:in `/'부분을 통해, 오류가 예제 파일의 두 번째 줄에서 이름이/인 메서드(첫 번째 인자가 Fixnum8이므로 곧Fixnum#/)로부터 발생했다는 사실을 확인할 수 있습니다.스택 트레이스의 두 번째 줄은
/메서드가 어디에서 호출되었는지를 보여줍니다. 이 경우에는 2번째 줄의divide메서드입니다.마지막 줄은
divide가<main>에서 호출되었음을 나타냅니다.<main>은 Ruby 애플리케이션의 최초 실행 컨텍스트를 가리키며, 일반적으로 어떤 '실질적인' 메서드의 바깥에서 호출되었다는 의미입니다.
참고: Ruby 2.5부터는 로거가 터미널 창에 출력이 들어가도록 스택 트레이스를 역순으로 표시합니다. 마지막 줄에 예외가 나타나고, 그 앞 줄에는 예외가 발생한 위치가 표시됩니다. 그 위의 줄들은 스택을 거슬러 올라가는 경로를 나타냅니다.
스택 트레이스 제대로 이해하기
스택 트레이스는 예외가 발생할 때마다 호출 스택의 현재 상태를 덤프해서 보여주므로, 문제가 어디에서 잘못되었는지 파악하는 데 큰 도움이 됩니다.
다만 스택 트레이스의 첫 번째 줄은 예외가 발생한 줄을 보여줄 뿐, 항상 오류의 근본 원인을 가리키지는 않습니다. 위 예제에서 프로그램 자체는 정상적으로 실행되었지만, divide 메서드에 전달된 데이터를 처리하지 못한 것이죠. 스택 트레이스를 따라 거슬러 올라가면 해당 메서드가 호출된 지점, 즉 진짜 문제의 원인에 도달할 수 있습니다.
이 글이 도움이 되었는지, 궁금한 점이 무엇인지, 그리고 다음에 어떤 주제를 다뤘으면 좋을지 @AppSignal을 통해 언제든 알려주세요.