Skip to content

Instantly share code, notes, and snippets.

@ryenski
Last active March 30, 2026 19:06
Show Gist options
  • Select an option

  • Save ryenski/e36b18e53a50156dbf5cc24b08fee1f3 to your computer and use it in GitHub Desktop.

Select an option

Save ryenski/e36b18e53a50156dbf5cc24b08fee1f3 to your computer and use it in GitHub Desktop.
Error Reporting Patterns — Rails.error API

Error Reporting Patterns

We use Rails' native error reporter (Rails.error) as the single interface for all error and exception reporting. It replaces our previous Honeybadger-specific calls and routes through our RailsErrorSubscriber, which forwards to Rails.logger and the OTel pipeline.


The three APIs

Method When to use
Rails.error.report Report a rescued exception without re-raising it
Rails.error.handle Execute a block — rescues and reports on error, returns fallback
Rails.error.set_context Attach ambient context to every error reported in this request/job

Rails.error.report — explicit reporting

Use when you have already rescued an exception and want to report it without re-raising.

rescue SomeError => e
  Rails.error.report(e, handled: true, severity: :error, context: { order_id: order.id })
end

Parameters:

Param Values Notes
handled: true / false true = you rescued it; false = unhandled/unexpected
severity: :error, :warning, :info Defaults to :error
context: Hash Merged with any ambient context from set_context

Severity guidance:

  • :error — server errors (5xx), unexpected failures
  • :warning — non-fatal, unexpected but recoverable (e.g. a missing icon asset)
  • :info — expected client errors (4xx), known edge cases
# Server error — default severity
Rails.error.report(e, handled: true)

# Client error — downgrade severity
Rails.error.report(e, handled: true, severity: :info, context: { status: 404 })

# Non-fatal warning
Rails.error.report(RuntimeError.new("Missing icon: #{name}"), handled: true, severity: :warning)

Rails.error.handle — report and swallow

Use when you want to attempt an operation and silently continue if it fails.

Rails.error.handle(context: { product_id: @product.id }) do
  ExternalService.sync(@product)
end

Optionally provide a fallback value:

result = Rails.error.handle(fallback: -> { [] }) do
  fetch_remote_items
end

Rails.error.set_context — ambient context

Set key/value pairs that are automatically included in every error reported during the current request or job. Call this once early in the lifecycle — don't pass duplicated keys into every report call.

Rails.error.set_context(account_id: Current.account.id, user_id: Current.user.id)

Context accumulates — each call merges with the existing context for the current execution.


Controllers

The ErrorContext concern sets user/session context on every request. Authorized::Account extends it with account and organization IDs after authorization resolves.

# app/controllers/concerns/error_context.rb
def set_error_context
  Rails.error.set_context(user_id: Current.session_user&.id, session_id: Current.session&.id)
end

# app/controllers/concerns/authorized/account.rb
def set_error_context
  super
  Rails.error.set_context(account_id: Current.account&.id, organization_id: Current.organization&.id)
end

In controller rescue blocks, call Rails.error.report with handled: true:

rescue SomeError => exception
  Rails.error.report(exception, handled: true, context: { request_params: request.filtered_parameters })
  render json: { error: "Something went wrong" }, status: :internal_server_error
end

Jobs

Set job-specific context at the start of perform, then report errors in rescue blocks:

def perform(import_id)
  Rails.error.set_context(import_id: import_id, job_class: self.class.name)

  # ... work ...
rescue StandardError => e
  Rails.error.report(e, handled: true, context: { import_id: import_id })
  raise
end

For the API job layer, Api::Concerns::ErrorNotifiable handles severity mapping from HTTP status codes automatically — use notify_failure(exception, :internal_server_error).


Models and services

Follow the same pattern — rescue, then report with relevant context:

rescue SomeError => exception
  Rails.error.report(exception, handled: true, context: { connection_id: id })
end

Avoid Rails.error.report outside of a rescue block. If you are not handling an exception, let it bubble up so Rails captures it as unhandled.


What not to do

# ❌ Don't use Honeybadger directly
Honeybadger.notify(e)
Honeybadger.context(user_id: user.id)

# ❌ Don't report without handled:
Rails.error.report(e)  # handled: is required — be explicit

# ❌ Don't swallow errors silently
rescue => e
  nil  # error lost entirely

# ❌ Don't set context inside every report call — set it once at the top
Rails.error.report(e, handled: true, context: { user_id: current_user.id })  # duplicates ambient context

How it works

RailsErrorSubscriber (app/errors/rails_error_subscriber.rb) is registered in config/initializers/rails_error_reporter.rb. It receives every call to Rails.error.report and Rails.error.handle and writes a tagged log line:

[ErrorReporter] [MyError] Something went wrong account_id=123 user_id=456

This log line flows through the OTel log pipeline and surfaces in Honeybadger via the log ingestion subscriber.


Reference

  • Rails Error Reporting Guide
  • app/errors/rails_error_subscriber.rb
  • config/initializers/rails_error_reporter.rb
  • app/controllers/concerns/error_context.rb
  • app/jobs/api/concerns/error_notifiable.rb
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment