solid_observer 0.3.0 → 0.5.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 (81) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +34 -0
  3. data/README.md +195 -82
  4. data/app/assets/javascripts/solid_observer/live_poll.js +3 -1
  5. data/app/controllers/solid_observer/application_controller.rb +1 -0
  6. data/app/controllers/solid_observer/cable_dashboard_controller.rb +52 -0
  7. data/app/controllers/solid_observer/cable_operations_controller.rb +16 -0
  8. data/app/controllers/solid_observer/cache_dashboard_controller.rb +52 -0
  9. data/app/controllers/solid_observer/cache_operations_controller.rb +24 -0
  10. data/app/controllers/solid_observer/dashboard_controller.rb +38 -1
  11. data/app/controllers/solid_observer/storages_controller.rb +1 -1
  12. data/app/helpers/solid_observer/application_helper.rb +268 -5
  13. data/app/helpers/solid_observer/dashboard_helper.rb +30 -11
  14. data/app/models/solid_observer/cable_event.rb +13 -0
  15. data/app/models/solid_observer/cable_metric.rb +12 -0
  16. data/app/models/solid_observer/cache_event.rb +15 -0
  17. data/app/models/solid_observer/cache_metric.rb +13 -0
  18. data/app/models/solid_observer/storage_info.rb +4 -1
  19. data/app/views/layouts/solid_observer/application.html.erb +157 -19
  20. data/app/views/solid_observer/cable_dashboard/_charts.html.erb +31 -0
  21. data/app/views/solid_observer/cable_dashboard/_recent_events.html.erb +34 -0
  22. data/app/views/solid_observer/cable_dashboard/_summary.html.erb +34 -0
  23. data/app/views/solid_observer/cable_dashboard/index.html.erb +118 -0
  24. data/app/views/solid_observer/cache_dashboard/_charts.html.erb +40 -0
  25. data/app/views/solid_observer/cache_dashboard/_recent_events.html.erb +34 -0
  26. data/app/views/solid_observer/cache_dashboard/_summary.html.erb +39 -0
  27. data/app/views/solid_observer/cache_dashboard/index.html.erb +62 -0
  28. data/app/views/solid_observer/cache_operations/_confirm_clear.html.erb +6 -0
  29. data/app/views/solid_observer/cache_operations/index.html.erb +60 -0
  30. data/app/views/solid_observer/dashboard/_queue_table.html.erb +1 -0
  31. data/app/views/solid_observer/dashboard/index.html.erb +32 -5
  32. data/app/views/solid_observer/events/index.html.erb +1 -0
  33. data/app/views/solid_observer/jobs/index.html.erb +1 -0
  34. data/app/views/solid_observer/jobs/show.html.erb +3 -3
  35. data/app/views/solid_observer/storages/show.html.erb +90 -32
  36. data/config/routes.rb +7 -0
  37. data/db/migrate/20260601000001_create_solid_observer_cache_events.rb +22 -0
  38. data/db/migrate/20260601000002_create_solid_observer_cache_metrics.rb +18 -0
  39. data/db/migrate/20260602000001_add_component_to_solid_observer_storage_infos.rb +8 -0
  40. data/db/migrate/20260612000001_add_event_type_recorded_at_index_to_cache_events.rb +21 -0
  41. data/db/migrate/20260619000001_create_solid_observer_cable_events.rb +22 -0
  42. data/db/migrate/20260619000002_create_solid_observer_cable_metrics.rb +17 -0
  43. data/lib/generators/solid_observer/install_generator.rb +8 -1
  44. data/lib/generators/solid_observer/templates/initializer.rb.tt +20 -4
  45. data/lib/solid_observer/base_event.rb +1 -1
  46. data/lib/solid_observer/base_metric.rb +1 -1
  47. data/lib/solid_observer/base_record.rb +8 -0
  48. data/lib/solid_observer/cable_event_buffer.rb +28 -0
  49. data/lib/solid_observer/cable_metric_buffer.rb +230 -0
  50. data/lib/solid_observer/cable_subscriber.rb +57 -0
  51. data/lib/solid_observer/cache_event_buffer.rb +28 -0
  52. data/lib/solid_observer/cache_metric_buffer.rb +229 -0
  53. data/lib/solid_observer/cache_subscriber.rb +47 -0
  54. data/lib/solid_observer/chart_buffer.rb +84 -27
  55. data/lib/solid_observer/cli/storage.rb +16 -13
  56. data/lib/solid_observer/configuration.rb +67 -5
  57. data/lib/solid_observer/engine.rb +70 -15
  58. data/lib/solid_observer/event_buffer_core.rb +218 -0
  59. data/lib/solid_observer/queue_event_buffer.rb +9 -201
  60. data/lib/solid_observer/services/cable_operations.rb +74 -0
  61. data/lib/solid_observer/services/cable_stats.rb +385 -0
  62. data/lib/solid_observer/services/cache_operations.rb +115 -0
  63. data/lib/solid_observer/services/cache_stats.rb +346 -0
  64. data/lib/solid_observer/services/cleanup_storage.rb +98 -47
  65. data/lib/solid_observer/services/database_size.rb +13 -8
  66. data/lib/solid_observer/services/flush_cable_event_buffer.rb +54 -0
  67. data/lib/solid_observer/services/flush_cable_metrics.rb +54 -0
  68. data/lib/solid_observer/services/flush_cache_event_buffer.rb +54 -0
  69. data/lib/solid_observer/services/flush_cache_metrics.rb +56 -0
  70. data/lib/solid_observer/services/record_cable_event.rb +114 -0
  71. data/lib/solid_observer/services/record_cable_metric.rb +73 -0
  72. data/lib/solid_observer/services/record_cache_event.rb +165 -0
  73. data/lib/solid_observer/services/record_cache_metric.rb +66 -0
  74. data/lib/solid_observer/services/storage_info_snapshot.rb +216 -0
  75. data/lib/solid_observer/version.rb +1 -1
  76. data/lib/solid_observer.rb +36 -11
  77. data/lib/tasks/solid_observer.rake +111 -21
  78. metadata +47 -5
  79. data/bin/console +0 -11
  80. data/bin/quality_gate +0 -95
  81. data/bin/setup +0 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a745bb3571f70b207e14f2e88f6162db6b2322a889d42f5ebd2a50e80c5b8014
4
- data.tar.gz: d05f835df0503f04f88223c0879145a2dab13279cd5bfc59346545c0e8263f1c
3
+ metadata.gz: 563c4874ef7e6a8ad8a485d0b3f86c9d1d8f924d5308d44c5137f76bda7e03de
4
+ data.tar.gz: 88ba3331b45977349604047db1a4e3e98efb17ebc1eca8d4c52a96825d320a83
5
5
  SHA512:
6
- metadata.gz: 8417c8013a7c9ff03a9d52481af63889adaba093abf0c9b041842664af7fb15edde929f7c7b94c6df12cbac00ca41d2aeee0f488fd7ffe831abf9d5f873bff09
7
- data.tar.gz: 92147e32675291ec743e055c55382bf719a9ab6dd10e2e507789d0163b703e06490ad97dbb785e0a9c0b51b8eb72eb7a849d95504ac52c8680e53a137b50e4e4
6
+ metadata.gz: e56550b5f3cfbbb6a54325901a19bb4a70850a994e047d282cea653b420822cd1a9148c603c2886c92fb5bc9683b650587db575ef90d85b39800430ee7ffbdde
7
+ data.tar.gz: dbf38ea6c5205ef027642cf02b84e3c59950299ea0477a2f7fef8df7543d71ece58dd90026f396650a99ad948ffa49cf16978f6a596c998d87ab085df739a114
data/CHANGELOG.md CHANGED
@@ -1,3 +1,37 @@
1
+ ## [0.5.0] - 2026-06-24
2
+
3
+ Headline: **Solid Cable observability** — optional Cable telemetry, dashboard, storage health, and guarded trim controls.
4
+
5
+ ### Added
6
+ - **Cable telemetry foundation** (SO-089, SO-090) — `observe_cable` config gate with `solid_cable_available?`/`solid_cable_enabled?` predicates; `CableSubscriber` hooks into `ActiveSupport::Notifications` broadcast events; event/metric buffers reuse `EventBufferCore`; `CableEvent`/`CableMetric` models with two migrations; `cable_sampling_rate` default 0.1; broadcasting names stored as `Digest::SHA256.hexdigest` (PII boundary).
7
+ - **Cable storage health** (SO-091) — Storages page aggregates Solid Cable message storage and SolidObserver Cable telemetry rows with optional fallback states.
8
+ - **Cable dashboard** (SO-092) — `/solid_observer/cable_dashboard` with summary cards, broadcast/rejection trends, stability indicator (hybrid: event + backlog signals), recent events. Three configurable thresholds: `cable_rejection_threshold` (0.05), `cable_backlog_threshold` (0.10), `cable_error_threshold` (0.0). Sidebar navigation entry.
9
+ - **Guarded Cable trim** (SO-093) — UI button for ≤1,000 trimmable messages; `solid_observer:cable:trim` Rake task for larger backlogs. No clear-all.
10
+
11
+ ### Changed
12
+ - **Self-contained SQLite generator config** (SO-094) — install generator produces a `solid_observer_queue` block with explicit `adapter: sqlite3`, `pool`, `timeout`, `database`, and `migrations_paths: db/solid_observer_migrate` — no `<<: *default` YAML merge. Post-install warning printed for PostgreSQL/MySQL hosts. Cable telemetry label standardised to `Cable telemetry`.
13
+
14
+ ## [0.4.0] - 2026-06-03
15
+
16
+ Headline: **Solid Cache observability** — multi-component foundation, cache dashboard, operational controls, and shared storage health.
17
+
18
+ ### Added
19
+ - **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`).
20
+ - **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.
21
+ - **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.
22
+ - **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.
23
+ - **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.
24
+ - **Cache dashboard** (SO-075) — `/solid_observer/cache_dashboard` — hit rate, ops/sec, error rate, avg duration, storage footprint, activity trend sparklines, and stability pill.
25
+ - **Cache overview activity trends and health parity** (SO-077) — activity trend sparklines on the cache dashboard; stability indicator parity with the Queue dashboard.
26
+
27
+ ### Fixed
28
+ - **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.
29
+ - **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.
30
+
31
+ ### Changed
32
+ - **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.
33
+ - **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.
34
+
1
35
  ## [0.3.0] - 2026-05-25
2
36
 
3
37
  > 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.
data/README.md CHANGED
@@ -7,35 +7,41 @@
7
7
  </p>
8
8
 
9
9
  <p align="center">
10
- <a href="https://github.com/bart-oz/solid_observer/releases"><img src="https://img.shields.io/badge/version-0.3.0-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.5.0-blue.svg" alt="Version"></a>
11
11
  <a href="LICENSE.txt"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License"></a>
12
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>
13
- <a href="https://github.com/bart-oz/solid_observer/actions"><img src="https://img.shields.io/badge/coverage-96.98%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>
14
14
  </p>
15
15
 
16
16
  ---
17
17
  <p align="center">
18
- <img src=".github/assets/dash_1.png" alt="Dashboard overview" width="700">
18
+ <a href=".github/assets/dash_1.png"><img src=".github/assets/dash_1.png" alt="SolidObserver dashboard overview" width="700"></a>
19
19
  </p>
20
20
 
21
- 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.
21
+ 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.
22
22
 
23
- ## Features (v0.3.0)
23
+ ## Features (v0.5.0)
24
24
 
25
- - 🖥️ **Web UI Dashboard** Live queue stats, job browser, event log, and storage info
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** Docker/CI/K8s safe boot, automatic cleanup, size limits, retention policies
32
- - 🚀 **Two Operating Modes** Real-time (no migrations) or persistence (full event history)
25
+ | | Solid Queue | Solid Cache | Solid Cable |
26
+ |---|---|---|---|
27
+ | **Web UI Dashboard** | Queue stats, jobs browser, events log | Hit rate, ops/sec, error rate, avg duration | Broadcasts, rejection rate, trends |
28
+ | **Storage footprint** | DB size, event counts | SolidCache table size, row counts | Message count + backlogs |
29
+ | **Activity trends** | Sparklines (Performed, Ready, Failed) | Activity trend sparklines | Broadcast/rejection sparklines |
30
+ | **Stability indicator** | Stable / Degraded / Critical badge | Stability pill badge | Stable / Degraded / Critical (hybrid) |
31
+ | **Operational controls** | Retry / discard failed jobs | Prune expired entries, clear all entries | Trim expired messages |
32
+ | **CLI tools** | status, jobs:list/show/retry/discard | cache:prune, cache:clear | cable:trim |
33
+ | **Privacy** | Job arguments excluded from persisted events | Keys and values **never** shown | Broadcasting names hashed (SHA256) |
34
+ | **Operating modes** | Real-time (no DB) or persistence (full history) | Persistence mode only | Persistence mode only |
35
+
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)
43
+ - Solid Cache (optional — enable with `config.observe_cache = true`)
44
+ - Solid Cable (optional — enable with `config.observe_cable = true`)
39
45
 
40
46
  > **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
47
 
@@ -71,7 +77,7 @@ That's it. You now have access to queue status, job listing, retry, and discard
71
77
 
72
78
  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.
73
79
 
74
- > 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.
80
+ > **⚠️ PostgreSQL / MySQL hosts:** The generator writes a self-contained `solid_observer_queue` block with `adapter: sqlite3`. **Review `config/database.yml` before running `db:create`** — do not merge `<<: *default` into the observer entry, as that pulls the host adapter in and `db:create` will fail. See [Multi-adapter setup](#multi-adapter-setup) for the correct pattern.
75
81
 
76
82
  ```bash
77
83
  bin/rails solid_observer:install:migrations
@@ -169,14 +175,6 @@ Configuration:
169
175
 
170
176
  SolidObserver ships with a zero-dependency Web UI (no asset pipeline, no JS framework) at `/solid_observer`.
171
177
 
172
- <table>
173
- <tr>
174
- <td align="center"><a href=".github/assets/dash_1.png"><img src=".github/assets/dash_1.png" alt="Dashboard overview" width="250"></a></td>
175
- <td align="center"><a href=".github/assets/dash_2.png"><img src=".github/assets/dash_2.png" alt="Dashboard jobs and events" width="250"></a></td>
176
- <td align="center"><a href=".github/assets/dash_3.png"><img src=".github/assets/dash_3.png" alt="Dashboard storage and details" width="250"></a></td>
177
- </tr>
178
- </table>
179
-
180
178
  ### Mount
181
179
 
182
180
  The install generator mounts it for you. To mount manually:
@@ -186,7 +184,19 @@ The install generator mounts it for you. To mount manually:
186
184
  mount SolidObserver::Engine, at: "/solid_observer"
187
185
  ```
188
186
 
189
- ### Configuration
187
+ Production dashboard exposure is the host application's responsibility. If you
188
+ enable and mount the dashboard in production, wrap the mount in your existing
189
+ admin authentication/authorization constraint (example shown for apps that
190
+ already provide an `authenticate` route helper):
191
+
192
+ ```ruby
193
+ # config/routes.rb
194
+ authenticate :user, ->(user) { user.admin? } do
195
+ mount SolidObserver::Engine, at: "/solid_observer"
196
+ end
197
+ ```
198
+
199
+ ### Auth Configuration
190
200
 
191
201
  ```ruby
192
202
  SolidObserver.configure do |config|
@@ -200,43 +210,131 @@ end
200
210
  | Option | Default | Purpose |
201
211
  |---|---|---|
202
212
  | `ui_enabled` | `!Rails.env.production?` | Master switch for the Web UI |
203
- | `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 |
204
- | `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 |
213
+ | `ui_username` / `ui_password` | `nil` | HTTP Basic Auth. Auth is enabled only when **both** are set |
214
+ | `ui_base_controller` | `"ApplicationController"` | Detect API-only apps to include rendering modules |
205
215
 
206
- Live polling cadence is hardcoded at 5s.
216
+ For production hardening (fail-loud on missing env var vs. fail-open), see [Configuration](#configuration) below.
207
217
 
208
- ### Production hardening (recommended)
218
+ ### Caveats
219
+
220
+ - **Realtime mode** — Events and Storage navigation links are hidden. Direct visits redirect with a flash alert.
221
+ - **API-only apps** — the Web UI works without manual configuration. The engine ships its own Cookies/Session/Flash middleware stack scoped to `/solid_observer/*`.
222
+ - **Host-app callbacks are not inherited.** Use `ui_username` / `ui_password` for built-in HTTP Basic Auth.
223
+ - **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`.
224
+ - **HTTP Basic Auth requires both credentials.** Setting only `ui_username` or only `ui_password` disables built-in auth; protect production mounts at the host-app route boundary.
225
+
226
+ See the full component breakdown in [Components](#components) below.
227
+
228
+ ## Components
229
+
230
+ ### Solid Queue Observability
231
+
232
+ 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.
233
+
234
+ <table>
235
+ <tr>
236
+ <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>
237
+ <td align="center"><a href=".github/assets/dash_3.png"><img src=".github/assets/dash_3.png" alt="Jobs list" width="340"></a></td>
238
+ </tr>
239
+ <tr>
240
+ <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>
241
+ <td align="center"><a href=".github/assets/dash_5.png"><img src=".github/assets/dash_5.png" alt="Events stream" width="340"></a></td>
242
+ </tr>
243
+ </table>
209
244
 
210
- 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:
245
+ **CLI commands (Solid Queue):**
246
+
247
+ ```bash
248
+ bin/rails solid_observer:status # Queue overview
249
+ bin/rails solid_observer:jobs:list # All active jobs
250
+ bin/rails "solid_observer:jobs:list[failed]" # Failed jobs
251
+ bin/rails "solid_observer:jobs:show[JOB_ID]" # Job details
252
+ bin/rails "solid_observer:jobs:retry[JOB_ID]" # Retry failed job
253
+ bin/rails "solid_observer:jobs:discard[JOB_ID]" # Discard failed job
254
+ bin/rails solid_observer:buffer:flush # Flush event buffer
255
+ bin/rails solid_observer:buffer:clear # Clear buffer without saving
256
+ ```
257
+
258
+ ### Solid Cache Observability
259
+
260
+ 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.
261
+
262
+ <table>
263
+ <tr>
264
+ <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>
265
+ <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>
266
+ </tr>
267
+ </table>
268
+
269
+ **Enabling Cache observability:**
270
+
271
+ SolidCache must be configured in your host app (Rails 8 default). Then enable in the SolidObserver initializer:
211
272
 
212
273
  ```ruby
213
- # Option A: fail at boot if the password env var is missing
214
274
  SolidObserver.configure do |config|
215
- config.ui_username = "admin"
216
- config.ui_password = ENV.fetch("SOLID_OBSERVER_PASSWORD") # raises KeyError if unset
275
+ config.observe_cache = true # default: false
217
276
  end
277
+ ```
218
278
 
219
- # Option B: only enable auth when the env var is present (no auth otherwise)
279
+ `solid_cache_enabled?` is `true` when both `observe_cache = true` and SolidCache is available in the host app.
280
+
281
+ **CLI commands (Solid Cache):**
282
+
283
+ ```bash
284
+ bin/rails solid_observer:cache:clear # Clear all SolidCache entries (with confirmation)
285
+ bin/rails solid_observer:cache:prune # Prune expired SolidCache entries
286
+ ```
287
+
288
+ ### Solid Cable Observability
289
+
290
+ Cable observability is optional. Enable with `config.observe_cable = true`. Requires SolidCable in the host app. SolidObserver does not add any SolidCable dependency.
291
+
292
+ 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.
293
+
294
+ **Enabling Cable observability:**
295
+
296
+ ```ruby
220
297
  SolidObserver.configure do |config|
221
- if (password = ENV["SOLID_OBSERVER_PASSWORD"]).present?
222
- config.ui_username = "admin"
223
- config.ui_password = password
224
- end
298
+ config.observe_cable = true # default: false
299
+ # config.cable_sampling_rate = 0.1 # Sample 10% of broadcast events (default: 0.1)
300
+ # config.cable_rejection_threshold = 0.05 # Rejection rate threshold for Degraded (default: 0.05)
301
+ # config.cable_backlog_threshold = 0.10 # Backlog ratio threshold (default: 0.10)
302
+ # config.cable_error_threshold = 0.0 # Error rate threshold (default: 0.0)
225
303
  end
226
304
  ```
227
305
 
228
- 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.
306
+ `solid_cable_enabled?` is `true` when both `observe_cable = true` and SolidCable is available in the host app.
229
307
 
230
- ### Caveats
308
+ **CLI commands (Solid Cable):**
309
+
310
+ ```bash
311
+ bin/rails solid_observer:cable:trim # Trim expired Cable messages (with confirmation)
312
+ ```
313
+
314
+ > Cable dashboard screenshots are not yet available — capture from a host app with SolidCable configured.
315
+
316
+ ### Storage
317
+
318
+ 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.
319
+
320
+ <p align="center">
321
+ <a href=".github/assets/dash_8.png"><img src=".github/assets/dash_8.png" alt="Component health — Queue Observer + Cache Observer + SolidCache + Cable telemetry" width="700"></a>
322
+ </p>
323
+
324
+ For adapter notes and multi-adapter setup, see [Database Setup](#database-setup-persistence-mode) below.
325
+
326
+ ### Storage unavailable diagnostics
231
327
 
232
- - **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`).
233
- - **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.
234
- - **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.
235
- - **Host-app callbacks are not inherited.** SolidObserver does not run your host app's `before_action`s or authentication. Use `ui_username` / `ui_password` for the engine's built-in HTTP Basic Auth.
236
- - **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.
328
+ The dashboard is admin/developer-facing. When SolidObserver storage is not
329
+ reachable, the current default 503 error page includes the raw exception class
330
+ and message so maintainers can diagnose adapter, credential, migration, or
331
+ database availability problems. Do not expose the dashboard to untrusted users.
237
332
 
238
333
  ## Configuration
239
334
 
335
+ <details>
336
+ <summary>Show full configuration reference</summary>
337
+
240
338
  After installation, configure SolidObserver in `config/initializers/solid_observer.rb`:
241
339
 
242
340
  ```ruby
@@ -249,6 +347,16 @@ SolidObserver.configure do |config|
249
347
  # Enable queue monitoring (default: true)
250
348
  config.observe_queue = true
251
349
 
350
+ # Enable cache monitoring (default: false; requires SolidCache in host app)
351
+ config.observe_cache = true
352
+
353
+ # Enable cable monitoring (default: false; requires SolidCable in host app)
354
+ config.observe_cable = true
355
+ config.cable_sampling_rate = 0.1 # Sample 10% of broadcast events
356
+ config.cable_rejection_threshold = 0.05 # Rejection rate → Degraded stability
357
+ config.cable_backlog_threshold = 0.10 # Backlog ratio threshold
358
+ config.cable_error_threshold = 0.0 # Error rate threshold
359
+
252
360
  # Data Retention (persistence mode only)
253
361
  config.event_retention = 30.days # Keep events for 30 days
254
362
  config.metrics_retention = 90.days # Keep metrics for 90 days
@@ -266,8 +374,6 @@ end
266
374
 
267
375
  ### APM Integration
268
376
 
269
- Connect SolidObserver with your Application Performance Monitoring tool for distributed tracing:
270
-
271
377
  ```ruby
272
378
  SolidObserver.configure do |config|
273
379
  # Datadog APM
@@ -294,6 +400,26 @@ end
294
400
 
295
401
  When configured, all job events will include your correlation ID, allowing you to trace jobs back to the originating request.
296
402
 
403
+ ### Production hardening (recommended)
404
+
405
+ ```ruby
406
+ # Option A: fail at boot if the password env var is missing
407
+ SolidObserver.configure do |config|
408
+ config.ui_username = "admin"
409
+ config.ui_password = ENV.fetch("SOLID_OBSERVER_PASSWORD") # raises KeyError if unset
410
+ end
411
+
412
+ # Option B: only enable auth when the env var is present (no auth otherwise)
413
+ SolidObserver.configure do |config|
414
+ if (password = ENV["SOLID_OBSERVER_PASSWORD"]).present?
415
+ config.ui_username = "admin"
416
+ config.ui_password = password
417
+ end
418
+ end
419
+ ```
420
+
421
+ </details>
422
+
297
423
  ## CLI Reference
298
424
 
299
425
  **Available in both modes (real-time and persistence):**
@@ -315,8 +441,11 @@ When configured, all job events will include your correlation ID, allowing you t
315
441
  | `solid_observer:buffer:clear` | Clear buffer without saving |
316
442
  | `solid_observer:storage:cleanup` | Run retention-based cleanup |
317
443
  | `solid_observer:storage:purge` | Delete ALL SolidObserver data |
444
+ | `solid_observer:cache:clear` | Clear all SolidCache entries (with confirmation) |
445
+ | `solid_observer:cache:prune` | Prune expired SolidCache entries |
446
+ | `solid_observer:cable:trim` | Trim expired Solid Cable messages |
318
447
 
319
- > **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`.
448
+ > **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.
320
449
 
321
450
  ### Jobs List Arguments
322
451
 
@@ -330,57 +459,45 @@ Arguments are positional: `[status, queue, job_class, limit]`
330
459
  | 4th | Max results (default: 20) | `50` |
331
460
 
332
461
  ```bash
333
- # Examples
334
462
  bin/rails solid_observer:jobs:list # All ready jobs
335
463
  bin/rails "solid_observer:jobs:list[failed]" # Failed jobs
336
464
  bin/rails "solid_observer:jobs:list[ready,mailers]" # Ready jobs in mailers queue
337
465
  bin/rails "solid_observer:jobs:list[failed,,,50]" # 50 failed jobs (skip queue/class)
338
466
  ```
339
467
 
340
- ### Buffer & Storage Management (Persistence Mode)
341
-
342
- ```bash
343
- # Flush in-memory buffer to database
344
- bin/rails solid_observer:buffer:flush
345
-
346
- # Clear buffer without saving (loses pending events!)
347
- bin/rails solid_observer:buffer:clear
348
-
349
- # Run cleanup based on retention policy (default: 30 days)
350
- bin/rails solid_observer:storage:cleanup
351
-
352
- # Delete ALL SolidObserver data (events + snapshots, interactive confirmation)
353
- bin/rails solid_observer:storage:purge
354
- ```
355
-
356
- > **Important:** `storage:purge` deletes SolidObserver's monitoring data, NOT your Solid Queue jobs. Your queued jobs remain safe.
357
-
358
468
  ## Database Setup (Persistence Mode)
359
469
 
360
470
  > **Tip:** If you're using real-time mode (`storage_mode: :realtime`), you can skip this section entirely — no database setup is needed.
361
471
 
362
472
  **SolidObserver works with any main application database** — PostgreSQL, MySQL, or SQLite.
363
473
 
364
- 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:
474
+ For its own monitoring data, the install generator defaults to a **separate SQLite database** — file-based, no extra infrastructure needed.
475
+
476
+ <details>
477
+ <summary>Show multi-adapter setup and configuration details</summary>
478
+
479
+ The example below is what `rails generate solid_observer:install` produces:
365
480
 
366
481
  ```yaml
367
482
  # config/database.yml (generator default)
368
483
  solid_observer_queue:
369
- <<: *default
370
484
  adapter: sqlite3
485
+ pool: 5
486
+ timeout: 5000
371
487
  database: storage/<%= Rails.env %>_solid_observer_queue.sqlite3
488
+ migrations_paths: db/solid_observer_migrate
372
489
  ```
373
490
 
374
- > **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.
491
+ > **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.
375
492
 
376
493
  ### Multi-adapter setup
377
494
 
378
- 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.
495
+ 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:
379
496
 
380
497
  ```yaml
381
498
  # config/database.yml
382
499
  default: &default
383
- adapter: postgresql # host's adapter
500
+ adapter: postgresql
384
501
  encoding: unicode
385
502
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
386
503
 
@@ -388,7 +505,6 @@ development:
388
505
  primary:
389
506
  <<: *default
390
507
  database: my_app_development
391
- # ... cache / queue / cable on PG ...
392
508
 
393
509
  solid_observer_queue:
394
510
  adapter: sqlite3 # explicit override — do NOT merge *default
@@ -398,7 +514,7 @@ development:
398
514
  migrations_paths: db/solid_observer_migrate
399
515
  ```
400
516
 
401
- 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.
517
+ Apply the same pattern to `test:` and `production:`. For production with SQLite, ensure the `storage/` path is on persistent disk.
402
518
 
403
519
  **Bundler note (PG-only hosts):** if your `Gemfile` does not already include `sqlite3`, add it:
404
520
 
@@ -406,21 +522,19 @@ Apply the same pattern to `test:` and `production:`. For production with SQLite,
406
522
  gem "sqlite3", "~> 2.0"
407
523
  ```
408
524
 
409
- then `bundle install`. SolidObserver does not declare `sqlite3` as a runtime dependency adapter choice is yours.
525
+ **`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.
410
526
 
411
- **`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.
527
+ </details>
412
528
 
413
529
  ## Roadmap
414
530
 
415
- SolidObserver is actively developed. Here's what's coming:
416
-
417
531
  | Version | Focus | Status |
418
532
  |---------|-------|--------|
419
533
  | v0.1.0 | Solid Queue monitoring, CLI tools | ✅ Released |
420
534
  | v0.1.1 | Real-time mode (no migrations needed) | ✅ Released |
421
- | v0.3.0 | Web UI dashboard + stability hardening | ✅ Current |
422
- | v0.4.0 | Solid Cache monitoring | 🔜 Planned |
423
- | v0.5.0 | Solid Cable monitoring | 🔜 Planned |
535
+ | v0.3.0 | Web UI dashboard + stability hardening | ✅ Released |
536
+ | v0.4.0 | Solid Cache monitoring | Released |
537
+ | v0.5.0 | Solid Cable monitoring | Current |
424
538
  | v0.6.0 | Cross-component correlation, health scores | 🔜 Planned |
425
539
  | v0.7.0 | Alerting & notifications | 🔜 Planned |
426
540
  | v1.0.0 | Production stable release | 🎯 Goal |
@@ -478,7 +592,7 @@ config.solid_queue.connects_to = { database: { writing: :queue } }
478
592
 
479
593
  ### Multi-database setup
480
594
 
481
- > **See also:** [Multi-adapter setup](#multi-adapter-setup) in the Database Setup section for the full pattern (explicit `adapter:` override, `gem "sqlite3"` Bundler note, `migrations_paths` rationale).
595
+ > **See also:** [Multi-adapter setup](#multi-adapter-setup) in the Database Setup section for the full pattern.
482
596
 
483
597
  SolidObserver works with Rails multi-database configurations. Quick-reference example with PostgreSQL as your primary database and SQLite for SolidObserver's storage:
484
598
 
@@ -487,7 +601,6 @@ development:
487
601
  primary:
488
602
  adapter: postgresql
489
603
  database: myapp_development
490
- # ... PostgreSQL settings
491
604
  solid_observer_queue:
492
605
  adapter: sqlite3
493
606
  database: storage/development_solid_observer_queue.sqlite3
@@ -10,7 +10,7 @@
10
10
  var checkbox, rangeSelect, refreshBtn, helpBtn, helpPanel, helpWrapper, freshnessEl;
11
11
  var hoverActive = false;
12
12
  var sparks = {};
13
- var url = "/solid_observer/poll_data";
13
+ var url;
14
14
  var inFlight = false;
15
15
  var timerId = null;
16
16
  var lastFullSnapshot = null;
@@ -21,6 +21,8 @@
21
21
  var wrapper = document.querySelector("[data-so-live]");
22
22
  if (!wrapper) return;
23
23
 
24
+ url = wrapper.getAttribute("data-so-poll-url") || "/solid_observer/poll_data";
25
+
24
26
  checkbox = wrapper.querySelector('[data-so-live-toggle]');
25
27
  if (!checkbox) return;
26
28
 
@@ -6,6 +6,7 @@ module SolidObserver
6
6
  [
7
7
  *([PG::ConnectionBad] if defined?(PG::ConnectionBad)),
8
8
  *([Mysql2::Error::ConnectionError] if defined?(Mysql2::Error::ConnectionError)),
9
+ *([Trilogy::Error] if defined?(Trilogy::Error)),
9
10
  *([SQLite3::CantOpenException] if defined?(SQLite3::CantOpenException))
10
11
  ]
11
12
  end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "dashboard_controller"
4
+
5
+ module SolidObserver
6
+ # :reek:TooManyInstanceVariables
7
+ class CableDashboardController < DashboardController
8
+ CABLE_STORAGE_COMPONENTS = %w[cable_observer solid_cable].freeze
9
+
10
+ def index
11
+ @component = "cable"
12
+ assign_cable_dashboard
13
+ end
14
+
15
+ private
16
+
17
+ # :reek:TooManyStatements
18
+ def assign_cable_dashboard
19
+ unless SolidObserver.config.solid_cable_enabled?
20
+ @cable_dashboard_available = false
21
+ @storage_components = []
22
+ @recent_events = []
23
+ return
24
+ end
25
+
26
+ range = SolidObserver::Services::CableStats.parse_range(request_range_param)
27
+ window = SolidObserver::Services::CableStats.range_duration(range)
28
+ stats = SolidObserver::Services::CableStats.call(window: window)
29
+
30
+ @cable_dashboard_available = true
31
+ @range = range
32
+ @stats = stats
33
+ @storage_components = cable_storage_components
34
+ @recent_events = recent_events(window)
35
+ end
36
+
37
+ def cable_storage_components
38
+ SolidObserver::Services::StorageInfoSnapshot.call.select do |snapshot|
39
+ CABLE_STORAGE_COMPONENTS.include?(snapshot[:component])
40
+ end
41
+ rescue *SolidObserver::Services::StorageInfoSnapshot::CONNECTION_ERRORS, TypeError, NoMethodError
42
+ []
43
+ end
44
+
45
+ def recent_events(window)
46
+ current_time = Time.current
47
+ SolidObserver::CableEvent.where(recorded_at: (current_time - window)..current_time).recent(10)
48
+ rescue ActiveRecord::StatementInvalid
49
+ []
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidObserver
4
+ class CableOperationsController < ApplicationController
5
+ def trim
6
+ redirect_with_result(SolidObserver::Services::CableOperations.trim)
7
+ end
8
+
9
+ private
10
+
11
+ def redirect_with_result(result)
12
+ flash_key = result[:ok] ? :notice : :alert
13
+ redirect_to cable_dashboard_path, flash_key => result[:message]
14
+ end
15
+ end
16
+ end