View Source ExDbug (ExDbug v2.0.0)
ExDbug
Debug utility for Elixir applications, inspired by the Node.js 'debug' package. Version 2.0 introduces decorator-based function tracing while maintaining compatibility with 1.x style debugging.
Features
- 🎯 Decorator-based function tracing - Zero-cost, compile-time instrumentation
- 🔄 1.x Compatibility Mode - Seamless upgrade path from earlier versions
- 🔍 Namespace-based filtering - Filter debug output by context
- 📊 Rich metadata support - Attach and format detailed debug information
- ⚡ Zero runtime cost when disabled - Compile-time optimization
- 🌍 Environment variable-based filtering - Easy runtime control
- 📝 Automatic metadata truncation - Smart handling of large values
- 🔧 Hierarchical configuration - Global, module, and function-level settings
- 📈 Value tracking - Monitor values through pipelines
- ⏱️ Optional timing and stack traces - Deep insights when needed
Installation
Add ex_dbug
to your list of dependencies in mix.exs
:
def deps do
[
{:ex_dbug, "~> 2.0"}
]
end
Usage (2.0 Style)
The new decorator-based approach makes debugging more elegant and maintainable:
defmodule MyApp.Worker do
use ExDbug, enabled: true
# Simple debug trace
@decorate dbug()
def process(data) do
# Implementation
end
# Configured debug trace
@decorate dbug(context: :important)
def process_important(data) do
# Implementation
end
# Debug all functions in module
@decorate_all dbug()
def bulk_1(arg), do: arg
def bulk_2(arg), do: arg
end
Compatibility Mode (1.x Style)
For existing projects or gradual migration, use compatibility mode:
defmodule MyApp.LegacyWorker do
use ExDbug, compatibility_mode: true
def process(data) do
dbug("Processing data", size: byte_size(data))
# ... processing logic
dbug("Completed processing", status: :ok)
end
end
Configuration
Compile-Time Configuration
In your config.exs
:
config :ex_dbug,
enabled: true,
config: [
max_depth: 3,
include_timing: true,
include_stack: true,
truncate: 100,
levels: [:debug, :error]
]
Module-Level Configuration
use ExDbug,
enabled: true,
max_depth: 5,
include_timing: true,
include_stack: false,
levels: [:debug, :error]
Function-Level Configuration (2.0)
@decorate dbug(
context: :important,
include_timing: true,
include_stack: true
)
def critical_function(arg) do
# Implementation
end
Runtime Configuration
Control debug output using the DEBUG
environment variable:
# Enable all debug output
DEBUG="*" mix run
# Enable specific namespace
DEBUG="myapp:worker" mix run
# Enable multiple patterns
DEBUG="myapp:*,other:thing" mix run
# Enable all except specific namespace
DEBUG="*,-myapp:secret" mix run
Migrating from 1.x to 2.0
Option 1: Direct Upgrade (Recommended)
Replace debug calls with decorators:
# Before (1.x)
def process(arg) do
dbug("Processing", value: arg)
# Implementation
end
# After (2.0)
@decorate dbug()
def process(arg) do
# Implementation
end
Option 2: Compatibility Mode
For gradual migration, enable compatibility mode:
use ExDbug, compatibility_mode: true
# All 1.x code continues to work
Configuration Updates
Update your configuration to use the new hierarchical structure:
# Before (1.x)
config :ex_dbug, enabled: true
# After (2.0)
config :ex_dbug,
enabled: true,
config: [
max_depth: 3,
include_timing: true
]
Best Practices
- Use descriptive context names matching your application structure
- Prefer decorator-based debugging for new code
- Use compatibility mode for gradual migration
- Set appropriate DEBUG patterns for different environments
- Configure hierarchically (global → module → function)
- Disable in production for zero overhead
Production Use
While ExDbug has minimal overhead when disabled, it's recommended to set
config :ex_dbug, enabled: false
in production unless debugging is specifically
needed. This ensures zero runtime cost as debug calls are compiled out completely.
Summary
Types
@type debug_opts() :: [ enabled: boolean(), context: atom() | String.t(), max_depth: non_neg_integer(), include_timing: boolean(), include_stack: boolean(), truncate: boolean() | non_neg_integer() ]