"""Structured JSON-lines logging of every model call (charter §10 — the seed and the full prompt logged with every call; §4 — free eval/replay infrastructure). One JSON object per line: everything needed to replay the call (canon_log + messages + model + seed) against a candidate model. No secrets to redact — the client never sends keys, the canon log carries no PII, and the prompt is exactly what we want on record. """ import json import sys from datetime import datetime, timezone from typing import Callable from . import config def _default_write(line: str) -> None: """Best-effort (charter §13): a good model call must never become a 500 because the log write failed (disk full, bad permissions, a misconfigured CALL_LOG_PATH). Any failure here is swallowed and reported to stderr — never stdout, since stdout may itself be the default sink and must stay clean JSON-lines for downstream tooling. """ try: path = config.call_log_path() if path: with open(path, "a", encoding="utf-8") as handle: handle.write(line + "\n") else: sys.stdout.write(line + "\n") except Exception as exc: # noqa: BLE001 - logging must never break a request print(f"call_log: failed to write log line: {exc}", file=sys.stderr) def record( *, role: str, model: str, options: dict, messages: list[dict], canon_log: dict, ok: bool, latency_ms: int, response: str | None = None, error: str | None = None, write: Callable[[str], None] = _default_write, ) -> dict: rec: dict = { "ts": datetime.now(timezone.utc).isoformat(), "role": role, "model": model, "options": options, "messages": messages, "canon_log": canon_log, "ok": ok, "latency_ms": latency_ms, } if ok: rec["response"] = response else: rec["error"] = error write(json.dumps(rec, ensure_ascii=False)) return rec