halldyll_core/observe/
traces.rs

1//! Traces - Distributed tracing
2
3use std::collections::HashMap;
4use uuid::Uuid;
5
6/// Trace context
7#[derive(Debug, Clone)]
8pub struct TraceContext {
9    /// Trace ID
10    pub trace_id: String,
11    /// Span ID
12    pub span_id: String,
13    /// Parent Span ID
14    pub parent_span_id: Option<String>,
15    /// Attributes
16    pub attributes: HashMap<String, String>,
17}
18
19impl Default for TraceContext {
20    fn default() -> Self {
21        Self::new()
22    }
23}
24
25impl TraceContext {
26    /// New trace context
27    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    /// Create a child span
37    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    /// Add an attribute
47    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    /// Add an attribute
53    pub fn set_attribute(&mut self, key: &str, value: &str) {
54        self.attributes.insert(key.to_string(), value.to_string());
55    }
56
57    /// Headers for propagation
58    pub fn to_headers(&self) -> HashMap<String, String> {
59        let mut headers = HashMap::new();
60        
61        // Format W3C Trace Context
62        let traceparent = format!(
63            "00-{}-{}-01",
64            self.trace_id,
65            self.span_id
66        );
67        headers.insert("traceparent".to_string(), traceparent);
68        
69        // Tracestate (optionnel)
70        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    /// Parse from headers
83    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        // Parse tracestate
102        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/// Trace span
115#[derive(Debug)]
116pub struct Span {
117    /// Context
118    pub context: TraceContext,
119    /// Span name
120    pub name: String,
121    /// Start timestamp
122    pub started_at: std::time::Instant,
123    /// End timestamp
124    pub ended_at: Option<std::time::Instant>,
125    /// Status
126    pub status: SpanStatus,
127    /// Events
128    pub events: Vec<SpanEvent>,
129}
130
131impl Span {
132    /// New span
133    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    /// Add an event
145    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    /// End the span successfully
154    pub fn end_ok(&mut self) {
155        self.ended_at = Some(std::time::Instant::now());
156        self.status = SpanStatus::Ok;
157    }
158
159    /// End the span with error
160    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    /// Span duration (ms)
166    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/// Span status
173#[derive(Debug, Clone)]
174pub enum SpanStatus {
175    /// Successful operation
176    Ok,
177    /// Failed operation with error message
178    Error(String),
179}
180
181/// Span event
182#[derive(Debug)]
183pub struct SpanEvent {
184    /// Event name
185    pub name: String,
186    /// Event timestamp
187    pub timestamp: std::time::Instant,
188    /// Event attributes
189    pub attributes: HashMap<String, String>,
190}