Otel
Pure Elixir, OpenTelemetry-compatible
Features
- Signals
- Traces
- Metrics
- Logs
- Propagators
- W3C TraceContext
- W3C Baggage
- Exporters
- OTLP/HTTP (Protobuf)
- Integrations
:loggerbridge:telemetrybridge
Requirements
-
Elixir
~> 1.18 -
Erlang/OTP
~> 26.2
Compatibility
| Component | Version |
|---|---|
| OpenTelemetry Specification | v1.55.0 (Stable signals only) |
| OpenTelemetry Protocol (OTLP) | v1.10.0 |
| W3C Trace Context | Level 2 (REC) |
| W3C Baggage | OTel Stable Baggage Propagator wire format |
Installation
Add :otel to deps in mix.exs:
def deps do
[
{:otel, "~> 0.4.2"}
]
endConfiguration
SDK
# config/config.exs
config :otel, otp_app: :my_app, req_options: []:req_options is forwarded to Req.new/1 — see :req for the full option list.
Logs
# config/config.exs
config :kernel,
logger: [
{:handler, :otel, Otel.LoggerHandler, %{}}
]Otel.LoggerHandler bridges Logger — see :logger for log levels and metadata.
Metrics
# lib/my_app/application.ex
children = [
{Otel.TelemetryReporter, metrics: []}
]Otel.TelemetryReporter bridges Telemetry.Metrics — see :telemetry_metrics for metric definitions.
Trace
# lib/my_app/application.ex
children = [
{Otel.TelemetryTracer, events: []}
]Otel.TelemetryTracer bridges :telemetry.span/3 — see :telemetry for instrumentation.
Optional: Otel.TelemetrySpanDecorator
# lib/my_app.ex
defmodule MyApp do
use Otel.TelemetrySpanDecorator
@span [:my_app, :hello]
# @span event: [:my_app, :hello], capture_io: true
def hello, do: :world
end@span auto-wraps the function body in :telemetry.span/3 and injects code.function.name / code.file.path / code.line.number; pass capture_io: true to also record arguments and the return value.
How-to
End-to-end example wiring traces, logs, and metrics for a MyApp.Calculator module.
# config/config.exs
import Config
config :otel, otp_app: :my_app
config :kernel,
logger: [
{:handler, :otel, Otel.LoggerHandler, %{}}
]# lib/my_app/application.ex
defmodule MyApp.Application do
use Application
import Telemetry.Metrics
@impl true
def start(_type, _args) do
children = [
{Otel.TelemetryTracer,
events: [
[:my_app, :calculator, :add]
]},
{Otel.TelemetryReporter,
metrics: [
counter("my_app.calculator.add.count",
event_name: [:my_app, :calculator, :add, :stop],
measurement: :duration
),
distribution("my_app.calculator.add.duration",
event_name: [:my_app, :calculator, :add, :stop],
measurement: :duration,
unit: {:native, :millisecond},
reporter_options: [buckets: [10, 50, 100, 500]]
),
summary("my_app.calculator.add.duration_summary",
event_name: [:my_app, :calculator, :add, :stop],
measurement: :duration,
unit: {:native, :millisecond}
),
sum("my_app.calculator.result.total",
event_name: [:my_app, :calculator, :result],
measurement: :value
),
last_value("my_app.calculator.result.last",
event_name: [:my_app, :calculator, :result],
measurement: :value
)
]}
]
Supervisor.start_link(children, strategy: :one_for_one, name: MyApp.Supervisor)
end
end# lib/my_app/calculator.ex
defmodule MyApp.Calculator do
use Otel.TelemetrySpanDecorator
require Logger
@span event: [:my_app, :calculator, :add], capture_io: true
def add(a, b) do
result = a + b
Logger.info("calculator.add", a: a, b: b)
:telemetry.execute([:my_app, :calculator, :result], %{value: result})
result
end
endLicense
Released under the MIT License.