Bridges existing Telemetry.Metrics definitions to OpenTelemetry metrics.
OtelBridge is useful when an application already describes business metrics
with Telemetry.Metrics, but wants to export them through the OpenTelemetry
SDK without rewriting those metric definitions in another format.
In practice, the bridge does three jobs:
- load metrics from one or more
OtelBridge.Specmodules - turn the supported
Telemetry.Metricsdefinitions into OpenTelemetry instruments - run any poller measurements and observer processes needed at runtime
OtelBridge does not replace the OpenTelemetry SDK. Exporters, readers, and
other SDK components are still configured through the standard OpenTelemetry
packages. OtelBridge only handles the translation layer between
Telemetry.Metrics and OpenTelemetry metrics.
When to use it
Use OtelBridge when:
- your app already emits telemetry events and defines metrics with
Telemetry.Metrics - you want to adopt OpenTelemetry metrics without rewriting existing metric definitions
- you want to keep metric definitions in plain Elixir modules
- you want backend-specific reader configuration to stay outside business code
If you need tracing, logs, or full OpenTelemetry SDK setup, use the standard OpenTelemetry libraries alongside this project.
How to use it
A typical integration has three steps.
MyApp.Metrics (OtelBridge.Spec)
|
v
OtelBridge
/ v v
telemetry handlers telemetry_poller
/
v v
OpenTelemetry metrics
|
v
OtelBridge.Profile
|
v
OTLP backend1. Define a metric spec
Create a module that uses OtelBridge.Spec and returns ordinary
Telemetry.Metrics definitions:
defmodule MyApp.Metrics do
use OtelBridge.Spec
@impl OtelBridge.Spec
def metrics(meta) do
[
summary("http.server.duration",
event_name: [:my_app, :http, :stop],
measurement: :duration,
unit: {:native, :millisecond},
tags: [:route, :status_code],
tag_values: fn metadata ->
metadata
|> Map.put(:route, metadata[:route] || "unknown")
|> Map.put(:status_code, metadata[:status_code] || 500)
|> Map.put(:service, Keyword.get(meta, :service))
end
)
]
end
end2. Start OtelBridge in your supervision tree
children = [
{OtelBridge,
specs: [MyApp.Metrics],
measurements: [{MyApp.Measurements, :dispatch, []}],
meta: [service: "my_app"],
poller: [period: 5_000]}
]OtelBridge collects metrics from the provided spec modules, filters them
down to the shapes the bridge supports, and starts the runtime processes that
publish them.
3. Configure an OpenTelemetry metric reader
Backend-specific reader helpers live in OtelBridge.Profile modules:
config :opentelemetry_experimental,
readers: [
OtelBridge.metric_reader!(:victoria_metrics,
export_interval_ms: 5_000,
endpoint: "http://localhost:4318"
)
]This keeps export policy separate from the metric definitions used by your application code.
What gets mapped
OtelBridge maps supported Telemetry.Metrics definitions to
OpenTelemetry instruments at runtime:
Telemetry.Metrics.Counter-> counterTelemetry.Metrics.Sum-> counterTelemetry.Metrics.Summary-> histogramTelemetry.Metrics.Distribution-> histogram
During that process, the bridge groups metrics by event name, extracts the
measurement value from telemetry events, applies any keep filter, derives
tags through tag_values, and carries over unit, description, and explicit
OTel reporter options.
Runtime options
:metrics- rawTelemetry.Metricsdefinitions to load directly:specs- metric spec modules implementingOtelBridge.Spec:optional_specs- spec modules to load only when available:measurements-:telemetry_pollermeasurement entries:meta- keyword metadata passed to each spec module:poller-:telemetry_polleroptions such as polling period:observer_children- extra children for gauge-like or observable metrics
Metrics such as Telemetry.Metrics.LastValue are intentionally left out of
the default bridge path and should be handled with observers or custom
runtime logic.
Summary
Types
Runtime option accepted by start_link/1 and child_spec/1.
Functions
Returns a standard supervisor child spec for OtelBridge.
Builds a metric reader config from a named or module-based backend profile.
Filters a list of Telemetry.Metrics definitions down to the metric shapes
handled by the bridge runtime.
Starts the OtelBridge supervision tree.
Types
@type option() :: {:metrics, [Telemetry.Metrics.t()]} | {:specs, [module()]} | {:optional_specs, [module()]} | {:measurements, [module() | {module(), atom(), [term()]}]} | {:meta, keyword()} | {:poller, keyword()} | {:observer_children, [Supervisor.child_spec()]}
Runtime option accepted by start_link/1 and child_spec/1.
Functions
@spec child_spec([option()]) :: Supervisor.child_spec()
Returns a standard supervisor child spec for OtelBridge.
@spec metric_reader!( OtelBridge.Profile.profile_ref(), keyword() ) :: map()
Builds a metric reader config from a named or module-based backend profile.
Use this when you want to keep exporter-specific policy in an
OtelBridge.Profile module instead of hardcoding reader maps in application
config.
@spec prepare_metrics([Telemetry.Metrics.t()]) :: [Telemetry.Metrics.t()]
Filters a list of Telemetry.Metrics definitions down to the metric shapes
handled by the bridge runtime.
Today this excludes shapes such as Telemetry.Metrics.LastValue, which are
expected to be implemented through observers or custom handling.
@spec start_link([option()]) :: Supervisor.on_start()
Starts the OtelBridge supervision tree.
This is the main entrypoint most applications should use when adding the bridge to a supervision tree.