use crate::resource::{Resource, ResourceDetector};
use opentelemetry::{Key, KeyValue, Value};
use std::env;
const OTEL_RESOURCE_ATTRIBUTES: &str = "OTEL_RESOURCE_ATTRIBUTES";
const OTEL_SERVICE_NAME: &str = "OTEL_SERVICE_NAME";
#[derive(Debug)]
pub struct EnvResourceDetector {
_private: (),
}
impl ResourceDetector for EnvResourceDetector {
fn detect(&self) -> Resource {
match env::var(OTEL_RESOURCE_ATTRIBUTES) {
Ok(s) if !s.is_empty() => construct_otel_resources(s),
Ok(_) | Err(_) => Resource::empty(), }
}
}
impl EnvResourceDetector {
pub fn new() -> Self {
EnvResourceDetector { _private: () }
}
}
impl Default for EnvResourceDetector {
fn default() -> Self {
EnvResourceDetector::new()
}
}
fn construct_otel_resources(s: String) -> Resource {
Resource::builder_empty()
.with_attributes(s.split_terminator(',').filter_map(|entry| {
let parts = match entry.split_once('=') {
Some(p) => p,
None => return None,
};
let key = parts.0.trim();
let value = parts.1.trim();
Some(KeyValue::new(key.to_owned(), value.to_owned()))
}))
.build()
}
#[derive(Debug)]
pub struct SdkProvidedResourceDetector;
impl ResourceDetector for SdkProvidedResourceDetector {
fn detect(&self) -> Resource {
Resource::builder_empty()
.with_attributes([KeyValue::new(
super::SERVICE_NAME,
env::var(OTEL_SERVICE_NAME)
.ok()
.filter(|s| !s.is_empty())
.map(Value::from)
.or_else(|| {
EnvResourceDetector::new()
.detect()
.get(&Key::new(super::SERVICE_NAME))
})
.unwrap_or_else(|| {
env::current_exe()
.ok()
.and_then(|path| {
path.file_name()
.and_then(|name| name.to_str())
.map(|name| format!("unknown_service:{}", name))
})
.unwrap_or_else(|| "unknown_service".to_string())
.into()
}),
)])
.build()
}
}
#[cfg(test)]
mod tests {
use crate::resource::env::{
SdkProvidedResourceDetector, OTEL_RESOURCE_ATTRIBUTES, OTEL_SERVICE_NAME,
};
use crate::resource::{EnvResourceDetector, Resource, ResourceDetector};
use opentelemetry::{Key, KeyValue, Value};
#[test]
fn test_read_from_env() {
temp_env::with_vars(
[
(
"OTEL_RESOURCE_ATTRIBUTES",
Some("key=value, k = v , a= x, a=z,base64=SGVsbG8sIFdvcmxkIQ=="),
),
("IRRELEVANT", Some("20200810")),
],
|| {
let detector = EnvResourceDetector::new();
let resource = detector.detect();
assert_eq!(
resource,
Resource::builder_empty()
.with_attributes([
KeyValue::new("key", "value"),
KeyValue::new("k", "v"),
KeyValue::new("a", "x"),
KeyValue::new("a", "z"),
KeyValue::new("base64", "SGVsbG8sIFdvcmxkIQ=="), ])
.build()
);
},
);
let detector = EnvResourceDetector::new();
let resource = detector.detect();
assert!(resource.is_empty());
}
#[test]
fn test_sdk_provided_resource_detector() {
let no_env = SdkProvidedResourceDetector.detect();
let service_name = no_env
.get(&Key::from_static_str(crate::resource::SERVICE_NAME))
.map(|v| v.to_string())
.unwrap();
assert!(
service_name.starts_with("unknown_service:opentelemetry_sdk-"),
"Expected service name to start with 'unknown_service:opentelemetry_sdk-', got: {}",
service_name
);
temp_env::with_var(OTEL_SERVICE_NAME, Some("test service"), || {
let with_service = SdkProvidedResourceDetector.detect();
assert_eq!(
with_service.get(&Key::from_static_str(crate::resource::SERVICE_NAME)),
Some(Value::from("test service")),
)
});
temp_env::with_var(
OTEL_RESOURCE_ATTRIBUTES,
Some("service.name=test service1"),
|| {
let with_service = SdkProvidedResourceDetector.detect();
assert_eq!(
with_service.get(&Key::from_static_str(crate::resource::SERVICE_NAME)),
Some(Value::from("test service1")),
)
},
);
temp_env::with_vars(
[
(OTEL_SERVICE_NAME, Some("test service")),
(OTEL_RESOURCE_ATTRIBUTES, Some("service.name=test service3")),
],
|| {
let with_service = SdkProvidedResourceDetector.detect();
assert_eq!(
with_service.get(&Key::from_static_str(crate::resource::SERVICE_NAME)),
Some(Value::from("test service"))
);
},
);
}
}