Resource Detectors in OpenTelemetry Rust
Resource detectors automatically detect and collect information about the environment in which your application is running. This information is attached to telemetry data (traces, metrics, and logs) to provide additional context for monitoring and debugging.
The Rust SDK detects resources from a variety of sources, and by default will use all available resource detectors: environment variables (OTEL_RESOURCE_ATTRIBUTES, OTEL_SERVICE_NAME), host, operating system, and process information.
Built-in detectors
The Rust SDK includes several built-in resource detectors:
- env - reads
OTEL_RESOURCE_ATTRIBUTESandOTEL_SERVICE_NAME - host - detects host information
- os - detects operating system details
- process - detects Rust process information
Basic usage with all built-in detectors:
use opentelemetry::{global, KeyValue};
use opentelemetry_sdk::{trace, Resource};
use opentelemetry_resource_detectors::{
HostResourceDetector, OsResourceDetector, ProcessResourceDetector,
};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
// Create resource with multiple detectors
let resource = Resource::builder()
.with_detector(Box::new(OsResourceDetector))
.with_detector(Box::new(HostResourceDetector::default()))
.with_detector(Box::new(ProcessResourceDetector))
.with_attributes([
KeyValue::new("service.name", "my-rust-service"),
KeyValue::new("service.version", env!("CARGO_PKG_VERSION")),
])
.build();
let tracer_provider = opentelemetry_otlp::new_pipeline()
.tracing()
.with_exporter(
opentelemetry_otlp::new_exporter()
.tonic()
.with_endpoint("https://api.uptrace.dev:4317")
)
.with_trace_config(
trace::Config::default()
.with_resource(resource)
)
.install_batch(opentelemetry_sdk::runtime::Tokio)?;
global::set_tracer_provider(tracer_provider);
// Your application code here
run_application().await?;
global::shutdown_tracer_provider();
Ok(())
}
Container detection
For containerized Rust applications, you can detect container information:
use std::fs;
use opentelemetry::{KeyValue, Value};
use opentelemetry_sdk::Resource;
pub struct ContainerResourceDetector;
impl ContainerResourceDetector {
pub fn new() -> Self {
Self
}
pub fn detect(&self) -> Resource {
let mut attributes = Vec::new();
// Docker container ID from cgroup
if let Ok(cgroup_content) = fs::read_to_string("/proc/self/cgroup") {
for line in cgroup_content.lines() {
if line.contains("/docker/") {
if let Some(container_id) = line.split('/').last() {
if container_id.len() >= 64 {
// Use short container ID (first 12 chars)
attributes.push(KeyValue::new("container.id", container_id[..12].to_string()));
break;
}
}
}
}
}
// Container image from environment
if let Ok(image) = std::env::var("DOCKER_IMAGE") {
attributes.push(KeyValue::new("container.image.name", image));
}
// Container name from environment
if let Ok(name) = std::env::var("CONTAINER_NAME") {
attributes.push(KeyValue::new("container.name", name));
}
Resource::new(attributes)
}
}
// Usage
let resource = Resource::builder()
.with_detector(Box::new(ContainerResourceDetector::new()))
.build();
Kubernetes detection
For Kubernetes deployments:
use std::fs;
use opentelemetry::{KeyValue, Value};
use opentelemetry_sdk::Resource;
pub struct K8sResourceDetector;
impl K8sResourceDetector {
pub fn new() -> Self {
Self
}
pub fn detect(&self) -> Resource {
let mut attributes = Vec::new();
// Check if running in Kubernetes
if std::env::var("KUBERNETES_SERVICE_HOST").is_err() {
return Resource::new(attributes);
}
// Namespace
if let Ok(namespace) = fs::read_to_string("/var/run/secrets/kubernetes.io/serviceaccount/namespace") {
attributes.push(KeyValue::new("k8s.namespace.name", namespace.trim().to_string()));
}
// Pod name from hostname or environment
if let Ok(pod_name) = std::env::var("HOSTNAME") {
attributes.push(KeyValue::new("k8s.pod.name", pod_name));
} else if let Ok(pod_name) = std::env::var("POD_NAME") {
attributes.push(KeyValue::new("k8s.pod.name", pod_name));
}
// Pod UID
if let Ok(pod_uid) = std::env::var("POD_UID") {
attributes.push(KeyValue::new("k8s.pod.uid", pod_uid));
}
// Node name
if let Ok(node_name) = std::env::var("NODE_NAME") {
attributes.push(KeyValue::new("k8s.node.name", node_name));
}
// Deployment name (often derived from pod name)
if let Ok(pod_name) = std::env::var("HOSTNAME") {
if let Some(deployment_name) = pod_name.rsplitn(3, '-').nth(2) {
attributes.push(KeyValue::new("k8s.deployment.name", deployment_name.to_string()));
}
}
// Cluster name
if let Ok(cluster_name) = std::env::var("CLUSTER_NAME") {
attributes.push(KeyValue::new("k8s.cluster.name", cluster_name));
}
Resource::new(attributes)
}
}
// Usage
let resource = Resource::builder()
.with_detector(Box::new(K8sResourceDetector::new()))
.build();
Cloud provider detection
AWS detection
use opentelemetry::{KeyValue, Value};
use opentelemetry_sdk::Resource;
pub struct AwsResourceDetector;
impl AwsResourceDetector {
pub fn new() -> Self {
Self
}
pub fn detect(&self) -> Resource {
let mut attributes = Vec::new();
// Basic AWS detection
if std::env::var("AWS_REGION").is_ok() {
attributes.push(KeyValue::new("cloud.provider", "aws"));
if let Ok(region) = std::env::var("AWS_REGION") {
attributes.push(KeyValue::new("cloud.region", region));
}
}
// EC2 instance metadata
if self.is_ec2_instance() {
attributes.push(KeyValue::new("cloud.platform", "aws_ec2"));
// Try to get instance ID from environment
if let Ok(instance_id) = std::env::var("EC2_INSTANCE_ID") {
attributes.push(KeyValue::new("cloud.instance.id", instance_id));
}
}
// ECS detection
if std::env::var("AWS_EXECUTION_ENV").is_ok() || std::env::var("ECS_CONTAINER_METADATA_URI_V4").is_ok() {
attributes.push(KeyValue::new("cloud.platform", "aws_ecs"));
if let Ok(cluster) = std::env::var("ECS_CLUSTER") {
attributes.push(KeyValue::new("aws.ecs.cluster.name", cluster));
}
}
// Lambda detection
if std::env::var("AWS_LAMBDA_FUNCTION_NAME").is_ok() {
attributes.push(KeyValue::new("cloud.platform", "aws_lambda"));
if let Ok(function_name) = std::env::var("AWS_LAMBDA_FUNCTION_NAME") {
attributes.push(KeyValue::new("faas.name", function_name));
}
if let Ok(function_version) = std::env::var("AWS_LAMBDA_FUNCTION_VERSION") {
attributes.push(KeyValue::new("faas.version", function_version));
}
}
Resource::new(attributes)
}
fn is_ec2_instance(&self) -> bool {
// Simple check for EC2 instance
std::path::Path::new("/sys/hypervisor/uuid").exists() ||
std::env::var("EC2_INSTANCE_ID").is_ok()
}
}
// Usage
let resource = Resource::builder()
.with_detector(Box::new(AwsResourceDetector::new()))
.build();
Custom resource via environment
The simplest way to add custom resources is via the OTEL_RESOURCE_ATTRIBUTES environment variable:
export OTEL_RESOURCE_ATTRIBUTES="service.name=my-rust-app,service.namespace=backend,service.version=2.1.0,deployment.environment=production"
Custom resource in code
Custom resources can also be configured directly in your code:
use opentelemetry::{global, KeyValue};
use opentelemetry_sdk::{trace, Resource};
// Create custom resource
let custom_resource = Resource::new(vec![
KeyValue::new("service.namespace", "backend"),
KeyValue::new("service.name", "user-service"),
KeyValue::new("service.instance.id", gethostname::gethostname().to_string_lossy().to_string()),
KeyValue::new("service.version", env!("CARGO_PKG_VERSION")),
KeyValue::new("deployment.environment", std::env::var("RUST_ENV").unwrap_or_else(|_| "development".to_string())),
]);
// The SDK will automatically merge with default detected resources
let tracer_provider = opentelemetry_otlp::new_pipeline()
.tracing()
.with_trace_config(
trace::Config::default()
.with_resource(custom_resource)
)
.install_batch(opentelemetry_sdk::runtime::Tokio)?;
Rust-specific resource detection
Create a helper to detect Rust-specific information:
use opentelemetry::{KeyValue, Value};
use opentelemetry_sdk::Resource;
pub struct RustResourceDetector;
impl RustResourceDetector {
pub fn new() -> Self {
Self
}
pub fn detect(&self) -> Resource {
let attributes = vec![
KeyValue::new("process.runtime.name", "rust"),
KeyValue::new("process.runtime.version", env!("RUSTC_VERSION")),
KeyValue::new("process.runtime.description", format!("{} {}", env!("RUSTC_VERSION"), std::env::consts::ARCH)),
KeyValue::new("process.pid", std::process::id().to_string()),
KeyValue::new("rust.package.name", env!("CARGO_PKG_NAME")),
KeyValue::new("rust.package.version", env!("CARGO_PKG_VERSION")),
KeyValue::new("rust.target.arch", std::env::consts::ARCH),
KeyValue::new("rust.target.os", std::env::consts::OS),
];
Resource::new(attributes)
}
}
pub struct FrameworkResourceDetector;
impl FrameworkResourceDetector {
pub fn new() -> Self {
Self
}
pub fn detect(&self) -> Resource {
let mut attributes = Vec::new();
// Tokio runtime detection
if tokio::runtime::Handle::try_current().is_ok() {
attributes.push(KeyValue::new("rust.runtime", "tokio"));
}
Resource::new(attributes)
}
}
// Complete resource detection
let resource = Resource::builder()
.with_detector(Box::new(RustResourceDetector::new()))
.with_detector(Box::new(FrameworkResourceDetector::new()))
.build();
Production considerations
Performance
Resource detection runs during application initialization. For production Rust applications:
- Resource detection is fast and doesn't impact runtime performance
- Detection happens once at startup
- Failed detectors don't prevent application startup
Error handling
Resource detectors should be resilient to failures:
use opentelemetry_sdk::Resource;
use tracing::{warn, debug};
pub fn safe_resource_detection() -> Resource {
let mut builder = Resource::builder();
// Safe host detection
builder = builder.with_detector(Box::new(HostResourceDetector::default()));
debug!("Added host resource detector");
// Safe container detection with error handling
if std::path::Path::new("/.dockerenv").exists() {
builder = builder.with_detector(Box::new(ContainerResourceDetector::new()));
debug!("Added container resource detector");
}
// Safe cloud detection
if std::env::var("AWS_REGION").is_ok() {
builder = builder.with_detector(Box::new(AwsResourceDetector::new()));
debug!("Added AWS resource detector");
}
builder.build()
}
Example production configuration
use opentelemetry_sdk::Resource;
use opentelemetry::{global, KeyValue};
// Configure based on environment
fn create_production_resource() -> Resource {
let env = std::env::var("RUST_ENV").unwrap_or_else(|_| "development".to_string());
let mut builder = Resource::builder();
match env.as_str() {
"production" => {
// Explicit configuration for production
builder = builder.with_attributes([
KeyValue::new("service.name", std::env::var("SERVICE_NAME").unwrap_or_else(|_| env!("CARGO_PKG_NAME").to_string())),
KeyValue::new("service.version", std::env::var("APP_VERSION").unwrap_or_else(|_| env!("CARGO_PKG_VERSION").to_string())),
KeyValue::new("service.namespace", std::env::var("SERVICE_NAMESPACE").unwrap_or_else(|_| "backend".to_string())),
KeyValue::new("deployment.environment", env),
]);
}
"development" => {
// Comprehensive detection for development
builder = builder
.with_detector(Box::new(RustResourceDetector::new()))
.with_detector(Box::new(FrameworkResourceDetector::new()))
.with_attributes([
KeyValue::new("deployment.environment", env),
]);
}
_ => {
// Default configuration
builder = builder.with_attributes([
KeyValue::new("service.name", env!("CARGO_PKG_NAME")),
KeyValue::new("deployment.environment", env),
]);
}
}
// Always try to add runtime detection
builder = builder
.with_detector(Box::new(HostResourceDetector::default()))
.with_detector(Box::new(OsResourceDetector))
.with_detector(Box::new(ProcessResourceDetector));
// Conditional detection based on environment
if std::env::var("KUBERNETES_SERVICE_HOST").is_ok() {
builder = builder.with_detector(Box::new(K8sResourceDetector::new()));
}
if std::path::Path::new("/.dockerenv").exists() {
builder = builder.with_detector(Box::new(ContainerResourceDetector::new()));
}
if std::env::var("AWS_REGION").is_ok() {
builder = builder.with_detector(Box::new(AwsResourceDetector::new()));
}
builder.build()
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
let resource = create_production_resource();
let tracer_provider = opentelemetry_otlp::new_pipeline()
.tracing()
.with_trace_config(
trace::Config::default()
.with_resource(resource)
)
.install_batch(opentelemetry_sdk::runtime::Tokio)?;
global::set_tracer_provider(tracer_provider);
// Your application code
run_application().await?;
global::shutdown_tracer_provider();
Ok(())
}