TracePoint

TracePoint (코드 추적 훅)

TracePointKernel#set_trace_func의 기능을 잘 구조화된 객체지향 API로 제공하는 클래스예요. 코드 실행 중 특정 지점(이벤트)에서 훅을 걸어 정보를 수집하고 싶을 때 써요.

예를 들어 예외가 발생하는 순간만 잡아내고 싶다면 이렇게 해요.

trace = TracePoint.new(:raise) do |tp|
  p [tp.lineno, tp.event, tp.raised_exception]
end
#=> #<TracePoint:disabled>

trace.enable  #=> false

0 / 0
#=> [5, :raise, #<ZeroDivisionError: divided by 0>]

출처: Ruby 4.0 API

본문

이벤트 (Events)

듣고 싶은 이벤트 타입을 지정하지 않으면 TracePoint가능한 모든 이벤트를 포함해요. 다만 현재 이벤트 집합에 의존하지 마세요. 이 목록은 바뀔 수 있기 때문이에요. 대신 쓰고 싶은 이벤트 타입을 명시하는 걸 권장해요.

추적 대상을 걸러내려면 events로 다음 중 원하는 만큼 넘기면 돼요.

  • :line - 새 줄에서 표현식/문장을 실행할 때.
  • :class - 클래스 또는 모듈 정의가 시작될 때.
  • :end - 클래스 또는 모듈 정의가 끝날 때.
  • :call - Ruby 메서드를 호출할 때.
  • :return - Ruby 메서드에서 반환할 때.
  • :c_call - C 언어 루틴을 호출할 때.
  • :c_return - C 언어 루틴에서 반환할 때.
  • :raise - 예외를 던질 때.
  • :rescue - 예외를 rescue할 때.
  • :b_call - 블록 진입 시 훅.
  • :b_return - 블록 종료 시 훅.
  • :a_call - 모든 호출(call, b_call, c_call) 시 훅.
  • :a_return - 모든 반환(return, b_return, c_return) 시 훅.
  • :thread_begin - 스레드 시작 시 훅.
  • :thread_end - 스레드 종료 시 훅.
  • :fiber_switch - fiber 전환 시 훅.
  • :script_compiled - 새 Ruby 코드가(eval, load, require로) 컴파일될 때.

클래스 메서드 (Public Class Methods)

allow_reentry { block }

일반적으로 TracePoint 콜백이 실행되는 동안에는 재진입(reentrance)으로 인한 혼란을 막기 위해 다른 등록된 콜백이 호출되지 않아요. 이 메서드는 주어진 블록 안에서 재진입을 허용해요. 무한 콜백 호출을 피하려고 조심히 써야 해요.

이미 재진입이 허용된 상태에서 호출하면 RuntimeError를 던져요.

# 재진입 없이
# ---------------
line_handler = TracePoint.new(:line) do |tp|
  next if tp.path != __FILE__ # 이 파일 안에서만 동작
  puts "Line handler"
  binding.eval("class C; end")
end.enable

class_handler = TracePoint.new(:class) do |tp|
  puts "Class handler"
end.enable

class B
end

# 이 스크립트는 "Class handler"를 딱 한 번만 출력해요.
# :line 핸들러 안에서는 다른 핸들러가 모두 무시되기 때문이에요.

# 재진입과 함께
# ------------
line_handler = TracePoint.new(:line) do |tp|
  next if tp.path != __FILE__ # 이 파일 안에서만 동작
  next if (__LINE__..__LINE__+3).cover?(tp.lineno) # 무한 호출 방지
  puts "Line handler"
  TracePoint.allow_reentry { binding.eval("class C; end") }
end.enable

class_handler = TracePoint.new(:class) do |tp|
  puts "Class handler"
end.enable

class B
end

# 이번에는 "Class handler"가 두 번 출력돼요.
# :line 핸들러 안의 allow_reentry 블록 안에서 다른 핸들러가 활성화되기 때문이에요.

예시는 이 메서드의 주된 효과를 보여줘요. 실제 활용은 디버깅 라이브러리가, trace point 처리에 들어간 동안 다른 라이브러리의 훅이 영향받지 않게 하고 싶을 때가 많아요. 무한 재귀를 조심해야 해요(예시에서도 :line 핸들러가 자기 자신에 의한 호출을 걸러내야 무한히 호출되지 않는 걸 볼 수 있어요).

new(*events) { |tp| block } → tp

기본적으로 활성화되지 않은 새 TracePoint 객체를 돌려줘요. 활성화하려면 TracePoint#enable을 쓰면 돼요.

trace = TracePoint.new(:call) do |tp|
  p [tp.lineno, tp.defined_class, tp.method_id, tp.event]
end
#=> #<TracePoint:disabled>

trace.enable  #=> false

puts "Hello, TracePoint!"
# ...
# [48, IRB::Notifier::AbstractNotifier, :printf, :call]
# ...

비활성화하려면 TracePoint#disable을 써요.

trace.disable

가능한 이벤트와 더 자세한 내용은 [이벤트(Events)]를 참고하세요.

블록이 반드시 주어져야 해요. 아니면 ArgumentError가 발생해요.

주어진 이벤트 필터에 해당 trace 메서드가 지원되지 않으면 RuntimeError가 발생해요.

TracePoint.trace(:line) do |tp|
  p tp.raised_exception
end
#=> RuntimeError: 'raised_exception' not supported by this event

trace 메서드를 블록 밖에서 호출하면 RuntimeError가 발생해요.

TracePoint.trace(:line) do |tp|
  $tp = tp
end
$tp.lineno #=> 블록 밖에서 접근 (RuntimeError)

다른 ractor, 스레드, fiber에서의 접근은 금지돼요. TracePoint는 ractor 단위로 활성화되므로 한 ractor에서 enable해도 다른 ractor에는 영향이 없어요.

stat → obj

TracePoint의 내부 정보를 돌려줘요. 반환 값의 내용은 구현 의존적이고 미래에 바뀔 수 있어요. 오직 TracePoint 자체를 디버깅하기 위한 메서드예요.

trace(*events) { |tp| block } → obj

TracePoint.new의 편의 메서드로, 추적을 자동으로 활성화해요.

trace = TracePoint.trace(:call) { |tp| [tp.lineno, tp.event] }
#=> #<TracePoint:enabled>

trace.enabled?  #=> true

인스턴스 메서드 (Public Instance Methods)

binding()

이벤트에서 생성된 binding 객체를 돌려줘요. :c_call:c_return 이벤트에서는 C 메서드 자체에 binding이 없으므로 nil을 돌려줘요.

callee_id()

호출되는 메서드의 호출 시 이름(callee name)을 돌려줘요.

defined_class()

호출되는 메서드가 정의된 클래스 또는 모듈을 돌려줘요.

class C; def foo; end; end
trace = TracePoint.new(:call) do |tp|
  p tp.defined_class #=> C
end.enable do
  C.new.foo
end

모듈에 정의된 메서드라면 그 모듈을 돌려줘요.

module M; def foo; end; end
class C; include M; end
trace = TracePoint.new(:call) do |tp|
  p tp.defined_class #=> M
end.enable do
  C.new.foo
end

주의: defined_class싱글턴 클래스를 돌려줘요. Kernel#set_trace_func의 6번째 블록 파라미터는 싱글턴 클래스가 붙은 원래 클래스를 넘겨요. 이것이 Kernel#set_trace_funcTracePoint의 차이점이에요.

class C; def self.foo; end; end
trace = TracePoint.new(:call) do |tp|
  p tp.defined_class #=> #<Class:C>
end.enable do
  C.foo
end

disable → true or false / disable { block } → obj

추적을 비활성화해요.

추적이 활성화돼 있었다면 true, 비활성화돼 있었다면 false를 돌려줘요.

trace.enabled?  #=> true
trace.disable   #=> true (이전 상태)
trace.enabled?  #=> false
trace.disable   #=> false

블록을 주면 그 블록의 스코프 안에서만 비활성화돼요.

trace.enabled?  #=> true

trace.disable do
  trace.enabled?
  # 이 블록 안에서만 비활성화
end

trace.enabled?  #=> true

주의: 블록 안에서는 이벤트 훅에 접근할 수 없어요.

trace.disable { p tp.lineno }
#=> RuntimeError: access from outside

enable(target: nil, target_line: nil, target_thread: nil) → true or false / enable(target: nil, target_line: nil, target_thread: :default) { block } → obj

추적을 활성화해요.

추적이 활성화돼 있었다면 true, 비활성화돼 있었다면 false를 돌려줘요.

trace.enabled?  #=> false
trace.enable    #=> false (이전 상태) / 추적 활성화됨
trace.enabled?  #=> true
trace.enable    #=> true (이전 상태) / 추적 여전히 활성화됨

블록을 주면 그 블록 실행 동안만 활성화돼요. targettarget_line이 모두 nil이면 블록이 주어졌을 때 target_thread는 기본적으로 현재 스레드가 돼요.

trace.enabled?  #=> false

trace.enable do
  trace.enabled?
  # 이 블록과 이 스레드에서만 활성화
end

trace.enabled?  #=> false

target, target_line, target_thread 파라미터는 추적 대상을 지정된 코드 객체로 한정하는 데 쓰여요. targetRubyVM::InstructionSequence.of가 instruction sequence를 돌려줄 코드 객체여야 해요.

t = TracePoint.new(:line) { |tp| p tp }

def m1
  p 1
end

def m2
  p 2
end

t.enable(target: method(:m1))

m1
# Prints #<TracePoint:line test.rb:4 in `m1'>
m2
# Prints nothing

주의: enable 블록 안에서는 이벤트 훅에 접근할 수 없어요.

trace.enable { p tp.lineno }
#=> RuntimeError: access from outside

enabled? → true or false

추적의 현재 상태를 돌려줘요.

eval_script()

:script_compiled 이벤트에서 eval 메서드로부터 컴파일된 소스 코드(String)를 돌려줘요. 파일에서 로드됐다면 nil을 돌려줘요.

event()

이벤트의 타입을 돌려줘요. 자세한 내용은 [이벤트(Events)]를 참고하세요.

inspect → string

사람이 읽을 수 있는 TracePoint 상태 문자열을 돌려줘요.

instruction_sequence()

:script_compiled 이벤트에서 RubyVM::InstructionSequence 인스턴스로 표현되는 컴파일된 instruction sequence를 돌려줘요. 이 메서드는 CRuby 전용이에요.

lineno()

이벤트의 줄 번호를 돌려줘요.

method_id()

호출되는 메서드의 정의 시 이름을 돌려줘요.

parameters()

현재 훅이 속한 메서드 또는 블록의 파라미터 정의를 돌려줘요. 형식은 Method#parameters와 같아요.

path()

실행 중인 파일의 경로를 돌려줘요.

raised_exception()

:raise 이벤트에서 던져진 예외 또는 :rescue 이벤트에서 rescue된 예외를 돌려줘요.

return_value()

:return, :c_return, :b_return 이벤트에서의 반환 값을 돌려줘요.

self()

이벤트 동안의 trace 객체를 돌려줘요. 아래 코드와 비슷하되, :c_call:c_return 이벤트에서 **올바른 객체(메서드 수신자)**를 돌려줘요.

trace.binding.eval('self')