Computer >> 컴퓨터 >  >> 프로그래밍 >> Ruby

Ruby TracePoint로 메서드 호출 추적하는 방법

Ruby에는 TracePoint 클래스를 통해 접근할 수 있는 내장 트레이싱(추적) 시스템이 있습니다. 이를 활용하면 메서드 호출, 새 스레드 생성, 예외 발생 등 다양한 이벤트를 추적할 수 있습니다.

이 기능은 언제 유용할까요?

특정 메서드의 실행 과정을 추적하고 싶을 때 특히 유용합니다. 어떤 메서드들이 호출되는지, 반환값은 무엇인지 한눈에 파악할 수 있기 때문입니다.

그럼 몇 가지 예제를 살펴보겠습니다!

메서드 호출 추적하기

대부분의 경우 TracePoint로 애플리케이션 코드를 추적하고 싶지, putssize 같은 내장 메서드까지 추적하고 싶지는 않을 겁니다.

이럴 때 call 이벤트를 사용하면 됩니다.

예제:

def the_method; other_method; end
 def other_method; end

 def start_trace
   trace =
   TracePoint.new(:call) { |tp| p [tp.path, tp.lineno, tp.event, tp.method_id] }

   trace.enable
   yield
   trace.disable
 end

 start_trace { the_method }

이 코드는 파일 경로, 라인 번호, 이벤트 이름, 메서드 이름을 출력합니다.

["test.rb", 1, :call, :the_method]
 ["test.rb", 2, :call, :other_method]

이벤트를 지정하지 않으면 Ruby가 모든 이벤트에 대해 블록을 호출하기 때문에 출력량이 훨씬 많아집니다. 따라서 원하는 정보를 더 빠르게 찾으려면 특정 이벤트에 집중하는 것을 추천합니다 🙂

아래는 TracePoint에서 사용할 수 있는 이벤트 목록입니다:

이벤트 이름 설명
call 애플리케이션 메서드 호출
c_call C 레벨 메서드 호출 (예: puts)
return 메서드 반환 (반환값 및 호출 깊이 추적에 활용)
b_call 블록 호출
b_return 블록 반환
raise 예외 발생
thread_begin 새 스레드 시작
thread_end 스레드 종료

TracePoint와 Graphviz로 시각화하기

특히 프레임워크 코드에서는 하나의 메서드가 세 개 이상의 메서드를 호출하는 경우가 많아, TracePoint의 텍스트 출력만으로는 결과를 파악하기 어려울 수 있습니다.

그래서 저는 시각적인 호출 그래프를 만들어 주는 젬(gem)을 만들었습니다:

require 'visual_call_graph'

 VisualCallGraph.trace { "Your method call here..." }

이 코드를 실행하면 결과가 담긴 call_graph.png 파일이 생성됩니다.

Ruby TracePoint로 메서드 호출 추적하는 방법

주의할 점은 이것이 정적 분석(static analysis)이 아니라는 것입니다. 실제로 해당 메서드가 호출됩니다!

파일 경로 표시하기

추적된 메서드들이 어디에 정의되어 있는지 알고 싶으신가요?

걱정 마세요! 각 메서드 호출의 파일 경로를 함께 보여주는 옵션을 추가해 두었습니다.

VisualCallGraph.trace(show_path: true) { Foo.aaa }

실행 결과는 다음과 같습니다:

Ruby TracePoint로 메서드 호출 추적하는 방법

거대한 호출 그래프를 보고 싶다면 Rails의 메서드를 한번 추적해 보세요 😉

반환값 추적하기

앞서 반환값도 확인할 수 있다고 언급했었죠?

이를 위해서는 return 이벤트를 추적하고 return_value 메서드를 사용하면 됩니다.

예제:

def the_method; "A" * 10; end

 trace = TracePoint.new(:return) { |tp| puts "Return value for #{tp.method_id} is #{tp.return_value}." }

 trace.enable
 the_method
 trace.disable

출력 결과:

Return value for the_method is AAAAAAAAAA.

이벤트는 실제 동작보다 먼저 발생한다

레딧(Reddit)에서 누군가 흥미로운 질문을 올렸습니다. 다음 코드에서 foo 메서드를 호출할 때 "bar"라는 문자열이 출력되지 않도록 하려면 어떻게 해야 할까요?

class Thing
   def foo
     puts "foo"
     bar
   end

   def bar
     puts "bar"
   end
 end

 # your code here

 t = Thing.new
 t.foo

이를 구현하는 방법은 여러 가지가 있습니다. 모듈을 prepend하거나, $stdout을 리다이렉트하거나, bar 메서드를 재정의하는 방식 등이 있죠.

창의적인 아이디어가 있다면 이 글에 댓글로 남겨주세요!

그런데 저는 그중 TracePoint 클래스를 활용한 답변이 특히 흥미로웠습니다.

바로 이 코드입니다:

TracePoint.trace(:call) { |tp| exit if tp.method_id == :bar }

이 코드는 bar 메서드가 호출되는 순간 exit을 실행해 프로그램을 종료시키므로, 문자열이 출력되기 전에 차단됩니다.

실제 프로덕션 코드에서 쓰고 싶은 방법은 아니지만, TracePoint에 대한 중요한 사실 하나를 보여줍니다. 바로 이벤트는 실제 동작이 일어나기 전에 트리거된다는 것입니다.

이런 기능을 활용해 도구를 만들 계획이라면 꼭 기억해 두세요 🙂

마무리

이번 글에서는 메서드 호출, 새 스레드 생성 같은 이벤트를 추적할 수 있는 TracePoint 클래스에 대해 알아봤습니다. 디버깅 도구로 활용하거나, 익숙하지 않은 코드베이스를 탐색할 때 매우 유용하게 사용할 수 있습니다.

더 많은 사람들이 이 글을 볼 수 있도록 공유 부탁드립니다 🙂