Instrument applications with OpenTelemetry SDK and validate telemetry using Kopai. Use when setting up observability, adding tracing/logging/metrics, testing instrumentation, debugging missing telemetry data, or when traces/logs/metrics aren't appearing after setup. Also use when users say things like "my traces aren't showing up", "I don't see any data", or "how do I add observability to my app".
100
100%
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Passed
No known issues
| title | impact | tags |
|---|---|---|
| Erlang/Elixir Instrumentation | HIGH | lang, erlang, elixir, beam, traces, logs, metrics |
Impact: HIGH
Set up OpenTelemetry for Erlang/Elixir applications with traces, logs, and metrics.
SDK Status:
| Signal | Support | Notes |
|---|---|---|
| Traces | SDK | Mature, use opentelemetry_cowboy for auto-instrumentation |
| Logs | Direct HTTP | No official OTLP log exporter available |
| Metrics | Direct HTTP | No official OTLP metric exporter available |
Note: Replace X.X with the latest versions from hex.pm.
defp deps do
[
# OpenTelemetry SDK
{:opentelemetry_api, "~> X.X"},
{:opentelemetry, "~> X.X"},
{:opentelemetry_exporter, "~> X.X"},
{:opentelemetry_cowboy, "~> X.X"}, # Auto-instrumentation for Cowboy/Plug
# For direct HTTP/JSON (logs and metrics)
{:jason, "~> X.X"}
]
endconfig/config.exs:
import Config
config :opentelemetry,
span_processor: :batch,
traces_exporter: :otlp
config :opentelemetry_exporter,
otlp_protocol: :http_json, # Use JSON format
otlp_compression: :none # Required for some backendsconfig/runtime.exs:
import Config
otel_endpoint = System.get_env("OTEL_EXPORTER_OTLP_ENDPOINT", "http://localhost:4318")
service_name = System.get_env("OTEL_SERVICE_NAME", "my-service")
config :opentelemetry,
resource: [
service: [
name: service_name,
namespace: "my-namespace"
]
]
config :opentelemetry_exporter,
otlp_endpoint: otel_endpointEnvironment Variables:
| Variable | Description |
|---|---|
OTEL_EXPORTER_OTLP_ENDPOINT | OTLP endpoint (e.g., http://localhost:4318) |
OTEL_SERVICE_NAME | Service name shown in observability backend |
defmodule MyApp.Application do
use Application
def start(_type, _args) do
# Initialize auto-instrumentation for Cowboy
:opentelemetry_cowboy.setup()
children = [
{Plug.Cowboy, scheme: :http, plug: MyApp.Router, options: [port: 3001]}
]
Supervisor.start_link(children, strategy: :one_for_one)
end
end
defmodule MyApp.Router do
use Plug.Router
require OpenTelemetry.Tracer, as: Tracer
plug :match
plug :dispatch
get "/hello" do
Tracer.with_span "process_request" do
Tracer.set_attributes([
{"http.method", "GET"},
{"http.route", "/hello"},
{"custom.attribute", "value"}
])
# Your code here
end
send_resp(conn, 200, "Hello!")
end
endNo official OTLP log exporter exists for Elixir. Use direct HTTP/JSON:
defp send_log(message, endpoint, service_name) do
timestamp = System.system_time(:nanosecond)
body = %{
resourceLogs: [%{
resource: %{attributes: [
%{key: "service.name", value: %{stringValue: service_name}}
]},
scopeLogs: [%{
scope: %{name: service_name},
logRecords: [%{
timeUnixNano: to_string(timestamp),
severityText: "INFO",
severityNumber: 9,
body: %{stringValue: message}
}]
}]
}]
}
send_otlp_request("#{endpoint}/v1/logs", body)
endNo official OTLP metric exporter exists for Elixir. Use direct HTTP/JSON:
defp send_metric(name, value, endpoint, service_name, attributes) do
timestamp = System.system_time(:nanosecond)
attrs = Enum.map(attributes, fn {k, v} ->
%{key: k, value: %{stringValue: v}}
end)
body = %{
resourceMetrics: [%{
resource: %{attributes: [
%{key: "service.name", value: %{stringValue: service_name}}
]},
scopeMetrics: [%{
scope: %{name: service_name},
metrics: [%{
name: name, unit: "1",
sum: %{
dataPoints: [%{
asInt: to_string(value),
timeUnixNano: to_string(timestamp),
attributes: attrs
}],
aggregationTemporality: 2, isMonotonic: true
}
}]
}]
}]
}
send_otlp_request("#{endpoint}/v1/metrics", body)
enddefp send_otlp_request(url, body) do
url_charlist = String.to_charlist(url)
json_body = Jason.encode!(body)
case :httpc.request(
:post,
{url_charlist, [{~c"content-type", ~c"application/json"}], ~c"application/json", json_body},
[{:timeout, 5000}],
[]
) do
{:ok, _response} -> :ok
{:error, reason} ->
IO.puts("OTLP request to #{url} failed: #{inspect(reason)}")
{:error, reason}
end
rescue
error ->
IO.puts("OTLP request to #{url} raised: #{inspect(error)}")
{:error, error}
endAuto-instrumentation: Call :opentelemetry_cowboy.setup() to enable automatic HTTP span creation.
HTTP JSON Protocol: Use otlp_protocol: :http_json for JSON format export.
Compression: Set otlp_compression: :none for compatibility with some backends.
Charlist Syntax (Elixir 1.18+): Use ~c"" sigil instead of single quotes for charlists.
Logs/Metrics: Use direct HTTP/JSON until official OTLP exporters are available.
rules