Silent Failures Are Your App’s Kryptonite — Error Handling & Logging Best Practices in Rails ⚠️πŸ”

Silent Failures Are Your App’s Kryptonite — Error Handling & Logging Best Practices in Rails ⚠️πŸ”

Want fewer 2 a.m. panic calls and faster debugging? This guide gives you practical, production-ready patterns for error handling and logging in Rails — with examples, config snippets, and the best live tools to use. πŸš€

Why this matters (short)
  • Errors that aren’t caught, reported, and contextualized cost time and users.
  • Poor logs = slow fixes. Good logs + alerts = fast remediation and happier users. 😊
Rails built-ins you should know (and use)

1) Parameter filtering — never log PII

Add sensitive keys to config.filter_parameters so Rails masks them in logs and when inspect is called on AR objects.

# config/initializers/filter_parameter_logging.rb
Rails.application.config.filter_parameters += [
:password, :credit_card_number, :ssn, :token, :authorization
]

Rails’ config provides this for you and is the first line of defense for not leaking secrets. Ruby on Rails Guides

2) Rails error reporter

Rails includes an error reporter that wraps request/job execution and lets error-reporting libraries subscribe to unhandled errors. That means Sentry/Honeybadger/etc. can hook in cleanly — and you can also create custom subscribers. Ruby on Rails Guides

Example (conceptual):

Rails.error.handle(MyCustomError) do
# code that might raise
end
Error-handling best practices (code + patterns)

Prefer fail-fast and explicit errors

Create domain-specific exceptions so handlers can react properly:

# app/errors/payment_error.rb
class PaymentError < StandardError; end

Raise them where appropriate (service objects, not controllers):

# app/services/payment_processor.rb
class PaymentProcessor
def call
charge = gateway.charge(...)
raise PaymentError, "card declined" unless charge.success?
charge
end
end

Use rescue_from in controllers for uniform responses

Centralize HTTP error mapping:

# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
rescue_from PaymentError, with: :payment_failure
rescue_from ActiveRecord::RecordNotFound, with: :not_found
rescue_from StandardError, with: :internal_error


private

def payment_failure(err)
Rails.logger.warn("Payment failed: #{err.message}")
Sentry.capture_exception(err) if defined?(Sentry)
render json: { error: 'Payment failed' }, status: :payment_required
end
# ...
end

Don’t swallow exceptions silently

Always log and (usually) report — silent failures are the worst. Use structured logging and an error tracker (below).

Background jobs: Sidekiq + retries

Sidekiq automatically retries failed jobs with an exponential backoff and moves exhausted jobs to the “dead” set — but you should still handle expected errors intentionally.

  • Use idempotent jobs.
  • Use sidekiq_retries_exhausted or middleware to notify after retries are done.
  • If you have transient external failures, prefer retries; if logic error, fail fast and inspect.

Sidekiq’s retry behavior and hooks are documented in the official docs. rubydoc.info

Example hook:

class MyWorker
include Sidekiq::Worker
sidekiq_options retry: 5

sidekiq_retries_exhausted do |msg, ex|
Rails.logger.error("Job exhausted: #{msg['class']} #{msg['args']} - #{ex.message}")
Sentry.capture_message("Job exhausted: #{msg['class']}", extra: { job: msg })
end

def perform(*args)
# ...
end
end
Logging best practices — make logs actionable

Use levels correctly

  • debug: in-depth dev info
  • info: normal operations (requests, major lifecycle events)
  • warn: recoverable or unusual events
  • error: exceptions you caught and are reporting
  • fatal: process-level crashes

In production prefer info or warn to avoid noise; debug is for local troubleshooting.

Add context: request_id, user_id, etc.

Correlate logs across services by including a request id and user identifiers (non-PII). Use Rails’ config.log_tags and TaggedLogging.

# config/environments/production.rb
config.log_tags = [:request_id, lambda { |req| "user:#{req.session[:current_user_id]}" }]

Structured logs (JSON)

Structured logs are machine-parseable and 10× easier to search/aggregate. Two common approaches in Rails:

  1. Lograge: convert Rails request logs into single-line, structured logs (JSON or key=value). Great for shipping to log systems. GitHub
# Gemfile
gem 'lograge'

# config/environments/production.rb
config.lograge.enabled = true
config.lograge.formatter = Lograge::Formatters::Json.new
config.lograge.custom_options = lambda do |event|
{
time: event.time.utc.iso8601,
params: event.payload[:params].except('controller','action'),
host: event.payload[:host]
}
end

2. Or use a JSON formatter for the standard logger:

# config/initializers/json_logger.rb
class JsonLogFormatter < Logger::Formatter
def call(severity, time, progname, msg)
{
level: severity,
time: time.utc.iso8601,
message: msg.to_s
}.to_json + "\n"
end
end

Rails.logger = ActiveSupport::TaggedLogging.new(Logger.new(STDOUT))
Rails.logger.formatter = JsonLogFormatter.new

Lograge is a widely used option for turning Rails’ verbose request logs into compact single-line logs suitable for aggregators. GitHub

Filter what you send

Double-check filter_parameters covers any custom keys and be careful with objects that include user data — don’t log full params in raw form.

Centralized logging & observability (where to send logs & traces)

SaaS / APMS (recommended for most teams)

  • Sentry — excellent Rails integration for error monitoring, breadcrumbs, tracing and alerting; easy install via sentry-rails. Great for grouping errors, seeing stack traces and traces across requests. Sentry Docs

Quick Sentry setup:

# Gemfile
gem 'sentry-rails'

# config/initializers/sentry.rb
Sentry.init do |config|
config.dsn = ENV['SENTRY_DSN']
config.breadcrumbs_logger = [:active_support_logger, :http_logger]
config.traces_sample_rate = 0.05 # sample traces (adjust carefully)
end
  • Datadog — logs, traces & metrics in one place (great if you want APM + logs). Datadog documents how to collect Rails logs and traces and how to tail logs or use the agent. Datadog MonitoringDatadog

Open-source / self-hosted

  • ELK / OpenSearch (Elasticsearch + Logstash/Filebeat + Kibana) — flexible and powerful but operationally heavy. Good if you want full ownership and custom pipelines. Elastic

Quick guidance on choosing:

  • Need fast setup & great UI: Sentry (errors) + Datadog/LogDNA/Mezmo (logs) — low ops overhead. Sentry Docsmezmo.com
  • Need full control & lower recurring cost: ELK + beats (more ops overhead). Elastic
Example: end-to-end pattern (service → controller → Sentry + Log)
# app/services/order_creator.rb
class OrderCreator
def initialize(user, params)
@user = user
@params = params
end

def call
validate!
create_order!
rescue ExternalPaymentGateway::Timeout => e
# expected transient error - let job retry or handle gracefully
raise
rescue => e
Rails.logger.error("OrderCreator failed: #{e.class} #{e.message}", user_id: @user.id)
Sentry.capture_exception(e, extra: { user_id: @user.id, params: @params })
raise
end
end
# app/controllers/orders_controller.rb
def create
OrderCreator.new(current_user, order_params).call
render json: { ok: true }
rescue PaymentError => e
render json: { error: e.message }, status: :payment_required
end

This combination: log locally with context, report to Sentry, and return a friendly HTTP error is the pattern to aim for.

Practical ops tips & gotchas
  • Log to STDOUT in containerized environments (RAILS_LOG_TO_STDOUT=true) so your platform (Kubernetes/Heroku) can handle collection. (Common gotcha: not sending logs to stdout prevents aggregators from seeing them.) Stack Overflow
  • Rotate logs / set retention to avoid bill shocks.
  • Sampling: for high-traffic apps, sample traces (APM) to control cost — but always send all error events.
  • Alert tuning: start with high-priority alerts (new error spike, high error rate, job dead queue) and iteratively refine to reduce noise.
  • Add breadcrumbs in Sentry to capture the lead-up to an error (DB queries, cache calls, external HTTP) — invaluable for reproducing issues. Sentry Docs
Recommended toolset (quick)
  • Error monitoring / grouping: Sentry (excellent Rails integration + tracing). Sentry Docs
  • Structured request logs: Lograge (compact single-line request logs). GitHub
  • Full-stack observability: Datadog (logs + traces + metrics) or ELK for self-hosted. Datadog MonitoringElastic
  • Background jobs: Sidekiq (use its retry hooks + monitoring UI). rubydoc.info
TL;DR (cheat sheet) ✅
  • Add sensitive keys to config.filter_parameters. (Ruby on Rails Guides)
  • Use rescue_from in controllers and explicit rescue + report in services.
  • Use Lograge or a JSON logger for structured request logs. (GitHub)
  • Ship errors to Sentry (or Rollbar/Honeybadger) and logs/traces to Datadog or ELK. (Sentry Docs Datadog Monitoring)
  • For background jobs, rely on Sidekiq’s retry semantics and hook sidekiq_retries_exhausted to notify on exhaustion.


Comments

Popular posts from this blog

πŸš€ Ruby on Rails 8: The Ultimate Upgrade for Modern Developers! Game-Changing Features Explained πŸŽ‰πŸ’Ž

πŸš€ Uploading Large Files in Ruby on Rails: A Complete Guide

πŸš€ Mastering Deployment: Top Tools You Must Know Before Launching Your App or Model!