Overview
Tracer provides comprehensive execution tracing capabilities for Ruby programs. The module captures and reports detailed information about program execution, including line-by-line code execution, method calls, returns, class definitions, and exception handling. Ruby implements tracing through event hooks that monitor the interpreter's execution state.
The primary class Tracer
offers both global and instance-based tracing modes. Global tracing affects the entire Ruby process, while instance tracing provides more targeted monitoring. The tracer captures events like line execution (line
), method calls (call
), method returns (return
), class definitions (class
), and exceptions (raise
).
Ruby's tracer integrates with the interpreter's event system using set_trace_func
internally. Each traced event generates output showing the filename, line number, event type, and relevant context information. The output format includes execution flow indicators and indentation to represent call depth.
require 'tracer'
Tracer.on
puts "Hello World"
def example_method
x = 42
x * 2
end
example_method
Tracer.off
The tracer captures method boundaries, variable assignments, and expression evaluations. Event filtering controls which types of execution events generate output. The module supports both immediate output and programmatic event handling through custom event handlers.
require 'tracer'
tracer = Tracer.new
tracer.set_get_line_procs("script.rb", proc { |line|
File.readlines("script.rb")[line - 1]
})
tracer.on
load "script.rb"
tracer.off
Basic Usage
Enable global tracing with Tracer.on
and disable with Tracer.off
. Global tracing affects all subsequent Ruby code execution until explicitly disabled. The tracer outputs each execution event to STDOUT with file location, line number, and event type.
require 'tracer'
Tracer.on
def calculate_sum(a, b)
result = a + b
puts "Sum: #{result}"
result
end
total = calculate_sum(10, 15)
Tracer.off
Block-based tracing provides automatic cleanup and scope control. The trace
method enables tracing only within the block scope, automatically disabling when the block completes. This approach prevents accidental long-running traces.
require 'tracer'
Tracer.trace do
numbers = [1, 2, 3, 4, 5]
sum = numbers.reduce(:+)
average = sum.to_f / numbers.length
puts "Average: #{average}"
end
Instance-based tracing creates isolated tracer objects with independent configuration. Multiple tracer instances operate simultaneously without interference. Each instance maintains separate output destinations and filtering rules.
require 'tracer'
tracer = Tracer.new($stdout)
tracer.on
class Calculator
def multiply(x, y)
x * y
end
end
calc = Calculator.new
result = calc.multiply(6, 7)
tracer.off
Output filtering controls event types and source files. The set_get_line_procs
method registers line retrieval procedures for specific files. Custom line processors enable source code display alongside execution traces.
require 'tracer'
tracer = Tracer.new
tracer.set_get_line_procs(__FILE__, proc { |line|
File.readlines(__FILE__)[line - 1].chomp rescue ""
})
tracer.trace do
array = %w[apple banana cherry]
array.each_with_index do |fruit, index|
puts "#{index}: #{fruit}"
end
end
Error Handling & Debugging
Exception tracing captures both raised and rescued exceptions. The tracer records exception events with class names, messages, and stack context. Exception events appear as raise
type in trace output, showing where exceptions originate and propagate through the call stack.
require 'tracer'
Tracer.on
begin
def risky_operation
raise StandardError, "Something went wrong"
end
risky_operation
rescue StandardError => e
puts "Caught: #{e.message}"
end
Tracer.off
Method call debugging reveals parameter passing and return values. The tracer shows method entry (call
events) and exit (return
events) with calling context. Call depth indentation visualizes nested method invocations and recursive calls.
Trace output filtering prevents overwhelming output volumes during debugging sessions. File-based filtering restricts tracing to specific source files, excluding standard library and gem code. Custom filtering logic can focus on particular classes or method patterns.
require 'tracer'
class DebuggingTarget
def complex_method(data)
processed = data.map { |item| transform_item(item) }
filtered = processed.select { |item| item[:valid] }
filtered.sort_by { |item| item[:priority] }
end
private
def transform_item(item)
{ value: item.upcase, valid: item.length > 3, priority: item.length }
end
end
# Trace only the specific file
tracer = Tracer.new
tracer.set_get_line_procs(__FILE__, proc { |line|
source_lines = File.readlines(__FILE__)
line <= source_lines.length ? source_lines[line - 1].chomp : ""
})
target = DebuggingTarget.new
tracer.trace do
result = target.complex_method(['cat', 'elephant', 'dog', 'butterfly'])
end
Stack overflow detection becomes easier with tracer output showing recursive call patterns. Deep recursion appears as increasing indentation levels with repeated method calls. The tracer helps identify infinite recursion before stack exhaustion occurs.
Conditional tracing activates only when specific conditions occur. Custom event handlers can examine execution state and enable tracing when particular variables reach certain values or when specific code paths execute.
Performance & Memory
Tracing overhead significantly impacts execution performance, typically adding 10-50x slowdown depending on trace frequency and output volume. Each traced event requires string formatting, I/O operations, and internal bookkeeping. Production systems should never run with tracing enabled continuously.
Memory consumption increases substantially during tracing due to output buffering and internal state tracking. Long-running traces can accumulate megabytes of output data. The tracer maintains internal data structures for each active trace context.
require 'tracer'
require 'benchmark'
def test_operation
1000.times do |i|
array = (1..100).to_a
array.map! { |x| x * 2 }
array.select { |x| x.even? }
end
end
# Measure without tracing
normal_time = Benchmark.realtime { test_operation }
# Measure with tracing to null device
null_output = File.open('/dev/null', 'w')
traced_time = Benchmark.realtime do
tracer = Tracer.new(null_output)
tracer.trace { test_operation }
null_output.close
end
puts "Normal execution: #{normal_time.round(4)}s"
puts "Traced execution: #{traced_time.round(4)}s"
puts "Overhead factor: #{(traced_time / normal_time).round(1)}x"
Output volume control prevents disk space exhaustion and reduces I/O bottlenecks. Redirecting trace output to /dev/null
eliminates file system overhead while maintaining event processing costs. Custom output handlers can implement sampling or filtering to reduce volume.
Selective tracing minimizes performance impact by focusing on specific code regions. Short-duration traces around suspected problem areas provide debugging information without sustained performance degradation. Block-based tracing automatically limits scope and duration.
require 'tracer'
class PerformanceCritical
def hot_path(data)
# Disable tracing in performance-critical sections
Tracer.off if defined?(Tracer) && Tracer.respond_to?(:off)
result = data.map { |x| expensive_calculation(x) }
# Re-enable for debugging specific sections
Tracer.trace do
validate_results(result) if $DEBUG
end
result
end
private
def expensive_calculation(value)
(1..1000).reduce(value) { |acc, n| acc + Math.sqrt(n) }
end
def validate_results(results)
results.each_with_index do |result, index|
raise "Invalid result at #{index}" if result.nan?
end
end
end
Production Patterns
Environment-based trace activation prevents accidental production tracing. Environment variables or configuration flags control tracer availability. Production deployments should exclude tracer initialization unless explicitly required for debugging.
require 'tracer' if ENV['RUBY_TRACE_ENABLED']
class ApplicationService
def self.with_tracing(enabled: ENV['DEBUG_TRACE'])
if enabled && defined?(Tracer)
Tracer.trace { yield }
else
yield
end
end
def process_request(request)
ApplicationService.with_tracing do
validate_request(request)
result = perform_business_logic(request)
format_response(result)
end
end
private
def validate_request(request)
raise ArgumentError, "Invalid request" unless request.respond_to?(:valid?)
raise ArgumentError, "Request validation failed" unless request.valid?
end
def perform_business_logic(request)
# Complex business operations
{ status: 'success', data: request.data.transform_values(&:upcase) }
end
def format_response(result)
{
timestamp: Time.now.iso8601,
result: result,
trace_enabled: defined?(Tracer) ? true : false
}
end
end
Log integration combines tracing with structured logging systems. Custom trace handlers can forward events to logging frameworks, enabling centralized debugging information. Trace events become part of the application's observability infrastructure.
Remote debugging capabilities enable trace activation through runtime signals or API endpoints. Production applications can expose controlled tracing interfaces for emergency debugging. Time-limited tracing prevents accidental long-running traces in production.
require 'tracer' if ENV['RACK_ENV'] == 'development'
require 'logger'
class ProductionTracer
def initialize(logger = Logger.new($stdout))
@logger = logger
@active = false
@timeout = nil
end
def start_trace(duration: 30)
return false unless defined?(Tracer)
return false if @active
@active = true
@timeout = Time.now + duration
@tracer = Tracer.new(StringIO.new)
@tracer.on
@logger.info("Tracing started for #{duration} seconds")
Thread.new do
sleep(duration)
stop_trace
end
true
end
def stop_trace
return unless @active && defined?(Tracer)
@tracer.off
trace_output = @tracer.instance_variable_get(:@out).string
@logger.info("Trace output length: #{trace_output.length} characters")
@logger.debug("Trace content: #{trace_output[0, 1000]}...")
@active = false
@timeout = nil
trace_output
end
def active?
@active && (@timeout.nil? || Time.now < @timeout)
end
end
Framework integration requires careful consideration of middleware positioning and request lifecycle. Rails applications can implement trace activation through parameter flags or admin interfaces. Trace data should be accessible through development tools and logging interfaces.
Common Pitfalls
Infinite trace loops occur when traced code generates its own tracing output. Output operations within trace handlers can trigger additional trace events, creating recursive tracing cycles. Custom output handlers must avoid operations that generate traceable events.
require 'tracer'
# Problematic: trace handler that generates more traces
class BadTraceHandler
def initialize
@output = File.open('trace.log', 'w')
end
def handle_event(event, file, line, id, binding, class_name)
# This puts call generates more trace events!
puts "Event: #{event} at #{file}:#{line}" # AVOID THIS
@output.puts "#{Time.now}: #{event}" # This is safer
@output.flush
end
end
# Correct approach: minimal operations in trace handlers
class SafeTraceHandler
def initialize
@output = $stderr # Use existing stream
@count = 0
end
def handle_event(event, file, line, id, binding, class_name)
@count += 1
@output.write("#{event[0]}") if @count % 100 == 0 # Sample output
end
end
Output volume explosion happens with high-frequency operations like loops and iterators. Mathematical calculations, string operations, and array processing generate excessive trace events. File I/O becomes a bottleneck when trace output exceeds system capacity.
Global tracer state conflicts arise when multiple parts of an application attempt to control tracing independently. Libraries that use internal tracing can interfere with application-level debugging. The global nature of Tracer.on
and Tracer.off
creates coordination challenges.
require 'tracer'
module SafeTracing
@trace_stack = []
def self.with_isolated_trace(output = $stdout)
# Save current trace state
was_tracing = Tracer.respond_to?(:tracing?) ? Tracer.tracing? : false
if was_tracing
Tracer.off
@trace_stack.push(:was_tracing)
end
tracer = Tracer.new(output)
tracer.on
begin
yield
ensure
tracer.off
# Restore previous trace state
if @trace_stack.pop == :was_tracing
Tracer.on
end
end
end
end
# Usage prevents state conflicts
SafeTracing.with_isolated_trace do
# This tracing won't interfere with other code
complex_operation
end
Thread safety issues emerge when tracing multi-threaded applications. The global tracer state affects all threads simultaneously. Race conditions can occur when multiple threads attempt to enable or disable tracing concurrently. Thread-local tracing requires careful implementation.
File handle leaks occur when tracer instances are not properly cleaned up. Each tracer instance that writes to files must close its output streams. Long-running applications can exhaust file descriptor limits without proper tracer lifecycle management.
External library interference happens when gems or standard library modules use their own tracing internally. Some debugging gems conflict with application-level tracing. The set_trace_func
mechanism allows only one global trace handler, creating conflicts between different tracing tools.
Reference
Core Classes and Modules
Class/Module | Purpose | Key Methods |
---|---|---|
Tracer |
Main tracing interface | on , off , trace , new |
Tracer::Single |
Singleton tracer instance | Global trace methods |
Class Methods
Method | Parameters | Returns | Description |
---|---|---|---|
Tracer.on |
None | nil |
Enable global tracing |
Tracer.off |
None | nil |
Disable global tracing |
Tracer.trace { block } |
Block | Block result | Execute block with tracing enabled |
Tracer.new(output = $stdout) |
IO object | Tracer instance | Create new tracer instance |
Instance Methods
Method | Parameters | Returns | Description |
---|---|---|---|
#on |
None | nil |
Enable this tracer instance |
#off |
None | nil |
Disable this tracer instance |
#trace { block } |
Block | Block result | Execute block with instance tracing |
#set_get_line_procs(file, proc) |
String, Proc | nil |
Set line retrieval procedure for file |
Trace Event Types
Event Type | Trigger Condition | Output Context |
---|---|---|
line |
Line execution | File, line number, source code |
call |
Method invocation | Method name, receiver class |
return |
Method completion | Method name, return value |
class |
Class/module definition | Class name, definition context |
end |
Class/module end | Class name, completion |
raise |
Exception raised | Exception class, message |
Output Format Components
Component | Format | Example |
---|---|---|
Thread ID | #<Thread:0x...> |
Thread identification |
File path | filename.rb |
Source file location |
Line number | :123 |
Line number in file |
Event type | :call , :line , :return |
Event type identifier |
Context | Variable content | Method names, values |
Indentation | Spaces | Call depth indication |
Configuration Options
Option | Type | Default | Purpose |
---|---|---|---|
Output stream | IO object | $stdout |
Trace output destination |
Line processors | Hash of Proc | Empty | Custom source line retrieval |
Event filtering | Internal | All events | Controls traced event types |
Error Conditions
Error Class | Trigger | Resolution |
---|---|---|
SystemStackError |
Trace handler recursion | Avoid traceable operations in handlers |
IOError |
Output stream closed | Ensure output stream remains open |
NoMethodError |
Invalid trace target | Verify object responds to traced methods |
Performance Characteristics
Operation | Time Complexity | Space Complexity | Notes |
---|---|---|---|
Event capture | O(1) per event | O(1) per event | Plus output overhead |
Line processing | O(n) file size | O(1) | When line processors used |
Output formatting | O(m) context size | O(m) | String concatenation costs |
Memory overhead | N/A | O(k) active traces | Proportional to trace depth |
Environment Integration
Environment Variable | Effect | Values |
---|---|---|
RUBY_TRACE_ENABLED |
Controls tracer loading | true , false |
DEBUG |
Conditional trace activation | Any truthy value |
TRACE_OUTPUT |
Output file path | File path string |