HT: Dmitri Skliarov for sharing this tip with me.

You can quickly see the call stack being invoked when you call a given Elixir function via a quick and simple call out to the Erlang DBG module. The following tip is a very simple example of how to use this; there’s quite a bit more that can be done with DBG but you need to look at the module docs to see them.

First a little code that we want to trace:

defmodule Trace do def add(n, m), do: n + m end

Next you will want to initialize the debugger:

:dbg.tracer() # => {:ok, #PID<0.66.0>}

NB: you are extremely unlikely to get the same PID I’ve shown here. Next we need to tell the debugger which item or items we want to trace:

:dbg.p(:all,:c) # => {:ok, [{:matched, :nonode@nohost, 45}]}

Next we tell it precisely what we want to track:

:dbg.tpl(Trace,:add,:x) # => {:ok, [{:matched, :nonode@nohost, 1}, {:saved, :x}]}

NB: the first parameter to tpl is actually an atom but since a Module name is automatically an atom it’s fine to pass the module name without a colon in front of it. Also note that you can pass the special atom :_ as the second parameter to trace all calls to all functions.

And finally we actually trace our code:

Trace.add(1,1) # => (<0.57.0>) call 'Elixir.Trace':add(1,1) # => (<0.57.0>) returned from 'Elixir.Trace':add/2 -> 2 # => 2

And for reference when we use :dbg.tpl(Trace,:_,:x):

Trace.add(1,1) (<0.57.0>) call 'Elixir.Trace':'__info__'(macros) (<0.57.0>) returned from 'Elixir.Trace':'__info__'/1 -> [] (<0.57.0>) call 'Elixir.Trace':add(1,1) (<0.57.0>) returned from 'Elixir.Trace':add/2 -> 2 2

And finally when you’re done tracing:

:dbg.stop_clear()

There’s a lot of power using this technique so I’d advise that you take some time to look at the DBG man page before you start using this extensively. Also, it’s my understanding that you should always be careful to use this technique only in development because if you trace the wrong thing you can cause deadlocks.