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