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.error reporter (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
  • /errors dashboard — 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:

  1. Add a migration creating error_track_error_events and error_track_occurrences
  2. Add config/initializers/error_track.rb
  3. Mount the engine at /errors in config/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