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_toin 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.
![]() |
![]() |
![]() |
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/eventsor/solid_observer/storageredirect 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 hasconfig.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, setconfig.ui_base_controllerto its name (e.g."Api::BaseController"). The engine uses this only to detectActionController::APIancestry; if detected, it includesActionView::Layouts,ActionView::Rendering, andActionController::RequestForgeryProtectionso layouts and CSRF forms render. - Host-app callbacks are not inherited. SolidObserver does not run your host app's
before_actions or authentication. Useui_username/ui_passwordfor the engine's built-in HTTP Basic Auth. - Auth misconfiguration is fail-open, but loud. If you set
ui_usernamebutui_passwordresolves tonil(e.g. an unset ENV var), the UI ships unauthenticated rather than locking everyone out — and the engine logs aWARNINGat 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:discardorjobs: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:purgedeletes 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_queuedatabase 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 returnsniland 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_pathsrationale).
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:
- good first issue — Great for newcomers
- help wanted — We'd love your help
Please follow the code of conduct.
License
The gem is available as open source under the terms of the MIT License.

