veecle_telemetry/
id.rs

1// Copyright 2020 TiKV Project Authors. Licensed under Apache-2.0.
2// Copyright 2025 Veecle GmbH.
3//
4// This file has been modified from the original TiKV implementation.
5
6//! Unique identifiers for traces and spans.
7//!
8//! This module provides the core identifier types used throughout the telemetry system
9//! to uniquely identify traces and spans in distributed tracing scenarios.
10//!
11//! # Core Types
12//!
13//! - [`SpanId`]: An identifier that uniquely identifies a span within a process.
14//! - [`SpanContext`]: A combination of process id and span id that uniquely identifies a span globally.
15
16use core::fmt;
17use core::str::FromStr;
18
19/// A globally-unique id identifying a process.
20///
21/// The primary purpose of this id is to provide a globally-unique context within which
22/// [`ThreadId`]s and [`SpanContext`]s are guaranteed to be unique. On a normal operating system
23/// that is the process, on other systems it should be whatever is the closest equivalent, e.g. for
24/// most embedded setups it should be unique for each time the system is restarted.
25///
26/// [`ThreadId`]: crate::protocol::ThreadId
27#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Default)]
28pub struct ProcessId(u128);
29
30impl ProcessId {
31    /// Uses a random number generator to generate the [`ProcessId`].
32    pub fn random(rng: &mut impl rand::Rng) -> Self {
33        Self(rng.random())
34    }
35
36    /// Creates a [`ProcessId`] from a raw value
37    ///
38    /// Extra care needs to be taken that this is not a constant value or re-used in any way.
39    ///
40    /// When possible prefer using [`ProcessId::random`].
41    pub const fn from_raw(raw: u128) -> Self {
42        Self(raw)
43    }
44
45    /// Returns the raw value of this id.
46    pub fn to_raw(self) -> u128 {
47        self.0
48    }
49}
50
51impl fmt::Display for ProcessId {
52    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53        write!(f, "{:032x}", self.0)
54    }
55}
56
57impl FromStr for ProcessId {
58    type Err = core::num::ParseIntError;
59
60    fn from_str(s: &str) -> Result<Self, Self::Err> {
61        u128::from_str_radix(s, 16).map(ProcessId)
62    }
63}
64
65impl serde::Serialize for ProcessId {
66    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
67    where
68        S: serde::Serializer,
69    {
70        let mut hex_bytes = [0u8; size_of::<u128>() * 2];
71        hex::encode_to_slice(self.0.to_le_bytes(), &mut hex_bytes).unwrap();
72
73        serializer.serialize_str(str::from_utf8(&hex_bytes).unwrap())
74    }
75}
76
77impl<'de> serde::Deserialize<'de> for ProcessId {
78    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
79    where
80        D: serde::Deserializer<'de>,
81    {
82        let bytes: [u8; size_of::<u128>()] = hex::serde::deserialize(deserializer)?;
83
84        Ok(ProcessId(u128::from_le_bytes(bytes)))
85    }
86}
87
88/// A process-unique id for a span.
89#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
90pub struct SpanId(pub u64);
91
92#[cfg(feature = "enable")]
93impl SpanId {
94    #[inline]
95    #[doc(hidden)]
96    /// Creates a non-zero [`SpanId`].
97    pub fn next_id() -> Self {
98        use core::sync::atomic;
99        static COUNTER: atomic::AtomicU64 = atomic::AtomicU64::new(1);
100        SpanId(COUNTER.fetch_add(1, atomic::Ordering::Relaxed))
101    }
102}
103
104impl fmt::Display for SpanId {
105    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106        write!(f, "{:016x}", self.0)
107    }
108}
109
110impl FromStr for SpanId {
111    type Err = core::num::ParseIntError;
112
113    fn from_str(s: &str) -> Result<Self, Self::Err> {
114        u64::from_str_radix(s, 16).map(SpanId)
115    }
116}
117
118impl serde::Serialize for SpanId {
119    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
120    where
121        S: serde::Serializer,
122    {
123        let mut hex_bytes = [0u8; size_of::<u64>() * 2];
124        hex::encode_to_slice(self.0.to_le_bytes(), &mut hex_bytes).unwrap();
125
126        serializer.serialize_str(str::from_utf8(&hex_bytes).unwrap())
127    }
128}
129
130impl<'de> serde::Deserialize<'de> for SpanId {
131    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
132    where
133        D: serde::Deserializer<'de>,
134    {
135        let bytes: [u8; size_of::<u64>()] = hex::serde::deserialize(deserializer)?;
136
137        Ok(SpanId(u64::from_le_bytes(bytes)))
138    }
139}
140
141/// A struct representing the context of a span, including its [`ProcessId`] and [`SpanId`].
142#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
143pub struct SpanContext {
144    /// The id of the process this span belongs to.
145    pub process_id: ProcessId,
146    /// The unique id of this span.
147    pub span_id: SpanId,
148}
149
150impl SpanContext {
151    /// Creates a new `SpanContext` with the given [`ProcessId`] and [`SpanId`].
152    ///
153    /// # Examples
154    ///
155    /// ```
156    /// use veecle_telemetry::{ProcessId, SpanId, SpanContext};
157    ///
158    /// let span_context = SpanContext::new(ProcessId::from_raw(12), SpanId(13));
159    /// ```
160    pub fn new(process_id: ProcessId, span_id: SpanId) -> Self {
161        Self {
162            process_id,
163            span_id,
164        }
165    }
166
167    /// Creates a `SpanContext` from the current local parent span. If there is no
168    /// local parent span, this function will return `None`.
169    ///
170    /// # Examples
171    ///
172    /// ```
173    /// use veecle_telemetry::*;
174    ///
175    /// let span = Span::new("root", &[]);
176    /// let _guard = span.entered();
177    ///
178    /// let span_context = SpanContext::current();
179    /// assert!(span_context.is_some());
180    /// ```
181    pub fn current() -> Option<Self> {
182        #[cfg(not(feature = "enable"))]
183        {
184            None
185        }
186
187        #[cfg(feature = "enable")]
188        {
189            crate::span::CURRENT_SPAN
190                .get()
191                .map(|span_id| Self::new(crate::collector::get_collector().process_id(), span_id))
192        }
193    }
194}
195
196impl fmt::Display for SpanContext {
197    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
198        let Self {
199            process_id,
200            span_id,
201        } = self;
202        write!(f, "{process_id}:{span_id}")
203    }
204}
205
206/// Errors that can occur while parsing [`SpanContext`] from a string.
207#[derive(Clone, Debug)]
208pub enum ParseSpanContextError {
209    /// The string is missing a `:` separator.
210    MissingSeparator,
211
212    /// The embedded [`ProcessId`] failed to parse.
213    InvalidProcessId(core::num::ParseIntError),
214
215    /// The embedded [`SpanId`] failed to parse.
216    InvalidSpanId(core::num::ParseIntError),
217}
218
219impl fmt::Display for ParseSpanContextError {
220    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
221        match self {
222            Self::MissingSeparator => f.write_str("missing ':' separator"),
223            Self::InvalidProcessId(_) => f.write_str("failed to parse process id"),
224            Self::InvalidSpanId(_) => f.write_str("failed to parse span id"),
225        }
226    }
227}
228
229impl core::error::Error for ParseSpanContextError {
230    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
231        match self {
232            Self::MissingSeparator => None,
233            Self::InvalidProcessId(error) => Some(error),
234            Self::InvalidSpanId(error) => Some(error),
235        }
236    }
237}
238
239impl FromStr for SpanContext {
240    type Err = ParseSpanContextError;
241
242    fn from_str(s: &str) -> Result<Self, Self::Err> {
243        let Some((process_id, span_id)) = s.split_once(":") else {
244            return Err(ParseSpanContextError::MissingSeparator);
245        };
246        let process_id =
247            ProcessId::from_str(process_id).map_err(ParseSpanContextError::InvalidProcessId)?;
248        let span_id = SpanId::from_str(span_id).map_err(ParseSpanContextError::InvalidSpanId)?;
249        Ok(Self {
250            process_id,
251            span_id,
252        })
253    }
254}
255
256impl serde::Serialize for SpanContext {
257    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
258    where
259        S: serde::Serializer,
260    {
261        let mut bytes = [0u8; 49];
262
263        hex::encode_to_slice(self.process_id.to_raw().to_le_bytes(), &mut bytes[..32]).unwrap();
264        bytes[32] = b':';
265        hex::encode_to_slice(self.span_id.0.to_le_bytes(), &mut bytes[33..]).unwrap();
266
267        serializer.serialize_str(str::from_utf8(&bytes).unwrap())
268    }
269}
270
271impl<'de> serde::Deserialize<'de> for SpanContext {
272    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
273    where
274        D: serde::Deserializer<'de>,
275    {
276        use serde::de::Error;
277
278        let string = <&str>::deserialize(deserializer)?;
279
280        if string.len() != 49 {
281            return Err(D::Error::invalid_length(
282                string.len(),
283                &"expected 49 byte string",
284            ));
285        }
286
287        let bytes = string.as_bytes();
288
289        if bytes[32] != b':' {
290            return Err(D::Error::invalid_value(
291                serde::de::Unexpected::Str(string),
292                &"expected : separator at byte 32",
293            ));
294        }
295
296        let mut process = [0; 16];
297        hex::decode_to_slice(&bytes[..32], &mut process).map_err(D::Error::custom)?;
298
299        let mut span = [0; 8];
300        hex::decode_to_slice(&bytes[33..], &mut span).map_err(D::Error::custom)?;
301
302        Ok(Self {
303            process_id: ProcessId::from_raw(u128::from_le_bytes(process)),
304            span_id: SpanId(u64::from_le_bytes(span)),
305        })
306    }
307}
308
309#[cfg(all(test, feature = "std"))]
310mod tests {
311    use std::collections::HashSet;
312    use std::format;
313    use std::string::String;
314    use std::vec::Vec;
315
316    use super::*;
317
318    #[test]
319    #[cfg(not(miri))] // VERY slow with Miri.
320    #[allow(clippy::needless_collect)]
321    fn unique_id() {
322        let handles = std::iter::repeat_with(|| {
323            std::thread::spawn(|| {
324                std::iter::repeat_with(SpanId::next_id)
325                    .take(1000)
326                    .collect::<Vec<_>>()
327            })
328        })
329        .take(32)
330        .collect::<Vec<_>>();
331
332        let k = handles
333            .into_iter()
334            .flat_map(|h| h.join().unwrap())
335            .collect::<HashSet<_>>();
336
337        assert_eq!(k.len(), 32 * 1000);
338    }
339
340    #[test]
341    fn span_id_formatting() {
342        assert_eq!(format!("{}", SpanId(0)), "0000000000000000");
343        assert_eq!(format!("{}", SpanId(u64::MAX)), "ffffffffffffffff");
344        assert_eq!(
345            format!("{}", SpanId(0xFEDCBA9876543210)),
346            "fedcba9876543210"
347        );
348        assert_eq!(format!("{}", SpanId(0x123)), "0000000000000123");
349    }
350
351    #[test]
352    fn span_id_from_str() {
353        assert_eq!(
354            "fedcba9876543210".parse::<SpanId>().unwrap(),
355            SpanId(0xFEDCBA9876543210)
356        );
357        assert_eq!(
358            "FEDCBA9876543210".parse::<SpanId>().unwrap(),
359            SpanId(0xFEDCBA9876543210)
360        );
361        assert_eq!("0000000000000000".parse::<SpanId>().unwrap(), SpanId(0));
362        assert_eq!(
363            "ffffffffffffffff".parse::<SpanId>().unwrap(),
364            SpanId(u64::MAX)
365        );
366        assert_eq!("123".parse::<SpanId>().unwrap(), SpanId(0x123));
367
368        assert!("xyz".parse::<SpanId>().is_err());
369        assert!("".parse::<SpanId>().is_err());
370    }
371
372    #[test]
373    fn span_id_format_from_str_roundtrip() {
374        let test_cases = [0u64, 1, 0x123, 0xFEDCBA9876543210, u64::MAX, u64::MAX - 1];
375
376        for value in test_cases {
377            let span_id = SpanId(value);
378            let formatted = format!("{span_id}");
379            let parsed = formatted.parse::<SpanId>().unwrap();
380            assert_eq!(span_id, parsed, "Failed roundtrip for value {value:#x}");
381        }
382    }
383
384    #[test]
385    fn span_id_serde_roundtrip() {
386        let test_cases = [
387            SpanId(0),
388            SpanId(1),
389            SpanId(0x123),
390            SpanId(0xFEDCBA9876543210),
391            SpanId(u64::MAX),
392            SpanId(u64::MAX - 1),
393        ];
394
395        for original in test_cases {
396            let json = serde_json::to_string(&original).unwrap();
397            let deserialized: SpanId = serde_json::from_str(&json).unwrap();
398            assert_eq!(
399                original, deserialized,
400                "JSON roundtrip failed for {:#x}",
401                original.0
402            );
403        }
404    }
405
406    #[test]
407    fn span_context_serde_roundtrip() {
408        let test_cases = [
409            SpanContext::new(ProcessId::from_raw(0), SpanId(0)),
410            SpanContext::new(
411                ProcessId::from_raw(0x123456789ABCDEF0FEDCBA9876543210),
412                SpanId(0xFEDCBA9876543210),
413            ),
414            SpanContext::new(ProcessId::from_raw(u128::MAX), SpanId(u64::MAX)),
415            SpanContext::new(ProcessId::from_raw(1), SpanId(1)),
416        ];
417
418        for original in test_cases {
419            let json = serde_json::to_string(&original).unwrap();
420            let deserialized: SpanContext = serde_json::from_str(&json).unwrap();
421            assert_eq!(
422                original.process_id, deserialized.process_id,
423                "JSON roundtrip failed for process_id"
424            );
425            assert_eq!(
426                original.span_id, deserialized.span_id,
427                "JSON roundtrip failed for span_id"
428            );
429        }
430    }
431
432    #[test]
433    fn span_id_serialization_format() {
434        let span_id = SpanId(0xFEDCBA9876543210);
435        let json = serde_json::to_string(&span_id).unwrap();
436
437        let expected_le_bytes = 0xFEDCBA9876543210u64.to_le_bytes();
438        let mut expected_hex = String::new();
439        for byte in &expected_le_bytes {
440            expected_hex.push_str(&format!("{byte:02x}"));
441        }
442        let expected_json = format!("\"{expected_hex}\"");
443
444        assert_eq!(json, expected_json);
445    }
446
447    #[test]
448    fn span_context_new_and_fields() {
449        let process_id = ProcessId::from_raw(0x123);
450        let span_id = SpanId(0x456);
451        let context = SpanContext::new(process_id, span_id);
452
453        assert_eq!(context.process_id, process_id);
454        assert_eq!(context.span_id, span_id);
455    }
456
457    #[test]
458    fn process_id_format_from_str_roundtrip() {
459        let test_cases = [
460            0u128,
461            1,
462            0x123,
463            0xFEDCBA9876543210,
464            0x123456789ABCDEF0FEDCBA9876543210,
465            u128::MAX,
466            u128::MAX - 1,
467        ];
468
469        for value in test_cases {
470            let process_id = ProcessId::from_raw(value);
471            let formatted = format!("{process_id}");
472            let parsed = formatted.parse::<ProcessId>().unwrap();
473            assert_eq!(process_id, parsed, "Failed roundtrip for value {value:#x}");
474        }
475    }
476
477    #[test]
478    fn process_id_serde_roundtrip() {
479        let test_cases = [
480            ProcessId::from_raw(0),
481            ProcessId::from_raw(1),
482            ProcessId::from_raw(0x123),
483            ProcessId::from_raw(0xFEDCBA9876543210),
484            ProcessId::from_raw(0x123456789ABCDEF0FEDCBA9876543210),
485            ProcessId::from_raw(u128::MAX),
486            ProcessId::from_raw(u128::MAX - 1),
487        ];
488
489        for original in test_cases {
490            let json = serde_json::to_string(&original).unwrap();
491            let deserialized: ProcessId = serde_json::from_str(&json).unwrap();
492            assert_eq!(
493                original,
494                deserialized,
495                "JSON roundtrip failed for {:#x}",
496                original.to_raw()
497            );
498        }
499    }
500
501    #[test]
502    fn span_context_format_from_str_roundtrip() {
503        let test_cases = [
504            SpanContext::new(ProcessId::from_raw(0), SpanId(0)),
505            SpanContext::new(
506                ProcessId::from_raw(0x123456789ABCDEF0FEDCBA9876543210),
507                SpanId(0xFEDCBA9876543210),
508            ),
509            SpanContext::new(ProcessId::from_raw(u128::MAX), SpanId(u64::MAX)),
510            SpanContext::new(ProcessId::from_raw(1), SpanId(1)),
511        ];
512
513        for context in test_cases {
514            let formatted = format!("{context}");
515            let parsed = formatted.parse::<SpanContext>().unwrap();
516            assert_eq!(
517                context,
518                parsed,
519                "Failed roundtrip for {:#x}:{:#x}",
520                context.process_id.to_raw(),
521                context.span_id.0
522            );
523        }
524    }
525
526    #[test]
527    fn span_id_next_id_produces_non_zero_values() {
528        let ids: Vec<SpanId> = (0..100).map(|_| SpanId::next_id()).collect();
529
530        for id in &ids {
531            assert_ne!(id.0, 0, "SpanId::next_id() should not produce zero values");
532        }
533
534        let mut unique_ids = HashSet::new();
535        for id in &ids {
536            assert!(
537                unique_ids.insert(id.0),
538                "SpanId::next_id() should produce unique values"
539            );
540        }
541    }
542}