solid_observer 0.3.0 → 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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +21 -0
- data/README.md +134 -81
- data/app/assets/stylesheets/solid_observer/application.css +18 -0
- data/app/controllers/solid_observer/cache_dashboard_controller.rb +59 -0
- data/app/controllers/solid_observer/cache_operations_controller.rb +24 -0
- data/app/controllers/solid_observer/dashboard_controller.rb +44 -1
- data/app/controllers/solid_observer/storages_controller.rb +1 -1
- data/app/helpers/solid_observer/application_helper.rb +154 -5
- data/app/helpers/solid_observer/dashboard_helper.rb +30 -11
- data/app/models/solid_observer/cache_event.rb +15 -0
- data/app/models/solid_observer/cache_metric.rb +14 -0
- data/app/models/solid_observer/storage_info.rb +4 -1
- data/app/views/layouts/solid_observer/application.html.erb +144 -17
- 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/index.html.erb +34 -4
- data/app/views/solid_observer/jobs/show.html.erb +3 -3
- data/app/views/solid_observer/storages/show.html.erb +64 -32
- data/bin/quality_gate +1 -1
- data/config/routes.rb +5 -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/lib/generators/solid_observer/templates/initializer.rb.tt +2 -1
- data/lib/solid_observer/cache_event_buffer.rb +53 -0
- data/lib/solid_observer/cache_subscriber.rb +47 -0
- data/lib/solid_observer/cli/storage.rb +16 -13
- data/lib/solid_observer/configuration.rb +22 -3
- data/lib/solid_observer/engine.rb +44 -7
- data/lib/solid_observer/services/cache_operations.rb +115 -0
- data/lib/solid_observer/services/cache_stats.rb +329 -0
- data/lib/solid_observer/services/cleanup_storage.rb +18 -2
- data/lib/solid_observer/services/database_size.rb +13 -8
- data/lib/solid_observer/services/flush_cache_event_buffer.rb +54 -0
- data/lib/solid_observer/services/record_cache_event.rb +142 -0
- data/lib/solid_observer/services/record_cache_metric.rb +74 -0
- data/lib/solid_observer/services/storage_info_snapshot.rb +128 -0
- data/lib/solid_observer/version.rb +1 -1
- data/lib/tasks/solid_observer.rake +29 -0
- metadata +23 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a9c3e774c9276371b59f696226979b922772191e0531a4d9ba2dbb09b8e3e452
|
|
4
|
+
data.tar.gz: 570ae5aacef981358f68998d3df14bfc532e2af48cbdd21c6cd18ecbfa7132e7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 50684bd0b286fc9b93f77a57f5d0b60c3e0334b6d7804bf63307b8cdc05a78cab5bb586305b96617f5fe0e76f76cd89112c6cb6e059a45d190ca6dc7c49fa885
|
|
7
|
+
data.tar.gz: 9e456400209630d4e3b777355bf1d1a651e0bac1d90d6d40de4272c9cce8afb322bf5ff9afed10fe1a9e0911c7e84c15d02e6b2de3360e04f5d4d349bbf02f34
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,24 @@
|
|
|
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
|
+
|
|
1
22
|
## [0.3.0] - 2026-05-25
|
|
2
23
|
|
|
3
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.
|
data/README.md
CHANGED
|
@@ -7,35 +7,39 @@
|
|
|
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.4.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.4.0 covers both **Solid Queue** and **Solid Cache** with a unified Web UI dashboard, CLI tools, metrics collection, and distributed tracing support.
|
|
22
22
|
|
|
23
|
-
## Features (v0.
|
|
23
|
+
## Features (v0.4.0)
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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 |
|
|
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)
|
|
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
|
|
|
@@ -169,14 +173,6 @@ Configuration:
|
|
|
169
173
|
|
|
170
174
|
SolidObserver ships with a zero-dependency Web UI (no asset pipeline, no JS framework) at `/solid_observer`.
|
|
171
175
|
|
|
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
176
|
### Mount
|
|
181
177
|
|
|
182
178
|
The install generator mounts it for you. To mount manually:
|
|
@@ -186,7 +182,7 @@ The install generator mounts it for you. To mount manually:
|
|
|
186
182
|
mount SolidObserver::Engine, at: "/solid_observer"
|
|
187
183
|
```
|
|
188
184
|
|
|
189
|
-
### Configuration
|
|
185
|
+
### Auth Configuration
|
|
190
186
|
|
|
191
187
|
```ruby
|
|
192
188
|
SolidObserver.configure do |config|
|
|
@@ -200,43 +196,95 @@ end
|
|
|
200
196
|
| Option | Default | Purpose |
|
|
201
197
|
|---|---|---|
|
|
202
198
|
| `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"` |
|
|
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 |
|
|
205
201
|
|
|
206
|
-
|
|
202
|
+
For production hardening (fail-loud on missing env var vs. fail-open), see [Configuration](#configuration) below.
|
|
207
203
|
|
|
208
|
-
###
|
|
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>
|
|
209
229
|
|
|
210
|
-
|
|
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:
|
|
211
257
|
|
|
212
258
|
```ruby
|
|
213
|
-
# Option A: fail at boot if the password env var is missing
|
|
214
259
|
SolidObserver.configure do |config|
|
|
215
|
-
config.
|
|
216
|
-
config.ui_password = ENV.fetch("SOLID_OBSERVER_PASSWORD") # raises KeyError if unset
|
|
260
|
+
config.observe_cache = true # default: false
|
|
217
261
|
end
|
|
262
|
+
```
|
|
218
263
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
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
|
|
226
271
|
```
|
|
227
272
|
|
|
228
|
-
|
|
273
|
+
### Storage
|
|
229
274
|
|
|
230
|
-
|
|
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.
|
|
231
276
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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.
|
|
237
282
|
|
|
238
283
|
## Configuration
|
|
239
284
|
|
|
285
|
+
<details>
|
|
286
|
+
<summary>Show full configuration reference</summary>
|
|
287
|
+
|
|
240
288
|
After installation, configure SolidObserver in `config/initializers/solid_observer.rb`:
|
|
241
289
|
|
|
242
290
|
```ruby
|
|
@@ -249,6 +297,9 @@ SolidObserver.configure do |config|
|
|
|
249
297
|
# Enable queue monitoring (default: true)
|
|
250
298
|
config.observe_queue = true
|
|
251
299
|
|
|
300
|
+
# Enable cache monitoring (default: false; requires SolidCache in host app)
|
|
301
|
+
config.observe_cache = true
|
|
302
|
+
|
|
252
303
|
# Data Retention (persistence mode only)
|
|
253
304
|
config.event_retention = 30.days # Keep events for 30 days
|
|
254
305
|
config.metrics_retention = 90.days # Keep metrics for 90 days
|
|
@@ -266,8 +317,6 @@ end
|
|
|
266
317
|
|
|
267
318
|
### APM Integration
|
|
268
319
|
|
|
269
|
-
Connect SolidObserver with your Application Performance Monitoring tool for distributed tracing:
|
|
270
|
-
|
|
271
320
|
```ruby
|
|
272
321
|
SolidObserver.configure do |config|
|
|
273
322
|
# Datadog APM
|
|
@@ -294,6 +343,26 @@ end
|
|
|
294
343
|
|
|
295
344
|
When configured, all job events will include your correlation ID, allowing you to trace jobs back to the originating request.
|
|
296
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
|
+
|
|
297
366
|
## CLI Reference
|
|
298
367
|
|
|
299
368
|
**Available in both modes (real-time and persistence):**
|
|
@@ -315,8 +384,10 @@ When configured, all job events will include your correlation ID, allowing you t
|
|
|
315
384
|
| `solid_observer:buffer:clear` | Clear buffer without saving |
|
|
316
385
|
| `solid_observer:storage:cleanup` | Run retention-based cleanup |
|
|
317
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 |
|
|
318
389
|
|
|
319
|
-
> **Note:** Storage commands manage **SolidObserver's storage** (event logs, metrics, snapshots) — not Solid Queue's jobs.
|
|
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.
|
|
320
391
|
|
|
321
392
|
### Jobs List Arguments
|
|
322
393
|
|
|
@@ -330,38 +401,24 @@ Arguments are positional: `[status, queue, job_class, limit]`
|
|
|
330
401
|
| 4th | Max results (default: 20) | `50` |
|
|
331
402
|
|
|
332
403
|
```bash
|
|
333
|
-
# Examples
|
|
334
404
|
bin/rails solid_observer:jobs:list # All ready jobs
|
|
335
405
|
bin/rails "solid_observer:jobs:list[failed]" # Failed jobs
|
|
336
406
|
bin/rails "solid_observer:jobs:list[ready,mailers]" # Ready jobs in mailers queue
|
|
337
407
|
bin/rails "solid_observer:jobs:list[failed,,,50]" # 50 failed jobs (skip queue/class)
|
|
338
408
|
```
|
|
339
409
|
|
|
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
410
|
## Database Setup (Persistence Mode)
|
|
359
411
|
|
|
360
412
|
> **Tip:** If you're using real-time mode (`storage_mode: :realtime`), you can skip this section entirely — no database setup is needed.
|
|
361
413
|
|
|
362
414
|
**SolidObserver works with any main application database** — PostgreSQL, MySQL, or SQLite.
|
|
363
415
|
|
|
364
|
-
For its own monitoring data, the install generator defaults to a **separate SQLite database** — file-based, no extra infrastructure needed.
|
|
416
|
+
For its own monitoring data, the install generator defaults to a **separate SQLite database** — file-based, no extra infrastructure needed.
|
|
417
|
+
|
|
418
|
+
<details>
|
|
419
|
+
<summary>Show multi-adapter setup and configuration details</summary>
|
|
420
|
+
|
|
421
|
+
The example below is what `rails generate solid_observer:install` produces:
|
|
365
422
|
|
|
366
423
|
```yaml
|
|
367
424
|
# config/database.yml (generator default)
|
|
@@ -371,16 +428,16 @@ solid_observer_queue:
|
|
|
371
428
|
database: storage/<%= Rails.env %>_solid_observer_queue.sqlite3
|
|
372
429
|
```
|
|
373
430
|
|
|
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
|
|
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.
|
|
375
432
|
|
|
376
433
|
### Multi-adapter setup
|
|
377
434
|
|
|
378
|
-
If your host application uses
|
|
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:
|
|
379
436
|
|
|
380
437
|
```yaml
|
|
381
438
|
# config/database.yml
|
|
382
439
|
default: &default
|
|
383
|
-
adapter: postgresql
|
|
440
|
+
adapter: postgresql
|
|
384
441
|
encoding: unicode
|
|
385
442
|
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
|
|
386
443
|
|
|
@@ -388,7 +445,6 @@ development:
|
|
|
388
445
|
primary:
|
|
389
446
|
<<: *default
|
|
390
447
|
database: my_app_development
|
|
391
|
-
# ... cache / queue / cable on PG ...
|
|
392
448
|
|
|
393
449
|
solid_observer_queue:
|
|
394
450
|
adapter: sqlite3 # explicit override — do NOT merge *default
|
|
@@ -398,7 +454,7 @@ development:
|
|
|
398
454
|
migrations_paths: db/solid_observer_migrate
|
|
399
455
|
```
|
|
400
456
|
|
|
401
|
-
Apply the same pattern to `test:` and `production:`. For production with SQLite, ensure the `storage/` path is on persistent disk
|
|
457
|
+
Apply the same pattern to `test:` and `production:`. For production with SQLite, ensure the `storage/` path is on persistent disk.
|
|
402
458
|
|
|
403
459
|
**Bundler note (PG-only hosts):** if your `Gemfile` does not already include `sqlite3`, add it:
|
|
404
460
|
|
|
@@ -406,20 +462,18 @@ Apply the same pattern to `test:` and `production:`. For production with SQLite,
|
|
|
406
462
|
gem "sqlite3", "~> 2.0"
|
|
407
463
|
```
|
|
408
464
|
|
|
409
|
-
|
|
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.
|
|
410
466
|
|
|
411
|
-
|
|
467
|
+
</details>
|
|
412
468
|
|
|
413
469
|
## Roadmap
|
|
414
470
|
|
|
415
|
-
SolidObserver is actively developed. Here's what's coming:
|
|
416
|
-
|
|
417
471
|
| Version | Focus | Status |
|
|
418
472
|
|---------|-------|--------|
|
|
419
473
|
| v0.1.0 | Solid Queue monitoring, CLI tools | ✅ Released |
|
|
420
474
|
| 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 |
|
|
475
|
+
| v0.3.0 | Web UI dashboard + stability hardening | ✅ Released |
|
|
476
|
+
| v0.4.0 | Solid Cache monitoring | ✅ Current |
|
|
423
477
|
| v0.5.0 | Solid Cable monitoring | 🔜 Planned |
|
|
424
478
|
| v0.6.0 | Cross-component correlation, health scores | 🔜 Planned |
|
|
425
479
|
| v0.7.0 | Alerting & notifications | 🔜 Planned |
|
|
@@ -478,7 +532,7 @@ config.solid_queue.connects_to = { database: { writing: :queue } }
|
|
|
478
532
|
|
|
479
533
|
### Multi-database setup
|
|
480
534
|
|
|
481
|
-
> **See also:** [Multi-adapter setup](#multi-adapter-setup) in the Database Setup section for the full pattern
|
|
535
|
+
> **See also:** [Multi-adapter setup](#multi-adapter-setup) in the Database Setup section for the full pattern.
|
|
482
536
|
|
|
483
537
|
SolidObserver works with Rails multi-database configurations. Quick-reference example with PostgreSQL as your primary database and SQLite for SolidObserver's storage:
|
|
484
538
|
|
|
@@ -487,7 +541,6 @@ development:
|
|
|
487
541
|
primary:
|
|
488
542
|
adapter: postgresql
|
|
489
543
|
database: myapp_development
|
|
490
|
-
# ... PostgreSQL settings
|
|
491
544
|
solid_observer_queue:
|
|
492
545
|
adapter: sqlite3
|
|
493
546
|
database: storage/development_solid_observer_queue.sqlite3
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
.so-sidebar__nav .active {
|
|
2
|
+
font-weight: 600;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
.so-sidebar__nav a:focus-visible {
|
|
6
|
+
outline: 2px solid var(--so-info);
|
|
7
|
+
outline-offset: 2px;
|
|
8
|
+
box-shadow: 0 0 0 3px var(--so-focus-ring);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.so-sidebar__section {
|
|
12
|
+
font-size: 0.7rem;
|
|
13
|
+
text-transform: uppercase;
|
|
14
|
+
letter-spacing: 0.08em;
|
|
15
|
+
color: var(--so-text-muted);
|
|
16
|
+
padding: 0.5rem 0.75rem 0.25rem;
|
|
17
|
+
margin: 0.5rem 0.75rem 0.25rem;
|
|
18
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "dashboard_controller"
|
|
4
|
+
|
|
5
|
+
module SolidObserver
|
|
6
|
+
class CacheDashboardController < DashboardController
|
|
7
|
+
CACHE_STORAGE_COMPONENTS = %w[solid_cache cache_observer].freeze
|
|
8
|
+
|
|
9
|
+
class << self
|
|
10
|
+
def cache_dashboard_assignments(range_param:)
|
|
11
|
+
return unavailable_assignments unless SolidObserver.config.solid_cache_enabled?
|
|
12
|
+
|
|
13
|
+
range = SolidObserver::Services::CacheStats.parse_range(range_param)
|
|
14
|
+
window = SolidObserver::Services::CacheStats.range_duration(range)
|
|
15
|
+
stats = SolidObserver::Services::CacheStats.call(window: window)
|
|
16
|
+
|
|
17
|
+
{
|
|
18
|
+
cache_dashboard_available: true,
|
|
19
|
+
range: range,
|
|
20
|
+
stats: stats,
|
|
21
|
+
activity_trends: stats[:activity_trends],
|
|
22
|
+
stability: stats[:stability],
|
|
23
|
+
storage_components: cache_storage_components,
|
|
24
|
+
recent_events: recent_events(window)
|
|
25
|
+
}
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def unavailable_assignments
|
|
31
|
+
{
|
|
32
|
+
cache_dashboard_available: false,
|
|
33
|
+
storage_components: [],
|
|
34
|
+
recent_events: [],
|
|
35
|
+
activity_trends: SolidObserver::Services::CacheStats::ACTIVITY_TREND_EMPTY,
|
|
36
|
+
stability: SolidObserver::Services::CacheStats::STABILITY_EMPTY
|
|
37
|
+
}
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def cache_storage_components
|
|
41
|
+
SolidObserver::Services::StorageInfoSnapshot.call.select do |snapshot|
|
|
42
|
+
CACHE_STORAGE_COMPONENTS.include?(snapshot[:component])
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def recent_events(window)
|
|
47
|
+
current_time = Time.current
|
|
48
|
+
SolidObserver::CacheEvent.where(recorded_at: (current_time - window)..current_time).recent(10)
|
|
49
|
+
rescue ActiveRecord::StatementInvalid
|
|
50
|
+
[]
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def index
|
|
55
|
+
@component = "cache"
|
|
56
|
+
assign_cache_dashboard
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SolidObserver
|
|
4
|
+
class CacheOperationsController < ApplicationController
|
|
5
|
+
def index
|
|
6
|
+
@cache_controls_available = SolidObserver::Services::CacheOperations.available?
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def prune
|
|
10
|
+
redirect_with_result(SolidObserver::Services::CacheOperations.prune)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def clear
|
|
14
|
+
redirect_with_result(SolidObserver::Services::CacheOperations.clear)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def redirect_with_result(result)
|
|
20
|
+
flash_key = result[:ok] ? :notice : :alert
|
|
21
|
+
redirect_to cache_operations_path, flash_key => result[:message]
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "../../helpers/solid_observer/application_helper"
|
|
4
|
+
|
|
3
5
|
module SolidObserver
|
|
4
6
|
class DashboardController < ApplicationController
|
|
7
|
+
helper SolidObserver::ApplicationHelper
|
|
8
|
+
|
|
5
9
|
skip_forgery_protection only: :live_poll
|
|
6
10
|
skip_after_action :verify_same_origin_request, only: :live_poll
|
|
7
11
|
|
|
8
12
|
def index
|
|
13
|
+
@component = selected_component
|
|
14
|
+
return assign_cache_dashboard if @component == "cache"
|
|
15
|
+
|
|
16
|
+
return unless @component == "queue" && SolidObserver.config.solid_queue_enabled?
|
|
17
|
+
|
|
9
18
|
assign_range_and_stats
|
|
10
19
|
load_persistence_data if persistence_mode?
|
|
11
20
|
end
|
|
@@ -32,13 +41,25 @@ module SolidObserver
|
|
|
32
41
|
@range = range
|
|
33
42
|
@live = request_live_param == "on"
|
|
34
43
|
@stats = QueueStats.snapshot(range: range)
|
|
35
|
-
@chart =
|
|
44
|
+
@chart = if @stats[:available]
|
|
45
|
+
QueueStats.chart_data(window: QueueStats.range_duration(@range))
|
|
46
|
+
else
|
|
47
|
+
{performed: [], failed: [], ready: []}
|
|
48
|
+
end
|
|
49
|
+
rescue
|
|
50
|
+
@chart = {performed: [], failed: [], ready: []}
|
|
36
51
|
end
|
|
37
52
|
|
|
38
53
|
def load_persistence_data
|
|
39
54
|
@recent_events = QueueEvent.recent(10)
|
|
40
55
|
end
|
|
41
56
|
|
|
57
|
+
def assign_cache_dashboard
|
|
58
|
+
SolidObserver::CacheDashboardController.cache_dashboard_assignments(range_param: request_range_param).each do |name, value|
|
|
59
|
+
instance_variable_set("@#{name}", value)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
42
63
|
def request_range_param
|
|
43
64
|
request&.query_parameters&.[]("range") || request&.query_parameters&.[](:range)
|
|
44
65
|
end
|
|
@@ -75,5 +96,27 @@ module SolidObserver
|
|
|
75
96
|
def append_chart_buffer
|
|
76
97
|
ChartBuffer.append(SolidQueue::ReadyExecution.count) if QueueStats.solid_queue_available?
|
|
77
98
|
end
|
|
99
|
+
|
|
100
|
+
def selected_component
|
|
101
|
+
requested = if request&.respond_to?(:path_parameters)
|
|
102
|
+
request.path_parameters&.[](:component).to_s
|
|
103
|
+
else
|
|
104
|
+
""
|
|
105
|
+
end
|
|
106
|
+
requested = path_component if requested.empty?
|
|
107
|
+
return "cache" if requested == "cache" && SolidObserver.config.solid_cache_enabled?
|
|
108
|
+
|
|
109
|
+
"queue"
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def path_component
|
|
113
|
+
return "" unless request&.respond_to?(:path)
|
|
114
|
+
|
|
115
|
+
path = request&.path.to_s
|
|
116
|
+
return "cache" if path.end_with?("/cache")
|
|
117
|
+
return "queue" if path.end_with?("/queue")
|
|
118
|
+
|
|
119
|
+
""
|
|
120
|
+
end
|
|
78
121
|
end
|
|
79
122
|
end
|
|
@@ -5,7 +5,7 @@ module SolidObserver
|
|
|
5
5
|
include RequirePersistenceMode
|
|
6
6
|
|
|
7
7
|
def show
|
|
8
|
-
@
|
|
8
|
+
@storage_components = SolidObserver::Services::StorageInfoSnapshot.call
|
|
9
9
|
@storage_history = SolidObserver::StorageInfo.recent(20)
|
|
10
10
|
end
|
|
11
11
|
end
|