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

`Telemetry.Metrics` reporter that bridges BEAM `:telemetry`
events into the OTel Metrics pipeline. Mirror of
`Otel.LoggerHandler` for the metrics pillar.

Add it to your supervision tree with a list of
`Telemetry.Metrics` definitions:

    defmodule MyApp.Application do
      use Application

      @impl true
      def start(_type, _args) do
        children = [
          {Otel.TelemetryReporter, metrics: metrics()}
        ]
        Supervisor.start_link(children, strategy: :one_for_one)
      end

      defp metrics do
        import Telemetry.Metrics

        [
          counter("phoenix.endpoint.stop.duration"),
          summary("phoenix.endpoint.stop.duration",
            unit: {:native, :millisecond}
          ),
          last_value("vm.memory.total", unit: {:byte, :kilobyte})
        ]
      end
    end

## Type mapping

| `Telemetry.Metrics`        | OTel instrument                   | dispatch                                               |
|---------------------------|-----------------------------------|--------------------------------------------------------|
| `counter/2`               | `Otel.Metrics.Counter`            | `Counter.add(inst, 1, attrs)` — measurement ignored    |
| `sum/2`                   | `Otel.Metrics.UpDownCounter`      | `UpDownCounter.add(inst, value, attrs)`                |
| `last_value/2`            | `Otel.Metrics.Gauge`              | `Gauge.record(inst, value, attrs)`                     |
| `summary/2`               | `Otel.Metrics.Histogram`          | `Histogram.record(inst, value, attrs)`                 |
| `distribution/2`          | `Otel.Metrics.Histogram`          | `Histogram.record(inst, value, attrs)` (uses buckets) |

`Telemetry.Metrics.Sum` carries no monotonic flag, so the
reporter conservatively maps it to `UpDownCounter`. If your
source values are guaranteed non-negative and you want monotonic
Sum semantics, pass `reporter_options: [monotonic: true]`:

    sum("http.request.bytes_sent", reporter_options: [monotonic: true])

## Tags / tag_values

`metric.tags` selects which metadata keys to use as OTel
attribute keys; `metric.tag_values` (default identity) can
pre-process metadata before extraction. Tag values are
string-coerced (atoms via `Atom.to_string/1`, others kept
as-is) on dispatch.

## Unit conversion

Unit conversion is performed by `Telemetry.Metrics` itself
at metric-definition time: when you pass `unit: {from, to}`,
the metric's `:measurement` field is wrapped to convert
values into the target unit before they reach this reporter.
We forward the resulting target-unit atom (e.g.
`:millisecond`, `:kilobyte`) to the OTel instrument's `unit`
field. Note byte conversions are **decimal** per
`Telemetry.Metrics` convention (1 kB = 1000 B).

## :keep / :drop predicates

Honored per `Telemetry.Metrics`: when `:keep` returns false or
`:drop` returns true, the measurement is skipped — the OTel
instrument is not updated.

## Lifecycle

The reporter is a `GenServer` with `trap_exit: true`. Each
registered telemetry event gets a `:telemetry.attach` keyed by
`{__MODULE__, event_name, self()}`. On `terminate/2` (supervisor
shutdown), every handler is detached so reloads / restarts
don't leave dangling handlers.

## References

- `Telemetry.Metrics`: <https://hexdocs.pm/telemetry_metrics>
- `Telemetry.Metrics.ConsoleReporter` — reference shape we
  mirror (group-by-event attach, single handle_event dispatch)

# `handler_config`

```elixir
@type handler_config() ::
  {[Telemetry.Metrics.t()], %{required(term()) =&gt; Otel.Metrics.Instrument.t()}}
```

Per-event handler config: the metrics defs grouped under one
event name, plus a shared `metric_id → instrument` lookup map.

# `opts`

```elixir
@type opts() :: [metrics: [Telemetry.Metrics.t()], name: GenServer.name()]
```

Options accepted by `start_link/1`. Both keys are optional —
omitting `:metrics` yields a no-op reporter (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() :: %{events: [[atom()]]}
```

GenServer state — the list of telemetry event names we own handlers for.

# `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*
