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:bash
elixir --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:
export OTEL_EXPORTER_OTLP_ENDPOINT="https://<MW_UID>.middleware.io:443"
export OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf"
export OTEL_EXPORTER_OTLP_HEADERS="authorization=<MW_API_KEY>"
export OTEL_SERVICE_NAME="<service-name>"
export 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:
config :opentelemetry,
resource: [service: [name: "<service-name>"]]
config :opentelemetry_exporter,
otlp_protocol: :http_protobuf,
otlp_endpoint: "https://<MW_UID>.middleware.io:443",
otlp_headers: [{"authorization", "<MW_API_KEY>"}]2 Add dependencies#
Add the OpenTelemetry packages to mix.exs:
defp deps do
[
# ... your existing dependencies
{:opentelemetry_exporter, "~> 1.11"},
{:opentelemetry, "~> 1.7"},
{:opentelemetry_api, "~> 1.5"},
{:opentelemetry_phoenix, "~> 2.0"},
{:opentelemetry_bandit, "~> 0.3"},
{:opentelemetry_ecto, "~> 1.2"}
]
endKeep :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:
mix deps.getIf you deploy with mix release, start the exporter before the SDK:
def project do
[
app: :my_app,
# ...
releases: [
my_app: [
applications: [opentelemetry_exporter: :permanent, opentelemetry: :temporary]
]
]
]
end3 Initialize instrumentation#
Attach the instrumentation handlers in lib/my_app/application.ex, before the supervision tree starts:
defmodule MyApp.Application do
@moduledoc false
use Application
@impl true
def start(_type, _args) do
OpentelemetryBandit.setup()
OpentelemetryPhoenix.setup(adapter: :bandit)
OpentelemetryEcto.setup([:my_app, :repo])
children = [
MyApp.Repo,
MyAppWeb.Endpoint
]
opts = [strategy: :one_for_one, name: MyApp.Supervisor]
Supervisor.start_link(children, opts)
end
endRunning 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#
mix phx.serverCreate custom spans#
Wrap your own code in spans with OpenTelemetry.Tracer, and attach attributes you can filter on in Middleware:
defmodule MyApp.Orders do
require OpenTelemetry.Tracer, as: Tracer
def handle_order(order_id) do
Tracer.with_span "handle-order" do
Tracer.set_attributes(%{"order.id": order_id})
# ...
end
end
endOther deployment targets#
The OpenTelemetry configuration is the same everywhere; only the way you declare environment variables changes.
docker run \
-e OTEL_EXPORTER_OTLP_ENDPOINT="https://<MW_UID>.middleware.io:443" \
-e OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf" \
-e OTEL_EXPORTER_OTLP_HEADERS="authorization=<MW_API_KEY>" \
-e OTEL_SERVICE_NAME="<service-name>" \
<your-image>env:
- name: OTEL_EXPORTER_OTLP_ENDPOINT
value: "https://<MW_UID>.middleware.io:443"
- name: OTEL_EXPORTER_OTLP_PROTOCOL
value: "http/protobuf"
- name: OTEL_EXPORTER_OTLP_HEADERS
value: "authorization=<MW_API_KEY>"
- name: OTEL_SERVICE_NAME
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.
What did you think of this content?