Otel.TelemetryReporter (otel v0.4.1)

Copy Markdown View Source

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.MetricsOTel instrumentdispatch
counter/2Otel.Metrics.CounterCounter.add(inst, 1, attrs) — measurement ignored
sum/2Otel.Metrics.UpDownCounterUpDownCounter.add(inst, value, attrs)
last_value/2Otel.Metrics.GaugeGauge.record(inst, value, attrs)
summary/2Otel.Metrics.HistogramHistogram.record(inst, value, attrs)
distribution/2Otel.Metrics.HistogramHistogram.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

Summary

Types

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

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

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

Functions

Returns a specification to start this module under a supervisor.

Types

handler_config()

@type handler_config() ::
  {[Telemetry.Metrics.t()], %{required(term()) => 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()

@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()

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

primitive_any()

@type primitive_any() ::
  primitive() | [primitive_any()] | %{required(String.t()) => primitive_any()}

state()

@type state() :: %{events: [[atom()]]}

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

Functions

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

start_link(opts)

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