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}