[proxy 02/13] obs.py: structlog JSON logging + prometheus registry #6

Closed
opened 2026-06-26 09:48:17 +00:00 by coilysiren · 4 comments
Owner

Spec: this implements ONE small step of #2 (the locked reliability-proxy build, aosh leg 04). Do not redesign. Stay tightly scoped to this single step.

Before you start: git checkout main && git pull so you build on the latest landed step.
Fill in app/obs.py (the observability layer - wire it before any logic).

  • Configure structlog to emit JSON to stdout, level from settings.log_level. Expose get_logger(name).
  • Create a prometheus CollectorRegistry (or use the default) and define the counters/gauges the later steps need so they import cleanly: llm_queue_depth (Gauge), llm_retries_total (Counter), llm_fallbacks_total (Counter), llm_circuit_state (Gauge, labelled by backend), llm_truncation_avoided_total (Counter). Export them.
  • Add init_sentry() that calls sentry_sdk.init(dsn=settings.sentry_dsn) only if the DSN is non-empty.
  • Add metrics_text() returning prometheus_client.generate_latest() bytes for the /metrics route.

Acceptance: uv run python -c "from app.obs import get_logger; get_logger('t').info('hello', k=1)" prints one JSON line containing "hello".

When done:

  1. pre-commit run --all-files (or the repo's gate) must pass, commit your work, and push to canonical main (this is an ephemeral feature container - finish the merge, do not leave it for a human).
  2. Update the matching line in docs/FEATURES.md from planned to landed for this feature, in the same push.
  3. Dispatch the next step: ward agent engineer coilyco-flight-deck/agent-proxy#7 --driver goose
**Spec:** this implements ONE small step of #2 (the locked reliability-proxy build, aosh leg 04). **Do not redesign. Stay tightly scoped to this single step.** **Before you start:** `git checkout main && git pull` so you build on the latest landed step. Fill in `app/obs.py` (the observability layer - wire it before any logic). - Configure structlog to emit JSON to stdout, level from `settings.log_level`. Expose `get_logger(name)`. - Create a prometheus `CollectorRegistry` (or use the default) and define the counters/gauges the later steps need so they import cleanly: `llm_queue_depth` (Gauge), `llm_retries_total` (Counter), `llm_fallbacks_total` (Counter), `llm_circuit_state` (Gauge, labelled by backend), `llm_truncation_avoided_total` (Counter). Export them. - Add `init_sentry()` that calls `sentry_sdk.init(dsn=settings.sentry_dsn)` only if the DSN is non-empty. - Add `metrics_text()` returning `prometheus_client.generate_latest()` bytes for the /metrics route. **Acceptance:** `uv run python -c "from app.obs import get_logger; get_logger('t').info('hello', k=1)"` prints one JSON line containing "hello". **When done:** 1. `pre-commit run --all-files` (or the repo's gate) must pass, commit your work, and push to canonical `main` (this is an ephemeral feature container - finish the merge, do not leave it for a human). 2. Update the matching line in `docs/FEATURES.md` from `planned` to `landed` for this feature, in the same push. 3. Dispatch the next step: `ward agent engineer coilyco-flight-deck/agent-proxy#7 --driver goose`
Author
Owner

🔒 Reserved by ward agent --driver goose — container engineer-goose-agent-proxy-6 on host 700fdb96cd95 is carrying this issue (reserved 2026-06-26T09:54:24Z). Concurrent ward agent runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); --force overrides.

— Goose, via ward agent

<!-- ward-agent-reservation --> 🔒 Reserved by `ward agent --driver goose` — container `engineer-goose-agent-proxy-6` on host `700fdb96cd95` is carrying this issue (reserved 2026-06-26T09:54:24Z). Concurrent `ward agent` runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); `--force` overrides. <!-- ward-agent-signature --> — Goose, via `ward agent`
Member

🔒 Reserved by ward agent --driver claude — container engineer-claude-agent-proxy-6 on host kais-macbook-pro-2.local is carrying this issue (reserved 2026-07-02T16:43:19Z). Concurrent ward agent runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); --force overrides.

— Claude (she/her), via ward agent

<!-- ward-agent-reservation --> 🔒 Reserved by `ward agent --driver claude` — container `engineer-claude-agent-proxy-6` on host `kais-macbook-pro-2.local` is carrying this issue (reserved 2026-07-02T16:43:19Z). Concurrent `ward agent` runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); `--force` overrides. <!-- ward-agent-signature --> — Claude (she/her), via `ward agent`
Author
Owner

WARD-OUTCOME: done - obs.py now exports get_logger/init_sentry/metrics_text; /metrics routes through metrics_text; landed on main.

Honest note: the scoped file was already far more built out than the issue implied. structlog, the full prometheus registry, OTel, and Sentry wiring were all in place from later steps that landed first, so the "fill in obs.py" framing didn't match reality. What was genuinely missing was the exact public surface the spec names: get_logger, init_sentry, and metrics_text. The acceptance test (from app.obs import get_logger) would have failed as-is, so those three thin wrappers were the real work. Added tests for all three and pointed the /metrics route at metrics_text so there's one source of truth.

Confidence is high on the code: the acceptance one-liner prints the expected JSON, and the suite was green (40 passed) before I integrated origin/main. One rough edge worth flagging: the container hit a host-level disk-full wall (/dev/vda1 at 100%), which forced me to delete the venv to get git headroom and then blocked a final re-sync/test on the merged tree. The merge only pulled in an independent test_upstream.py change (issue #9), so I'm confident it's still green, but I couldn't re-run pytest to prove it in-container. If the disk pressure is recurring across these feature containers, it's probably worth a follow-up.

Next step #7 was already reserved remotely by @coilyco-ops, so I left the dispatch alone rather than --force over a live run.

WARD-OUTCOME: done - obs.py now exports get_logger/init_sentry/metrics_text; /metrics routes through metrics_text; landed on main. Honest note: the scoped file was already far more built out than the issue implied. structlog, the full prometheus registry, OTel, and Sentry wiring were all in place from later steps that landed first, so the "fill in obs.py" framing didn't match reality. What was genuinely missing was the exact public surface the spec names: get_logger, init_sentry, and metrics_text. The acceptance test (from app.obs import get_logger) would have failed as-is, so those three thin wrappers were the real work. Added tests for all three and pointed the /metrics route at metrics_text so there's one source of truth. Confidence is high on the code: the acceptance one-liner prints the expected JSON, and the suite was green (40 passed) before I integrated origin/main. One rough edge worth flagging: the container hit a host-level disk-full wall (/dev/vda1 at 100%), which forced me to delete the venv to get git headroom and then blocked a final re-sync/test on the merged tree. The merge only pulled in an independent test_upstream.py change (issue #9), so I'm confident it's still green, but I couldn't re-run pytest to prove it in-container. If the disk pressure is recurring across these feature containers, it's probably worth a follow-up. Next step #7 was already reserved remotely by @coilyco-ops, so I left the dispatch alone rather than --force over a live run.
Member

🪿 Summoned by @coilysiren. Goose ran an advisory pass (nothing was pushed):

coilyco-ops could not run: the goose CLI is not on this host's PATH.

🪿 Summoned by @coilysiren. Goose ran an advisory pass (nothing was pushed): _coilyco-ops could not run: the `goose` CLI is not on this host's PATH._ <!-- coilyco-ops-summon -->
Sign in to join this conversation.
No description provided.