OpenTelemetry Phoenix monitoring

Vladimir Mihailenco
February 14, 2026
5 min read

Learn how to instrument your Phoenix application with OpenTelemetry to trace HTTP requests, LiveView interactions, and Ecto database queries.

What is Phoenix?

Phoenix is a web framework for Elixir that provides a productive, reliable foundation for building web applications. It uses the Cowboy or Bandit HTTP server and integrates with Ecto for database access.

Key features of Phoenix include:

  • Real-time communication with Channels and LiveView
  • High performance built on the Erlang VM (BEAM)
  • Fault tolerance through process supervision trees
  • LiveView for server-rendered interactive UIs without JavaScript
  • Ecto integration for database access with query tracing

What is OpenTelemetry?

OpenTelemetry is an open-source observability framework that aims to standardize and simplify the collection, processing, and export of telemetry data from applications and systems.

OpenTelemetry supports multiple programming languages and platforms, making it suitable for a wide range of applications and environments.

OpenTelemetry enables developers to instrument their code and collect telemetry data, which can then be exported to various OpenTelemetry backends or observability platforms for analysis and visualization. The OpenTelemetry Collector can be deployed as a sidecar or standalone service to efficiently collect and process telemetry data from your Phoenix applications.

Installation

Add the required dependencies to your mix.exs file:

elixir
defp deps do
  [
    # OpenTelemetry core
    {:opentelemetry, "~> 1.5"},
    {:opentelemetry_api, "~> 1.4"},
    {:opentelemetry_exporter, "~> 1.8"},

    # Phoenix instrumentation
    {:opentelemetry_phoenix, "~> 2.0"},

    # HTTP server instrumentation (choose one)
    {:opentelemetry_cowboy, "~> 1.0"},
    # {:opentelemetry_bandit, "~> 0.2"},

    # Ecto instrumentation
    {:opentelemetry_ecto, "~> 1.2"},
  ]
end

You need both opentelemetry_phoenix and one of the HTTP server libraries (opentelemetry_cowboy or opentelemetry_bandit) because Phoenix only handles part of the request lifecycle. The server library captures the full HTTP request/response cycle.

Setup

Initialize the telemetry handlers in your application's start/2 callback, before the supervisor starts:

elixir
defmodule MyApp.Application do
  use Application

  def start(_type, _args) do
    # Set up OpenTelemetry instrumentation
    :opentelemetry_cowboy.setup()
    OpentelemetryPhoenix.setup(adapter: :cowboy2)
    OpentelemetryEcto.setup([:my_app, :repo])

    children = [
      MyApp.Repo,
      {Phoenix.PubSub, name: MyApp.PubSub},
      MyAppWeb.Endpoint
    ]

    opts = [strategy: :one_for_one, name: MyApp.Supervisor]
    Supervisor.start_link(children, opts)
  end
end

If you're using Bandit instead of Cowboy, change the adapter:

elixir
OpentelemetryPhoenix.setup(adapter: :bandit)

Endpoint configuration

Ensure your endpoint.ex includes Plug.Telemetry so that endpoint calls are properly traced:

elixir
plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint]

Configuration options

The OpentelemetryPhoenix.setup/1 function accepts these options:

OptionTypeDefaultDescription
adapter:cowboy2 or :banditrequiredThe Phoenix HTTP server adapter
endpoint_prefixlist of atoms[:phoenix, :endpoint]The telemetry event prefix for your endpoint
liveviewbooleantrueWhether to instrument LiveView events

Span names

OpenTelemetry Phoenix generates spans with the following naming conventions:

ComponentSpan Name FormatExample
HTTP request"{METHOD} {route}"GET /users/:id
LiveView mount"{Module}.mount"MyAppWeb.UserLive.mount
LiveView params"{Module}.handle_params"MyAppWeb.UserLive.handle_params
LiveView event"{Module}.handle_event#{event}"MyAppWeb.UserLive.handle_event#save
LiveComponent"{Module}.handle_event#{event}"MyAppWeb.UserForm.handle_event#validate

Span attributes

Router dispatch spans include:

AttributeDescription
phoenix.plugThe plug module handling the request
phoenix.actionThe plug action/options
http.routeThe matched route pattern

LiveView instrumentation

When liveview: true (the default), OpenTelemetry Phoenix automatically traces the following LiveView events:

  • mount - Initial LiveView mount and connected mount
  • handle_params - URL parameter changes
  • handle_event - User interactions (clicks, form submits)
  • LiveComponent handle_event - Events on nested components

No additional code is needed. The instrumentation hooks into Phoenix telemetry events automatically.

Ecto instrumentation

To trace database queries, set up opentelemetry_ecto with your repo's telemetry prefix:

elixir
# For a repo named MyApp.Repo, the default prefix is [:my_app, :repo]
OpentelemetryEcto.setup([:my_app, :repo])

Each Ecto query creates a span with these attributes:

AttributeDescription
db.systemDatabase type (postgresql, mysql, sqlite)
db.statementSQL query text (disabled by default, see below)
db.operation.nameSQL command (SELECT, INSERT, etc.)
db.sql.tableTable name
server.addressDatabase hostname

Ecto options

elixir
OpentelemetryEcto.setup([:my_app, :repo],
  # Enable SQL statement capture (disabled by default)
  db_statement: :enabled,

  # Add custom attributes to all spans
  additional_attributes: %{environment: "production"},

  # Use a sanitizer function instead of :enabled for sensitive data
  # db_statement: &MyApp.Sanitizer.sanitize/1
)

Adding custom spans

Create custom spans within your controllers or LiveView handlers:

elixir
require OpenTelemetry.Tracer, as: Tracer

def show(conn, %{"id" => id}) do
  user = Tracer.with_span "fetch_user" do
    Tracer.set_attributes([{"user.id", id}])
    Accounts.get_user!(id)
  end

  render(conn, :show, user: user)
end

For LiveView:

elixir
def handle_event("save", %{"user" => params}, socket) do
  Tracer.with_span "save_user" do
    case Accounts.update_user(socket.assigns.user, params) do
      {:ok, user} ->
        {:noreply, assign(socket, :user, user)}

      {:error, changeset} ->
        Tracer.set_status(:error, "validation failed")
        {:noreply, assign(socket, :changeset, changeset)}
    end
  end
end

Exporter configuration

Configure the OTLP exporter in your config/runtime.exs:

elixir
config :opentelemetry,
  resource: %{
    "service.name" => "my-phoenix-app",
    "service.version" => "1.0.0"
  },
  span_processor: :batch,
  traces_exporter: :otlp

config :opentelemetry_exporter,
  otlp_protocol: :grpc,
  otlp_endpoint: System.get_env("OTEL_EXPORTER_OTLP_ENDPOINT", "http://localhost:4317"),
  otlp_headers: [
    {"uptrace-dsn", System.get_env("UPTRACE_DSN", "")}
  ]

What is Uptrace?

Uptrace is an OpenTelemetry APM that supports distributed tracing, metrics, and logs. You can use it to monitor applications and troubleshoot issues.

Uptrace Overview

Uptrace comes with an intuitive query builder, rich dashboards, alerting rules with notifications, and integrations for most languages and frameworks.

Uptrace can process billions of spans and metrics on a single server and allows you to monitor your applications at 10x lower cost.

In just a few minutes, you can try Uptrace by visiting the cloud demo (no login required) or running it locally with Docker. The source code is available on GitHub.

FAQ

What packages do I need for Phoenix instrumentation? You need opentelemetry_phoenix for Phoenix-level tracing, plus either opentelemetry_cowboy or opentelemetry_bandit for HTTP server-level tracing. Add opentelemetry_ecto to also trace database queries.

Does OpenTelemetry Phoenix support LiveView? Yes, LiveView instrumentation is enabled by default. It traces mount, handle_params, and handle_event callbacks for both LiveView and LiveComponent modules.

Why do I need both opentelemetry_phoenix and opentelemetry_cowboy? Phoenix only handles part of the request lifecycle (routing, controllers). The HTTP server (Cowboy or Bandit) handles the full request/response cycle. You need both for complete request tracing.

How do I trace Ecto queries? Add opentelemetry_ecto to your dependencies and call OpentelemetryEcto.setup([:my_app, :repo]) in your application start. Queries automatically create spans with table names and timing. To include SQL statements, pass db_statement: :enabled.

Can I disable LiveView tracing? Yes, pass liveview: false to OpentelemetryPhoenix.setup/1 to skip LiveView instrumentation.

How do I add custom attributes to spans? Use OpenTelemetry.Tracer.set_attributes/1 within a span context to add custom key-value pairs. You can also use Tracer.with_span/2 to create custom spans with attributes.

What's next?

With OpenTelemetry Phoenix instrumentation in place, you can monitor request latency, trace LiveView interactions, and identify slow database queries.

Next steps to enhance your observability: