philiprehberger-circuit_board
Health check framework with dependency aggregation and Rack endpoint
Requirements
- Ruby >= 3.1
Installation
Add to your Gemfile:
gem "philiprehberger-circuit_board"
Or install directly:
gem install philiprehberger-circuit_board
Usage
require "philiprehberger/circuit_board"
Philiprehberger::CircuitBoard.configure do
check(:database) { ActiveRecord::Base.connection.active? }
check(:redis, timeout: 2) { Redis.current.ping == 'PONG' }
end
status = Philiprehberger::CircuitBoard.check
status.healthy? # => true
status.degraded? # => false
status.to_h # => { status: 'healthy', checks: [...] }
Single Check
result = Philiprehberger::CircuitBoard.check_one(:database)
# => { name: :database, healthy: true, duration: 0.0012 }
Raises Philiprehberger::CircuitBoard::Error if the named check does not exist.
Rack Middleware
# config.ru
require "philiprehberger/circuit_board"
Philiprehberger::CircuitBoard.configure do
check(:database) { DB.connected? }
end
use Philiprehberger::CircuitBoard::Rack::Middleware
run MyApp
Exposes three endpoints:
GET /health- full health check with all dependency resultsGET /health/ready- readiness probe (all checks must pass)GET /health/live- liveness probe (always returns 200)
Critical and Non-Critical Checks
Philiprehberger::CircuitBoard.configure do
check(:database) { ActiveRecord::Base.connection.active? } # critical (default)
check(:cache, critical: false) { Redis.current.ping == 'PONG' } # non-critical
end
status = Philiprehberger::CircuitBoard.check
# If only cache fails: status is "degraded"
# If database fails: status is "unhealthy"
Parallel Execution
Run all checks concurrently for lower latency:
status = Philiprehberger::CircuitBoard.check(parallel: true)
status.healthy? # => true
status.duration # => wall-clock time of the slowest check
status.unhealthy_checks # => []
status.to_json # => '{"status":"healthy","checks":[...]}'
Caching Expensive Checks
Philiprehberger::CircuitBoard.configure do
# Cache successful database probe for 30 seconds
check(:database, cache: 30) { ActiveRecord::Base.connection.active? }
end
# First call hits the DB; subsequent calls within 30s return the cached result.
# Failed checks are never cached — failures re-run on every probe.
Check Timeouts
Philiprehberger::CircuitBoard.configure do
check(:fast_service, timeout: 1) { FastService.ping }
check(:slow_service, timeout: 10) { SlowService.ping }
end
State Change Callback
require "philiprehberger/circuit_board"
Philiprehberger::CircuitBoard.configure do |c|
c.check("database") { ActiveRecord::Base.connection.active? }
c.on_change do |from, to|
puts "Health changed: #{from} -> #{to}"
end
end
API
| Method | Description |
|---|---|
.configure { ... } |
Define health checks using the DSL (check(name, timeout:, critical:, cache:)) |
.check(parallel: false) |
Run all checks and return a Status; parallel: true runs concurrently |
.check_one(name) |
Run a single named check and return its result hash |
.reset! |
Remove all configured checks |
on_change(&block) |
Callback invoked on health status transitions |
Status#healthy? |
Whether all checks passed |
Status#degraded? |
Whether some checks passed but not all |
Status#to_h |
Hash with :status and :checks keys |
Status#results |
Array of individual check results |
Status#unhealthy_checks |
Failed check results |
Status#healthy_checks |
Passed check results |
Status#duration |
Wall-clock duration of slowest check |
Status#to_json |
JSON string of to_h |
Rack::Middleware.new(app) |
Rack middleware for health endpoints |
Development
bundle install
bundle exec rspec
bundle exec rubocop
Support
If you find this project useful: