opentelemetry_spanprocessor_any/
baggage.rs

1//! Primitives for sending name-value data across system boundaries.
2//!
3//! Main types in this module are:
4//!
5//! * [`Baggage`]: Baggage is used to annotate telemetry, adding context and
6//!   information to metrics, traces, and logs.
7//! * [`BaggageExt`]: Extensions for managing `Baggage` in a [`Context`].
8//!
9//! Baggage can be sent between systems using the [`BaggagePropagator`] in
10//! accordance with the [W3C Baggage] specification.
11//!
12//! [`BaggagePropagator`]: crate::sdk::propagation::BaggagePropagator
13//! [W3C Baggage]: https://w3c.github.io/baggage
14//!
15//! # Examples
16//!
17//! ```
18//! # #[cfg(feature = "trace")]
19//! # {
20//! use opentelemetry::{baggage::BaggageExt, Key, propagation::TextMapPropagator};
21//! use opentelemetry::sdk::propagation::BaggagePropagator;
22//! use std::collections::HashMap;
23//!
24//! // Example baggage value passed in externally via http headers
25//! let mut headers = HashMap::new();
26//! headers.insert("baggage".to_string(), "user_id=1".to_string());
27//!
28//! let propagator = BaggagePropagator::new();
29//! // can extract from any type that impls `Extractor`, usually an HTTP header map
30//! let cx = propagator.extract(&headers);
31//!
32//! // Iterate over extracted name-value pairs
33//! for (name, value) in cx.baggage() {
34//!     // ...
35//! }
36//!
37//! // Add new baggage
38//! let cx_with_additions = cx.with_baggage(vec![Key::new("server_id").i64(42)]);
39//!
40//! // Inject baggage into http request
41//! propagator.inject_context(&cx_with_additions, &mut headers);
42//!
43//! let header_value = headers.get("baggage").expect("header is injected");
44//! assert!(header_value.contains("user_id=1"), "still contains previous name-value");
45//! assert!(header_value.contains("server_id=42"), "contains new name-value pair");
46//! # }
47//! ```
48use crate::{Context, Key, KeyValue, Value};
49#[cfg(feature = "serialize")]
50use serde::{Deserialize, Serialize};
51use std::collections::{hash_map, HashMap};
52use std::iter::FromIterator;
53
54lazy_static::lazy_static! {
55    static ref DEFAULT_BAGGAGE: Baggage = Baggage::default();
56}
57
58const MAX_KEY_VALUE_PAIRS: usize = 180;
59const MAX_BYTES_FOR_ONE_PAIR: usize = 4096;
60const MAX_LEN_OF_ALL_PAIRS: usize = 8192;
61
62/// A set of name-value pairs describing user-defined properties.
63///
64/// ### Baggage Names
65///
66/// * ASCII strings according to the token format, defined in [RFC2616, Section 2.2]
67///
68/// ### Baggage Values
69///
70/// * URL encoded UTF-8 strings.
71///
72/// ### Baggage Value Metadata
73///
74/// Additional metadata can be added to values in the form of a property set,
75/// represented as semi-colon `;` delimited list of names and/or name-value pairs,
76/// e.g. `;k1=v1;k2;k3=v3`.
77///
78/// ### Limits
79///
80/// * Maximum number of name-value pairs: `180`.
81/// * Maximum number of bytes per a single name-value pair: `4096`.
82/// * Maximum total length of all name-value pairs: `8192`.
83///
84/// [RFC2616, Section 2.2]: https://tools.ietf.org/html/rfc2616#section-2.2
85#[derive(Debug, Default)]
86pub struct Baggage {
87    inner: HashMap<Key, (Value, BaggageMetadata)>,
88    kv_content_len: usize, // the length of key-value-metadata string in `inner`
89}
90
91impl Baggage {
92    /// Creates an empty `Baggage`.
93    pub fn new() -> Self {
94        Baggage {
95            inner: HashMap::default(),
96            kv_content_len: 0,
97        }
98    }
99
100    /// Returns a reference to the value associated with a given name
101    ///
102    /// # Examples
103    ///
104    /// ```
105    /// use opentelemetry::{baggage::Baggage, Value};
106    ///
107    /// let mut cc = Baggage::new();
108    /// let _ = cc.insert("my-name", "my-value");
109    ///
110    /// assert_eq!(cc.get("my-name"), Some(&Value::from("my-value")))
111    /// ```
112    pub fn get<T: Into<Key>>(&self, key: T) -> Option<&Value> {
113        self.inner.get(&key.into()).map(|(value, _metadata)| value)
114    }
115
116    /// Returns a reference to the value and metadata associated with a given name
117    ///
118    /// # Examples
119    /// ```
120    /// use opentelemetry::{baggage::{Baggage, BaggageMetadata}, Value};
121    ///
122    /// let mut cc = Baggage::new();
123    /// let _ = cc.insert("my-name", "my-value");
124    ///
125    /// // By default, the metadata is empty
126    /// assert_eq!(cc.get_with_metadata("my-name"), Some(&(Value::from("my-value"), BaggageMetadata::from(""))))
127    /// ```
128    pub fn get_with_metadata<T: Into<Key>>(&self, key: T) -> Option<&(Value, BaggageMetadata)> {
129        self.inner.get(&key.into())
130    }
131
132    /// Inserts a name-value pair into the baggage.
133    ///
134    /// If the name was not present, [`None`] is returned. If the name was present,
135    /// the value is updated, and the old value is returned.
136    ///
137    /// # Examples
138    ///
139    /// ```
140    /// use opentelemetry::{baggage::Baggage, Value};
141    ///
142    /// let mut cc = Baggage::new();
143    /// let _ = cc.insert("my-name", "my-value");
144    ///
145    /// assert_eq!(cc.get("my-name"), Some(&Value::from("my-value")))
146    /// ```
147    pub fn insert<K, V>(&mut self, key: K, value: V) -> Option<Value>
148    where
149        K: Into<Key>,
150        V: Into<Value>,
151    {
152        self.insert_with_metadata(key, value, BaggageMetadata::default())
153            .map(|pair| pair.0)
154    }
155
156    /// Inserts a name-value pair into the baggage.
157    ///
158    /// Same with `insert`, if the name was not present, [`None`] will be returned.
159    /// If the name is present, the old value and metadata will be returned.
160    ///
161    /// # Examples
162    ///
163    /// ```
164    /// use opentelemetry::{baggage::{Baggage, BaggageMetadata}, Value};
165    ///
166    /// let mut cc = Baggage::new();
167    /// let _ = cc.insert_with_metadata("my-name", "my-value", "test");
168    ///
169    /// assert_eq!(cc.get_with_metadata("my-name"), Some(&(Value::from("my-value"), BaggageMetadata::from("test"))))
170    /// ```
171    pub fn insert_with_metadata<K, V, S>(
172        &mut self,
173        key: K,
174        value: V,
175        metadata: S,
176    ) -> Option<(Value, BaggageMetadata)>
177    where
178        K: Into<Key>,
179        V: Into<Value>,
180        S: Into<BaggageMetadata>,
181    {
182        let (key, value, metadata) = (key.into(), value.into(), metadata.into());
183        if self.insertable(&key, &value, &metadata) {
184            self.inner.insert(key, (value, metadata))
185        } else {
186            None
187        }
188    }
189
190    /// Removes a name from the baggage, returning the value
191    /// corresponding to the name if the pair was previously in the map.
192    pub fn remove<K: Into<Key>>(&mut self, key: K) -> Option<(Value, BaggageMetadata)> {
193        self.inner.remove(&key.into())
194    }
195
196    /// Returns the number of attributes for this baggage
197    pub fn len(&self) -> usize {
198        self.inner.len()
199    }
200
201    /// Returns `true` if the baggage contains no items.
202    pub fn is_empty(&self) -> bool {
203        self.inner.is_empty()
204    }
205
206    /// Gets an iterator over the baggage items, sorted by name.
207    pub fn iter(&self) -> Iter<'_> {
208        self.into_iter()
209    }
210
211    /// Determine whether the key value pair exceed one of the [limits](https://w3c.github.io/baggage/#limits).
212    /// If not, update the total length of key values
213    fn insertable(&mut self, key: &Key, value: &Value, metadata: &BaggageMetadata) -> bool {
214        if !key.as_str().is_ascii() {
215            return false;
216        }
217        let value = value.as_str();
218        if key_value_metadata_bytes_size(key.as_str(), value.as_ref(), metadata.as_str())
219            < MAX_BYTES_FOR_ONE_PAIR
220        {
221            match self.inner.get(key) {
222                None => {
223                    // check total length
224                    if self.kv_content_len
225                        + metadata.as_str().len()
226                        + value.len()
227                        + key.as_str().len()
228                        > MAX_LEN_OF_ALL_PAIRS
229                    {
230                        return false;
231                    }
232                    // check number of pairs
233                    if self.inner.len() + 1 > MAX_KEY_VALUE_PAIRS {
234                        return false;
235                    }
236                    self.kv_content_len +=
237                        metadata.as_str().len() + value.len() + key.as_str().len()
238                }
239                Some((old_value, old_metadata)) => {
240                    let old_value = old_value.as_str();
241                    if self.kv_content_len - old_metadata.as_str().len() - old_value.len()
242                        + metadata.as_str().len()
243                        + value.len()
244                        > MAX_LEN_OF_ALL_PAIRS
245                    {
246                        return false;
247                    }
248                    self.kv_content_len =
249                        self.kv_content_len - old_metadata.as_str().len() - old_value.len()
250                            + metadata.as_str().len()
251                            + value.len()
252                }
253            }
254            true
255        } else {
256            false
257        }
258    }
259}
260
261/// Get the number of bytes for one key-value pair
262fn key_value_metadata_bytes_size(key: &str, value: &str, metadata: &str) -> usize {
263    key.bytes().len() + value.bytes().len() + metadata.bytes().len()
264}
265
266/// An iterator over the entries of a [`Baggage`].
267#[derive(Debug)]
268pub struct Iter<'a>(hash_map::Iter<'a, Key, (Value, BaggageMetadata)>);
269
270impl<'a> Iterator for Iter<'a> {
271    type Item = (&'a Key, &'a (Value, BaggageMetadata));
272
273    fn next(&mut self) -> Option<Self::Item> {
274        self.0.next()
275    }
276}
277
278impl<'a> IntoIterator for &'a Baggage {
279    type Item = (&'a Key, &'a (Value, BaggageMetadata));
280    type IntoIter = Iter<'a>;
281
282    fn into_iter(self) -> Self::IntoIter {
283        Iter(self.inner.iter())
284    }
285}
286
287impl FromIterator<(Key, (Value, BaggageMetadata))> for Baggage {
288    fn from_iter<I: IntoIterator<Item = (Key, (Value, BaggageMetadata))>>(iter: I) -> Self {
289        let mut baggage = Baggage::default();
290        for (key, (value, metadata)) in iter.into_iter() {
291            baggage.insert_with_metadata(key, value, metadata);
292        }
293        baggage
294    }
295}
296
297impl FromIterator<KeyValue> for Baggage {
298    fn from_iter<I: IntoIterator<Item = KeyValue>>(iter: I) -> Self {
299        let mut baggage = Baggage::default();
300        for kv in iter.into_iter() {
301            baggage.insert(kv.key, kv.value);
302        }
303        baggage
304    }
305}
306
307impl FromIterator<KeyValueMetadata> for Baggage {
308    fn from_iter<I: IntoIterator<Item = KeyValueMetadata>>(iter: I) -> Self {
309        let mut baggage = Baggage::default();
310        for kvm in iter.into_iter() {
311            baggage.insert_with_metadata(kvm.key, kvm.value, kvm.metadata);
312        }
313        baggage
314    }
315}
316
317/// Methods for sorting and retrieving baggage data in a context.
318pub trait BaggageExt {
319    /// Returns a clone of the given context with the included name-value pairs.
320    ///
321    /// # Examples
322    ///
323    /// ```
324    /// use opentelemetry::{baggage::BaggageExt, Context, KeyValue, Value};
325    ///
326    /// let some_context = Context::current();
327    /// let cx = some_context.with_baggage(vec![KeyValue::new("my-name", "my-value")]);
328    ///
329    /// assert_eq!(
330    ///     cx.baggage().get("my-name"),
331    ///     Some(&Value::from("my-value")),
332    /// )
333    /// ```
334    fn with_baggage<T: IntoIterator<Item = I>, I: Into<KeyValueMetadata>>(
335        &self,
336        baggage: T,
337    ) -> Self;
338
339    /// Returns a clone of the current context with the included name-value pairs.
340    ///
341    /// # Examples
342    ///
343    /// ```
344    /// use opentelemetry::{baggage::BaggageExt, Context, KeyValue, Value};
345    ///
346    /// let cx = Context::current_with_baggage(vec![KeyValue::new("my-name", "my-value")]);
347    ///
348    /// assert_eq!(
349    ///     cx.baggage().get("my-name"),
350    ///     Some(&Value::from("my-value")),
351    /// )
352    /// ```
353    fn current_with_baggage<T: IntoIterator<Item = I>, I: Into<KeyValueMetadata>>(
354        baggage: T,
355    ) -> Self;
356
357    /// Returns a clone of the given context with the included name-value pairs.
358    ///
359    /// # Examples
360    ///
361    /// ```
362    /// use opentelemetry::{baggage::BaggageExt, Context, KeyValue, Value};
363    ///
364    /// let cx = Context::current().with_cleared_baggage();
365    ///
366    /// assert_eq!(cx.baggage().len(), 0);
367    /// ```
368    fn with_cleared_baggage(&self) -> Self;
369
370    /// Returns a reference to this context's baggage, or the default
371    /// empty baggage if none has been set.
372    fn baggage(&self) -> &Baggage;
373}
374
375impl BaggageExt for Context {
376    fn with_baggage<T: IntoIterator<Item = I>, I: Into<KeyValueMetadata>>(
377        &self,
378        baggage: T,
379    ) -> Self {
380        let mut merged: Baggage = self
381            .baggage()
382            .iter()
383            .map(|(key, (value, metadata))| {
384                KeyValueMetadata::new(key.clone(), value.clone(), metadata.clone())
385            })
386            .collect();
387        for kvm in baggage.into_iter().map(|kv| kv.into()) {
388            merged.insert_with_metadata(kvm.key, kvm.value, kvm.metadata);
389        }
390
391        self.with_value(merged)
392    }
393
394    fn current_with_baggage<T: IntoIterator<Item = I>, I: Into<KeyValueMetadata>>(kvs: T) -> Self {
395        Context::current().with_baggage(kvs)
396    }
397
398    fn with_cleared_baggage(&self) -> Self {
399        self.with_value(Baggage::new())
400    }
401
402    fn baggage(&self) -> &Baggage {
403        self.get::<Baggage>().unwrap_or(&DEFAULT_BAGGAGE)
404    }
405}
406
407/// An optional property set that can be added to [`Baggage`] values.
408///
409/// `BaggageMetadata` can be added to values in the form of a property set,
410/// represented as semi-colon `;` delimited list of names and/or name-value
411/// pairs, e.g. `;k1=v1;k2;k3=v3`.
412#[cfg_attr(feature = "serialize", derive(Deserialize, Serialize))]
413#[derive(Clone, Debug, PartialOrd, PartialEq, Default)]
414pub struct BaggageMetadata(String);
415
416impl BaggageMetadata {
417    /// Return underlying string
418    pub fn as_str(&self) -> &str {
419        self.0.as_str()
420    }
421}
422
423impl From<String> for BaggageMetadata {
424    fn from(s: String) -> BaggageMetadata {
425        BaggageMetadata(s.trim().to_string())
426    }
427}
428
429impl From<&str> for BaggageMetadata {
430    fn from(s: &str) -> Self {
431        BaggageMetadata(s.trim().to_string())
432    }
433}
434
435/// [`Baggage`] name-value pairs with their associated metadata.
436#[cfg_attr(feature = "serialize", derive(Deserialize, Serialize))]
437#[derive(Clone, Debug, PartialEq)]
438pub struct KeyValueMetadata {
439    /// Dimension or event key
440    pub key: Key,
441    /// Dimension or event value
442    pub value: Value,
443    /// Metadata associate with this key value pair
444    pub metadata: BaggageMetadata,
445}
446
447impl KeyValueMetadata {
448    /// Create a new `KeyValue` pair with metadata
449    pub fn new<K, V, S>(key: K, value: V, metadata: S) -> Self
450    where
451        K: Into<Key>,
452        V: Into<Value>,
453        S: Into<BaggageMetadata>,
454    {
455        KeyValueMetadata {
456            key: key.into(),
457            value: value.into(),
458            metadata: metadata.into(),
459        }
460    }
461}
462
463impl From<KeyValue> for KeyValueMetadata {
464    fn from(kv: KeyValue) -> Self {
465        KeyValueMetadata {
466            key: kv.key,
467            value: kv.value,
468            metadata: BaggageMetadata::default(),
469        }
470    }
471}
472
473#[cfg(test)]
474mod tests {
475    use super::*;
476
477    #[test]
478    fn insert_non_ascii_key() {
479        let mut baggage = Baggage::new();
480        baggage.insert("🚫", "not ascii key");
481        assert_eq!(baggage.len(), 0, "did not insert invalid key");
482    }
483
484    #[test]
485    fn insert_too_much_baggage() {
486        // too many key pairs
487        let over_limit = MAX_KEY_VALUE_PAIRS + 1;
488        let mut data = Vec::with_capacity(over_limit);
489        for i in 0..over_limit {
490            data.push(KeyValue::new(format!("key{}", i), format!("key{}", i)))
491        }
492        let baggage = data.into_iter().collect::<Baggage>();
493        assert_eq!(baggage.len(), MAX_KEY_VALUE_PAIRS)
494    }
495
496    #[test]
497    fn insert_too_long_pair() {
498        let pair = KeyValue::new(
499            "test",
500            String::from_utf8_lossy(vec![12u8; MAX_BYTES_FOR_ONE_PAIR].as_slice()).to_string(),
501        );
502        let mut baggage = Baggage::default();
503        baggage.insert(pair.key.clone(), pair.value.clone());
504        assert_eq!(
505            baggage.len(),
506            0,
507            "The input pair is too long to insert into baggage"
508        );
509
510        baggage.insert("test", "value");
511        baggage.insert(pair.key.clone(), pair.value);
512        assert_eq!(
513            baggage.get(pair.key),
514            Some(&Value::from("value")),
515            "If the input pair is too long, then don't replace entry with same key"
516        )
517    }
518
519    #[test]
520    fn insert_pairs_length_exceed() {
521        let mut data = vec![];
522        for letter in vec!['a', 'b', 'c', 'd'].into_iter() {
523            data.push(KeyValue::new(
524                (0..MAX_LEN_OF_ALL_PAIRS / 3)
525                    .map(|_| letter)
526                    .collect::<String>(),
527                "",
528            ));
529        }
530        let baggage = data.into_iter().collect::<Baggage>();
531        assert_eq!(baggage.len(), 3)
532    }
533}