otel_bridge bridges existing Telemetry.Metrics definitions to
OpenTelemetry metrics.
It is intended for applications that already emit telemetry events and already
define metrics with Telemetry.Metrics, but want to export those metrics
through the OpenTelemetry SDK.
It does not replace the OpenTelemetry SDK. Its role is narrow:
- keep metric definitions in plain
Telemetry.Metrics - translate supported metric shapes into OpenTelemetry instruments
- provide small profile helpers for backend-specific reader configuration
When to use it
Use otel_bridge when:
- your app already uses
Telemetry.Metrics - you want to adopt OpenTelemetry metrics without rewriting existing metric definitions
- you want backend-specific export configuration to stay outside business code
How it works
Most integrations follow this flow:
- define one or more spec modules with
use OtelBridge.Spec - start
OtelBridgeunder your supervision tree - configure an OpenTelemetry metric reader, optionally via a profile helper
MyApp.Metrics (OtelBridge.Spec)
|
v
OtelBridge
/ \
v v
Telemetry handlers telemetry_poller
\ /
v v
OpenTelemetry metrics
|
v
OtelBridge.Profile
|
v
OTLP backendInstallation
Add otel_bridge to your dependencies:
def deps do
[
{:otel_bridge, "~> 0.1.2"}
]
endQuick start
1. Define metrics
Create a spec module with OtelBridge.Spec:
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
endThe meta argument comes from the :meta option passed to OtelBridge. Use
it for shared values such as service name, default tags, or environment data.
2. Start the bridge
Add OtelBridge to your supervision tree:
children = [
{OtelBridge,
specs: [MyApp.Metrics],
optional_specs: [MyApp.OptionalMetrics],
measurements: [{MyApp.Measurements, :dispatch, []}],
meta: [service: "my_app"],
poller: [period: 5_000]}
]Common options:
:metrics- rawTelemetry.Metricsdefinitions:specs- modules implementingOtelBridge.Spec:optional_specs- spec modules to load when available:measurements-:telemetry_pollermeasurements:meta- shared keyword metadata passed to spec modules:poller-:telemetry_polleroptions:observer_children- custom children for observable or gauge-like metrics
3. Configure metric export
otel_bridge helps build metric reader configuration, but the OpenTelemetry
SDK remains configured through the standard OpenTelemetry packages.
For VictoriaMetrics:
config :opentelemetry_experimental,
readers: [
OtelBridge.metric_reader!(:victoria_metrics,
export_interval_ms: 5_000,
endpoint: "http://localhost:4318"
)
]You can also configure the reader yourself and use otel_bridge only for
metrics bridging.
Metric mapping
The default bridge path maps:
Telemetry.Metrics.Counter-> OpenTelemetry counterTelemetry.Metrics.Sum-> OpenTelemetry counterTelemetry.Metrics.Summary-> OpenTelemetry histogramTelemetry.Metrics.Distribution-> OpenTelemetry histogramTelemetry.Metrics.LastValue-> OpenTelemetry observable gauge
During that process, the bridge also:
- groups metrics by telemetry event name
- extracts measurements from event payloads
- applies
keepfilters when present - derives exported tags from
tag_values - carries over unit, description, and explicit OTel reporter options
Telemetry.Metrics.LastValue is handled differently from synchronous metrics.
Telemetry events update an internal latest-value store, and the OpenTelemetry
reader observes that store through an observable gauge callback during
collection. This preserves the current-state semantics of gauges without
treating absolute values as counter deltas.
last_value cardinality protection
Each last_value series is keyed by {metric_name, tags}. Low-cardinality
gauges such as VM memory, queue depth, or cache size are a natural fit. Avoid
high-cardinality tags such as request IDs, user IDs, or raw dynamic URLs unless
you configure bounds.
Use reporter_options[:otel][:last_value] to cap retained series:
last_value("queue.depth",
event_name: [:my_app, :queue, :stats],
measurement: :depth,
tags: [:queue],
reporter_options: [
otel: [
last_value: [
ttl_ms: 300_000,
max_series: 1_000,
on_overflow: :drop_new
]
]
]
)Supported options:
:ttl_ms- deletes stale series after the given age in milliseconds; defaults to:infinity:max_series- maximum retained tag combinations per metric; defaults to:infinity:on_overflow-:drop_newto ignore new tag combinations, or:drop_oldestto evict the oldest retained series; defaults to:drop_new
Expired series are pruned when new last_value events arrive and when the OTel
reader observes the metric.
Scope
Supported today:
Telemetry.Metrics.CounterTelemetry.Metrics.SumTelemetry.Metrics.SummaryTelemetry.Metrics.DistributionTelemetry.Metrics.LastValue- backend policy helpers through
OtelBridge.Profile :victoria_metricsprofile
Out of scope:
- tracing APIs
- logs
- automatic dashboard generation
Examples and references
See the runnable examples in:
The first shows the smallest business integration shape.
The second shows how to wire the VictoriaMetrics profile into
config/runtime.exs.
Useful modules:
OtelBridge- the main integration entrypointOtelBridge.Spec- how to define metricsOtelBridge.Profile- how export profiles workOtelBridge.Profile.VictoriaMetrics- the built-in backend profile
See CHANGELOG.md for release history.