View Source ExDbug (ExDbug v2.0.0)

ExDbug

Hex.pm Docs

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

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

  1. Use descriptive context names matching your application structure
  2. Prefer decorator-based debugging for new code
  3. Use compatibility mode for gradual migration
  4. Set appropriate DEBUG patterns for different environments
  5. Configure hierarchically (global → module → function)
  6. 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

debug_opts()

@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()
]

Functions

format_output(message, context, metadata, opts \\ %{})