symfonic.services.observability.otel_spans¶
otel_spans ¶
What one run holds in OTEL, and when it is given back.
Two resources are opened when a run starts and must be unwound when it ends: the root span (a context manager entered by hand, because a run's start and its terminal are two separate awaits on the event stream) and the OTEL carrier token that publishes the service's run identity. They are opened together, released together, and released in a fixed order — so they are one object here rather than two dicts and four helpers on the observer, which drives the event lifecycle and should not also own the resource lifecycle.
Keyed by run, and only correct because of it. A single slot made an
observer's two possible lifetimes mutually exclusive: built per run it leaked
exporters, shared across runs it silently lost run A's root span the moment run
B started. Keying by run_id removes that, but keying alone is not enough,
and the two remaining holes are both silent:
- A reused key is the same single-slot leak narrowed to one key. Overwriting
the entry abandons a root span that was never exited and a token that is
never reset, and the span reaches the exporter only if the collector happens
to throw
GeneratorExitinto it — with the wrong end time. :meth:opentherefore displaces the old entry explicitly and logs it. - Wholesale release is not run cleanup. :meth:
releaseexists so a per-run owner (ObservabilityBridge.aclose) can give back its own run without ending the spans of every other run in flight; :meth:release_allis the process-shutdown call.
Nothing here raises. Telemetry that fails on the way out must not take down the run or the caller's cleanup path.
RunSpanTable ¶
The open root span and carrier token of every run in flight.
Source code in src/symfonic/services/observability/otel_spans.py
open ¶
Start one run's root span and publish its identity.
A run id that is already open is a caller bug — a retried invocation reusing its id, or two sinks built over one scope. It is survivable, so the displaced entry is closed rather than dropped, but it is not hidden.
Source code in src/symfonic/services/observability/otel_spans.py
release ¶
Unwind one run. Unknown ids are a no-op — a terminal may follow a cancellation that already released the run.
Root span first, carrier second: the span's own exit resets the carrier entry it pushed, so unwinding in the other order would reset tokens out of the order they were taken.
Source code in src/symfonic/services/observability/otel_spans.py
release_all ¶
Unwind every run still held. Process shutdown, not run cleanup.