opentelemetry_aws/detector/
lambda.rs

1use opentelemetry::{Array, KeyValue, StringValue, Value};
2use opentelemetry_sdk::resource::ResourceDetector;
3use opentelemetry_sdk::Resource;
4use opentelemetry_semantic_conventions as semconv;
5use std::env;
6
7// For a complete list of reserved environment variables in Lambda, see:
8// https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html
9const AWS_LAMBDA_FUNCTION_NAME_ENV_VAR: &str = "AWS_LAMBDA_FUNCTION_NAME";
10const AWS_REGION_ENV_VAR: &str = "AWS_REGION";
11const AWS_LAMBDA_FUNCTION_VERSION_ENV_VAR: &str = "AWS_LAMBDA_FUNCTION_VERSION";
12const AWS_LAMBDA_LOG_STREAM_NAME_ENV_VAR: &str = "AWS_LAMBDA_LOG_STREAM_NAME";
13const AWS_LAMBDA_MEMORY_LIMIT_ENV_VAR: &str = "AWS_LAMBDA_FUNCTION_MEMORY_SIZE";
14const AWS_LAMBDA_LOG_GROUP_NAME_ENV_VAR: &str = "AWS_LAMBDA_LOG_GROUP_NAME";
15
16/// Resource detector that collects resource information from AWS Lambda environment.
17pub struct LambdaResourceDetector;
18
19impl ResourceDetector for LambdaResourceDetector {
20    fn detect(&self) -> Resource {
21        let lambda_name = env::var(AWS_LAMBDA_FUNCTION_NAME_ENV_VAR).unwrap_or_default();
22        // If no lambda name is provided, it means that
23        // we're not on a Lambda environment, so we return empty resource.
24        if lambda_name.is_empty() {
25            return Resource::builder_empty().build();
26        }
27
28        let aws_region = env::var(AWS_REGION_ENV_VAR).unwrap_or_default();
29        let function_version = env::var(AWS_LAMBDA_FUNCTION_VERSION_ENV_VAR).unwrap_or_default();
30        // Convert memory limit from MB (string) to Bytes (int) as required by semantic conventions.
31        let function_memory_limit = env::var(AWS_LAMBDA_MEMORY_LIMIT_ENV_VAR)
32            .map(|s| s.parse::<i64>().unwrap_or_default() * 1024 * 1024)
33            .unwrap_or_default();
34        // Instance attributes corresponds to the log stream name for AWS Lambda;
35        // See the FaaS resource specification for more details.
36        let instance = env::var(AWS_LAMBDA_LOG_STREAM_NAME_ENV_VAR).unwrap_or_default();
37        let log_group_name = env::var(AWS_LAMBDA_LOG_GROUP_NAME_ENV_VAR).unwrap_or_default();
38
39        let attributes = [
40            KeyValue::new(semconv::resource::CLOUD_PROVIDER, "aws"),
41            KeyValue::new(semconv::resource::CLOUD_REGION, aws_region),
42            KeyValue::new(semconv::resource::FAAS_INSTANCE, instance),
43            KeyValue::new(semconv::resource::FAAS_NAME, lambda_name),
44            KeyValue::new(semconv::resource::FAAS_VERSION, function_version),
45            KeyValue::new(semconv::resource::FAAS_MAX_MEMORY, function_memory_limit),
46            KeyValue::new(
47                semconv::resource::AWS_LOG_GROUP_NAMES,
48                Value::Array(Array::from(vec![StringValue::from(log_group_name)])),
49            ),
50        ];
51
52        Resource::builder_empty()
53            .with_attributes(attributes)
54            .build()
55    }
56}
57
58#[cfg(test)]
59mod tests {
60    use super::*;
61    use sealed_test::prelude::*;
62
63    #[sealed_test]
64    fn test_aws_lambda_detector() {
65        temp_env::with_vars(
66            [
67                (AWS_LAMBDA_FUNCTION_NAME_ENV_VAR, Some("my-lambda-function")),
68                (AWS_REGION_ENV_VAR, Some("eu-west-3")),
69                (AWS_LAMBDA_FUNCTION_VERSION_ENV_VAR, Some("$LATEST")),
70                (
71                    AWS_LAMBDA_LOG_STREAM_NAME_ENV_VAR,
72                    Some("2023/01/01/[$LATEST]5d1edb9e525d486696cf01a3503487bc"),
73                ),
74                (AWS_LAMBDA_MEMORY_LIMIT_ENV_VAR, Some("128")),
75                (
76                    AWS_LAMBDA_LOG_GROUP_NAME_ENV_VAR,
77                    Some("/aws/lambda/my-lambda-function"),
78                ),
79            ],
80            || {
81                let expected = Resource::builder_empty()
82                    .with_attributes([
83                        KeyValue::new(semconv::resource::CLOUD_PROVIDER, "aws"),
84                        KeyValue::new(semconv::resource::CLOUD_REGION, "eu-west-3"),
85                        KeyValue::new(
86                            semconv::resource::FAAS_INSTANCE,
87                            "2023/01/01/[$LATEST]5d1edb9e525d486696cf01a3503487bc",
88                        ),
89                        KeyValue::new(semconv::resource::FAAS_NAME, "my-lambda-function"),
90                        KeyValue::new(semconv::resource::FAAS_VERSION, "$LATEST"),
91                        KeyValue::new(semconv::resource::FAAS_MAX_MEMORY, 128 * 1024 * 1024),
92                        KeyValue::new(
93                            semconv::resource::AWS_LOG_GROUP_NAMES,
94                            Value::Array(Array::from(vec![StringValue::from(
95                                "/aws/lambda/my-lambda-function".to_string(),
96                            )])),
97                        ),
98                    ])
99                    .build();
100
101                let detector = LambdaResourceDetector {};
102                let got = detector.detect();
103
104                assert_eq!(expected, got);
105            },
106        );
107    }
108
109    #[sealed_test]
110    fn test_aws_lambda_detector_returns_empty_if_no_lambda_environment() {
111        let detector = LambdaResourceDetector {};
112        let got = detector.detect();
113        assert_eq!(Resource::builder_empty().build(), got);
114    }
115}