Skip to main content

opentelemetry/trace/
span_context.rs

1use crate::{SpanId, TraceFlags, TraceId};
2use std::collections::VecDeque;
3use std::hash::Hash;
4use std::str::FromStr;
5use thiserror::Error;
6
7/// TraceState carries system-specific configuration data, represented as a list
8/// of key-value pairs. TraceState allows multiple tracing systems to
9/// participate in the same trace.
10///
11/// Please review the [W3C specification] for details on this field.
12///
13/// [W3C specification]: https://www.w3.org/TR/trace-context-2/#tracestate-header
14#[derive(Clone, Debug, Default, Eq, PartialEq, Hash)]
15pub struct TraceState(Option<VecDeque<(String, String)>>);
16
17const MAX_LIST_MEMBERS: usize = 32;
18
19impl TraceState {
20    /// The default `TraceState`, as a constant
21    pub const NONE: TraceState = TraceState(None);
22
23    /// Validates that the given `TraceState` list-member key is valid per the [W3 Spec].
24    ///
25    /// [W3 Spec]: https://www.w3.org/TR/trace-context-2/#key
26    fn valid_key(key: &str) -> bool {
27        if key.len() > 256 {
28            return false;
29        }
30
31        let allowed_special = |b: u8| b == b'_' || b == b'-' || b == b'*' || b == b'/';
32        let mut vendor_start = None;
33        for (i, &b) in key.as_bytes().iter().enumerate() {
34            if !(b.is_ascii_lowercase() || b.is_ascii_digit() || allowed_special(b) || b == b'@') {
35                return false;
36            }
37
38            if i == 0 && (!b.is_ascii_lowercase() && !b.is_ascii_digit()) {
39                return false;
40            } else if b == b'@' {
41                if vendor_start.is_some() || i + 14 < key.len() {
42                    return false;
43                }
44                vendor_start = Some(i);
45            } else if let Some(start) = vendor_start {
46                if i == start + 1 && !(b.is_ascii_lowercase() || b.is_ascii_digit()) {
47                    return false;
48                }
49            }
50        }
51
52        true
53    }
54
55    /// Validates that the given `TraceState` list-member value is valid per the [W3 Spec].
56    ///
57    /// [W3 Spec]: https://www.w3.org/TR/trace-context-2/#value
58    fn valid_value(value: &str) -> bool {
59        if value.len() > 256 {
60            return false;
61        }
62
63        !(value.contains(',') || value.contains('='))
64    }
65
66    /// Creates a new `TraceState` from the given key-value collection, keeping at most the 32
67    /// list-members the [W3C specification] allows. Pairs beyond the 32nd are dropped without
68    /// being validated, so a key or value that would otherwise make this return an `Err` has no
69    /// effect if it only appears past the limit.
70    ///
71    /// [W3C specification]: https://www.w3.org/TR/trace-context-2/#tracestate-header-field-values
72    ///
73    /// # Examples
74    ///
75    /// ```
76    /// use opentelemetry::trace::TraceState;
77    ///
78    /// let kvs = vec![("foo", "bar"), ("apple", "banana")];
79    /// let trace_state = TraceState::from_key_value(kvs);
80    ///
81    /// assert!(trace_state.is_ok());
82    /// assert_eq!(trace_state.unwrap().header(), String::from("foo=bar,apple=banana"))
83    /// ```
84    pub fn from_key_value<T, K, V>(trace_state: T) -> TraceStateResult<Self>
85    where
86        T: IntoIterator<Item = (K, V)>,
87        K: ToString,
88        V: ToString,
89    {
90        let ordered_data = trace_state
91            .into_iter()
92            .take(MAX_LIST_MEMBERS)
93            .map(|(key, value)| {
94                let (key, value) = (key.to_string(), value.to_string());
95                if !TraceState::valid_key(key.as_str()) {
96                    return Err(TraceStateError::Key(key));
97                }
98                if !TraceState::valid_value(value.as_str()) {
99                    return Err(TraceStateError::Value(value));
100                }
101
102                Ok((key, value))
103            })
104            .collect::<Result<VecDeque<_>, TraceStateError>>()?;
105
106        if ordered_data.is_empty() {
107            Ok(TraceState(None))
108        } else {
109            Ok(TraceState(Some(ordered_data)))
110        }
111    }
112
113    /// Retrieves a value for a given key from the `TraceState` if it exists.
114    pub fn get(&self, key: &str) -> Option<&str> {
115        self.0.as_ref().and_then(|kvs| {
116            kvs.iter().find_map(|item| {
117                if item.0.as_str() == key {
118                    Some(item.1.as_str())
119                } else {
120                    None
121                }
122            })
123        })
124    }
125
126    /// Inserts the given key-value pair into the `TraceState`. If a value already exists for the
127    /// given key, this updates the value and updates the value's position. If the key or value are
128    /// invalid per the [W3 Spec] an `Err` is returned, else a new `TraceState` with the
129    /// updated key/value is returned.
130    ///
131    /// A `TraceState` holds at most the 32 list-members the [W3 Spec] allows. Inserting into a full
132    /// `TraceState` keeps the inserted pair and drops the last list-member.
133    ///
134    /// [W3 Spec]: https://www.w3.org/TR/trace-context-2/#mutating-the-tracestate-field
135    pub fn insert<K, V>(&self, key: K, value: V) -> TraceStateResult<TraceState>
136    where
137        K: Into<String>,
138        V: Into<String>,
139    {
140        let (key, value) = (key.into(), value.into());
141        if !TraceState::valid_key(key.as_str()) {
142            return Err(TraceStateError::Key(key));
143        }
144        if !TraceState::valid_value(value.as_str()) {
145            return Err(TraceStateError::Value(value));
146        }
147
148        let mut trace_state = self.delete_from_deque(&key);
149        let kvs = trace_state.0.get_or_insert(VecDeque::with_capacity(1));
150        if kvs.len() >= MAX_LIST_MEMBERS {
151            kvs.truncate(MAX_LIST_MEMBERS - 1);
152        }
153
154        kvs.push_front((key, value));
155
156        Ok(trace_state)
157    }
158
159    /// Removes the given key-value pair from the `TraceState`. If the key is invalid per the
160    /// [W3 Spec] an `Err` is returned. Else, a new `TraceState`
161    /// with the removed entry is returned.
162    ///
163    /// If the key is not in `TraceState`. The original `TraceState` will be cloned and returned.
164    ///
165    /// [W3 Spec]: https://www.w3.org/TR/trace-context-2/#mutating-the-tracestate-field
166    pub fn delete<K: Into<String>>(&self, key: K) -> TraceStateResult<TraceState> {
167        let key = key.into();
168        if !TraceState::valid_key(key.as_str()) {
169            return Err(TraceStateError::Key(key));
170        }
171
172        Ok(self.delete_from_deque(&key))
173    }
174
175    /// Delete key from trace state's deque. The key MUST be valid
176    fn delete_from_deque(&self, key: &str) -> TraceState {
177        let mut owned = self.clone();
178        if let Some(kvs) = owned.0.as_mut() {
179            if let Some(index) = kvs.iter().position(|x| x.0 == key) {
180                kvs.remove(index);
181            }
182        }
183        owned
184    }
185
186    /// Creates a new `TraceState` header string, delimiting each key and value with a `=` and each
187    /// entry with a `,`.
188    pub fn header(&self) -> String {
189        self.header_delimited("=", ",")
190    }
191
192    /// Creates a new `TraceState` header string, with the given key/value delimiter and entry delimiter.
193    pub fn header_delimited(&self, entry_delimiter: &str, list_delimiter: &str) -> String {
194        self.0
195            .as_ref()
196            .map(|kvs| {
197                kvs.iter()
198                    .map(|(key, value)| format!("{key}{entry_delimiter}{value}"))
199                    .collect::<Vec<String>>()
200                    .join(list_delimiter)
201            })
202            .unwrap_or_default()
203    }
204}
205
206impl FromStr for TraceState {
207    type Err = TraceStateError;
208
209    fn from_str(s: &str) -> Result<Self, Self::Err> {
210        let mut key_value_pairs: Vec<(String, String)> = Vec::new();
211
212        for list_member in s.split_terminator(',').take(MAX_LIST_MEMBERS) {
213            // W3C OWS around list members consists of spaces and horizontal tabs.
214            let list_member = list_member.trim_matches([' ', '\t']);
215            if list_member.is_empty() {
216                continue;
217            }
218
219            match list_member.find('=') {
220                None => return Err(TraceStateError::List(list_member.to_string())),
221                Some(separator_index) => {
222                    let (key, value) = list_member.split_at(separator_index);
223                    key_value_pairs
224                        .push((key.to_string(), value.trim_start_matches('=').to_string()));
225                }
226            }
227        }
228
229        TraceState::from_key_value(key_value_pairs)
230    }
231}
232
233/// Iterator over TraceState key-value pairs as (&str, &str)
234#[derive(Debug)]
235pub struct TraceStateIter<'a> {
236    inner: Option<std::collections::vec_deque::Iter<'a, (String, String)>>,
237}
238
239impl<'a> Iterator for TraceStateIter<'a> {
240    type Item = (&'a str, &'a str);
241
242    fn next(&mut self) -> Option<Self::Item> {
243        self.inner
244            .as_mut()?
245            .next()
246            .map(|(key, value)| (key.as_str(), value.as_str()))
247    }
248
249    fn size_hint(&self) -> (usize, Option<usize>) {
250        match &self.inner {
251            Some(iter) => iter.size_hint(),
252            None => (0, Some(0)),
253        }
254    }
255}
256
257impl ExactSizeIterator for TraceStateIter<'_> {
258    fn len(&self) -> usize {
259        match &self.inner {
260            Some(iter) => iter.len(),
261            None => 0,
262        }
263    }
264}
265
266impl<'a> IntoIterator for &'a TraceState {
267    type Item = (&'a str, &'a str);
268    type IntoIter = TraceStateIter<'a>;
269
270    fn into_iter(self) -> Self::IntoIter {
271        TraceStateIter {
272            inner: self.0.as_ref().map(|deque| deque.iter()),
273        }
274    }
275}
276
277/// A specialized `Result` type for trace state operations.
278type TraceStateResult<T> = Result<T, TraceStateError>;
279
280/// Error returned by `TraceState` operations.
281#[derive(Error, Debug)]
282#[non_exhaustive]
283pub enum TraceStateError {
284    /// The key is invalid.
285    ///
286    /// See <https://www.w3.org/TR/trace-context-2/#key> for requirement for keys.
287    #[error("{0} is not a valid key in TraceState, see https://www.w3.org/TR/trace-context-2/#key for more details")]
288    Key(String),
289
290    /// The value is invalid.
291    ///
292    /// See <https://www.w3.org/TR/trace-context-2/#value> for requirement for values.
293    #[error("{0} is not a valid value in TraceState, see https://www.w3.org/TR/trace-context-2/#value for more details")]
294    Value(String),
295
296    /// The list is invalid.
297    ///
298    /// See <https://www.w3.org/TR/trace-context-2/#list> for requirement for list members.
299    #[error("{0} is not a valid list member in TraceState, see https://www.w3.org/TR/trace-context-2/#list for more details")]
300    List(String),
301}
302
303/// Immutable portion of a [`Span`] which can be serialized and propagated.
304///
305/// This representation conforms to the [W3C TraceContext specification].
306///
307/// Spans that do not have the `sampled` flag set in their [`TraceFlags`] will
308/// be ignored by most tracing tools.
309///
310/// [`Span`]: crate::trace::Span
311/// [W3C TraceContext specification]: https://www.w3.org/TR/trace-context
312#[derive(Clone, Debug, PartialEq, Hash, Eq)]
313pub struct SpanContext {
314    trace_id: TraceId,
315    span_id: SpanId,
316    trace_flags: TraceFlags,
317    is_remote: bool,
318    trace_state: TraceState,
319}
320
321impl SpanContext {
322    /// An invalid span context
323    pub const NONE: SpanContext = SpanContext {
324        trace_id: TraceId::INVALID,
325        span_id: SpanId::INVALID,
326        trace_flags: TraceFlags::NOT_SAMPLED,
327        is_remote: false,
328        trace_state: TraceState::NONE,
329    };
330
331    /// Create an invalid empty span context
332    pub fn empty_context() -> Self {
333        SpanContext::NONE
334    }
335
336    /// Construct a new `SpanContext`
337    pub fn new(
338        trace_id: TraceId,
339        span_id: SpanId,
340        trace_flags: TraceFlags,
341        is_remote: bool,
342        trace_state: TraceState,
343    ) -> Self {
344        SpanContext {
345            trace_id,
346            span_id,
347            trace_flags,
348            is_remote,
349            trace_state,
350        }
351    }
352
353    /// The [`TraceId`] for this span context.
354    pub fn trace_id(&self) -> TraceId {
355        self.trace_id
356    }
357
358    /// The [`SpanId`] for this span context.
359    pub fn span_id(&self) -> SpanId {
360        self.span_id
361    }
362
363    /// Returns details about the trace.
364    ///
365    /// Unlike `TraceState` values, these are present in all traces. The current
366    /// version of the specification only supports a single flag [`TraceFlags::SAMPLED`].
367    pub fn trace_flags(&self) -> TraceFlags {
368        self.trace_flags
369    }
370
371    /// Returns `true` if the span context has a valid (non-zero) `trace_id` and a
372    /// valid (non-zero) `span_id`.
373    pub fn is_valid(&self) -> bool {
374        self.trace_id != TraceId::INVALID && self.span_id != SpanId::INVALID
375    }
376
377    /// Returns `true` if the span context was propagated from a remote parent.
378    pub fn is_remote(&self) -> bool {
379        self.is_remote
380    }
381
382    /// Returns `true` if the `sampled` trace flag is set.
383    ///
384    /// Spans that are not sampled will be ignored by most tracing tools.
385    pub fn is_sampled(&self) -> bool {
386        self.trace_flags.is_sampled()
387    }
388
389    /// A reference to the span context's [`TraceState`].
390    pub fn trace_state(&self) -> &TraceState {
391        &self.trace_state
392    }
393}
394
395#[cfg(test)]
396mod tests {
397    use super::*;
398    use crate::{trace::TraceContextExt, Context};
399
400    #[rustfmt::skip]
401    fn trace_state_test_data() -> Vec<(TraceState, &'static str, &'static str)> {
402        vec![
403            (TraceState::from_key_value(vec![("foo", "bar")]).unwrap(), "foo=bar", "foo"),
404            (TraceState::from_key_value(vec![("foo", ""), ("apple", "banana")]).unwrap(), "foo=,apple=banana", "apple"),
405            (TraceState::from_key_value(vec![("foo", "bar"), ("apple", "banana")]).unwrap(), "foo=bar,apple=banana", "apple"),
406        ]
407    }
408
409    #[test]
410    fn test_trace_state() {
411        for test_case in trace_state_test_data() {
412            assert_eq!(test_case.0.clone().header(), test_case.1);
413
414            let new_key = format!("{}-{}", test_case.0.get(test_case.2).unwrap(), "test");
415
416            let updated_trace_state = test_case.0.insert(test_case.2, new_key.clone());
417            assert!(updated_trace_state.is_ok());
418            let updated_trace_state = updated_trace_state.unwrap();
419
420            let updated = format!("{}={}", test_case.2, new_key);
421
422            let index = updated_trace_state.clone().header().find(&updated);
423
424            assert!(index.is_some());
425            assert_eq!(index.unwrap(), 0);
426
427            let deleted_trace_state = updated_trace_state.delete(test_case.2.to_string());
428            assert!(deleted_trace_state.is_ok());
429
430            let deleted_trace_state = deleted_trace_state.unwrap();
431
432            assert!(deleted_trace_state.get(test_case.2).is_none());
433        }
434    }
435
436    #[test]
437    fn test_trace_state_key() {
438        let test_data: Vec<(&'static str, bool)> = vec![
439            ("123", true),
440            ("bar", true),
441            ("foo@bar", true),
442            ("foo@0123456789abcdef", false),
443            ("foo@012345678", true),
444            ("FOO@BAR", false),
445            ("你好", false),
446        ];
447
448        for (key, expected) in test_data {
449            assert_eq!(TraceState::valid_key(key), expected, "test key: {key:?}");
450        }
451    }
452
453    #[test]
454    fn test_trace_state_insert() {
455        let trace_state = TraceState::from_key_value(vec![("foo", "bar")]).unwrap();
456        let inserted_trace_state = trace_state.insert("testkey", "testvalue").unwrap();
457        assert!(trace_state.get("testkey").is_none()); // The original state doesn't change
458        assert_eq!(inserted_trace_state.get("testkey").unwrap(), "testvalue"); //
459    }
460
461    #[test]
462    fn test_context_span_debug() {
463        let cx = Context::current();
464        assert_eq!(
465            format!("{cx:?}"),
466            "Context { span: \"None\", entries count: 0, suppress_telemetry: false }"
467        );
468        let cx = Context::current().with_remote_span_context(SpanContext::NONE);
469        assert_eq!(
470            format!("{cx:?}"),
471            "Context { \
472               span: SpanContext { \
473                       trace_id: 00000000000000000000000000000000, \
474                       span_id: 0000000000000000, \
475                       trace_flags: TraceFlags(0), \
476                       is_remote: false, \
477                       trace_state: TraceState(None) \
478                     }, \
479               entries count: 1, suppress_telemetry: false \
480             }"
481        );
482    }
483
484    #[test]
485    fn test_tracestate_iter_empty() {
486        let ts = TraceState::NONE;
487        let mut iter = ts.into_iter();
488        assert_eq!(iter.next(), None);
489        assert_eq!(iter.size_hint(), (0, Some(0)));
490        assert_eq!(iter.len(), 0);
491    }
492
493    #[test]
494    fn test_tracestate_iter_single() {
495        let ts = TraceState::from_key_value(vec![("foo", "bar")]).unwrap();
496        let mut iter = ts.into_iter();
497        assert_eq!(iter.next(), Some(("foo", "bar")));
498        assert_eq!(iter.next(), None);
499        assert_eq!(iter.size_hint(), (0, Some(0)));
500    }
501
502    #[test]
503    fn test_tracestate_iter_multiple() {
504        let ts = TraceState::from_key_value(vec![("foo", "bar"), ("apple", "banana")]).unwrap();
505        let mut iter = ts.into_iter();
506        assert_eq!(iter.next(), Some(("foo", "bar")));
507        assert_eq!(iter.next(), Some(("apple", "banana")));
508        assert_eq!(iter.next(), None);
509    }
510
511    #[test]
512    fn test_tracestate_iter_size_hint_and_len() {
513        let ts = TraceState::from_key_value(vec![("foo", "bar"), ("apple", "banana")]).unwrap();
514        let iter = ts.into_iter();
515        assert_eq!(iter.size_hint(), (2, Some(2)));
516        assert_eq!(iter.len(), 2);
517    }
518
519    #[test]
520    fn test_tracestate_from_str_keeps_at_most_32_list_members() {
521        let header = (0..64)
522            .map(|i| format!("key{i}=value{i}"))
523            .collect::<Vec<_>>()
524            .join(",");
525        let trace_state = TraceState::from_str(&header).unwrap();
526
527        assert_eq!(trace_state.into_iter().count(), 32);
528        assert_eq!(trace_state.get("key0"), Some("value0"));
529        assert_eq!(trace_state.get("key31"), Some("value31"));
530        assert_eq!(trace_state.get("key32"), None);
531
532        let expected_header = (0..32)
533            .map(|i| format!("key{i}=value{i}"))
534            .collect::<Vec<_>>()
535            .join(",");
536        assert_eq!(trace_state.header(), expected_header);
537    }
538
539    #[test]
540    fn test_tracestate_from_str_allows_optional_whitespace_around_members() {
541        for header in [
542            "rojo=1, congo=2",
543            " rojo=1,\tcongo=2 \t",
544            "rojo=1 \t, congo=2",
545        ] {
546            let trace_state = TraceState::from_str(header).unwrap();
547
548            assert_eq!(trace_state.header(), "rojo=1,congo=2", "{header:?}");
549        }
550    }
551
552    #[test]
553    fn test_tracestate_from_str_preserves_leading_spaces_in_values() {
554        let trace_state = TraceState::from_str("rojo= 1 , congo=  2\t").unwrap();
555
556        assert_eq!(trace_state.get("rojo"), Some(" 1"));
557        assert_eq!(trace_state.get("congo"), Some("  2"));
558    }
559
560    #[test]
561    fn test_tracestate_from_str_ignores_empty_members() {
562        let trace_state = TraceState::from_str("rojo=1, ,\t,congo=2").unwrap();
563
564        assert_eq!(trace_state.get("rojo"), Some("1"));
565        assert_eq!(trace_state.get("congo"), Some("2"));
566        assert_eq!(trace_state.into_iter().count(), 2);
567
568        assert_eq!(TraceState::from_str(" \t ").unwrap(), TraceState::NONE);
569    }
570    #[test]
571    fn test_tracestate_from_str_ignores_invalid_members_beyond_the_limit() {
572        let mut members: Vec<String> = (0..32).map(|i| format!("key{i}=value{i}")).collect();
573        members.push("invalid-no-separator".to_string());
574        let trace_state = TraceState::from_str(&members.join(",")).unwrap();
575
576        assert_eq!(trace_state.into_iter().count(), 32);
577    }
578    #[test]
579    fn test_tracestate_from_str_rejects_invalid_members_within_the_limit() {
580        let mut members: Vec<String> = (0..31).map(|i| format!("key{i}=value{i}")).collect();
581        members.push("invalid-no-separator".to_string());
582
583        assert!(TraceState::from_str(&members.join(",")).is_err());
584    }
585    #[test]
586    fn test_tracestate_from_key_value_keeps_at_most_32_list_members() {
587        let kvs: Vec<(String, String)> = (0..64)
588            .map(|i| (format!("key{i}"), format!("value{i}")))
589            .collect();
590        let trace_state = TraceState::from_key_value(kvs).unwrap();
591
592        assert_eq!(trace_state.into_iter().count(), 32);
593        assert_eq!(trace_state.get("key0"), Some("value0"));
594        assert_eq!(trace_state.get("key32"), None);
595    }
596    #[test]
597    fn test_tracestate_insert_keeps_at_most_32_list_members() {
598        let mut trace_state = TraceState::default();
599        for i in 0..64 {
600            trace_state = trace_state
601                .insert(format!("key{i}"), format!("value{i}"))
602                .unwrap();
603        }
604
605        assert_eq!(trace_state.into_iter().count(), 32);
606        assert_eq!(trace_state.get("key63"), Some("value63"));
607        assert_eq!(trace_state.get("key32"), Some("value32"));
608        assert_eq!(trace_state.get("key31"), None);
609    }
610    #[test]
611    fn test_tracestate_insert_of_an_existing_key_evicts_nothing() {
612        let mut trace_state = TraceState::default();
613        for i in 0..32 {
614            trace_state = trace_state
615                .insert(format!("key{i}"), format!("value{i}"))
616                .unwrap();
617        }
618        let updated = trace_state.insert("key20", "updated").unwrap();
619
620        assert_eq!(updated.into_iter().count(), 32);
621        assert_eq!(updated.get("key20"), Some("updated"));
622        assert_eq!(updated.get("key0"), Some("value0"));
623    }
624}