Overview
Ruby provides a comprehensive exception system built around the Exception
class hierarchy and control flow statements. The core mechanism uses begin
, rescue
, ensure
, and raise
keywords to manage exceptional conditions during program execution.
The Exception
class serves as the root of Ruby's exception hierarchy. StandardError
inherits from Exception
and represents recoverable errors that applications typically handle. System-level exceptions like SystemExit
and Interrupt
inherit directly from Exception
and generally should not be rescued in application code.
begin
risky_operation
rescue StandardError => e
puts "Caught error: #{e.message}"
puts "Backtrace: #{e.backtrace.first(3).join("\n")}"
ensure
cleanup_resources
end
Ruby exceptions carry extensive metadata including message text, backtrace information, and the ability to attach custom data. The backtrace provides a complete call stack showing the execution path leading to the exception.
class DataProcessingError < StandardError
attr_reader :record_id, :field_name
def initialize(message, record_id: nil, field_name: nil)
super(message)
@record_id = record_id
@field_name = field_name
end
end
raise DataProcessingError.new(
"Invalid data format",
record_id: 12345,
field_name: "email"
)
The raise
method can accept an exception class, instance, or string message. When called without arguments inside a rescue block, it re-raises the current exception. The retry
statement restarts execution from the beginning of the begin block, enabling recovery patterns.
Exception handling blocks can specify multiple rescue clauses targeting different exception types. Ruby evaluates rescue clauses in order, matching the first compatible type using the ===
operator. This allows inheritance-based exception handling where parent classes catch child exceptions.
Basic Usage
Exception tracking begins with strategic placement of rescue blocks around code that might fail. Ruby's exception handling integrates with method definitions, where the entire method body acts as an implicit begin block.
def process_file(filename)
File.open(filename, 'r') do |file|
file.each_line.with_index do |line, index|
process_line(line)
end
end
rescue Errno::ENOENT
puts "File not found: #{filename}"
false
rescue Errno::EACCES
puts "Permission denied: #{filename}"
false
rescue => e
puts "Unexpected error processing #{filename}: #{e.message}"
false
else
puts "Successfully processed #{filename}"
true
ensure
puts "Finished processing attempt for #{filename}"
end
The rescue
clause without a specific exception type catches StandardError
and its subclasses. The =>
operator assigns the caught exception to a variable for inspection. The else
clause executes only if no exceptions occur, while ensure
always executes regardless of exception status.
Custom exception classes provide structured error information and enable targeted handling strategies. Define exception classes to carry domain-specific data that aids in debugging and error reporting.
module APIClient
class RequestError < StandardError
attr_reader :status_code, :response_body, :request_url
def initialize(message, status_code: nil, response_body: nil, request_url: nil)
super(message)
@status_code = status_code
@response_body = response_body
@request_url = request_url
end
def to_h
{
message: message,
status_code: status_code,
response_body: response_body,
request_url: request_url,
backtrace: backtrace
}
end
end
end
def make_api_request(url)
response = Net::HTTP.get_response(URI(url))
unless response.code.start_with?('2')
raise APIClient::RequestError.new(
"API request failed",
status_code: response.code,
response_body: response.body,
request_url: url
)
end
response.body
rescue Net::TimeoutError => e
raise APIClient::RequestError.new(
"Request timeout",
request_url: url
)
end
Exception instances provide access to backtrace information through the backtrace
method, which returns an array of strings representing the call stack. The backtrace_locations
method returns Thread::Backtrace::Location
objects with detailed position information.
begin
deeply_nested_method_call
rescue => e
puts "Exception: #{e.class} - #{e.message}"
puts "Location: #{e.backtrace_locations.first}"
puts "Method: #{e.backtrace_locations.first.label}"
puts "File: #{e.backtrace_locations.first.path}:#{e.backtrace_locations.first.lineno}"
end
The retry
statement provides recovery mechanisms for transient failures. Combine retry with counters or exponential backoff to handle temporary network issues or resource contention.
def fetch_with_retry(url, max_attempts: 3, delay: 1)
attempts = 0
begin
attempts += 1
Net::HTTP.get(URI(url))
rescue Net::TimeoutError, Net::HTTPServerError => e
if attempts < max_attempts
puts "Attempt #{attempts} failed: #{e.message}. Retrying in #{delay} seconds..."
sleep(delay)
delay *= 2 # Exponential backoff
retry
else
puts "Failed after #{max_attempts} attempts"
raise
end
end
end
Error Handling & Debugging
Exception debugging requires systematic analysis of error conditions, stack traces, and application state. Ruby provides several mechanisms for extracting diagnostic information from exceptions and runtime context.
The binding
method captures the current execution context, including local variables and method scope. Combined with debugging tools, binding enables interactive exception analysis. The caller
method returns the current call stack as an array of strings, useful for custom error reporting.
class DiagnosticError < StandardError
attr_reader :context_binding, :local_variables, :call_stack
def initialize(message, capture_context: true)
super(message)
if capture_context
@context_binding = binding.of_caller(1) # Requires binding_of_caller gem
@local_variables = extract_local_variables(@context_binding)
@call_stack = caller(1, 10) # Skip current frame, get next 10
end
end
private
def extract_local_variables(binding)
return {} unless binding
binding.local_variables.each_with_object({}) do |var, hash|
begin
hash[var] = binding.local_variable_get(var)
rescue NameError
hash[var] = "<unavailable>"
end
end
end
end
def risky_calculation(x, y, z)
coefficient = 0.5
result = x / y * coefficient
if result > z
raise DiagnosticError.new(
"Result exceeds threshold: #{result} > #{z}"
)
end
result
end
Exception filtering and transformation enable applications to present meaningful error messages while preserving debugging information. Create error boundaries that catch low-level exceptions and convert them to application-specific errors.
module ErrorBoundary
def self.wrap_database_errors
yield
rescue ActiveRecord::RecordNotFound => e
raise ApplicationError.new(
"Resource not found",
category: :not_found,
original_exception: e
)
rescue ActiveRecord::RecordInvalid => e
raise ApplicationError.new(
"Validation failed: #{e.record.errors.full_messages.join(', ')}",
category: :validation_error,
original_exception: e,
validation_errors: e.record.errors
)
rescue ActiveRecord::ConnectionTimeoutError => e
raise ApplicationError.new(
"Database connection timeout",
category: :service_unavailable,
original_exception: e,
retry_after: 30
)
end
end
class ApplicationError < StandardError
attr_reader :category, :original_exception, :metadata
def initialize(message, category: :general, original_exception: nil, **metadata)
super(message)
@category = category
@original_exception = original_exception
@metadata = metadata
end
def full_backtrace
traces = [backtrace]
traces << original_exception.backtrace if original_exception
traces.flatten.compact
end
end
Stack trace filtering improves debugging by highlighting application code and hiding framework noise. Create custom backtrace cleaners that remove irrelevant frames and emphasize important execution paths.
class BacktraceCleaner
FRAMEWORK_PATTERNS = [
/\/gems\/.*\/lib/,
/\/ruby\/.*\/lib/,
/internal:/,
/<internal:/
].freeze
APP_PATTERNS = [
/\/app\//,
/\/lib\//,
/\/config\//
].freeze
def self.clean(backtrace)
return [] unless backtrace
app_frames = backtrace.select { |frame| app_frame?(frame) }
return app_frames unless app_frames.empty?
# If no app frames, return filtered framework frames
backtrace.reject { |frame| noise_frame?(frame) }.first(10)
end
def self.app_frame?(frame)
APP_PATTERNS.any? { |pattern| frame =~ pattern }
end
def self.noise_frame?(frame)
FRAMEWORK_PATTERNS.any? { |pattern| frame =~ pattern }
end
end
# Usage in exception handling
begin
complex_operation
rescue => e
cleaned_trace = BacktraceCleaner.clean(e.backtrace)
puts "Error: #{e.message}"
puts "Relevant stack trace:"
cleaned_trace.first(5).each { |frame| puts " #{frame}" }
end
Exception aggregation and pattern detection help identify recurring issues. Implement error fingerprinting based on exception type, message patterns, and stack trace similarity.
class ExceptionFingerprinter
def self.generate_fingerprint(exception)
signature_parts = [
exception.class.name,
normalize_message(exception.message),
extract_stack_signature(exception.backtrace)
]
Digest::MD5.hexdigest(signature_parts.join(':'))
end
private
def self.normalize_message(message)
return 'no_message' unless message
# Remove variable content like IDs, timestamps, file paths
normalized = message.dup
normalized.gsub!(/\b\d+\b/, 'NUMBER')
normalized.gsub!(/\b[0-9a-f-]{36}\b/, 'UUID')
normalized.gsub!(/\/[^\s]+/, 'PATH')
normalized.downcase
end
def self.extract_stack_signature(backtrace)
return 'no_stack' unless backtrace
# Take first 3 application frames
app_frames = backtrace.select { |frame| BacktraceCleaner.app_frame?(frame) }
signature_frames = app_frames.first(3)
signature_frames.map do |frame|
# Extract method and file without line numbers
frame.gsub(/:in `.*'/, '').gsub(/:\d+/, '')
end.join('|')
end
end
Testing Strategies
Exception testing requires systematic verification of error conditions, recovery behaviors, and error message clarity. Ruby's testing frameworks provide specialized methods for asserting exception behavior and validating error handling paths.
RSpec offers expect { }.to raise_error()
syntax for testing exceptions with detailed matching capabilities. Test both that exceptions occur and that they carry appropriate metadata and messages.
describe APIClient do
describe '#make_request' do
context 'when server returns 404' do
it 'raises RequestError with status information' do
stub_request(:get, 'http://api.example.com/resource')
.to_return(status: 404, body: '{"error": "Not found"}')
expect {
APIClient.make_request('http://api.example.com/resource')
}.to raise_error(APIClient::RequestError) do |error|
expect(error.status_code).to eq('404')
expect(error.response_body).to include('Not found')
expect(error.request_url).to eq('http://api.example.com/resource')
end
end
end
context 'when network timeout occurs' do
it 'transforms timeout to RequestError' do
stub_request(:get, 'http://api.example.com/slow')
.to_timeout
expect {
APIClient.make_request('http://api.example.com/slow')
}.to raise_error(APIClient::RequestError, /timeout/i)
end
end
end
end
Test exception recovery and retry logic by simulating transient failures and verifying retry attempts. Use test doubles to control failure scenarios and count retry attempts.
describe '#fetch_with_retry' do
let(:http_client) { instance_double(Net::HTTP) }
it 'retries on timeout errors up to max attempts' do
call_count = 0
allow(Net::HTTP).to receive(:get) do
call_count += 1
if call_count <= 2
raise Net::TimeoutError, "Request timeout"
else
"Success response"
end
end
result = fetch_with_retry('http://example.com', max_attempts: 3, delay: 0)
expect(result).to eq("Success response")
expect(call_count).to eq(3)
end
it 'exhausts retries and re-raises final exception' do
allow(Net::HTTP).to receive(:get)
.and_raise(Net::TimeoutError, "Persistent timeout")
expect {
fetch_with_retry('http://example.com', max_attempts: 2, delay: 0)
}.to raise_error(Net::TimeoutError, "Persistent timeout")
expect(Net::HTTP).to have_received(:get).exactly(2).times
end
end
Mock external dependencies to test error boundary behavior without triggering actual failures. Create controlled error conditions that verify exception transformation and metadata preservation.
describe ErrorBoundary do
describe '.wrap_database_errors' do
let(:user) { double('User') }
context 'when ActiveRecord::RecordNotFound occurs' do
it 'transforms to ApplicationError with not_found category' do
allow(User).to receive(:find).and_raise(
ActiveRecord::RecordNotFound, "Couldn't find User with id=999"
)
expect {
ErrorBoundary.wrap_database_errors { User.find(999) }
}.to raise_error(ApplicationError) do |error|
expect(error.message).to eq("Resource not found")
expect(error.category).to eq(:not_found)
expect(error.original_exception).to be_a(ActiveRecord::RecordNotFound)
end
end
end
context 'when ActiveRecord::RecordInvalid occurs' do
it 'includes validation errors in metadata' do
validation_errors = double('Errors', full_messages: ['Name is required', 'Email is invalid'])
invalid_record = double('User', errors: validation_errors)
exception = ActiveRecord::RecordInvalid.new(invalid_record)
allow(user).to receive(:save!).and_raise(exception)
expect {
ErrorBoundary.wrap_database_errors { user.save! }
}.to raise_error(ApplicationError) do |error|
expect(error.category).to eq(:validation_error)
expect(error.metadata[:validation_errors]).to eq(validation_errors)
expect(error.message).to include('Name is required', 'Email is invalid')
end
end
end
end
end
Test exception fingerprinting and aggregation logic to ensure consistent error grouping. Verify that similar exceptions generate identical fingerprints while distinct errors produce different signatures.
describe ExceptionFingerprinter do
describe '.generate_fingerprint' do
it 'generates identical fingerprints for similar exceptions' do
exception1 = StandardError.new("User 123 not found")
exception1.set_backtrace(['/app/models/user.rb:45:in `find_user`'])
exception2 = StandardError.new("User 456 not found")
exception2.set_backtrace(['/app/models/user.rb:45:in `find_user`'])
fingerprint1 = ExceptionFingerprinter.generate_fingerprint(exception1)
fingerprint2 = ExceptionFingerprinter.generate_fingerprint(exception2)
expect(fingerprint1).to eq(fingerprint2)
end
it 'generates different fingerprints for different exception types' do
error1 = ArgumentError.new("Invalid input")
error2 = StandardError.new("Invalid input")
fingerprint1 = ExceptionFingerprinter.generate_fingerprint(error1)
fingerprint2 = ExceptionFingerprinter.generate_fingerprint(error2)
expect(fingerprint1).not_to eq(fingerprint2)
end
it 'handles exceptions without backtrace' do
exception = StandardError.new("Test error")
# No backtrace set
expect {
ExceptionFingerprinter.generate_fingerprint(exception)
}.not_to raise_error
end
end
end
Production Patterns
Production exception tracking requires comprehensive monitoring, alerting, and analysis systems that capture errors without impacting application performance. Implement structured logging, error aggregation, and automated incident response workflows.
Structured exception logging formats error data as JSON or key-value pairs for efficient parsing by log aggregation systems. Include contextual information like user IDs, request IDs, and feature flags to aid in debugging production issues.
class ProductionExceptionLogger
def self.log_exception(exception, context = {})
log_entry = {
timestamp: Time.current.iso8601,
level: 'ERROR',
exception: {
class: exception.class.name,
message: exception.message,
fingerprint: ExceptionFingerprinter.generate_fingerprint(exception),
backtrace: BacktraceCleaner.clean(exception.backtrace).first(10)
},
context: sanitize_context(context),
environment: {
hostname: Socket.gethostname,
process_id: Process.pid,
ruby_version: RUBY_VERSION,
rails_env: Rails.env
}
}
Rails.logger.error(JSON.generate(log_entry))
# Send to external monitoring service
send_to_monitoring_service(log_entry) if should_report_externally?(exception)
end
private
def self.sanitize_context(context)
sanitized = context.dup
# Remove sensitive data
sensitive_keys = [:password, :token, :secret, :api_key, :credit_card]
sensitive_keys.each { |key| sanitized.delete(key) }
# Truncate large values
sanitized.transform_values do |value|
case value
when String
value.length > 1000 ? "#{value[0, 1000]}..." : value
when Hash
value.size > 20 ? "#{value.size} items (truncated)" : value
when Array
value.size > 50 ? "#{value.size} items (truncated)" : value
else
value
end
end
end
def self.should_report_externally?(exception)
# Don't report certain types of exceptions externally
[ActionController::RoutingError, ActionController::InvalidAuthenticityToken]
.none? { |type| exception.is_a?(type) }
end
def self.send_to_monitoring_service(log_entry)
# Integration with services like Sentry, Rollbar, etc.
ExceptionMonitoringService.report(log_entry)
rescue => e
# Never let monitoring failures affect application flow
Rails.logger.warn("Failed to send exception to monitoring service: #{e.message}")
end
end
Exception rate limiting prevents error storms from overwhelming logging systems and external monitoring services. Implement exponential backoff and circuit breaker patterns for exception reporting.
class ExceptionRateLimiter
RATE_LIMITS = {
default: { max_count: 100, window_seconds: 300 }, # 100 per 5 minutes
critical: { max_count: 1000, window_seconds: 300 }, # 1000 per 5 minutes
low_priority: { max_count: 10, window_seconds: 300 } # 10 per 5 minutes
}.freeze
def initialize
@counters = {}
@mutex = Mutex.new
end
def should_report?(exception, priority: :default)
fingerprint = ExceptionFingerprinter.generate_fingerprint(exception)
limits = RATE_LIMITS[priority]
@mutex.synchronize do
current_time = Time.current.to_i
window_start = current_time - limits[:window_seconds]
# Clean old entries
@counters[fingerprint] ||= []
@counters[fingerprint].reject! { |timestamp| timestamp < window_start }
# Check if under limit
if @counters[fingerprint].size < limits[:max_count]
@counters[fingerprint] << current_time
true
else
false
end
end
end
def current_count(exception)
fingerprint = ExceptionFingerprinter.generate_fingerprint(exception)
@mutex.synchronize { @counters[fingerprint]&.size || 0 }
end
end
# Global rate limiter instance
EXCEPTION_RATE_LIMITER = ExceptionRateLimiter.new
# Usage in exception handler
def handle_production_exception(exception, context = {})
priority = determine_exception_priority(exception)
if EXCEPTION_RATE_LIMITER.should_report?(exception, priority: priority)
ProductionExceptionLogger.log_exception(exception, context)
else
# Still log locally but don't send externally
Rails.logger.info(
"Exception rate limited: #{exception.class} " \
"(#{EXCEPTION_RATE_LIMITER.current_count(exception)} occurrences)"
)
end
end
Exception context enrichment adds relevant application state to error reports. Capture request parameters, user information, feature flags, and system metrics at the time of error occurrence.
module ExceptionContextEnricher
extend self
def enrich_context(base_context = {})
enriched = base_context.dup
# Request context
if defined?(Rails) && Rails.application
enriched.merge!(request_context)
end
# User context
enriched.merge!(user_context) if current_user_available?
# System context
enriched.merge!(system_context)
# Feature flags
enriched.merge!(feature_flag_context)
# Performance metrics
enriched.merge!(performance_context)
enriched
end
private
def request_context
return {} unless defined?(RequestStore) && RequestStore.store[:current_request]
request = RequestStore.store[:current_request]
{
request: {
id: request.uuid,
method: request.method,
path: request.path,
remote_ip: request.remote_ip,
user_agent: request.user_agent&.truncate(200),
referer: request.referer&.truncate(200)
}
}
end
def user_context
return {} unless defined?(Current) && Current.user
{
user: {
id: Current.user.id,
email: Current.user.email,
role: Current.user.role,
created_at: Current.user.created_at,
last_active: Current.user.last_active_at
}
}
end
def system_context
{
system: {
memory_usage: get_memory_usage,
cpu_usage: get_cpu_usage,
disk_usage: get_disk_usage,
active_record_pool: get_connection_pool_status
}
}
end
def feature_flag_context
return {} unless defined?(FeatureFlag)
{
feature_flags: FeatureFlag.active_flags_for_context
}
end
def performance_context
{
performance: {
request_start: RequestStore.store[:request_start_time],
duration_ms: calculate_request_duration,
db_query_count: db_query_count,
cache_hit_rate: cache_hit_rate
}
}
end
def get_memory_usage
`ps -o rss= -p #{Process.pid}`.to_i * 1024 rescue 0
end
def get_cpu_usage
# Implementation depends on system monitoring tools
0.0
end
def get_disk_usage
statvfs = `df / | tail -1 | awk '{print $5}'`.strip rescue "0%"
statvfs.to_i
end
def get_connection_pool_status
return {} unless defined?(ActiveRecord)
{
size: ActiveRecord::Base.connection_pool.size,
checked_out: ActiveRecord::Base.connection_pool.stat[:busy],
available: ActiveRecord::Base.connection_pool.stat[:size] - ActiveRecord::Base.connection_pool.stat[:busy]
}
rescue
{}
end
end
Health check endpoints monitor exception rates and system stability. Implement application health indicators that include error rates, response times, and resource utilization metrics.
class HealthCheckController < ApplicationController
def show
health_data = {
status: overall_health_status,
timestamp: Time.current.iso8601,
checks: {
database: database_health,
redis: redis_health,
external_services: external_services_health,
exception_rate: exception_rate_health,
memory: memory_health,
disk: disk_health
},
metrics: system_metrics
}
status_code = health_data[:status] == 'healthy' ? 200 : 503
render json: health_data, status: status_code
end
private
def overall_health_status
checks = [
database_health[:status],
redis_health[:status],
external_services_health[:status],
exception_rate_health[:status],
memory_health[:status]
]
checks.all? { |status| status == 'healthy' } ? 'healthy' : 'unhealthy'
end
def exception_rate_health
recent_error_count = count_recent_errors(window_minutes: 5)
threshold = 50 # Max errors per 5-minute window
{
status: recent_error_count < threshold ? 'healthy' : 'unhealthy',
recent_error_count: recent_error_count,
threshold: threshold,
window_minutes: 5
}
end
def count_recent_errors(window_minutes:)
# Implementation depends on log aggregation system
# This example assumes structured logging with searchable timestamps
since = Time.current - window_minutes.minutes
log_query = Rails.logger.respond_to?(:search) ?
Rails.logger.search(level: 'ERROR', since: since) : []
log_query.count
rescue
0 # Fail safely if log search unavailable
end
def system_metrics
{
ruby_version: RUBY_VERSION,
rails_version: Rails::VERSION::STRING,
uptime_seconds: Process.clock_gettime(Process::CLOCK_MONOTONIC).to_i,
process_id: Process.pid,
thread_count: Thread.list.size,
memory_usage_mb: (get_memory_usage / 1024 / 1024).round(2)
}
end
end
Reference
Core Exception Classes
Class | Parent | Description | Usage |
---|---|---|---|
Exception |
BasicObject | Root of exception hierarchy | System-level exceptions |
StandardError |
Exception | Base for application errors | Default rescue target |
RuntimeError |
StandardError | Generic runtime error | Default for raise "message" |
ArgumentError |
StandardError | Wrong number or type of arguments | Method parameter validation |
NameError |
StandardError | Undefined variable or method | Missing constant/method |
NoMethodError |
NameError | Method not found | Method call on wrong object |
SystemExit |
Exception | Program termination | exit command |
Interrupt |
Exception | User interruption | Ctrl+C signal |
SignalException |
Exception | System signal received | OS signal handling |
Exception Handling Keywords
Keyword | Purpose | Usage | Notes |
---|---|---|---|
begin |
Start exception block | begin...rescue...end |
Optional in method definitions |
rescue |
Catch exceptions | rescue ExceptionType => e |
Matches StandardError by default |
ensure |
Always execute | ensure...end |
Runs regardless of exceptions |
else |
No exception occurred | else...end |
Only runs if no exceptions |
raise |
Throw exception | raise ExceptionClass, "message" |
Re-raises current if no args |
retry |
Restart begin block | retry |
Use with counter to avoid infinite loops |
Exception Instance Methods
Method | Returns | Description |
---|---|---|
#message |
String |
Exception message text |
#backtrace |
Array<String> |
Stack trace as string array |
#backtrace_locations |
Array<Thread::Backtrace::Location> |
Detailed location objects |
#cause |
Exception or nil |
Exception that caused this one |
#full_message |
String |
Formatted message with backtrace |
#inspect |
String |
Object inspection string |
#to_s |
String |
String representation (same as message) |
Thread::Backtrace::Location Methods
Method | Returns | Description |
---|---|---|
#path |
String |
File path |
#lineno |
Integer |
Line number |
#label |
String |
Method or block name |
#absolute_path |
String |
Absolute file path |
#to_s |
String |
Formatted location string |
Common Exception Patterns
# Basic exception handling
begin
risky_operation
rescue SpecificError => e
handle_specific_error(e)
rescue StandardError => e
handle_general_error(e)
ensure
cleanup_resources
end
# Method-level exception handling
def process_data
# method body acts as implicit begin block
rescue ValidationError => e
return { success: false, error: e.message }
end
# Custom exception with metadata
class ProcessingError < StandardError
attr_reader :record_id, :step
def initialize(message, record_id: nil, step: nil)
super(message)
@record_id = record_id
@step = step
end
end
# Exception transformation
begin
external_api_call
rescue Net::TimeoutError => e
raise ServiceUnavailableError, "External service timeout"
rescue JSON::ParserError => e
raise DataFormatError, "Invalid response format"
end
# Retry with exponential backoff
def retry_with_backoff(max_attempts: 3)
attempts = 0
delay = 1
begin
attempts += 1
yield
rescue RetryableError => e
if attempts < max_attempts
sleep(delay)
delay *= 2
retry
else
raise
end
end
end
# Exception fingerprinting
def exception_fingerprint(exception)
[
exception.class.name,
normalize_message(exception.message),
stack_signature(exception.backtrace)
].join(':')
end
Testing Exception Assertions
Framework | Syntax | Example |
---|---|---|
RSpec | expect { }.to raise_error() |
expect { method }.to raise_error(CustomError, /message/) |
Minitest | assert_raises() |
assert_raises(CustomError) { method } |
Test::Unit | assert_raise() |
assert_raise(CustomError) { method } |
Performance Considerations
Pattern | Impact | Recommendation |
---|---|---|
Deep rescue blocks | High CPU overhead | Keep rescue logic minimal |
Exception for flow control | Very high overhead | Use conditional logic instead |
Large backtrace capture | Memory overhead | Limit backtrace depth |
Frequent exception creation | Allocation overhead | Cache exception instances |
Exception in tight loops | Severe performance impact | Validate before loop entry |
Configuration Options
Setting | Default | Purpose |
---|---|---|
$DEBUG |
false |
Enable debug mode |
$VERBOSE |
nil |
Verbosity level |
Exception.to_tty? |
Auto-detected | TTY-specific formatting |
--backtrace-limit |
No limit | CLI backtrace limitation |
RUBY_BACKTRACE_LIMIT |
No limit | Environment variable limit |