opentelemetry/
baggage.rs

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