TracePoint
TracePoint (코드 추적 훅)
TracePoint는 Kernel#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_func과 TracePoint의 차이점이에요.
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 (이전 상태) / 추적 여전히 활성화됨
블록을 주면 그 블록 실행 동안만 활성화돼요. target과 target_line이 모두 nil이면 블록이 주어졌을 때 target_thread는 기본적으로 현재 스레드가 돼요.
trace.enabled? #=> false
trace.enable do
trace.enabled?
# 이 블록과 이 스레드에서만 활성화
end
trace.enabled? #=> false
target, target_line, target_thread 파라미터는 추적 대상을 지정된 코드 객체로 한정하는 데 쓰여요. target은 RubyVM::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')