DEV Community

Anjan
Anjan

Posted on • Originally published at Medium

Self-Hosted Error Tracking for Rails 8.1 + SolidQueue: A Complete Setup Guide

How to set up rails_error_dashboard with SolidQueue — capturing errors from controllers AND background jobs, with optional database isolation.


Rails 8.1 ships with SolidQueue as the default background job backend. No more Redis. No more Sidekiq licensing questions. Just your database.

But here's a question nobody talks about: what happens when your background jobs fail?

Sure, SolidQueue tracks failed executions. But do you get a dashboard? Severity classification? Trend charts? Notifications? Priority scoring?

Not out of the box.

I built rails_error_dashboard to solve exactly this — a self-hosted, open-source error tracking gem that works entirely inside your Rails process. No external services, no monthly fees, no data leaving your servers.

In this post, I'll walk you through setting it up with a Rails 8.1 app using SolidQueue, including the optional separate database configuration for production-grade isolation.

What We're Building

By the end of this guide, you'll have:

  • Automatic error capture from both controllers and SolidQueue jobs
  • A full error dashboard at /error_dashboard with analytics, severity scoring, and workflow management
  • Optional: errors stored in a separate database from your app data
  • Verified that SolidQueue's retry mechanism still works (errors are captured, not swallowed)

Job Health page - Rails Error Dashboard

Prerequisites

  • Ruby 3.2+ (tested with Ruby 4.0.1)
  • Rails 7.0+ (this guide uses Rails 8.1.2)
  • SolidQueue (ships with Rails 8.1 by default)

Part 1: Basic Setup (Same Database)

Step 1: Add the Gem

# Gemfile
gem "rails_error_dashboard"
Enter fullscreen mode Exit fullscreen mode
bundle install
Enter fullscreen mode Exit fullscreen mode

Step 2: Run the Installer

rails generate rails_error_dashboard:install
rails db:migrate
Enter fullscreen mode Exit fullscreen mode

That's it. Seriously. The installer:

  • Creates the initializer at config/initializers/rails_error_dashboard.rb
  • Copies 19 migrations for error logs, occurrences, baselines, comments, and more
  • Mounts the engine at /error_dashboard in your routes

Step 3: Configure SolidQueue for Development

Rails 8.1 only configures SolidQueue as the queue adapter in production by default. For development testing, add it explicitly:

# config/environments/development.rb
config.active_job.queue_adapter = :solid_queue
config.solid_queue.connects_to = { database: { writing: :queue } }
Enter fullscreen mode Exit fullscreen mode

You also need the multi-database setup for the queue database in development:

# config/database.yml
development:
  primary:
    <<: *default
    database: storage/development.sqlite3
  queue:
    <<: *default
    database: storage/development_queue.sqlite3
    migrations_paths: db/queue_migrate
Enter fullscreen mode Exit fullscreen mode

Then load the queue schema:

rails db:schema:load:queue
Enter fullscreen mode Exit fullscreen mode

Step 4: Start the Server with SolidQueue

The simplest way to run both Puma and SolidQueue together is the Puma plugin:

SOLID_QUEUE_IN_PUMA=1 rails server
Enter fullscreen mode Exit fullscreen mode

This runs the SolidQueue supervisor (dispatcher + worker) inside the Puma process — perfect for development and single-server deployments.

Alternatively, run them separately:

# Terminal 1
rails server

# Terminal 2
bin/jobs
Enter fullscreen mode Exit fullscreen mode

Step 5: Verify It Works

Visit http://localhost:3000/error_dashboard and log in with the default credentials (gandalf / youshallnotpass). Change these in your initializer before deploying to production.

To test error capture, create a simple failing job:

# app/jobs/test_error_job.rb
class TestErrorJob < ApplicationJob
  queue_as :default

  def perform
    raise RuntimeError, "Test error from SolidQueue"
  end
end
Enter fullscreen mode Exit fullscreen mode

Enqueue it:

rails runner "TestErrorJob.perform_later"
Enter fullscreen mode Exit fullscreen mode

Within seconds, the error should appear in your dashboard with:

  • Error type and message
  • Full backtrace
  • Severity classification (auto-detected)
  • Occurrence count
  • Priority score

How Error Capture Works

The gem uses a dual-layer approach to ensure no error goes uncaptured:

Layer 1: Rails Error Subscriber

Rails 7+ introduced Rails.error.subscribe — a built-in error reporting API. The gem subscribes to this, which means it automatically captures errors from:

  • Controllers (via rescue_from and error handling)
  • ActiveJob (SolidQueue, Sidekiq, etc.)
  • Anywhere you use Rails.error.handle or Rails.error.record
# This happens automatically — you don't need to do anything
Rails.error.subscribe(RailsErrorDashboard::ErrorReporter.new)
Enter fullscreen mode Exit fullscreen mode

Layer 2: Rack Middleware

A lightweight Rack middleware sits at the top of the middleware stack as a final safety net. If an exception escapes all the way up, it captures it and then re-raises it so Rails can still render the appropriate error page.

# Simplified version of what the middleware does
def call(env)
  @app.call(env)
rescue => error
  Rails.error.report(error, handled: false, source: "middleware")
  raise  # Always re-raise!
end
Enter fullscreen mode Exit fullscreen mode

The Critical Rule: Always Re-raise

This is the most important design decision in the entire gem. When we capture an error from a background job, we never swallow the exception. We report it and re-raise it.

Why? Because of Sentry issue #1173. Sentry's Sidekiq middleware once swallowed exceptions, which meant Sidekiq thought failed jobs had succeeded — retries never happened. Data was silently lost.

We verified this explicitly: after our gem captures a SolidQueue job error, the error still appears in solid_queue_failed_executions. SolidQueue's retry mechanism is fully intact.

Part 2: Separate Database Setup (Production-Grade)

For production apps, you might want error data in a separate database. Benefits:

  • Performance isolation — high-frequency error writes don't contend with your app's queries
  • Independent scaling — put your error DB on a different server
  • Different retention — aggressive cleanup of old errors without touching app data
  • Security — separate access controls and backup schedules

Step 1: Add the Database Configuration

# config/database.yml
development:
  primary:
    <<: *default
    database: storage/development.sqlite3
  queue:
    <<: *default
    database: storage/development_queue.sqlite3
    migrations_paths: db/queue_migrate
  error_dashboard:
    <<: *default
    database: storage/development_error_dashboard.sqlite3
    migrations_paths: db/error_dashboard_migrate

production:
  primary:
    <<: *default
    database: storage/production.sqlite3
  queue:
    <<: *default
    database: storage/production_queue.sqlite3
    migrations_paths: db/queue_migrate
  error_dashboard:
    <<: *default
    database: storage/production_error_dashboard.sqlite3
    migrations_paths: db/error_dashboard_migrate
Enter fullscreen mode Exit fullscreen mode

For PostgreSQL in production (recommended):

production:
  primary:
    adapter: postgresql
    database: myapp_production
    # ... your existing config

  queue:
    adapter: postgresql
    database: myapp_queue_production
    # ...

  error_dashboard:
    adapter: postgresql
    database: myapp_errors_production
    pool: <%= ENV.fetch("RAILS_MAX_THREADS", 5) %>
    username: <%= ENV['ERROR_DB_USER'] %>
    password: <%= ENV['ERROR_DB_PASSWORD'] %>
    host: <%= ENV['ERROR_DB_HOST'] %>
    migrations_paths: db/error_dashboard_migrate
Enter fullscreen mode Exit fullscreen mode

Step 2: Install with Separate Database Flag

rails generate rails_error_dashboard:install \
  --separate-database \
  --database error_dashboard
Enter fullscreen mode Exit fullscreen mode

Or if you've already installed, update the initializer:

# config/initializers/rails_error_dashboard.rb
RailsErrorDashboard.configure do |config|
  config.use_separate_database = true
  config.database = :error_dashboard  # Must match your database.yml key
  # ...
end
Enter fullscreen mode Exit fullscreen mode

Step 3: Move Migrations

The installer places migrations in db/migrate/ by default. For a separate database, move them to match your migrations_paths:

mkdir -p db/error_dashboard_migrate
mv db/migrate/*rails_error_dashboard* db/error_dashboard_migrate/
Enter fullscreen mode Exit fullscreen mode

Step 4: Create and Migrate

rails db:create    # Creates all databases
rails db:migrate   # Runs migrations against the correct databases
Enter fullscreen mode Exit fullscreen mode

Step 5: Verify Isolation

You can verify the databases are truly isolated:

# Check error_dashboard DB has the gem's tables
sqlite3 storage/development_error_dashboard.sqlite3 ".tables"
# => rails_error_dashboard_error_logs, rails_error_dashboard_applications, ...

# Check primary DB has NO error tables
sqlite3 storage/development.sqlite3 ".tables"
# => ar_internal_metadata, schema_migrations (clean!)

# Check queue DB has only SolidQueue tables
sqlite3 storage/development_queue.sqlite3 ".tables"
# => solid_queue_jobs, solid_queue_failed_executions, ...
Enter fullscreen mode Exit fullscreen mode

Three databases, three concerns, zero cross-contamination.

What You Get Out of the Box

Once installed, the dashboard at /error_dashboard gives you:

  • Error list with search, filtering by type/severity/status/timeframe, and batch operations
  • Error details with full backtrace, occurrence timeline, and context
  • Analytics with 7-day trend charts, severity breakdown, and spike detection
  • Workflow — assign errors, set priority, add comments, snooze, resolve
  • Multi-channel notifications — Slack, Email, Discord, PagerDuty, webhooks (all optional)
  • Platform detection — automatically tags errors as iOS/Android/Web/API
  • Dark mode — because we're not savages

Production Checklist

Before deploying:

  1. Change the default credentials in your initializer
  2. Set retention_days to prevent unbounded growth (e.g., config.retention_days = 90)
  3. Consider async logging for high-throughput apps:
   config.async_logging = true
   config.async_adapter = :solid_queue  # Use your existing SolidQueue!
Enter fullscreen mode Exit fullscreen mode
  1. Configure notifications — at minimum, set up Slack or email for critical errors
  2. Consider error sampling if you're generating thousands of errors per day:
   config.sampling_rate = 0.1  # Log 10% of non-critical errors
Enter fullscreen mode Exit fullscreen mode

Common Gotchas

SolidQueue Queue Database Not Set Up

Rails 8.1 only configures the queue database in production. If you see Could not find table 'solid_queue_processes' in development, you need to:

  1. Add the queue database to your development config in database.yml
  2. Add config.solid_queue.connects_to = { database: { writing: :queue } } to your development environment
  3. Run rails db:schema:load:queue

Migrations Running Against the Wrong Database

When using separate databases, the gem's migrations must be in the directory matching your migrations_paths. If you see error tables in your primary database, move the migration files:

mv db/migrate/*rails_error_dashboard* db/error_dashboard_migrate/
Enter fullscreen mode Exit fullscreen mode

CSRF Errors When Testing with curl

If you're testing the enqueue endpoint with curl and getting 422 errors, that's Rails' CSRF protection working correctly. Use rails runner or the Rails console instead:

rails runner "TestErrorJob.perform_later"
Enter fullscreen mode Exit fullscreen mode

Try It Out

The gem is open source and free forever:

If you're running Rails 8.1 with SolidQueue, this is the easiest way to get production-grade error tracking without paying for a SaaS product. Five minutes of setup, zero monthly fees, and your data never leaves your servers.


Have questions or feedback? Open an issue on GitHub

Top comments (0)