mod env;
mod os;
mod process;
pub use env::EnvResourceDetector;
pub use env::SdkProvidedResourceDetector;
pub use os::OsResourceDetector;
pub use process::ProcessResourceDetector;
#[cfg(feature = "metrics")]
use crate::attributes;
use crate::{Key, KeyValue, Value};
#[cfg(feature = "serialize")]
use serde::{Deserialize, Serialize};
use std::collections::{btree_map, BTreeMap};
use std::ops::Deref;
use std::time::Duration;
#[cfg_attr(feature = "serialize", derive(Deserialize, Serialize))]
#[derive(Clone, Debug, PartialEq)]
pub struct Resource {
attrs: BTreeMap<Key, Value>,
}
impl Default for Resource {
fn default() -> Self {
Self::from_detectors(
Duration::from_secs(0),
vec![Box::new(EnvResourceDetector::new())],
)
}
}
impl Resource {
pub fn empty() -> Self {
Self {
attrs: Default::default(),
}
}
pub fn new<T: IntoIterator<Item = KeyValue>>(kvs: T) -> Self {
let mut resource = Resource::empty();
for kv in kvs.into_iter() {
resource.attrs.insert(kv.key, kv.value);
}
resource
}
pub fn from_detectors(timeout: Duration, detectors: Vec<Box<dyn ResourceDetector>>) -> Self {
let mut resource = Resource::empty();
for detector in detectors {
let detected_res = detector.detect(timeout);
for (key, value) in detected_res.into_iter() {
resource.attrs.insert(key, value);
}
}
resource
}
pub fn merge<T: Deref<Target = Self>>(&self, other: T) -> Self {
if self.attrs.is_empty() {
return other.clone();
}
if other.attrs.is_empty() {
return self.clone();
}
let mut resource = Resource::empty();
for (k, v) in self.attrs.iter() {
resource.attrs.insert(k.clone(), v.clone());
}
for (k, v) in other.attrs.iter() {
resource.attrs.insert(k.clone(), v.clone());
}
resource
}
pub fn len(&self) -> usize {
self.attrs.len()
}
pub fn is_empty(&self) -> bool {
self.attrs.is_empty()
}
pub fn iter(&self) -> Iter<'_> {
self.into_iter()
}
pub fn get(&self, key: Key) -> Option<Value> {
self.attrs.get(&key).cloned()
}
#[cfg(feature = "metrics")]
#[cfg_attr(docsrs, doc(cfg(feature = "metrics")))]
pub fn encoded(&self, encoder: &dyn attributes::Encoder) -> String {
encoder.encode(&mut self.into_iter())
}
}
#[derive(Debug)]
pub struct IntoIter(btree_map::IntoIter<Key, Value>);
impl Iterator for IntoIter {
type Item = (Key, Value);
fn next(&mut self) -> Option<Self::Item> {
self.0.next()
}
}
impl IntoIterator for Resource {
type Item = (Key, Value);
type IntoIter = IntoIter;
fn into_iter(self) -> Self::IntoIter {
IntoIter(self.attrs.into_iter())
}
}
#[derive(Debug)]
pub struct Iter<'a>(btree_map::Iter<'a, Key, Value>);
impl<'a> Iterator for Iter<'a> {
type Item = (&'a Key, &'a Value);
fn next(&mut self) -> Option<Self::Item> {
self.0.next()
}
}
impl<'a> IntoIterator for &'a Resource {
type Item = (&'a Key, &'a Value);
type IntoIter = Iter<'a>;
fn into_iter(self) -> Self::IntoIter {
Iter(self.attrs.iter())
}
}
pub trait ResourceDetector {
fn detect(&self, timeout: Duration) -> Resource;
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sdk::resource::EnvResourceDetector;
use std::collections::BTreeMap;
use std::{env, time};
#[test]
fn new_resource() {
let args_with_dupe_keys = vec![KeyValue::new("a", ""), KeyValue::new("a", "final")];
let mut expected_attrs = BTreeMap::new();
expected_attrs.insert(Key::new("a"), Value::from("final"));
assert_eq!(
Resource::new(args_with_dupe_keys),
Resource {
attrs: expected_attrs
}
);
}
#[test]
fn merge_resource() {
let resource_a = Resource::new(vec![
KeyValue::new("a", ""),
KeyValue::new("b", "b-value"),
KeyValue::new("d", "d-value"),
]);
let resource_b = Resource::new(vec![
KeyValue::new("a", "a-value"),
KeyValue::new("c", "c-value"),
KeyValue::new("d", ""),
]);
let mut expected_attrs = BTreeMap::new();
expected_attrs.insert(Key::new("a"), Value::from("a-value"));
expected_attrs.insert(Key::new("b"), Value::from("b-value"));
expected_attrs.insert(Key::new("c"), Value::from("c-value"));
expected_attrs.insert(Key::new("d"), Value::from(""));
assert_eq!(
resource_a.merge(&resource_b),
Resource {
attrs: expected_attrs
}
);
}
#[test]
fn detect_resource() {
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 =
Resource::from_detectors(time::Duration::from_secs(5), vec![Box::new(detector)]);
assert_eq!(
resource,
Resource::new(vec![
KeyValue::new("key", "value"),
KeyValue::new("k", "v"),
KeyValue::new("a", "x"),
KeyValue::new("a", "z")
])
)
}
}