Span operations facade (OTel trace/api.md §Span operations
L449-L705; Status: Stable).
All operations dispatch to the SDK-registered span module and no-op when no SDK is installed (spec L860-L874 "Behavior of the API in the absence of an installed SDK"). Once the span has ended, subsequent mutations SHOULD be silently ignored (spec L652-L653 "the Span becomes non-recording by being ended").
BEAM representation
On BEAM there is no separate mutable Span object — the API
takes a SpanContext as the handle and the SDK resolves it
to the actual span record stored in ETS. get_context/1 is
therefore identity; spec L461 "returned value MUST be the
same for the entire Span lifetime" is satisfied
automatically by the value-semantic SpanContext.
This same identity satisfies spec §"Wrapping a SpanContext in
a Span" (trace/api.md L720-L739) without a dedicated
constructor: a SpanContext received from any source
(propagator extraction, custom protocol bridge, manual
reconstruction) is already a usable Span handle. Hand it to
Otel.API.Trace.set_current_span/1,2, make_current/1, or
any function on this module — the registered SDK module
handles unknown spans as no-ops, satisfying the spec's
non-recording requirement (L731-L734).
Dispatch shape
Unlike Tracer / Meter / Logger — which receive a
{module, config} tuple as their handle and pattern-match on
it — Span's handle is a SpanContext, a pure W3C-defined
data value that does not carry the SDK dispatcher. Instead,
the SDK registers its Span operations module via
set_module/1 and each facade function looks it up through
a private get_module/0 which reads from
:persistent_term.
get_module/0 defaults to Otel.API.Trace.Span.Noop when no
SDK has registered, so every dispatch returns a valid
module pointer and each facade function is a direct call.
This mirrors how TracerProvider.get_tracer/1 returns
{Tracer.Noop, []} when no SDK is installed — a Noop
dispatcher is always available, the absence-of-SDK branch is
absorbed into the Noop module's own no-op implementations.
All functions are safe for concurrent use.
Public API
| Function | Role |
|---|---|
get_context/1 | Application (OTel API MUST) — GetContext (L458-L461) |
recording?/1 | Application (OTel API MUST) — IsRecording (L463-L493) |
set_attribute/3 | Application (OTel API MUST) — SetAttribute (L495-L520) |
set_attributes/2 | Application (OTel API MAY) — SetAttributes (L506-L508) |
add_event/2 | Application (OTel API MUST) — AddEvent (L525-L557) |
add_link/2 | Application (OTel API MUST) — AddLink (L562-L564) |
set_status/2 | Application (OTel API MUST) — SetStatus (L565-L624) |
update_name/2 | Application (OTel API MUST) — UpdateName (L628-L645) |
end_span/2 | Application (OTel API MUST) — End (L647-L682) |
record_exception/4 | Application (OTel API SHOULD) — Record Exception (L684-L704 + exceptions.md L44-L55) |
@callback for each of the above mutations | SDK (OTel API MUST/SHOULD) — SDK dispatch contract |
set_module/1 | SDK (installation hook) — register SDK Span module |
References
- OTel Trace API §Span operations:
opentelemetry-specification/specification/trace/api.mdL449-L705 - OTel Trace API §Behavior in absence of SDK:
opentelemetry-specification/specification/trace/api.mdL860-L874 - OTel Trace Exceptions §Attributes:
opentelemetry-specification/specification/trace/exceptions.mdL44-L55 - Reference impl:
opentelemetry-erlang/apps/opentelemetry_api/src/otel_span.erl
Summary
Types
Options accepted by Otel.API.Trace.Tracer callback
start_span/4 and by facade Otel.API.Trace.start_span/3,4.
Callbacks
SDK (OTel API MUST) — "AddEvent" (trace/api.md
L525-L557).
SDK (OTel API MUST) — "Add Link" (trace/api.md
L562-L564).
SDK (OTel API MUST) — "End" (trace/api.md L647-L682).
SDK (OTel API SHOULD) — "Record Exception" (trace/api.md
L684-L704 + exceptions.md L44-L55).
SDK (OTel API MUST) — "IsRecording" (trace/api.md
L463-L493).
SDK (OTel API MUST) — "SetAttribute" (trace/api.md
L495-L520).
SDK (OTel API MAY) — "SetAttributes" convenience
(trace/api.md L506-L508).
SDK (OTel API MUST) — "SetStatus" (trace/api.md
L565-L624).
SDK (OTel API MUST) — "UpdateName" (trace/api.md
L628-L645).
Functions
Application (OTel API MUST) — "AddEvent" (trace/api.md
L525-L557).
Application (OTel API MUST) — "Add Link" (trace/api.md
L562-L564).
Application (OTel API MUST) — "End" (trace/api.md
L647-L682).
Application (OTel API MUST) — "Get Context" (trace/api.md
L458-L461).
Application (OTel API SHOULD) — "Record Exception"
(trace/api.md L684-L704, specialized AddEvent variant).
Application (OTel API MUST) — "IsRecording" (trace/api.md
L463-L493).
Application (OTel API MUST) — "SetAttribute"
(trace/api.md L495-L520).
Application (OTel API MAY) — "SetAttributes" convenience
(trace/api.md L506-L508).
SDK (installation hook) — register the SDK Span operations module.
Application (OTel API MUST) — "SetStatus" (trace/api.md
L565-L624).
Application (OTel API MUST) — "UpdateName" (trace/api.md
L628-L645).
Types
@type primitive_any() :: primitive() | [primitive_any()] | %{required(String.t()) => primitive_any()}
@type start_opts() :: [ kind: Otel.API.Trace.SpanKind.t(), attributes: %{required(String.t()) => primitive_any()}, links: [Otel.API.Trace.Link.t()], start_time: non_neg_integer(), is_root: boolean() ]
Options accepted by Otel.API.Trace.Tracer callback
start_span/4 and by facade Otel.API.Trace.start_span/3,4.
Keys mirror the parameters required by spec L386-L414 (§Span Creation):
:kind—Otel.API.Trace.SpanKind.t/0. Spec L405-L406.:attributes— initial attributes. Spec L407-L409.:links— initial Links. Spec L410-L412.:start_time— explicit start timestamp (nanoseconds since the Unix epoch). Spec L413-L414.:is_root— boolean indicator that this Span should be a root Span, ignoring whatever current span the resolved Context carries. Spec L390-L391 ("a Span MAY be passed in as a parent or be specified as the root Span") — the "specified as the root Span" mechanism is what:is_rootexpresses. Erlang exposes the same indication implicitly by passing an empty Context; the explicit boolean is an Elixir-level convenience over that SDK input.
Callbacks
@callback add_event( span_ctx :: Otel.API.Trace.SpanContext.t(), event :: Otel.API.Trace.Event.t() ) :: :ok
SDK (OTel API MUST) — "AddEvent" (trace/api.md
L525-L557).
Per spec L547 events SHOULD preserve the order in which they are recorded.
@callback add_link( span_ctx :: Otel.API.Trace.SpanContext.t(), link :: Otel.API.Trace.Link.t() ) :: :ok
SDK (OTel API MUST) — "Add Link" (trace/api.md
L562-L564).
Per spec L563 adding links at span creation (via
Tracer.start_span/4 opts) is preferred — samplers may not
consider links added later.
Per spec L820-L823 SHOULD the SDK implementation SHOULD
record links containing a SpanContext with empty TraceId
or SpanId when either the attribute set or the
TraceState is non-empty. Implementations should not
silently drop such links.
@callback end_span( span_ctx :: Otel.API.Trace.SpanContext.t(), timestamp :: non_neg_integer() ) :: :ok
SDK (OTel API MUST) — "End" (trace/api.md L647-L682).
- L672-L673 if timestamp is omitted upstream, the facade
substitutes
System.system_time(:nanosecond); this callback always receives an explicit integer - L652-L653 implementations SHOULD ignore subsequent calls
to
end_spanand any other Span methods - L677 MUST NOT perform blocking I/O on the calling thread
- L665-L668 ending the span MUST NOT inactivate it in any Context it is active in
@callback record_exception( span_ctx :: Otel.API.Trace.SpanContext.t(), exception :: Exception.t(), stacktrace :: list(), attributes :: %{required(String.t()) => primitive_any()} ) :: :ok
SDK (OTel API SHOULD) — "Record Exception" (trace/api.md
L684-L704 + exceptions.md L44-L55).
A specialized variant of AddEvent (L688). Per L697-L699
attributes take precedence over attributes generated from
the exception object. The emitted event follows
exceptions.md §Attributes L44-L55.
@callback recording?(span_ctx :: Otel.API.Trace.SpanContext.t()) :: boolean()
SDK (OTel API MUST) — "IsRecording" (trace/api.md
L463-L493).
Returns whether the span is currently recording. Per spec
L472-L476 IsRecording is independent of the sampled flag in
TraceFlags; per L478-L481 an ended span SHOULD become
non-recording.
@callback set_attribute( span_ctx :: Otel.API.Trace.SpanContext.t(), key :: String.t(), value :: primitive_any() ) :: :ok
SDK (OTel API MUST) — "SetAttribute" (trace/api.md
L495-L520).
Per spec L513-L514 setting an attribute with the same key as an existing attribute SHOULD overwrite the previous value. Silently ignored if the span is non-recording (L468-L469) or already ended (L652-L653).
@callback set_attributes( span_ctx :: Otel.API.Trace.SpanContext.t(), attributes :: %{required(String.t()) => primitive_any()} ) :: :ok
SDK (OTel API MAY) — "SetAttributes" convenience
(trace/api.md L506-L508).
Sets multiple attributes in a single call. Same overwrite
and recording-state rules as set_attribute/3.
@callback set_status( span_ctx :: Otel.API.Trace.SpanContext.t(), status :: Otel.API.Trace.Status.t() ) :: :ok
SDK (OTel API MUST) — "SetStatus" (trace/api.md
L565-L624).
Status priority per L590: Ok > Error > Unset. The SDK
implementation MUST honour:
- L599
DescriptionMUST be IGNORED for:ok/:unset. A caller that constructed%Status{}directly may still pass a stale description on:ok/:unset; the SDK MUST drop it before recording. - L604 SHOULD — an attempt to set
:unsetSHOULD be ignored (do not overwrite an existing:okor:error). - L619-L620 SHOULD — once the status is
:ok, furtherset_statusattempts SHOULD be ignored.:okis final.
@callback update_name( span_ctx :: Otel.API.Trace.SpanContext.t(), name :: String.t() ) :: :ok
SDK (OTel API MUST) — "UpdateName" (trace/api.md
L628-L645).
Functions
@spec add_event( span_ctx :: Otel.API.Trace.SpanContext.t(), event :: Otel.API.Trace.Event.t() ) :: :ok
Application (OTel API MUST) — "AddEvent" (trace/api.md
L525-L557).
Records an event on the span. The caller constructs the
Event via Otel.API.Trace.Event.new/3 — per spec L543-L544
if no timestamp is supplied the implementation sets it to
the time at which the API is called.
Per spec L547 events SHOULD preserve the order in which they are recorded. This facade dispatches to the SDK-registered module, so ordering is the SDK implementation's responsibility; API users relying on order should confirm their SDK honours it.
@spec add_link( span_ctx :: Otel.API.Trace.SpanContext.t(), link :: Otel.API.Trace.Link.t() ) :: :ok
Application (OTel API MUST) — "Add Link" (trace/api.md
L562-L564).
Adds a link to another span after creation. Per spec L563
adding links at span creation (via Tracer.start_span/4
opts) is preferred — samplers may not consider links added
later.
Per spec L820-L823 SHOULD — "Implementations SHOULD record
links containing SpanContext with empty TraceId or
SpanId (all zeros) as long as either the attribute set or
TraceState is non-empty." SDKs implementing the
add_link/2 callback are responsible for honouring this
recording rule (the API facade is a pure dispatcher).
@spec end_span( span_ctx :: Otel.API.Trace.SpanContext.t(), timestamp :: non_neg_integer() ) :: :ok
Application (OTel API MUST) — "End" (trace/api.md
L647-L682).
Signals that the operation described by the span has ended.
- L672-L673 if
timestampis omitted, the current time is used ("MUST be treated equivalent to passing the current time") — the default arg evaluatesSystem.system_time(:nanosecond)at call time - L652-L653 implementations SHOULD ignore subsequent calls
to
end_spanand any other Span methods — the span becomes non-recording by being ended - L677 this operation MUST NOT perform blocking I/O on the calling thread
- L665-L668 ending the span MUST NOT inactivate it in any Context it is active in — ended spans remain usable as parents
L429-L430: "Any span that is created MUST also be ended. This is the responsibility of the user."
Values are Unix epoch nanoseconds (OTLP
time_unix_nano, a fixed64 unsigned proto3 field).
The typespec is non_neg_integer() enforcing the
unsigned invariant at the API boundary.
@spec get_context(span_ctx :: Otel.API.Trace.SpanContext.t()) :: Otel.API.Trace.SpanContext.t()
Application (OTel API MUST) — "Get Context" (trace/api.md
L458-L461).
Returns the SpanContext associated with this span. On BEAM
the SpanContext is itself the handle, so this is identity;
per spec L461 the returned value MUST be the same for the
entire Span lifetime — satisfied automatically by value
semantics.
@spec record_exception( span_ctx :: Otel.API.Trace.SpanContext.t(), exception :: Exception.t(), stacktrace :: list(), attributes :: %{required(String.t()) => primitive_any()} ) :: :ok
Application (OTel API SHOULD) — "Record Exception"
(trace/api.md L684-L704, specialized AddEvent variant).
Records an exception as an event on the span. Per spec L688
this is a specialized variant of AddEvent; per L697-L699
the method MUST accept an optional parameter for additional
event attributes, which take precedence over attributes
generated from the exception object.
The event follows trace/exceptions.md §Attributes L44-L55:
- event name:
"exception"(MUST) exception.message(SHOULD)exception.stacktrace(SHOULD)exception.type(SHOULD)
@spec recording?(span_ctx :: Otel.API.Trace.SpanContext.t()) :: boolean()
Application (OTel API MUST) — "IsRecording" (trace/api.md
L463-L493).
Returns whether the span is currently recording data. Per
spec L472-L476 IsRecording is independent of the sampled
flag in TraceFlags — a span may be recording locally while
the trace is not sampled for export. Per spec L478-L481 an
ended span SHOULD become non-recording.
Without an SDK installed always returns false via
Otel.API.Trace.Span.Noop.
@spec set_attribute( span_ctx :: Otel.API.Trace.SpanContext.t(), key :: String.t(), value :: primitive_any() ) :: :ok
Application (OTel API MUST) — "SetAttribute"
(trace/api.md L495-L520).
Sets a single attribute on the span. Per spec L513-L514 setting an attribute with the same key as an existing attribute SHOULD overwrite the previous value.
Silently ignored if the span is non-recording (L468-L469) or already ended (L652-L653).
@spec set_attributes( span_ctx :: Otel.API.Trace.SpanContext.t(), attributes :: %{required(String.t()) => primitive_any()} ) :: :ok
Application (OTel API MAY) — "SetAttributes" convenience
(trace/api.md L506-L508).
Sets multiple attributes in a single call. Per spec this is
a MAY convenience over repeated set_attribute/3; same
overwrite and recording-state rules apply.
@spec set_module(module :: module()) :: :ok
SDK (installation hook) — register the SDK Span operations module.
Called by Otel.SDK.Application.start/2 to register the
SDK's Span operations module. The Application-tier operations
in this module dispatch to the registered module; when no SDK
has been installed, get_module/0 falls back to
Otel.API.Trace.Span.Noop.
module must implement the Otel.API.Trace.Span behaviour.
@spec set_status( span_ctx :: Otel.API.Trace.SpanContext.t(), status :: Otel.API.Trace.Status.t() ) :: :ok
Application (OTel API MUST) — "SetStatus" (trace/api.md
L565-L624).
Sets the status of the span. Per spec L590 status values form
the total order Ok > Error > Unset. The MUST/SHOULD rules
(L599 description IGNORE on :ok/:unset, L604 ignore
:unset writes, L619-L620 :ok is final) are recording-time
invariants and belong to the SDK implementation — see the
@callback set_status/2 contract below.
This facade is a pure dispatcher; it forwards the
caller-supplied Status verbatim. Callers building a
Status via Otel.API.Trace.Status.new/2 are protected from
the L599 trap at construction time.
@spec update_name(span_ctx :: Otel.API.Trace.SpanContext.t(), name :: String.t()) :: :ok
Application (OTel API MUST) — "UpdateName" (trace/api.md
L628-L645).
Updates the span name. Per spec L632-L634 any sampling behaviour based on span name is implementation-dependent — samplers can only consider information already present during span creation.