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/#tracestate-header
14#[derive(Clone, Debug, Default, Eq, PartialEq, Hash)]
15pub struct TraceState(Option<VecDeque<(String, String)>>);
16
17impl TraceState {
18    /// The default `TraceState`, as a constant
19    pub const NONE: TraceState = TraceState(None);
20
21    /// Validates that the given `TraceState` list-member key is valid per the [W3 Spec].
22    ///
23    /// [W3 Spec]: https://www.w3.org/TR/trace-context/#key
24    fn valid_key(key: &str) -> bool {
25        if key.len() > 256 {
26            return false;
27        }
28
29        let allowed_special = |b: u8| b == b'_' || b == b'-' || b == b'*' || b == b'/';
30        let mut vendor_start = None;
31        for (i, &b) in key.as_bytes().iter().enumerate() {
32            if !(b.is_ascii_lowercase() || b.is_ascii_digit() || allowed_special(b) || b == b'@') {
33                return false;
34            }
35
36            if i == 0 && (!b.is_ascii_lowercase() && !b.is_ascii_digit()) {
37                return false;
38            } else if b == b'@' {
39                if vendor_start.is_some() || i + 14 < key.len() {
40                    return false;
41                }
42                vendor_start = Some(i);
43            } else if let Some(start) = vendor_start {
44                if i == start + 1 && !(b.is_ascii_lowercase() || b.is_ascii_digit()) {
45                    return false;
46                }
47            }
48        }
49
50        true
51    }
52
53    /// Validates that the given `TraceState` list-member value is valid per the [W3 Spec].
54    ///
55    /// [W3 Spec]: https://www.w3.org/TR/trace-context/#value
56    fn valid_value(value: &str) -> bool {
57        if value.len() > 256 {
58            return false;
59        }
60
61        !(value.contains(',') || value.contains('='))
62    }
63
64    /// Creates a new `TraceState` from the given key-value collection.
65    ///
66    /// # Examples
67    ///
68    /// ```
69    /// use opentelemetry::trace::TraceState;
70    ///
71    /// let kvs = vec![("foo", "bar"), ("apple", "banana")];
72    /// let trace_state = TraceState::from_key_value(kvs);
73    ///
74    /// assert!(trace_state.is_ok());
75    /// assert_eq!(trace_state.unwrap().header(), String::from("foo=bar,apple=banana"))
76    /// ```
77    pub fn from_key_value<T, K, V>(trace_state: T) -> TraceStateResult<Self>
78    where
79        T: IntoIterator<Item = (K, V)>,
80        K: ToString,
81        V: ToString,
82    {
83        let ordered_data = trace_state
84            .into_iter()
85            .map(|(key, value)| {
86                let (key, value) = (key.to_string(), value.to_string());
87                if !TraceState::valid_key(key.as_str()) {
88                    return Err(TraceStateError::Key(key));
89                }
90                if !TraceState::valid_value(value.as_str()) {
91                    return Err(TraceStateError::Value(value));
92                }
93
94                Ok((key, value))
95            })
96            .collect::<Result<VecDeque<_>, TraceStateError>>()?;
97
98        if ordered_data.is_empty() {
99            Ok(TraceState(None))
100        } else {
101            Ok(TraceState(Some(ordered_data)))
102        }
103    }
104
105    /// Retrieves a value for a given key from the `TraceState` if it exists.
106    pub fn get(&self, key: &str) -> Option<&str> {
107        self.0.as_ref().and_then(|kvs| {
108            kvs.iter().find_map(|item| {
109                if item.0.as_str() == key {
110                    Some(item.1.as_str())
111                } else {
112                    None
113                }
114            })
115        })
116    }
117
118    /// Inserts the given key-value pair into the `TraceState`. If a value already exists for the
119    /// given key, this updates the value and updates the value's position. If the key or value are
120    /// invalid per the [W3 Spec] an `Err` is returned, else a new `TraceState` with the
121    /// updated key/value is returned.
122    ///
123    /// [W3 Spec]: https://www.w3.org/TR/trace-context/#mutating-the-tracestate-field
124    pub fn insert<K, V>(&self, key: K, value: V) -> TraceStateResult<TraceState>
125    where
126        K: Into<String>,
127        V: Into<String>,
128    {
129        let (key, value) = (key.into(), value.into());
130        if !TraceState::valid_key(key.as_str()) {
131            return Err(TraceStateError::Key(key));
132        }
133        if !TraceState::valid_value(value.as_str()) {
134            return Err(TraceStateError::Value(value));
135        }
136
137        let mut trace_state = self.delete_from_deque(&key);
138        let kvs = trace_state.0.get_or_insert(VecDeque::with_capacity(1));
139
140        kvs.push_front((key, value));
141
142        Ok(trace_state)
143    }
144
145    /// Removes the given key-value pair from the `TraceState`. If the key is invalid per the
146    /// [W3 Spec] an `Err` is returned. Else, a new `TraceState`
147    /// with the removed entry is returned.
148    ///
149    /// If the key is not in `TraceState`. The original `TraceState` will be cloned and returned.
150    ///
151    /// [W3 Spec]: https://www.w3.org/TR/trace-context/#mutating-the-tracestate-field
152    pub fn delete<K: Into<String>>(&self, key: K) -> TraceStateResult<TraceState> {
153        let key = key.into();
154        if !TraceState::valid_key(key.as_str()) {
155            return Err(TraceStateError::Key(key));
156        }
157
158        Ok(self.delete_from_deque(&key))
159    }
160
161    /// Delete key from trace state's deque. The key MUST be valid
162    fn delete_from_deque(&self, key: &str) -> TraceState {
163        let mut owned = self.clone();
164        if let Some(kvs) = owned.0.as_mut() {
165            if let Some(index) = kvs.iter().position(|x| x.0 == key) {
166                kvs.remove(index);
167            }
168        }
169        owned
170    }
171
172    /// Creates a new `TraceState` header string, delimiting each key and value with a `=` and each
173    /// entry with a `,`.
174    pub fn header(&self) -> String {
175        self.header_delimited("=", ",")
176    }
177
178    /// Creates a new `TraceState` header string, with the given key/value delimiter and entry delimiter.
179    pub fn header_delimited(&self, entry_delimiter: &str, list_delimiter: &str) -> String {
180        self.0
181            .as_ref()
182            .map(|kvs| {
183                kvs.iter()
184                    .map(|(key, value)| format!("{key}{entry_delimiter}{value}"))
185                    .collect::<Vec<String>>()
186                    .join(list_delimiter)
187            })
188            .unwrap_or_default()
189    }
190}
191
192impl FromStr for TraceState {
193    type Err = TraceStateError;
194
195    fn from_str(s: &str) -> Result<Self, Self::Err> {
196        let list_members: Vec<&str> = s.split_terminator(',').collect();
197        let mut key_value_pairs: Vec<(String, String)> = Vec::with_capacity(list_members.len());
198
199        for list_member in list_members {
200            match list_member.find('=') {
201                None => return Err(TraceStateError::List(list_member.to_string())),
202                Some(separator_index) => {
203                    let (key, value) = list_member.split_at(separator_index);
204                    key_value_pairs
205                        .push((key.to_string(), value.trim_start_matches('=').to_string()));
206                }
207            }
208        }
209
210        TraceState::from_key_value(key_value_pairs)
211    }
212}
213
214/// Iterator over TraceState key-value pairs as (&str, &str)
215#[derive(Debug)]
216pub struct TraceStateIter<'a> {
217    inner: Option<std::collections::vec_deque::Iter<'a, (String, String)>>,
218}
219
220impl<'a> Iterator for TraceStateIter<'a> {
221    type Item = (&'a str, &'a str);
222
223    fn next(&mut self) -> Option<Self::Item> {
224        self.inner
225            .as_mut()?
226            .next()
227            .map(|(key, value)| (key.as_str(), value.as_str()))
228    }
229
230    fn size_hint(&self) -> (usize, Option<usize>) {
231        match &self.inner {
232            Some(iter) => iter.size_hint(),
233            None => (0, Some(0)),
234        }
235    }
236}
237
238impl ExactSizeIterator for TraceStateIter<'_> {
239    fn len(&self) -> usize {
240        match &self.inner {
241            Some(iter) => iter.len(),
242            None => 0,
243        }
244    }
245}
246
247impl<'a> IntoIterator for &'a TraceState {
248    type Item = (&'a str, &'a str);
249    type IntoIter = TraceStateIter<'a>;
250
251    fn into_iter(self) -> Self::IntoIter {
252        TraceStateIter {
253            inner: self.0.as_ref().map(|deque| deque.iter()),
254        }
255    }
256}
257
258/// A specialized `Result` type for trace state operations.
259type TraceStateResult<T> = Result<T, TraceStateError>;
260
261/// Error returned by `TraceState` operations.
262#[derive(Error, Debug)]
263#[non_exhaustive]
264pub enum TraceStateError {
265    /// The key is invalid.
266    ///
267    /// See <https://www.w3.org/TR/trace-context/#key> for requirement for keys.
268    #[error("{0} is not a valid key in TraceState, see https://www.w3.org/TR/trace-context/#key for more details")]
269    Key(String),
270
271    /// The value is invalid.
272    ///
273    /// See <https://www.w3.org/TR/trace-context/#value> for requirement for values.
274    #[error("{0} is not a valid value in TraceState, see https://www.w3.org/TR/trace-context/#value for more details")]
275    Value(String),
276
277    /// The list is invalid.
278    ///
279    /// See <https://www.w3.org/TR/trace-context/#list> for requirement for list members.
280    #[error("{0} is not a valid list member in TraceState, see https://www.w3.org/TR/trace-context/#list for more details")]
281    List(String),
282}
283
284/// Immutable portion of a [`Span`] which can be serialized and propagated.
285///
286/// This representation conforms to the [W3C TraceContext specification].
287///
288/// Spans that do not have the `sampled` flag set in their [`TraceFlags`] will
289/// be ignored by most tracing tools.
290///
291/// [`Span`]: crate::trace::Span
292/// [W3C TraceContext specification]: https://www.w3.org/TR/trace-context
293#[derive(Clone, Debug, PartialEq, Hash, Eq)]
294pub struct SpanContext {
295    trace_id: TraceId,
296    span_id: SpanId,
297    trace_flags: TraceFlags,
298    is_remote: bool,
299    trace_state: TraceState,
300}
301
302impl SpanContext {
303    /// An invalid span context
304    pub const NONE: SpanContext = SpanContext {
305        trace_id: TraceId::INVALID,
306        span_id: SpanId::INVALID,
307        trace_flags: TraceFlags::NOT_SAMPLED,
308        is_remote: false,
309        trace_state: TraceState::NONE,
310    };
311
312    /// Create an invalid empty span context
313    pub fn empty_context() -> Self {
314        SpanContext::NONE
315    }
316
317    /// Construct a new `SpanContext`
318    pub fn new(
319        trace_id: TraceId,
320        span_id: SpanId,
321        trace_flags: TraceFlags,
322        is_remote: bool,
323        trace_state: TraceState,
324    ) -> Self {
325        SpanContext {
326            trace_id,
327            span_id,
328            trace_flags,
329            is_remote,
330            trace_state,
331        }
332    }
333
334    /// The [`TraceId`] for this span context.
335    pub fn trace_id(&self) -> TraceId {
336        self.trace_id
337    }
338
339    /// The [`SpanId`] for this span context.
340    pub fn span_id(&self) -> SpanId {
341        self.span_id
342    }
343
344    /// Returns details about the trace.
345    ///
346    /// Unlike `TraceState` values, these are present in all traces. The current
347    /// version of the specification only supports a single flag [`TraceFlags::SAMPLED`].
348    pub fn trace_flags(&self) -> TraceFlags {
349        self.trace_flags
350    }
351
352    /// Returns `true` if the span context has a valid (non-zero) `trace_id` and a
353    /// valid (non-zero) `span_id`.
354    pub fn is_valid(&self) -> bool {
355        self.trace_id != TraceId::INVALID && self.span_id != SpanId::INVALID
356    }
357
358    /// Returns `true` if the span context was propagated from a remote parent.
359    pub fn is_remote(&self) -> bool {
360        self.is_remote
361    }
362
363    /// Returns `true` if the `sampled` trace flag is set.
364    ///
365    /// Spans that are not sampled will be ignored by most tracing tools.
366    pub fn is_sampled(&self) -> bool {
367        self.trace_flags.is_sampled()
368    }
369
370    /// A reference to the span context's [`TraceState`].
371    pub fn trace_state(&self) -> &TraceState {
372        &self.trace_state
373    }
374}
375
376#[cfg(test)]
377mod tests {
378    use super::*;
379    use crate::{trace::TraceContextExt, Context};
380
381    #[rustfmt::skip]
382    fn trace_state_test_data() -> Vec<(TraceState, &'static str, &'static str)> {
383        vec![
384            (TraceState::from_key_value(vec![("foo", "bar")]).unwrap(), "foo=bar", "foo"),
385            (TraceState::from_key_value(vec![("foo", ""), ("apple", "banana")]).unwrap(), "foo=,apple=banana", "apple"),
386            (TraceState::from_key_value(vec![("foo", "bar"), ("apple", "banana")]).unwrap(), "foo=bar,apple=banana", "apple"),
387        ]
388    }
389
390    #[test]
391    fn test_trace_state() {
392        for test_case in trace_state_test_data() {
393            assert_eq!(test_case.0.clone().header(), test_case.1);
394
395            let new_key = format!("{}-{}", test_case.0.get(test_case.2).unwrap(), "test");
396
397            let updated_trace_state = test_case.0.insert(test_case.2, new_key.clone());
398            assert!(updated_trace_state.is_ok());
399            let updated_trace_state = updated_trace_state.unwrap();
400
401            let updated = format!("{}={}", test_case.2, new_key);
402
403            let index = updated_trace_state.clone().header().find(&updated);
404
405            assert!(index.is_some());
406            assert_eq!(index.unwrap(), 0);
407
408            let deleted_trace_state = updated_trace_state.delete(test_case.2.to_string());
409            assert!(deleted_trace_state.is_ok());
410
411            let deleted_trace_state = deleted_trace_state.unwrap();
412
413            assert!(deleted_trace_state.get(test_case.2).is_none());
414        }
415    }
416
417    #[test]
418    fn test_trace_state_key() {
419        let test_data: Vec<(&'static str, bool)> = vec![
420            ("123", true),
421            ("bar", true),
422            ("foo@bar", true),
423            ("foo@0123456789abcdef", false),
424            ("foo@012345678", true),
425            ("FOO@BAR", false),
426            ("你好", false),
427        ];
428
429        for (key, expected) in test_data {
430            assert_eq!(TraceState::valid_key(key), expected, "test key: {key:?}");
431        }
432    }
433
434    #[test]
435    fn test_trace_state_insert() {
436        let trace_state = TraceState::from_key_value(vec![("foo", "bar")]).unwrap();
437        let inserted_trace_state = trace_state.insert("testkey", "testvalue").unwrap();
438        assert!(trace_state.get("testkey").is_none()); // The original state doesn't change
439        assert_eq!(inserted_trace_state.get("testkey").unwrap(), "testvalue"); //
440    }
441
442    #[test]
443    fn test_context_span_debug() {
444        let cx = Context::current();
445        assert_eq!(
446            format!("{cx:?}"),
447            "Context { span: \"None\", entries count: 0, suppress_telemetry: false }"
448        );
449        let cx = Context::current().with_remote_span_context(SpanContext::NONE);
450        assert_eq!(
451            format!("{cx:?}"),
452            "Context { \
453               span: SpanContext { \
454                       trace_id: 00000000000000000000000000000000, \
455                       span_id: 0000000000000000, \
456                       trace_flags: TraceFlags(0), \
457                       is_remote: false, \
458                       trace_state: TraceState(None) \
459                     }, \
460               entries count: 1, suppress_telemetry: false \
461             }"
462        );
463    }
464
465    #[test]
466    fn test_tracestate_iter_empty() {
467        let ts = TraceState::NONE;
468        let mut iter = ts.into_iter();
469        assert_eq!(iter.next(), None);
470        assert_eq!(iter.size_hint(), (0, Some(0)));
471        assert_eq!(iter.len(), 0);
472    }
473
474    #[test]
475    fn test_tracestate_iter_single() {
476        let ts = TraceState::from_key_value(vec![("foo", "bar")]).unwrap();
477        let mut iter = ts.into_iter();
478        assert_eq!(iter.next(), Some(("foo", "bar")));
479        assert_eq!(iter.next(), None);
480        assert_eq!(iter.size_hint(), (0, Some(0)));
481    }
482
483    #[test]
484    fn test_tracestate_iter_multiple() {
485        let ts = TraceState::from_key_value(vec![("foo", "bar"), ("apple", "banana")]).unwrap();
486        let mut iter = ts.into_iter();
487        assert_eq!(iter.next(), Some(("foo", "bar")));
488        assert_eq!(iter.next(), Some(("apple", "banana")));
489        assert_eq!(iter.next(), None);
490    }
491
492    #[test]
493    fn test_tracestate_iter_size_hint_and_len() {
494        let ts = TraceState::from_key_value(vec![("foo", "bar"), ("apple", "banana")]).unwrap();
495        let iter = ts.into_iter();
496        assert_eq!(iter.size_hint(), (2, Some(2)));
497        assert_eq!(iter.len(), 2);
498    }
499}