OpentelemetryStatifier

CI Hex.pm Version Hex Downloads Hex Docs License

OpenTelemetry spans and links for chart executions. The Statifier family emits :telemetry events as a chart executes; this package turns them into spans, span events and span links, in the opentelemetry_oban and opentelemetry_ecto mold. It depends only on opentelemetry_api: your host brings the SDK and the exporter.

Why this package

A chart execution moves through states on events that arrive minutes or days apart, often on different processes and nodes, and the family reports each step as plain :telemetry events. Without a bridge, a trace shows the HTTP request or the job that delivered an event and nothing of what the chart did with it, and the events are left for you to pair, nest and correlate by hand. With this package attached, every macrostep (one event taken to rest) is a statifier.macrostep span, what happened inside it lands on that span as span events, and links stitch each macrostep to the previous one and to the parent that invoked it, so an execution reads as a chain of traces you can follow. Span names stay fixed whatever the chart's vocabulary, and nothing unbounded, such as a datamodel value, becomes an attribute unless you ask for it.

Install

Add opentelemetry_statifier to the dependencies in your mix.exs:

def deps do
[
{:opentelemetry_statifier, "~> 0.9.0"}
]
end

Your host keeps its own opentelemetry SDK and exporter configuration; the bridge sends spans through whatever tracer provider is running.

Basic usage

A library loan: a copy of a book is checked out to a patron, renewed once before it falls due, and returned. Attach the bridge once, at application start, after your host has started its OpenTelemetry SDK; then execute the chart exactly as you would without it, since the bridge is out of the calling path:

:ok = OpentelemetryStatifier.setup()
chart_source = """
<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" initial="on_shelf">
<state id="on_shelf">
<transition event="loan.checked_out" target="on_loan"/>
</state>
<state id="on_loan">
<onentry>
<log label="loan" expr="'on loan'"/>
</onentry>
<transition event="loan.renewed" target="on_loan"/>
<transition event="loan.returned" target="returned"/>
<transition event="loan.lost" target="lost"/>
</state>
<final id="returned"/>
<final id="lost"/>
</scxml>
"""
{:ok, machine} = Statifier.compile(chart_source)
{:ok, session} = Statifier.Session.start_link(machine, session_id: "loan_42")
:ok = Statifier.Session.send_event(session, "loan.checked_out")
:ok = Statifier.Session.send_event(session, "loan.renewed")
:ok = Statifier.Session.send_event(session, "loan.returned")
# send_event/2 is a cast; status/1 is a call on the same process, so it
# answers after all three events are taken.
%{status: :done} = Statifier.Session.status(session)

That execution exports four statifier.macrostep spans, one per macrostep: never one per state, and never one for the session.

Span Key attributes Span events Links
start trigger=initialize, outcome=quiescent, configuration=["on_shelf"], macrostep=1 statifier.effect.datamodel_init none
check out trigger=event, event_name=loan.checked_out, configuration=["on_loan"] statifier.effect.log previous macrostep
renew trigger=event, event_name=loan.renewed, configuration=["on_loan"] statifier.effect.log previous macrostep
return trigger=event, event_name=loan.returned, outcome=done, macrostep=4 statifier.halt, statifier.effect.done previous macrostep

Every attribute sits under the statifier. namespace (statifier.session_id is "loan_42" on all four), and the <log> span event carries statifier.source.line and statifier.source.column, so it points at the line of the chart that produced it. Why the state and event names are attributes rather than span names is in Why spans and links are shaped this way. An <invoke>, such as a check for holds on the copy before it goes out, shows up the same way: a statifier.effect.invoke span event on the macrostep that entered the invoking state, and statifier.effect.cancel_invoke on the one that left it; the invoked work itself is yours to span. The first span opens late: the session emits the :initialize macrostep's start after some of its work is done, so read statifier.duration rather than that span's wall time for its real cost. Call OpentelemetryStatifier.teardown/0 to detach the bridge.

Documentation

Compatibility

The package needs Elixir 1.18 or later (elixir: "~> 1.18" in mix.exs). Its runtime dependencies are statifier ~> 2.4, opentelemetry_api ~> 1.4 and telemetry ~> 1.0. The Persistence and Oban bridges take no dependency on statifier_persistence or statifier_oban: each attaches to the event names its own events/0 lists.

Until 1.0, the public surface may change between minor releases: a release may rename modules, callbacks, telemetry events or error vocabulary with no compatibility shim. Every such change is recorded in the changelog under a bold Breaking heading that says what to do about it, and pinning to an exact minor, ~> X.Y.0, is the recommended way to take the package until then.

License

MIT - see LICENSE.