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
168impl fmt::Display for SpanContext {
169    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
170        let Self {
171            process_id,
172            span_id,
173        } = self;
174        write!(f, "{process_id}:{span_id}")
175    }
176}
177
178/// Errors that can occur while parsing [`SpanContext`] from a string.
179#[derive(Clone, Debug)]
180pub enum ParseSpanContextError {
181    /// The string is missing a `:` separator.
182    MissingSeparator,
183
184    /// The embedded [`ProcessId`] failed to parse.
185    InvalidProcessId(core::num::ParseIntError),
186
187    /// The embedded [`SpanId`] failed to parse.
188    InvalidSpanId(core::num::ParseIntError),
189}
190
191impl fmt::Display for ParseSpanContextError {
192    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
193        match self {
194            Self::MissingSeparator => f.write_str("missing ':' separator"),
195            Self::InvalidProcessId(_) => f.write_str("failed to parse process id"),
196            Self::InvalidSpanId(_) => f.write_str("failed to parse span id"),
197        }
198    }
199}
200
201impl core::error::Error for ParseSpanContextError {
202    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
203        match self {
204            Self::MissingSeparator => None,
205            Self::InvalidProcessId(error) => Some(error),
206            Self::InvalidSpanId(error) => Some(error),
207        }
208    }
209}
210
211impl FromStr for SpanContext {
212    type Err = ParseSpanContextError;
213
214    fn from_str(s: &str) -> Result<Self, Self::Err> {
215        let Some((process_id, span_id)) = s.split_once(":") else {
216            return Err(ParseSpanContextError::MissingSeparator);
217        };
218        let process_id =
219            ProcessId::from_str(process_id).map_err(ParseSpanContextError::InvalidProcessId)?;
220        let span_id = SpanId::from_str(span_id).map_err(ParseSpanContextError::InvalidSpanId)?;
221        Ok(Self {
222            process_id,
223            span_id,
224        })
225    }
226}
227
228impl serde::Serialize for SpanContext {
229    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
230    where
231        S: serde::Serializer,
232    {
233        let mut bytes = [0u8; 49];
234
235        hex::encode_to_slice(self.process_id.to_raw().to_le_bytes(), &mut bytes[..32]).unwrap();
236        bytes[32] = b':';
237        hex::encode_to_slice(self.span_id.0.to_le_bytes(), &mut bytes[33..]).unwrap();
238
239        serializer.serialize_str(str::from_utf8(&bytes).unwrap())
240    }
241}
242
243impl<'de> serde::Deserialize<'de> for SpanContext {
244    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
245    where
246        D: serde::Deserializer<'de>,
247    {
248        use serde::de::Error;
249
250        let string = <&str>::deserialize(deserializer)?;
251
252        if string.len() != 49 {
253            return Err(D::Error::invalid_length(
254                string.len(),
255                &"expected 49 byte string",
256            ));
257        }
258
259        let bytes = string.as_bytes();
260
261        if bytes[32] != b':' {
262            return Err(D::Error::invalid_value(
263                serde::de::Unexpected::Str(string),
264                &"expected : separator at byte 32",
265            ));
266        }
267
268        let mut process = [0; 16];
269        hex::decode_to_slice(&bytes[..32], &mut process).map_err(D::Error::custom)?;
270
271        let mut span = [0; 8];
272        hex::decode_to_slice(&bytes[33..], &mut span).map_err(D::Error::custom)?;
273
274        Ok(Self {
275            process_id: ProcessId::from_raw(u128::from_le_bytes(process)),
276            span_id: SpanId(u64::from_le_bytes(span)),
277        })
278    }
279}
280
281#[cfg(all(test, feature = "std"))]
282mod tests {
283    use std::collections::HashSet;
284    use std::format;
285    use std::string::String;
286    use std::vec::Vec;
287
288    use super::*;
289
290    #[test]
291    #[cfg(not(miri))] // VERY slow with Miri.
292    #[allow(clippy::needless_collect)]
293    fn unique_id() {
294        let handles = std::iter::repeat_with(|| {
295            std::thread::spawn(|| {
296                std::iter::repeat_with(SpanId::next_id)
297                    .take(1000)
298                    .collect::<Vec<_>>()
299            })
300        })
301        .take(32)
302        .collect::<Vec<_>>();
303
304        let k = handles
305            .into_iter()
306            .flat_map(|h| h.join().unwrap())
307            .collect::<HashSet<_>>();
308
309        assert_eq!(k.len(), 32 * 1000);
310    }
311
312    #[test]
313    fn span_id_formatting() {
314        assert_eq!(format!("{}", SpanId(0)), "0000000000000000");
315        assert_eq!(format!("{}", SpanId(u64::MAX)), "ffffffffffffffff");
316        assert_eq!(
317            format!("{}", SpanId(0xFEDCBA9876543210)),
318            "fedcba9876543210"
319        );
320        assert_eq!(format!("{}", SpanId(0x123)), "0000000000000123");
321    }
322
323    #[test]
324    fn span_id_from_str() {
325        assert_eq!(
326            "fedcba9876543210".parse::<SpanId>().unwrap(),
327            SpanId(0xFEDCBA9876543210)
328        );
329        assert_eq!(
330            "FEDCBA9876543210".parse::<SpanId>().unwrap(),
331            SpanId(0xFEDCBA9876543210)
332        );
333        assert_eq!("0000000000000000".parse::<SpanId>().unwrap(), SpanId(0));
334        assert_eq!(
335            "ffffffffffffffff".parse::<SpanId>().unwrap(),
336            SpanId(u64::MAX)
337        );
338        assert_eq!("123".parse::<SpanId>().unwrap(), SpanId(0x123));
339
340        assert!("xyz".parse::<SpanId>().is_err());
341        assert!("".parse::<SpanId>().is_err());
342    }
343
344    #[test]
345    fn span_id_format_from_str_roundtrip() {
346        let test_cases = [0u64, 1, 0x123, 0xFEDCBA9876543210, u64::MAX, u64::MAX - 1];
347
348        for value in test_cases {
349            let span_id = SpanId(value);
350            let formatted = format!("{span_id}");
351            let parsed = formatted.parse::<SpanId>().unwrap();
352            assert_eq!(span_id, parsed, "Failed roundtrip for value {value:#x}");
353        }
354    }
355
356    #[test]
357    fn span_id_serde_roundtrip() {
358        let test_cases = [
359            SpanId(0),
360            SpanId(1),
361            SpanId(0x123),
362            SpanId(0xFEDCBA9876543210),
363            SpanId(u64::MAX),
364            SpanId(u64::MAX - 1),
365        ];
366
367        for original in test_cases {
368            let json = serde_json::to_string(&original).unwrap();
369            let deserialized: SpanId = serde_json::from_str(&json).unwrap();
370            assert_eq!(
371                original, deserialized,
372                "JSON roundtrip failed for {:#x}",
373                original.0
374            );
375        }
376    }
377
378    #[test]
379    fn span_context_serde_roundtrip() {
380        let test_cases = [
381            SpanContext::new(ProcessId::from_raw(0), SpanId(0)),
382            SpanContext::new(
383                ProcessId::from_raw(0x123456789ABCDEF0FEDCBA9876543210),
384                SpanId(0xFEDCBA9876543210),
385            ),
386            SpanContext::new(ProcessId::from_raw(u128::MAX), SpanId(u64::MAX)),
387            SpanContext::new(ProcessId::from_raw(1), SpanId(1)),
388        ];
389
390        for original in test_cases {
391            let json = serde_json::to_string(&original).unwrap();
392            let deserialized: SpanContext = serde_json::from_str(&json).unwrap();
393            assert_eq!(
394                original.process_id, deserialized.process_id,
395                "JSON roundtrip failed for process_id"
396            );
397            assert_eq!(
398                original.span_id, deserialized.span_id,
399                "JSON roundtrip failed for span_id"
400            );
401        }
402    }
403
404    #[test]
405    fn span_id_serialization_format() {
406        let span_id = SpanId(0xFEDCBA9876543210);
407        let json = serde_json::to_string(&span_id).unwrap();
408
409        let expected_le_bytes = 0xFEDCBA9876543210u64.to_le_bytes();
410        let mut expected_hex = String::new();
411        for byte in &expected_le_bytes {
412            expected_hex.push_str(&format!("{byte:02x}"));
413        }
414        let expected_json = format!("\"{expected_hex}\"");
415
416        assert_eq!(json, expected_json);
417    }
418
419    #[test]
420    fn span_context_new_and_fields() {
421        let process_id = ProcessId::from_raw(0x123);
422        let span_id = SpanId(0x456);
423        let context = SpanContext::new(process_id, span_id);
424
425        assert_eq!(context.process_id, process_id);
426        assert_eq!(context.span_id, span_id);
427    }
428
429    #[test]
430    fn process_id_format_from_str_roundtrip() {
431        let test_cases = [
432            0u128,
433            1,
434            0x123,
435            0xFEDCBA9876543210,
436            0x123456789ABCDEF0FEDCBA9876543210,
437            u128::MAX,
438            u128::MAX - 1,
439        ];
440
441        for value in test_cases {
442            let process_id = ProcessId::from_raw(value);
443            let formatted = format!("{process_id}");
444            let parsed = formatted.parse::<ProcessId>().unwrap();
445            assert_eq!(process_id, parsed, "Failed roundtrip for value {value:#x}");
446        }
447    }
448
449    #[test]
450    fn process_id_serde_roundtrip() {
451        let test_cases = [
452            ProcessId::from_raw(0),
453            ProcessId::from_raw(1),
454            ProcessId::from_raw(0x123),
455            ProcessId::from_raw(0xFEDCBA9876543210),
456            ProcessId::from_raw(0x123456789ABCDEF0FEDCBA9876543210),
457            ProcessId::from_raw(u128::MAX),
458            ProcessId::from_raw(u128::MAX - 1),
459        ];
460
461        for original in test_cases {
462            let json = serde_json::to_string(&original).unwrap();
463            let deserialized: ProcessId = serde_json::from_str(&json).unwrap();
464            assert_eq!(
465                original,
466                deserialized,
467                "JSON roundtrip failed for {:#x}",
468                original.to_raw()
469            );
470        }
471    }
472
473    #[test]
474    fn span_context_format_from_str_roundtrip() {
475        let test_cases = [
476            SpanContext::new(ProcessId::from_raw(0), SpanId(0)),
477            SpanContext::new(
478                ProcessId::from_raw(0x123456789ABCDEF0FEDCBA9876543210),
479                SpanId(0xFEDCBA9876543210),
480            ),
481            SpanContext::new(ProcessId::from_raw(u128::MAX), SpanId(u64::MAX)),
482            SpanContext::new(ProcessId::from_raw(1), SpanId(1)),
483        ];
484
485        for context in test_cases {
486            let formatted = format!("{context}");
487            let parsed = formatted.parse::<SpanContext>().unwrap();
488            assert_eq!(
489                context,
490                parsed,
491                "Failed roundtrip for {:#x}:{:#x}",
492                context.process_id.to_raw(),
493                context.span_id.0
494            );
495        }
496    }
497
498    #[test]
499    fn span_id_next_id_produces_non_zero_values() {
500        let ids: Vec<SpanId> = (0..100).map(|_| SpanId::next_id()).collect();
501
502        for id in &ids {
503            assert_ne!(id.0, 0, "SpanId::next_id() should not produce zero values");
504        }
505
506        let mut unique_ids = HashSet::new();
507        for id in &ids {
508            assert!(
509                unique_ids.insert(id.0),
510                "SpanId::next_id() should produce unique values"
511            );
512        }
513    }
514}