# `Otel.TelemetrySpanDecorator`
[🔗](https://github.com/yangbancode/otel/blob/main/lib/otel/telemetry_span_decorator.ex#L1)

`@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`):

| Attribute | Source |
|---|---|
| `code.function.name` | `"#{inspect(env.module)}.#{name}"` |
| `code.file.path` | `env.file` |
| `code.line.number` | `env.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:

| Where | Captured | Source |
|---|---|---|
| `start_metadata.__args__` | `%{<arg_name> => <value>}` | function args, source-name keys |
| `stop_metadata.__result__` | function's return value | last 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.

# `event_prefix`

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

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

# `span_opts`

```elixir
@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`.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
