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

Bridges BEAM `:telemetry.span/3` events into the OTel Trace
pipeline. Trace pillar's analog of `Otel.LoggerHandler` (Logs)
and `Otel.TelemetryReporter` (Metrics).

Add to your supervision tree with the event prefixes that
should be promoted to OTel spans:

    defmodule MyApp.Application do
      use Application

      @impl true
      def start(_type, _args) do
        children = [
          {Otel.TelemetryTracer, events: [
            [:my_app, :checkout],
            [:phoenix, :endpoint],
            [:my_app, :repo, :query]
          ]}
        ]

        Supervisor.start_link(children, strategy: :one_for_one, name: MyApp.Supervisor)
      end
    end

Each entry in `events:` is the **prefix** (matches the first
argument of `:telemetry.span/3`); the bridge subscribes to the
three lifecycle events derived from it
(`prefix ++ [:start | :stop | :exception]`).

## What lands in Tempo

| OTel field | Source |
|---|---|
| `name` | event prefix joined by `.` — e.g. `[:my_app, :checkout]` → `"my_app.checkout"` |
| `parent_span_id` | implicit from the calling process's current OTel context (works for nested `:telemetry.span/3` and mixed `with_span/4`) |
| `attributes` | `start_metadata` ∪ `stop_metadata`, minus `:telemetry`-internal keys (see "Reserved metadata keys" below). All keys coerced to `String.t()` |
| `status` | `:ok` on `:stop`; `:error` on `:exception` (description = `Exception.message/1` for exceptions, `inspect(reason)` for `:exit`/`:throw`) |
| `exception.*` | `record_exception/4` on the `:exception` event, using `metadata.reason` + `metadata.stacktrace` |

Span `kind` defaults to `INTERNAL`. Pass
`metadata.span_kind: :server | :client | :producer | :consumer`
on the `:start` event to override.

## Context propagation

The `:start` handler runs synchronously in the calling process
before the user function executes; `:stop` / `:exception`
handlers run after. Between them, the OTel current-span ctx is
the new span (stored in process dictionary keyed by the
`:telemetry.span/3`-issued ref). This means:

- Nested `:telemetry.span/3` calls automatically form a
  parent-child chain.
- `:telemetry.span/3` ↔ `Otel.Trace.with_span/4` mixed in the
  same process also form a chain (both APIs share the
  process-dictionary ctx channel).
- Cross-process work (`Task.async`, `GenServer.cast`, etc.)
  still requires explicit `Otel.Ctx.attach/1` of the captured
  parent ctx — same constraint as `with_span/4`.

## Reserved metadata keys

`:telemetry.span/3` inserts a few internal keys into the
metadata map (notably `:telemetry_span_context`, the
start/stop matching ref). The bridge filters these so they
don't leak as span attributes. Keys filtered:
`:telemetry_span_context`, `:duration`, `:monotonic_time`,
`:system_time`, `:kind`, `:reason`, `:stacktrace`, `:span_kind`.

All remaining metadata keys are stringified (`to_string/1`)
and merged onto the span as attributes.

## Lifecycle

The bridge is a `GenServer` with `trap_exit: true`. Each
configured event prefix attaches three handlers via
`:telemetry.attach/4` keyed by `{__MODULE__, event_name,
self()}`. `terminate/2` detaches all of them so a clean
shutdown leaves no stale handlers.

Multiple instances under different supervisors can co-exist
— each instance owns handlers keyed by its own pid.

## References

- `:telemetry.span/3`: <https://hexdocs.pm/telemetry/telemetry.html#span/3>
- OTel Trace API §Span Creation: `opentelemetry-specification/specification/trace/api.md` L378-L414
- OTel Trace API §record_exception: `trace/api.md` L654-L705

# `event_prefix`

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

A `:telemetry.span/3` event prefix — the first arg to
`:telemetry.span/3`. The bridge subscribes to
`prefix ++ [:start | :stop | :exception]` for each entry.

# `handler_config`

```elixir
@type handler_config() :: %{
  prefix: event_prefix(),
  suffix: :start | :stop | :exception
}
```

Per-handler config passed via `:telemetry.attach/4`'s
`config` argument and received as the 4th arg of
`handle_event/4`.

# `opts`

```elixir
@type opts() :: [events: [event_prefix()], name: GenServer.name()]
```

Options accepted by `start_link/1`. Both keys are optional —
omitting `:events` yields a no-op tracer (no handlers
attached).

# `primitive`

```elixir
@type primitive() ::
  String.t() | {:bytes, binary()} | boolean() | integer() | float() | nil
```

# `primitive_any`

```elixir
@type primitive_any() ::
  primitive() | [primitive_any()] | %{required(String.t()) =&gt; primitive_any()}
```

# `state`

```elixir
@type state() :: %{handlers: [:telemetry.handler_id()]}
```

GenServer state — the list of telemetry handler IDs we own.

# `child_spec`

Returns a specification to start this module under a supervisor.

See `Supervisor`.

# `start_link`

```elixir
@spec start_link(opts :: opts()) :: GenServer.on_start()
```

---

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