SolidObserver is a production-grade observability solution for Rails 8's Solid Stack. v0.5.0 covers Solid Queue, Solid Cache, and Solid Cable with a unified Web UI dashboard, CLI tools, metrics collection, and distributed tracing support.
Features (v0.5.0)
| Solid Queue | Solid Cache | Solid Cable | |
|---|---|---|---|
| Web UI Dashboard | Queue stats, jobs browser, events log | Hit rate, ops/sec, error rate, avg duration | Broadcasts, rejection rate, trends |
| Storage footprint | DB size, event counts | SolidCache table size, row counts | Message count + backlogs |
| Activity trends | Sparklines (Performed, Ready, Failed) | Activity trend sparklines | Broadcast/rejection sparklines |
| Stability indicator | Stable / Degraded / Critical badge | Stability pill badge | Stable / Degraded / Critical (hybrid) |
| Operational controls | Retry / discard failed jobs | Prune expired entries, clear all entries | Trim expired messages |
| CLI tools | status, jobs:list/show/retry/discard | cache:prune, cache:clear | cable:trim |
| Privacy | Job arguments excluded from persisted events | Keys and values never shown | Broadcasting names hashed (SHA256) |
| Operating modes | Real-time (no DB) or persistence (full history) | Persistence mode only | Persistence mode only |
Additional: ๐ APM distributed tracing ยท โก buffered writes ยท ๐ก๏ธ Docker/CI/K8s safe boot
Requirements
- Ruby 3.2+
- Rails 8.0+
- Solid Queue (configured with
connects_toin all environments) - Solid Cache (optional โ enable with
config.observe_cache = true) - Solid Cable (optional โ enable with
config.observe_cable = true)
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:installSolidObserver 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
endThat'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.
โ ๏ธ PostgreSQL / MySQL hosts: The generator writes a self-contained
solid_observer_queueblock withadapter: sqlite3. Reviewconfig/database.ymlbefore runningdb:createโ do not merge<<: *defaultinto the observer entry, as that pulls the host adapter in anddb:createwill fail. See Multi-adapter setup for the correct pattern.
bin/rails solid_observer:install:migrations
bin/rails db:create
bin/rails db:migrateFor SQLite-default apps, no further configuration is needed โ persistence is the default storage_mode.
Quick Start
Check Queue Status
bin/rails solid_observer:statusOutput:
๐ 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:storageOutput:
๐พ 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"Production dashboard exposure is the host application's responsibility. If you
enable and mount the dashboard in production, wrap the mount in your existing
admin authentication/authorization constraint (example shown for apps that
already provide an authenticate route helper):
# config/routes.rb
authenticate :user, ->(user) { user.admin? } do
mount SolidObserver::Engine, at: "/solid_observer"
endAuth 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. Auth is enabled only when both are set |
ui_base_controller |
"ApplicationController" |
Detect API-only apps to include rendering modules |
For production hardening (fail-loud on missing env var vs. fail-open), see Configuration below.
Caveats
- Realtime mode โ Events and Storage navigation links are hidden. Direct visits redirect with a flash alert.
-
API-only apps โ the Web UI works without manual configuration. The engine ships its own Cookies/Session/Flash middleware stack scoped to
/solid_observer/*. -
Host-app callbacks are not inherited. Use
ui_username/ui_passwordfor built-in HTTP Basic Auth. -
Auth misconfiguration is fail-open, but loud. If
ui_usernameis set butui_passwordresolves tonil, the UI ships unauthenticated and logs a bootWARNING. -
HTTP Basic Auth requires both credentials. Setting only
ui_usernameor onlyui_passworddisables built-in auth; protect production mounts at the host-app route boundary.
See the full component breakdown in Components below.
Components
Solid Queue Observability
Live queue stats, job browser (all states: ready/scheduled/claimed/failed), events log, time-range selector (15m โ 14d), sparklines for Performed/Ready/Failed, stability indicator, retry/discard with confirmation dialogs. Realtime and persistence modes supported.
![]() |
![]() |
![]() |
![]() |
CLI commands (Solid Queue):
bin/rails solid_observer:status # Queue overview
bin/rails solid_observer:jobs:list # All active jobs
bin/rails "solid_observer:jobs:list[failed]" # Failed jobs
bin/rails "solid_observer:jobs:show[JOB_ID]" # Job details
bin/rails "solid_observer:jobs:retry[JOB_ID]" # Retry failed job
bin/rails "solid_observer:jobs:discard[JOB_ID]" # Discard failed job
bin/rails solid_observer:buffer:flush # Flush event buffer
bin/rails solid_observer:buffer:clear # Clear buffer without savingSolid Cache Observability
Cache dashboard shows hit rate, operations/sec, error rate, average duration, storage footprint, activity trend sparklines, and a stability pill. Cache controls page provides prune and clear operations. Keys and values are never shown โ only aggregate metrics are collected.
![]() |
![]() |
Enabling Cache observability:
SolidCache must be configured in your host app (Rails 8 default). Then enable in the SolidObserver initializer:
SolidObserver.configure do |config|
config.observe_cache = true # default: false
endsolid_cache_enabled? is true when both observe_cache = true and SolidCache is available in the host app.
CLI commands (Solid Cache):
bin/rails solid_observer:cache:clear # Clear all SolidCache entries (with confirmation)
bin/rails solid_observer:cache:prune # Prune expired SolidCache entriesSolid Cable Observability
Cable observability is optional. Enable with config.observe_cable = true. Requires SolidCable in the host app. SolidObserver does not add any SolidCable dependency.
Cable dashboard (/solid_observer/cable_dashboard) shows broadcast/rejection trends, a stability indicator (hybrid: event + backlog signals), and recent safe events. Storages page includes Cable telemetry rows. Guarded trim controls: UI button for โค1,000 trimmable messages; solid_observer:cable:trim Rake task for larger backlogs. Broadcasting names are stored as SHA256 digests โ raw names are never persisted.
Enabling Cable observability:
SolidObserver.configure do |config|
config.observe_cable = true # default: false
# config.cable_sampling_rate = 0.1 # Sample 10% of broadcast events (default: 0.1)
# config.cable_rejection_threshold = 0.05 # Rejection rate threshold for Degraded (default: 0.05)
# config.cable_backlog_threshold = 0.10 # Backlog ratio threshold (default: 0.10)
# config.cable_error_threshold = 0.0 # Error rate threshold (default: 0.0)
endsolid_cable_enabled? is true when both observe_cable = true and SolidCable is available in the host app.
CLI commands (Solid Cable):
bin/rails solid_observer:cable:trim # Trim expired Cable messages (with confirmation)Cable dashboard screenshots are not yet available โ capture from a host app with SolidCable configured.
Storage
The Storage page aggregates per-component health rows: Queue Observer database, Cache Observer, SolidCache table sizes, and Cable telemetry. Each row reports size, record counts, and a status indicator.
For adapter notes and multi-adapter setup, see Database Setup below.
Storage unavailable diagnostics
The dashboard is admin/developer-facing. When SolidObserver storage is not reachable, the current default 503 error page includes the raw exception class and message so maintainers can diagnose adapter, credential, migration, or database availability problems. Do not expose the dashboard to untrusted users.
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
# Enable cache monitoring (default: false; requires SolidCache in host app)
config.observe_cache = true
# Enable cable monitoring (default: false; requires SolidCable in host app)
config.observe_cable = true
config.cable_sampling_rate = 0.1 # Sample 10% of broadcast events
config.cable_rejection_threshold = 0.05 # Rejection rate โ Degraded stability
config.cable_backlog_threshold = 0.10 # Backlog ratio threshold
config.cable_error_threshold = 0.0 # Error rate threshold
# 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
endAPM Integration
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
}
endWhen configured, all job events will include your correlation ID, allowing you to trace jobs back to the originating request.
Production hardening (recommended)
# 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
endCLI 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 |
solid_observer:cache:clear |
Clear all SolidCache entries (with confirmation) |
solid_observer:cache:prune |
Prune expired SolidCache entries |
solid_observer:cable:trim |
Trim expired Solid Cable messages |
Note: Storage commands manage SolidObserver's storage (event logs, metrics, snapshots) โ not Solid Queue's jobs. Cache commands operate on SolidCache entries in the host app's cache store.
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 |
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)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:
adapter: sqlite3
pool: 5
timeout: 5000
database: storage/<%= Rails.env %>_solid_observer_queue.sqlite3
migrations_paths: db/solid_observer_migrateNote: 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.
Multi-adapter setup
If your host application uses PostgreSQL and you want SolidObserver to use SQLite, keep the solid_observer_queue block self-contained โ do not rely on <<: *default. The generator produces a self-contained block with explicit adapter: sqlite3 and migrations_paths, which is safe on any host adapter. On a PostgreSQL host, merging <<: *default without an explicit adapter override pulls the PG adapter in and fails at db:create. For multi-adapter connections, omit the merge entirely:
# config/database.yml
default: &default
adapter: postgresql
encoding: unicode
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
development:
primary:
<<: *default
database: my_app_development
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_migrateApply the same pattern to test: and production:. For production with SQLite, ensure the storage/ path is on persistent disk.
Bundler note (PG-only hosts): if your Gemfile does not already include sqlite3, add it:
gem "sqlite3", "~> 2.0"migrations_paths is recommended to isolate SolidObserver's migrations from the host's primary db/migrate/ folder. When migrations_paths is set on solid_observer_queue, bin/rails solid_observer:install:migrations copies migrations to that path automatically.
Roadmap
| 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 | โ Released |
| v0.4.0 | Solid Cache monitoring | โ Released |
| v0.5.0 | Solid Cable monitoring | โ Current |
| 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 reekTroubleshooting
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.
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
solid_observer_queue:
adapter: sqlite3
database: storage/development_solid_observer_queue.sqlite3
migrations_paths: db/solid_observer_migrateContributing
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.







