Debugging Phoenix and Elixir Applications in Visual Studio Code.

Brooklin Myers
Geek Culture
Published in
7 min readJun 21, 2021

--

Spend less time fixing code and more time delivering valuable new features.

Photo by Munro Studio on Unsplash

Knowing how to debug your Phoenix or Elixir project properly is the difference between finding a bug in a few minutes or spending the next few painstaking hours reading thousands of lines of code looking for the one line that’s causing your app to crash, your boss to lose their hair (there’s a reason many developers are bald), and your users to abandon ship.

In this article, you’re going to learn how to leverage IO, IEX, and the built-in Visual Studio Code debugger to improve your debugging ability.

Debugging using Elixir’s IO module

The simplest way to debug is printing. You can print values using the IO.puts and IO.inspect functions.

IO.puts

IO.puts is used for simple, readable values which implement the String.Chars protocol. This includes integers, strings, and atoms.

iex(1)> IO.puts("example string")
example string
:ok
iex(2)> IO.puts(:example_atom)
example_atom
:ok
iex(3)> IO.puts(2)
2
:ok

IO.puts will crash if you give it something like a tuple.

iex(1)> IO.puts({1, 2, 3}) 
** (Protocol.UndefinedError) protocol String.Chars not implemented for {1, 2, 3} of type Tuple
(elixir 1.11.2) lib/string/chars.ex:3: String.Chars.impl_for!/1
(elixir 1.11.2) lib/string/chars.ex:22: String.Chars.to_string/1
(elixir 1.11.2) lib/io.ex:686: IO.puts/2

IO.puts can also give weird values for lists because it’s not converting elixir’s internal representation of the value back into something human-readable.

iex(1)> IO.puts [1, 2, 3]                
☺☻♥
:ok

IO.inspect

IO.inspect knows how to print all data structures, including lists, structs, tuples, and maps.

iex(1)> IO.inspect([1,2,3]) 
[1, 2, 3]
[1, 2, 3]
iex(2)> IO.inspect({1,2,3})
{1, 2, 3}
{1, 2, 3}
iex(3)> IO.inspect(%{"example" => "map"})
%{"example" => "map"}
%{"example" => "map"}

You’ll notice that inspect appears to print everything twice. Actually, inspect only prints once, and iex prints the return value. That’s right, IO.inspect returns the value passed to it. Here’s an example using a function that returns a tuple. This function still returns the tuple {1, 2, 3}, and also prints {1,2,3}.

So when I run the function elsewhere, it has the correct return value.

If you want, you can clone the example repository and run mix test. You should see something like this.

You can also give your inspect values a label to make them easier to read.

IO.inspect binding

You can also use IO.inspect with the binding() function to view the arguments the function was called with.

Debugging using IEX

to run your Elixir project in the Interactive Elixir shell, run:

iex -S mix

To run your tests in an Elixir or Phoenix project in the Elixir Interactive shell run:

iex -S mix test

To run your Phoenix app in the Elixir Interactive shell, you can run:

iex -S mix phoenix.server

IEx.pry()

IEx.pry() allows you to set a breakpoint in your code and inspect values. It’s great when you want to quickly inspect many values at a particular spot in your code without digging through tons of print statements.

Now when elixir evaluates the code where the IEx.pry() function is, you’ll be prompted with a request to pry. If you accept, then you can inspect values in the Interactive Elixir shell.

Ignore that I run iex.bat instead of iex. That’s a quirk of using Elixir on a Windows computer.

You can accomplish the same thing when running a phoenix application in the Interactive Elixir shell.

Here’s an example from a phoenix blog app where the user navigates to http://localhost:4000/author/1 to see a list of posts by an author.

Using the Visual Studio Code debugger

While using IEx for quickly viewing values at a particular spot in the code base is great. However, using an actual debugger with a visual interface is much more convenient when you want to view multiple locations, step through the code, and monitor data as your application runs.

Set up the debugger

Setting up the debugger for phoenix or elixir is done automatically for you by Visual Studio Code.

When you open a phoenix or elixir project in Visual Studio Code, you should have a Run and Debugger option in your menu. If you don’t see it, you can run a Show Run and Debug command with ctrl-shift-D .

If you want to run the example code for yourself, then you can clone the example repository.

Make sure you have an elixir .ex file open and click create a launch.json file. This should create a launch.json file for you.

Running elixir project with the debugger.

You can run your tests with the debugger using the mix test configuration.

However, you might want to run your project using the iex -S mix command to use the Interactive Elixir shell with the debugger.

At the time of writing, I experienced a bug when running the default mix task for the elixir project.

I would get an error::elixir_ls_expression_timeout

Thank you to Ralphe Maute who figured out the solution. The problem was there was no entry point into the application.

If you experience the same issue, make sure you have an application.ex file in /lib.

Then reference the file in mix.exs.

The Example Repository has been updated so that if you set a breakpoint and run the default mix task, it should work:

Running a phoenix project with the debugger.

If you are using phoenix, add “task”: “phx.server” to the config.

Set a breakpoint

Now you can set breakpoints in your code by clicking the red dot to the left of any line. I’ve set up some example code and a test below to show the start of a breakpoint.

Here’s the test that will execute the code above.

Then select the mix test configuration and run it using the green play button.

Step Over

You can step through your code by pressing the blue step-over button. Notice that the step0 variable is listed now because it’s been defined, but variables defined below the yellow line are not listed because they don’t exist yet.

Step Into

You can step into a function using the blue step-into button. This allows you to see the inside of a function rather than jumping to the next line.

First, step over until you are on the line with the function you want to step into.

Then press the blue step-into button. Notice that the variables and arguments change because we’re inside of a different function now.

Continue

To conveniently navigate your code without stepping over and into every function, set another breakpoint and hit the blue continue button.

Watch variables

You can also set variables to watch so you can track the progress of a value.

Conditional Breakpoints

You can set a breakpoint to trigger only if a condition is true. Right-click where you would normally add a breakpoint and select the conditional breakpoint option.

This set the condition for the breakpoint to trigger. For example, this breakpoint will only trigger if a equals 1.

Conclusion

Knowing how to debug your code means you spend more time delivering valuable new features and less time fixing the bugs you create along the way. I hope this article helps save you some headaches the next time you have a tricky bug to fix. If you have a comment or question, please reply to this article, and I would be happy to get back to you.

--

--

Brooklin Myers
Geek Culture

Software Engineer. I create educational content focused on technology for mobile and web applications.