CommonLibrary/Telemetry/
Traceparent.rs1use std::{
15 collections::hash_map::DefaultHasher,
16 hash::{Hash, Hasher},
17 time::{SystemTime, UNIX_EPOCH},
18};
19
20use crate::Telemetry::EmitOTLPSpan;
21
22const VERSION:&str = "00";
24
25const SAMPLED_FLAG:&str = "01";
26
27fn FreshSpanId() -> String {
28 let mut H = DefaultHasher::new();
29
30 std::thread::current().id().hash(&mut H);
31
32 if let Ok(D) = SystemTime::now().duration_since(UNIX_EPOCH) {
33 D.as_nanos().hash(&mut H);
34 }
35
36 format!("{:016x}", H.finish())
37}
38
39pub fn Build() -> String {
44 let TraceId = TraceIdValue();
45
46 let SpanId = FreshSpanId();
47
48 format!("{}-{}-{}-{}", VERSION, TraceId, SpanId, SAMPLED_FLAG)
49}
50
51#[derive(Clone, Debug, PartialEq, Eq)]
54pub struct Decoded {
55 pub TraceId:String,
56
57 pub ParentSpanId:String,
58
59 pub Sampled:bool,
60}
61
62pub fn Parse(Header:&str) -> Option<Decoded> {
65 let Parts:Vec<&str> = Header.split('-').collect();
66
67 if Parts.len() != 4 {
68 return None;
69 }
70
71 if Parts[0] != VERSION {
72 return None;
73 }
74
75 if Parts[1].len() != 32 || !Parts[1].chars().all(|C| C.is_ascii_hexdigit()) {
76 return None;
77 }
78
79 if Parts[2].len() != 16 || !Parts[2].chars().all(|C| C.is_ascii_hexdigit()) {
80 return None;
81 }
82
83 let Sampled = Parts[3] == SAMPLED_FLAG || Parts[3] == "01";
84
85 Some(Decoded { TraceId:Parts[1].to_string(), ParentSpanId:Parts[2].to_string(), Sampled })
86}
87
88pub fn TraceIdValue() -> String {
92 let mut H = DefaultHasher::new();
96
97 std::process::id().hash(&mut H);
98
99 EmitOTLPSpan::NowNanoPub().hash(&mut H);
100
101 format!("{:032x}", H.finish() as u128)
106}
107
108#[cfg(test)]
109mod tests {
110
111 use super::*;
112
113 #[test]
114 fn RoundTrip() {
115 let Header = Build();
116
117 let Decoded = Parse(&Header).expect("parse");
118
119 assert_eq!(Decoded.TraceId.len(), 32);
120
121 assert_eq!(Decoded.ParentSpanId.len(), 16);
122
123 assert!(Decoded.Sampled);
124 }
125
126 #[test]
127 fn RejectsMalformed() {
128 assert!(Parse("").is_none());
129
130 assert!(Parse("not-a-valid-header").is_none());
131
132 assert!(Parse("00-tooshort-00f067aa0ba902b7-01").is_none());
133
134 assert!(Parse("01-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01").is_none());
135 }
136}