Three Ways to Debug Code in Elixir
Tags: elixir
Published: 2021-12-07 13:11:29

Elixir provides a very powerful suite of tools that devs can use to observe the behavior of their code and debug errors.

There are several different strategies you can use to debug code in Elixir.

While it is hard to produce a comprehensive list of all possible debugging methods, we will cover some of the most common methods in today's post.

## 1. The IO Module: `puts/2` and `inspect/2`

Use the [`IO` module](https://hexdocs.pm/elixir/1.12/IO.html) for a quick and easy way to get some basic visibility into your code when debugging.

You can print out log statements that:

- tell you where you are in executing code
- inspect structs and other entities
- display the function's arguments

`puts/2` and `inspect/2` are the most interesting to use when debugging. With these, it's easy to sprinkle a few good output messages throughout your code and then visualize what's happening.

`puts/2` just prints out a string to the intended device (or `:stdio` if you don't provide anything).

`inspect/2` does something similar but writes out formatted output (e.g., pretty-printing maps, structs, and arrays).
There are a couple of options to select the `width` or a `label` for the message:

```elixir
> IO.inspect(%{foo: :bar}, label: "some map")
# some map: %{foo: :bar}
```

The great thing about `IO.inspect/2` is that it returns the input, so it is easy to tap into long pipes:

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

In addition to this, if you need to create strings with embedded maps for the log messages, it is also possible to use `Kernel.inspect/2` inside puts strings — like this:

```elixir
> IO.puts("some map: #{inspect(%{foo: :bar})}")
# some map: %{foo: :bar}
```

The [`inspect` method has a lot more options](https://hexdocs.pm/elixir/1.12/Inspect.Opts.html) to customize your output.

Finally, if you need to access a function's arguments quickly, it is possible to use `binding/1`.

```elixir
defmodule Greeter do
  def greet(name \\ "John Doe") do
    IO.inspect(binding())
  end
end

Greeter.greet() # Prints [name: "John Doe"]
Greeter.greet("Jane Doe") # Prints [name: "Jane Doe"]
```

## 2. IEx for Advanced Debugging Control

If you need advanced control for debugging, the next tool you'll find useful is the interactive shell IEx. IEx lets you inspect and visualize the current state of your code, manually execute code, and examine the results.

Just pop in `require IEx; IEx.pry` anywhere in your code and then run it with `iex`. So, if you are running a:

* Standalone elixir file with `elixir fib.exs`, use `iex -r fib.exs` instead.
* Mix command like `mix run fib.exs` or `mix phx.server`, use `iex -S mix run fib.exs` or `iex -S mix phx.server`.

Let's take a look at some of the things we can do with IEx.

### Using Pry with IEx

Let's see how debugging works with pry by looking at a [buggy Fibonacci number generator](https://gist.github.com/pulkit110/cdf5341405d36a945faf6882b187c96d). The code does not produce the expected result. Let's put a `require IEx; IEx.pry` on line 9 (just after `fib2 = fib`) and run it with `iex -r fib.exs`:

```elixir
Request to pry #PID<0.104.0> at Fib.number/1 (fib.exs:9)

    7:       fib1 = fib2
    8:       fib2 = fib
    9:       require IEx; IEx.pry
   10:     end)
   11:     fib2

Allow? [Yn] y
Interactive Elixir (1.12.0) - press Ctrl+C to exit (type h() ENTER for help)
pry(1)> binding()
[_i: 2, fib: 1, fib1: 1, fib2: 1, n: 5]
```

We can see that it stops at the `IEx.pry()` call. Then, we can inspect the values of the variables (or just use `binding` to output all context).

For the first iteration, everything looks good, `fib2` updates to `1`, and `fib1` uses the previous value of `fib2`, i.e., `1`.

On the next iteration, we expect `fib2` to be `1 + 1 = 2` and `fib1` to be `1`, and then `fib2` to be `2 + 1 = 3` and `fib1` to be `2`, and so on.

So let's type `continue` to go to the next `pry` call and inspect the binding again:

```elixir
pry(2)> continue
Break reached: Fib.number/1 (fib.exs:9)

    7:       fib1 = fib2
    8:       fib2 = fib
    9:       require IEx; IEx.pry
   10:     end)
   11:     fib2

pry(1)> binding()
[_i: 3, fib: 1, fib1: 1, fib2: 1, n: 5]
```

Here, we see that we are on the next iteration (`_i` is `3`), but apparently, the other variables do not change at all.
So this is where our bug lies. Everything is immutable in Elixir, so assigning variables inside the anonymous function creates new variables rather than overriding the ones on the outer scope.

We can now use this insight to fix our code.

If you are curious, [see the fixed version of the Fibonacci number generator](https://gist.github.com/pulkit110/1c477da9e47dbc5fdb6df582b7999e59).

### Using Breakpoints with IEx

In the previous section, we had to change the code to enter the pry session.
IEx also provides a `break!` function to set breakpoints without changing code.

This is very important when you want to set breakpoints in parts of code that you don't own, coming from a library or even from Elixir standard modules.

The only drawback is that this only works on compiled code, and you can only `break!` at the start of the function, not on any arbitrary line.

To use `break!` with our Fibonacci example:

```sh
$ elixirc fib.ex # This generates a beam file in your current dir
$ ls *.beam
Elixir.Fib.beam
$ iex            # This will load all beam files in the current directory
iex> IEx.Helpers.break!(Fib.number/1)
iex> Fib.number(5)
Break reached: Fib.number/1 (fib.ex:2)

    1: defmodule Fib do
    2:   def number(n) do
    3:     fib1 = 0
    4:     fib2 = 1
```

### Bonus: IEx Tips and Tricks

While we are on IEx, let us look at some general tips that can help you be more productive with it.

The first, and possibly the most important, is to enable shell history if you use IEx a lot. You can then press `↑` to get your last used commands or use `^ + R` to reverse search the history of used commands.

There are two ways you can enable shell history:

1. Enable each session by starting it with a flag:
    
    ```shell
    $ iex --erl "-kernel shell_history enabled"
    ```

2. Enable all sessions by setting the `ERL_AFLAGS` environment on your shell. Depending on your terminal configuration, you will need to add the following (or its equivalent) to a startup script (like `~/.zshrc`/`~/.bashrc`):
    
    ```shell
    export ERL_AFLAGS="-kernel shell_history enabled"
    ```

The second tip, which works on the recent version of Elixir (1.12+), means that you can use multi-line pipes directly in the shell. The pipe automatically gets the last evaluated statement's return value.

So you can just copy and paste long pipes from your code directly in the IEx session:

```elixir
iex(1)> [1, [2], 3]
[1, [2], 3]
iex(2)> |> List.flatten()
[1, 2, 3]
```

If you often use modules in IEx, you can create a file called `.iex.exs` from the directory used to access IEx. Alternatively, you can create a global file inside the home directory (`~/.iex/exs`), and it will be evaluated every time you open an IEx session.

```elixir
# Load another ".iex.exs" file
import_file("~/.iex.exs")

# Import some module from lib that may not yet have been defined
import_if_available(MyApp.Mod)

# Import Ecto.Query so that querying is always available in the shell
import Ecto.Query
```

Finally, there are some cases when you might be using IEx and you make a typo (like an additional bracket or ") and the command cannot be terminated, for example:

```shell
iex> ["abc"
...  "
...  ]
```

In this case, you cannot use `^ + C` or `^ + \` as they would terminate the session, rather than just the command. To terminate the command immediately, start a new line with `#iex:break`:

```shell
iex> ["abc"
...  "
...  ]
...  #iex:break
** (TokenMissingError) iex:1: incomplete expression
>
```

## 3. Visual Debugging

In addition to the prying functionality provided by Elixir, there is also a more sophisticated Erlang debugger that you can use.

While it works with a single compiled file like `IEx.break!`, let's try using a file that is a part of a mix project with `iex -S mix` this time.

```elixir
iex> :debugger.start()
{:ok, #PID<0.672.0>}
iex> :int.ni(Fib)
{:module, Fib}
iex> :int.break(Fib, 6)
ok
iex> Fib.number(5)
```

The above will open the Erlang debugger and stop at the configured breakpoint.

This provides a more traditional debugging approach where you can perform a single step or continue to the next breakpoint, evaluate expressions in the current context, etc.

![Erlang Debugger](https://blog.appsignal.com/images/blog/2021-11/debugging-code-in-elixir-erlang-debugger.png)

If you are using Visual Studio Code, the [ElixirLS plugin](https://marketplace.visualstudio.com/items?itemName=JakeBecker.elixir-ls) supports in-editor breakpoints.
There is also similar support in IntelliJ through the [intellij-elixir plugin](https://github.com/KronicDeth/intellij-elixir#debugger).

## Debugging Elixir Processes

A post on debugging Elixir code wouldn't be complete without also covering how to debug processes.

While we can use the debugging methods we've already covered, a couple of other process-specific options are available.

### Using Trace to Debug Processes in Elixir

Use `:sys.trace/2` when you quickly want to see all the messages exchanged between a process and its state updates.
We can use it to start/stop logging the process states and messages. Let's continue with the Fibonacci computer, but this time, [wrapped inside a GenServer](https://gist.github.com/pulkit110/e49fadea22e60758c26c5b6e81391ec1):

```
iex(3)> {:ok, pid} = GenServer.start_link(Fib, nil)
{:ok, #PID<0.1023.0>}
iex(4)> :sys.trace(pid, true)
:ok
iex(5)> GenServer.call(pid, {:get, 1})
*DBG* <0.1023.0> got call {get,1} from <0.1004.0>
*DBG* <0.1023.0> sent 1 to <0.1004.0>, new state #{0 => 0,1 => 1}
1
iex(6)> GenServer.call(pid, {:get, 2})
*DBG* <0.1023.0> got call {get,2} from <0.1004.0>
*DBG* <0.1023.0> sent nil to <0.1004.0>, new state #{0 => 0,1 => 1}
nil
iex(3)> GenServer.cast(pid, {:compute, 10})
*DBG* <0.1031.0> got cast {compute,10}
:ok
*DBG* <0.1031.0> new state #{0 => 0,1 => 1,2 => 1,3 => 2,4 => 3,5 => 5,6 => 8,
                             7 => 13,8 => 21,9 => 34,10 => 55}
iex(4)> GenServer.call(pid, {:get, 2})
*DBG* <0.1031.0> got call {get,2} from <0.1029.0>
*DBG* <0.1031.0> sent 1 to <0.1029.0>, new state #{0 => 0,1 => 1,2 => 1,
                                                   3 => 2,4 => 3,5 => 5,
                                                   6 => 8,7 => 13,8 => 21,
                                                   9 => 34,10 => 55}
1
```

### Debugging Processes with Observer in Elixir

If you prefer a more visual approach, Erlang provides an `:observer` that opens a user interface you can use to browse the Supervision Tree or check process states and messages.

To access this, all you need is:

```elixir
iex> :observer.start()
```

While a full review of `:observer` would take up a whole new post, here is a small demo of all that is possible:

<video width="100%" loop muted autoplay controls>
  <source src="https://blog.appsignal.com/videos/debugging-code-in-elixir-observer.mp4" type="video/mp4" />
  <source src="https://blog.appsignal.com/videos/debugging-code-in-elixir-observer.ogv" type="video/ogg" />
  <source src="https://blog.appsignal.com/videos/debugging-code-in-elixir-observer.webm" type="video/webm" />
</video>

## Wrap-up

In this post, we've covered three common methods of debugging: using the IO module, IEx, and visual debugging. We've also touched on debugging Elixir processes using trace and observer.

Elixir's powerful debugging tools are what make it such a compelling language choice for developers and businesses.

Until next time, enjoy getting stuck into debugging code and processes in Elixir!

**P.S. If you'd like to read Elixir Alchemy posts as soon as they get off the press, [subscribe to our Elixir Alchemy newsletter and never miss a single post](https://blog.appsignal.com/category/elixir-alchemy.html#elixir-alchemy)!**

*Pulkit is a senior full-stack engineer and consultant. In his free time, he writes about his experiences on [his blog](https://pulkitgoyal.in).*