How To Spy on Your Ruby Methods

Ruby has a built-in tracing system which you can access using the TracePoint class. Some of the things you can trace are method calls, new threads & exceptions.

Why would you want to use this?

Well, it could be useful if you want to trace the execution of a certain method. You will be able to see what other methods are being called & what are the return values.

Let’s see a few examples!

Tracing Method Calls

Most of the time you will want TracePoint to trace application code & not built-in methods (like puts, size, etc).

You can do this using the call event.

Example:

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 }

This prints the file path, the line number, the event name & the method name.

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

If you don’t specify any events Ruby will call your block for all of them, resulting in more output. So I would recommend that you focus on specific events to find what you want faster 🙂

Here’s is a table of TracePoint events:

Event name Description
call Application methods
c_call C-level methods (like puts)
return Method return (for tracing return values & call depth)
b_call Block call
b_return Block return
raise Exception raised
thread_begin New thread
thread_end Thread ending

TracePoint + Graphviz

Many methods will make more than just 3 methods calls, especially in framework code, so the output from Tracepoint can be hard to visualize.

So I made a gem that lets you create a visual call graph like this:

require 'visual_call_graph'

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

This generates a call_graph.png file with the results.

ruby call graph

Keep in mind that this is not static analysis, this will actually call the method!

Showing File Paths

Would you like to know where these methods are defined?

Don’t worry, I got you covered! I added an option you can enable to show the file path for each method call.

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

Which results in:

visual call graph

If you want to see some massive call graphs you just have to trace some Rails methods 😉

Return Values

In the introduction I mentioned that you can also get return values…

For this you will need to trace the return event and use the return_value method.

Example:

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

This will print:

Return value for the_method is AAAAAAAAAA.

Events First

Someone asked on reddit how it’s possible to avoid having the word “bar” printed when calling the foo method in the following code:

class Thing
  def foo
    puts "foo"
    bar
  end

  def bar
    puts "bar"
  end
end

# your code here

t = Thing.new
t.foo

There are many ways to achieve this, like prepending a module, redirecting $stdout or redefining the bar method.

If you are feeling creative, comment on this post with your own idea!

But I found one of the answers particularly interesting because it used the TracePoint class.

Here it is:

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

This code will call exit when the method bar is called, which prevents the string from being printed by ending the program.

Probably not something you want to use in real code, but it proves one thing about TracePoint: Events are triggered before they happen.

Something to keep in mind if you are going to build some sort of tool around this 🙂

Summary

In this post you learned about the TracePoint class, which allows you to trace a few events like methods calls or new threads. This can be useful as a debugging tool or for code exploration.

Remember to share this post so more people can enjoy it 🙂

7 thoughts on “How To Spy on Your Ruby Methods”

Comments are closed.