Overview
Byebug provides interactive debugging capabilities for Ruby programs through breakpoints, step execution, and runtime inspection. The gem implements a command-line debugger that integrates directly into Ruby code execution, allowing developers to pause program flow and examine application state at specific points.
The debugger operates through the byebug
method call, which creates breakpoints where execution stops. When a breakpoint triggers, byebug opens an interactive prompt that accepts debugging commands for navigation, inspection, and code evaluation. The debugger maintains a call stack context, variable scope access, and step-by-step execution control.
require 'byebug'
def calculate_total(items)
byebug # Execution stops here
total = 0
items.each do |item|
total += item[:price] * item[:quantity]
end
total
end
items = [
{ name: 'Widget', price: 10.99, quantity: 2 },
{ name: 'Gadget', price: 25.50, quantity: 1 }
]
result = calculate_total(items)
Byebug supports remote debugging through TCP connections, conditional breakpoints based on expressions, and integration with popular editors and IDEs. The debugger works with standard Ruby programs, Rails applications, and test suites without requiring application modifications beyond adding breakpoint calls.
The gem provides thread-aware debugging for concurrent applications and maintains separate debugging sessions for each thread. Remote debugging enables debugging applications running in different processes or on remote servers through network connections.
Basic Usage
Installing byebug requires adding the gem to your application's dependencies and calling the byebug
method where debugging should begin. The debugger provides immediate access to the current execution context, including local variables, method parameters, and instance variables.
require 'byebug'
class OrderProcessor
def initialize(orders)
@orders = orders
@processed_count = 0
end
def process_all
@orders.each do |order|
byebug # Interactive debugging starts
process_single_order(order)
@processed_count += 1
end
end
private
def process_single_order(order)
order[:status] = 'processed'
order[:processed_at] = Time.now
end
end
When execution reaches a byebug
call, the debugger displays the current line and opens an interactive prompt. The list
command shows surrounding code context, while next
executes the following line. The step
command enters method calls, and continue
resumes normal execution until the next breakpoint.
Variable inspection uses the p
command followed by variable names or expressions. The debugger evaluates Ruby expressions in the current context, providing access to local variables, instance variables, and method calls:
def complex_calculation(data, options = {})
multiplier = options[:multiplier] || 1.5
threshold = options[:threshold] || 100
byebug
# At debug prompt:
# p data # Shows data contents
# p multiplier # Shows 1.5
# p threshold # Shows 100
# data.size # Evaluates expression
results = data.map { |item| item * multiplier }
filtered = results.select { |value| value > threshold }
filtered.sum
end
The backtrace
command displays the call stack, showing method calls leading to the current breakpoint. The up
and down
commands navigate between stack frames, changing the inspection context to different method calls in the execution chain.
Breaking out of the debugger requires the quit
command, which terminates the debugging session and continues program execution. The finish
command executes until the current method returns, while continue
runs until the next breakpoint.
Setting breakpoints programmatically uses the break
command with file names and line numbers. The debugger maintains breakpoint lists and provides commands for enabling, disabling, and removing breakpoints during debugging sessions.
Advanced Usage
Byebug supports conditional breakpoints that trigger only when specified expressions evaluate to true. The break
command accepts Ruby expressions as conditions, enabling targeted debugging based on variable values, object states, or complex logical conditions.
class DataProcessor
def process_batch(records)
records.each_with_index do |record, index|
# Conditional breakpoint: stop only for problematic records
byebug if record[:error_count] > 5 || record[:status] == 'failed'
result = transform_record(record)
validate_result(result)
save_record(result)
end
end
def transform_record(record)
# Complex transformation logic
transformed = record.dup
transformed[:processed_at] = Time.now
transformed[:hash] = calculate_hash(record)
transformed
end
end
Remote debugging enables debugging applications running on different machines or in containerized environments. The byebug
gem provides server and client modes for remote debugging sessions:
require 'byebug/core'
# Start remote debugging server
Byebug.start_server('localhost', 8989)
class RemoteService
def perform_operation
byebug # Triggers remote debugger
complex_operation
end
end
# Connect from remote client:
# byebug -R localhost:8989
Thread-aware debugging handles concurrent applications by maintaining separate debugging contexts for each thread. The debugger can switch between threads during debugging sessions, inspect thread-specific variables, and set thread-specific breakpoints:
require 'byebug'
class ConcurrentProcessor
def initialize
@results = Queue.new
@workers = []
end
def start_workers(work_items)
work_items.each_slice(10) do |chunk|
@workers << Thread.new(chunk) do |items|
Thread.current[:name] = "Worker-#{Thread.current.object_id}"
items.each do |item|
byebug # Thread-specific debugging
result = process_item(item)
@results << result
end
end
end
@workers.each(&:join)
end
end
The debugger provides expression evaluation with full Ruby syntax support, including method calls, variable assignments, and object creation. This capability enables testing fixes and exploring alternative implementations during debugging sessions:
def analyze_performance(dataset)
start_time = Time.now
byebug
# At debug prompt, test different approaches:
# dataset.select { |x| x > 100 }.size
# dataset.count { |x| x > 100 }
# benchmark_approach(dataset)
filtered_data = dataset.select { |item| item[:score] > threshold }
processed_data = filtered_data.map { |item| transform_item(item) }
end_time = Time.now
{ data: processed_data, duration: end_time - start_time }
end
Post-mortem debugging captures debugging sessions from crashed applications or failed operations. The debugger can attach to existing processes or analyze crash dumps for debugging applications that have already terminated.
Error Handling & Debugging
Byebug integrates with Ruby's exception handling system to provide debugging capabilities when errors occur. The debugger can catch exceptions before they propagate, examine error contexts, and provide detailed inspection of failure states.
Setting breakpoints on exception raises enables debugging at the moment errors occur, before stack unwinding begins. This approach provides access to the exact variable states and execution context when problems first manifest:
require 'byebug'
class PaymentProcessor
def process_payment(payment_data)
begin
validate_payment_data(payment_data)
charge_result = charge_payment(payment_data)
update_payment_record(charge_result)
rescue => error
byebug # Debug on any exception
# Inspect error context:
# p error.message
# p error.backtrace
# p payment_data
# p charge_result if defined?(charge_result)
handle_payment_error(error, payment_data)
end
end
private
def validate_payment_data(data)
raise ArgumentError, "Missing amount" unless data[:amount]
raise ArgumentError, "Invalid amount" if data[:amount] <= 0
raise ArgumentError, "Missing payment method" unless data[:payment_method]
end
end
Debugging failed assertions and test cases requires strategic breakpoint placement before assertion calls. The debugger enables inspection of actual versus expected values and examination of object states leading to test failures:
class UserRegistrationTest
def test_user_creation_with_invalid_email
user_params = {
name: 'John Doe',
email: 'invalid-email',
password: 'secure123'
}
user = User.new(user_params)
byebug # Debug before assertion
# Inspect validation state:
# p user.valid?
# p user.errors.full_messages
# user.errors.details
assert_not user.valid?, "User should be invalid with malformed email"
assert_includes user.errors[:email], "is not a valid email address"
end
end
Debugging complex object hierarchies and nested data structures requires systematic inspection techniques. The debugger provides methods for exploring object relationships and identifying data inconsistencies:
class OrderAnalyzer
def analyze_order_discrepancies(order)
byebug
# Debug complex nested structure:
# pp order # Pretty print entire structure
# order.line_items.each_with_index { |item, i| puts "#{i}: #{item.inspect}" }
# order.line_items.map(&:total).sum == order.subtotal
line_item_total = order.line_items.sum(&:total)
calculated_tax = calculate_tax(line_item_total, order.tax_rate)
expected_total = line_item_total + calculated_tax + order.shipping_cost
if expected_total != order.total
byebug # Debug discrepancy
# p line_item_total
# p calculated_tax
# p order.shipping_cost
# p expected_total
# p order.total
end
{ discrepancy: expected_total - order.total }
end
end
Memory debugging identifies memory leaks and excessive object creation through debugging session inspection. The debugger can examine object references, garbage collection states, and memory allocation patterns during execution.
Integration with logging systems enables debugging based on log patterns and error frequencies. The debugger can trigger based on specific log events or error threshold conditions, providing targeted debugging for production issues.
Production Patterns
Production debugging with byebug requires careful consideration of performance impacts and security implications. Remote debugging provides the safest approach for production environments, avoiding direct code modifications in production deployments.
Remote debugging servers enable debugging production applications without embedding breakpoints in production code. The debugging server runs as a separate process and connects to the application through inter-process communication:
# In production application initialization
if ENV['DEBUG_MODE'] == 'enabled'
require 'byebug/core'
Byebug.start_server(ENV['DEBUG_HOST'] || 'localhost',
ENV['DEBUG_PORT']&.to_i || 8989)
end
class ProductionService
def critical_operation(data)
# No embedded byebug calls in production
result = perform_complex_calculation(data)
validate_production_result(result)
result
end
def debug_hook
# Optional debug entry point for remote debugging
byebug if ENV['DEBUG_MODE'] == 'enabled'
end
end
Conditional debugging based on environment variables and feature flags enables debugging capabilities without permanent code changes. This approach maintains clean production code while providing debugging access when needed:
class FeatureDebugger
DEBUGGING_ENABLED = ENV['FEATURE_DEBUG'] == 'true'
DEBUG_FEATURES = (ENV['DEBUG_FEATURES'] || '').split(',')
def self.debug_if_enabled(feature_name)
return unless DEBUGGING_ENABLED
return unless DEBUG_FEATURES.include?(feature_name)
byebug
end
end
class PaymentGateway
def process_transaction(transaction)
FeatureDebugger.debug_if_enabled('payments')
gateway_response = send_to_gateway(transaction)
process_gateway_response(gateway_response)
end
end
Rails integration enables debugging within request-response cycles while maintaining application performance. The debugger integrates with Rails middleware stack and provides debugging capabilities for controller actions, model operations, and view rendering:
class ApplicationController < ActionController::Base
before_action :debug_hook, if: -> { debug_mode_enabled? }
private
def debug_hook
# Debug specific controllers or actions
if params[:controller] == 'orders' && params[:action] == 'create'
byebug
end
end
def debug_mode_enabled?
Rails.env.development? &&
session[:debug_enabled] &&
current_user&.admin?
end
end
class OrdersController < ApplicationController
def create
@order = Order.new(order_params)
# Conditional debugging for order creation issues
if @order.total > 10000 && Rails.env.production?
byebug # Debug high-value orders in production
end
if @order.save
redirect_to order_path(@order)
else
render :new
end
end
end
Background job debugging requires integration with job processing frameworks and queue systems. The debugger can pause job execution for inspection of job parameters, processing contexts, and error states:
class DebuggableJob
include Sidekiq::Worker
def perform(job_data)
if debugging_enabled_for_job?(job_data)
byebug
end
process_job_data(job_data)
rescue => error
if critical_error?(error)
byebug # Debug critical job failures
end
raise error
end
private
def debugging_enabled_for_job?(data)
data['debug'] == true ||
data['user_id'].in?(debug_user_ids)
end
end
Common Pitfalls
Byebug sessions can inadvertently modify application state through expression evaluation and variable assignments during debugging. The debugger executes Ruby code in the current context, making permanent changes to objects and variables if expressions contain side effects.
class StateModificationExample
def initialize
@counter = 0
@items = []
end
def process_items(new_items)
byebug
# Dangerous debug commands that modify state:
# @counter += 1 # Permanently increases counter
# @items << 'debug_item' # Adds debug item to real data
# new_items.clear # Destroys input data
# Safe alternatives:
# @counter # Inspect without modifying
# @items.dup << 'debug' # Test changes on copy
# new_items.size # Check data without modifying
@items.concat(new_items)
@counter += new_items.size
end
end
Thread-related debugging issues occur when breakpoints pause one thread while other threads continue execution. This situation can lead to race conditions, deadlocks, and inconsistent debugging results in concurrent applications:
class ConcurrentDebuggingProblem
def initialize
@shared_resource = 0
@mutex = Mutex.new
end
def worker_method(thread_id)
@mutex.synchronize do
byebug # Pausing here blocks other threads
# Other threads wait for mutex while debugging
@shared_resource += 1
puts "Thread #{thread_id}: #{@shared_resource}"
end
end
# Better approach: conditional debugging
def safer_worker_method(thread_id)
@mutex.synchronize do
# Only debug specific threads or conditions
byebug if thread_id == 'debug_thread' && debugging_enabled?
@shared_resource += 1
puts "Thread #{thread_id}: #{@shared_resource}"
end
end
end
Production debugging pitfalls include leaving debugging statements in deployed code, which can halt production processes when breakpoints trigger. Remote users or automated processes cannot interact with debugging prompts, causing application hang-ups:
class ProductionPitfall
def user_registration(params)
user = User.new(params)
# DANGEROUS: This will hang production if triggered
byebug if user.email.blank?
# SAFER: Conditional debugging with safeguards
if user.email.blank? && debug_mode_safe?
byebug
end
user.save!
end
private
def debug_mode_safe?
Rails.env.development? ||
(ENV['PRODUCTION_DEBUG'] == 'true' && interactive_session?)
end
def interactive_session?
# Check if running in interactive environment
$stdin.tty? && $stdout.tty?
end
end
Performance implications include significant execution slowdowns when breakpoints trigger frequently within loops or high-traffic code paths. Each debugging session adds substantial overhead compared to normal execution:
class PerformancePitfall
def process_large_dataset(records)
records.each do |record|
# PROBLEMATIC: Creates thousands of debug sessions
byebug if record[:flag]
transform_record(record)
end
end
# Better approach: Sample debugging
def optimized_processing(records)
debug_sample_rate = 0.01 # Debug 1% of records
records.each_with_index do |record, index|
should_debug = record[:flag] &&
(rand < debug_sample_rate || index < 10)
byebug if should_debug
transform_record(record)
end
end
end
Variable scope confusion occurs when debugging in complex method contexts with multiple levels of local variables, block parameters, and closure variables. The debugger reflects the exact scope at the breakpoint location, which may not include all expected variables:
class ScopeConfusion
def complex_method(input)
outer_var = "outer"
result = input.map do |item|
inner_var = "inner"
item.transform do |element|
byebug # Scope includes: element, inner_var, outer_var, input
# But not variables from other closures or methods
element.process
end
end
final_var = "final"
byebug # Scope includes: final_var, result, outer_var, input
# But not inner_var or element
result
end
end
Reference
Core Methods
Method | Parameters | Returns | Description |
---|---|---|---|
byebug |
None | nil |
Creates breakpoint at current line |
debugger |
None | nil |
Alias for byebug method |
remote_byebug(host, port) |
host (String), port (Integer) |
nil |
Connects to remote debugging server |
Interactive Commands
Command | Syntax | Description |
---|---|---|
next |
n [lines] |
Execute next line(s) without entering methods |
step |
s [lines] |
Step into method calls |
continue |
c |
Resume execution until next breakpoint |
finish |
f |
Execute until current method returns |
up |
up [frames] |
Move up call stack frames |
down |
down [frames] |
Move down call stack frames |
list |
l [start,end] |
Show source code around current line |
print |
p <expression> |
Evaluate and display expression |
pp |
pp <expression> |
Pretty print expression |
backtrace |
bt |
Show call stack |
break |
b <file:line> |
Set breakpoint at specified location |
delete |
del <number> |
Remove breakpoint by number |
quit |
q |
Exit debugger and terminate program |
Breakpoint Management
Command | Syntax | Description |
---|---|---|
break |
break <file>:<line> |
Set breakpoint at file and line |
break |
break <method> |
Set breakpoint at method definition |
condition |
condition <n> <expr> |
Add condition to breakpoint n |
enable |
enable <numbers> |
Enable specified breakpoints |
disable |
disable <numbers> |
Disable specified breakpoints |
info breakpoints |
info breakpoints |
List all breakpoints |
Variable Inspection
Command | Syntax | Description |
---|---|---|
var local |
var local |
Show local variables |
var instance |
var instance |
Show instance variables |
var global |
var global |
Show global variables |
var class |
var class |
Show class variables |
var const |
var const |
Show constants |
display |
display <expr> |
Auto-display expression at each stop |
undisplay |
undisplay <number> |
Remove auto-display |
Thread Commands
Command | Syntax | Description |
---|---|---|
thread list |
thread list |
Show all threads |
thread switch |
thread switch <n> |
Switch to thread n |
thread stop |
thread stop <n> |
Stop thread n |
thread resume |
thread resume <n> |
Resume thread n |
Configuration Options
Setting | Values | Default | Description |
---|---|---|---|
autolist |
true , false |
true |
Auto-display code listings |
autoirb |
true , false |
false |
Auto-start IRB at breakpoints |
autopry |
true , false |
false |
Auto-start Pry at breakpoints |
callstyle |
short , long |
short |
Call stack display format |
histfile |
String | ~/.byebug_history |
Command history file location |
histsize |
Integer | 256 |
Maximum history entries |
linetrace |
true , false |
false |
Trace line execution |
savefile |
String | ~/.byebug_save |
Saved commands file |
width |
Integer | 80 |
Display width for listings |
Remote Debugging
Method | Parameters | Description |
---|---|---|
Byebug.start_server |
host, port, **opts |
Start remote debugging server |
Byebug.client |
host, port |
Connect to remote debugging server |
Byebug.stop_server |
None | Stop remote debugging server |
Environment Variables
Variable | Values | Description |
---|---|---|
BYEBUG_RC |
File path | Custom configuration file location |
BYEBUG_HISTORY |
File path | Command history file location |
BYEBUG_OPTS |
Options string | Default command-line options |
Common Patterns
Conditional breakpoints:
byebug if condition_met?
break method_name if variable > threshold
Exception debugging:
rescue => e
byebug
raise e
end
Remote debugging setup:
Byebug.start_server('0.0.0.0', 8989)
# Connect with: byebug -R host:port