anomonitor 0.6.1
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 +7 -0
- data/CHANGELOG.md +90 -0
- data/MIT-LICENSE +20 -0
- data/README.md +275 -0
- data/RELEASE.md +32 -0
- data/Rakefile +13 -0
- data/SECURITY.md +24 -0
- data/app/assets/config/anomonitor_manifest.js +1 -0
- data/app/assets/stylesheets/anomonitor/application.css +370 -0
- data/app/controllers/anomonitor/anomalies_controller.rb +44 -0
- data/app/controllers/anomonitor/application_controller.rb +21 -0
- data/app/controllers/anomonitor/dashboard_controller.rb +69 -0
- data/app/controllers/anomonitor/jobs_controller.rb +61 -0
- data/app/controllers/anomonitor/metrics_controller.rb +18 -0
- data/app/controllers/anomonitor/mutes_controller.rb +41 -0
- data/app/helpers/anomonitor/application_helper.rb +71 -0
- data/app/models/anomonitor/anomaly.rb +105 -0
- data/app/models/anomonitor/application_record.rb +5 -0
- data/app/models/anomonitor/metric_sample.rb +25 -0
- data/app/models/anomonitor/mute.rb +56 -0
- data/app/views/anomonitor/anomalies/index.html.erb +54 -0
- data/app/views/anomonitor/anomalies/show.html.erb +54 -0
- data/app/views/anomonitor/dashboard/show.html.erb +124 -0
- data/app/views/anomonitor/jobs/index.html.erb +135 -0
- data/app/views/anomonitor/metrics/index.html.erb +33 -0
- data/app/views/anomonitor/mutes/index.html.erb +56 -0
- data/app/views/layouts/anomonitor/application.html.erb +39 -0
- data/config/routes.rb +15 -0
- data/db/migrate/20260807100000_create_anomonitor_metric_samples.rb +17 -0
- data/db/migrate/20260807100001_create_anomonitor_anomalies.rb +23 -0
- data/db/migrate/20260808100000_add_resolved_at_to_anomonitor_anomalies.rb +6 -0
- data/db/migrate/20260808120000_create_anomonitor_mutes.rb +16 -0
- data/lib/anomonitor/collectors/base.rb +25 -0
- data/lib/anomonitor/collectors/delayed_job.rb +55 -0
- data/lib/anomonitor/collectors/schema_drift.rb +144 -0
- data/lib/anomonitor/collectors/sidekiq.rb +47 -0
- data/lib/anomonitor/collectors/solid_queue.rb +57 -0
- data/lib/anomonitor/collectors/table.rb +116 -0
- data/lib/anomonitor/configuration.rb +184 -0
- data/lib/anomonitor/configuration_validator.rb +40 -0
- data/lib/anomonitor/detector.rb +195 -0
- data/lib/anomonitor/digester.rb +79 -0
- data/lib/anomonitor/engine.rb +35 -0
- data/lib/anomonitor/jobs/browser.rb +89 -0
- data/lib/anomonitor/jobs/browsers/delayed_job.rb +143 -0
- data/lib/anomonitor/jobs/browsers/sidekiq.rb +164 -0
- data/lib/anomonitor/jobs/browsers/solid_queue.rb +149 -0
- data/lib/anomonitor/jobs/browsers/table.rb +206 -0
- data/lib/anomonitor/jobs/row.rb +30 -0
- data/lib/anomonitor/metric_point.rb +42 -0
- data/lib/anomonitor/metrics_export.rb +55 -0
- data/lib/anomonitor/notifiers/callable.rb +44 -0
- data/lib/anomonitor/notifiers/composite.rb +33 -0
- data/lib/anomonitor/notifiers/rate_limited.rb +44 -0
- data/lib/anomonitor/notifiers/webhook.rb +108 -0
- data/lib/anomonitor/notifiers.rb +59 -0
- data/lib/anomonitor/poll_lock.rb +67 -0
- data/lib/anomonitor/poller.rb +164 -0
- data/lib/anomonitor/tenancy.rb +46 -0
- data/lib/anomonitor/version.rb +3 -0
- data/lib/anomonitor.rb +52 -0
- data/lib/generators/anomonitor/install/install_generator.rb +27 -0
- data/lib/generators/anomonitor/install/templates/anomonitor.rb +87 -0
- data/lib/tasks/anomonitor_tasks.rake +27 -0
- metadata +126 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 6598d41498f36ba18da8391e8dff7dd940532ca218cbe5240357d496ed24c71a
|
|
4
|
+
data.tar.gz: 5064d81305a4c871b0ccaa1d6ab398c316ff09fc5a7ea6511ceb771f584c15c6
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 16534086abd65a8fb0e1888440e96dc1b4aafd146743489e05bc854c38b09f4a388e3458a14fa4b228a9c842a522259334c165c253d578d75b9b2d55101a4eca
|
|
7
|
+
data.tar.gz: eef964fa8914f22ff0a0fc031423f3a0a2fd3c5be6064018bedf07a39fb10dff28afb513175bd18a3edd08eb90f95e7188a44e3223e9a9d335b18cb22d57cac4
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.6.1
|
|
4
|
+
|
|
5
|
+
- CI: allow `sqlite3 >= 2.1` for Rails 8; matrix Rails 7.1 / 7.2 / 8.0
|
|
6
|
+
|
|
7
|
+
## 0.6.0
|
|
8
|
+
|
|
9
|
+
- Mutes / snoozes (`anomonitor_mutes` + `/mutes` UI + `c.mute(...)`)
|
|
10
|
+
- Anomaly digest mode (`c.digest_interval`) → `anomaly.digest` webhook batch
|
|
11
|
+
- Alert `match:` filters (e.g. per-queue Sidekiq: `match: { queue: true }`)
|
|
12
|
+
- Manual resolve emits `anomaly.acked` (auto-clear still `anomaly.resolved`)
|
|
13
|
+
- Notifier rate limit (`c.notifier_rate_limit`)
|
|
14
|
+
- Jobs browser expandable detail (args / handler / error)
|
|
15
|
+
- Overview tenant filter + by-tenant summary
|
|
16
|
+
- SchemaDrift collector tests + engine route surface tests
|
|
17
|
+
- RubyGems release notes (`RELEASE.md`) + gemspec MFA metadata
|
|
18
|
+
|
|
19
|
+
## 0.5.0
|
|
20
|
+
|
|
21
|
+
- Manual resolve/ack and reopen on anomaly detail (sticky silence until clear)
|
|
22
|
+
- Retry failed webhooks from UI and `rails anomonitor:retry_webhooks`
|
|
23
|
+
- Full schema-drift `items_list` in tags (detail page shows all items)
|
|
24
|
+
- Boot config warnings (`ConfigurationValidator`)
|
|
25
|
+
- Jobs browser search (`q`) by job class/name
|
|
26
|
+
- Metrics export: `/anomonitor/metrics.json` and `/anomonitor/metrics.prom`
|
|
27
|
+
- Poll advisory/file lock (`c.poll_lock`, default on) + cooperative thread stop
|
|
28
|
+
- Spike baselines sample more candidates before tag filter
|
|
29
|
+
- Dedupe Solid Queue / status-table metrics (single `queue_depth`)
|
|
30
|
+
- Remove unused `ApplicationJob`
|
|
31
|
+
|
|
32
|
+
## 0.4.1
|
|
33
|
+
|
|
34
|
+
- Pluggable `c.notifier` (callable / `#deliver` / array) + `Anomonitor::Notifiers.payload` for host transports like `Webhook::Broadcast`
|
|
35
|
+
|
|
36
|
+
## 0.4.0
|
|
37
|
+
|
|
38
|
+
- Absolute webhook dashboard URLs via `dashboard_base_url` / `ANOMONITOR_DASHBOARD_BASE_URL`
|
|
39
|
+
- Sticky schema drift: `anomaly.resolved` webhook when drift clears; anomalies UI open/resolved filter
|
|
40
|
+
- Fingerprint full item sets (`items_digest`) so changes beyond the first 25 still re-alert
|
|
41
|
+
- Jobs browser: Delayed Job / table `pending` matches collectors (`run_at <= now`); hide `locked` for status-style tables
|
|
42
|
+
- Dashboard auth hook (`c.authenticate`) + docs / SECURITY note
|
|
43
|
+
- Default install alerts for `failed` and `latency`
|
|
44
|
+
- `schema_drift_interval` (default 15m) so drift polls less often than queue metrics
|
|
45
|
+
- Prune old anomalies with `retention_days` (keep open schema-drift rows)
|
|
46
|
+
- GitHub Actions CI (`rake test` on Ruby 3.1–3.3)
|
|
47
|
+
|
|
48
|
+
## 0.3.2
|
|
49
|
+
|
|
50
|
+
- Schema drift alerts are sticky: notify once, stay silent until the drift clears or the item set changes (no 15‑minute re-alerts)
|
|
51
|
+
- Anomalies gain `resolved_at` for sticky resolution
|
|
52
|
+
- Jobs browser: honor `queue` filter for custom tables and Delayed Job
|
|
53
|
+
|
|
54
|
+
## 0.3.1
|
|
55
|
+
|
|
56
|
+
- Update slack text ```"<#{dashboard}|Open anomaly>" -> "<#{dashboard}>"```
|
|
57
|
+
|
|
58
|
+
## 0.3.0
|
|
59
|
+
|
|
60
|
+
- Jobs dashboard: collector health cards + read-only live job browser for Sidekiq, Delayed Job, Solid Queue, and custom tables
|
|
61
|
+
|
|
62
|
+
## 0.2.3
|
|
63
|
+
|
|
64
|
+
- Wrap long metric/anomaly tag lines so tables stay within the viewport
|
|
65
|
+
|
|
66
|
+
## 0.2.2
|
|
67
|
+
|
|
68
|
+
- Stop auto-appending engine migrations (use `rails anomonitor:install:migrations`) so Apartment hosts can own public-only copies without duplicate migration names
|
|
69
|
+
|
|
70
|
+
## 0.2.1
|
|
71
|
+
|
|
72
|
+
- Support Rails `>= 6.1` (migrations use `ActiveRecord::Migration[6.1]`)
|
|
73
|
+
- Dashboard cron mode UI (mode/schedule/last run from samples)
|
|
74
|
+
|
|
75
|
+
## 0.2.0
|
|
76
|
+
|
|
77
|
+
- Multi-tenancy: `tenants`, `exclude_tenants`, `tenant_switch` (Apartment-aware by default)
|
|
78
|
+
- Per-tenant Delayed Job metrics when tenants are configured
|
|
79
|
+
- Schema drift collector (`missing_tables` / `extra_tables` / `missing_columns` / `extra_columns`)
|
|
80
|
+
- Configurable `schema_drift_exclude` globs
|
|
81
|
+
- Custom tables: `style: :delayed_job` and optional `tenant:` for per-tenant index tables
|
|
82
|
+
- Tenant-aware cooldown keys, spike baselines, and Slack webhook text
|
|
83
|
+
- `poll_mode` `:thread` (default) or `:cron` — cron uses `rails anomonitor:poll`
|
|
84
|
+
|
|
85
|
+
## 0.1.0
|
|
86
|
+
|
|
87
|
+
- Initial release: Sidekiq, Delayed Job, Solid Queue, and custom table collectors
|
|
88
|
+
- Threshold and growth-spike detection with cooldown
|
|
89
|
+
- Webhook notifier
|
|
90
|
+
- Mountable dashboard (overview, anomalies, metrics)
|
data/MIT-LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Copyright flori-s
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
4
|
+
a copy of this software and associated documentation files (the
|
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
9
|
+
the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be
|
|
12
|
+
included in all copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
# Anomonitor
|
|
2
|
+
|
|
3
|
+
Rails engine gem that watches **Sidekiq**, **Delayed Job**, **Solid Queue**, and **custom job tables** for anomalies — threshold breaches, growth spikes, and **schema drift** — then notifies via **webhook** and shows an ops **dashboard**.
|
|
4
|
+
|
|
5
|
+
<img width="1512" height="836" alt="anomonitor" src="https://github.com/user-attachments/assets/62938225-02a7-4e5c-8baf-d8ec24658a79" />
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
# Gemfile
|
|
11
|
+
gem "anomonitor", github: "flori-s/anomonitor"
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Requires **Ruby >= 3.0** and **Rails >= 6.1**.
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
bundle install
|
|
18
|
+
rails generate anomonitor:install
|
|
19
|
+
rails anomonitor:install:migrations
|
|
20
|
+
rails db:migrate
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Mount the engine:
|
|
24
|
+
|
|
25
|
+
```ruby
|
|
26
|
+
# config/routes.rb
|
|
27
|
+
mount Anomonitor::Engine => "/anomonitor"
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Configure
|
|
31
|
+
|
|
32
|
+
`config/initializers/anomonitor.rb`:
|
|
33
|
+
|
|
34
|
+
```ruby
|
|
35
|
+
Anomonitor.configure do |c|
|
|
36
|
+
c.webhook_url = ENV.fetch("ANOMONITOR_WEBHOOK_URL")
|
|
37
|
+
c.dashboard_base_url = ENV["ANOMONITOR_DASHBOARD_BASE_URL"] # e.g. "https://ops.example.com"
|
|
38
|
+
c.poll_interval = 60
|
|
39
|
+
c.cooldown = 15 * 60
|
|
40
|
+
c.retention_days = 7
|
|
41
|
+
c.schema_drift_interval = 15 * 60
|
|
42
|
+
|
|
43
|
+
c.collectors.sidekiq = true
|
|
44
|
+
c.collectors.delayed_job = true
|
|
45
|
+
c.collectors.solid_queue = true
|
|
46
|
+
|
|
47
|
+
c.alert :queue_depth, max: 1_000
|
|
48
|
+
c.alert :failed, max: 50
|
|
49
|
+
c.alert :latency, max: 120
|
|
50
|
+
c.alert :growth_spike, window: 5 * 60, multiplier: 3.0
|
|
51
|
+
end
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Per-queue Sidekiq (tagged `queue_depth` / `latency` points):
|
|
55
|
+
|
|
56
|
+
```ruby
|
|
57
|
+
c.alert :queue_depth, max: 250, match: { queue: true }
|
|
58
|
+
c.alert :latency, max: 60, match: { queue: "mailers" }
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`match` values: `true` = tag present, `nil`/`false` = tag absent, string = exact.
|
|
62
|
+
|
|
63
|
+
### Mutes
|
|
64
|
+
|
|
65
|
+
```ruby
|
|
66
|
+
Anomonitor.config.mute(metric: "extra_tables", tenant: "acme", duration: 24.hours, reason: "migration")
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Or use `/anomonitor/mutes` in the dashboard.
|
|
70
|
+
|
|
71
|
+
### Digests
|
|
72
|
+
|
|
73
|
+
```ruby
|
|
74
|
+
c.digest_interval = 3600 # batch detections into anomaly.digest
|
|
75
|
+
c.notifier_rate_limit = 30 # max delivers per minute
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Dashboard auth
|
|
79
|
+
|
|
80
|
+
The engine is **open by default**. Protect it:
|
|
81
|
+
|
|
82
|
+
```ruby
|
|
83
|
+
Anomonitor.configure do |c|
|
|
84
|
+
c.authenticate = -> {
|
|
85
|
+
authenticate_or_request_with_http_basic("Anomonitor") do |user, pass|
|
|
86
|
+
ActiveSupport::SecurityUtils.secure_compare(user, ENV.fetch("ANOMONITOR_USER")) &&
|
|
87
|
+
ActiveSupport::SecurityUtils.secure_compare(pass, ENV.fetch("ANOMONITOR_PASSWORD"))
|
|
88
|
+
end
|
|
89
|
+
}
|
|
90
|
+
end
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Or reuse host auth:
|
|
94
|
+
|
|
95
|
+
```ruby
|
|
96
|
+
c.authenticate = -> { redirect_to main_app.root_path unless current_user&.admin? }
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
You can also constrain the mount in routes (`authenticate :user, ->(u) { u.admin? } do ... end`).
|
|
100
|
+
|
|
101
|
+
### Multi-tenancy + schema drift
|
|
102
|
+
|
|
103
|
+
For Apartment / schema-per-tenant apps, set `tenants` so Delayed Job is collected **per tenant** and enable schema drift (**PostgreSQL** / `information_schema`):
|
|
104
|
+
|
|
105
|
+
```ruby
|
|
106
|
+
Anomonitor.configure do |c|
|
|
107
|
+
c.tenants = -> { CustomerTenant.pluck(:name) }
|
|
108
|
+
c.exclude_tenants = %w[public]
|
|
109
|
+
# optional — defaults to Apartment::Tenant.switch when Apartment is loaded
|
|
110
|
+
c.tenant_switch = ->(name, &block) { Apartment::Tenant.switch(name, &block) }
|
|
111
|
+
|
|
112
|
+
c.collectors.delayed_job = true
|
|
113
|
+
c.collectors.schema_drift = true
|
|
114
|
+
c.schema_drift_interval = 15 * 60 # heavier than queue polls; default 15m
|
|
115
|
+
c.schema_drift_exclude = %w[
|
|
116
|
+
schema_migrations
|
|
117
|
+
ar_internal_metadata
|
|
118
|
+
anomonitor_*
|
|
119
|
+
a*
|
|
120
|
+
conv_*
|
|
121
|
+
]
|
|
122
|
+
|
|
123
|
+
c.alert :queue_depth, max: 1_000
|
|
124
|
+
c.alert :missing_tables, max: 0
|
|
125
|
+
c.alert :extra_tables, max: 0
|
|
126
|
+
c.alert :missing_columns, max: 0
|
|
127
|
+
c.alert :extra_columns, max: 0
|
|
128
|
+
end
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
`schema_drift_exclude` accepts exact names or `File.fnmatch` globs (`a*` matches all a-prefixed tables; `a_*` only matches `a_…`).
|
|
132
|
+
|
|
133
|
+
Schema drift webhooks are **sticky**: one `anomaly.detected` per tenant/metric/item-set, silence until clear or the set changes, then `anomaly.resolved` when it clears. Queue threshold and growth alerts still use `c.cooldown` (default 15 minutes).
|
|
134
|
+
|
|
135
|
+
### Custom tables
|
|
136
|
+
|
|
137
|
+
Status-based queues:
|
|
138
|
+
|
|
139
|
+
```ruby
|
|
140
|
+
c.table :customer_jobs do |t|
|
|
141
|
+
t.model = "Job"
|
|
142
|
+
t.timestamp = :created_at
|
|
143
|
+
t.status = :status
|
|
144
|
+
t.active = %w[pending running]
|
|
145
|
+
end
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Cross-tenant Delayed Job index (host provides an AR model, e.g. `SuppJob` → `public.supp_jobs`):
|
|
149
|
+
|
|
150
|
+
```ruby
|
|
151
|
+
c.table :supp_jobs do |t|
|
|
152
|
+
t.model = "SuppJob"
|
|
153
|
+
t.style = :delayed_job # failed_at / locked_at / run_at / locked_by
|
|
154
|
+
t.tenant = :tenant # emit per-tenant metrics
|
|
155
|
+
end
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Webhooks
|
|
159
|
+
|
|
160
|
+
Built-in: set `ANOMONITOR_WEBHOOK_URL`. Set `ANOMONITOR_DASHBOARD_BASE_URL` (or `c.dashboard_base_url`) so Slack/JSON links are absolute.
|
|
161
|
+
|
|
162
|
+
**Custom notifier** (e.g. your app’s `Webhook::Broadcast` proxy) — replaces the built-in HTTP client:
|
|
163
|
+
|
|
164
|
+
```ruby
|
|
165
|
+
Anomonitor.configure do |c|
|
|
166
|
+
c.notifier = ->(anomaly, event:) {
|
|
167
|
+
Webhook::Broadcast.new(
|
|
168
|
+
urls: [{ url: ENV.fetch("ANOMONITOR_DEST_URL"), headers: [
|
|
169
|
+
{ name: "Content-Type", value: "application/json" }
|
|
170
|
+
] }],
|
|
171
|
+
message: Anomonitor::Notifiers.payload(anomaly, event: event)
|
|
172
|
+
).call
|
|
173
|
+
}
|
|
174
|
+
end
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Callables may return `true`/`false`, or a Hash with `:accepted` (Broadcast’s return value). Objects with `#deliver(anomaly, event:)` also work; pass an Array to fan out to several notifiers.
|
|
178
|
+
|
|
179
|
+
Slack Incoming Webhooks (`hooks.slack.com`) get a formatted `text` payload when using the built-in notifier. Other built-in URLs receive JSON:
|
|
180
|
+
|
|
181
|
+
```json
|
|
182
|
+
{
|
|
183
|
+
"gem": "anomonitor",
|
|
184
|
+
"event": "anomaly.detected",
|
|
185
|
+
"severity": "high",
|
|
186
|
+
"rule": "growth_spike",
|
|
187
|
+
"source": "delayed_job",
|
|
188
|
+
"metric": "queue_depth",
|
|
189
|
+
"value": 4200,
|
|
190
|
+
"threshold": 1000,
|
|
191
|
+
"sampled_at": "2026-08-07T09:00:00Z",
|
|
192
|
+
"resolved_at": null,
|
|
193
|
+
"dashboard_url": "https://ops.example.com/anomonitor/anomalies/123",
|
|
194
|
+
"tags": { "tenant": "acme" }
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Events: `anomaly.detected` and `anomaly.resolved` (sticky schema drift clear).
|
|
199
|
+
|
|
200
|
+
## Polling: thread vs cron
|
|
201
|
+
|
|
202
|
+
Anomonitor can collect on a background thread **or** via an external scheduler.
|
|
203
|
+
|
|
204
|
+
| `poll_mode` | Behavior |
|
|
205
|
+
|---|---|
|
|
206
|
+
| `:thread` (default) | Starts an in-process poller after boot (`poll_interval` seconds). Avoid with multi-worker Puma/Unicorn unless only one worker should run it. Prefer `:cron` in multi-worker setups. |
|
|
207
|
+
| `:cron` | No background thread. Schedule `rails anomonitor:poll` yourself. |
|
|
208
|
+
|
|
209
|
+
```ruby
|
|
210
|
+
# In-process (default)
|
|
211
|
+
c.poll_mode = :thread
|
|
212
|
+
|
|
213
|
+
# Cron / systemd / Kubernetes CronJob / whenever
|
|
214
|
+
c.poll_mode = :cron
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Cron examples:
|
|
218
|
+
|
|
219
|
+
```cron
|
|
220
|
+
* * * * * cd /app && bin/rails anomonitor:poll
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
```ruby
|
|
224
|
+
# config/schedule.rb (whenever)
|
|
225
|
+
every 1.minute do
|
|
226
|
+
rake "anomonitor:poll"
|
|
227
|
+
end
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
`c.auto_start = false` is still supported and is equivalent to `poll_mode = :cron`.
|
|
231
|
+
|
|
232
|
+
Samples and resolved anomalies older than `retention_days` are pruned (open schema-drift rows are kept until resolved).
|
|
233
|
+
|
|
234
|
+
## Dashboard
|
|
235
|
+
|
|
236
|
+
Open `/anomonitor` for:
|
|
237
|
+
|
|
238
|
+
- Current metric cards and mini history bars
|
|
239
|
+
- **Jobs** — per-collector health plus a read-only live job browser (filter by source, status, tenant, queue, search; expandable detail)
|
|
240
|
+
- Open anomalies (filter open / resolved / all) + resolve/ack, reopen, retry webhook
|
|
241
|
+
- **Mutes** — snooze by metric/rule/source/tenant
|
|
242
|
+
- Overview tenant filter
|
|
243
|
+
- Poller health (last run, collector status)
|
|
244
|
+
|
|
245
|
+
### Metrics export
|
|
246
|
+
|
|
247
|
+
- JSON: `/anomonitor/metrics.json`
|
|
248
|
+
- Prometheus text: `/anomonitor/metrics.prom`
|
|
249
|
+
|
|
250
|
+
### Retry failed webhooks
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
rails anomonitor:retry_webhooks
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
`c.poll_lock = true` (default) skips overlapping ticks via Postgres advisory lock or a tmp file lock. Prefer `poll_mode = :cron` in multi-worker setups.
|
|
257
|
+
|
|
258
|
+
## Manual poll
|
|
259
|
+
|
|
260
|
+
```bash
|
|
261
|
+
rails anomonitor:poll
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
With Apartment, the rake task switches to the first `exclude_tenants` entry (default `public`) before collecting.
|
|
265
|
+
|
|
266
|
+
## Development
|
|
267
|
+
|
|
268
|
+
```bash
|
|
269
|
+
bundle install
|
|
270
|
+
bundle exec rake test
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
## License
|
|
274
|
+
|
|
275
|
+
MIT
|
data/RELEASE.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Releasing Anomonitor
|
|
2
|
+
|
|
3
|
+
## Version bump
|
|
4
|
+
|
|
5
|
+
1. Update `lib/anomonitor/version.rb`
|
|
6
|
+
2. Add a section to `CHANGELOG.md`
|
|
7
|
+
3. Commit and tag: `git tag v0.6.1 && git push origin v0.6.1`
|
|
8
|
+
|
|
9
|
+
## RubyGems
|
|
10
|
+
|
|
11
|
+
Requires owner credentials on rubygems.org for the `anomonitor` gem.
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
gem build anomonitor.gemspec
|
|
15
|
+
gem push anomonitor-0.6.1.gem
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Or:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
bundle exec rake release
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
(if a `release` task is configured via bundler gem tasks)
|
|
25
|
+
|
|
26
|
+
## GitHub-only installs
|
|
27
|
+
|
|
28
|
+
Until published, hosts can use:
|
|
29
|
+
|
|
30
|
+
```ruby
|
|
31
|
+
gem "anomonitor", github: "flori-s/anomonitor", tag: "v0.6.1"
|
|
32
|
+
```
|
data/Rakefile
ADDED
data/SECURITY.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Supported Versions
|
|
4
|
+
|
|
5
|
+
| Version | Supported |
|
|
6
|
+
| ------- | ------------------ |
|
|
7
|
+
| 0.6.x | :white_check_mark: |
|
|
8
|
+
| 0.5.x | :white_check_mark: |
|
|
9
|
+
| 0.4.x | :white_check_mark: |
|
|
10
|
+
| 0.3.x | :white_check_mark: |
|
|
11
|
+
| < 0.3 | :x: |
|
|
12
|
+
|
|
13
|
+
## Dashboard access
|
|
14
|
+
|
|
15
|
+
The Anomonitor engine is **not authenticated by default**. If you mount it on a publicly reachable host, set `c.authenticate` in the initializer (HTTP basic or your app’s admin check). See the README.
|
|
16
|
+
|
|
17
|
+
## Reporting a Vulnerability
|
|
18
|
+
|
|
19
|
+
Please report security vulnerabilities privately. Do **not** open a public issue.
|
|
20
|
+
|
|
21
|
+
- Prefer GitHub Security Advisories for [flori-s/anomonitor](https://github.com/flori-s/anomonitor/security/advisories/new) if available
|
|
22
|
+
- Or email the maintainer via the contact listed in the gemspec / GitHub profile
|
|
23
|
+
|
|
24
|
+
You can expect an initial response within **7 days**. If the report is accepted, we will work on a fix and coordinate disclosure. If declined, we will explain why.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//= link_directory ../stylesheets/anomonitor .css
|