ErrorTrack
A self-hosted, Sentry/Honeybadger-style error tracker for Rails — except
errors are logged to your own app's database, and shown at /errors.
No external service, no API keys, no extra infrastructure.
Features
- Automatically captures unhandled exceptions (Rack middleware) and errors
reported via Rails' built-in
Rails.errorreporter (retries, background rescues, etc.) - Manual reporting API:
ErrorTrack.notify(exception, context: {...}) - Groups repeated occurrences of the same error together (like Sentry "issues"), with a count, first-seen/last-seen timestamps
- Stores backtrace + request context (URL, method, params, user) per occurrence
/errorsdashboard — a single self-contained page, hand-rolled with vanilla JS and a Radix/Tailwind-inspired utility CSS. No Hotwire, no React, no npm, no CDN, no build step. It talks to plain JSON endpoints and polls lightly for live updates.- Works the same in full-stack Rails apps and API-only Rails apps
(
--api), and on older Rails versions that don't have Hotwire installed. CSRF/session usage is auto-detected and skipped entirely when there's no session store in the middleware stack, so it never blows up in an API-only app. - Zero required external services — just your existing database
Installation
Add to your Gemfile:
gem "error_track"
Then:
bundle install
rails generate error_track:install
rails db:migrate
This will:
- Add a migration creating
error_track_error_eventsanderror_track_occurrences - Add
config/initializers/error_track.rb - Mount the engine at
/errorsinconfig/routes.rb
Visit /errors in your running app — works identically whether your app
is a normal Rails app or config.api_only = true.
API-only apps + a separate frontend (e.g. Next.js)
If your Rails app is a pure JSON API and your actual product frontend
lives elsewhere (Next.js, etc.), /errors still works out of the box: it's
just an extra HTML route your Rails app happens to serve, independent of
your API routes and independent of your Next.js app. Nothing about your
API surface changes, and you don't need to build any dashboard UI into
your Next.js app - just visit yourapi.example.com/errors directly (behind
your own auth/VPN, see below).
If you'd rather it live inside your Next.js app instead, the JSON
endpoints (GET /errors.json, GET /errors/:id.json, etc. - see below)
are plain JSON and CORS-friendly, so you could point a custom Next.js page
at them instead of using the bundled HTML dashboard. That's more work than
just visiting /errors though, and isn't required.
Securing the dashboard
This is important — by default /errors has no auth and will show
backtraces, params, and any user info you configure. Restrict it in
config/initializers/error_track.rb, e.g.:
Rails.application.config.to_prepare do
ErrorTrack::ErrorsController.class_eval do
before_action :authenticate_admin! # your existing auth method
end
end
or gate the route itself in config/routes.rb:
authenticate :user, ->(u) { u.admin? } do
mount ErrorTrack::Engine => "/errors"
end
For an API-only app with no session-based auth at all, HTTP basic auth is often simplest:
Rails.application.config.to_prepare do
ErrorTrack::ErrorsController.class_eval do
http_basic_authenticate_with name: Rails.application.credentials.dig(:error_track, :user),
password: Rails.application.credentials.dig(:error_track, :password)
end
end
Manual reporting
begin
risky_call
rescue => e
ErrorTrack.notify(e, context: { user_id: current_user&.id, order_id: order.id })
# handle/re-raise as needed
end
JSON API
The dashboard is powered by these endpoints (mounted relative to wherever
you mount the engine, e.g. /errors):
| Method | Path | Description | ||
|---|---|---|---|---|
| GET | /errors.json |
List, supports `?filter=unresolved\ | resolved\ | alland?page=` |
| GET | /errors/:id.json |
Single error group + recent occurrences | ||
| POST | /errors/:id/resolve |
Mark resolved | ||
| POST | /errors/:id/reopen |
Reopen | ||
| DELETE | /errors/:id |
Delete an error group and its occurrences |
Configuration
See the generated config/initializers/error_track.rb for all options:
enabling/disabling capture per environment, ignored exception classes,
a current_user_resolver for attaching user info to occurrences, and
retention settings.
How grouping works
Errors are fingerprinted from the exception class + the first backtrace line inside your app (with line numbers normalized out), so the same bug firing 500 times shows up as one row with a count of 500 — not 500 rows.
License
MIT