opentelemetry_spanprocessor_any/sdk/resource/
env.rs

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