DEV Community

Cover image for Elixir — quick reference for debugging techniques
Leandro Cesquini Pereira
Leandro Cesquini Pereira

Posted on • Originally published at leandrocp.com.br on

Elixir — quick reference for debugging techniques

Much has been said about Elixir debugging techniques, but in this post, I’d like to give a quick overview of all possible options to serve as a go-to reference when you need to debug Elixir code. Enough talking, let’s check each of them:

IO.inspect

The simplest technique:

my_list = [1, 2, 3]
IO.inspect(my_list)

# Outputs
[1, 2, 3]
Enter fullscreen mode Exit fullscreen mode

IO.inspect/2can also be used inside pipelines because it returns the item passed to be inspected. And the tip here is to use the option label:to output a string identifying each inspect:

[1, 2, 3]
|> IO.inspect(label: "before")
|> Enum.map(&(&1 * 2))
|> IO.inspect(label: "after")
|> Enum.sum

# Outputs
before: [1, 2, 3]
after: [2, 4, 6]
Enter fullscreen mode Exit fullscreen mode

IO.inspect with binding

binding/1 is very useful when you want to see all variable names and values of the current function:

def some_fun(a, b, c) do
  IO.inspect binding()
  ...
end

iex> some_fun(:foo, "bar", :baz)
[a: :foo, b: "bar", c: :baz]
Enter fullscreen mode Exit fullscreen mode

apex

Similar to IO.inspect/2, apex is a lib worth mentioning especially because of its adef macro:

# import apex adef macro
import Apex.AwesomeDef

# change def to adef
adef test(data, options \\ []) do
  data
end

# when you call your function, you'll receive detailed information about its execution
iex(1)> Apex.test "foo"
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
Function Elixir.Apex.test was called
defined in /Users/bjoernrochel/Coding/Laboratory/apex/lib/apex.ex:12
----------------------------------------------------------------------------------------------------
Parameters:
----------------------------------------------------------------------------------------------------
[
  [0] "foo"
  [1] []
]

----------------------------------------------------------------------------------------------------
Result:
----------------------------------------------------------------------------------------------------
"foo"
Enter fullscreen mode Exit fullscreen mode

IEx.pry

But it can be very tedious and be limiting to debug with just data inspection likeIO.inspect or apex, that’s when IEx.pry/0 comes at hand because it allows you to pry into the current code. Put the following code at the line you want to pry:

def some_fun(a, b, c) do
  require IEx; IEx.pry
  ...
end
Enter fullscreen mode Exit fullscreen mode

And now execute your code inside an IEx session: iex -S mix or iex -S mix phx.server if you are using Phoenix Framework. Tip: if you are running tasks like database seeds, you can pry into that code by running iex -S mix run priv/repo/seeds.exs or any other script.

Once the code execution gets to the point of IEx.pry , an interactive shell opens and allow you to interact with the current code.

Go to Debugging Phoenix with IEx.pry if you want more tips about debugging Phoenix with IEx.pry.

:debugger

Pretty much the same as IEx.pry which stops the execution at the break point and allows you to inspect the current code, but :debugger gives you a nice visual interface, like the ones in IDEs:

gif from http://blog.plataformatec.com.br/2016/04/debugging-techniques-in-elixir-lang/)

gif from http://blog.plataformatec.com.br/2016/04/debugging-techniques-in-elixir-lang

To open this debugger, you need to start it and set a break point:

# given that you have this module
defmodule MyApp.Example do
  def sum(x, y) do
    x + y
  end
end

# start a new iex session
$ iex -S mix

# then start :debugger
iex> :debugger.start()

# prepare your module for debugging
iex> :int.ni(MyApp.Example)

# set a break point at the line you want to capture
iex> :int.break(MyApp.Example, 3)

# and finally call your function
iex> MyApp.Example.sum(1,2)
Enter fullscreen mode Exit fullscreen mode

:sys.get_state and :sys.get_status

This one works only for processes. As the name suggests, :sys.get_state/1gets the current state of a process, and not only from a GenServer but also from any kind of process:

iex(1)> defmodule Example, do: use GenServer
iex(2)> {:ok, pid} = GenServer.start_link(Example, %{ping: "pong"})
iex(3)> :sys.get_state(pid)
%{ping: "pong"}
Enter fullscreen mode Exit fullscreen mode

Snippet extracted from Looking at the state of processes in Elixir.

If you need more data than just the current state, just call :sys.get_status/1, which will return whole process information:

{:status, #PID<0.134.0>, {:module, :gen_server},
  [
    [
      "$initial_call": {Sequence.Server, :init, 1},
      "$ancestors": [#PID<0.118.0>, #PID<0.57.0>]
    ],
    :running,
    #PID<0.118.0>,
    [statistics: {{{2017, 12, 23}, {14, 11, 13}}, {:reductions, 14}, 3, 0},
    [
      header: 'Status for generic server <0.134.0>',
      data: [
        {'Status', :running},
        {'Parent', #PID<0.118.0>},
        {'Logged events', []}
    ],
    data: [{'State', 103}]
  ]

# Excerpt From: Dave Thomas. “Programming Elixir ≥ 1.6"
Enter fullscreen mode Exit fullscreen mode

Bonus: you can replace a process state at runtime using :sys.replace_state/2, which can be very handy to test some specific situation.

Process.info

You can also use Process.info/1 to get information about a specific process:

iex> defmodule Example, do: use GenServer
iex> {:ok, pid} = GenServer.start_link(Example, %{ping: "pong"})
iex> Process.info pid
[
  current_function: {:gen_server, :loop, 7},
  initial_call: {:proc_lib, :init_p, 5},
  status: :waiting,
  message_queue_len: 0,
  messages: [],
  links: [#PID<0.610.0>],
  dictionary: [
    "$initial_call": {Example, :init, 1},
    "$ancestors": [#PID<0.610.0>, #PID<0.70.0>]
  ],
  trap_exit: false,
  error_handler: :error_handler,
  priority: :normal,
  group_leader: #PID<0.60.0>,
  total_heap_size: 233,
  heap_size: 233,
  stack_size: 10,
  reductions: 27,
  garbage_collection: [
    max_heap_size: %{error_logger: true, kill: true, size: 0},
    min_bin_vheap_size: 46422,
    min_heap_size: 233,
    fullsweep_after: 65535,
    minor_gcs: 0
  ],
  suspending: []
]
Enter fullscreen mode Exit fullscreen mode

:sys.trace

Still talking about process debug, another resource we have is :sys.trace/2to trace process calls, which will display each call and state change of the calling process:

iex> :sys.trace pid, true
:ok
iex> GenServer.call(pid, :next_number)
*DBG* <0.69.0> got call next_number from <0.25.0>
*DBG* <0.69.0> sent 105 to <0.25.0>, new state 106
105

# Excerpt From: Dave Thomas. “Programming Elixir ≥ 1.6” iBooks.
Enter fullscreen mode Exit fullscreen mode

Distillery commands

After you deploy your application as a package release, it may be very helpful to get information about it, specially if something is not working as expected. If you have deployed using Distillery, you’ll soon discover that mix does not work the same way as in your local machine, and that’s expected. But you can execute some commands on that release by calling bin/<app_name> <command>, the two more useful commands are: remote_console to start an IEx session on the running release and help to give you a list of all commands.

:observer

Although it’s not exactly a debug tool, it’s worth mentioning that :observer helps you get an overview of the running system. I’ll not dig into details because there are a lot of articles that already do that, so I’ll just leave here the function call to start the observer:

iex> :observer.start
Enter fullscreen mode Exit fullscreen mode

Notes and sources

If you have more tips and techniques, please leave a comment so I can update this post and help spread more Elixir knowledge. :)

Some code snippets were extracted from the great official documentation or from the official getting started tutorial.

The book Programming Elixir was also used as source.

The Today I Learned from Hashrocket is also a great source of tips.

Top comments (3)

Collapse
 
ryanwilldev profile image
Ryan Will

Great post! I had no idea about the visual debugger. Debugging with IEx.pry is doable but can be time consuming having to keep adding pry statements and no way, at least that I know of, to step through your code execution.

Collapse
 
leandrocp profile image
Leandro Cesquini Pereira • Edited

Hey Ryan, you're right, pry is limited regarding code navigation. The best you can do is to use break!/4 along with continue/0, or use the visual debugger.

Collapse
 
edisonywh profile image
Edison Yap

Oh that's cool! Didn't know about a handful of them, thanks for sharing!