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

Tracing in Elixir/Erlang using dbg trips and tricks.
Tips and tricks for tracing in elixir/erlang using dbg module and some helper functions.

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:

Elixir compile-time code injection / AOP
I’ve previously used AOP-style code to separate Logic from Logging, and been very pleased with the results. I recognize that opinions on AOP vary, but I’d like to figure out a solution in Elixir, ...