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_ATTRIBUTES and OTEL_SERVICE_NAME
  • host - detects host information
  • os - detects operating system details
  • process - detects Rust process information

Basic usage with all built-in detectors:

rust
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:

rust
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:

rust
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

rust
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:

bash
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:

rust
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:

rust
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:

rust
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

rust
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(())
}

What's next?