awssdk-instrumentation 0.2.0

Out-of-the-box OpenTelemetry/X-Ray instrumentation for the AWS SDK for Rust, with first-class support for AWS Lambda
Documentation
//! EC2 resource detector (`env-ec2` feature).
//!
//! Queries the EC2 Instance Metadata Service v2 (IMDSv2) and returns an OTel
//! [`Resource`] with the following attributes:
//!
//! | OTel attribute              | IMDSv2 path                                  |
//! |-----------------------------|----------------------------------------------|
//! | `cloud.provider`            | hardcoded `"aws"`                            |
//! | `cloud.platform`            | hardcoded `"aws_ec2"`                        |
//! | `host.id`                   | `instance-id`                                |
//! | `host.type`                 | `instance-type`                              |
//! | `host.image.id`             | `ami-id`                                     |
//! | `cloud.availability_zone`   | `placement/availability-zone`                |
//! | `cloud.region`              | derived from AZ (strip trailing letter)      |
//! | `cloud.account.id`          | `identity-credentials/ec2/info` (JSON)       |
//!
//! Detection succeeds only when IMDSv2 is reachable and returns an instance ID.
//! If the IMDS call fails (e.g. not running on EC2), [`ec2_resource()`] returns
//! `None`.
//!
//! [`Resource`]: opentelemetry_sdk::Resource

// EC2 ResourceDetector — populates OTel Resource with instance ID,
// AMI, availability zone, etc.

use opentelemetry::KeyValue;
use opentelemetry_sdk::Resource;
use opentelemetry_semantic_conventions::attribute as semco;

use super::imds::ImdsClient;

/// Builds an OTel [`Resource`] by querying the EC2 Instance Metadata Service v2.
///
/// Returns `Some(Resource)` when IMDSv2 is reachable and returns an instance
/// ID, or `None` otherwise (e.g. when not running on EC2).
///
/// The region is derived from the availability zone by stripping the trailing
/// letter (e.g. `us-east-1a` → `us-east-1`). The account ID is read from the
/// IMDSv2 identity credentials endpoint.
///
/// See the [module-level documentation](self) for the full attribute table.
///
/// # Examples
///
/// ```no_run
/// use awssdk_instrumentation::env::ec2::ec2_resource;
///
/// // Returns None when not running on EC2 or when IMDSv2 is unreachable.
/// let resource = ec2_resource();
/// ```
///
/// [`Resource`]: opentelemetry_sdk::Resource
pub fn ec2_resource() -> Option<Resource> {
    let imds = ImdsClient::new()?;

    // Instance ID — required; bail if unavailable (not running on EC2)
    let instance_id = imds.get("instance-id")?;

    let az = imds.get("placement/availability-zone");

    let attribute_options = [
        Some(KeyValue::new(semco::CLOUD_PROVIDER, "aws")),
        Some(KeyValue::new(semco::CLOUD_PLATFORM, "aws_ec2")),
        Some(KeyValue::new(semco::HOST_ID, instance_id)),
        imds.get("instance-type")
            .map(|v| KeyValue::new(semco::HOST_TYPE, v)),
        imds.get("ami-id")
            .map(|v| KeyValue::new(semco::HOST_IMAGE_ID, v)),
        az.as_deref()
            .map(|v| KeyValue::new(semco::CLOUD_AVAILABILITY_ZONE, v.to_owned())),
        // Region — derived from AZ (e.g., us-east-1a -> us-east-1)
        az.as_deref()
            .and_then(|v| v.strip_suffix(|c: char| c.is_ascii_alphabetic()))
            .map(|v| KeyValue::new(semco::CLOUD_REGION, v.to_owned())),
        // Account ID — from the IAM identity document (JSON body, not a sub-path)
        imds.get_json::<Ec2IdentityCredentials>("identity-credentials/ec2/info")
            .and_then(|c| c.account_id)
            .map(|a| KeyValue::new(semco::CLOUD_ACCOUNT_ID, a)),
    ];
    Some(
        Resource::builder()
            .with_attributes(attribute_options.into_iter().flatten())
            .build(),
    )
}

/// Deserialization target for the IMDSv2 `identity-credentials/ec2/info` JSON response.
#[derive(serde::Deserialize)]
struct Ec2IdentityCredentials {
    #[serde(rename = "AccountId")]
    account_id: Option<String>,
}