---
title: "Elixir"
description: "Instrument Elixir and Phoenix applications with OpenTelemetry and send traces directly to Middleware over OTLP/HTTP."
section: "APM"
breadcrumb: "APM > Elixir"
canonical: "https://docs.middleware.io/get-started/apm/elixir"
---
# Elixir

This guide shows you how to instrument an Elixir application with the upstream [OpenTelemetry Erlang/Elixir SDK](https://github.com/open-telemetry/opentelemetry-erlang) 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](/get-started/agent/otel).

## Setup

#### 1 Set environment variables

The OpenTelemetry exporter reads its configuration from standard environment variables. Set them wherever your application runs:

```bash
export OTEL_EXPORTER_OTLP_ENDPOINT="https://<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`).

> **Note:** 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`:

```elixir
config :opentelemetry,
  resource: [service: [name: "<service-name>"]]

config :opentelemetry_exporter,
  otlp_protocol: :http_protobuf,
  otlp_endpoint: "https://<uid>.middleware.io:443",
  otlp_headers: [{"authorization", "<MW_API_KEY>"}]
```

#### 2 Add dependencies

Add the OpenTelemetry packages to `mix.exs`:

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

Keep `: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:

```bash
mix deps.get
```

If you deploy with `mix release`, start the exporter before the SDK:

```elixir
def project do
  [
    app: :my_app,
    # ...
    releases: [
      my_app: [
        applications: [opentelemetry_exporter: :permanent, opentelemetry: :temporary]
      ]
    ]
  ]
end
```

#### 3 Initialize instrumentation

Attach the instrumentation handlers in `lib/my_app/application.ex`, before the supervision tree starts:

```elixir
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
end
```

> **Note:** Running 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

```bash
mix phx.server
```

## Create custom spans

Wrap your own code in spans with `OpenTelemetry.Tracer`, and attach attributes you can filter on in Middleware:

```elixir
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
end
```

## Other deployment targets

The OpenTelemetry configuration is the same everywhere; only the way you declare environment variables changes.

#### Docker

```bash
docker run \
  -e OTEL_EXPORTER_OTLP_ENDPOINT="https://<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>
```

#### Kubernetes

```yaml
env:
  - name: OTEL_EXPORTER_OTLP_ENDPOINT
    value: "https://<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**](https://app.middleware.io/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_exporter` is listed before `opentelemetry` in `deps` and in the release `applications` list, as shown in step 2.
- **Ecto queries missing:** check that the prefix passed to `OpentelemetryEcto.setup/1` matches your repo's `telemetry_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 support `OTEL_TRACES_EXPORTER=console`.

Need assistance or want to learn more about Middleware? Get in touch with us via our Contact Us page or join our Slack channel.
