opentelemetry_spanprocessor_any/sdk/resource/
mod.rs

1//! # Resource
2//!
3//! A `Resource` is an immutable representation of the entity producing telemetry. For example, a
4//! process producing telemetry that is running in a container on Kubernetes has a Pod name, it is
5//! in a namespace, and possibly is part of a Deployment which also has a name. All three of these
6//! attributes can be included in the `Resource`.
7//!
8//! The primary purpose of resources as a first-class concept in the SDK is decoupling of discovery
9//! of resource information from exporters. This allows for independent development and easy
10//! customization for users that need to integrate with closed source environments. When used with
11//! distributed tracing, a resource can be associated with the [`TracerProvider`] when it is created.
12//! That association cannot be changed later. When associated with a `TracerProvider`, all `Span`s
13//! produced by any `Tracer` from the provider are associated with this `Resource`.
14//!
15//! [`TracerProvider`]: crate::trace::TracerProvider
16//!
17//! # Resource detectors
18//!
19//! `ResourceDetector`s are used to detect resource from runtime or environmental variables. The
20//! following `ResourceDetector`s are provided along with this SDK.
21//!
22//! - EnvResourceDetector, detect resource from environmental variables.
23//! - OsResourceDetector, detect OS from runtime.
24//! - ProcessResourceDetector, detect process information
25mod env;
26mod os;
27mod process;
28
29pub use env::EnvResourceDetector;
30pub use env::SdkProvidedResourceDetector;
31pub use os::OsResourceDetector;
32pub use process::ProcessResourceDetector;
33
34#[cfg(feature = "metrics")]
35use crate::attributes;
36use crate::{Key, KeyValue, Value};
37#[cfg(feature = "serialize")]
38use serde::{Deserialize, Serialize};
39use std::collections::{btree_map, BTreeMap};
40use std::ops::Deref;
41use std::time::Duration;
42
43/// Describes an entity about which identifying information and metadata is exposed.
44///
45/// Items are sorted by their key, and are only overwritten if the value is an empty string.
46#[cfg_attr(feature = "serialize", derive(Deserialize, Serialize))]
47#[derive(Clone, Debug, PartialEq)]
48pub struct Resource {
49    attrs: BTreeMap<Key, Value>,
50}
51
52impl Default for Resource {
53    fn default() -> Self {
54        Self::from_detectors(
55            Duration::from_secs(0),
56            vec![Box::new(EnvResourceDetector::new())],
57        )
58    }
59}
60
61impl Resource {
62    /// Creates an empty resource.
63    pub fn empty() -> Self {
64        Self {
65            attrs: Default::default(),
66        }
67    }
68
69    /// Create a new `Resource` from key value pairs.
70    ///
71    /// Values are de-duplicated by key, and the first key-value pair with a non-empty string value
72    /// will be retained
73    pub fn new<T: IntoIterator<Item = KeyValue>>(kvs: T) -> Self {
74        let mut resource = Resource::empty();
75
76        for kv in kvs.into_iter() {
77            resource.attrs.insert(kv.key, kv.value);
78        }
79
80        resource
81    }
82
83    /// Create a new `Resource` from resource detectors.
84    ///
85    /// timeout will be applied to each detector.
86    pub fn from_detectors(timeout: Duration, detectors: Vec<Box<dyn ResourceDetector>>) -> Self {
87        let mut resource = Resource::empty();
88        for detector in detectors {
89            let detected_res = detector.detect(timeout);
90            for (key, value) in detected_res.into_iter() {
91                // using insert instead of merge to avoid clone.
92                resource.attrs.insert(key, value);
93            }
94        }
95
96        resource
97    }
98
99    /// Create a new `Resource` by combining two resources.
100    ///
101    /// Keys from the `other` resource have priority over keys from this resource, even if the
102    /// updated value is empty.
103    pub fn merge<T: Deref<Target = Self>>(&self, other: T) -> Self {
104        if self.attrs.is_empty() {
105            return other.clone();
106        }
107        if other.attrs.is_empty() {
108            return self.clone();
109        }
110
111        let mut resource = Resource::empty();
112
113        // attrs from self take the less priority, even when the new value is empty.
114        for (k, v) in self.attrs.iter() {
115            resource.attrs.insert(k.clone(), v.clone());
116        }
117        for (k, v) in other.attrs.iter() {
118            resource.attrs.insert(k.clone(), v.clone());
119        }
120
121        resource
122    }
123
124    /// Returns the number of attributes for this resource
125    pub fn len(&self) -> usize {
126        self.attrs.len()
127    }
128
129    /// Returns `true` if the resource contains no attributes.
130    pub fn is_empty(&self) -> bool {
131        self.attrs.is_empty()
132    }
133
134    /// Gets an iterator over the attributes of this resource, sorted by key.
135    pub fn iter(&self) -> Iter<'_> {
136        self.into_iter()
137    }
138
139    /// Retrieve the value from resource associate with given key.
140    pub fn get(&self, key: Key) -> Option<Value> {
141        self.attrs.get(&key).cloned()
142    }
143
144    /// Encoded attributes
145    #[cfg(feature = "metrics")]
146    #[cfg_attr(docsrs, doc(cfg(feature = "metrics")))]
147    pub fn encoded(&self, encoder: &dyn attributes::Encoder) -> String {
148        encoder.encode(&mut self.into_iter())
149    }
150}
151
152/// An owned iterator over the entries of a `Resource`.
153#[derive(Debug)]
154pub struct IntoIter(btree_map::IntoIter<Key, Value>);
155
156impl Iterator for IntoIter {
157    type Item = (Key, Value);
158
159    fn next(&mut self) -> Option<Self::Item> {
160        self.0.next()
161    }
162}
163
164impl IntoIterator for Resource {
165    type Item = (Key, Value);
166    type IntoIter = IntoIter;
167
168    fn into_iter(self) -> Self::IntoIter {
169        IntoIter(self.attrs.into_iter())
170    }
171}
172
173/// An iterator over the entries of a `Resource`.
174#[derive(Debug)]
175pub struct Iter<'a>(btree_map::Iter<'a, Key, Value>);
176
177impl<'a> Iterator for Iter<'a> {
178    type Item = (&'a Key, &'a Value);
179
180    fn next(&mut self) -> Option<Self::Item> {
181        self.0.next()
182    }
183}
184
185impl<'a> IntoIterator for &'a Resource {
186    type Item = (&'a Key, &'a Value);
187    type IntoIter = Iter<'a>;
188
189    fn into_iter(self) -> Self::IntoIter {
190        Iter(self.attrs.iter())
191    }
192}
193
194/// ResourceDetector detects OpenTelemetry resource information
195///
196/// Implementations of this trait can be passed to
197/// the `Resource::from_detectors` function to generate a Resource from the merged information.
198pub trait ResourceDetector {
199    /// detect returns an initialized Resource based on gathered information.
200    ///
201    /// timeout is used in case the detection operation takes too much time.
202    ///
203    /// If source information to construct a Resource is inaccessible, an empty Resource should be returned
204    ///
205    /// If source information to construct a Resource is invalid, for example,
206    /// missing required values. an empty Resource should be returned.
207    fn detect(&self, timeout: Duration) -> Resource;
208}
209
210#[cfg(test)]
211mod tests {
212    use super::*;
213    use crate::sdk::resource::EnvResourceDetector;
214    use std::collections::BTreeMap;
215    use std::{env, time};
216
217    #[test]
218    fn new_resource() {
219        let args_with_dupe_keys = vec![KeyValue::new("a", ""), KeyValue::new("a", "final")];
220
221        let mut expected_attrs = BTreeMap::new();
222        expected_attrs.insert(Key::new("a"), Value::from("final"));
223
224        assert_eq!(
225            Resource::new(args_with_dupe_keys),
226            Resource {
227                attrs: expected_attrs
228            }
229        );
230    }
231
232    #[test]
233    fn merge_resource() {
234        let resource_a = Resource::new(vec![
235            KeyValue::new("a", ""),
236            KeyValue::new("b", "b-value"),
237            KeyValue::new("d", "d-value"),
238        ]);
239
240        let resource_b = Resource::new(vec![
241            KeyValue::new("a", "a-value"),
242            KeyValue::new("c", "c-value"),
243            KeyValue::new("d", ""),
244        ]);
245
246        let mut expected_attrs = BTreeMap::new();
247        expected_attrs.insert(Key::new("a"), Value::from("a-value"));
248        expected_attrs.insert(Key::new("b"), Value::from("b-value"));
249        expected_attrs.insert(Key::new("c"), Value::from("c-value"));
250        expected_attrs.insert(Key::new("d"), Value::from(""));
251
252        assert_eq!(
253            resource_a.merge(&resource_b),
254            Resource {
255                attrs: expected_attrs
256            }
257        );
258    }
259
260    #[test]
261    fn detect_resource() {
262        env::set_var("OTEL_RESOURCE_ATTRIBUTES", "key=value, k = v , a= x, a=z");
263        env::set_var("irrelevant".to_uppercase(), "20200810");
264
265        let detector = EnvResourceDetector::new();
266        let resource =
267            Resource::from_detectors(time::Duration::from_secs(5), vec![Box::new(detector)]);
268        assert_eq!(
269            resource,
270            Resource::new(vec![
271                KeyValue::new("key", "value"),
272                KeyValue::new("k", "v"),
273                KeyValue::new("a", "x"),
274                KeyValue::new("a", "z")
275            ])
276        )
277    }
278}