Debugging in Elixir and Erlang: An Introduction
Tags: elixir, erlang
Published: 2022-12-06 12:54:28

Welcome to part one of this two-part series on debugging in Elixir and Erlang. In this post, we'll use several different tools and techniques to debug Elixir code.

First, we'll get to know the demo project I created to showcase certain tools: [dist_messenger](https://github.com/msramos/dist_messenger). It's an Elixir project of a distributed messaging system that can be used directly on `IEx`. It allows users in different nodes to send and receive messages to each other.

Then, we'll learn about the basic debugging tools of Elixir: inspect, pry and dbg.

In part two, we'll turn our attention to Erlang, putting together what we learn into a simple script to trace a process message across nodes.

Let's get going!

## The Necessity of Debugging

You write a piece of code, you run it, and it doesn't execute as expected.

That is what programming looks like most of the time. Trying to figure out what's happening can not only be time-consuming, but very frustrating when we don't have the right tools.

Can you level your washing machine without a leveling tool, instead using a ruler? Of course, but it will take much more time and energy.

So, to understand what's happening in our code, it's good sometimes to take a few steps back and ask some questions, like:

- Is the value of this variable correct?
- Are the transformations being applied to a variable producing the expected result?
- Are the messages being correctly sent from one process to another?
- Is there any abnormality in resource consumption?

Both Elixir and Erlang provide tools to answer these questions. Knowing how to use the tools certainly adds a lot of value.

## Getting to Know Our Demo Project

The [dist_messenger](https://github.com/msramos/dist_messenger) project uses a combination of [`GenServer`](https://hexdocs.pm/elixir/1.14.1/GenServer.html) and [`:global`](https://www.erlang.org/doc/man/global.html) to provide a distributed messaging system inside an Erlang cluster.

Each node in this cluster can handle one user, and its state is maintained by a single GenServer, the [`Messenger.MessagingServer`](https://github.com/msramos/dist_messenger/blob/main/lib/messenger/messaging_server.ex). Users can sign in to the cluster with a unique name, which is then registered across all nodes using the [`:global.register_name/2`](https://www.erlang.org/doc/man/global.html#register_name-2) function.

For the user interface, the [`Messenger.IExClient`](https://github.com/msramos/dist_messenger/blob/main/lib/messenger/iex_client.ex) provides several functions that allow users to interact with the messaging system in IEx. This module is imported by the [`.iex.exs`](https://github.com/msramos/dist_messenger/blob/main/.iex.exs)script, so we can use all its functions directly on `iex` when running it with `iex -S mix`. [Check the docs for `.iex.exs`
files](https://hexdocs.pm/iex/1.14/IEx.html#module-the-iex-exs-file).

Because each node handles one user, to get the full experience of this project, we need to run [multiple nodes](https://www.erlang.org/doc/reference_manual/distributed.html) (by running nodes with a node name and a shared secret cookie). To make things simple, the project includes a script [`start`](https://github.com/msramos/dist_messenger/blob/main/start) that will do that for us. You just need to give the name (it will run all nodes on `0.0.0.0`).

You'll notice that the included `.iex.exs` script will automatically try to connect to nodes `node0` up to `node9` on localhost. So, by naming the nodes within this range, we should get a cluster.

Let's start two nodes and sign-in two users: user `alice` on `node0` and `bob` on `node1`.

```shell
# On terminal 1
$ ./start node0

# On terminal 2
$ ./start node0
```

To sign in to the messaging system, you can call `sign in <name>` on iex:

```elixir
# node0 - user: alice
iex> sign in "alice"
Signed in!
```

Now you can send a message to other users using `msg <user> <message>`:

```elixir
# node0 - user: alice
iex> msg "bob", "Hello! How are you?"
Message sent!
```

In `bob`'s session, we get a notification that a new message has arrived:

```elixir
# node1 - user: bob
[!] New message from alice
```

We can use the `inbox` command to view all received messages:

```elixir
# node1 - user: bob
iex> inbox
> from: alice
> Hello! How are you?
---
```

In `alice`'s session, we can use `sent` to view all sent messages:

```elixir
# node0 - user: alice
iex> sent
> to: bob
> Hello! How are you?
---
```

There are also other commands:

- `signout`: signs out the current users from the cluster
- `whoami`: prints the current signed-in user
- `last_msg`: prints the last received message
- `editor`: sends a message like `msg`, but it's interactive

Now that we know how our example works, let's get started with debugging!

## Elixir Debugging Basics

You can use the tools in this section to:

- quickly gather information about a specific variable
- get the result of a function call
- check a whole pipe chain

They do require changes in the source code, so we'll use them during development and tests.

## Debugging with `IO.inspect/2` in Elixir

This is the easiest — and probably the first — tool that an Elixir developer will reach for to investigate issues. The [`IO.inspect/2`](https://hexdocs.pm/elixir/1.14.0/IO.html#inspect/2) function prints the contents of an expression and returns the expression itself.

```elixir
iex> name = {:name, "Jack Shephard"}
iex> IO.inspect(name, label: "Name")
# Name: {:name, "Jack Shephard"}
```

It returns the value of the evaluated expression so that you can use it within a pipeline. The `label` option is very useful when you have multiple calls to `IO.inspect` and need some hints to understand what you are looking at.

```elixir
person = %{name: "Kate Austen"}

iex> " some text "
...> |> String.trim()
...> |> String.upcase() |> IO.inspect(label: "Here")
...> |> String.split()
Here: "SOME TEXT"
["SOME", "TEXT"]
```

## `IEx.pry` for Elixir

While `IO.inspect` allows you to read the content of any variable, `IEx.pry` gives us a bit more insight and information on the code.

You can think of `IO.pry` as an interactive `IO.inspect`.

Let's use it in the [`editor/0` function](https://github.com/msramos/dist_messenger/blob/main/lib/messenger/iex_client.ex#L90-L101) and see what happens:

```elixir
...

def editor do
  case Messaging.whoami() do
    {:ok, _user} ->
      recipient = IO.gets("recipient: ") |> String.trim()
      message = IO.gets("message: ") |> String.trim()

      # Pause the execution here
      require IEx; IEx.pry

      msg(recipient, message)

    _ ->
      IO.puts("Not signed in!")
  end
end

...
```

Note: you _must_ use IEx in the application context in order to use `IEx.pry`. This means you need to start it with `iex -S mix`.

When you call the `editor/0` (after you type in the _recipient_ and the _message_), the `iex` session will pause execution on the line.

```elixir
iex> editor
recipient: kate.austen
message: Hello, Kate!
Break reached: Messenger.IExClient.editor/0 (lib/messenger/iex_client.ex:96)

   93:         recipient = IO.gets("recipient: ") |> String.trim()
   94:         message = IO.gets("message: ") |> String.trim()
   95:
   96:         require IEx; IEx.pry
   97:
   98:         msg(recipient, message)
   99:

pry>
```

In this new console, you can type a variable's name to see its contents:

```elixir
pry> recipient
"kate.austen"
pry> message
"Hello, Kate!"
```

Type `continue` or `next` to continue the execution.

## `dbg/2` Debugging in Elixir

This is a new feature since Elixir 1.14. It's pretty much `IO.inspect/2` with superpowers.

To demonstrate it, we'll add [`dbg`](https://hexdocs.pm/elixir/main/Kernel.html#dbg/2) to [this demo line](https://github.com/msramos/dist_messenger/blob/main/lib/messenger/messaging_server.ex#L82). It will be called whenever the server receives a message from another server.

```elixir
# ...

def handle_info({:msg, from, body}, %{inbox: inbox} = state) do
  # ...

  message = {from, body}
  updated_state = %{state | inbox: [message | inbox]} |> dbg()

  {:noreply, updated_state}
end

#...
```

Now, when your node receives a message, the recipient's `iex` session will request to pry on the code:

```elixir
[!] New message from jacob
Request to pry #PID<0.159.0> at Messenger.MessagingServer.handle_info/2 (lib/messenger/messaging_server.ex:82)

   79:     end
   80:
   81:     message = {from, body}
   82:     updated_state = %{state | inbox: [message | inbox]} |> dbg()
   83:
   84:     {:noreply, updated_state}
   85:   end

Allow? [Yn]
```

This will open an `IEx.pry` session, and you can check the values of all variables.

You can also use `dbg` at the end of a pipe to see what's happening:

```elixir
[["h"], ["i"]] #=> [["h"], ["i"]
|> List.flatten() #=> ["h", "i"]
|> Enum.join() #=> "hi
|> dbg()
```

`dbg/2` is macro-evaluated at compile time. When called, it will inject code
that allows the interpreter to pause an application's execution and pry the
current context. [Check out 'Elixir 1.14: Better Debugging with dbg/2 and More'](https://blog.appsignal.com/2022/09/13/elixir-1-14-better-debugging-with-dbg-and-more.html) for an overview of `dbg/2`.

## Debugging Production Apps with AppSignal

After deploying your app to production, AppSignal can help debug issues from the real world.

Set up [AppSignal for your Elixir application](https://www.appsignal.com/elixir), and you'll automatically gain access to an error dashboard with information about all the errors in your app.

![Errors dashboard](https://blog.appsignal.com/images/blog/2022-11/errors-dashboard.png)

From there, you can dig deeper for more information on individual errors, so you can fix them:

![Specific errors in Elixir](https://blog.appsignal.com/images/blog/2022-11/specific-error.png)

[Read more about AppSignal for Elixir](https://www.appsignal.com/elixir).

## Wrap Up

This post has introduced debugging basics in Elixir using `IO.inspect/2`, `IEx.pry`, and `dbg/2`. Check out [Three Ways to Debug Code in Elixir](https://blog.appsignal.com/2021/11/30/three-ways-to-debug-code-in-elixir.html) for some more information.

Next time, we'll focus on Erlang specifically, debugging runtime and finding out how to trace an Elixir clustered application using Erlang's `:debugger`.

Until then, happy coding!

**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](/elixir-alchemy)!**