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"
}