solid_observer 0.1.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +84 -0
  3. data/README.md +241 -59
  4. data/app/assets/javascripts/solid_observer/live_poll.js +376 -0
  5. data/app/assets/stylesheets/solid_observer/application.css +18 -0
  6. data/app/controllers/concerns/solid_observer/paginatable.rb +17 -0
  7. data/app/controllers/concerns/solid_observer/require_persistence_mode.rb +19 -0
  8. data/app/controllers/concerns/solid_observer/require_solid_queue.rb +19 -0
  9. data/app/controllers/solid_observer/application_controller.rb +69 -0
  10. data/app/controllers/solid_observer/cache_dashboard_controller.rb +59 -0
  11. data/app/controllers/solid_observer/cache_operations_controller.rb +24 -0
  12. data/app/controllers/solid_observer/dashboard_controller.rb +122 -0
  13. data/app/controllers/solid_observer/events_controller.rb +50 -0
  14. data/app/controllers/solid_observer/jobs_controller.rb +85 -0
  15. data/app/controllers/solid_observer/storages_controller.rb +12 -0
  16. data/app/helpers/solid_observer/application_helper.rb +244 -0
  17. data/app/helpers/solid_observer/dashboard_helper.rb +58 -0
  18. data/app/models/solid_observer/cache_event.rb +15 -0
  19. data/app/models/solid_observer/cache_metric.rb +14 -0
  20. data/app/models/solid_observer/queue_event.rb +134 -0
  21. data/app/models/solid_observer/queue_metric.rb +1 -1
  22. data/app/models/solid_observer/storage_info.rb +4 -1
  23. data/app/presenters/solid_observer/execution_presenter.rb +50 -0
  24. data/app/views/layouts/solid_observer/application.html.erb +597 -0
  25. data/app/views/solid_observer/cache_dashboard/_charts.html.erb +40 -0
  26. data/app/views/solid_observer/cache_dashboard/_recent_events.html.erb +34 -0
  27. data/app/views/solid_observer/cache_dashboard/_summary.html.erb +39 -0
  28. data/app/views/solid_observer/cache_dashboard/index.html.erb +62 -0
  29. data/app/views/solid_observer/cache_operations/_confirm_clear.html.erb +6 -0
  30. data/app/views/solid_observer/cache_operations/index.html.erb +60 -0
  31. data/app/views/solid_observer/dashboard/_chart.html.erb +28 -0
  32. data/app/views/solid_observer/dashboard/_live_state.html.erb +20 -0
  33. data/app/views/solid_observer/dashboard/_queue_table.html.erb +34 -0
  34. data/app/views/solid_observer/dashboard/_right_now.html.erb +3 -0
  35. data/app/views/solid_observer/dashboard/_throughput.html.erb +32 -0
  36. data/app/views/solid_observer/dashboard/index.html.erb +143 -0
  37. data/app/views/solid_observer/errors/storage_unavailable.html.erb +27 -0
  38. data/app/views/solid_observer/events/index.html.erb +53 -0
  39. data/app/views/solid_observer/events/show.html.erb +47 -0
  40. data/app/views/solid_observer/jobs/index.html.erb +61 -0
  41. data/app/views/solid_observer/jobs/show.html.erb +71 -0
  42. data/app/views/solid_observer/shared/_empty_state.html.erb +5 -0
  43. data/app/views/solid_observer/shared/_pagination.html.erb +17 -0
  44. data/app/views/solid_observer/shared/_stat_card.html.erb +9 -0
  45. data/app/views/solid_observer/storages/show.html.erb +71 -0
  46. data/bin/quality_gate +95 -0
  47. data/config/routes.rb +22 -0
  48. data/db/migrate/20260424000001_add_composite_indexes_to_queue_events.rb +30 -0
  49. data/db/migrate/20260601000001_create_solid_observer_cache_events.rb +22 -0
  50. data/db/migrate/20260601000002_create_solid_observer_cache_metrics.rb +18 -0
  51. data/db/migrate/20260602000001_add_component_to_solid_observer_storage_infos.rb +8 -0
  52. data/lib/generators/solid_observer/install_generator.rb +12 -25
  53. data/lib/generators/solid_observer/templates/initializer.rb.tt +6 -6
  54. data/lib/solid_observer/base_metric.rb +1 -1
  55. data/lib/solid_observer/cache_event_buffer.rb +53 -0
  56. data/lib/solid_observer/cache_subscriber.rb +47 -0
  57. data/lib/solid_observer/chart_buffer.rb +83 -0
  58. data/lib/solid_observer/cli/base.rb +2 -2
  59. data/lib/solid_observer/cli/jobs.rb +2 -2
  60. data/lib/solid_observer/cli/status.rb +20 -2
  61. data/lib/solid_observer/cli/storage.rb +48 -44
  62. data/lib/solid_observer/configuration.rb +67 -38
  63. data/lib/solid_observer/correlation_id_resolver.rb +8 -6
  64. data/lib/solid_observer/engine.rb +110 -18
  65. data/lib/solid_observer/params/events_filter.rb +37 -0
  66. data/lib/solid_observer/params/jobs_filter.rb +35 -0
  67. data/lib/solid_observer/queries/events_query.rb +27 -0
  68. data/lib/solid_observer/queries/execution_finder.rb +42 -0
  69. data/lib/solid_observer/queries/job_executions_query.rb +73 -0
  70. data/lib/solid_observer/queue_event_buffer.rb +163 -25
  71. data/lib/solid_observer/queue_stats.rb +165 -19
  72. data/lib/solid_observer/services/cache_operations.rb +115 -0
  73. data/lib/solid_observer/services/cache_stats.rb +329 -0
  74. data/lib/solid_observer/services/cleanup_storage.rb +73 -41
  75. data/lib/solid_observer/services/database_size.rb +91 -0
  76. data/lib/solid_observer/services/flush_cache_event_buffer.rb +54 -0
  77. data/lib/solid_observer/services/flush_event_buffer.rb +31 -15
  78. data/lib/solid_observer/services/install_migrations.rb +49 -0
  79. data/lib/solid_observer/services/record_cache_event.rb +142 -0
  80. data/lib/solid_observer/services/record_cache_metric.rb +74 -0
  81. data/lib/solid_observer/services/record_event.rb +51 -14
  82. data/lib/solid_observer/services/storage_info_snapshot.rb +128 -0
  83. data/lib/solid_observer/services/ui_auth_check.rb +65 -0
  84. data/lib/solid_observer/subscriber.rb +15 -8
  85. data/lib/solid_observer/version.rb +1 -1
  86. data/lib/solid_observer.rb +7 -0
  87. data/lib/tasks/solid_observer.rake +39 -2
  88. metadata +77 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c2e83e38f795537bcacb9a431fe39543476b615cb205904fd5af324a4a1886e8
4
- data.tar.gz: cf501ff1b33535eb7a2b4f4858289bea3c93af7371af83bb867255b3f00de462
3
+ metadata.gz: a9c3e774c9276371b59f696226979b922772191e0531a4d9ba2dbb09b8e3e452
4
+ data.tar.gz: 570ae5aacef981358f68998d3df14bfc532e2af48cbdd21c6cd18ecbfa7132e7
5
5
  SHA512:
6
- metadata.gz: 58fb350eda47268f65f13b71ac4f317fe7a9cd6274a05c636a4d7a91477ad5a6269a652a7bb537255cbce8e690231c6bd33507530baf6fb626e672b738bf175b
7
- data.tar.gz: 9042759cdeca68bf9fd50a0c62493c6e9d057915b714cb8d68ef81f36028e6437c8f85dfeab8bac971419bc8471b147840051aa881a01871f720640f4abdfdb9
6
+ metadata.gz: 50684bd0b286fc9b93f77a57f5d0b60c3e0334b6d7804bf63307b8cdc05a78cab5bb586305b96617f5fe0e76f76cd89112c6cb6e059a45d190ca6dc7c49fa885
7
+ data.tar.gz: 9e456400209630d4e3b777355bf1d1a651e0bac1d90d6d40de4272c9cce8afb322bf5ff9afed10fe1a9e0911c7e84c15d02e6b2de3360e04f5d4d349bbf02f34
data/CHANGELOG.md CHANGED
@@ -1,3 +1,87 @@
1
+ ## [0.4.0] - 2026-06-03
2
+
3
+ Headline: **Solid Cache observability** — multi-component foundation, cache dashboard, operational controls, and shared storage health.
4
+
5
+ ### Added
6
+ - **Multi-component SolidObserver foundation** (SO-070) — refactored engine, configuration, and storage to support multiple Solid Stack components alongside Solid Queue. Introduced `observe_cache` config flag (default `false`).
7
+ - **Cache telemetry foundation** (SO-071) — `CacheSubscriber` hooks into `ActiveSupport::Notifications` cache events; collects hit/miss/write/delete/prune operations with duration and error tracking. Keys and values are never recorded.
8
+ - **Cache metrics aggregation** (SO-072) — `CacheMetricsAggregator` computes hit rate, operations/sec, error rate, and average duration from raw cache events; scoped to configurable time windows.
9
+ - **Multi-component storage health** (SO-073) — Storage page aggregates Queue Observer DB, Cache Observer, and SolidCache table sizes into unified per-component rows with status indicators.
10
+ - **Cache operational controls** (SO-074) — Cache controls dashboard page; `solid_observer:cache:clear` and `solid_observer:cache:prune` rake tasks; CSRF-protected prune/clear actions in the Web UI.
11
+ - **Cache dashboard** (SO-075) — `/solid_observer/cache_dashboard` — hit rate, ops/sec, error rate, avg duration, storage footprint, activity trend sparklines, and stability pill.
12
+ - **Cache overview activity trends and health parity** (SO-077) — activity trend sparklines on the cache dashboard; stability indicator parity with the Queue dashboard.
13
+
14
+ ### Fixed
15
+ - **SolidCache storage snapshot TypeError** (SO-076) — `StorageInfoSnapshot` raised `TypeError: nil can't be coerced into Integer` on PostgreSQL hosts when SolidCache table names resolved to `nil`. Fixed with a nil-table-name guard on the PG size query path.
16
+ - **Failed-job error payload read as Hash** (SO-079) — `Jobs::Show` raised `NoMethodError` when the serialised `SolidQueue::FailedExecution#error` payload was a plain `Hash` instead of an `ActiveSupport::HashWithIndifferentAccess`. Now reads both representations safely.
17
+
18
+ ### Changed
19
+ - **Cross-tab styling consistency** (SO-078) — unified badge colours, font sizes, and border radii across Queue and Cache dashboard tabs so both surfaces share the same visual language.
20
+ - **Dashboard headers, select heights, and Storage nav** (SO-080) — normalised `<h1>` sizing, select control heights, and Storage navigation link to be consistent across all component tabs; Storage promoted to top-level nav item.
21
+
22
+ ## [0.3.0] - 2026-05-25
23
+
24
+ > Note: there is no separate `0.2.0` gem release — work originally scoped for v0.2.0 (stability + refactoring, SO-040 through SO-049) was folded into this release.
25
+
26
+ Headline: **Web UI Dashboard** + stability hardening from pre-release review.
27
+
28
+ ### Added
29
+ - Web UI dashboard at `/solid_observer` — queue stats, jobs browser, events log, storage info, responsive layout, optional HTTP Basic Auth
30
+ - Web UI config: `ui_enabled`, `ui_username`, `ui_password`, `ui_base_controller`, `ui_refresh_interval`
31
+ - Dashboard "Live" toggle: when enabled, only the Right-Now card region (`so_right_now` Turbo Frame) reloads on the configured cadence (`SolidObserver.config.ui_refresh_interval`). Scoped cards stay frozen until the range selector changes. Toggle state lives in the `?live=on` URL param so it survives range navigations.
32
+ - Dashboard now supports a Time-Range selector (`15m`, `30m`, `1h`, `7h`, `1d`, `7d`, `14d`, default `1h`) that scopes the new "Last <range>" card region (Performed / Failed / Enqueue rate). The "Right Now" region (Ready / Scheduled / Claimed / Failed-awaiting-retry / Workers) is unaffected by the range and reflects current SolidQueue state.
33
+ - Retry / discard actions with confirmation dialogs and CSRF protection
34
+ - `QueueEventBuffer#metrics` and `#shutdown` (graceful drain on app exit)
35
+ - Configuration: `max_buffer_size` (default 10_000), `buffer_overflow_strategy` (`:drop_old` / `:drop_new`), `filter_cache_ttl`
36
+ - `Services::DatabaseSize` for cross-adapter table-size measurement; composite indexes and `distinct_job_classes` / `distinct_queue_names` scopes
37
+ - Dashboard now shows three throughput counters (Performed last hour, Failed last 24h, Enqueue rate last 5 min) sourced from the events table, alongside existing point-in-time counters. Throughput counters are persistence-mode only.
38
+ - Stat counter subtitles ("queued" / "future runs" / "in progress" / "awaiting retry" / "active processes") clarify SolidQueue lifecycle terminology.
39
+ - Conceptual hint banner on the dashboard explains the difference between Jobs tab (in-flight + failed) and Events tab (historical record).
40
+ - Jobs tab now has an empty state with a hint pointing to Events tab for completed-job history.
41
+ - Dashboard polled chart strip and unified card grid: a JSON-fed polling client refreshes six stat cards (Ready, Scheduled, Claimed, Workers, Failed, Enqueue Rate) and three sparklines (Performed/min, Ready depth, Failed/min) every `SolidObserver.config.ui_refresh_interval` seconds (default `30`, `0` disables polling). In realtime mode only the Ready sparkline renders and the Enqueue Rate card is omitted. Toggle in the dashboard top bar enables/disables polling; state lives in `?live=on` URL param. Hand-rolled inline-SVG sparklines, no JS framework, no external chart library.
42
+
43
+ ### Fixed
44
+ - **Duration was displayed off by 1000x.** `RecordEvent` stored `ActiveSupport::Notifications::Event#duration` (milliseconds) directly; `format_duration` interpreted it as seconds. Fixed by converting ms → seconds at write time. No data migration needed (fixed before v0.3.0 release); run `bin/rails solid_observer:storage:purge` to clear pre-fix local data.
45
+ - **Dashboard chart strip renders populated polylines on first paint** instead of empty placeholders. Server-side `spark_points` helper mirrors the JS `Sparkline.render` projection formula so the first HTML response ships with real data; the first JS poll appends one additional segment with no visible "empty → full" transition.
46
+ - **Live toggle cadence label stays in sync with toggle state.** The `.so-toggle__cadence` span now carries `aria-live="polite"` and is updated in the same synchronous tick as the `--on` class toggle. Server-side ERB hardcodes `"5s"` / `"off"` matching the JS literals.
47
+ - Jobs details page no longer crashes for `SolidQueue::FailedExecution`; Queue and Priority now fall back to underlying `SolidQueue::Job` values and show `N/A` when unavailable.
48
+ - Web UI now works on API-only Rails hosts. The engine ships its own Cookies / Session::CookieStore (`key: "_solid_observer_session"`) / Flash middleware stack, so requests routed to `/solid_observer/*` get the middleware they need regardless of whether the host app strips them via `config.api_only = true`. Previously, API-only hosts hit `NoMethodError: undefined method 'flash' for an instance of ActionDispatch::Request` rendering the dashboard layout.
49
+ - `bin/rails solid_observer:install:migrations` now respects `migrations_paths` from the `solid_observer_queue` connection in `config/database.yml`. When the host configures a dedicated migration folder (e.g. `db/solid_observer_migrate`), the install task copies migrations directly there instead of `db/migrate/`. Previously, operators in multi-database setups had to manually move the files after install to prevent cross-database migration contamination.
50
+ - Web UI now degrades gracefully when the `solid_observer_queue` database is missing or unreachable at request time. Previously, requests to `/solid_observer/*` raised `ActiveRecord::NoDatabaseError` / `ConnectionNotEstablished` with a raw 500 stack trace. The engine now renders a 503 "Storage unavailable" page using the dashboard layout, with an actionable hint to run migrations or check `database.yml`. Realtime mode is unaffected. Boot-time resilience (Engine activation skip) was already in place; this closes the equivalent gap at request time.
51
+ - Engine boot no longer requires a live DB (Docker/CI/K8s safe); table check uses `BaseEvent.connection_pool` (multi-DB safe); broader rescue covering adapter-specific connection errors
52
+ - `QueueEventBuffer` hard-caps at `max_buffer_size` with overflow strategy; uses a single persistent `Concurrent::TimerTask` instead of spawning a thread per flush
53
+ - Storage monitoring uses adapter-native size queries (SQLite / PostgreSQL / MySQL / Trilogy) — fixes `0 MB` readings off SQLite
54
+ - `EventsController` and `JobsController` filter dropdowns no longer full-table-scan on every request (cached via `filter_cache_ttl`)
55
+ - `RecordEvent` correctly handles real `ActiveJob::Base` payload objects (with hash fallback)
56
+
57
+ ### Security
58
+ - Job arguments removed from persisted event metadata and the jobs detail view (PII reduction)
59
+ - Web UI HTTP Basic Auth now requires **both** `ui_username` and `ui_password` to be configured. Previously, setting only `ui_username` (with `ui_password` missing or `nil`) would still trigger an auth challenge that any blank-password request would pass `secure_compare("", "")`, granting unauthenticated access. The README's "both must be set" guidance now matches the implementation; misconfigured auth now ships unauthenticated rather than allowing a bypass.
60
+ - Boot-time `Engine.check_ui_authentication` now warns on partial misconfiguration (exactly one of `ui_username` / `ui_password` set), naming the missing credential. Previously the check exited silently as soon as `ui_username.present?`, hiding the fail-open auth misconfiguration from operators.
61
+
62
+ ### Documentation
63
+ - Documented multi-adapter installation (PG host + SQLite observer DB) inline in Database Setup, with explicit `adapter:` override, `gem "sqlite3"` Bundler note, `migrations_paths` migration-isolation guidance, and cross-reference from the install steps.
64
+
65
+ ### Removed
66
+ - Removed the legacy implicit dashboard auto-refresh (`<meta http-equiv="refresh">` plus inline fetch/DOM-swap script for `.so-content`). Replaced by explicit, opt-in Live Mode targeting only the Right-Now frame.
67
+ - Removed the legacy `GET /solid_observer/right_now` HTML endpoint and its action template (it was the SO-060-era polling target that returned a partial-only HTML response wrapped in a turbo-frame). Replaced by `GET /solid_observer/poll_data` which returns JSON for the polling client. The `live_poll.js` script-delivery route is unchanged.
68
+ - **Removed `SolidObserver.config.ui_refresh_interval`** (was unreliable; cadence is now hardcoded at 5s). Upgrade note: remove the line from your initializer or boot will raise `NoMethodError`.
69
+
70
+ ### Changed
71
+ - Web UI controllers refactored to thin actions + query/param/presenter objects; Rails built-in number helpers replace custom ones
72
+ - Specs: `allow_any_instance_of` → `instance_double`; sleep-based timer specs use deterministic synchronisation; dead private-method `describe` blocks removed
73
+ - `.reek.yml` suppressions trimmed; hot-path services/buffer/engine methods refactored to pass `TooManyStatements` without new suppressions
74
+ - `SolidObserver.config.ui_refresh_interval` now controls Live Mode polling cadence instead of implicit full-page dashboard refresh. Default value is unchanged.
75
+ - Dashboard default range is `15m` (was `1h`); aligns with poll default.
76
+ - Live polling pauses while the tab is hidden and resumes on return (one immediate tick on visibility return so the user doesn't wait up to 5s for fresh data).
77
+ - Jobs tab default filter changed from `status=ready` to `status=all_active` (ready + scheduled + claimed + failed).
78
+ - Duration values on the Events index and detail pages now use per-event-type semantic context via `<abbr title="...">` tooltips so operators can distinguish enqueue call latency (`job_enqueued`) from perform-time duration (`job_completed` / `job_failed` / `job_discarded`).
79
+ - Refreshed the engine UI to a minimalist visual language: light surfaces, near-black text, restrained semantic colour accents reserved for badges/state, hairline separators, consistent rounding. Sidebar moves from dark slate to a light surface. No external CSS dependencies, no JS, no dark mode (single light theme).
80
+ - Dashboard: replaced the multi-line "Recent Failures" panel with a single-line **Stability** indicator (pill badge + summary + "View failures" link). Three states based on rolling failure counts: **Stable** (no failures in last 24h), **Degraded** (failures in last 24h but none in last hour), **Critical** (any failure in the last hour). Click-through targets the Events page filtered to `job_failed`.
81
+ - Dashboard: removed the "Jobs tab / Events tab" orientation banner. The distinction is discoverable via navigation; the dashboard is reserved for signal.
82
+ - Dashboard layout consolidated: dropped the previous two-frame split (Right-Now + Scoped) and the redundant Performed-in-range / Failed-in-range cards (those metrics now live in the polled sparkline strip).
83
+ - Live toggle restyled as a pill switch with cadence visible in-line ("Live · 2s" / "Live · off") and a pulsing dot when active.
84
+
1
85
  ## [0.1.1] - 2026-02-10
2
86
 
3
87
  ### Added
data/README.md CHANGED
@@ -2,40 +2,44 @@
2
2
  <picture>
3
3
  <source media="(prefers-color-scheme: dark)" srcset=".github/solid_logo_dark.svg">
4
4
  <source media="(prefers-color-scheme: light)" srcset=".github/solid_logo_light.svg">
5
- <img alt="SolidObserver" src=".github/solid_logo_light.svg" width="250">
5
+ <img alt="SolidObserver" src=".github/solid_logo_light.svg" width="220">
6
6
  </picture>
7
7
  </p>
8
8
 
9
9
  <p align="center">
10
- <strong>Observe your Solid Stack like a pro!</strong>
11
- </p>
12
-
13
- <p align="center">
14
- <a href="https://github.com/bart-oz/solid_observer/releases"><img src="https://img.shields.io/badge/version-0.1.1-blue.svg" alt="Version"></a>
10
+ <a href="https://github.com/bart-oz/solid_observer/releases"><img src="https://img.shields.io/badge/version-0.4.0-blue.svg" alt="Version"></a>
15
11
  <a href="LICENSE.txt"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License"></a>
16
12
  <a href="https://github.com/bart-oz/solid_observer/actions"><img src="https://img.shields.io/badge/tests-passing-brightgreen.svg" alt="Tests"></a>
17
- <a href="https://github.com/bart-oz/solid_observer/actions"><img src="https://img.shields.io/badge/coverage-93.06%25-brightgreen.svg" alt="Coverage"></a>
13
+ <a href="https://github.com/bart-oz/solid_observer/actions"><img src="https://img.shields.io/badge/coverage-96.22%25-brightgreen.svg" alt="Coverage"></a>
18
14
  </p>
19
15
 
20
16
  ---
17
+ <p align="center">
18
+ <a href=".github/assets/dash_1.png"><img src=".github/assets/dash_1.png" alt="SolidObserver dashboard overview" width="700"></a>
19
+ </p>
20
+
21
+ SolidObserver is a production-grade observability solution for Rails 8's Solid Stack. v0.4.0 covers both **Solid Queue** and **Solid Cache** with a unified Web UI dashboard, CLI tools, metrics collection, and distributed tracing support.
21
22
 
22
- SolidObserver is a production-grade observability solution for Rails 8's Solid Stack. Starting with **Solid Queue** monitoring in v0.1.0, it provides unified visibility into your background job processing with CLI tools, metrics collection, and distributed tracing support.
23
+ ## Features (v0.4.0)
23
24
 
24
- ## Features (v0.1.1)
25
+ | | Solid Queue | Solid Cache |
26
+ |---|---|---|
27
+ | **Web UI Dashboard** | Queue stats, jobs browser, events log | Hit rate, ops/sec, error rate, avg duration |
28
+ | **Storage footprint** | DB size, event counts | SolidCache table size, row counts |
29
+ | **Activity trends** | Sparklines (Performed, Ready, Failed) | Activity trend sparklines |
30
+ | **Stability indicator** | Stable / Degraded / Critical badge | Stability pill badge |
31
+ | **Operational controls** | Retry / discard failed jobs | Prune expired entries, clear all entries |
32
+ | **CLI tools** | status, jobs:list/show/retry/discard | cache:prune, cache:clear |
33
+ | **Privacy** | Job arguments excluded from persisted events | Keys and values **never** shown |
34
+ | **Operating modes** | Real-time (no DB) or persistence (full history) | Persistence mode only |
25
35
 
26
- - 📊 **Real-time Queue Status** Monitor jobs across all states (ready, scheduled, claimed, failed)
27
- - 🔍 **Job Management CLI** — List, inspect, retry, and discard failed jobs
28
- - 💾 **Storage Monitoring** — Track database size and event counts
29
- - 🔗 **Distributed Tracing** — Correlate jobs with APM tools (Datadog, Sentry, OpenTelemetry)
30
- - ⚡ **High Performance** — Buffered writes, configurable sampling, minimal overhead
31
- - 🛡️ **Production Ready** — Automatic cleanup, size limits, retention policies
32
- - 🚀 **Two Operating Modes** — Real-time (no migrations) or persistence (full event history)
36
+ Additional: 🔗 APM distributed tracing · buffered writes · 🛡️ Docker/CI/K8s safe boot
33
37
 
34
38
  ## Requirements
35
39
 
36
40
  - Ruby 3.2+
37
41
  - Rails 8.0+
38
- - Solid Queue (properly configured for all environments)
42
+ - Solid Queue (configured with `connects_to` in all environments)
39
43
 
40
44
  > **Note:** Ensure Solid Queue is configured with `connects_to` in all environments, not just production. See [Troubleshooting](#troubleshooting) if you encounter database connection issues.
41
45
 
@@ -69,7 +73,9 @@ That's it. You now have access to queue status, job listing, retry, and discard
69
73
 
70
74
  ### Persistence Mode (default)
71
75
 
72
- Store event history, metrics, and storage snapshots in a dedicated SQLite database. This gives you everything in real-time mode plus long-term event tracking, buffered writes, and retention-based cleanup.
76
+ 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](#database-setup-persistence-mode) below.
77
+
78
+ > If your host app uses a different adapter than SQLite (e.g. PostgreSQL or MySQL), see [Multi-adapter setup](#multi-adapter-setup) before running these commands.
73
79
 
74
80
  ```bash
75
81
  bin/rails solid_observer:install:migrations
@@ -77,7 +83,7 @@ bin/rails db:create
77
83
  bin/rails db:migrate
78
84
  ```
79
85
 
80
- No additional configuration needed — persistence is the default `storage_mode`.
86
+ For SQLite-default apps, no further configuration is needed — persistence is the default `storage_mode`.
81
87
 
82
88
  ## Quick Start
83
89
 
@@ -134,8 +140,17 @@ bin/rails "solid_observer:jobs:retry[JOB_ID]"
134
140
  bin/rails "solid_observer:jobs:discard[JOB_ID]"
135
141
  ```
136
142
 
143
+ ### Events Page Filters (Web UI)
144
+
145
+ Filter dropdowns on `/solid_observer/events` (job class and queue) are cached for 1 minute by default and scoped to the configured retention window.
146
+ Tune the cache TTL with `config.filter_cache_ttl`.
147
+
137
148
  ### Check Storage (Persistence Mode)
138
149
 
150
+ Storage monitoring is cross-adapter: SQLite, PostgreSQL, MySQL, and Trilogy are supported.
151
+ SolidObserver uses adapter-native SQL queries to measure size (not filesystem `File.size`),
152
+ so production deployments report real values even when the database is remote.
153
+
139
154
  ```bash
140
155
  bin/rails solid_observer:storage
141
156
  ```
@@ -154,8 +169,122 @@ Configuration:
154
169
  Warning: 80% threshold
155
170
  ```
156
171
 
172
+ ## Web UI Dashboard
173
+
174
+ SolidObserver ships with a zero-dependency Web UI (no asset pipeline, no JS framework) at `/solid_observer`.
175
+
176
+ ### Mount
177
+
178
+ The install generator mounts it for you. To mount manually:
179
+
180
+ ```ruby
181
+ # config/routes.rb
182
+ mount SolidObserver::Engine, at: "/solid_observer"
183
+ ```
184
+
185
+ ### Auth Configuration
186
+
187
+ ```ruby
188
+ SolidObserver.configure do |config|
189
+ config.ui_enabled = !Rails.env.production? # default: true outside production
190
+ config.ui_username = "admin" # HTTP Basic Auth: BOTH username AND password must be set
191
+ config.ui_password = ENV["SOLID_OBSERVER_PASSWORD"]
192
+ config.ui_base_controller = "ApplicationController" # name of your host app's base controller (used for API-only detection)
193
+ end
194
+ ```
195
+
196
+ | Option | Default | Purpose |
197
+ |---|---|---|
198
+ | `ui_enabled` | `!Rails.env.production?` | Master switch for the Web UI |
199
+ | `ui_username` / `ui_password` | `nil` | HTTP Basic Auth. Auth is enabled only when **both** are set |
200
+ | `ui_base_controller` | `"ApplicationController"` | Detect API-only apps to include rendering modules |
201
+
202
+ For production hardening (fail-loud on missing env var vs. fail-open), see [Configuration](#configuration) below.
203
+
204
+ ### Caveats
205
+
206
+ - **Realtime mode** — Events and Storage navigation links are hidden. Direct visits redirect with a flash alert.
207
+ - **API-only apps** — the Web UI works without manual configuration. The engine ships its own Cookies/Session/Flash middleware stack scoped to `/solid_observer/*`.
208
+ - **Host-app callbacks are not inherited.** Use `ui_username` / `ui_password` for built-in HTTP Basic Auth.
209
+ - **Auth misconfiguration is fail-open, but loud.** If `ui_username` is set but `ui_password` resolves to `nil`, the UI ships unauthenticated and logs a boot `WARNING`.
210
+
211
+ See the full component breakdown in [Components](#components) below.
212
+
213
+ ## Components
214
+
215
+ ### Solid Queue Observability
216
+
217
+ 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.
218
+
219
+ <table>
220
+ <tr>
221
+ <td align="center"><a href=".github/assets/dash_2.png"><img src=".github/assets/dash_2.png" alt="Queue overview — 1d range" width="340"></a></td>
222
+ <td align="center"><a href=".github/assets/dash_3.png"><img src=".github/assets/dash_3.png" alt="Jobs list" width="340"></a></td>
223
+ </tr>
224
+ <tr>
225
+ <td align="center"><a href=".github/assets/dash_4.png"><img src=".github/assets/dash_4.png" alt="Failed job detail" width="340"></a></td>
226
+ <td align="center"><a href=".github/assets/dash_5.png"><img src=".github/assets/dash_5.png" alt="Events stream" width="340"></a></td>
227
+ </tr>
228
+ </table>
229
+
230
+ **CLI commands (Solid Queue):**
231
+
232
+ ```bash
233
+ bin/rails solid_observer:status # Queue overview
234
+ bin/rails solid_observer:jobs:list # All active jobs
235
+ bin/rails "solid_observer:jobs:list[failed]" # Failed jobs
236
+ bin/rails "solid_observer:jobs:show[JOB_ID]" # Job details
237
+ bin/rails "solid_observer:jobs:retry[JOB_ID]" # Retry failed job
238
+ bin/rails "solid_observer:jobs:discard[JOB_ID]" # Discard failed job
239
+ bin/rails solid_observer:buffer:flush # Flush event buffer
240
+ bin/rails solid_observer:buffer:clear # Clear buffer without saving
241
+ ```
242
+
243
+ ### Solid Cache Observability
244
+
245
+ 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.
246
+
247
+ <table>
248
+ <tr>
249
+ <td align="center"><a href=".github/assets/dash_6.png"><img src=".github/assets/dash_6.png" alt="Cache overview dashboard" width="340"></a></td>
250
+ <td align="center"><a href=".github/assets/dash_7.png"><img src=".github/assets/dash_7.png" alt="Cache operational controls" width="340"></a></td>
251
+ </tr>
252
+ </table>
253
+
254
+ **Enabling Cache observability:**
255
+
256
+ SolidCache must be configured in your host app (Rails 8 default). Then enable in the SolidObserver initializer:
257
+
258
+ ```ruby
259
+ SolidObserver.configure do |config|
260
+ config.observe_cache = true # default: false
261
+ end
262
+ ```
263
+
264
+ `solid_cache_enabled?` is `true` when both `observe_cache = true` and SolidCache is available in the host app.
265
+
266
+ **CLI commands (Solid Cache):**
267
+
268
+ ```bash
269
+ bin/rails solid_observer:cache:clear # Clear all SolidCache entries (with confirmation)
270
+ bin/rails solid_observer:cache:prune # Prune expired SolidCache entries
271
+ ```
272
+
273
+ ### Storage
274
+
275
+ The Storage page aggregates per-component health rows: Queue Observer database, Cache Observer, and SolidCache table sizes. Each row reports size, record counts, and a status indicator.
276
+
277
+ <p align="center">
278
+ <a href=".github/assets/dash_8.png"><img src=".github/assets/dash_8.png" alt="Component health — Queue Observer + Cache Observer + SolidCache" width="700"></a>
279
+ </p>
280
+
281
+ For adapter notes and multi-adapter setup, see [Database Setup](#database-setup-persistence-mode) below.
282
+
157
283
  ## Configuration
158
284
 
285
+ <details>
286
+ <summary>Show full configuration reference</summary>
287
+
159
288
  After installation, configure SolidObserver in `config/initializers/solid_observer.rb`:
160
289
 
161
290
  ```ruby
@@ -168,6 +297,9 @@ SolidObserver.configure do |config|
168
297
  # Enable queue monitoring (default: true)
169
298
  config.observe_queue = true
170
299
 
300
+ # Enable cache monitoring (default: false; requires SolidCache in host app)
301
+ config.observe_cache = true
302
+
171
303
  # Data Retention (persistence mode only)
172
304
  config.event_retention = 30.days # Keep events for 30 days
173
305
  config.metrics_retention = 90.days # Keep metrics for 90 days
@@ -185,8 +317,6 @@ end
185
317
 
186
318
  ### APM Integration
187
319
 
188
- Connect SolidObserver with your Application Performance Monitoring tool for distributed tracing:
189
-
190
320
  ```ruby
191
321
  SolidObserver.configure do |config|
192
322
  # Datadog APM
@@ -213,6 +343,26 @@ end
213
343
 
214
344
  When configured, all job events will include your correlation ID, allowing you to trace jobs back to the originating request.
215
345
 
346
+ ### Production hardening (recommended)
347
+
348
+ ```ruby
349
+ # Option A: fail at boot if the password env var is missing
350
+ SolidObserver.configure do |config|
351
+ config.ui_username = "admin"
352
+ config.ui_password = ENV.fetch("SOLID_OBSERVER_PASSWORD") # raises KeyError if unset
353
+ end
354
+
355
+ # Option B: only enable auth when the env var is present (no auth otherwise)
356
+ SolidObserver.configure do |config|
357
+ if (password = ENV["SOLID_OBSERVER_PASSWORD"]).present?
358
+ config.ui_username = "admin"
359
+ config.ui_password = password
360
+ end
361
+ end
362
+ ```
363
+
364
+ </details>
365
+
216
366
  ## CLI Reference
217
367
 
218
368
  **Available in both modes (real-time and persistence):**
@@ -234,8 +384,10 @@ When configured, all job events will include your correlation ID, allowing you t
234
384
  | `solid_observer:buffer:clear` | Clear buffer without saving |
235
385
  | `solid_observer:storage:cleanup` | Run retention-based cleanup |
236
386
  | `solid_observer:storage:purge` | Delete ALL SolidObserver data |
387
+ | `solid_observer:cache:clear` | Clear all SolidCache entries (with confirmation) |
388
+ | `solid_observer:cache:prune` | Prune expired SolidCache entries |
237
389
 
238
- > **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`.
390
+ > **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.
239
391
 
240
392
  ### Jobs List Arguments
241
393
 
@@ -249,62 +401,82 @@ Arguments are positional: `[status, queue, job_class, limit]`
249
401
  | 4th | Max results (default: 20) | `50` |
250
402
 
251
403
  ```bash
252
- # Examples
253
404
  bin/rails solid_observer:jobs:list # All ready jobs
254
405
  bin/rails "solid_observer:jobs:list[failed]" # Failed jobs
255
406
  bin/rails "solid_observer:jobs:list[ready,mailers]" # Ready jobs in mailers queue
256
407
  bin/rails "solid_observer:jobs:list[failed,,,50]" # 50 failed jobs (skip queue/class)
257
408
  ```
258
409
 
259
- ### Buffer & Storage Management (Persistence Mode)
410
+ ## Database Setup (Persistence Mode)
260
411
 
261
- ```bash
262
- # Flush in-memory buffer to database
263
- bin/rails solid_observer:buffer:flush
412
+ > **Tip:** If you're using real-time mode (`storage_mode: :realtime`), you can skip this section entirely — no database setup is needed.
264
413
 
265
- # Clear buffer without saving (loses pending events!)
266
- bin/rails solid_observer:buffer:clear
414
+ **SolidObserver works with any main application database** — PostgreSQL, MySQL, or SQLite.
267
415
 
268
- # Run cleanup based on retention policy (default: 30 days)
269
- bin/rails solid_observer:storage:cleanup
416
+ For its own monitoring data, the install generator defaults to a **separate SQLite database** — file-based, no extra infrastructure needed.
270
417
 
271
- # Delete ALL SolidObserver data (events + snapshots, interactive confirmation)
272
- bin/rails solid_observer:storage:purge
273
- ```
418
+ <details>
419
+ <summary>Show multi-adapter setup and configuration details</summary>
274
420
 
275
- > **Important:** `storage:purge` deletes SolidObserver's monitoring data, NOT your Solid Queue jobs. Your queued jobs remain safe.
421
+ The example below is what `rails generate solid_observer:install` produces:
276
422
 
277
- ## Database Setup (Persistence Mode)
423
+ ```yaml
424
+ # config/database.yml (generator default)
425
+ solid_observer_queue:
426
+ <<: *default
427
+ adapter: sqlite3
428
+ database: storage/<%= Rails.env %>_solid_observer_queue.sqlite3
429
+ ```
278
430
 
279
- > **Tip:** If you're using real-time mode (`storage_mode: :realtime`), you can skip this section entirely no database setup is needed.
431
+ > **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.
280
432
 
281
- **SolidObserver works with any main application database** — PostgreSQL, MySQL, or SQLite.
433
+ ### Multi-adapter setup
282
434
 
283
- For its own monitoring data, SolidObserver uses a **separate SQLite database**. This keeps monitoring isolated from your main app and provides simple file-based storage that requires no additional infrastructure.
435
+ 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 default uses `<<: *default` with an explicit `adapter: sqlite3` override; that is safe on SQLite-primary hosts. 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:
284
436
 
285
437
  ```yaml
286
438
  # config/database.yml
287
- solid_observer_queue:
288
- <<: *default
289
- adapter: sqlite3 # Always SQLite for SolidObserver storage
290
- database: storage/<%= Rails.env %>_solid_observer_queue.sqlite3
439
+ default: &default
440
+ adapter: postgresql
441
+ encoding: unicode
442
+ pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
443
+
444
+ development:
445
+ primary:
446
+ <<: *default
447
+ database: my_app_development
448
+
449
+ solid_observer_queue:
450
+ adapter: sqlite3 # explicit override — do NOT merge *default
451
+ pool: 5
452
+ timeout: 5000
453
+ database: storage/development_solid_observer_queue.sqlite3
454
+ migrations_paths: db/solid_observer_migrate
291
455
  ```
292
456
 
293
- > **Note:** Your main app's `primary` database can be PostgreSQL, MySQL, or any Rails-supported adapter. Only the `solid_observer_queue` database needs to be SQLite.
457
+ Apply the same pattern to `test:` and `production:`. For production with SQLite, ensure the `storage/` path is on persistent disk.
294
458
 
295
- ## Roadmap
459
+ **Bundler note (PG-only hosts):** if your `Gemfile` does not already include `sqlite3`, add it:
296
460
 
297
- SolidObserver is actively developed. Here's what's coming:
461
+ ```ruby
462
+ gem "sqlite3", "~> 2.0"
463
+ ```
464
+
465
+ **`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.
466
+
467
+ </details>
468
+
469
+ ## Roadmap
298
470
 
299
471
  | Version | Focus | Status |
300
472
  |---------|-------|--------|
301
473
  | v0.1.0 | Solid Queue monitoring, CLI tools | ✅ Released |
302
- | v0.1.1 | Real-time mode (no migrations needed) | ✅ Current |
303
- | v0.2.0 | Web UI dashboard (vanilla HTML/CSS) | 🔜 Planned |
304
- | v0.3.0 | Solid Cache monitoring | 🔜 Planned |
305
- | v0.4.0 | Solid Cable monitoring | 🔜 Planned |
306
- | v0.5.0 | Cross-component correlation, health scores | 🔜 Planned |
307
- | v0.6.0 | Alerting & notifications | 🔜 Planned |
474
+ | v0.1.1 | Real-time mode (no migrations needed) | ✅ Released |
475
+ | v0.3.0 | Web UI dashboard + stability hardening | Released |
476
+ | v0.4.0 | Solid Cache monitoring | Current |
477
+ | v0.5.0 | Solid Cable monitoring | 🔜 Planned |
478
+ | v0.6.0 | Cross-component correlation, health scores | 🔜 Planned |
479
+ | v0.7.0 | Alerting & notifications | 🔜 Planned |
308
480
  | v1.0.0 | Production stable release | 🎯 Goal |
309
481
 
310
482
  See [GitHub Milestones](https://github.com/bart-oz/solid_observer/milestones) for detailed plans.
@@ -331,6 +503,19 @@ bundle exec reek
331
503
 
332
504
  ## Troubleshooting
333
505
 
506
+ ### Docker, CI, and Offline Boot
507
+
508
+ SolidObserver is designed to boot without a live database connection. During
509
+ `rails assets:precompile`, CI runs without a database service, or Kubernetes init
510
+ containers, the engine logs a single info message and skips subscriber activation:
511
+
512
+ ```text
513
+ [SolidObserver] Database not reachable at boot. Skipping subscriber activation.
514
+ ```
515
+
516
+ No monkey-patching or environment-variable workarounds are needed. Once the application
517
+ boots with a live database, the engine activates normally on the next restart.
518
+
334
519
  ### "no such table: solid_queue_ready_executions"
335
520
 
336
521
  This error means Solid Queue isn't configured to use the correct database in your environment.
@@ -347,22 +532,19 @@ config.solid_queue.connects_to = { database: { writing: :queue } }
347
532
 
348
533
  ### Multi-database setup
349
534
 
350
- SolidObserver works with Rails multi-database configurations. Here's an example with PostgreSQL as your primary database:
535
+ > **See also:** [Multi-adapter setup](#multi-adapter-setup) in the Database Setup section for the full pattern.
536
+
537
+ SolidObserver works with Rails multi-database configurations. Quick-reference example with PostgreSQL as your primary database and SQLite for SolidObserver's storage:
351
538
 
352
539
  ```yaml
353
540
  development:
354
541
  primary:
355
542
  adapter: postgresql
356
543
  database: myapp_development
357
- # ... PostgreSQL settings
358
- queue:
359
- <<: *default
360
- adapter: sqlite3
361
- database: storage/development_queue.sqlite3
362
- migrations_paths: db/queue_migrate
363
544
  solid_observer_queue:
364
545
  adapter: sqlite3
365
546
  database: storage/development_solid_observer_queue.sqlite3
547
+ migrations_paths: db/solid_observer_migrate
366
548
  ```
367
549
 
368
550
  ## Contributing