Observability⌗
Structured logs + OpenTelemetry span-name constants. Importing tape.obs has zero side effects
and zero third-party dependencies — adapters install hooks.
from tape.obs import log_json, span, SPAN_DISPATCH_EFFECT
log_json("effect.dispatched", run_id="r-1", tool="wire_money", reactor="outbox")
with span(SPAN_DISPATCH_EFFECT, run_id="r-1") as sp:
... # your code; sp is None if no OTel tracer is installed
Observability — structured-log helpers and OpenTelemetry span-name constants.
Span names (use as the operation name when starting a span — emitter is free
to use OTel SDK, the agnostic tape.obs.span(name, **attrs) helper here, or
nothing at all):
tape.begin_run, tape.resume_run,
tape.record_decision,
tape.begin_effect, tape.complete_effect,
tape.reconcile_effect, tape.dispatch_effect,
tape.compensate, tape.redrive,
tape.await_signal, tape.send_signal
Structured log fields — emit these as JSON when available::
tenant_id, app_name, run_id, invocation_id, session_id, seq,
effect_key, decision_index, reactor, lease_owner
The log_json(...) helper writes one JSON line to stderr with stable
key ordering; the span(...) helper is a no-op context manager when
opentelemetry is not installed.
log_json
⌗
Emit one structured JSON line to stderr, following STRUCTURED_FIELDS.
span
⌗
Open a span if OpenTelemetry is installed, otherwise yield None.
Importing OTel lazily keeps tape usable with no extra deps.
configure_cloud_trace_exporter
⌗
available. Returns True on success, False otherwise (so callers can warn once at startup).