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