bennydreamtech23

bennydreamtech23

How to Use OpenTelemetry, PromEx, and Promtail to Build an Observability System with OpenObserver Exporter in Elixir

Observability is a key part of building scalable and maintainable applications. Today, I want to share how to set up one of the most powerful observability stacks in Elixir using tracing, metrics, and logging. Together, these help you monitor performance, detect bottlenecks, and improve system reliability.

First, I want to thank God for His guidance in helping me understand and set up these tools, my supervisor for his support, and @smaller_infinity from Elixir forum channel for clearing doubts and giving invaluable insights.

Why Observability Matters

  • Tracing: Tracks the performance of your application and helps find bottlenecks.

  • Metrics: Measures the system’s behavior and performance over time.

  • Logs: Provides insight into system events and application state.

Combining these three gives a full picture of your system’s health and improves scalability.


Packages Needed

Here’s a breakdown of the packages we’ll use:

OpenTelemetry & Auto-Instrumentation

{:opentelemetry, "~> 1.3"}
{:opentelemetry_api, "~> 1.2"}
{:opentelemetry_exporter, "~> 1.6"}
{:opentelemetry_phoenix, "~> 1.1"}
{:opentelemetry_cowboy, "~> 0.2"}
{:opentelemetry_ecto, "~> 1.2"}
{:opentelemetry_logger_metadata, "~> 0.2.0"}
{:opentelemetry_liveview, "~> 1.0.0-rc.4"}
{:opentelemetry_telemetry, "~> 1.0.0-beta.7"}

Purpose of each library:

  • opentelemetry & opentelemetry_api: Core OpenTelemetry tracing libraries.

  • opentelemetry_exporter: Exports traces to your observability system (OpenObserver).

  • opentelemetry_phoenix: Auto-instrument Phoenix requests.

  • opentelemetry_cowboy: Auto-instrument Cowboy requests.

  • opentelemetry_ecto: Auto-instrument database queries.

  • opentelemetry_logger_metadata: Attach logger metadata to traces.

  • opentelemetry_liveview: Instrument LiveView events automatically.

  • opentelemetry_telemetry: Bridges telemetry events to OpenTelemetry.

Metrics & Logs

{:prom_ex, "~> 1.9.0"}
{:logger_json, "~> 7.0"}
{:otel_http, "~> 0.2.0"}
{:jason, "~> 1.2"}
{:telemetry_metrics, "~> 0.6"}
{:telemetry_poller, "~> 1.0"}
{:logger_file_backend, "~> 0.0.10"}

  • prom_ex: Exposes metrics to Prometheus.

  • logger_json: Structured JSON logging.

  • otel_http: Adds HTTP instrumentation.

  • telemetry_metrics & telemetry_poller: Collect and export telemetry metrics.

  • logger_file_backend: Writes logs to files.


Setting Up Tracing

  1. Application Configuration in application.ex:
def start(_type, _args) do
  # OpenTelemetry Instrumentation
  :opentelemetry_cowboy.setup()
  OpentelemetryPhoenix.setup(adapter: :cowboy2)
  OpentelemetryEcto.setup([:myApp, :repo])
  OpentelemetryLoggerMetadata.setup()
  OpentelemetryLiveView.setup()

  children = [
    MyApp.Repo,
    MyApp.Endpoint
  ]

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

  1. Configuration in config.exs:
config :opentelemetry,
  traces_exporter: :otlp,
  metrics_exporter: :otlp,
  span_processor: :batch,
  resource: %{service: %{name: "MyApp_ServiceName"}}

config :opentelemetry_exporter,
  otlp_protocol: :http_protobuf,
  otlp_endpoint: System.get_env("OTEL_EXPORTER_OTLP_ENDPOINT")

  1. Environment Variables:

Create a .env file and set:

export OTEL_TRACES_EXPORTER="otlp"
export OTEL_EXPORTER_OTLP_COMPRESSION="gzip"
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Basic <base64-username-password>"
export OTEL_RESOURCE_ATTRIBUTES="service.name=MyApp-dev,service.namespace=MyApp,deployment.environment=development"
export OTEL_EXPORTER_OTLP_ENDPOINT="<endpoint-from-OpenObserver or your own>"
export OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf"

  1. Start the Application:
mix deps.get
source .env
mix phx.server

After starting the app, visit your OpenObserver dashboard to see your traces in real-time.


This setup gives you automatic instrumentation for Phoenix, Cowboy, Ecto, and LiveView, while exporting traces to OpenObserver, providing a full observability of distributed tracing in elixir



Setting Up Metrics with PromEx

The second pillar of observability is metrics. In Elixir, we can use the PromEx library to collect and expose metrics to Prometheus (or any remote monitoring system like OpenObserver).


1. Install PromEx

Add PromEx to your mix.exs:

{:prom_ex, "~> 1.9.0"}

Then run:

mix deps.get


2. Generate PromEx Configuration

Run the PromEx generator:

mix prom_ex.gen.config --datasource YOUR_PROMETHEUS_DATASOURCE_ID

This will create a PromEx module with default settings. You can also add more libraries or custom metrics later—just follow the PromEx documentation to avoid errors from invalid configurations.


3. Add PromEx to Your Application

In application.ex, add MyApp.PromEx to your children array:

children = [
  MyApp.Repo,
  MyApp.Endpoint,
  MyApp.PromEx
]


4. Plug PromEx into Endpoint

In endpoint.ex, add the PromEx plug before the Plug.RequestId line:

plug PromEx.Plug, prom_ex_module: MyApp.PromEx
plug Plug.RequestId
plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint]

This ensures metrics are collected for each HTTP request.


5. Configure Prometheus Remote Write

Create a prometheus.yml file:

global:
  scrape_interval: 15s

scrape_configs:
  - job_name: 'elixir-app'
    static_configs:
      - targets: ['172.17.0.1:4000']
    metrics_path: /metrics
    scrape_interval: 10s

remote_write:
  - url: "https://api.openobserve.ai/api/32v/prometheus/api/v1/write"
    basic_auth:
      username: "username_from_remote_write"
      password: "password_from_remote_write"
    queue_config:
      max_samples_per_send: 10000

Notes:

  • Replace username_from_remote_write and password_from_remote_write with your OpenObserver credentials.

  • Copy the remote_write URL exactly from your OpenObserver dashboard.


6. Run Prometheus with Docker

Create a docker-compose.yml:

version: '3.8'

services:
  prometheus:
    image: prom/prometheus:latest
    container_name: prometheus
    restart: unless-stopped
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
    extra_hosts:
      - "host.docker.internal:host-gateway"

Then start Prometheus:

docker-compose up -d


7. Ensure Endpoint is Accessible

In dev.exs, configure your Phoenix endpoint to allow access:

config :myApp, MyAppWeb.Endpoint,
  http: [ip: {0, 0, 0, 0}, port: 4000]

This binds the server to all interfaces, allowing Prometheus to scrape metrics from Docker.


8. Check Observability System

  • Start your Phoenix app: mix phx.server.

  • Start Prometheus: docker-compose up.

  • Go to OpenObserver and verify that metrics appear.

  • If you don’t see metrics, check Docker logs for Prometheus and debug networking or authentication issues.


This setup gives you real-time application metrics exported from Elixir to OpenObserver using PromEx and Prometheus. Combined with tracing, this forms the second pillar of your observability stack.

Perfect! Let’s structure your logs section for your article so it’s clear, step-by-step, and easy to follow.


Setting Up Logs with LoggerJSON and Promtail/Filebeat

The third pillar of observability is logs. Unlike tracing and metrics, logs aren’t fully supported in OpenTelemetry yet for Elixir, and setting up an OTEL collector can be complex. Thanks to smaller_infinity, we can use Promtail (or Filebeat) to ship logs to an observability system like OpenObserver.


1. Install Logger File Backend

Add the file backend for structured logging:

{:logger_file_backend, "~> 0.0.10"},
{:logger_json, "~> 7.0"}


2. Configure Logger in config.exs

# Use both console and file backends
config :logger,
  backends: [:console, {LoggerFileBackend, :file}]

# Console backend with JSON formatting
config :logger, :console,
  formatter: {LoggerJSON.Formatters.Elastic,
              metadata: [:request_id, :user_id, :status, :trace_id, :span_id]}

# File backend with JSON formatting
config :logger, :file,
  path: "log/error_success.log", # Path where logs will be saved
  level: :info,
  formatter: {LoggerJSON.Formatters.Elastic,
              metadata: [:request_id, :user_id, :status, :trace_id, :span_id]}

This ensures that all logs are saved in JSON format, which is required by Promtail or Filebeat.


3. Promtail Setup

Create a promtail-config.yml:

server:
  http_listen_port: 9080
  grpc_listen_port: 0

positions:
  filename: /tmp/positions.yaml

clients:
  - url: "observability_endpoint_url"
    basic_auth:
      username: "username"
      password: "password"

scrape_configs:
  - job_name: myAPP_logs
    static_configs:
      - targets: ["localhost"]
        labels:
          job: elixir
          __path__: /var/log/elixir/error_success.log # Match the mounted volume

Then run Promtail in Docker:

promtail:
  image: grafana/promtail:latest
  container_name: promtail
  volumes:
    - /home/systemname/Myapp/log:/var/log/elixir:ro
    - ./promtail-config.yml:/etc/promtail/config.yml
  command: -config.file=/etc/promtail/config.yml
  ports:
    - "9080:9080"


4. Alternative: Filebeat Setup

If you don’t have JSON logs, Filebeat is a better option. Create a filebeat.yml:

setup.ilm.enabled: false
setup.template.enabled: false

filebeat.inputs:
- type: log
  enabled: true
  paths:
    - /var/log/elixir/error_success.log # Mounted volume path

output.elasticsearch:
  hosts: ["https://api.openobserve.ai:443"]
  timeout: 10
  path: "/api/32vB2Af8Wk5vQWvwBcFfMANde8n/" # Your organization ID
  index: "default"
  username: "username"
  password: "password"

processors:
- dissect:
    tokenizer: "%{timestamp} [%{level}] %{message}"
    field: "message"
    target_prefix: "elixir"

Run Filebeat in Docker:

filebeat:
  image: docker.elastic.co/beats/filebeat:8.11.0
  container_name: filebeat
  user: root
  volumes:
    - /home/systemname/appname/log:/var/log/elixir:ro
    - ./filebeat.yml:/usr/share/filebeat/filebeat.yml:ro
  command: filebeat -e -strict.perms=false
  ports:
    - "5066:5066"


5. Verify Logs

  • Start your Phoenix app: mix phx.server.

  • Start Promtail/Filebeat: docker-compose up.

  • Refresh your OpenObserver dashboard to see the logs.

  • Check Docker logs if logs are not appearing.


:white_check_mark: This completes the full observability stack in Elixir:

  1. Tracing: OpenTelemetry → OpenObserver

  2. Metrics: PromEx + Prometheus → OpenObserver

  3. Logs: LoggerJSON → Promtail/Filebeat → OpenObserver

Now you have a scalable, maintainable observability system.

Where Next?

Popular in Guides/Tuts Top

AstonJ
This blog post hit my timeline earlier, and I’ve also been learning about some fantastic Elixir related tips via @pragdave’s new online c...
New
benwilson512
Correct if me I’m wrong, as best I can tell there aren’t any reasons to use mix run --no-halt in production vs releases. The marginal val...
New
cheerfulstoic
I spent quite a bit of time trying to find a good configuration to build / test my Elixir app in CircleCI and then push an image to Docke...
New
AstonJ
..or as and when you can think of one :icon_cool: This thread my also be of interest: What took you way too long to figure out? :003:
New
bentanweihao
I wrote a thing: http://engineering.pivotal.io/post/how-to-set-up-an-elixir-cluster-on-amazon-ec2/ Hope this is helpful!
New
hauks96
Hello everyone, I created a deployment tutorial for Phoenix applications with Kubernetes (microk8s) a few months back with the goal of s...
New
danschultzer
I wrote this blog post based on our experiences setting up continuous delivery for our first production umbrella Phoenix app. Coming from...
New
AstonJ
This was originally posted on my blog, but since my I’ve taken it down (I hadn’t posted anything on it for a while and since it was runni...
New
Eiji
Hey, today I give amnesia library a try and found a few problems. I would like describe how to setup it properly and solve problems which...
New
sergio
Hey there, we’re going to walk through deploying a Phoenix app to a DigitalOcean droplet, manually - no tools no nothing. Just straight u...
New

Other popular topics Top

sen
Hi All, I set a environment variables in dev.exs , like below code. when i start server, how can i set the ${enable} value? thanks. d...
New
danschultzer
None of the current solutions worked well for me, so I went ahead and built a user management system from scratch. This project took far...
548 29603 241
New
baxterw3b
Hi guys, i’m new in the Elixir world, and i have to say, that i love it! i’m having some problem to understand anonymous functions with ...
New
greenz1
I have a phoenix application from which a user can download multiple(5-6) files of size 1MB. I couldn’t find anything related to sending ...
New
jononomo
I am trying to figure out how Mix knows whether the environment is test, dev, or prod – where is this set? Thanks.
New
belgoros
I’m not a pro in using Regex and can’t figure out why the following behaviour happens, especially if we take into account the difference ...
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers’ Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
boundedvariable
I am going through the kafka architecture. All the features what the kafka is providing are already in Erlang. I would like hear your opi...
New
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 records...
New

Latest on Elixir Forum

Elixir Forum

We're in Beta

About us Mission Statement