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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +34 -0
- data/README.md +195 -82
- data/app/assets/javascripts/solid_observer/live_poll.js +3 -1
- data/app/controllers/solid_observer/application_controller.rb +1 -0
- data/app/controllers/solid_observer/cable_dashboard_controller.rb +52 -0
- data/app/controllers/solid_observer/cable_operations_controller.rb +16 -0
- data/app/controllers/solid_observer/cache_dashboard_controller.rb +52 -0
- data/app/controllers/solid_observer/cache_operations_controller.rb +24 -0
- data/app/controllers/solid_observer/dashboard_controller.rb +38 -1
- data/app/controllers/solid_observer/storages_controller.rb +1 -1
- data/app/helpers/solid_observer/application_helper.rb +268 -5
- data/app/helpers/solid_observer/dashboard_helper.rb +30 -11
- data/app/models/solid_observer/cable_event.rb +13 -0
- data/app/models/solid_observer/cable_metric.rb +12 -0
- data/app/models/solid_observer/cache_event.rb +15 -0
- data/app/models/solid_observer/cache_metric.rb +13 -0
- data/app/models/solid_observer/storage_info.rb +4 -1
- data/app/views/layouts/solid_observer/application.html.erb +157 -19
- data/app/views/solid_observer/cable_dashboard/_charts.html.erb +31 -0
- data/app/views/solid_observer/cable_dashboard/_recent_events.html.erb +34 -0
- data/app/views/solid_observer/cable_dashboard/_summary.html.erb +34 -0
- data/app/views/solid_observer/cable_dashboard/index.html.erb +118 -0
- data/app/views/solid_observer/cache_dashboard/_charts.html.erb +40 -0
- data/app/views/solid_observer/cache_dashboard/_recent_events.html.erb +34 -0
- data/app/views/solid_observer/cache_dashboard/_summary.html.erb +39 -0
- data/app/views/solid_observer/cache_dashboard/index.html.erb +62 -0
- data/app/views/solid_observer/cache_operations/_confirm_clear.html.erb +6 -0
- data/app/views/solid_observer/cache_operations/index.html.erb +60 -0
- data/app/views/solid_observer/dashboard/_queue_table.html.erb +1 -0
- data/app/views/solid_observer/dashboard/index.html.erb +32 -5
- data/app/views/solid_observer/events/index.html.erb +1 -0
- data/app/views/solid_observer/jobs/index.html.erb +1 -0
- data/app/views/solid_observer/jobs/show.html.erb +3 -3
- data/app/views/solid_observer/storages/show.html.erb +90 -32
- data/config/routes.rb +7 -0
- data/db/migrate/20260601000001_create_solid_observer_cache_events.rb +22 -0
- data/db/migrate/20260601000002_create_solid_observer_cache_metrics.rb +18 -0
- data/db/migrate/20260602000001_add_component_to_solid_observer_storage_infos.rb +8 -0
- data/db/migrate/20260612000001_add_event_type_recorded_at_index_to_cache_events.rb +21 -0
- data/db/migrate/20260619000001_create_solid_observer_cable_events.rb +22 -0
- data/db/migrate/20260619000002_create_solid_observer_cable_metrics.rb +17 -0
- data/lib/generators/solid_observer/install_generator.rb +8 -1
- data/lib/generators/solid_observer/templates/initializer.rb.tt +20 -4
- data/lib/solid_observer/base_event.rb +1 -1
- data/lib/solid_observer/base_metric.rb +1 -1
- data/lib/solid_observer/base_record.rb +8 -0
- data/lib/solid_observer/cable_event_buffer.rb +28 -0
- data/lib/solid_observer/cable_metric_buffer.rb +230 -0
- data/lib/solid_observer/cable_subscriber.rb +57 -0
- data/lib/solid_observer/cache_event_buffer.rb +28 -0
- data/lib/solid_observer/cache_metric_buffer.rb +229 -0
- data/lib/solid_observer/cache_subscriber.rb +47 -0
- data/lib/solid_observer/chart_buffer.rb +84 -27
- data/lib/solid_observer/cli/storage.rb +16 -13
- data/lib/solid_observer/configuration.rb +67 -5
- data/lib/solid_observer/engine.rb +70 -15
- data/lib/solid_observer/event_buffer_core.rb +218 -0
- data/lib/solid_observer/queue_event_buffer.rb +9 -201
- data/lib/solid_observer/services/cable_operations.rb +74 -0
- data/lib/solid_observer/services/cable_stats.rb +385 -0
- data/lib/solid_observer/services/cache_operations.rb +115 -0
- data/lib/solid_observer/services/cache_stats.rb +346 -0
- data/lib/solid_observer/services/cleanup_storage.rb +98 -47
- data/lib/solid_observer/services/database_size.rb +13 -8
- data/lib/solid_observer/services/flush_cable_event_buffer.rb +54 -0
- data/lib/solid_observer/services/flush_cable_metrics.rb +54 -0
- data/lib/solid_observer/services/flush_cache_event_buffer.rb +54 -0
- data/lib/solid_observer/services/flush_cache_metrics.rb +56 -0
- data/lib/solid_observer/services/record_cable_event.rb +114 -0
- data/lib/solid_observer/services/record_cable_metric.rb +73 -0
- data/lib/solid_observer/services/record_cache_event.rb +165 -0
- data/lib/solid_observer/services/record_cache_metric.rb +66 -0
- data/lib/solid_observer/services/storage_info_snapshot.rb +216 -0
- data/lib/solid_observer/version.rb +1 -1
- data/lib/solid_observer.rb +36 -11
- data/lib/tasks/solid_observer.rake +111 -21
- metadata +47 -5
- data/bin/console +0 -11
- data/bin/quality_gate +0 -95
- data/bin/setup +0 -8
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 563c4874ef7e6a8ad8a485d0b3f86c9d1d8f924d5308d44c5137f76bda7e03de
|
|
4
|
+
data.tar.gz: 88ba3331b45977349604047db1a4e3e98efb17ebc1eca8d4c52a96825d320a83
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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.
|
|
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.
|
|
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="
|
|
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.
|
|
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.
|
|
23
|
+
## Features (v0.5.0)
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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 (
|
|
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
|
-
>
|
|
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
|
-
|
|
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
|
|
204
|
-
| `ui_base_controller` | `"ApplicationController"` |
|
|
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
|
-
|
|
216
|
+
For production hardening (fail-loud on missing env var vs. fail-open), see [Configuration](#configuration) below.
|
|
207
217
|
|
|
208
|
-
###
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
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
|
-
|
|
306
|
+
`solid_cable_enabled?` is `true` when both `observe_cable = true` and SolidCable is available in the host app.
|
|
229
307
|
|
|
230
|
-
|
|
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
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 | ✅
|
|
422
|
-
| v0.4.0 | Solid Cache monitoring |
|
|
423
|
-
| v0.5.0 | Solid Cable monitoring |
|
|
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
|
|
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
|
|
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
|