Health checks and reports reference¶
Health reaches the reports from two places: probes attached to a service
(WithStatus, WithLiveness, WithReadiness; see
Services) and standalone
HealthCheck values registered with RegisterHealthCheck. This page is the
field-by-field reference for the standalone checks and for the report values all
of them produce.
HealthCheck fields¶
type HealthCheck struct {
Name string
Check func(ctx context.Context) CheckResult
Timeout time.Duration
Interval time.Duration
Type CheckType
}
| Field | Required | Zero value means | What it controls |
|---|---|---|---|
Name |
yes | — | The name in the report entry, and the key for GetCheckResult. Must be unique among health checks; RegisterHealthCheck returns duplicate health check name: "<name>" otherwise. |
Check |
yes | reports unhealthy: a nil Check is called and panics, recovered as health check panicked: … |
The probe. The ctx it receives already carries Timeout. A panic in it reports the check unhealthy with health check panicked: <value>. |
Timeout |
no | use the default (5s) | Bounds a single run of Check. |
Interval |
no | synchronous: run inline on every report that includes it | Non-zero makes the check asynchronous: a background goroutine polls it and reports read the cached result. |
Type |
no | CheckTypeReadiness |
Which reports the check appears in. |
Registration rules, both enforced by the returned error:
cannot register health check after start: checks must be registered while the controller is still in theNeverStartedstate.duplicate health check name: "<name>": only checked against other health checks. A check may silently share a name with a registered service, in which case the report contains two entries with the samenameand consumers keying on it will collide.
Sync and async checks¶
Sync (Interval == 0) |
Async (Interval > 0) |
|
|---|---|---|
When Check runs |
inline, every time a report including it is built | immediately at Start, then every Interval |
| What reports read | the result of that run | the last cached result |
| Cost of a report | one live probe per request | none |
GetCheckResult before any report |
false |
true once the first run has completed |
| Staleness | not applicable | a cached result older than 3 × Interval is reported ERROR with cached health result is stale |
An async check's first run starts as soon as the controller starts, so the
window in which it has no result is the duration of that first run, not a whole
interval. During that window Readiness() reports it as not-ready with
check has not completed its first run, and Status() and Liveness() treat
it as OK.
Every run, sync or async, is executed in its own goroutine and raced against the
timeout. A Check that ignores its context cannot hang a report or wedge the
poller: at expiry the run records CheckResult{Status: CheckUnhealthy, Message:
"health check timed out"} and the abandoned goroutine is left to finish alone. A
late answer is not accepted; once the run has stopped waiting, the timeout stands.
A cancelled caller is not a timeout. If the controller's context has already
been cancelled the check is not invoked at all and the run records
"health check cancelled: the controller is shutting down". Still CheckUnhealthy,
because a check with no result cannot say a dependency is well, but the message
names the real cause: an operator reading "timed out" during a shutdown looks for
a slow dependency that was never there.
A panic inside Check reports the check unhealthy, with the message
health check panicked: <value>, and reaches WithOnPanic. CheckResult has
no error field, so match on that prefix. A goroutine Check starts is not
covered.
CheckResult¶
| Field | Notes |
|---|---|
Status |
One of CheckHealthy, CheckDegraded, CheckUnhealthy. The zero value is CheckHealthy, so an empty CheckResult{} reports OK. |
Message |
Free text. Surfaces as the error field of the report entry for DEGRADED and ERROR; ignored for OK. |
Timestamp |
Set by the controller, not by you. Whatever your Check puts here is overwritten with the time the run completed, which is the value the staleness bound is measured against. |
CheckStatus in the report¶
CheckStatus |
ServiceStatus.Status |
OverallHealthy |
|---|---|---|
CheckHealthy |
"OK" |
unchanged |
CheckDegraded |
"DEGRADED", with Message in error |
unchanged: degraded still passes every gate, including readiness |
CheckUnhealthy |
"ERROR", with Message in error |
false |
CheckType routing¶
CheckType |
Liveness() |
Readiness() |
Status() |
|---|---|---|---|
CheckTypeReadiness (zero value, so the default) |
✓ | ✓ | |
CheckTypeLiveness |
✓ | ✓ | |
CheckTypeBoth |
✓ | ✓ | ✓ |
HealthReport and ServiceStatus¶
type HealthReport struct {
OverallHealthy bool `json:"overall_healthy"`
State State `json:"state"`
Services []ServiceStatus `json:"services"`
}
type ServiceStatus struct {
Name string `json:"name"`
Status string `json:"status"`
Error string `json:"error,omitempty"`
}
Marshalled, a report looks like this:
{
"overall_healthy": false,
"state": "running",
"services": [
{ "name": "http-api", "status": "OK" },
{ "name": "cache", "status": "DEGRADED", "error": "warming" },
{ "name": "database", "status": "ERROR", "error": "dial tcp: connection refused" }
]
}
stateis the controller's lifecycle state when the report was taken, on all three reports. A reader that only seesoverall_healthycannot tell an unhealthy service from a controller that has begun shutting down, and those want different responses.statusis one of"OK","DEGRADED","ERROR".erroris omitted when empty, and carries the probe's error text or the check result'sMessage.- Service entries come first, in registration order, followed by health-check
entries in Go map order. The order of check entries is not stable between
calls. Match on
name, not on position. overall_healthyisfalseif any entry isERROR, and unaffected byDEGRADED.
What each report actually asserts¶
A report is the aggregate of the probes you supplied. It is not evidence that
a service's goroutine is running. A service with no probe always reports "OK",
including one whose StartFunc has already returned an error, and one
registered after Start that was never started at all. If you need the report to
reflect that a service is alive, give it a probe that checks something real.
Calling a report method after shutdown has completed does not re-run the
checks. The controller context is cancelled, so each sync check records
"ERROR" with health check cancelled: the controller is shutting down without
the consumer's Check being invoked, and each async check serves its last
cached result until that goes stale. Read the reports while the controller is
running; since readiness is gated on the lifecycle state, a report taken later
says little beyond the state itself.
Related¶
- Add health checks: the task-oriented recipe, including wiring a report onto an HTTP endpoint.
- Health, liveness and readiness: why readiness fails closed and liveness does not.
- Defaults and timings: check timeout, interval and staleness bound alongside every other default.