A class that provides the functionality of Kernel#set_trace_func in a nice Object-Oriented API.
Establishes proc as the handler for tracing, or disables tracing if the parameter is nil.
proc takes up to six parameters:
- an event name
- a filename
- a line number
- an object id
- a binding
- the name of a class
c-call call a C-language routine
c-return return from a C-language routine
call call a Ruby method
class start a class or module definition
end finish a class or module definition
line execute code on a new line
raise raise an exception
return return from a Ruby method
https://aphyr.com/posts/173-monkeypatching-is-for-wimps-use-set-trace-func
class Fixnum
def add(other)
self + other
end
end
set_trace_func proc { |event, file, line, id, binding, classname|
if classname == Fixnum && id == :add && event == 'call'
# We can, of course, find the receiver of the current method
me = binding.eval("self")
# And the binding gives us access to all variables declared
# in that method's scope. At call time only the method arguments will be
# defined.
args = binding.eval("local_variables")
.each_with_object({}) do |var_name, vars|
value = binding.eval var_name
vars[var_name] = value unless value.nil?
end
# We can also *change* those arguments.
args.each do |var_name, value|
if value.is_a?(Numeric)
binding.eval "#{var_name} = #{value + 1}"
end
end
end
}
puts 1.add 1 # => 3
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>]
TracePoint#event
- Type of eventTracePoint#lineno
- Line number the eventTracePoint#method_id
- Return the name at the definition of the method being called. (basically method_name)TracePoint#binding
-Return the generated binding object from eventTracePoint#defined_class
- Return class or module of the method being called.TracePoint#enable/disable
- Enable or disable the traceTracePoint#parameters
- Return the parameters of the method or block that the current hook belongs to.TracePoint#path
- Path of the file being runTracePoint#self
- Return the trace object during event
class Integer
def add(other) self + other end
end
add_trace = TracePoint.new(:call) do |trace|
if trace.defined_class == Integer && trace.method_id == :add
args = trace
.parameters
.map(&:last)
.to_h { |variable_name| [variable_name, trace.binding.eval(variable_name.to_s)] }
args.each do |variable_name, value|
if value.is_a?(Numeric)
trace.binding.eval "#{variable_name} = #{value + 1}"
end
end
end
end
- Fixnum was deprecated in Ruby 2.5
trace.binding.eval
isn't fond of symbols
MyTracePoint
def on_method(method_name, &fn)
TracePoint.new(:call, :c_call) do |trace|
begin
next unless trace.method_id == method_name.to_sym
yield(trace)
rescue RuntimeError => e
p e
end
end
end
def extract_args(trace)
trace.binding.eval('local_variables').to_h do |name|
[name, trace.binding.eval(name.to_s)]
end
end
def method_with_args_like(method_name, arg_matchers, &fn)
on_method(method_name) do |trace|
arg_hash = extract_args(trace)
next unless Qo[**arg_matchers].match?(arg_hash)
yield(trace)
end
end
def method_matches(method_name, qo_matcher, &fn)
on_method(method_name) do |trace|
arg_hash = extract_args(trace)
next unless qo_matcher.match?(arg_hash)
yield(trace)
end
end
def on_method_return(method_name, &fn)
TracePoint.new(:c_return, :return) do |trace|
begin
next unless trace.method_id == method_name.to_sym
yield(trace)
rescue RuntimeError => e
p e
end
end
end
def method_return_matches(method_name, expected_match, &fn)
on_method_return(method_name) do |trace|
next unless expected_match === trace.return_value
yield(trace)
end
end
def on_method_raise(method_name, &fn)
TracePoint.new(:raise) do |trace|
begin
next unless trace.method_id == method_name.to_sym
yield(trace)
rescue RuntimeError => e
p e
end
end
end
def method_raised_with(method_name, qo_matcher, &fn)
on_method_raise(method_name) do |trace|
arg_hash = extract_args(trace).tap { |v| p v }
next unless qo_matcher.match?(arg_hash)
yield(trace)
end
end
https://bugs.ruby-lang.org/issues/9358
In TracePoint class, if a particular introspection method is not supported then 'not supported by this event (RuntimeError)' is thrown.
https://ruby-doc.org/core-2.6/TracePoint.html#method-c-new
A block must be given, otherwise an ArgumentError is raised.
If the trace method isn't included in the given events filter, a RuntimeError is raised.