Overview
Debug Gem provides Ruby's standard debugging interface, replacing the deprecated debugger and byebug gems as the official debugging solution. The gem integrates deeply with Ruby's virtual machine to offer breakpoint management, step-through debugging, variable inspection, and call stack navigation.
The core functionality centers around the Debugger
class and its associated command interface. When activated, Debug Gem pauses program execution at specified breakpoints and opens an interactive REPL session where developers can examine program state, modify variables, and control execution flow.
Debug Gem operates through several key mechanisms: breakpoint insertion via binding.break
, remote debugging capabilities through DAP (Debug Adapter Protocol), and integration with development servers. The gem supports both synchronous debugging where execution halts completely, and asynchronous debugging where multiple threads can be inspected simultaneously.
# Basic breakpoint insertion
def calculate_total(items)
binding.break # Execution pauses here
total = items.sum { |item| item.price }
total * 1.08 # Tax calculation
end
The debug session provides access to local variables, instance variables, and method definitions within the current scope. Developers can execute arbitrary Ruby code, call methods, and inspect object states without modifying the original program flow.
# Conditional breakpoint
def process_orders(orders)
orders.each do |order|
binding.break if order.total > 1000 # Only break for large orders
order.process
end
end
Debug Gem maintains compatibility with Ruby's standard REPL features while extending functionality for debugging-specific tasks. The command interface supports navigation commands, variable inspection, and execution control through a consistent syntax that integrates with existing Ruby development workflows.
Basic Usage
Debug Gem activation requires adding the gem to your project and inserting breakpoints using binding.break
. The most direct approach involves placing breakpoint calls at specific code locations where inspection is needed.
require 'debug'
class OrderProcessor
def initialize(orders)
@orders = orders
binding.break # Inspect initial state
end
def process_all
@orders.each_with_index do |order, index|
binding.break if index == 5 # Break on sixth order
process_single(order)
end
end
private
def process_single(order)
binding.break(pre: -> { order.status == 'pending' }) # Conditional break
order.calculate_shipping
order.apply_discounts
end
end
Once a breakpoint is hit, Debug Gem opens an interactive session with several navigation commands. The step
command advances execution by one line, while next
advances to the next line in the current method without descending into method calls. The continue
command resumes normal execution until the next breakpoint.
Variable inspection uses standard Ruby syntax within the debug session. Local variables, instance variables, and method calls execute within the current scope context. The info
command displays comprehensive information about the current execution state.
# In debug session:
# (rdbg) p order # Print order object
# (rdbg) p @orders.count # Print instance variable
# (rdbg) info locals # List all local variables
# (rdbg) step # Advance one line
# (rdbg) continue # Resume execution
Debug Gem supports remote debugging through the --open
flag, which starts a debug server accessible via TCP connection. This enables debugging applications running in containers, remote servers, or separate processes.
# Start with remote debugging
# ruby -r debug/open script.rb
# Connect from another terminal: rdbg --attach
class RemoteService
def start
binding.break # Will be accessible remotely
perform_complex_calculation
end
end
Method breakpoints allow setting breakpoints on specific methods without modifying source code. The break
command within a debug session can set breakpoints on method definitions, class instantiations, or exception conditions.
# Debug session commands:
# (rdbg) break User#save # Break when User#save is called
# (rdbg) break User.new # Break on User instantiation
# (rdbg) break rescue StandardError # Break on exception rescue
Debug Gem maintains a history of commands and supports readline functionality for command completion and editing. The history
command displays previous commands, while tab completion assists with variable names and method suggestions.
Advanced Usage
Debug Gem provides sophisticated breakpoint management through programmatic APIs and advanced command configurations. Custom breakpoint conditions can execute complex Ruby expressions to determine when execution should pause, enabling precise control over debugging scenarios.
require 'debug'
class AdvancedDebugger
def self.configure_conditional_breaks(processor)
# Set breakpoint with complex condition
processor.define_singleton_method(:debug_checkpoint) do |context|
condition = -> {
context[:user_id] > 1000 &&
context[:order_total] > 500.0 &&
Time.current.hour.between?(9, 17)
}
binding.break(pre: condition, do: -> { puts "High value order detected" })
end
end
def self.setup_method_tracing(target_class)
target_class.prepend(Module.new do
def method_added(name)
original_method = instance_method(name)
define_method(name) do |*args, &block|
binding.break(pre: -> { args.length > 3 }) # Break on complex calls
original_method.bind(self).call(*args, &block)
end
super
end
end)
end
end
Thread debugging requires careful consideration of concurrent execution patterns. Debug Gem can attach to specific threads and provides commands for switching between thread contexts during debugging sessions.
class ConcurrentProcessor
def initialize
@threads = []
@results = Concurrent::Hash.new
end
def process_parallel(data_chunks)
data_chunks.each_with_index do |chunk, index|
@threads << Thread.new do
Thread.current[:name] = "worker_#{index}"
binding.break(pre: -> { Thread.current[:name] == 'worker_3' })
result = process_chunk(chunk)
@results[index] = result
end
end
@threads.each(&:join)
end
private
def process_chunk(chunk)
# Complex processing that might need debugging
chunk.map { |item| transform_item(item) }.compact
end
end
Advanced breakpoint management includes watchpoints that trigger when variable values change, and tracepoints that monitor method calls, returns, and exceptions across the entire program execution.
# Debug session advanced commands:
# (rdbg) watch @user_count # Break when @user_count changes
# (rdbg) trace call User # Trace all User method calls
# (rdbg) catch ArgumentError # Break on ArgumentError
# (rdbg) eval Thread.list.map(&:name) # Execute complex expressions
Custom debug configurations can be saved in .rdbgrc
files to establish consistent debugging environments across projects. These configurations can define custom commands, set default breakpoints, and configure output formatting.
# .rdbgrc configuration file
config.no_color = false
config.use_colorize = true
config.show_src_lines = 10
config.show_frames = 5
# Custom command definition
config.command('show_request') do
puts "Request: #{request.inspect}"
puts "Params: #{params.inspect}"
puts "Session: #{session.keys}"
end
# Auto-break configuration
config.postmortem = true # Enter debugger on unhandled exceptions
Integration with development tools extends Debug Gem functionality through editor plugins and IDE extensions. The Debug Adapter Protocol (DAP) enables connection with VS Code, Vim, and other editors for graphical debugging interfaces.
# Launch with DAP support
# ruby -r debug/open_nonstop --dap-port=12345 app.rb
class DevelopmentIntegration
def self.setup_editor_debugging
if ENV['EDITOR_DEBUG']
require 'debug/open_nonstop'
DEBUGGER__.start_dap_server(host: '0.0.0.0', port: ENV['DAP_PORT'] || 12345)
end
end
def critical_method(data)
# Breakpoint will appear in editor
binding.break
process_data(data)
end
end
Production Patterns
Production debugging requires careful consideration of performance impact and security implications. Debug Gem provides mechanisms for enabling debugging features only in development environments while maintaining production safety.
class ProductionSafeDebugger
def self.configure(environment:, enabled_features: [])
return unless ['development', 'staging'].include?(environment)
if enabled_features.include?(:remote_debugging)
require 'debug/open_nonstop'
port = ENV.fetch('DEBUG_PORT', 12345).to_i
DEBUGGER__.start_dap_server(host: '127.0.0.1', port: port)
end
if enabled_features.include?(:conditional_breaks)
setup_production_breakpoints
end
end
private
def self.setup_production_breakpoints
# Only break on specific error conditions
TracePoint.new(:raise) do |tp|
if tp.raised_exception.is_a?(CustomBusinessError)
binding.break(pre: -> { ENV['ENABLE_ERROR_DEBUGGING'] == 'true' })
end
end.enable
end
end
Rails applications require special consideration for Debug Gem integration due to request-response cycles and multi-threaded server architectures. Debug Gem can be configured to work within Rails development servers while avoiding interference with normal request processing.
# config/environments/development.rb
Rails.application.configure do
# Enable debug gem for Rails development
if Rails.env.development?
config.after_initialize do
if ENV['RAILS_DEBUG_ENABLED']
require 'debug/open_nonstop'
DEBUGGER__.start_dap_server(host: '127.0.0.1', port: 12346)
end
end
end
end
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
before_action :debug_checkpoint, if: :debugging_enabled?
private
def debug_checkpoint
if params[:debug] && Rails.env.development?
binding.break(do: -> {
puts "Request: #{request.method} #{request.path}"
puts "User: #{current_user&.id}"
})
end
end
def debugging_enabled?
Rails.env.development? && ENV['CONTROLLER_DEBUG'] == 'true'
end
end
Container environments present unique challenges for Debug Gem usage. Docker configurations must expose debug ports and configure networking to allow external connections to debugging sessions.
# Dockerfile debugging configuration
# EXPOSE 12345
# ENV DEBUG_PORT=12345
class ContainerDebugSetup
def self.configure_for_container
if ENV['CONTAINER_DEBUG'] == 'true'
require 'debug/open'
# Bind to all interfaces in container
Thread.new do
begin
DEBUGGER__.start_dap_server(
host: '0.0.0.0',
port: ENV.fetch('DEBUG_PORT', 12345).to_i
)
rescue => e
puts "Debug server failed to start: #{e.message}"
end
end
end
end
end
Monitoring and logging integration ensures debug sessions are tracked and managed appropriately in production-like environments. Debug Gem can be configured to log debugging activity and integrate with existing monitoring systems.
class DebugMonitoring
def self.setup_logging(logger:)
DEBUGGER__.singleton_class.prepend(Module.new do
def start_session(*)
logger.info("Debug session started",
thread: Thread.current.object_id,
location: caller_locations(1, 1).first.to_s,
timestamp: Time.current.iso8601
)
super
end
def end_session(*)
logger.info("Debug session ended",
thread: Thread.current.object_id,
timestamp: Time.current.iso8601
)
super
end
end)
end
def self.setup_metrics(metrics_client:)
# Track debugging usage patterns
original_break = Kernel.instance_method(:binding)
Kernel.define_method(:binding) do
result = original_break.bind(self).call
if caller_locations.any? { |loc| loc.label == 'break' }
metrics_client.increment('debug.breakpoint_hit',
tags: { file: caller_locations.first.path })
end
result
end
end
end
Performance monitoring during debug sessions prevents debugging from significantly impacting application performance. Debug Gem can be configured with timeouts and resource limits to ensure debugging sessions don't consume excessive system resources.
class PerformanceAwareDebugger
def self.configure_limits
DEBUGGER__.configure do |config|
config.session_timeout = 300 # 5 minute timeout
config.max_memory_usage = 100 * 1024 * 1024 # 100MB limit
config.max_string_display = 1000 # Limit large string display
end
# Monitor debug session resource usage
Thread.new do
loop do
if DEBUGGER__.session_active?
memory_usage = `ps -o rss= -p #{Process.pid}`.to_i * 1024
if memory_usage > 500 * 1024 * 1024 # 500MB
DEBUGGER__.end_session("Memory limit exceeded")
end
end
sleep(10)
end
end
end
end
Common Pitfalls
Debug Gem usage involves several common mistakes that can lead to unexpected behavior or performance problems. The most frequent issue occurs when developers leave binding.break
statements in production code, causing applications to hang indefinitely when breakpoints are hit.
# PROBLEMATIC: Debug statement left in production code
class PaymentProcessor
def charge_card(amount)
binding.break # This will hang in production!
process_payment(amount)
end
end
# SOLUTION: Environment-conditional debugging
class PaymentProcessor
def charge_card(amount)
binding.break if Rails.env.development? # Safe for production
process_payment(amount)
end
# Better approach: Use custom debug method
def debug_break
binding.break if ENV['DEBUG_MODE'] == 'true'
end
def charge_card(amount)
debug_break
process_payment(amount)
end
end
Thread safety issues emerge when debugging multi-threaded applications. Debug Gem sessions can interfere with concurrent execution, causing race conditions or deadlocks that wouldn't occur during normal execution.
# PROBLEMATIC: Debugging in thread-sensitive code
class ThreadedProcessor
def initialize
@mutex = Mutex.new
@shared_resource = []
end
def add_item(item)
@mutex.synchronize do
binding.break # Dangerous: holds mutex during debug session
@shared_resource << item
end
end
end
# SOLUTION: Debug before critical sections
class ThreadedProcessor
def add_item(item)
binding.break if ENV['DEBUG_THREADS'] # Debug outside mutex
@mutex.synchronize do
@shared_resource << item
end
end
# Alternative: Use thread-specific debugging
def add_item_with_safe_debug(item)
if Thread.current[:debug_enabled]
item_copy = item.dup # Work with copy during debugging
binding.break(do: -> { puts "Processing: #{item_copy.inspect}" })
end
@mutex.synchronize do
@shared_resource << item
end
end
end
Variable modification during debug sessions can alter program behavior in unexpected ways. Changes made to objects during debugging persist after continuing execution, potentially masking bugs or creating new ones.
# PROBLEMATIC: Modifying state during debugging
class OrderCalculator
def calculate_total(items)
total = 0
items.each do |item|
binding.break
# In debug session: item.price = 999 # Accidentally modified!
total += item.price
end
total
end
end
# SOLUTION: Use inspection methods instead of modification
class OrderCalculator
def calculate_total(items)
total = 0
items.each_with_index do |item, index|
binding.break(do: -> {
puts "Item #{index}: #{item.inspect}"
puts "Current total: #{total}"
# Don't modify item directly - use inspection only
})
total += item.price
end
total
end
# Create debug-safe inspection methods
def debug_item_state(item, index, running_total)
{
index: index,
item_id: item.id,
price: item.price,
running_total: running_total,
item_copy: item.dup # Safe copy for inspection
}
end
end
Infinite loops and recursive breakpoints can occur when debug conditions create circular debugging scenarios. This happens when breakpoint conditions themselves trigger additional breakpoints or when debugging recursive methods without proper guards.
# PROBLEMATIC: Recursive debugging
def fibonacci(n)
binding.break # Breaks on every recursive call!
return n if n <= 1
fibonacci(n-1) + fibonacci(n-2)
end
# SOLUTION: Use depth-limited or conditional debugging
def fibonacci(n, debug_depth = 0)
# Only debug first few levels
binding.break if debug_depth < 3 && ENV['DEBUG_RECURSION']
return n if n <= 1
fibonacci(n-1, debug_depth + 1) + fibonacci(n-2, debug_depth + 1)
end
# Alternative: Debug specific conditions only
def fibonacci(n)
binding.break if n > 10 && n % 5 == 0 # Debug only specific values
return n if n <= 1
fibonacci(n-1) + fibonacci(n-2)
end
Performance degradation occurs when debug configurations remain enabled in production-like environments. Debug Gem maintains internal state and monitoring that consumes memory and CPU cycles even when breakpoints aren't hit.
# PROBLEMATIC: Always loading debug gem
require 'debug' # Loaded even in production
class Application
def start
setup_debugging # Always called
run_main_loop
end
end
# SOLUTION: Conditional loading and setup
class Application
def start
setup_debugging if debug_mode_enabled?
run_main_loop
end
private
def debug_mode_enabled?
ENV['RAILS_ENV'] == 'development' || ENV['DEBUG_MODE'] == 'true'
end
def setup_debugging
require 'debug' unless defined?(DEBUGGER__)
configure_debug_environment
end
def configure_debug_environment
return unless debug_mode_enabled?
DEBUGGER__.configure do |config|
config.skip_path = [/gems/] # Skip gem code
config.show_src_lines = 5 # Limit output
end
end
end
Remote debugging security issues arise when debug servers are exposed without proper authentication or network restrictions. Debug sessions provide full Ruby execution access, making them significant security risks if improperly configured.
# PROBLEMATIC: Unsecured remote debugging
DEBUGGER__.start_dap_server(host: '0.0.0.0', port: 12345) # Exposed to all!
# SOLUTION: Secure remote debugging setup
class SecureDebugServer
def self.start_if_authorized
return unless authorized_debug_environment?
host = ENV.fetch('DEBUG_HOST', '127.0.0.1') # Default to localhost
port = ENV.fetch('DEBUG_PORT', 12345).to_i
# Add authentication if needed
if ENV['DEBUG_AUTH_TOKEN']
setup_authenticated_debugging(host, port)
else
DEBUGGER__.start_dap_server(host: host, port: port)
end
end
private
def self.authorized_debug_environment?
allowed_environments = ['development', 'test']
current_env = ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
allowed_environments.include?(current_env) &&
ENV['DEBUG_ENABLED'] == 'true'
end
def self.setup_authenticated_debugging(host, port)
# Implement token-based authentication for remote debugging
expected_token = ENV['DEBUG_AUTH_TOKEN']
# Custom debug server with authentication
require 'socket'
server = TCPServer.new(host, port)
Thread.new do
loop do
client = server.accept
handle_authenticated_client(client, expected_token)
end
end
end
end
Reference
Core Methods
Method | Parameters | Returns | Description |
---|---|---|---|
binding.break |
pre: Proc , do: Proc , path: String |
nil |
Sets breakpoint at current location with optional conditions |
DEBUGGER__.start |
host: String , port: Integer |
void |
Starts debug server with network configuration |
DEBUGGER__.configure |
block |
void |
Configures global debug settings |
DEBUGGER__.stop |
none | void |
Stops debug server and closes sessions |
Debug Session Commands
Command | Syntax | Description |
---|---|---|
step |
s [count] |
Execute next line, entering method calls |
next |
n [count] |
Execute next line in current method |
continue |
c |
Resume execution until next breakpoint |
finish |
fin |
Execute until current method returns |
break |
b <location> |
Set breakpoint at specified location |
delete |
del <number> |
Delete breakpoint by number |
info |
i <type> |
Display information (locals, ivars, methods) |
eval |
e <code> |
Evaluate Ruby expression in current context |
watch |
w <variable> |
Set watchpoint on variable |
trace |
trace <type> |
Enable method or line tracing |
thread |
th [list|switch] |
List or switch between threads |
backtrace |
bt [count] |
Display call stack |
help |
h [command] |
Display help information |
Breakpoint Locations
Location Type | Syntax | Example |
---|---|---|
Method | ClassName#method_name |
User#save |
Class Method | ClassName.method_name |
User.find |
Line Number | filename:line |
app.rb:42 |
Exception | rescue ExceptionClass |
rescue ArgumentError |
Configuration Options
Option | Type | Default | Description |
---|---|---|---|
no_color |
Boolean |
false |
Disable colored output |
use_colorize |
Boolean |
true |
Enable syntax highlighting |
show_src_lines |
Integer |
10 |
Lines of source code to display |
show_frames |
Integer |
2 |
Number of stack frames to show |
skip_path |
Array |
[] |
Paths to skip during debugging |
postmortem |
Boolean |
false |
Enter debugger on unhandled exceptions |
session_timeout |
Integer |
300 |
Debug session timeout in seconds |
Environment Variables
Variable | Purpose | Example |
---|---|---|
DEBUG_MODE |
Enable/disable debugging | DEBUG_MODE=true |
DEBUG_PORT |
Set debug server port | DEBUG_PORT=12345 |
DEBUG_HOST |
Set debug server host | DEBUG_HOST=0.0.0.0 |
RUBY_DEBUG_NO_COLOR |
Disable colored output | RUBY_DEBUG_NO_COLOR=1 |
RUBY_DEBUG_SHOW_SRC_LINES |
Source lines to display | RUBY_DEBUG_SHOW_SRC_LINES=5 |
Exception Classes
Exception | Description |
---|---|
DEBUGGER__::Error |
Base debug error class |
DEBUGGER__::ConfigurationError |
Invalid configuration |
DEBUGGER__::ConnectionError |
Remote debugging connection failed |
DEBUGGER__::SessionError |
Debug session error |
Integration Patterns
Pattern | Use Case | Configuration |
---|---|---|
Rails Development | Web application debugging | Add to Gemfile development group |
Remote Debugging | Container/server debugging | Start with --open flag |
DAP Integration | Editor/IDE debugging | Configure DAP server port |
Test Suite Debugging | RSpec/Test debugging | Conditional breakpoints in tests |
Command Line Options
Option | Description |
---|---|
-r debug/open |
Start with remote debugging enabled |
--dap-port=PORT |
Specify DAP server port |
--no-color |
Disable colored output |
--show-src-lines=N |
Set source line display count |