Exceptify sends exception reports from Rails and Rack applications to the channels your team already watches: email, chat, webhooks, and monitoring tools.
This repository is maintained by @lukin-io as lukin-io/exceptify with the exceptify gem name and Exceptify API. Version 1.0.0 starts the maintained Exceptify release line.
Add the gem to your Rails application's Gemfile:
gem "exceptify"Install it and generate the initializer:
bundle install
bundle exec rails generate exceptify:installConfigure at least one notifier:
# config/initializers/exceptify.rb
require "exceptify/rails"
require "exceptify/rake"
Exceptify.configure do |config|
config.add_notifier :email, {
email_prefix: "[#{Rails.env.upcase}] ",
sender_address: %("Exceptify" <notifier@example.com>),
exception_recipients: %w[exceptions@example.com]
}
config.ignore_if do |_exception, _options|
Rails.env.local?
end
endEmail delivery uses your application's ActionMailer configuration. See ActionMailer configuration if emails do not send.
After configuring a notifier, trigger a real exception in a non-production environment.
# config/routes.rb
get "/exceptify_test", to: proc {
raise "Exceptify test"
}Start Rails, visit /exceptify_test, and confirm the notification arrives. Remove the route after testing.
- Quick Start
- How To Test It
- What It Does
- Compatibility
- Installation
- Rails Setup
- Common Workflows
- Notifiers
- Noise Control
- Production Checklist
- Background Jobs
- Rack and Sinatra
- Maintenance
- Development
- License
Exceptify installs a Rack middleware that watches for unhandled exceptions during web requests and sends a notification with request, session, environment, backtrace, and optional application data.
Use it when you want a small, self-hosted notification layer for exceptions without adopting a hosted error tracker.
It supports:
- Rails integration through a generator and initializer.
- Rack middleware configuration for Rails, Sinatra, and other Rack apps.
- Built-in notifiers for email, Slack, Teams, Amazon SNS, Datadog, and webhooks.
- Manual reporting for rescued exceptions, background jobs, scripts, rake tasks, and runners.
- Ignore rules, crawler filtering, per-notifier filtering, and repeated-error grouping.
- Exceptify 1.0.0 or newer.
- Ruby 3.4.4 or newer.
- Rails 8.0.2 or newer, below Rails 9.
- Rack applications, including Sinatra.
The changelog starts at 1.0.0 for the maintained exceptify release line.
Add the gem to your application's Gemfile:
gem "exceptify"Install it:
bundle installTo use this maintained repository directly from Git:
gem "exceptify", git: "https://github.com/lukin-io/exceptify.git"For local gem development, point a test application at your checkout:
gem "exceptify", path: "../exceptify"If the application should keep a Git source in its Gemfile while Bundler uses a local checkout, configure a local override from that application:
bundle config set local.exceptify ../exceptify
bundle installGenerate the Rails initializer:
bundle exec rails generate exceptify:installThe generated file is written to config/initializers/exceptify.rb.
Keep the gem available to every environment that loads the initializer. If you want notifications only in production, keep the gem outside a production-only bundle group and add an ignore rule for local environments.
The generated initializer should load the Rails and Rake integrations, then register one or more notifiers. The email example in Quick Start is enough for a first setup; add other notifiers from Notifiers as needed.
If you want rails runner commands to report exceptions, load the Rails integration from config/application.rb below Bundler.require:
# config/application.rb
require "exceptify/rails"The initializer is too late for runner callbacks. You can still keep notifier configuration in config/initializers/exceptify.rb.
The runner hook is guarded, so loading the integration more than once does not install duplicate at_exit callbacks.
Slack notifications require the slack-notifier gem:
gem "slack-notifier"Then register both notifiers:
Exceptify.configure do |config|
config.add_notifier :email, {
email_prefix: "[#{Rails.env.upcase}] ",
sender_address: %("Exceptify" <notifier@example.com>),
exception_recipients: %w[exceptions@example.com]
}
if (webhook_url = Rails.application.credentials.dig(:slack, :exceptions_webhook_url))
config.add_notifier :slack, {
webhook_url: webhook_url,
channel: "#exceptions",
additional_parameters: {
mrkdwn: true
}
}
end
endStore application data in the request environment before an exception is raised:
class ApplicationController < ActionController::Base
before_action :prepare_exceptify_notification
private
def prepare_exceptify_notification
request.env["exceptify.exception_data"] = {
current_user_id: current_user&.id,
account_id: current_account&.id,
request_id: request.request_id
}
end
endThat data is included in supported notifier payloads and email sections.
Middleware only sees exceptions that continue up the Rack stack. If a controller rescues an error, notify manually:
class OrdersController < ApplicationController
rescue_from PaymentGateway::Timeout, with: :payment_gateway_timeout
private
def payment_gateway_timeout(exception)
Exceptify.notify_exception(
exception,
env: request.env,
data: {
order_id: params[:id],
request_id: request.request_id
}
)
render json: {error: "payment_gateway_timeout"}, status: :bad_gateway
end
endbegin
ImportCustomers.call(file_path)
rescue => exception
Exceptify.notify_exception(
exception,
data: {
job: "ImportCustomers",
file_path: file_path
}
)
raise
endException emails can include request parameters. Use Rails parameter filtering for secrets:
# config/application.rb
config.filter_parameters += [
:password,
:password_confirmation,
:credit_card_number,
:secret_details
]Built-in notifier docs and support status:
| Notifier | Status | Docs |
|---|---|---|
| Supported | ||
| Slack | Supported | Slack |
| Teams | Supported | Teams |
| Amazon SNS | Supported | Amazon SNS |
| Datadog | Supported | Datadog |
| WebHook | Supported | WebHook |
| Custom notifiers | Supported extension point | Custom notifiers |
Network notifiers validate required options during setup. Missing webhook_url, url, or AWS credentials raise ArgumentError instead of silently disabling notifications.
For tests, custom transports, or apps that already wrap provider clients, pass an injected client:
Exceptify.configure do |config|
config.add_notifier :webhook, {
url: "https://example.com/exception-webhook",
http_client: MyHTTPClient
}
config.add_notifier :sns, {
topic_arn: "arn:aws:sns:us-east-1:123456789012:exceptions",
client: Aws::SNS::Client.new(region: "us-east-1")
}
endSlack accepts notifier: for a prebuilt Slack client. Use fail_silently: true only as a temporary compatibility option while migrating old silent configurations.
You can also register any object that responds to #call(exception, options):
Exceptify.configure do |config|
config.add_notifier :logger, lambda { |exception, options|
Rails.logger.error(
"[exceptify] #{exception.class}: #{exception.message} #{options[:data].inspect}"
)
}
endExceptify.configure do |config|
config.ignored_exceptions += %w[
ActionView::TemplateError
MyApp::ExpectedError
]
endThe default ignored exceptions include common routing, record-not-found, and invalid-parameter errors.
Exceptify.configure do |config|
config.ignore_crawlers %w[Googlebot bingbot]
endExceptify.configure do |config|
config.ignore_if do |exception, options|
path = options.dig(:env, "PATH_INFO")
Rails.env.local? ||
path == "/health" ||
exception.message.match?(/Couldn't find Page with ID=/)
end
endUse per-notifier filtering when email should still send but chat should stay quiet, or the reverse:
Exceptify.configure do |config|
config.ignore_notifier_if(:slack) do |exception, _options|
exception.is_a?(ActionController::RoutingError)
end
endError grouping prevents notification floods for the same exception. With the default trigger, notifications are sent at counts 1, 2, 4, 8, 16, and so on.
Exceptify.configure do |config|
config.error_grouping = true
config.error_grouping_cache = Rails.cache
config.error_grouping_period = 5.minutes
config.notification_trigger = lambda { |_exception, count|
count == 1 || (count % 10).zero?
}
endBefore relying on exception notifications in production:
- Configure at least one notifier.
- Verify delivery with a real test exception in staging.
- Confirm ActionMailer delivery settings if using email.
- Filter secrets with
config.filter_parameters. - Ignore local and test environments.
- Add crawler or health-check ignores if they create noise.
- Enable error grouping for high-traffic applications.
- Make sure background jobs are covered separately from web requests.
The Rack middleware only catches exceptions during web requests. For jobs and command-line work, use one of the integrations below.
The generated initializer includes:
require "exceptify/rake"That reports unhandled exceptions from rake tasks.
For rails runner, require the Rails integration from config/application.rb as shown in Rails Runner Support.
Generate an initializer with the integration you need:
bundle exec rails generate exceptify:install --sidekiqor:
bundle exec rails generate exceptify:install --resqueor:
bundle exec rails generate exceptify:install --solid-queueThe Solid Queue integration hooks into Active Job failure notifications and reports unhandled exceptions for jobs using the solid_queue adapter. It preserves Active Job failure behavior, so failed jobs still end up in Solid Queue's failed executions table and retry_on or discard_on handlers still run normally.
If a job system is not integrated, report exceptions directly:
class RebuildSearchIndexJob
def perform(account_id)
RebuildSearchIndex.call(account_id)
rescue => exception
Exceptify.notify_exception(
exception,
data: {
job: self.class.name,
account_id: account_id
}
)
raise
end
endUse the Rack middleware directly when you are not using the Rails generator, or when you need Rack-only options such as ignore_cascade_pass.
require "exceptify"
use Exceptify::Rack,
email: {
email_prefix: "[RACK ERROR] ",
sender_address: %("Exceptify" <notifier@example.com>),
exception_recipients: %w[exceptions@example.com]
},
ignore_exceptions: ["Sinatra::NotFound"],
ignore_cascade_pass: trueOptions passed directly to Exceptify::Rack are local to that middleware instance. To use one application-wide configuration, configure Exceptify once and mount the middleware without notifier options:
require "exceptify"
Exceptify.configure do |config|
config.add_notifier :email, {
sender_address: %("Exceptify" <notifier@example.com>),
exception_recipients: %w[exceptions@example.com]
}
end
use Exceptify::RackSinatra users can also review the example application.
This repository is the current home for the exceptify gem. Maintenance focuses on modern Ruby and Rails support, clear documentation, and practical bug fixes around the Exceptify API.
Issues and pull requests are welcome when they include enough context to reproduce the behavior. The current refactoring direction is tracked in REFACTORING.md.
Install dependencies:
bundle installRun tests:
bundle exec rake testOpen a console with the gem loaded:
bundle exec rake consoleBuild the gem locally:
bundle exec rake buildPull requests and issues are welcome. Please read the Contributing Guide and follow the Code of Conduct.
After publishing a version to RubyGems.org, commit and tag the exact repo state
that produced the gem. RubyGems.org versions are immutable, so 1.0.0 should
point to the code and gemspec that were pushed.
git add exceptify.gemspec Gemfile.lock .github/workflows/gem-push.yml RELEASING.md
git commit -m "Release 1.0.0"
git tag v1.0.0
git push origin main
git push origin v1.0.0Do not add exceptify-1.0.0.gem unless gem build artifacts are intentionally
stored in git.
Released under the MIT license.
Maintainer: @lukin-io