opentelemetry_sdk/resource/
env.rs

1//! Environment variables resource detector
2//!
3//! Implementation of `ResourceDetector` to extract a `Resource` from environment
4//! variables.
5use crate::resource::{Resource, ResourceDetector};
6use opentelemetry::{Key, KeyValue, Value};
7use std::env;
8use std::time::Duration;
9
10const OTEL_RESOURCE_ATTRIBUTES: &str = "OTEL_RESOURCE_ATTRIBUTES";
11const OTEL_SERVICE_NAME: &str = "OTEL_SERVICE_NAME";
12
13/// EnvResourceDetector extract resource from environment variable
14/// `OTEL_RESOURCE_ATTRIBUTES`. See [OpenTelemetry Resource
15/// Spec](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/resource/sdk.md#specifying-resource-information-via-an-environment-variable)
16/// for details.
17#[derive(Debug)]
18pub struct EnvResourceDetector {
19    _private: (),
20}
21
22impl ResourceDetector for EnvResourceDetector {
23    fn detect(&self, _timeout: Duration) -> Resource {
24        match env::var(OTEL_RESOURCE_ATTRIBUTES) {
25            Ok(s) if !s.is_empty() => construct_otel_resources(s),
26            Ok(_) | Err(_) => Resource::new(vec![]), // return empty resource
27        }
28    }
29}
30
31impl EnvResourceDetector {
32    /// Create `EnvResourceDetector` instance.
33    pub fn new() -> Self {
34        EnvResourceDetector { _private: () }
35    }
36}
37
38impl Default for EnvResourceDetector {
39    fn default() -> Self {
40        EnvResourceDetector::new()
41    }
42}
43
44/// Extract key value pairs and construct a resource from resources string like
45/// key1=value1,key2=value2,...
46fn construct_otel_resources(s: String) -> Resource {
47    Resource::new(s.split_terminator(',').filter_map(|entry| {
48        let mut parts = entry.splitn(2, '=');
49        let key = parts.next()?.trim();
50        let value = parts.next()?.trim();
51        if value.find('=').is_some() {
52            return None;
53        }
54
55        Some(KeyValue::new(key.to_owned(), value.to_owned()))
56    }))
57}
58
59/// There are attributes which MUST be provided by the SDK as specified in
60/// [the Resource SDK specification]. This detector detects those attributes and
61/// if the attribute cannot be detected, it uses the default value.
62///
63/// This detector will first try `OTEL_SERVICE_NAME` env. If it's not available,
64/// then it will check the `OTEL_RESOURCE_ATTRIBUTES` env and see if it contains
65/// `service.name` resource. If it's also not available, it will use `unknown_service`.
66///
67/// If users want to set an empty service name, they can provide
68/// a resource with empty value and `service.name` key.
69///
70/// [the Resource SDK specification]:https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/resource/sdk.md#sdk-provided-resource-attributes
71#[derive(Debug)]
72pub struct SdkProvidedResourceDetector;
73
74impl ResourceDetector for SdkProvidedResourceDetector {
75    fn detect(&self, _timeout: Duration) -> Resource {
76        Resource::new(vec![KeyValue::new(
77            super::SERVICE_NAME,
78            env::var(OTEL_SERVICE_NAME)
79                .ok()
80                .filter(|s| !s.is_empty())
81                .map(Value::from)
82                .or_else(|| {
83                    EnvResourceDetector::new()
84                        .detect(Duration::from_secs(0))
85                        .get(Key::new(super::SERVICE_NAME))
86                })
87                .unwrap_or_else(|| "unknown_service".into()),
88        )])
89    }
90}
91
92#[cfg(test)]
93mod tests {
94    use crate::resource::env::{
95        SdkProvidedResourceDetector, OTEL_RESOURCE_ATTRIBUTES, OTEL_SERVICE_NAME,
96    };
97    use crate::resource::{EnvResourceDetector, Resource, ResourceDetector};
98    use opentelemetry::{Key, KeyValue, Value};
99    use std::time::Duration;
100
101    #[test]
102    fn test_read_from_env() {
103        temp_env::with_vars(
104            [
105                (
106                    "OTEL_RESOURCE_ATTRIBUTES",
107                    Some("key=value, k = v , a= x, a=z"),
108                ),
109                ("IRRELEVANT", Some("20200810")),
110            ],
111            || {
112                let detector = EnvResourceDetector::new();
113                let resource = detector.detect(Duration::from_secs(5));
114                assert_eq!(
115                    resource,
116                    Resource::new(vec![
117                        KeyValue::new("key", "value"),
118                        KeyValue::new("k", "v"),
119                        KeyValue::new("a", "x"),
120                        KeyValue::new("a", "z"),
121                    ])
122                );
123            },
124        );
125
126        let detector = EnvResourceDetector::new();
127        let resource = detector.detect(Duration::from_secs(5));
128        assert!(resource.is_empty());
129    }
130
131    #[test]
132    fn test_sdk_provided_resource_detector() {
133        // Ensure no env var set
134        let no_env = SdkProvidedResourceDetector.detect(Duration::from_secs(1));
135        assert_eq!(
136            no_env.get(Key::from_static_str(crate::resource::SERVICE_NAME)),
137            Some(Value::from("unknown_service")),
138        );
139
140        temp_env::with_var(OTEL_SERVICE_NAME, Some("test service"), || {
141            let with_service = SdkProvidedResourceDetector.detect(Duration::from_secs(1));
142            assert_eq!(
143                with_service.get(Key::from_static_str(crate::resource::SERVICE_NAME)),
144                Some(Value::from("test service")),
145            )
146        });
147
148        temp_env::with_var(
149            OTEL_RESOURCE_ATTRIBUTES,
150            Some("service.name=test service1"),
151            || {
152                let with_service = SdkProvidedResourceDetector.detect(Duration::from_secs(1));
153                assert_eq!(
154                    with_service.get(Key::from_static_str(crate::resource::SERVICE_NAME)),
155                    Some(Value::from("test service1")),
156                )
157            },
158        );
159
160        // OTEL_SERVICE_NAME takes priority
161        temp_env::with_vars(
162            [
163                (OTEL_SERVICE_NAME, Some("test service")),
164                (OTEL_RESOURCE_ATTRIBUTES, Some("service.name=test service3")),
165            ],
166            || {
167                let with_service = SdkProvidedResourceDetector.detect(Duration::from_secs(1));
168                assert_eq!(
169                    with_service.get(Key::from_static_str(crate::resource::SERVICE_NAME)),
170                    Some(Value::from("test service"))
171                );
172            },
173        );
174    }
175}