Otel.TelemetrySpanDecorator (otel v0.4.1)

Copy Markdown View Source

@span annotation that auto-wraps a function in :telemetry.span/3. Companion to Otel.TelemetryTracer — the tracer turns telemetry spans into OTel spans, the decorator removes the boilerplate of wrapping function bodies manually.

Usage

defmodule MyApp.Calculator do
  use Otel.TelemetrySpanDecorator

  @span [:my_app, :calculator, :add]
  def add(a, b) do
    a + b
  end
end

Compiles to roughly:

def add(a, b) do
  :telemetry.span(
    [:my_app, :calculator, :add],
    %{
      :"code.function.name" => "MyApp.Calculator.add",
      :"code.file.path" => "/abs/.../calculator.ex",
      :"code.line.number" => 4
    },
    fn -> {a + b, %{}} end
  )
end

The event prefix passed to @span must match an entry in the Otel.TelemetryTracer's events: list — registration is the user's responsibility.

Auto-injected attributes

Always emitted, no opt-out (aligns with the OTel code.* registry — semantic-conventions/registry/attributes/code.md):

AttributeSource
code.function.name"#{inspect(env.module)}.#{name}"
code.file.pathenv.file
code.line.numberenv.line

Optional argument / return capture

Use the keyword form to opt in:

@span event: [:my_app, :calculator, :sub], capture_io: true
def sub(a, b), do: a - b

When enabled:

WhereCapturedSource
start_metadata.__args__%{<arg_name> => <value>}function args, source-name keys
stop_metadata.__result__function's return valuelast expression

Plain vars and default args (x \\ 1) keep their original name; pattern-match args (%{...}, [h | t], etc.) fall back to a positional :arg_<idx> name; underscore-prefixed args (_ignored) keep the leading _. The __args__ / __result__ magic names follow the __name__ convention (mirrors __struct__, __info__) to avoid collision with any user arg literally named args or result.

Privacy note — when capture_io: true, all argument values AND the return value flow into the span attribute set. Avoid on functions whose args / returns carry secrets or PII unless your collector / sampler strips them.

Searchability note__args__ and __result__ are nested kvlist_value AnyValue attributes — spec-correct per opentelemetry-specification/specification/common/README.md L41-54 ("arbitrary deep nesting of values for arrays and maps is allowed") and OTLP common/v1/common.proto L25-51 (kvlist_value is a first-class AnyValue variant). Backend support for indexing inner fields varies — Tempo (LGTM 0.26.0) displays them in the span detail view but does not index them for /api/search?tags= lookup. To make a field tag-searchable on Tempo, set it as a top-level attribute inside the function body via Otel.Trace.Span.set_attribute(Otel.Trace.current_span(), key, value).

Multi-clause functions

Place @span once before the first clause; the decorator wraps all clauses of the same name/arity via defoverridable + super. Pattern-matching dispatch happens inside the wrapped function, so exactly one span is emitted per call regardless of which clause matched.

@span [:my_app, :calculator, :sign]
def sign(0), do: :zero
def sign(_), do: :nonzero

Span shape

  • name: derived from event prefix ([:a, :b]"a.b") — same convention as Otel.TelemetryTracer.
  • kind: always :internal (no override; use Otel.Trace.with_span/4 directly for non-internal kinds).
  • status: :ok on normal return, :error on exception (handled by :telemetry.span/3's :exception event).
  • attributes: code.* always; __args__ / __result__ only when capture_io: true.

Implementation

Built on @on_definition + @before_compile + defoverridable. The @on_definition callback records each def / defp whose preceding @span attribute is set; the @before_compile macro emits one defoverridable + override per recorded name/arity. Multi-clause definitions only need one override because super(...) dispatches into the original clauses.

Summary

Types

An event prefix accepted by @span — same shape as :telemetry.span/3's first argument.

Options accepted in the keyword form of @span.

Types

event_prefix()

@type event_prefix() :: [atom()]

An event prefix accepted by @span — same shape as :telemetry.span/3's first argument.

span_opts()

@type span_opts() :: [event: event_prefix(), capture_io: boolean()]

Options accepted in the keyword form of @span.

  • :event — required event prefix.
  • :capture_io — when true, includes __args__ in start metadata and __result__ in stop metadata. Defaults to false.