Don't want to miss out on Elixir news? Subscribe to ElixirPulse!

Debugging Elixir

Elixir provides a couple of tools to make debugging a breeze. In this blog post, we’ll focus on three built-in debugging methods in Elixir: IO.inspect, dbg, and tap.

IO.inspect

The IO.inspect/2 function is a simple yet powerful tool for debugging. It returns the value it was given, making it easy to insert into your code without altering behavior. It also prints the value to the standard output, which can be customized.

Example Scenario: Use IO.inspect when you want a quick and dirty way to see what a variable or expression evaluates to without affecting the existing code flow.

iex(2)> list = [1, 2, 3]
iex(3)> new_list = Enum.map(list, fn x -> IO.inspect(x) * 2 end)
1
2
3
[2, 4, 6]

IO.inspect/2 also accepts a label option to make it easy to search through terminal output.

iex(1)> IO.inspect obscure_map, label: "What is in this map?"
What is in this map?: %{key: "value"}

Now you can search backwards for the label phrase - I like to use labeling for seeing how data looks in a before and after state:

my_map
|> IO.inspect(label: "Before")
|> transform_map()
|> IO.inspect(label: "After")
Before: %{key: "value"}
After: %{key: "value", new_key: "new value"}

dbg

The dbg/2 function allows you to inspect the value returned by a function or a pipeline of functions. It is especially useful for debugging pipelines, as it prints the value at each step of the pipeline before it.

Example Scenario: Use dbg when you want to understand how data transforms at each stage of a pipeline.

"Elixir is cool!"
|> String.trim_trailing("!")
|> String.split()
|> List.first()
|> dbg()

This will output:

"Elixir is cool!"  => "Elixir is cool!"
|> String.trim_trailing("!")  => "Elixir is cool"
|> String.split()  => ["Elixir", "is", "cool"]
|> List.first()  => "Elixir"

tap

The tap/2 function is similar to IO.inspect but allows you to pass a function that will receive the value for further actions, such as logging or computation.

Example Scenario: Use tap when you want to perform some side-effect operation on the value, like logging or sending it to a monitoring service.

value
|> do_something()
|> tap(&send_to_monitoring(&1))
|> do_something_else()

Conclusion

Elixir provides a range of debugging tools, each with its own strengths. IO.inspect is great for quick inspections, dbg excels in pipeline debugging, and tap is useful for side-effects. Choose the one that fits your debugging needs.