Debugging with dbg: Exploring Elixir's Built-in Debugger
João Paulo Abreu
Posted on November 30, 2024
In this article, we'll explore Elixir's built-in debugger dbg
, a powerful tool introduced in Elixir 1.14 that makes debugging more straightforward and effective. Whether you're working in IEx or debugging your application code, dbg
provides valuable insights into your code's behavior.
Note: The examples in this article use Elixir 1.17.3. While
dbg
was introduced in Elixir 1.14, its behavior and output may vary slightly in different versions.
Table of Contents
- Understanding dbg
- Using dbg in IEx
- Debugging Files with dbg
- Advanced dbg Techniques
- Best Practices
- Conclusion
- Next Steps
Understanding dbg
dbg
is a macro that allows you to inspect values and function calls in your code. It's particularly useful because it shows both the code being debugged and its result. Unlike IO.inspect
, which only shows the value, dbg
provides:
- The exact expression being evaluated
- The file and line number where
dbg
was called - The resulting value of the expression
Using dbg in IEx
Let's start with some simple examples in IEx:
iex(1)> dbg(String.upcase("hello"))
[iex:1: (file)]
String.upcase("hello") #=> "HELLO"
"HELLO"
iex(2)> list = [1, 2, 3, 4, 5]
[1, 2, 3, 4, 5]
iex(3)> dbg(Enum.map(list, &(&1 * 2)))
[iex:3: (file)]
Enum.map(list, &(&1 * 2)) #=> [2, 4, 6, 8, 10]
[2, 4, 6, 8, 10]
Debugging Files with dbg
Let's create a simple module to demonstrate debugging with files:
# calculator.exs
defmodule Calculator do
def calculate_total(items) do
items
|> dbg()
|> Enum.map(&apply_tax/1)
|> dbg()
|> Enum.sum()
|> dbg()
end
defp apply_tax(price) do
tax = price * 0.1
dbg(tax)
price + tax
end
end
Output:
elixir calculator.exs
[calculator.exs:4: Calculator.calculate_total/1]
value #=> [100, 200, 300]
[calculator.exs:13: Calculator.apply_tax/1]
tax #=> 10.0
[calculator.exs:13: Calculator.apply_tax/1]
tax #=> 20.0
[calculator.exs:13: Calculator.apply_tax/1]
tax #=> 30.0
[calculator.exs:6: Calculator.calculate_total/1]
value #=> [110.0, 220.0, 330.0]
[calculator.exs:8: Calculator.calculate_total/1]
items #=> [100, 200, 300]
|> dbg() #=> [100, 200, 300]
|> Enum.map(&apply_tax/1) #=> [110.0, 220.0, 330.0]
|> dbg() #=> [110.0, 220.0, 330.0]
|> Enum.sum() #=> 660.0
Advanced dbg Techniques
Debugging multiple values
iex(6)> x = 10
10
iex(7)> y = 20
20
iex(8)> dbg(x + y)
[iex:8: (file)]
x + y #=> 30
30
iex(9)> dbg(x * y)
[iex:9: (file)]
x * y #=> 200
200
Debugging in Pipelines
You can use dbg
effectively in pipeline operations to see intermediate results:
iex(1)> [1, 2, 3]
[1, 2, 3]
iex(2)> |> dbg()
[iex:2: (file)]
v(-1) #=> [1, 2, 3]
[1, 2, 3]
iex(3)> |> Enum.map(&(&1 * 2))
[2, 4, 6]
iex(4)> |> dbg()
[iex:4: (file)]
v(-1) #=> [2, 4, 6]
[2, 4, 6]
iex(5)> |> Enum.sum()
12
Conditional Debugging
You can combine dbg
with conditional statements for targeted debugging. Here's a practical example:
# debug_example1.exs
defmodule DebugExample do
def process_numbers(list) when is_list(list) do
list
|> Enum.map(fn value ->
# Only debug negative numbers
if value < 0 do
dbg(value)
end
transform_number(value)
end)
end
defp transform_number(value) when value < 0 do
abs(value) * 2
end
defp transform_number(value), do: value
end
# Usage example:
numbers = [1, -2, 3, -4, 5]
DebugExample.process_numbers(numbers)
Output:
elixir debug_example1.exs
[debug_example1.exs:7: DebugExample.process_numbers/1]
value #=> -2
[debug_example1.exs:7: DebugExample.process_numbers/1]
value #=> -4
Best Practices
-
Strategic Placement
- Place
dbg
calls at critical points in your code - Use it before and after complex transformations
- Remove debugging code before committing
- Place
-
Clear Context
- Add meaningful variable names
- Use
dbg
in pipeline operations to track transformations - Keep debugging code temporary
-
Development Workflow
- Use
dbg
during development - Combine with other debugging tools like
IEx.pry
- Clean up debugging code after use
- Use
-
Version Control
- Consider adding
dbg
to your.gitignore
patterns - Use comments to mark temporary debugging code:
# TODO: Remove debug statements before commit |> dbg() # DEBUG
- Consider adding
- Consider using a linter configuration to catch accidental commits of debugging code
Conclusion
dbg
is a powerful addition to Elixir's debugging toolkit. It provides a clean and effective way to inspect values and understand code behavior, making it an essential tool for Elixir developers.
Next Steps
In the next article, "Customizing IEx", we'll explore how to enhance your Elixir development experience by:
- Configuring your IEx shell for maximum productivity
- Creating custom IEx helpers and commands
- Setting up personalized IEx configurations with
.iex.exs
- Customizing the IEx prompt and colors
- Defining helpful IEx aliases and imports
Stay tuned to learn how to make your Elixir interactive shell work better for your development workflow!
Posted on November 30, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.