OpentelemetryStatifier (OpentelemetryStatifier v0.4.0)

Copy Markdown View Source

OpenTelemetry instrumentation for the Statifier family of statechart packages.

Attaches to the public :telemetry events the family emits - starting with statifier's [:statifier, :session, ...] contract (Statifier.Session.Telemetry) - and turns them into OpenTelemetry spans, span events, and span links. The library depends only on opentelemetry_api; hosts bring their own SDK and exporter.

setup/0/setup/1 attach OpentelemetryStatifier.Handler.handle_event/4 to every name Statifier.Session.Telemetry.events/0 returns, one :telemetry.attach/4 call per name under a per-event handler id, so a raise in one event's handling never costs the others (deps/telemetry/src/telemetry.erl:109-116). teardown/0 detaches all of them.

A macrostep :start opens a root span named statifier.macrostep, and the :stop whose span_ref matches closes it, carrying statifier.session_id, statifier.trigger, statifier.outcome, the macrostep's counters, and its resulting statifier.configuration as attributes (OpentelemetryStatifier.Handler, OpentelemetryStatifier.SpanTable). Span start times come from each event's own monotonic_time, so a macrostep span's wall time tracks the statifier.duration measurement closely - with one documented exception. Statifier emits the :initialize macrostep's :start from the session's init/1 and its :stop from the following handle_continue, so that span opens after some of the work it covers, and reads materially shorter than statifier.duration reports. The skew is accepted rather than corrected; see the README and docs/adr/0003-handler-attach-and-span-table-mechanism.md.

Everything that fires between a :start and its :stop - the eleven [:statifier, :session, :effect, _] events, the nine [:statifier, :session, :trace, _] events, :interpret, :unroutable, and :halt - lands as a span event on the session's open macrostep span, attributes mapped uniformly by OpentelemetryStatifier.Attributes (locations flattened to statifier.source.line/.column, the raw effect struct never serialized, datamodel values excluded unless setup/1 received record_datamodel_values: true). Each macrostep span is the root of its own trace, stitched to its neighbors with span links: one to the same session's previous macrostep span, and - on a child session's :initialize macrostep - one to the invoking parent's macrostep span, resolved from the :init event's invoked_by. The bridge does not leak on crashes: :terminate removes the session's rows from the span table (ending a still-open macrostep span with an error status), and because :terminate does not fire on a brutal kill, OpentelemetryStatifier.SpanTable also sweeps on a timer, ending the orphaned spans of sessions whose process no longer exists the same way. With trace: false the core emits no trace-family events at all, so the bridge degrades to macrostep-grained spans with effect-level span events and needs no configuration of its own. The design this package implements lives in statifier-ex: docs/opentelemetry.md (span topology, context propagation, cardinality, failure tolerance) and st-ADR-0062 (packaging and scope).

The family's sibling packages are bridged by their own setup calls - OpentelemetryStatifier.Persistence.setup/1 and OpentelemetryStatifier.Oban.setup/1, each attaching only its own family - the shape opentelemetry_ecto and opentelemetry_oban compose in a host. With Persistence attached, a durable macrostep span nests inside the statifier_persistence.run.step span around it rather than rooting its own trace; with neither attached, nothing here behaves differently (ADR-0004).

A host running a durable stepper of its own asks for that same nesting through OpentelemetryStatifier.Parent: register/2 and unregister/1, or the scoped within/3, declare the span a process's macrostep spans belong inside. It is a declaration and never an ambient read - the bridge still neither inherits nor clobbers the process's OTel context - and a host that never calls it sees no change at all (ADR-0004 decision 4, generalized in that record's 2026-09-02 note).

OpentelemetryStatifier.SpanContext.lookup/2 is the one read the bridge offers back: the open macrostep span for a (session_id, macrostep) pair, as its W3C trace and span ids. It exists so a statifier_ui trace subscriber can be handed it as the :otel_context producer that package's ADR-0013 specifies, and it is only ever a read of the span table - no OTel context is created, entered, or attached.

Summary

Functions

Attaches the bridge with default options. Delegates to setup/1.

Validates opts into a OpentelemetryStatifier.Config.t() and attaches the bridge's handler to every name Statifier.Session.Telemetry.events/0 returns.

Detaches every handler id this package owns. Always returns :ok, even when nothing was attached.

Functions

setup()

@spec setup() :: :ok | {:error, term()}

Attaches the bridge with default options. Delegates to setup/1.

Examples

iex> OpentelemetryStatifier.setup()
:ok

setup(opts)

@spec setup(keyword()) :: :ok | {:error, term()}

Validates opts into a OpentelemetryStatifier.Config.t() and attaches the bridge's handler to every name Statifier.Session.Telemetry.events/0 returns.

Idempotent: any ids this package already owns are detached first, so a second call - with the same or different options - replaces the first attachment rather than returning an "already exists" error.

Returns {:error, reason} from OpentelemetryStatifier.Config.new/1 without attaching anything when opts is invalid.

teardown()

@spec teardown() :: :ok

Detaches every handler id this package owns. Always returns :ok, even when nothing was attached.