rails_health_checks 0.7.0 → 1.0.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/README.md +289 -34
- data/Rakefile +5 -0
- data/lib/generators/rails_health_checks/initializer_generator.rb +17 -0
- data/lib/generators/rails_health_checks/templates/initializer.rb +103 -0
- data/lib/rails_health_checks/check_registry.rb +5 -0
- data/lib/rails_health_checks/checks/redis_check.rb +26 -0
- data/lib/rails_health_checks/checks/smtp_check.rb +46 -0
- data/lib/rails_health_checks/configuration.rb +6 -1
- data/lib/rails_health_checks/version.rb +1 -1
- data/lib/rails_health_checks.rb +2 -0
- metadata +7 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f105b603379473145f5d8340416290d38b09ee3f1aa1d1511b9bf7e7245286ea
|
|
4
|
+
data.tar.gz: 623dada0a331945d0923d0338858ec7b2e3b224378981660f5065965fd96a916
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ea3dc72e7ab0f7f76aa0b6223643e26401d46421302f41b347721d56814d139a03e65c8a84b757ef0445ad6db1d49c3c331db1635dbc76c5454625890282cd8d
|
|
7
|
+
data.tar.gz: dc8fb5b87af3e8b06b7b721d3ece226614d48dc91c023860e066289f8be43ab9bde8f359ed5999511bc609ed9effe3fa2fe9d44d780e52dd657c238fa60587ea
|
data/README.md
CHANGED
|
@@ -13,13 +13,19 @@ A Rails engine providing structured, pluggable health check endpoints for monito
|
|
|
13
13
|
- [Installation](#installation)
|
|
14
14
|
- [Endpoints](#endpoints)
|
|
15
15
|
- [Configuration](#configuration)
|
|
16
|
+
- [Configuration Reference](#configuration-reference)
|
|
16
17
|
- [Authentication](#authentication)
|
|
17
18
|
- [Built-in Checks](#built-in-checks)
|
|
18
19
|
- [Notifications](#notifications)
|
|
19
20
|
- [Prometheus Metrics](#prometheus-metrics)
|
|
21
|
+
- [Result Caching](#result-caching)
|
|
20
22
|
- [Per-Environment Toggling](#per-environment-toggling)
|
|
21
23
|
- [Check Groups](#check-groups)
|
|
22
24
|
- [Custom Checks](#custom-checks)
|
|
25
|
+
- [Check API](#check-api)
|
|
26
|
+
- [Testing Custom Checks](#testing-custom-checks)
|
|
27
|
+
- [Migrating from OkComputer](#migrating-from-okcomputer)
|
|
28
|
+
- [Performance](#performance)
|
|
23
29
|
- [Contributing](#contributing)
|
|
24
30
|
- [License](#license)
|
|
25
31
|
|
|
@@ -56,6 +62,7 @@ mount RailsHealthChecks::Engine => "/health"
|
|
|
56
62
|
| `GET /health` | JSON | Monitoring dashboards, detailed diagnostics |
|
|
57
63
|
| `GET /health/live` | Plain text | Load balancer liveness probes |
|
|
58
64
|
| `GET /health/metrics` | Prometheus text | Prometheus / OpenMetrics scraping |
|
|
65
|
+
| `GET /health/:group` | JSON | Scoped check group (e.g. `/health/workers`) |
|
|
59
66
|
|
|
60
67
|
`/health` and `/health/live` also respond to `HEAD` requests (useful for lightweight load balancer probes).
|
|
61
68
|
|
|
@@ -68,12 +75,13 @@ HTTP status is `200 OK` when all checks pass, `503 Service Unavailable` otherwis
|
|
|
68
75
|
"status": "ok",
|
|
69
76
|
"timestamp": "2026-06-08T20:00:00Z",
|
|
70
77
|
"checks": {
|
|
71
|
-
"database": { "status": "ok", "latency_ms": 4 }
|
|
78
|
+
"database": { "status": "ok", "latency_ms": 4 },
|
|
79
|
+
"cache": { "status": "ok", "latency_ms": 1 }
|
|
72
80
|
}
|
|
73
81
|
}
|
|
74
82
|
```
|
|
75
83
|
|
|
76
|
-
Status values: `ok` | `degraded` | `critical`. Overall status is `critical` if any check is `critical`, `degraded` if any is `degraded
|
|
84
|
+
Status values: `ok` | `degraded` | `critical`. Overall status is `critical` if any check is `critical`, `degraded` if any is `degraded`, `ok` otherwise.
|
|
77
85
|
|
|
78
86
|
[↑ Back to top](#table-of-contents)
|
|
79
87
|
|
|
@@ -81,16 +89,145 @@ Status values: `ok` | `degraded` | `critical`. Overall status is `critical` if a
|
|
|
81
89
|
|
|
82
90
|
## Configuration
|
|
83
91
|
|
|
92
|
+
Run the initializer generator to create `config/initializers/rails_health_checks.rb` with every option documented as a commented example:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
rails generate rails_health_checks:initializer
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
The generated file (shown below with all options) uses the block-style `configure` API. Every setting has a sensible default — uncomment only what you need:
|
|
99
|
+
|
|
84
100
|
```ruby
|
|
85
|
-
#
|
|
101
|
+
# frozen_string_literal: true
|
|
102
|
+
|
|
86
103
|
RailsHealthChecks.configure do |config|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
104
|
+
# Checks to run (default: [:database])
|
|
105
|
+
# Available built-ins: :database, :cache, :redis, :smtp, :sidekiq, :solid_queue,
|
|
106
|
+
# :good_job, :resque, :disk, :memory, :http
|
|
107
|
+
config.checks = [:database]
|
|
108
|
+
|
|
109
|
+
# Global timeout per check in seconds (default: 5)
|
|
110
|
+
config.timeout = 5
|
|
111
|
+
|
|
112
|
+
# Cache check results for N seconds to avoid re-running on every request (default: nil, disabled)
|
|
113
|
+
# config.cache_duration = 10
|
|
114
|
+
|
|
115
|
+
# ---------------------------------------------------------------------------
|
|
116
|
+
# Authentication — all strategies are mutually exclusive; default is public
|
|
117
|
+
# ---------------------------------------------------------------------------
|
|
118
|
+
|
|
119
|
+
# Bearer token: requests must include Authorization: Bearer <token>
|
|
120
|
+
# config.token = ENV["HEALTH_TOKEN"]
|
|
121
|
+
|
|
122
|
+
# IP allowlist: exact IPs or CIDR ranges
|
|
123
|
+
# config.allowed_ips = ["127.0.0.1", "10.0.0.0/8"]
|
|
124
|
+
|
|
125
|
+
# Custom block: return truthy to allow the request
|
|
126
|
+
# config.authenticate { |request| request.headers["X-Internal"] == "true" }
|
|
127
|
+
|
|
128
|
+
# ---------------------------------------------------------------------------
|
|
129
|
+
# Per-environment toggling
|
|
130
|
+
# ---------------------------------------------------------------------------
|
|
131
|
+
# config.disable :disk, in: :test
|
|
132
|
+
# config.disable :memory, in: [:test, :development]
|
|
133
|
+
|
|
134
|
+
# ---------------------------------------------------------------------------
|
|
135
|
+
# Check groups — expose subsets at GET /health/:group
|
|
136
|
+
# ---------------------------------------------------------------------------
|
|
137
|
+
# config.group :system, [:disk, :memory]
|
|
138
|
+
# config.group :workers, [:sidekiq, :good_job]
|
|
139
|
+
|
|
140
|
+
# ---------------------------------------------------------------------------
|
|
141
|
+
# Redis check (requires :redis in config.checks and the redis gem)
|
|
142
|
+
# ---------------------------------------------------------------------------
|
|
143
|
+
# config.redis_url = ENV["REDIS_URL"] # default: redis://localhost:6379/0
|
|
144
|
+
|
|
145
|
+
# ---------------------------------------------------------------------------
|
|
146
|
+
# SMTP check (requires :smtp in config.checks)
|
|
147
|
+
# Reads ActionMailer::Base.smtp_settings automatically if not set here.
|
|
148
|
+
# ---------------------------------------------------------------------------
|
|
149
|
+
# config.smtp_address = "smtp.example.com" # default: ActionMailer config or localhost
|
|
150
|
+
# config.smtp_port = 587 # default: ActionMailer config or 25
|
|
151
|
+
|
|
152
|
+
# ---------------------------------------------------------------------------
|
|
153
|
+
# Disk check (requires :disk in config.checks)
|
|
154
|
+
# ---------------------------------------------------------------------------
|
|
155
|
+
# config.disk_path = "/" # mount point (default: "/")
|
|
156
|
+
# config.disk_warn_threshold = 2 * 1024**3 # bytes free → degraded
|
|
157
|
+
# config.disk_critical_threshold = 512 * 1024**2 # bytes free → critical
|
|
158
|
+
|
|
159
|
+
# ---------------------------------------------------------------------------
|
|
160
|
+
# Memory check (requires :memory in config.checks)
|
|
161
|
+
# ---------------------------------------------------------------------------
|
|
162
|
+
# config.memory_threshold = 512 * 1024**2 # RSS bytes → degraded
|
|
163
|
+
|
|
164
|
+
# ---------------------------------------------------------------------------
|
|
165
|
+
# HTTP check (requires :http in config.checks)
|
|
166
|
+
# ---------------------------------------------------------------------------
|
|
167
|
+
# config.http_url = "https://api.example.com/status"
|
|
168
|
+
# config.http_expected_status = 200 # expected response code (default: 200)
|
|
169
|
+
# config.http_headers = { "Authorization" => "Bearer #{ENV['API_TOKEN']}" }
|
|
170
|
+
|
|
171
|
+
# ---------------------------------------------------------------------------
|
|
172
|
+
# Sidekiq check (requires :sidekiq in config.checks)
|
|
173
|
+
# ---------------------------------------------------------------------------
|
|
174
|
+
# config.sidekiq_queue_size = 1000 # total depth → degraded
|
|
175
|
+
|
|
176
|
+
# ---------------------------------------------------------------------------
|
|
177
|
+
# Solid Queue check (requires :solid_queue in config.checks)
|
|
178
|
+
# ---------------------------------------------------------------------------
|
|
179
|
+
# config.solid_queue_job_count = 500 # pending jobs → degraded
|
|
180
|
+
|
|
181
|
+
# ---------------------------------------------------------------------------
|
|
182
|
+
# GoodJob check (requires :good_job in config.checks)
|
|
183
|
+
# ---------------------------------------------------------------------------
|
|
184
|
+
# config.good_job_latency = 300 # seconds oldest job waiting → degraded
|
|
185
|
+
|
|
186
|
+
# ---------------------------------------------------------------------------
|
|
187
|
+
# Resque check (requires :resque in config.checks)
|
|
188
|
+
# ---------------------------------------------------------------------------
|
|
189
|
+
# config.resque_queue_size = 1000 # total depth → degraded
|
|
190
|
+
|
|
191
|
+
# ---------------------------------------------------------------------------
|
|
192
|
+
# Custom checks
|
|
193
|
+
# ---------------------------------------------------------------------------
|
|
194
|
+
# class MyApiCheck < RailsHealthChecks::Check
|
|
195
|
+
# def call
|
|
196
|
+
# res = Net::HTTP.get_response(URI("https://api.example.com/status"))
|
|
197
|
+
# res.code == "200" ? pass : fail_with("API returned #{res.code}")
|
|
198
|
+
# end
|
|
199
|
+
# end
|
|
200
|
+
#
|
|
201
|
+
# config.register :my_api, MyApiCheck.new
|
|
202
|
+
# config.register :slow_api, MyApiCheck.new, timeout: 10 # per-check timeout override
|
|
90
203
|
end
|
|
91
204
|
```
|
|
92
205
|
|
|
93
|
-
Configuration is validated at boot time. An unknown check name
|
|
206
|
+
Configuration is validated at boot time. An unknown check name, a missing `http_url` for the `:http` check, or a group referencing an undefined check raises `RailsHealthChecks::ConfigurationError` on startup rather than silently failing on the first request.
|
|
207
|
+
|
|
208
|
+
### Configuration Reference
|
|
209
|
+
|
|
210
|
+
| Option | Type | Default | Description |
|
|
211
|
+
|--------|------|---------|-------------|
|
|
212
|
+
| `checks` | `Array` | `[:database]` | Built-in or custom check names to run |
|
|
213
|
+
| `timeout` | `Integer` | `5` | Global per-check timeout in seconds |
|
|
214
|
+
| `cache_duration` | `Integer\|nil` | `nil` | Cache results for N seconds; `nil` disables caching |
|
|
215
|
+
| `token` | `String\|nil` | `nil` | Bearer token for authentication |
|
|
216
|
+
| `allowed_ips` | `Array\|nil` | `nil` | IP allowlist; accepts exact IPs and CIDR ranges |
|
|
217
|
+
| `redis_url` | `String\|nil` | `nil` | Redis URL for `:redis` check; falls back to `REDIS_URL` env var then `redis://localhost:6379/0` |
|
|
218
|
+
| `smtp_address` | `String\|nil` | `nil` | SMTP host for `:smtp` check; falls back to `ActionMailer` config then `localhost` |
|
|
219
|
+
| `smtp_port` | `Integer\|nil` | `nil` | SMTP port for `:smtp` check; falls back to `ActionMailer` config then `25` |
|
|
220
|
+
| `sidekiq_queue_size` | `Integer\|nil` | `nil` | Total Sidekiq queue depth that triggers `degraded` |
|
|
221
|
+
| `solid_queue_job_count` | `Integer\|nil` | `nil` | Pending SolidQueue jobs that trigger `degraded` |
|
|
222
|
+
| `good_job_latency` | `Integer\|nil` | `nil` | Oldest pending GoodJob age (seconds) that triggers `degraded` |
|
|
223
|
+
| `resque_queue_size` | `Integer\|nil` | `nil` | Total Resque queue depth that triggers `degraded` |
|
|
224
|
+
| `disk_path` | `String` | `"/"` | Mount point for `:disk` check |
|
|
225
|
+
| `disk_warn_threshold` | `Integer\|nil` | `nil` | Free bytes below which `:disk` reports `degraded` |
|
|
226
|
+
| `disk_critical_threshold` | `Integer\|nil` | `nil` | Free bytes below which `:disk` reports `critical` |
|
|
227
|
+
| `memory_threshold` | `Integer\|nil` | `nil` | Process RSS bytes above which `:memory` reports `degraded` |
|
|
228
|
+
| `http_url` | `String\|nil` | `nil` | Target URL for `:http` check (**required** when `:http` is active) |
|
|
229
|
+
| `http_expected_status` | `Integer` | `200` | Expected HTTP response code for `:http` check |
|
|
230
|
+
| `http_headers` | `Hash` | `{}` | Request headers sent by `:http` check |
|
|
94
231
|
|
|
95
232
|
[↑ Back to top](#table-of-contents)
|
|
96
233
|
|
|
@@ -134,17 +271,21 @@ The block receives the `ActionDispatch::Request` object and must return a truthy
|
|
|
134
271
|
|
|
135
272
|
## Built-in Checks
|
|
136
273
|
|
|
137
|
-
| Check | Description |
|
|
138
|
-
|
|
139
|
-
| `:database` | ActiveRecord `SELECT 1` against the primary connection
|
|
140
|
-
| `:cache` | `Rails.cache` read/write probe; works with
|
|
141
|
-
| `:
|
|
142
|
-
| `:
|
|
143
|
-
| `:
|
|
144
|
-
| `:
|
|
145
|
-
| `:
|
|
146
|
-
| `:
|
|
147
|
-
| `:
|
|
274
|
+
| Check | Requires | Description |
|
|
275
|
+
|-------|----------|-------------|
|
|
276
|
+
| `:database` | — | ActiveRecord `SELECT 1` against the primary connection |
|
|
277
|
+
| `:cache` | — | `Rails.cache` read/write probe; works with any cache store |
|
|
278
|
+
| `:redis` | `redis` gem | Direct Redis `PING`; `config.redis_url` or `REDIS_URL` env var |
|
|
279
|
+
| `:smtp` | — | SMTP connectivity via `Net::SMTP`; reads `ActionMailer` config automatically |
|
|
280
|
+
| `:sidekiq` | `sidekiq` gem | Sidekiq Redis connectivity; optional `config.sidekiq_queue_size` depth threshold |
|
|
281
|
+
| `:solid_queue` | `solid_queue` gem | SolidQueue DB connectivity; optional `config.solid_queue_job_count` threshold |
|
|
282
|
+
| `:good_job` | `good_job` gem | GoodJob queue latency; optional `config.good_job_latency` (seconds) threshold |
|
|
283
|
+
| `:resque` | `resque` gem | Resque Redis connectivity; optional `config.resque_queue_size` depth threshold |
|
|
284
|
+
| `:disk` | — | Free disk space via `df`; `config.disk_warn_threshold` / `config.disk_critical_threshold` (bytes) |
|
|
285
|
+
| `:memory` | — | Process RSS via `ps`; optional `config.memory_threshold` (bytes) reports `degraded` when exceeded |
|
|
286
|
+
| `:http` | — | HTTP GET to `config.http_url`; `config.http_expected_status` and `config.http_headers` |
|
|
287
|
+
|
|
288
|
+
All checks run in parallel. Each check times out independently using `config.timeout` (default: 5s) or a per-check override set via `config.register`.
|
|
148
289
|
|
|
149
290
|
[↑ Back to top](#table-of-contents)
|
|
150
291
|
|
|
@@ -162,7 +303,14 @@ ActiveSupport::Notifications.subscribe("health_check.rails_health_checks") do |*
|
|
|
162
303
|
end
|
|
163
304
|
```
|
|
164
305
|
|
|
165
|
-
The payload includes
|
|
306
|
+
The payload includes:
|
|
307
|
+
|
|
308
|
+
| Key | Value |
|
|
309
|
+
|-----|-------|
|
|
310
|
+
| `status` | Overall status: `"ok"`, `"degraded"`, or `"critical"` |
|
|
311
|
+
| `checks` | Hash of `{ check_name => { status:, latency_ms:, message: } }` |
|
|
312
|
+
|
|
313
|
+
`duration` on the event covers the entire parallel check run, not individual checks.
|
|
166
314
|
|
|
167
315
|
[↑ Back to top](#table-of-contents)
|
|
168
316
|
|
|
@@ -170,7 +318,7 @@ The payload includes `status` (overall: `ok`/`degraded`/`critical`) and `checks`
|
|
|
170
318
|
|
|
171
319
|
## Prometheus Metrics
|
|
172
320
|
|
|
173
|
-
`GET /health/metrics` returns Prometheus text exposition format (`text/plain; version=0.0.4`). This endpoint always returns HTTP 200
|
|
321
|
+
`GET /health/metrics` returns Prometheus text exposition format (`text/plain; version=0.0.4`). This endpoint always returns HTTP 200 per Prometheus scraping convention — check state is encoded in metric values.
|
|
174
322
|
|
|
175
323
|
```
|
|
176
324
|
# HELP rails_health_check_status Health check status (0=ok, 1=degraded, 2=critical)
|
|
@@ -184,6 +332,24 @@ rails_health_check_latency_ms{check="database"} 4
|
|
|
184
332
|
rails_health_check_latency_ms{check="cache"} 2
|
|
185
333
|
```
|
|
186
334
|
|
|
335
|
+
Latency lines are omitted for checks that do not call `measure { }`.
|
|
336
|
+
|
|
337
|
+
[↑ Back to top](#table-of-contents)
|
|
338
|
+
|
|
339
|
+
---
|
|
340
|
+
|
|
341
|
+
## Result Caching
|
|
342
|
+
|
|
343
|
+
By default every request re-runs all checks. Set `cache_duration` to serve cached results for N seconds, reducing load on the database, Redis, and other dependencies:
|
|
344
|
+
|
|
345
|
+
```ruby
|
|
346
|
+
RailsHealthChecks.configure do |config|
|
|
347
|
+
config.cache_duration = 10 # seconds
|
|
348
|
+
end
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
The cache is keyed per check set — `GET /health` and `GET /health/workers` cache independently. The cache is in-process (not shared across dynos/containers), so each instance maintains its own result window.
|
|
352
|
+
|
|
187
353
|
[↑ Back to top](#table-of-contents)
|
|
188
354
|
|
|
189
355
|
---
|
|
@@ -217,12 +383,12 @@ RailsHealthChecks.configure do |config|
|
|
|
217
383
|
end
|
|
218
384
|
```
|
|
219
385
|
|
|
220
|
-
| Endpoint |
|
|
221
|
-
|
|
222
|
-
| `GET /health/system` |
|
|
223
|
-
| `GET /health/workers` |
|
|
386
|
+
| Endpoint | Runs |
|
|
387
|
+
|----------|------|
|
|
388
|
+
| `GET /health/system` | `:disk`, `:memory` |
|
|
389
|
+
| `GET /health/workers` | `:sidekiq`, `:good_job` |
|
|
224
390
|
|
|
225
|
-
Unknown group names return `404 Not Found`.
|
|
391
|
+
The response shape is identical to `GET /health`. Unknown group names return `404 Not Found`.
|
|
226
392
|
|
|
227
393
|
[↑ Back to top](#table-of-contents)
|
|
228
394
|
|
|
@@ -230,26 +396,115 @@ Unknown group names return `404 Not Found`.
|
|
|
230
396
|
|
|
231
397
|
## Custom Checks
|
|
232
398
|
|
|
233
|
-
|
|
399
|
+
### Authoring
|
|
400
|
+
|
|
401
|
+
Define a class inheriting from `RailsHealthChecks::Check`, implement `call`, and register it:
|
|
234
402
|
|
|
235
403
|
```ruby
|
|
236
|
-
class
|
|
404
|
+
class PaymentGatewayCheck < RailsHealthChecks::Check
|
|
237
405
|
def call
|
|
238
|
-
|
|
239
|
-
|
|
406
|
+
measure do
|
|
407
|
+
response = Net::HTTP.get_response(URI("https://api.stripe.com/v1/charges"))
|
|
408
|
+
case response.code.to_i
|
|
409
|
+
when 200, 401 # 401 = auth error, but gateway is reachable
|
|
410
|
+
pass
|
|
411
|
+
when 429
|
|
412
|
+
warn_with("rate limited (429)")
|
|
413
|
+
else
|
|
414
|
+
fail_with("unexpected status #{response.code}")
|
|
415
|
+
end
|
|
416
|
+
end
|
|
417
|
+
rescue StandardError => e
|
|
418
|
+
fail_with(e.message)
|
|
240
419
|
end
|
|
241
420
|
end
|
|
242
421
|
|
|
243
422
|
RailsHealthChecks.configure do |config|
|
|
244
|
-
config.register :
|
|
423
|
+
config.register :payment_gateway, PaymentGatewayCheck.new
|
|
424
|
+
config.register :slow_gateway, PaymentGatewayCheck.new, timeout: 15
|
|
245
425
|
end
|
|
246
426
|
```
|
|
247
427
|
|
|
248
|
-
`config.register`
|
|
428
|
+
`config.register` appends the check to the active list automatically.
|
|
429
|
+
|
|
430
|
+
### Check API
|
|
431
|
+
|
|
432
|
+
| Method | Status set | Use when |
|
|
433
|
+
|--------|-----------|----------|
|
|
434
|
+
| `pass(message = nil)` | `ok` | Check passed; optional message |
|
|
435
|
+
| `warn_with(message)` | `degraded` | Check is functional but degraded |
|
|
436
|
+
| `fail_with(message)` | `critical` | Check failed; service is impaired |
|
|
437
|
+
| `measure { }` | — | Wraps a block and records `latency_ms` |
|
|
438
|
+
|
|
439
|
+
**State contract:** call exactly one of `pass`, `warn_with`, or `fail_with` per `call` invocation. The check instance is `dup`'d before each run, so instance variables set during one request do not bleed into the next.
|
|
440
|
+
|
|
441
|
+
### Testing Custom Checks
|
|
442
|
+
|
|
443
|
+
Call the check directly in a unit test — no request stack needed:
|
|
249
444
|
|
|
250
445
|
```ruby
|
|
251
|
-
|
|
252
|
-
|
|
446
|
+
RSpec.describe PaymentGatewayCheck do
|
|
447
|
+
subject(:check) { described_class.new }
|
|
448
|
+
|
|
449
|
+
context "when the gateway is reachable" do
|
|
450
|
+
before do
|
|
451
|
+
stub_request(:get, "https://api.stripe.com/v1/charges")
|
|
452
|
+
.to_return(status: 200)
|
|
453
|
+
end
|
|
454
|
+
|
|
455
|
+
it "passes" do
|
|
456
|
+
check.call
|
|
457
|
+
expect(check.status).to eq("ok")
|
|
458
|
+
end
|
|
459
|
+
end
|
|
460
|
+
|
|
461
|
+
context "when the gateway is rate-limited" do
|
|
462
|
+
before do
|
|
463
|
+
stub_request(:get, "https://api.stripe.com/v1/charges")
|
|
464
|
+
.to_return(status: 429)
|
|
465
|
+
end
|
|
466
|
+
|
|
467
|
+
it "warns" do
|
|
468
|
+
check.call
|
|
469
|
+
expect(check.status).to eq("degraded")
|
|
470
|
+
expect(check.message).to include("rate limited")
|
|
471
|
+
end
|
|
472
|
+
end
|
|
473
|
+
end
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
[↑ Back to top](#table-of-contents)
|
|
477
|
+
|
|
478
|
+
---
|
|
479
|
+
|
|
480
|
+
## Migrating from OkComputer
|
|
481
|
+
|
|
482
|
+
See [MIGRATING_FROM_OKCOMPUTER.md](MIGRATING_FROM_OKCOMPUTER.md) for a full mapping of check names, configuration keys, and endpoint differences.
|
|
483
|
+
|
|
484
|
+
Quick reference:
|
|
485
|
+
|
|
486
|
+
| OkComputer | rails_health_checks |
|
|
487
|
+
|------------|---------------------|
|
|
488
|
+
| `OkComputer::ActiveRecordCheck` | `:database` |
|
|
489
|
+
| `OkComputer::CacheCheck` | `:cache` |
|
|
490
|
+
| `OkComputer::RedisCheck` | `:redis` |
|
|
491
|
+
| `OkComputer::SidekiqLatencyCheck` | `:sidekiq` + `config.sidekiq_queue_size` |
|
|
492
|
+
| `OkComputer::HttpCheck` | `:http` + `config.http_url` |
|
|
493
|
+
| `OkComputer::CustomCheck` subclass | Subclass `RailsHealthChecks::Check` |
|
|
494
|
+
| `GET /okcomputer` | `GET /health` |
|
|
495
|
+
| `GET /okcomputer/all` | `GET /health` |
|
|
496
|
+
|
|
497
|
+
[↑ Back to top](#table-of-contents)
|
|
498
|
+
|
|
499
|
+
---
|
|
500
|
+
|
|
501
|
+
## Performance
|
|
502
|
+
|
|
503
|
+
See [BENCHMARKS.md](BENCHMARKS.md) for throughput numbers, parallel execution speedup, and cache effectiveness measurements. To run the suite locally:
|
|
504
|
+
|
|
505
|
+
```bash
|
|
506
|
+
bundle exec rake benchmark
|
|
507
|
+
```
|
|
253
508
|
|
|
254
509
|
[↑ Back to top](#table-of-contents)
|
|
255
510
|
|
|
@@ -269,4 +524,4 @@ config.register :slow_api, MyApiCheck.new, timeout: 10
|
|
|
269
524
|
|
|
270
525
|
## License
|
|
271
526
|
|
|
272
|
-
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
527
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
CHANGED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators"
|
|
4
|
+
|
|
5
|
+
module RailsHealthChecks
|
|
6
|
+
module Generators
|
|
7
|
+
class InitializerGenerator < Rails::Generators::Base
|
|
8
|
+
source_root File.expand_path("templates", __dir__)
|
|
9
|
+
|
|
10
|
+
desc "Creates a RailsHealthChecks initializer with all available configuration options."
|
|
11
|
+
|
|
12
|
+
def copy_initializer
|
|
13
|
+
template "initializer.rb", "config/initializers/rails_health_checks.rb"
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RailsHealthChecks.configure do |config|
|
|
4
|
+
# Checks to run (default: [:database])
|
|
5
|
+
# Available built-ins: :database, :cache, :redis, :smtp, :sidekiq, :solid_queue,
|
|
6
|
+
# :good_job, :resque, :disk, :memory, :http
|
|
7
|
+
config.checks = [:database]
|
|
8
|
+
|
|
9
|
+
# Global timeout per check in seconds (default: 5)
|
|
10
|
+
config.timeout = 5
|
|
11
|
+
|
|
12
|
+
# Cache check results for N seconds to avoid re-running on every request (default: nil, disabled)
|
|
13
|
+
# config.cache_duration = 10
|
|
14
|
+
|
|
15
|
+
# ---------------------------------------------------------------------------
|
|
16
|
+
# Authentication — all strategies are mutually exclusive; default is public
|
|
17
|
+
# ---------------------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
# Bearer token: requests must include Authorization: Bearer <token>
|
|
20
|
+
# config.token = ENV["HEALTH_TOKEN"]
|
|
21
|
+
|
|
22
|
+
# IP allowlist: exact IPs or CIDR ranges
|
|
23
|
+
# config.allowed_ips = ["127.0.0.1", "10.0.0.0/8"]
|
|
24
|
+
|
|
25
|
+
# Custom block: return truthy to allow the request
|
|
26
|
+
# config.authenticate { |request| request.headers["X-Internal"] == "true" }
|
|
27
|
+
|
|
28
|
+
# ---------------------------------------------------------------------------
|
|
29
|
+
# Per-environment toggling
|
|
30
|
+
# ---------------------------------------------------------------------------
|
|
31
|
+
# config.disable :disk, in: :test
|
|
32
|
+
# config.disable :memory, in: [:test, :development]
|
|
33
|
+
|
|
34
|
+
# ---------------------------------------------------------------------------
|
|
35
|
+
# Check groups — expose subsets at GET /health/:group
|
|
36
|
+
# ---------------------------------------------------------------------------
|
|
37
|
+
# config.group :system, [:disk, :memory]
|
|
38
|
+
# config.group :workers, [:sidekiq, :good_job]
|
|
39
|
+
|
|
40
|
+
# ---------------------------------------------------------------------------
|
|
41
|
+
# Redis check (requires :redis in config.checks and the redis gem)
|
|
42
|
+
# ---------------------------------------------------------------------------
|
|
43
|
+
# config.redis_url = ENV["REDIS_URL"] # default: redis://localhost:6379/0
|
|
44
|
+
|
|
45
|
+
# ---------------------------------------------------------------------------
|
|
46
|
+
# SMTP check (requires :smtp in config.checks)
|
|
47
|
+
# Reads ActionMailer::Base.smtp_settings automatically if not set here.
|
|
48
|
+
# ---------------------------------------------------------------------------
|
|
49
|
+
# config.smtp_address = "smtp.example.com" # default: ActionMailer config or localhost
|
|
50
|
+
# config.smtp_port = 587 # default: ActionMailer config or 25
|
|
51
|
+
|
|
52
|
+
# ---------------------------------------------------------------------------
|
|
53
|
+
# Disk check (requires :disk in config.checks)
|
|
54
|
+
# ---------------------------------------------------------------------------
|
|
55
|
+
# config.disk_path = "/" # mount point (default: "/")
|
|
56
|
+
# config.disk_warn_threshold = 2 * 1024**3 # bytes free → degraded
|
|
57
|
+
# config.disk_critical_threshold = 512 * 1024**2 # bytes free → critical
|
|
58
|
+
|
|
59
|
+
# ---------------------------------------------------------------------------
|
|
60
|
+
# Memory check (requires :memory in config.checks)
|
|
61
|
+
# ---------------------------------------------------------------------------
|
|
62
|
+
# config.memory_threshold = 512 * 1024**2 # RSS bytes → degraded
|
|
63
|
+
|
|
64
|
+
# ---------------------------------------------------------------------------
|
|
65
|
+
# HTTP check (requires :http in config.checks)
|
|
66
|
+
# ---------------------------------------------------------------------------
|
|
67
|
+
# config.http_url = "https://api.example.com/status"
|
|
68
|
+
# config.http_expected_status = 200 # expected response code (default: 200)
|
|
69
|
+
# config.http_headers = { "Authorization" => "Bearer #{ENV['API_TOKEN']}" }
|
|
70
|
+
|
|
71
|
+
# ---------------------------------------------------------------------------
|
|
72
|
+
# Sidekiq check (requires :sidekiq in config.checks)
|
|
73
|
+
# ---------------------------------------------------------------------------
|
|
74
|
+
# config.sidekiq_queue_size = 1000 # total depth → degraded
|
|
75
|
+
|
|
76
|
+
# ---------------------------------------------------------------------------
|
|
77
|
+
# Solid Queue check (requires :solid_queue in config.checks)
|
|
78
|
+
# ---------------------------------------------------------------------------
|
|
79
|
+
# config.solid_queue_job_count = 500 # pending jobs → degraded
|
|
80
|
+
|
|
81
|
+
# ---------------------------------------------------------------------------
|
|
82
|
+
# GoodJob check (requires :good_job in config.checks)
|
|
83
|
+
# ---------------------------------------------------------------------------
|
|
84
|
+
# config.good_job_latency = 300 # seconds oldest job waiting → degraded
|
|
85
|
+
|
|
86
|
+
# ---------------------------------------------------------------------------
|
|
87
|
+
# Resque check (requires :resque in config.checks)
|
|
88
|
+
# ---------------------------------------------------------------------------
|
|
89
|
+
# config.resque_queue_size = 1000 # total depth → degraded
|
|
90
|
+
|
|
91
|
+
# ---------------------------------------------------------------------------
|
|
92
|
+
# Custom checks
|
|
93
|
+
# ---------------------------------------------------------------------------
|
|
94
|
+
# class MyApiCheck < RailsHealthChecks::Check
|
|
95
|
+
# def call
|
|
96
|
+
# res = Net::HTTP.get_response(URI("https://api.example.com/status"))
|
|
97
|
+
# res.code == "200" ? pass : fail_with("API returned #{res.code}")
|
|
98
|
+
# end
|
|
99
|
+
# end
|
|
100
|
+
#
|
|
101
|
+
# config.register :my_api, MyApiCheck.new
|
|
102
|
+
# config.register :slow_api, MyApiCheck.new, timeout: 10 # per-check timeout override
|
|
103
|
+
end
|
|
@@ -8,6 +8,11 @@ module RailsHealthChecks
|
|
|
8
8
|
BUILT_INS = {
|
|
9
9
|
database: -> { Checks::DatabaseCheck.new },
|
|
10
10
|
cache: -> { Checks::CacheCheck.new },
|
|
11
|
+
redis: -> { Checks::RedisCheck.new(url: RailsHealthChecks.configuration.redis_url) },
|
|
12
|
+
smtp: -> { Checks::SmtpCheck.new(
|
|
13
|
+
address: RailsHealthChecks.configuration.smtp_address,
|
|
14
|
+
port: RailsHealthChecks.configuration.smtp_port
|
|
15
|
+
) },
|
|
11
16
|
sidekiq: -> { Checks::SidekiqCheck.new(queue_size: RailsHealthChecks.configuration.sidekiq_queue_size) },
|
|
12
17
|
solid_queue: -> { Checks::SolidQueueCheck.new(job_count: RailsHealthChecks.configuration.solid_queue_job_count) },
|
|
13
18
|
good_job: -> { Checks::GoodJobCheck.new(latency: RailsHealthChecks.configuration.good_job_latency) },
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsHealthChecks
|
|
4
|
+
module Checks
|
|
5
|
+
class RedisCheck < Check
|
|
6
|
+
def initialize(url: nil)
|
|
7
|
+
unless defined?(::Redis)
|
|
8
|
+
raise LoadError, "Redis is not installed. Add `gem 'redis'` to your Gemfile to use the :redis check."
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
@url = url
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def call
|
|
15
|
+
measure do
|
|
16
|
+
client = ::Redis.new(url: @url || ENV.fetch("REDIS_URL", "redis://localhost:6379/0"))
|
|
17
|
+
client.ping
|
|
18
|
+
client.close
|
|
19
|
+
end
|
|
20
|
+
pass
|
|
21
|
+
rescue StandardError => e
|
|
22
|
+
fail_with(e.message)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "net/smtp"
|
|
4
|
+
|
|
5
|
+
module RailsHealthChecks
|
|
6
|
+
module Checks
|
|
7
|
+
class SmtpCheck < Check
|
|
8
|
+
def initialize(address: nil, port: nil)
|
|
9
|
+
@address = address
|
|
10
|
+
@port = port
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def call
|
|
14
|
+
measure do
|
|
15
|
+
smtp = Net::SMTP.new(resolved_address, resolved_port)
|
|
16
|
+
smtp.start { }
|
|
17
|
+
end
|
|
18
|
+
pass
|
|
19
|
+
rescue StandardError => e
|
|
20
|
+
fail_with(e.message)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def resolved_address
|
|
26
|
+
@address ||
|
|
27
|
+
actionmailer_setting(:address) ||
|
|
28
|
+
"localhost"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def resolved_port
|
|
32
|
+
@port ||
|
|
33
|
+
actionmailer_setting(:port) ||
|
|
34
|
+
25
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def actionmailer_setting(key)
|
|
38
|
+
return unless defined?(::ActionMailer::Base)
|
|
39
|
+
|
|
40
|
+
::ActionMailer::Base.smtp_settings[key]
|
|
41
|
+
rescue StandardError
|
|
42
|
+
nil
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -4,10 +4,12 @@ module RailsHealthChecks
|
|
|
4
4
|
class ConfigurationError < StandardError; end
|
|
5
5
|
|
|
6
6
|
class Configuration
|
|
7
|
-
BUILT_IN_NAMES = %i[database cache sidekiq solid_queue good_job resque disk memory http].freeze
|
|
7
|
+
BUILT_IN_NAMES = %i[database cache redis sidekiq solid_queue good_job resque disk memory http smtp].freeze
|
|
8
8
|
|
|
9
9
|
attr_writer :checks
|
|
10
10
|
attr_accessor :timeout, :cache_duration, :allowed_ips, :token,
|
|
11
|
+
:redis_url,
|
|
12
|
+
:smtp_address, :smtp_port,
|
|
11
13
|
:sidekiq_queue_size, :solid_queue_job_count, :good_job_latency,
|
|
12
14
|
:resque_queue_size, :disk_warn_threshold, :disk_critical_threshold, :disk_path,
|
|
13
15
|
:memory_threshold, :http_url, :http_expected_status, :http_headers
|
|
@@ -20,6 +22,9 @@ module RailsHealthChecks
|
|
|
20
22
|
@allowed_ips = nil
|
|
21
23
|
@token = nil
|
|
22
24
|
@authenticate_block = nil
|
|
25
|
+
@redis_url = nil
|
|
26
|
+
@smtp_address = nil
|
|
27
|
+
@smtp_port = nil
|
|
23
28
|
@sidekiq_queue_size = nil
|
|
24
29
|
@solid_queue_job_count = nil
|
|
25
30
|
@good_job_latency = nil
|
data/lib/rails_health_checks.rb
CHANGED
|
@@ -7,6 +7,8 @@ require "rails_health_checks/authentication"
|
|
|
7
7
|
require "rails_health_checks/check"
|
|
8
8
|
require "rails_health_checks/checks/database_check"
|
|
9
9
|
require "rails_health_checks/checks/cache_check"
|
|
10
|
+
require "rails_health_checks/checks/redis_check"
|
|
11
|
+
require "rails_health_checks/checks/smtp_check"
|
|
10
12
|
require "rails_health_checks/checks/sidekiq_check"
|
|
11
13
|
require "rails_health_checks/checks/solid_queue_check"
|
|
12
14
|
require "rails_health_checks/checks/good_job_check"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rails_health_checks
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 1.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Chuck Smith
|
|
@@ -15,14 +15,14 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - ">="
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: 8.
|
|
18
|
+
version: '8.0'
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - ">="
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: 8.
|
|
25
|
+
version: '8.0'
|
|
26
26
|
description: A Rails engine providing configurable health check endpoints for monitoring
|
|
27
27
|
application status.
|
|
28
28
|
email:
|
|
@@ -43,6 +43,8 @@ files:
|
|
|
43
43
|
- app/mailers/rails_health_checks/application_mailer.rb
|
|
44
44
|
- app/models/rails_health_checks/application_record.rb
|
|
45
45
|
- config/routes.rb
|
|
46
|
+
- lib/generators/rails_health_checks/initializer_generator.rb
|
|
47
|
+
- lib/generators/rails_health_checks/templates/initializer.rb
|
|
46
48
|
- lib/rails_health_checks.rb
|
|
47
49
|
- lib/rails_health_checks/authentication.rb
|
|
48
50
|
- lib/rails_health_checks/check.rb
|
|
@@ -53,8 +55,10 @@ files:
|
|
|
53
55
|
- lib/rails_health_checks/checks/good_job_check.rb
|
|
54
56
|
- lib/rails_health_checks/checks/http_check.rb
|
|
55
57
|
- lib/rails_health_checks/checks/memory_check.rb
|
|
58
|
+
- lib/rails_health_checks/checks/redis_check.rb
|
|
56
59
|
- lib/rails_health_checks/checks/resque_check.rb
|
|
57
60
|
- lib/rails_health_checks/checks/sidekiq_check.rb
|
|
61
|
+
- lib/rails_health_checks/checks/smtp_check.rb
|
|
58
62
|
- lib/rails_health_checks/checks/solid_queue_check.rb
|
|
59
63
|
- lib/rails_health_checks/configuration.rb
|
|
60
64
|
- lib/rails_health_checks/engine.rb
|