1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
//! EnvResourceDetector
//!
//! Implementation of `ResourceDetector` to extract a `Resource` from environment
//! variables.
use crate::api::{Key, KeyValue, Value};
use crate::sdk::resource::ResourceDetector;
use crate::sdk::Resource;
use std::env;
use std::time::Duration;

static OTEL_RESOURCE_ATTRIBUTES: &str = "OTEL_RESOURCE_ATTRIBUTES";

/// Resource detector implements ResourceDetector and is used to extract
/// general SDK configuration from environment.
///
/// See
/// [semantic conventions](https://github.com/open-telemetry/opentelemetry-specification/tree/master/specification/resource/semantic_conventions#telemetry-sdk)
/// for details.
#[derive(Debug)]
pub struct EnvResourceDetector {
    _private: (),
}

impl ResourceDetector for EnvResourceDetector {
    fn detect(&self, _timeout: Duration) -> Resource {
        match env::var(OTEL_RESOURCE_ATTRIBUTES) {
            Ok(s) if !s.is_empty() => construct_otel_resources(s),
            Ok(_) | Err(_) => Resource::new(vec![]), // return empty resource
        }
    }
}

impl EnvResourceDetector {
    /// Create `EnvResourceDetector` instance.
    pub fn new() -> Self {
        EnvResourceDetector { _private: () }
    }
}

impl Default for EnvResourceDetector {
    fn default() -> Self {
        EnvResourceDetector::new()
    }
}

/// Extract key value pairs and construct a resource from resources string like
/// key1=value1,key2=value2,...
fn construct_otel_resources(s: String) -> Resource {
    let mut key_values = vec![];
    for entries in s.split_terminator(',') {
        let key_value_strs = entries.split('=').map(str::trim).collect::<Vec<&str>>();
        if key_value_strs.len() != 2 {
            continue;
        }

        let key = Key::from(key_value_strs.get(0).unwrap().to_string());
        let value = Value::String(key_value_strs.get(1).unwrap().to_string());

        key_values.push(KeyValue::new(key, value));
    }

    Resource::new(key_values)
}

#[cfg(test)]
mod tests {
    use crate::api::{Key, KeyValue, Value};
    use crate::sdk::env::OTEL_RESOURCE_ATTRIBUTES;
    use crate::sdk::resource::{Resource, ResourceDetector};
    use crate::sdk::EnvResourceDetector;
    use std::{env, time};

    #[test]
    fn test_read_from_env() {
        env::set_var(OTEL_RESOURCE_ATTRIBUTES, "key=value, k = v , a= x, a=z");
        env::set_var("irrelevant".to_uppercase(), "20200810");

        let detector = EnvResourceDetector::new();
        let resource = detector.detect(time::Duration::from_secs(5));
        assert_eq!(
            resource,
            Resource::new(vec![
                KeyValue::new(
                    Key::new("key".to_string()),
                    Value::String("value".to_string())
                ),
                KeyValue::new(Key::new("k".to_string()), Value::String("v".to_string())),
                KeyValue::new(Key::new("a".to_string()), Value::String("x".to_string())),
                KeyValue::new(Key::new("a".to_string()), Value::String("z".to_string()))
            ])
        );

        // Test this case in same test to avoid race condition when running tests in parallel.
        env::set_var(OTEL_RESOURCE_ATTRIBUTES, "");

        let detector = EnvResourceDetector::new();
        let resource = detector.detect(time::Duration::from_secs(5));
        assert!(resource.is_empty());
    }
}