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 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278
//! # Resource
//!
//! A `Resource` is an immutable representation of the entity producing telemetry. For example, a
//! process producing telemetry that is running in a container on Kubernetes has a Pod name, it is
//! in a namespace, and possibly is part of a Deployment which also has a name. All three of these
//! attributes can be included in the `Resource`.
//!
//! The primary purpose of resources as a first-class concept in the SDK is decoupling of discovery
//! of resource information from exporters. This allows for independent development and easy
//! customization for users that need to integrate with closed source environments. When used with
//! distributed tracing, a resource can be associated with the [`TracerProvider`] when it is created.
//! That association cannot be changed later. When associated with a `TracerProvider`, all `Span`s
//! produced by any `Tracer` from the provider are associated with this `Resource`.
//!
//! [`TracerProvider`]: crate::trace::TracerProvider
//!
//! # Resource detectors
//!
//! `ResourceDetector`s are used to detect resource from runtime or environmental variables. The
//! following `ResourceDetector`s are provided along with this SDK.
//!
//! - EnvResourceDetector, detect resource from environmental variables.
//! - OsResourceDetector, detect OS from runtime.
//! - ProcessResourceDetector, detect process information
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;
/// Describes an entity about which identifying information and metadata is exposed.
///
/// Items are sorted by their key, and are only overwritten if the value is an empty string.
#[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 {
/// Creates an empty resource.
pub fn empty() -> Self {
Self {
attrs: Default::default(),
}
}
/// Create a new `Resource` from key value pairs.
///
/// Values are de-duplicated by key, and the first key-value pair with a non-empty string value
/// will be retained
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
}
/// Create a new `Resource` from resource detectors.
///
/// timeout will be applied to each detector.
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() {
// using insert instead of merge to avoid clone.
resource.attrs.insert(key, value);
}
}
resource
}
/// Create a new `Resource` by combining two resources.
///
/// Keys from the `other` resource have priority over keys from this resource, even if the
/// updated value is empty.
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();
// attrs from self take the less priority, even when the new value is 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
}
/// Returns the number of attributes for this resource
pub fn len(&self) -> usize {
self.attrs.len()
}
/// Returns `true` if the resource contains no attributes.
pub fn is_empty(&self) -> bool {
self.attrs.is_empty()
}
/// Gets an iterator over the attributes of this resource, sorted by key.
pub fn iter(&self) -> Iter<'_> {
self.into_iter()
}
/// Retrieve the value from resource associate with given key.
pub fn get(&self, key: Key) -> Option<Value> {
self.attrs.get(&key).cloned()
}
/// Encoded attributes
#[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())
}
}
/// An owned iterator over the entries of a `Resource`.
#[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())
}
}
/// An iterator over the entries of a `Resource`.
#[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())
}
}
/// ResourceDetector detects OpenTelemetry resource information
///
/// Implementations of this trait can be passed to
/// the `Resource::from_detectors` function to generate a Resource from the merged information.
pub trait ResourceDetector {
/// detect returns an initialized Resource based on gathered information.
///
/// timeout is used in case the detection operation takes too much time.
///
/// If source information to construct a Resource is inaccessible, an empty Resource should be returned
///
/// If source information to construct a Resource is invalid, for example,
/// missing required values. an empty Resource should be returned.
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")
])
)
}
}