Operating Husaria
Metrics & health
Metrics & health #
Husaria splits liveness, readiness, and metadata into separate endpoints so you can wire each into the right place in your observability stack.
Health endpoints #
| Path | Method(s) | Semantics | Use for |
|---|---|---|---|
/live | GET / HEAD | Always returns 200 once the process is reachable. Does not depend on DB or schema. | k8s liveness probe |
/ready, /healthz, /health | GET / HEAD | 200 once the startup manager and the initial GraphQL schema are ready. 503 with status: "warming-up" while schemagen is still building the initial schema. | k8s readiness probe |
/version | GET / HEAD | Build / runtime info. Surfaces `read_only: true | false` so an upstream LB can route around a replica. |
HEAD handlers are registered alongside GET on all probe endpoints — sidecars that do HEAD-only checks (graphql-proxy, some Istio configurations) work out of the box.
Both /healthz and /version payloads carry the read_only flag. A typical LB pattern is to route mutations to whichever instance has read_only:false and reads to whichever is closer / cheaper.
Prometheus metrics #
/metrics is exposed when HUSARIA_METRICS_ENABLE=true (default). The path can be moved with HUSARIA_METRICS_PATH.
Authentication required.
/metricswas relocated to the admin group during a security pass. Prometheus needs anX-Husaria-Admin-Secretheader in its scrape config — previously the endpoint was open on the engine root and leaked goroutine counts, DB pool stats, andpg_stat_statementslabels (admin-issued SQL text) to any pod on the cluster network. Add to your scrape job:scrape_configs: - job_name: husaria static_configs: - targets: ['husaria.husaria.svc.cluster.local:8080'] metrics_path: /metrics authorization: credentials_file: /etc/prometheus/secrets/husaria-admin-secret # OR, if your Prometheus build doesn't honour `credentials` with # a non-Bearer auth scheme, use static headers: # http_config: # headers: # X-Husaria-Admin-Secret: ${HUSARIA_ADMIN_SECRET}
Surfaced series #
husaria_executor_*— query / mutation / aggregate latency + counts, broken down by operation type.husaria_subs_*— subscription cohort + tick metrics:husaria_subs_cohorts_total(counter,kind={live,stream})husaria_subs_cohorts_active(gauge)husaria_subs_subscribers_active(gauge)husaria_subs_ticks_total(counter,outcome={success,error})husaria_subs_emits_total(counter)husaria_subs_tick_duration_seconds(histogram, 5ms–10s)husaria_subs_dropped_subscribers_total(counter,reason={slow,client_error})
husaria_cache_*— response-cache hit / miss / size + per-op latency. Latency samples are keyed by a monotonic counter rather than wall-clock nanoseconds, so the histogram count is accurate even on hot goroutines that record multiple samples in the same nanosecond.- Standard
process_*andgo_*— exposed by the default Prometheus registry.
Label cardinality is bounded by design — no per-query, per-cohort, or per-subscriber labels that would blow up the time-series store. Cohort identity surfaces in structured logs only (first 16 hex chars of the SHA-256 cohort key). Full GraphQL queries and variables are never logged.
Logs #
Log output is JSON in production (ENVIRONMENT=production) and console-formatted in development. Each request emits a single line per surface:
{
"level": "info",
"ts": "...",
"msg": "REQ",
"trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
"status": 200,
"duration_ms": 1.96
}
Variants: REQ 4xx, REQ 5xx, REQ slow. GraphQL paths only emit the post-handler completion line (with operation complexity / depth fields) — the duplicate pre-handler line was removed. At info level the monitoring middleware skips request/response body capture entirely.
The trace_id field correlates with the traceparent header on the response — see Tracing.
What's not there #
There is no built-in distributed-tracing exporter. Husaria propagates trace IDs but does not emit spans of its own — run an OpenTelemetry collector in front of (or beside) Husaria to capture spans at the ingress layer. See Tracing for the full propagation contract and a wiring example.