Observability¶
- Logs are JSON lines on stdout (
LOG_FORMAT=json), one per request, withrequest_id,method,path,statusandms. SetLOG_FORMAT=textfor a readable local view. - Request IDs: an incoming
X-Request-IDis honoured, otherwise one is generated. It is echoed in the response and attached to every log line written while handling the request. - Health:
GET /healthzsays the process is up.GET /readyzreports whether configuration loaded, which Model API mode is active (live,fixture,record, orfixture (key failed)when the startup probe found the key not answering; behind a gate with a key,live for the code, recorded otherwise), whether the data directory is writable, and the spend so far against the cap.
Model API calls¶
Every call writes one log line from app.model_api with op, model, attempts, ms,
input_tokens, output_tokens, usd and the running total_usd. Retries log a warning with
the delay and the error. Reaching the cap logs a warning and GET /readyz turns paused on.
The demo gate¶
A refusal by the gate logs the path and the reason under app.gate: limit: requests per
minute, limit: visitor calls per hour, limit: daily calls, or paused: a call that spends
was refused, with the Retry-After given. The code and the tokens never appear in a line.
/readyz carries the gate's state under demo: gated, live_models (whether a key is
there for the code's door), paused, daily_used, daily_cap, visitor_cap_per_hour.
Spend ledger¶
DATA_DIR/model_api_spend.jsonl holds one JSON line per call (timestamp, request ID, operation,
model, endpoint, usage, price, fixture flag, attempts, duration). The file survives restarts, so
the cap is cumulative for the deployment, not per start: the backend reads it once at startup
and keeps the running total in memory from there, which is why production runs one backend
process (see Deployment). GET /api/spend summarises it per model. Delete the
file to reset the total.