CrackedRuby logo

CrackedRuby

Tracer

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