howdy/health

A health check for load balancers, container platforms and uptime monitors. Mount it with howdy.health_check:

howdy.new()
|> howdy.controller(pages())
|> howdy.health_check(health.new())

GET /up then answers 200 with {"status":"ok"} while the app is running. It is answered before routing and before any middleware, so rate limits, guards, CSRF checks and redirects cannot turn it away, and the request logger and tracing leave it out, so a probe every few seconds does not fill the logs.

Add checks for what the app cannot serve without, such as its database. They run at the same time, and any that fails or runs out of time turns the answer into a 503:

health.new()
|> health.check("database", database.health_check(db))

Types

Where the health check answers, and what it checks.

pub opaque type Health

Values

pub fn at(health: Health, path path: String) -> Health

Answer at another path, such as /healthz. The path must start with / and is matched exactly. A route of the app’s own at the same path is never reached by GET or HEAD.

pub fn check(
  health: Health,
  named name: String,
  run run: fn() -> Result(Nil, String),
) -> Health

Add a check. run returns Ok(Nil) when what it checks is usable, or an Error with the reason it is not. The reason is logged, at most once every ten seconds per check, and never sent to the client: the response only says which checks failed, by name.

A check that crashes counts as failed. One still running after the timeout is stopped and counts as failed too, so keep checks quick and cheap, such as a SELECT 1. Names must be unique.

pub fn new() -> Health

A health check at /up with no checks: it answers 200 whenever the app is running and accepting connections.

pub fn timeout(health: Health, milliseconds: Int) -> Health

How long the checks get, all together, in milliseconds. One second by default. Keep it below the prober’s own timeout, such as a Kubernetes probe’s timeoutSeconds, or the prober gives up first.

✨ Search Document