Elixir
This guide shows you how to instrument an Elixir application with the upstream OpenTelemetry Erlang/Elixir SDK and send traces directly to Middleware. It covers Phoenix, Bandit (or Cowboy) and Ecto out of the box. No Middleware Host Agent is required: the OTLP exporter in your application sends data straight to your Middleware account.
Prerequisites#
- Elixir 1.15+ on Erlang/OTP 25+. Check using:
1elixir --version - Middleware account & project: your Middleware UID (
https://<uid>.middleware.io) and API key (MW_API_KEY) - Network access from your application to
https://<uid>.middleware.io:443
For a deeper overview of Middleware's OTLP endpoints and headers, see the OpenTelemetry getting started guide.
Setup#
1 Set environment variables#
The OpenTelemetry exporter reads its configuration from standard environment variables. Set them wherever your application runs:
1export OTEL_EXPORTER_OTLP_ENDPOINT="https://<MW_UID>.middleware.io:443"
2export OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf"
3export OTEL_EXPORTER_OTLP_HEADERS="authorization=<MW_API_KEY>"
4export OTEL_SERVICE_NAME="<service-name>"
5export OTEL_RESOURCE_ATTRIBUTES="service.version=<service-version>,deployment.environment=<environment>"Replace:
<uid>with your Middleware project UID.<MW_API_KEY>with your Middleware API key.<service-name>with a meaningful name for this service (for example,my-phoenix-app).
Set OTEL_EXPORTER_OTLP_ENDPOINT to the root URL only. The OTLP/HTTP exporter appends /v1/traces for you.
Prefer application config over environment variables? Set the same values in config/runtime.exs:
1config :opentelemetry,
2 resource: [service: [name: "<service-name>"]]
3
4config :opentelemetry_exporter,
5 otlp_protocol: :http_protobuf,
6 otlp_endpoint: "https://<MW_UID>.middleware.io:443",
7 otlp_headers: [{"authorization", "<MW_API_KEY>"}]2 Add dependencies#
Add the OpenTelemetry packages to mix.exs:
1defp deps do
2 [
3 # ... your existing dependencies
4 {:opentelemetry_exporter, "~> 1.11"},
5 {:opentelemetry, "~> 1.7"},
6 {:opentelemetry_api, "~> 1.5"},
7 {:opentelemetry_phoenix, "~> 2.0"},
8 {:opentelemetry_bandit, "~> 0.3"},
9 {:opentelemetry_ecto, "~> 1.2"}
10 ]
11endKeep :opentelemetry_exporter above :opentelemetry so the exporter starts first. Leave out opentelemetry_phoenix, opentelemetry_bandit or opentelemetry_ecto if your app doesn't use that library.
Then fetch the dependencies:
1mix deps.getIf you deploy with mix release, start the exporter before the SDK:
1def project do
2 [
3 app: :my_app,
4 # ...
5 releases: [
6 my_app: [
7 applications: [opentelemetry_exporter: :permanent, opentelemetry: :temporary]
8 ]
9 ]
10 ]
11end3 Initialize instrumentation#
Attach the instrumentation handlers in lib/my_app/application.ex, before the supervision tree starts:
1defmodule MyApp.Application do
2 @moduledoc false
3
4 use Application
5
6 @impl true
7 def start(_type, _args) do
8 OpentelemetryBandit.setup()
9 OpentelemetryPhoenix.setup(adapter: :bandit)
10 OpentelemetryEcto.setup([:my_app, :repo])
11
12 children = [
13 MyApp.Repo,
14 MyAppWeb.Endpoint
15 ]
16
17 opts = [strategy: :one_for_one, name: MyApp.Supervisor]
18 Supervisor.start_link(children, opts)
19 end
20endRunning Cowboy instead of Bandit? Replace {:opentelemetry_bandit, "~> 0.3"} with {:opentelemetry_cowboy, "~> 1.0"}, call :opentelemetry_cowboy.setup() instead of OpentelemetryBandit.setup(), and use OpentelemetryPhoenix.setup(adapter: :cowboy2).
OpentelemetryEcto.setup/1 takes your repo's telemetry prefix. By default this is your app name followed by :repo, for example [:my_app, :repo].
4 Run your application#
1mix phx.serverCreate custom spans#
Wrap your own code in spans with OpenTelemetry.Tracer, and attach attributes you can filter on in Middleware:
1defmodule MyApp.Orders do
2 require OpenTelemetry.Tracer, as: Tracer
3
4 def handle_order(order_id) do
5 Tracer.with_span "handle-order" do
6 Tracer.set_attributes(%{"order.id": order_id})
7 # ...
8 end
9 end
10endOther deployment targets#
The OpenTelemetry configuration is the same everywhere; only the way you declare environment variables changes.
1docker run \
2 -e OTEL_EXPORTER_OTLP_ENDPOINT="https://<MW_UID>.middleware.io:443" \
3 -e OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf" \
4 -e OTEL_EXPORTER_OTLP_HEADERS="authorization=<MW_API_KEY>" \
5 -e OTEL_SERVICE_NAME="<service-name>" \
6 <your-image>1env:
2 - name: OTEL_EXPORTER_OTLP_ENDPOINT
3 value: "https://<MW_UID>.middleware.io:443"
4 - name: OTEL_EXPORTER_OTLP_PROTOCOL
5 value: "http/protobuf"
6 - name: OTEL_EXPORTER_OTLP_HEADERS
7 value: "authorization=<MW_API_KEY>"
8 - name: OTEL_SERVICE_NAME
9 value: "<service-name>"Store the API key in a Kubernetes Secret and reference it with valueFrom.secretKeyRef in production.
View your data#
After you start the application and send some traffic, wait 3–5 minutes, then open APM → Services or the Traces explorer in Middleware and filter by your service name.
Environment variables#
| Variable | Purpose | Example |
|---|---|---|
OTEL_EXPORTER_OTLP_ENDPOINT | Middleware OTLP endpoint for your project. | https://<uid>.middleware.io:443 |
OTEL_EXPORTER_OTLP_PROTOCOL | OTLP transport used by the exporter. | http/protobuf |
OTEL_EXPORTER_OTLP_HEADERS | Authenticates requests with your Middleware API key. | authorization=<MW_API_KEY> |
OTEL_SERVICE_NAME | Service name shown in Middleware APM. | my-phoenix-app |
OTEL_RESOURCE_ATTRIBUTES | Extra resource attributes attached to every span. | service.version=1.4.2,deployment.environment=prod |
Troubleshooting#
- No data in Middleware: confirm the environment variables are set in the BEAM process that runs your app, and that the endpoint does not already include
/v1/traces. - Spans dropped in releases: make sure
opentelemetry_exporteris listed beforeopentelemetryindepsand in the releaseapplicationslist, as shown in step 2. - Ecto queries missing: check that the prefix passed to
OpentelemetryEcto.setup/1matches your repo'stelemetry_prefix. - Debugging locally: to print spans to stdout instead of exporting them, temporarily add
config :opentelemetry, traces_exporter: {:otel_exporter_stdout, []}to your config. The Erlang/Elixir SDK does not supportOTEL_TRACES_EXPORTER=console.
Need assistance or want to learn more about Middleware? Get in touch with us via our Contact Us or join our Slack channel.