diff --git a/opentelemetry-resource-detectors/CHANGELOG.md b/opentelemetry-resource-detectors/CHANGELOG.md index 1b4e1c90..c4e32289 100644 --- a/opentelemetry-resource-detectors/CHANGELOG.md +++ b/opentelemetry-resource-detectors/CHANGELOG.md @@ -2,6 +2,10 @@ ## vNext +### Added + +- `LambdaResourceDetector` has been added to the crate to detect AWS Lambda attributes. To enable it in your code, use the feature `aws-lambda`. + ## v0.3.0 ### Changed diff --git a/opentelemetry-resource-detectors/Cargo.toml b/opentelemetry-resource-detectors/Cargo.toml index afeb2c6f..3fa2da0e 100644 --- a/opentelemetry-resource-detectors/Cargo.toml +++ b/opentelemetry-resource-detectors/Cargo.toml @@ -16,3 +16,9 @@ rust-version = "1.65" opentelemetry = { workspace = true } opentelemetry_sdk = { workspace = true } opentelemetry-semantic-conventions = { workspace = true } + +[dev-dependencies] +sealed_test = "1.1.0" + +[features] +aws-lambda = [] diff --git a/opentelemetry-resource-detectors/src/aws.rs b/opentelemetry-resource-detectors/src/aws.rs new file mode 100644 index 00000000..5c68ef66 --- /dev/null +++ b/opentelemetry-resource-detectors/src/aws.rs @@ -0,0 +1,5 @@ +#[cfg(feature = "aws-lambda")] +mod lambda; +#[cfg(feature = "aws-lambda")] +pub use lambda::LambdaResourceDetector; + diff --git a/opentelemetry-resource-detectors/src/aws/lambda.rs b/opentelemetry-resource-detectors/src/aws/lambda.rs new file mode 100644 index 00000000..4af990b5 --- /dev/null +++ b/opentelemetry-resource-detectors/src/aws/lambda.rs @@ -0,0 +1,96 @@ +use opentelemetry::KeyValue; +use opentelemetry_sdk::resource::ResourceDetector; +use opentelemetry_sdk::Resource; +use opentelemetry_semantic_conventions as semconv; +use std::env; +use std::time::Duration; + +// For a complete list of reserved environment variables in Lambda, see: +// https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html +const AWS_LAMBDA_FUNCTION_NAME_ENV_VAR: &str = "AWS_LAMBDA_FUNCTION_NAME"; +const AWS_REGION_ENV_VAR: &str = "AWS_REGION"; +const AWS_LAMBDA_FUNCTION_VERSION_ENV_VAR: &str = "AWS_LAMBDA_FUNCTION_VERSION"; +const AWS_LAMBDA_LOG_STREAM_NAME_ENV_VAR: &str = "AWS_LAMBDA_LOG_STREAM_NAME"; +const AWS_LAMBDA_MEMORY_LIMIT_ENV_VAR: &str = "AWS_LAMBDA_FUNCTION_MEMORY_SIZE"; + +/// Resource detector that collects resource information from AWS Lambda environment. +pub struct LambdaResourceDetector; + +impl ResourceDetector for LambdaResourceDetector { + fn detect(&self, _: Duration) -> Resource { + let lambda_name = env::var(AWS_LAMBDA_FUNCTION_NAME_ENV_VAR).unwrap_or_default(); + // If no lambda name is provided, it means that + // we're not on a Lambda environment, so we return empty resource. + if lambda_name.is_empty() { + return Resource::empty(); + } + + let aws_region = env::var(AWS_REGION_ENV_VAR).unwrap_or_default(); + let function_version = env::var(AWS_LAMBDA_FUNCTION_VERSION_ENV_VAR).unwrap_or_default(); + let function_memory_limit = env::var(AWS_LAMBDA_MEMORY_LIMIT_ENV_VAR).unwrap_or_default(); + // Instance attributes corresponds to the log stream name for AWS Lambda; + // See the FaaS resource specification for more details. + let instance = env::var(AWS_LAMBDA_LOG_STREAM_NAME_ENV_VAR).unwrap_or_default(); + + let attributes = [ + KeyValue::new(semconv::resource::CLOUD_PROVIDER, "aws"), + KeyValue::new(semconv::resource::CLOUD_REGION, aws_region), + KeyValue::new(semconv::resource::FAAS_INSTANCE, instance), + KeyValue::new(semconv::resource::FAAS_NAME, lambda_name), + KeyValue::new(semconv::resource::FAAS_VERSION, function_version), + KeyValue::new(semconv::resource::FAAS_MAX_MEMORY, function_memory_limit), + ]; + + Resource::new(attributes) + } +} + +#[cfg(test)] +#[cfg(feature = "aws-lambda")] +mod tests { + use super::*; + use sealed_test::prelude::*; + use std::env::{remove_var, set_var}; + + #[sealed_test] + fn test_aws_lambda_detector() { + set_var(AWS_LAMBDA_FUNCTION_NAME_ENV_VAR, "my-lambda-function"); + set_var(AWS_REGION_ENV_VAR, "eu-west-3"); + set_var(AWS_LAMBDA_FUNCTION_VERSION_ENV_VAR, "$LATEST"); + set_var( + AWS_LAMBDA_LOG_STREAM_NAME_ENV_VAR, + "2023/01/01/[$LATEST]5d1edb9e525d486696cf01a3503487bc", + ); + set_var(AWS_LAMBDA_MEMORY_LIMIT_ENV_VAR, "128"); + + let expected = Resource::new([ + KeyValue::new(semconv::resource::CLOUD_PROVIDER, "aws"), + KeyValue::new(semconv::resource::CLOUD_REGION, "eu-west-3"), + KeyValue::new( + semconv::resource::FAAS_INSTANCE, + "2023/01/01/[$LATEST]5d1edb9e525d486696cf01a3503487bc", + ), + KeyValue::new(semconv::resource::FAAS_NAME, "my-lambda-function"), + KeyValue::new(semconv::resource::FAAS_VERSION, "$LATEST"), + KeyValue::new(semconv::resource::FAAS_MAX_MEMORY, "128"), + ]); + + let detector = LambdaResourceDetector {}; + let got = detector.detect(Duration::from_secs(0)); + + assert_eq!(expected, got); + + remove_var(AWS_LAMBDA_FUNCTION_NAME_ENV_VAR); + remove_var(AWS_REGION_ENV_VAR); + remove_var(AWS_LAMBDA_FUNCTION_VERSION_ENV_VAR); + remove_var(AWS_LAMBDA_LOG_STREAM_NAME_ENV_VAR); + remove_var(AWS_LAMBDA_MEMORY_LIMIT_ENV_VAR); + } + + #[sealed_test] + fn test_aws_lambda_detector_returns_empty_if_no_lambda_environment() { + let detector = LambdaResourceDetector {}; + let got = detector.detect(Duration::from_secs(0)); + assert_eq!(Resource::empty(), got); + } +} diff --git a/opentelemetry-resource-detectors/src/lib.rs b/opentelemetry-resource-detectors/src/lib.rs index 61837690..f291c978 100644 --- a/opentelemetry-resource-detectors/src/lib.rs +++ b/opentelemetry-resource-detectors/src/lib.rs @@ -6,10 +6,14 @@ //! - [`OsResourceDetector`] - detect OS from runtime. //! - [`ProcessResourceDetector`] - detect process information. //! - [`HostResourceDetector`] - detect unique host ID. +//! - [`LambdaResourceDetector`] - detect resource metadata from an AWS Lambda environment. +mod aws; mod host; mod os; mod process; +#[cfg(feature = "aws-lambda")] +pub use aws::LambdaResourceDetector; pub use host::HostResourceDetector; pub use os::OsResourceDetector; pub use process::ProcessResourceDetector;