Tracing in Elixir/Erlang using :erlang.trace and GenServer
Recently, I wanted to trace a running Elixir system and see the messages that a function received. I was looking to inspect the params that it received and even manipulate the params to fiddle with some edge cases in the system.
I knew that I could use dbg
to start a trace on the function and play with the arguments and get more info on the results.
I wrote about some tips on using dbg
here before
After a bit of googling, I came across this amazing answer in StackOverflow by Saša Jurić, where he provided an example where he wrapped the :erlang.trace
around a GenServer which can be hooked to the module.
Copying the code that he shared:
defmodule Tracer do
use GenServer
def start(modules), do: GenServer.start(__MODULE__, modules)
def init(modules) do
:erlang.trace(:all, true, [:call])
for module <- modules do
:erlang.trace_pattern({module, :_, :_}, [{:_, [], [{:return_trace}]}])
end
{:ok, nil}
end
def handle_info({:trace, _, :call, {mod, fun, args}}, state) do
IO.puts "called #{mod}.#{fun}(#{Enum.join(Enum.map(args, &inspect/1), ",")})"
{:noreply, state}
end
def handle_info({:trace, _, :return_from, {mod, fun, arity}, res}, state) do
IO.puts "#{mod}.#{fun}/#{arity} returned #{res}"
{:noreply, state}
end
def handle_info(_, state), do: {:noreply, state}
end
And then you can trace any module like:
Tracer.start([YourModule])
Pretty neat stuff.!
Just reminds me again how powerful tracing in Elixir/Erlang is.
Reference: