Operations

This page is for operators running arona-server serve. It covers the health endpoints you probe, the log lines worth grepping, the timeout model applied to upstream backends, how backend failures map to HTTP errors, and the operational gotchas that trip people up. Deployment itself is covered in the deployment guide.

Health matrix

All three health endpoints are unauthenticated and return 200 OK whenever the process is serving — there is no liveness/readiness distinction:

EndpointResponse
/healthz, /readyz200 {"status":"ok","version":<CARGO_PKG_VERSION>,"build_hash":<BUILD_HASH>,"models":<n>,"providers":<n>}
/v1/healththe same detailed body as above
/api/healthplana HealthResponse: status, version (CARGO_PKG_VERSION), kind (Dev), uptime (seconds), network (transport / region / asn), build_hash (BUILD_HASH), engine_version ("0.1.0")

/healthz and /readyz are aliases of the same handler, and /v1/health shares it, so the Kubernetes-style probes and the OpenAI-compatible health route are interchangeable. /api/health adds uptime, network and engine version. Use /readyz for load balancers and supervisors; use /api/health when you need the richer payload.

Logging

The server logs through tracing, filtered with the standard RUST_LOG variable (RUST_LOG=info is the common setting; RUST_LOG=debug reveals probe traffic). Events worth knowing, in rough order of frequency:

Log lineLevelWhat it tells you
chat completions request / chat completions SSE requestinfoOne per chat request, with key_prefix, model, stream and request_id — the simplest per-request audit trail.
request completedinfoLogged by the logging_middleware helper after every non-streaming /v1/chat/completions and /v1/embeddings response: method, path, status, latency_ms, trace_id. (Streaming chat logs chat completions SSE request at start instead.)
usage recorded / usage persistedinfoA usage row was recorded (in-memory, with tokens/cost) and then written to the usage_records table.
external probe: sending / external probe: returneddebugA health probe of an external backend's /v1/models; matched says whether the probe completed within the 2s probe timeout.
billing gate rejected: monthly quota exceeded / billing gate rejected: tier rate limit exceededwarnA /v1/* request refused by the billing gate — the client received 429 plus Retry-After.
rpc billing gate rejected: monthly quota exceededwarnThe RPC-side quota gate for JWT-authenticated methods (whole-user window; JSON-RPC error response).
restored persisted backends / restored backend / restored persisted agent nodesinfoStartup restore: admin-registered backends and agent nodes loaded from the database and made routable again.
Shutdown signal received, draining connections…infoGraceful shutdown began (SIGINT/SIGTERM).

Timeout model

Timeouts are enforced on the upstream client used for external backends (packages/core/src/backends/external.rs):

TimeoutValueApplies to
Connect10sEstablishing the upstream TCP/TLS connection.
Read idle120s per readEvery upstream call; each received byte resets the clock, so a healthy-but-slow stream is never cut.
Non-streaming overall600sNon-streaming chat/embeddings calls — a slow-but-alive upstream cannot hold a request forever.
Streaming (SSE)noneStreaming calls carry no overall deadline; long generations are legal and hang detection relies on the read-idle timeout.
Health probe2sThe /v1/models probe.

Error mapping

Backend failures map to HTTP statuses in the chat/embeddings handlers (packages/core/src/gateway/server.rs):

ConditionHTTPtype / codeMessage
Upstream non-2xx status (UpstreamStatus)502 Bad Gatewayserver_error / bad_gatewayupstream <status>: <detail>
Upstream transport failure (RequestFailed)502 Bad Gatewayserver_error / bad_gatewaythe transport error string
Any other backend error500server_error / backend_errorthe error string
No backend for the model (NoBackend)404invalid_request_error / model_not_foundNo backend available for model: <model>
Invalid API key (Unauthorized)401authentication_error / invalid_api_keyInvalid API key
Rate limit (RateLimited)429rate_limit_error / rate_limit_exceededRate limit exceeded

The design intent: callers can tell "your provider rejected or failed" (502) apart from "the gateway itself is broken" (500). Every error body has the same OpenAI-style shape — {"error":{"message":...,"type":...,"code":...}} (json_error_response). The billing-gate 429s additionally carry a Retry-After header and use quota_error/quota_exceeded (quota) and rate_limit_error/rate_limit_exceeded (tier rate limit) respectively.

Troubleshooting

A newly registered backend stays fail-closed until probed

External backends start in an unknown health state and report "<url> not probed yet". They flip healthy when (a) the health checker's first round runs — immediately at startup, then every 60s — or (b) the fire-and-forget probe launched at registration or restore time succeeds, normally within ~1-2 seconds. Until then, requests routed to the backend fail closed by design.

A 404 on the probe's /models is normal for some backends

The external probe hits GET {base}/v1/models (or {base}/models for base URLs with a path prefix). Some OpenAI-compatible servers implement chat but expose no model listing — the Zhipu GLM coding-plan endpoint is one. A 404 is tolerated: the backend is marked healthy and the admin-configured models list stays authoritative for routing. Only genuinely failed probes (timeout, network error, other non-2xx) mark the backend unhealthy.

SSE streams that produce nothing are not billed

A streaming response is recorded to usage only when the stream produced text or carried terminal usage; a stream that ended with neither is not recorded at all. If you see a request without a matching usage recorded line, check whether the stream actually produced content.

Version reporting

version in the health bodies is CARGO_PKG_VERSION; build_hash is the build-time BUILD_HASH value emitted by packages/core/build.rs. Compare build_hash across nodes to confirm they all run the same artifact.