Hex.pmHexDocsCIE2ECoverage StatusLicense

Otel

Pure Elixir, OpenTelemetry-compatible

Features

Requirements

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"}
  ]
end

Configuration

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
end

License

Released under the MIT License.