This version is in beta. Some features may change before release.

Health checks

Liveness and readiness probes for umbral. Mounts GET /healthz (liveness) and GET /ready (readiness), with pluggable readiness checks for your dependencies.

Health checks

umbral-health mounts two probe endpoints so an orchestrator (Kubernetes, a load balancer, an uptime monitor) can tell whether your app is alive and whether it is ready to serve traffic:

  • GET /healthzliveness. Always returns 200 as long as the process is answering. Use it to decide whether to restart the container. It never touches a dependency, so a slow database can't trigger a restart loop.
  • GET /readyreadiness. Runs every registered check and returns 200 with a JSON body when they all pass, or 503 (with the failing check's reason in the body) when one fails. Use it to decide whether to route traffic to this instance.
Info

Liveness and readiness are split on purpose: a failed dependency (DB down) should pull the instance out of rotation (readiness 503), not kill the process (liveness stays 200) — restarting won't fix a downstream outage.

Wiring

Code
rust
use umbral::prelude::*;
use umbral_health::HealthPlugin;
 
App::builder()
.plugin(HealthPlugin::default()) // GET /healthz + GET /ready
.build()?;

With no checks registered, /ready just confirms the app is up — the same signal as /healthz, but on the endpoint your orchestrator polls for routing decisions.

Readiness checks

A readiness check is anything that implements HealthCheck: a stable name() plus an async check() that returns Ok(()) when healthy or a HealthError when not. Register one per dependency you want /ready to verify on every call.

Code
rust
use umbral::prelude::*;
use umbral_health::{HealthCheck, HealthError, HealthPlugin};
use std::time::Duration;
 
struct DatabaseCheck;
 
impl HealthCheck for DatabaseCheck {
fn name(&self) -> &'static str {
"database"
}
 
async fn check(&self) -> Result<(), HealthError> {
// Run a cheap query against the dependency; turn any error into a HealthError.
// `SomeModel` is one of your own models.
SomeModel::objects()
.count()
.await
.map(|_| ())
.map_err(|e| HealthError::new(format!("database unreachable: {e}")))
}
}
 
App::builder()
.plugin(
HealthPlugin::default()
.check(DatabaseCheck)
.check_timeout(Duration::from_secs(3)), // per-check cap (default 5s)
)
.build()?;

Each check's name is surfaced in the /ready JSON body, so a 503 tells operators exactly which dependency is down. check_timeout bounds every check so a hung dependency can't make the probe itself hang.

See also

healthlivenessreadinessprobeskubernetesplugin