fix(observability): wire logging, tracing spans, and FastAPI instrumentation (#64)#81
Merged
Merged
Conversation
…ntation (#64) The OpenTelemetry + structlog story was exported but inert at runtime: the lifespan called configure_tracing but never configure_logging, the span helpers had zero call sites, and FastAPIInstrumentor was a declared dependency that was never imported. A TracerProvider existed but nothing made spans, and structured logging never activated. - Add configure_observability(settings) as the single process-level entry point (logging + tracing); the lifespan calls it instead of configure_tracing alone. - Instrument the app: FastAPIInstrumentor.instrument_app(app) in build_app() (per-app, idempotent across the many test-fixture builds) → a server span per request. - Give the span helpers real call sites: span_for_fetcher wraps each fetcher run (via a thin _run → _run_guarded split that leaves the tested fetch/timeout/ breaker logic untouched), span_for_llm wraps the whole diagnose_call. Tests: configure_observability wires both; build_app instruments the app; an in-memory span exporter proves a span per fetcher and one per LLM call; and the lifespan-wiring test now asserts configure_observability is called (guards the exact gh#64 regression). 505 unit tests pass, mypy --strict + ruff clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #64.
The OpenTelemetry + structlog observability story was exported but inert at runtime. A
TracerProviderexisted but nothing created spans, and structured logging never activated.What was broken
configure_tracing(settings)but neverconfigure_logging→ runtime logging fell back to bare stdlib, structlog never configured.span_for_fetcher/span_for_llmhad zero call sites → no enrichment or LLM spans ever emitted.opentelemetry-instrumentation-fastapiwas a declared dependency, butFastAPIInstrumentorwas never imported → no request spans.What changed
configure_observability(settings)— a single process-level entry point (logging + tracing) insentinel/observability/__init__.py; the lifespan calls it instead ofconfigure_tracingalone, so the "observability is wired" contract has one place to set up and one to test.FastAPIInstrumentor.instrument_app(app)inbuild_app()— per-app (not the global.instrument()) and idempotent across the many test-fixture builds → a server span per request.span_for_fetcherwraps each fetcher run via a thin_run → _run_guardedsplit that leaves the tested fetch/timeout/breaker/coerce logic untouched;span_for_llmwraps the wholediagnose_call(end-to-end LLM latency incl. timeout + retry).Tests
configure_observabilitywires both logging and tracing (and runs for real without error).build_app()instruments the app (_is_instrumented_by_opentelemetry).configure_observabilityis called — guards the exact gh#64 regression.505 unit tests pass, 84.84% coverage; ruff + mypy --strict clean. Senior code-review pass: no Critical/Important findings.
🤖 Generated with Claude Code