0.0
The project is in a healthy, maintained state
Production-grade observability for Rails 8's Solid Stack. Monitor ActiveJob performance, track queue metrics, and debug issues with zero external dependencies. Built-in CLI, retention policies, and APM integrations.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Runtime

>= 8.0
 Project Readme

SolidObserver

Version License Tests Coverage


Dashboard overview

SolidObserver is a production-grade observability solution for Rails 8's Solid Stack. Starting with Solid Queue monitoring in v0.3.0, it provides unified visibility into your background job processing with a Web UI dashboard, CLI tools, metrics collection, and distributed tracing support.

Features (v0.3.0)

  • 🖥️ Web UI Dashboard — Live queue stats, job browser, event log, and storage info
  • 📊 Real-time Queue Status — Monitor jobs across all states (ready, scheduled, claimed, failed)
  • 🔍 Job Management CLI — List, inspect, retry, and discard failed jobs
  • 💾 Storage Monitoring — Track database size and event counts
  • 🔗 Distributed Tracing — Correlate jobs with APM tools (Datadog, Sentry, OpenTelemetry)
  • High Performance — Buffered writes, configurable sampling, minimal overhead
  • 🛡️ Production Ready — Docker/CI/K8s safe boot, automatic cleanup, size limits, retention policies
  • 🚀 Two Operating Modes — Real-time (no migrations) or persistence (full event history)

Requirements

  • Ruby 3.2+
  • Rails 8.0+
  • Solid Queue (properly configured for all environments)

Note: Ensure Solid Queue is configured with connects_to in all environments, not just production. See Troubleshooting if you encounter database connection issues.

Installation

Add to your Gemfile:

gem "solid_observer"
bundle install
rails generate solid_observer:install

SolidObserver supports two operating modes. Choose the one that fits your needs:

Real-time Mode (no migrations needed)

Get queue monitoring and job management instantly — no database setup required. SolidObserver queries Solid Queue directly.

# config/initializers/solid_observer.rb
SolidObserver.configure do |config|
  config.storage_mode = :realtime
end

That's it. You now have access to queue status, job listing, retry, and discard commands.

Persistence Mode (default)

Store event history, metrics, and storage snapshots in a dedicated observer database. This gives you everything in real-time mode plus long-term event tracking, buffered writes, and retention-based cleanup. The install generator defaults to SQLite; the database can use any Rails-supported adapter for record persistence, and storage-size monitoring is implemented for SQLite, PostgreSQL/PostGIS, MySQL, and Trilogy. See Database Setup below.

If your host app uses a different adapter than SQLite (e.g. PostgreSQL or MySQL), see Multi-adapter setup before running these commands.

bin/rails solid_observer:install:migrations
bin/rails db:create
bin/rails db:migrate

For SQLite-default apps, no further configuration is needed — persistence is the default storage_mode.

Quick Start

Check Queue Status

bin/rails solid_observer:status

Output:

📊 SolidObserver Status
==================================================

🚀 Solid Queue

| Metric    | Value |
|-----------|-------|
| Ready     | 42    |
| Scheduled | 15    |
| Claimed   | 3     |
| Failed    | 2     |
| Workers   | 4     |

📋 Queue Depths

| Queue      | Jobs |
|------------|------|
| default    | 38   |
| mailers    | 12   |
| critical   | 10   |

Manage Jobs

# List jobs (defaults to ready jobs)
bin/rails solid_observer:jobs:list

# List failed jobs
bin/rails "solid_observer:jobs:list[failed]"

# Filter by status, queue, job class, and limit
bin/rails "solid_observer:jobs:list[failed,mailers]"
bin/rails "solid_observer:jobs:list[ready,default,UserNotificationJob,50]"

# Inspect a specific job
bin/rails "solid_observer:jobs:show[JOB_ID]"

# Retry a failed job
bin/rails "solid_observer:jobs:retry[JOB_ID]"

# Discard a failed job
bin/rails "solid_observer:jobs:discard[JOB_ID]"

Events Page Filters (Web UI)

Filter dropdowns on /solid_observer/events (job class and queue) are cached for 1 minute by default and scoped to the configured retention window. Tune the cache TTL with config.filter_cache_ttl.

Check Storage (Persistence Mode)

Storage monitoring is cross-adapter: SQLite, PostgreSQL, MySQL, and Trilogy are supported. SolidObserver uses adapter-native SQL queries to measure size (not filesystem File.size), so production deployments report real values even when the database is remote.

bin/rails solid_observer:storage

Output:

💾 Storage Status

| Component | Size    | Events | Usage | Status |
|-----------|---------|--------|-------|--------|
| Queue     | 12.5 MB | 45,231 | 1.2%  | ✓      |

Configuration:
  Retention: 30 days
  Max size:  1024.0 MB per database
  Warning:   80% threshold

Web UI Dashboard

SolidObserver ships with a zero-dependency Web UI (no asset pipeline, no JS framework) at /solid_observer.

Dashboard overview Dashboard jobs and events Dashboard storage and details

Mount

The install generator mounts it for you. To mount manually:

# config/routes.rb
mount SolidObserver::Engine, at: "/solid_observer"

Configuration

SolidObserver.configure do |config|
  config.ui_enabled          = !Rails.env.production?  # default: true outside production
  config.ui_username         = "admin"                 # HTTP Basic Auth: BOTH username AND password must be set
  config.ui_password         = ENV["SOLID_OBSERVER_PASSWORD"]
  config.ui_base_controller  = "ApplicationController" # name of your host app's base controller (used for API-only detection)
end
Option Default Purpose
ui_enabled !Rails.env.production? Master switch for the Web UI
ui_username / ui_password nil HTTP Basic Auth credentials. Auth is enabled only when both are set; if either is missing or nil, the UI is unauthenticated
ui_base_controller "ApplicationController" Name of your host app's base controller. SolidObserver does not inherit from it; the value is used to detect API-only apps so the engine can include the rendering modules its dashboard needs

Live polling cadence is hardcoded at 5s.

Production hardening (recommended)

The snippet above silently disables auth if ENV["SOLID_OBSERVER_PASSWORD"] is unset (fail-open with a boot warning — see Caveats). For production, prefer one of these patterns so a missing env var fails loudly at boot rather than shipping an unauthenticated UI:

# Option A: fail at boot if the password env var is missing
SolidObserver.configure do |config|
  config.ui_username = "admin"
  config.ui_password = ENV.fetch("SOLID_OBSERVER_PASSWORD")  # raises KeyError if unset
end

# Option B: only enable auth when the env var is present (no auth otherwise)
SolidObserver.configure do |config|
  if (password = ENV["SOLID_OBSERVER_PASSWORD"]).present?
    config.ui_username = "admin"
    config.ui_password = password
  end
end

Option A is best when the UI must always be authenticated in production (a missing env var crashes boot — you find out immediately). Option B is best when the UI is optional in some environments.

Caveats

  • Realtime mode (storage_mode: :realtime) — Events and Storage navigation links are hidden. Direct visits to /solid_observer/events or /solid_observer/storage redirect to the dashboard with a flash alert (This page is not available in real-time mode).
  • API-only apps — the Web UI works in API-only Rails apps without manual configuration. SolidObserver's engine ships its own Cookies/Session/Flash middleware stack scoped to /solid_observer/* requests, so the dashboard renders, flash messages display, and retry/discard CSRF forms work even when the host app has config.api_only = true. The host app's middleware stack is not modified.
  • Custom API base controller — if your host app's API base controller is not named ApplicationController, set config.ui_base_controller to its name (e.g. "Api::BaseController"). The engine uses this only to detect ActionController::API ancestry; if detected, it includes ActionView::Layouts, ActionView::Rendering, and ActionController::RequestForgeryProtection so layouts and CSRF forms render.
  • Host-app callbacks are not inherited. SolidObserver does not run your host app's before_actions or authentication. Use ui_username / ui_password for the engine's built-in HTTP Basic Auth.
  • Auth misconfiguration is fail-open, but loud. If you set ui_username but ui_password resolves to nil (e.g. an unset ENV var), the UI ships unauthenticated rather than locking everyone out — and the engine logs a WARNING at boot naming the missing credential. Verify both are set in production.

Configuration

After installation, configure SolidObserver in config/initializers/solid_observer.rb:

SolidObserver.configure do |config|
  # Storage Mode (:persistence or :realtime)
  # :persistence — stores events, metrics, snapshots (requires migrations)
  # :realtime    — live monitoring only, no database needed
  config.storage_mode = :persistence  # default

  # Enable queue monitoring (default: true)
  config.observe_queue = true

  # Data Retention (persistence mode only)
  config.event_retention = 30.days    # Keep events for 30 days
  config.metrics_retention = 90.days  # Keep metrics for 90 days

  # Database Limits (persistence mode only)
  config.max_db_size = 1.gigabyte     # Maximum database size
  config.warning_threshold = 0.8      # Warn at 80% capacity

  # Performance Tuning (persistence mode only)
  config.buffer_size = 1000           # Buffer before flushing to DB
  config.flush_interval = 10.seconds  # Flush interval
  config.sampling_rate = 1.0          # 1.0 = capture all events
end

APM Integration

Connect SolidObserver with your Application Performance Monitoring tool for distributed tracing:

SolidObserver.configure do |config|
  # Datadog APM
  config.correlation_id_generator = -> {
    Datadog::Tracing.active_trace&.id
  }

  # Sentry
  config.correlation_id_generator = -> {
    Sentry.get_current_scope&.transaction&.trace_id
  }

  # OpenTelemetry
  config.correlation_id_generator = -> {
    OpenTelemetry::Trace.current_span&.context&.trace_id
  }

  # Custom implementation
  config.correlation_id_generator = -> {
    Thread.current[:request_id] || SecureRandom.uuid
  }
end

When configured, all job events will include your correlation ID, allowing you to trace jobs back to the originating request.

CLI Reference

Available in both modes (real-time and persistence):

Command Description
solid_observer:status Show queue status overview
solid_observer:jobs:list[status,queue,class,limit] List jobs with optional filters
solid_observer:jobs:show[ID] Show job details
solid_observer:jobs:retry[ID] Retry a failed job
solid_observer:jobs:discard[ID] Discard a failed job

Persistence mode only:

Command Description
solid_observer:storage Show storage statistics
solid_observer:buffer:flush Force flush event buffer to database
solid_observer:buffer:clear Clear buffer without saving
solid_observer:storage:cleanup Run retention-based cleanup
solid_observer:storage:purge Delete ALL SolidObserver data

Note: Storage commands manage SolidObserver's storage (event logs, metrics, snapshots) — not Solid Queue's jobs. To manage jobs, use jobs:discard or jobs:retry.

Jobs List Arguments

Arguments are positional: [status, queue, job_class, limit]

Position Description Example
1st Filter by status failed, ready, scheduled
2nd Filter by queue name default, mailers
3rd Filter by job class UserNotificationJob
4th Max results (default: 20) 50
# Examples
bin/rails solid_observer:jobs:list                           # All ready jobs
bin/rails "solid_observer:jobs:list[failed]"                 # Failed jobs
bin/rails "solid_observer:jobs:list[ready,mailers]"          # Ready jobs in mailers queue
bin/rails "solid_observer:jobs:list[failed,,,50]"            # 50 failed jobs (skip queue/class)

Buffer & Storage Management (Persistence Mode)

# Flush in-memory buffer to database
bin/rails solid_observer:buffer:flush

# Clear buffer without saving (loses pending events!)
bin/rails solid_observer:buffer:clear

# Run cleanup based on retention policy (default: 30 days)
bin/rails solid_observer:storage:cleanup

# Delete ALL SolidObserver data (events + snapshots, interactive confirmation)
bin/rails solid_observer:storage:purge

Important: storage:purge deletes SolidObserver's monitoring data, NOT your Solid Queue jobs. Your queued jobs remain safe.

Database Setup (Persistence Mode)

Tip: If you're using real-time mode (storage_mode: :realtime), you can skip this section entirely — no database setup is needed.

SolidObserver works with any main application database — PostgreSQL, MySQL, or SQLite.

For its own monitoring data, the install generator defaults to a separate SQLite database — file-based, no extra infrastructure needed. The example below is what rails generate solid_observer:install produces:

# config/database.yml (generator default)
solid_observer_queue:
  <<: *default
  adapter: sqlite3
  database: storage/<%= Rails.env %>_solid_observer_queue.sqlite3

Note: SQLite is the generator default, not a requirement. The solid_observer_queue database can use any Rails-supported adapter for record persistence. Adapter-native storage-size monitoring is currently implemented for SQLite, PostgreSQL/PostGIS, MySQL, and Trilogy. On other adapters, the size query returns nil and the engine logs a single [SolidObserver] Unknown adapter for DatabaseSize: … warning — record persistence still works, but the size column on the dashboard will be empty until adapter support is added.

Multi-adapter setup

If your host application uses one database adapter (e.g. PostgreSQL) and you want SolidObserver to use a different adapter (e.g. SQLite — for isolation, simpler ops, or to avoid loading observability traffic onto your primary DB), keep the solid_observer_queue block self-contained — do not rely on <<: *default. The generator default (shown above) uses <<: *default with an explicit adapter: sqlite3 override; that is safe on SQLite-primary hosts where the anchor is also SQLite. On a PostgreSQL host, merging <<: *default without an explicit adapter override pulls the PG adapter into the observer connection and fails at db:create with PG::SyntaxError (PG treating a SQLite file path as a database name). For multi-adapter connections, omit the merge entirely, as shown below.

# config/database.yml
default: &default
  adapter: postgresql           # host's adapter
  encoding: unicode
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>

development:
  primary:
    <<: *default
    database: my_app_development
  # ... cache / queue / cable on PG ...

  solid_observer_queue:
    adapter: sqlite3            # explicit override — do NOT merge *default
    pool: 5
    timeout: 5000
    database: storage/development_solid_observer_queue.sqlite3
    migrations_paths: db/solid_observer_migrate

Apply the same pattern to test: and production:. For production with SQLite, ensure the storage/ path is on persistent disk (not ephemeral container storage) — otherwise prefer PostgreSQL for the observer DB too.

Bundler note (PG-only hosts): if your Gemfile does not already include sqlite3, add it:

gem "sqlite3", "~> 2.0"

then bundle install. SolidObserver does not declare sqlite3 as a runtime dependency — adapter choice is yours.

migrations_paths is recommended to isolate SolidObserver's migrations from the host's primary db/migrate/ folder. This prevents Rails from running SolidObserver's migrations against your primary database (which would create an unused solid_observer_queue_events table there). When migrations_paths is set on solid_observer_queue, bin/rails solid_observer:install:migrations copies migrations to that path automatically — no manual mv required.

Roadmap

SolidObserver is actively developed. Here's what's coming:

Version Focus Status
v0.1.0 Solid Queue monitoring, CLI tools ✅ Released
v0.1.1 Real-time mode (no migrations needed) ✅ Released
v0.3.0 Web UI dashboard + stability hardening ✅ Current
v0.4.0 Solid Cache monitoring 🔜 Planned
v0.5.0 Solid Cable monitoring 🔜 Planned
v0.6.0 Cross-component correlation, health scores 🔜 Planned
v0.7.0 Alerting & notifications 🔜 Planned
v1.0.0 Production stable release 🎯 Goal

See GitHub Milestones for detailed plans.

Development

# Clone the repository
git clone https://github.com/bart-oz/solid_observer.git
cd solid_observer

# Install dependencies
bin/setup

# Run tests
bundle exec rspec

# Run linter
bundle exec standardrb

# Run code smell detector
bundle exec reek

Troubleshooting

Docker, CI, and Offline Boot

SolidObserver is designed to boot without a live database connection. During rails assets:precompile, CI runs without a database service, or Kubernetes init containers, the engine logs a single info message and skips subscriber activation:

[SolidObserver] Database not reachable at boot. Skipping subscriber activation.

No monkey-patching or environment-variable workarounds are needed. Once the application boots with a live database, the engine activates normally on the next restart.

"no such table: solid_queue_ready_executions"

This error means Solid Queue isn't configured to use the correct database in your environment.

Solution: Ensure connects_to is configured for all environments, not just production:

# config/environments/development.rb
config.solid_queue.connects_to = { database: { writing: :queue } }

# config/environments/test.rb
config.solid_queue.connects_to = { database: { writing: :queue } }

Multi-database setup

See also: Multi-adapter setup in the Database Setup section for the full pattern (explicit adapter: override, gem "sqlite3" Bundler note, migrations_paths rationale).

SolidObserver works with Rails multi-database configurations. Quick-reference example with PostgreSQL as your primary database and SQLite for SolidObserver's storage:

development:
  primary:
    adapter: postgresql
    database: myapp_development
    # ... PostgreSQL settings
  solid_observer_queue:
    adapter: sqlite3
    database: storage/development_solid_observer_queue.sqlite3
    migrations_paths: db/solid_observer_migrate

Contributing

Bug reports and pull requests are welcome on GitHub.

Check out issues labeled:

Please follow the code of conduct.

License

The gem is available as open source under the terms of the MIT License.