Overview
Ruby's debug gem provides an interactive debugging environment with a comprehensive set of commands for controlling program execution, inspecting variables, setting breakpoints, and analyzing call stacks. The debug gem replaces the legacy debugger and byebug gems, offering improved performance and modern debugging capabilities.
The debug gem operates through a command-line interface that activates when debugger
statements execute or breakpoints trigger. The debugging session provides access to the current execution context, allowing inspection of local variables, instance variables, method parameters, and object state at any point during program execution.
Core command categories include execution control (step
, next
, continue
), breakpoint management (break
, delete
), program inspection (list
, whereami
, backtrace
), and variable examination (info
, eval
, display
). Commands accept various options and parameters to customize their behavior and output format.
# Basic debugging session
def calculate_total(items)
debugger # Debugging session starts here
total = 0
items.each do |item|
total += item[:price] * item[:quantity]
end
total
end
# When debugger hits, available commands:
# (rdbg) info locals # Show local variables
# (rdbg) step # Step into each iteration
# (rdbg) continue # Resume execution
The debug gem integrates with Ruby's execution model, providing access to the current binding, method definitions, class hierarchies, and module inclusion chains. Commands operate within the context of the current stack frame, with options to navigate up and down the call stack to examine different execution contexts.
class OrderProcessor
def process(order)
validate_order(order)
debugger # Session starts in process method context
calculate_shipping(order)
end
private
def validate_order(order)
raise "Invalid order" if order.nil?
end
end
# Debug session provides access to:
# - Current method context (process)
# - Instance variables (@order_count, etc.)
# - Method parameters (order)
# - Call stack (process -> calling method)
Basic Usage
Debug gem commands follow a consistent syntax pattern with command names, optional parameters, and flags. Most commands accept abbreviated forms for faster typing during debugging sessions. The debug prompt (rdbg)
indicates an active debugging session waiting for command input.
Execution control commands manage program flow. The step
command executes the next line and steps into method calls, while next
executes the next line in the current method without stepping into called methods. The continue
command resumes program execution until the next breakpoint or program termination.
def fibonacci(n)
return n if n <= 1
debugger
result = fibonacci(n - 1) + fibonacci(n - 2) # Step will enter recursive calls
result
end
# Debug session:
# (rdbg) step # Steps into first fibonacci(n-1) call
# (rdbg) next # Executes current line without stepping into calls
# (rdbg) continue # Runs until next debugger or program end
Breakpoint commands control where execution pauses. The break
command sets breakpoints at specific lines, methods, or conditions. Breakpoints persist across method calls and can include conditional logic to trigger only when specific conditions occur.
class DataProcessor
def process_batch(data)
data.each_with_index do |item, index|
process_item(item, index)
end
end
def process_item(item, index)
# Complex processing logic
result = transform_data(item)
store_result(result)
end
end
# Setting breakpoints:
# (rdbg) break DataProcessor#process_item # Break on method entry
# (rdbg) break 15 # Break on line 15
# (rdbg) break 20 if index > 100 # Conditional breakpoint
Information and inspection commands examine program state. The list
command displays source code around the current execution point, whereami
shows the exact current location with context, and backtrace
displays the complete call stack with method names and line numbers.
def complex_calculation(data)
step1 = data.map { |x| x * 2 }
step2 = step1.select { |x| x > 10 }
debugger
step3 = step2.reduce(:+) # Current execution point
step3 / step2.length
end
# Information commands:
# (rdbg) list # Shows surrounding source code
# (rdbg) list 1, 20 # Shows lines 1-20
# (rdbg) whereami # Shows current location with context
# (rdbg) backtrace # Shows complete call stack
Variable inspection uses the info
command with various parameters. info locals
displays local variables, info instance
shows instance variables, and info const
lists constants. The eval
command executes arbitrary Ruby expressions within the current context.
class ReportGenerator
def initialize(data)
@data = data
@report_date = Time.now
end
def generate
filtered_data = @data.select { |item| item.valid? }
debugger
summary = calculate_summary(filtered_data)
end
private
def calculate_summary(data)
# Implementation
end
end
# Variable inspection:
# (rdbg) info locals # Shows filtered_data, summary
# (rdbg) info instance # Shows @data, @report_date
# (rdbg) eval @data.size # Executes expression: returns data size
# (rdbg) p filtered_data # Pretty-prints variable value
Error Handling & Debugging
Debug gem commands provide specialized functionality for investigating errors, exceptions, and unexpected program behavior. Exception handling debugging involves examining the call stack at the point of error, inspecting variable states that led to the exception, and tracing execution flow through error conditions.
The catch
command sets breakpoints on exception types, automatically pausing execution when specific exceptions occur. This allows examination of the program state immediately before exception handling begins, providing insight into the conditions that triggered the error.
class FileProcessor
def process_files(file_paths)
file_paths.each do |path|
begin
content = File.read(path)
process_content(content)
rescue StandardError => e
debugger # Examine error state
handle_error(e, path)
end
end
end
def process_content(content)
# May raise various exceptions
parsed_data = JSON.parse(content)
validate_data(parsed_data)
end
end
# Exception debugging:
# (rdbg) catch StandardError # Break on any StandardError
# (rdbg) catch JSON::ParserError # Break on specific exception type
# (rdbg) info exception # Show current exception details
# (rdbg) eval e.backtrace # Examine exception backtrace
Stack frame navigation commands help trace the execution path leading to errors. The up
and down
commands move between stack frames, allowing inspection of variables and context at different levels of the call stack. The frame
command jumps directly to specific stack frames.
def outer_method(data)
processed = data.map { |item| middle_method(item) }
processed.compact
end
def middle_method(item)
inner_method(item[:value])
rescue ArgumentError => e
debugger # Error occurred in inner_method
nil
end
def inner_method(value)
raise ArgumentError, "Invalid value" if value.nil?
value.to_s.upcase
end
# Stack navigation during error:
# (rdbg) backtrace # Show full call stack
# (rdbg) up # Move to middle_method frame
# (rdbg) up # Move to outer_method frame
# (rdbg) frame 0 # Jump to inner_method frame
# (rdbg) info locals # Show locals at current frame
Conditional debugging addresses intermittent errors and complex state-dependent bugs. Conditional breakpoints and watchpoints monitor specific variable states and execution conditions, triggering only when problematic conditions occur.
class BatchProcessor
def process_batch(items)
items.each_with_index do |item, index|
debugger if item.nil? && index > 1000 # Conditional debugging
result = process_single_item(item)
update_progress(index, result)
end
end
def process_single_item(item)
return nil if item.nil?
# Complex processing that sometimes fails
item.transform.validate.finalize
end
end
# Conditional debugging commands:
# (rdbg) break process_single_item if item.nil?
# (rdbg) watch @progress_count > 1000
# (rdbg) trace method process_single_item # Trace all calls
# (rdbg) trace line # Trace line execution
Debug command error recovery helps when debugging sessions encounter problems. The quit
command exits debugging cleanly, restart
reinitializes the debugging session, and help
provides command documentation when debugging behavior seems unexpected.
# Error recovery scenarios:
# (rdbg) eval some_complex_expression # If eval crashes debugger
# (rdbg) quit # Clean exit from debug session
# (rdbg) restart # Restart debugging session
# (rdbg) help break # Get help on break command
# (rdbg) help # List all available commands
Production Patterns
Debug gem commands adapt to production debugging scenarios where traditional debugging approaches may not be feasible. Production debugging requires careful consideration of performance impact, log management, and minimal disruption to running systems.
Remote debugging capabilities allow debugging sessions to connect to running production processes. The debug gem supports TCP connections and Unix socket connections for remote debugging, enabling developers to debug production issues without direct server access.
# Production debugging setup
require 'debug'
require 'debug/open'
class ProductionService
def handle_request(request)
if Rails.env.production? && request.headers['DEBUG_TOKEN'] == ENV['DEBUG_TOKEN']
debugger # Only activate with valid debug token
end
process_request(request)
rescue StandardError => e
if should_debug_error?(e)
binding.break # Conditional production debugging
end
raise
end
private
def should_debug_error?(error)
error.class.name.include?('Critical') &&
Time.current.hour.between?(2, 6) # Debug during low traffic
end
end
# Remote debugging connection:
# RUBY_DEBUG_PORT=12345 rails server
# Then connect: rdbg --attach localhost:12345
Logging integration combines debug commands with application logging to capture debugging information without interactive sessions. The debug gem can output command results to log files, capturing variable states and execution traces for later analysis.
class OrderProcessor
def process_order(order)
debug_context = {
order_id: order.id,
user_id: order.user_id,
timestamp: Time.current
}
if Rails.env.production?
# Non-interactive debugging for production
Debug.capture_locals(debug_context) if order.total > 10000
else
debugger if order.total > 10000
end
validate_order(order)
calculate_totals(order)
finalize_order(order)
rescue ValidationError => e
# Capture debug information on validation errors
Debug.log_context(locals: true, backtrace: true) if Rails.env.production?
raise
end
end
# Production debug logging configuration:
# RUBY_DEBUG_LOG_LEVEL=info
# RUBY_DEBUG_LOG_FILE=tmp/debug.log
Performance-conscious debugging minimizes the impact of debugging commands on production performance. Conditional breakpoints, sampling-based debugging, and time-limited debugging sessions prevent debugging from significantly affecting system performance.
class PerformanceCriticalService
def process_batch(items)
debug_sample_rate = ENV.fetch('DEBUG_SAMPLE_RATE', 0).to_f
items.each_with_index do |item, index|
should_debug = rand < debug_sample_rate ||
item.priority == 'critical' ||
index % 1000 == 0
if should_debug
debugger(do: 'info locals; continue') # Auto-continue debugging
end
process_item(item)
end
end
def process_item(item)
start_time = Time.current
result = expensive_operation(item)
duration = Time.current - start_time
if duration > 5.seconds
# Debug slow operations
debugger(do: 'eval duration; eval item.attributes; continue')
end
result
end
end
# Performance debugging commands:
# (rdbg) time # Show execution timing
# (rdbg) profile start # Start performance profiling
# (rdbg) profile stop # Stop and show profile results
Monitoring integration connects debug commands with application monitoring systems. Debug sessions can emit metrics, alerts, and structured data to monitoring platforms, providing observability into debugging activities and their outcomes.
class MonitoredService
def critical_operation(data)
operation_id = SecureRandom.uuid
begin
validate_data(data)
result = perform_operation(data)
if result.anomalous?
# Debug anomalous results with monitoring
debug_anomaly(operation_id, data, result)
end
result
rescue StandardError => e
emit_debug_metrics(operation_id, e)
debugger if should_debug_error?(e)
raise
end
end
private
def debug_anomaly(operation_id, input, output)
debug_info = {
operation_id: operation_id,
input_hash: Digest::SHA256.hexdigest(input.to_json),
output_anomaly_score: output.anomaly_score,
context: Debug.capture_context(locals: true, frames: 3)
}
Rails.logger.info("Debug anomaly detected", debug_info)
Metrics.increment('debug.anomaly.detected')
debugger if Rails.env.development?
end
def emit_debug_metrics(operation_id, error)
Metrics.increment('debug.error.captured', tags: {
error_class: error.class.name,
operation: 'critical_operation'
})
end
end
Common Pitfalls
Debug gem commands present several common misconceptions and error-prone usage patterns that can lead to frustrating debugging experiences or incorrect program analysis. Understanding these pitfalls prevents wasted debugging time and improves debugging effectiveness.
Stepping behavior misconceptions cause confusion about when and how execution control commands navigate through code. The difference between step
, next
, and continue
becomes critical when debugging complex method call chains, blocks, and recursive functions.
def process_data(items)
items.map do |item| # Line 2
transform_item(item) # Line 3 - next vs step difference
end.select do |result| # Line 4
validate_result(result) # Line 5 - another method call
end # Line 6
end
def transform_item(item)
item.upcase.reverse # Method implementation
end
# Common stepping mistakes:
# (rdbg) step # From line 3: steps INTO transform_item method
# (rdbg) next # From line 3: executes transform_item, stops at line 4
# (rdbg) step # From line 5: steps INTO validate_result method
#
# Pitfall: Expecting 'step' to go to next line in current method
# Reality: 'step' follows execution flow into method calls
Variable scope confusion occurs when inspecting variables across different stack frames and execution contexts. Debug commands operate within the current frame context, and variable availability changes as execution moves through different scopes and method boundaries.
class DataAnalyzer
def initialize
@instance_var = "analyzer"
end
def analyze(data)
local_var = "analysis"
data.each do |item|
block_var = item.process
debugger # Context: inside block
calculate_stats(block_var)
end
end
private
def calculate_stats(value)
stats_local = value * 2
debugger # Context: calculate_stats method
end
end
# Variable scope pitfalls:
# Inside block context:
# (rdbg) info locals # Shows: item, block_var, local_var
# (rdbg) info instance # Shows: @instance_var
# Inside calculate_stats context:
# (rdbg) info locals # Shows: value, stats_local
# (rdbg) info instance # Shows: @instance_var
# (rdbg) eval local_var # ERROR: undefined local variable
#
# Pitfall: Assuming variables from outer scopes are accessible
# Reality: Only current frame variables are directly accessible
Breakpoint persistence and management problems arise when breakpoints accumulate across debugging sessions or interfere with program flow. Breakpoints remain active until explicitly deleted, and conditional breakpoints may have unintended side effects or performance implications.
class ServiceManager
def start_service(name)
debugger # Temporary breakpoint for current issue
service = find_service(name)
service.initialize_resources
service.start
end
def stop_service(name)
service = find_service(name)
debugger if service.nil? # Conditional breakpoint
service.cleanup
service.stop
end
end
# Breakpoint management pitfalls:
# (rdbg) break ServiceManager#start_service # Set breakpoint
# (rdbg) break 25 if name == 'critical' # Conditional breakpoint
#
# Problem: Breakpoints persist across sessions
# Solution: Always clean up breakpoints
# (rdbg) info break # List all active breakpoints
# (rdbg) delete 1 # Delete specific breakpoint
# (rdbg) delete # Delete all breakpoints
#
# Pitfall: Leaving debug breakpoints in production code
# Reality: debugger statements execute in all environments
Expression evaluation context issues occur when using eval
commands with complex expressions or when the evaluation context differs from expectations. Eval operates within the current binding, but method calls and constant resolution may behave unexpectedly.
module Analytics
THRESHOLD = 100
class Calculator
def initialize(multiplier)
@multiplier = multiplier
end
def process(value)
intermediate = value * @multiplier
debugger
result = apply_threshold(intermediate)
end
private
def apply_threshold(val)
val > THRESHOLD ? val : 0
end
end
end
# Expression evaluation pitfalls:
# (rdbg) eval intermediate # Works: local variable
# (rdbg) eval @multiplier # Works: instance variable
# (rdbg) eval THRESHOLD # ERROR: uninitialized constant
# (rdbg) eval Analytics::THRESHOLD # Works: fully qualified constant
# (rdbg) eval apply_threshold(50) # Works: private method call
# (rdbg) eval Calculator.new(2) # ERROR: uninitialized constant
# (rdbg) eval self.class.new(2) # Works: uses current context
#
# Pitfall: Assuming eval has global scope access
# Reality: eval operates within current method binding
Performance impact misconceptions lead to inappropriate use of debugging commands in performance-sensitive code or production environments. Debug operations have computational overhead, and some commands can significantly impact execution performance.
class HighPerformanceProcessor
def process_large_dataset(data)
data.each_with_index do |item, index|
# WRONG: Performance-killing debug usage
debugger if complex_condition?(item) # Evaluates condition every iteration
result = expensive_operation(item)
# WRONG: Expensive debug operations in tight loops
if Rails.env.development?
Debug.eval("expensive_debug_calculation(item)") # Runs every iteration
end
end
end
def better_debugging(data)
debug_enabled = Rails.env.development? && ENV['ENABLE_DEBUG']
data.each_with_index do |item, index|
# BETTER: Conditional debugging with pre-computed flags
if debug_enabled && index % 1000 == 0 # Sample debugging
debugger(do: 'info locals; continue') # Auto-continue
end
result = expensive_operation(item)
end
end
end
# Performance debugging best practices:
# (rdbg) disable # Temporarily disable all breakpoints
# (rdbg) enable # Re-enable breakpoints
# (rdbg) time # Monitor debug command execution time
Reference
Core Execution Control Commands
Command | Parameters | Returns | Description |
---|---|---|---|
step [count] |
count (Integer) |
nil |
Execute next line, stepping into method calls |
next [count] |
count (Integer) |
nil |
Execute next line in current method without stepping into calls |
continue |
None | nil |
Resume program execution until next breakpoint |
finish |
None | nil |
Execute until current method returns |
quit |
None | Exit | Terminate debugging session and program |
restart |
None | nil |
Restart the debugging session from beginning |
Breakpoint Management Commands
Command | Parameters | Returns | Description |
---|---|---|---|
break <location> |
location (String/Integer) |
nil |
Set breakpoint at line number or method |
break <location> if <condition> |
location , condition (String) |
nil |
Set conditional breakpoint |
delete [id] |
id (Integer, optional) |
nil |
Delete specific breakpoint or all breakpoints |
disable [id] |
id (Integer, optional) |
nil |
Disable specific breakpoint or all breakpoints |
enable [id] |
id (Integer, optional) |
nil |
Enable specific breakpoint or all breakpoints |
info break |
None | Array | List all breakpoints with status |
Program Inspection Commands
Command | Parameters | Returns | Description |
---|---|---|---|
list [start, end] |
start , end (Integer) |
nil |
Display source code around current line |
whereami |
None | nil |
Show current execution location with context |
backtrace [count] |
count (Integer, optional) |
nil |
Display call stack with line numbers |
up [count] |
count (Integer) |
nil |
Move up stack frames |
down [count] |
count (Integer) |
nil |
Move down stack frames |
frame <number> |
number (Integer) |
nil |
Jump to specific stack frame |
Variable and Context Inspection
Command | Parameters | Returns | Description |
---|---|---|---|
info locals |
None | Hash | Display local variables in current scope |
info instance |
None | Hash | Display instance variables |
info const |
None | Array | Display constants in current scope |
info exception |
None | Object | Display current exception details |
eval <expression> |
expression (String) |
Any | Evaluate Ruby expression in current context |
p <expression> |
expression (String) |
Any | Pretty-print expression result |
pp <expression> |
expression (String) |
Any | Pretty-print with formatting |
Advanced Debugging Commands
Command | Parameters | Returns | Description |
---|---|---|---|
display <expression> |
expression (String) |
nil |
Auto-display expression value at each stop |
undisplay [id] |
id (Integer, optional) |
nil |
Remove auto-display expression |
watch <expression> |
expression (String) |
nil |
Break when expression value changes |
catch <exception> |
exception (String) |
nil |
Break on specific exception type |
trace line |
None | nil |
Trace line execution |
trace method |
None | nil |
Trace method calls |
untrace |
None | nil |
Disable all tracing |
Session Management and Help
Command | Parameters | Returns | Description |
---|---|---|---|
help [command] |
command (String, optional) |
nil |
Display help for specific command or all commands |
show |
None | nil |
Display current debug session configuration |
set <option> <value> |
option , value (String) |
nil |
Configure debug session options |
source <filename> |
filename (String) |
nil |
Execute debug commands from file |
save <filename> |
filename (String) |
nil |
Save current session commands to file |
Breakpoint Location Formats
Format | Example | Description |
---|---|---|
Line number | 15 |
Break at line 15 in current file |
Method name | MyClass#method_name |
Break at instance method |
Class method | MyClass.class_method |
Break at class method |
File and line | lib/processor.rb:25 |
Break at specific file and line |
Module method | MyModule#method |
Break at module method |
Conditional Breakpoint Operators
Operator | Example | Description |
---|---|---|
== |
break 15 if count == 10 |
Equality comparison |
!= |
break method if value != nil |
Inequality comparison |
> , < , >= , <= |
break 20 if index > 100 |
Numerical comparisons |
&& , || |
break 25 if flag && count > 5 |
Logical operators |
=~ |
break 30 if name =~ /pattern/ |
Regular expression match |
Debug Session Configuration Options
Option | Values | Default | Description |
---|---|---|---|
autolist |
on , off |
on |
Automatically list code at breakpoints |
autoeval |
on , off |
off |
Automatically evaluate expressions |
listsize |
Integer | 10 |
Number of lines to display with list command |
width |
Integer | 80 |
Terminal width for output formatting |
prompt |
String | (rdbg) |
Debug prompt string |
Error Types and Exception Handling
Exception Class | Common Causes | Debug Strategy |
---|---|---|
NoMethodError |
Undefined method calls | Use info locals , check object type |
NameError |
Undefined variables/constants | Use info locals , info const |
ArgumentError |
Wrong number/type of arguments | Use backtrace , inspect parameters |
TypeError |
Type mismatch operations | Use eval to check object types |
StandardError |
General application errors | Use catch StandardError for broad catching |