Resource Detectors in OpenTelemetry Elixir/Erlang
Resource detectors automatically detect and collect information about the environment in which your Elixir/Erlang application is running. This information is attached to telemetry data (traces, metrics, and logs) to provide additional context for monitoring and debugging.
OpenTelemetry Erlang/Elixir provides several built-in resource detectors, and you can create custom detectors to gather application-specific information.
By default, OpenTelemetry uses the OS environment variable OTEL_RESOURCE_ATTRIBUTES
and the opentelemetry
OTP application environment variable resource
:
# config/runtime.exs
config :opentelemetry,
resource: %{
service: %{
name: "user-api",
version: "1.0.0"
},
deployment: %{
environment: "production"
}
}
Configuration
Environment Variables
You can configure resource detectors through application configuration or environment variables:
# config/runtime.exs
config :opentelemetry,
resource_detectors: [:otel_resource_env_var, :otel_resource_app_env]
Or using environment variables:
export OTEL_RESOURCE_DETECTORS="otel_resource_env_var,otel_resource_app_env"
export OTEL_RESOURCE_ATTRIBUTES="service.name=user-api,service.version=1.2.0,deployment.environment=production"
export OTEL_SERVICE_NAME="user-api"
Timeout Configuration
All resource detectors are protected with a timeout, in milliseconds, after which they return an empty value. The default is 5000 milliseconds:
# config/runtime.exs
config :opentelemetry, otel_resource_detector_timeout: 3000
Or via environment variable:
export OTEL_RESOURCE_DETECTOR_TIMEOUT=3000
Built-in Detectors
:otel_resource_env_var
Detects resource attributes from the OTEL_RESOURCE_ATTRIBUTES
environment variable:
export OTEL_RESOURCE_ATTRIBUTES="service.name=user-api,service.version=1.2.0,deployment.environment=production"
:otel_resource_app_env
Uses the resource
configuration from the opentelemetry
OTP application environment:
# config/runtime.exs
config :opentelemetry,
resource: %{
service: %{
name: "user-api",
version: "1.2.0",
instance_id: "instance-1"
},
host: %{
name: System.get_env("HOSTNAME", "localhost")
},
deployment: %{
environment: "production"
}
}
Resource attributes in the resource
OTP application environment variable are flattened and combined with .
, so %{deployment: %{environment: "development"}}
becomes "deployment.environment" => "development"
.
Custom Resource Detectors
Custom resource detectors can be created by implementing the otel_resource_detector
behaviour which contains a single callback get_resource/1
that returns an otel_resource
.
Basic Detector
defmodule MyApp.CustomResourceDetector do
@moduledoc """
Custom resource detector that adds application-specific metadata.
"""
@behaviour :otel_resource_detector
def get_resource(_config) do
attributes = %{
"service.name" => Application.get_env(:my_app, :service_name, "my-app"),
"service.version" => Application.spec(:my_app, :vsn) |> to_string(),
"service.namespace" => "ecommerce",
"deployment.environment" => System.get_env("MIX_ENV", "development"),
"host.name" => System.get_env("HOSTNAME", "localhost"),
"process.pid" => System.get_env("HOSTNAME") |> String.to_integer() |> to_string()
}
:otel_resource.create(attributes, [])
end
end
Advanced Detector
defmodule MyApp.MetadataResourceDetector do
@behaviour :otel_resource_detector
def get_resource(_config) do
attributes =
%{}
|> maybe_add_metadata_from_file("/etc/app/metadata.properties")
|> maybe_add_system_attributes()
:otel_resource.create(attributes, [])
end
defp maybe_add_metadata_from_file(attributes, file_path) do
try do
file_path
|> File.read!()
|> String.split("\n", trim: true)
|> Enum.reduce(attributes, fn line, acc ->
case String.split(line, "=", parts: 2) do
[key, value] when key != "" -> Map.put(acc, String.trim(key), String.trim(value))
_ -> acc
end
end)
rescue
File.Error -> attributes
end
end
defp maybe_add_system_attributes(attributes) do
Map.merge(attributes, %{
"host.name" => System.get_env("HOSTNAME", "unknown"),
"process.runtime.name" => "beam",
"process.runtime.version" => System.version()
})
end
end
Cloud Detector
defmodule MyApp.CloudResourceDetector do
@behaviour :otel_resource_detector
def get_resource(_config) do
attributes =
%{}
|> maybe_add_aws_metadata()
|> maybe_add_kubernetes_metadata()
:otel_resource.create(attributes, [])
end
defp maybe_add_aws_metadata(attributes) do
case System.get_env("AWS_REGION") do
nil -> attributes
region ->
Map.merge(attributes, %{
"cloud.provider" => "aws",
"cloud.region" => region
})
end
end
defp maybe_add_kubernetes_metadata(attributes) do
case System.get_env("KUBERNETES_SERVICE_HOST") do
nil -> attributes
_host ->
Map.merge(attributes, %{
"k8s.namespace.name" => System.get_env("POD_NAMESPACE"),
"k8s.pod.name" => System.get_env("POD_NAME")
})
end
end
end
Registration
Add your custom detectors to the configuration:
# config/runtime.exs
config :opentelemetry,
resource_detectors: [
:otel_resource_env_var,
:otel_resource_app_env,
MyApp.CustomResourceDetector,
MyApp.MetadataResourceDetector,
MyApp.CloudResourceDetector
]
Validation and Debugging
defmodule MyApp.ResourceDebugger do
require Logger
def print_resource_attributes do
resource = :otel_resource_detector.get_resource()
attributes = :otel_resource.attributes(resource)
Logger.info("Resource attributes:")
Enum.each(attributes, fn {key, value} ->
Logger.info(" #{key} = #{inspect(value)}")
end)
end
def validate_service_name do
resource = :otel_resource_detector.get_resource()
attributes = :otel_resource.attributes(resource)
case Map.get(attributes, "service.name") do
nil -> raise "Service name is required"
"" -> raise "Service name cannot be empty"
_name -> :ok
end
end
end
Common Attributes
Service Attributes
%{
"service.name" => "user-api", # Required
"service.version" => "1.2.3", # Recommended
"service.namespace" => "ecommerce", # Optional
"service.instance.id" => "instance-12345" # Optional, must be unique
}
Deployment & Host
%{
"deployment.environment" => "production", # staging, development, etc.
"host.name" => "web-server-01",
"process.runtime.name" => "beam",
"process.runtime.version" => "14.2.2"
}
OpenTelemetry APM
Uptrace is a OpenTelemetry backend that supports distributed tracing, metrics, and logs. You can use it to monitor applications and troubleshoot issues.
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.