halldyll_core/observe/
traces.rs1use std::collections::HashMap;
4use uuid::Uuid;
5
6#[derive(Debug, Clone)]
8pub struct TraceContext {
9 pub trace_id: String,
11 pub span_id: String,
13 pub parent_span_id: Option<String>,
15 pub attributes: HashMap<String, String>,
17}
18
19impl Default for TraceContext {
20 fn default() -> Self {
21 Self::new()
22 }
23}
24
25impl TraceContext {
26 pub fn new() -> Self {
28 Self {
29 trace_id: Uuid::new_v4().to_string(),
30 span_id: Uuid::new_v4().to_string()[..16].to_string(),
31 parent_span_id: None,
32 attributes: HashMap::new(),
33 }
34 }
35
36 pub fn child_span(&self) -> Self {
38 Self {
39 trace_id: self.trace_id.clone(),
40 span_id: Uuid::new_v4().to_string()[..16].to_string(),
41 parent_span_id: Some(self.span_id.clone()),
42 attributes: self.attributes.clone(),
43 }
44 }
45
46 pub fn with_attribute(mut self, key: &str, value: &str) -> Self {
48 self.attributes.insert(key.to_string(), value.to_string());
49 self
50 }
51
52 pub fn set_attribute(&mut self, key: &str, value: &str) {
54 self.attributes.insert(key.to_string(), value.to_string());
55 }
56
57 pub fn to_headers(&self) -> HashMap<String, String> {
59 let mut headers = HashMap::new();
60
61 let traceparent = format!(
63 "00-{}-{}-01",
64 self.trace_id,
65 self.span_id
66 );
67 headers.insert("traceparent".to_string(), traceparent);
68
69 if !self.attributes.is_empty() {
71 let tracestate: String = self.attributes
72 .iter()
73 .map(|(k, v)| format!("{}={}", k, v))
74 .collect::<Vec<_>>()
75 .join(",");
76 headers.insert("tracestate".to_string(), tracestate);
77 }
78
79 headers
80 }
81
82 pub fn from_headers(headers: &HashMap<String, String>) -> Option<Self> {
84 let traceparent = headers.get("traceparent")?;
85 let parts: Vec<&str> = traceparent.split('-').collect();
86
87 if parts.len() != 4 || parts[0] != "00" {
88 return None;
89 }
90
91 let trace_id = parts[1].to_string();
92 let span_id = parts[2].to_string();
93
94 let mut ctx = Self {
95 trace_id,
96 span_id: Uuid::new_v4().to_string()[..16].to_string(),
97 parent_span_id: Some(span_id),
98 attributes: HashMap::new(),
99 };
100
101 if let Some(tracestate) = headers.get("tracestate") {
103 for pair in tracestate.split(',') {
104 if let Some((key, value)) = pair.split_once('=') {
105 ctx.attributes.insert(key.trim().to_string(), value.trim().to_string());
106 }
107 }
108 }
109
110 Some(ctx)
111 }
112}
113
114#[derive(Debug)]
116pub struct Span {
117 pub context: TraceContext,
119 pub name: String,
121 pub started_at: std::time::Instant,
123 pub ended_at: Option<std::time::Instant>,
125 pub status: SpanStatus,
127 pub events: Vec<SpanEvent>,
129}
130
131impl Span {
132 pub fn new(context: TraceContext, name: &str) -> Self {
134 Self {
135 context,
136 name: name.to_string(),
137 started_at: std::time::Instant::now(),
138 ended_at: None,
139 status: SpanStatus::Ok,
140 events: Vec::new(),
141 }
142 }
143
144 pub fn add_event(&mut self, name: &str, attributes: HashMap<String, String>) {
146 self.events.push(SpanEvent {
147 name: name.to_string(),
148 timestamp: std::time::Instant::now(),
149 attributes,
150 });
151 }
152
153 pub fn end_ok(&mut self) {
155 self.ended_at = Some(std::time::Instant::now());
156 self.status = SpanStatus::Ok;
157 }
158
159 pub fn end_error(&mut self, message: &str) {
161 self.ended_at = Some(std::time::Instant::now());
162 self.status = SpanStatus::Error(message.to_string());
163 }
164
165 pub fn duration_ms(&self) -> u64 {
167 let end = self.ended_at.unwrap_or_else(std::time::Instant::now);
168 end.duration_since(self.started_at).as_millis() as u64
169 }
170}
171
172#[derive(Debug, Clone)]
174pub enum SpanStatus {
175 Ok,
177 Error(String),
179}
180
181#[derive(Debug)]
183pub struct SpanEvent {
184 pub name: String,
186 pub timestamp: std::time::Instant,
188 pub attributes: HashMap<String, String>,
190}