Skip to main content

otelite_core/telemetry/
trace.rs

1//! Trace and span telemetry types
2
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6/// Represents a distributed trace
7#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
8pub struct Trace {
9    /// Trace ID (unique identifier for the trace)
10    pub trace_id: String,
11
12    /// Spans in this trace
13    pub spans: Vec<Span>,
14
15    /// Associated resource
16    pub resource: Option<super::Resource>,
17}
18
19/// Represents a span within a trace
20#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
21pub struct Span {
22    /// Trace ID this span belongs to
23    pub trace_id: String,
24
25    /// Span ID (unique within the trace)
26    pub span_id: String,
27
28    /// Parent span ID (if this is a child span)
29    pub parent_span_id: Option<String>,
30
31    /// Span name
32    pub name: String,
33
34    /// Span kind
35    pub kind: SpanKind,
36
37    /// Start time in nanoseconds since Unix epoch
38    pub start_time: i64,
39
40    /// End time in nanoseconds since Unix epoch
41    pub end_time: i64,
42
43    /// Span attributes
44    pub attributes: HashMap<String, String>,
45
46    /// Span events
47    pub events: Vec<SpanEvent>,
48
49    /// Span status
50    pub status: SpanStatus,
51
52    /// Associated resource
53    pub resource: Option<super::Resource>,
54}
55
56/// Span kind (type of operation)
57#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
58pub enum SpanKind {
59    /// Internal operation
60    Internal,
61
62    /// Server-side operation
63    Server,
64
65    /// Client-side operation
66    Client,
67
68    /// Producer operation (e.g., message queue)
69    Producer,
70
71    /// Consumer operation (e.g., message queue)
72    Consumer,
73}
74
75impl SpanKind {
76    /// Convert from integer representation
77    pub fn from_i32(value: i32) -> Option<Self> {
78        match value {
79            0 => Some(Self::Internal),
80            1 => Some(Self::Server),
81            2 => Some(Self::Client),
82            3 => Some(Self::Producer),
83            4 => Some(Self::Consumer),
84            _ => None,
85        }
86    }
87
88    /// Convert to integer representation
89    pub fn to_i32(self) -> i32 {
90        self as i32
91    }
92}
93
94/// Span event (point-in-time occurrence during a span)
95#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
96pub struct SpanEvent {
97    /// Event name
98    pub name: String,
99
100    /// Timestamp in nanoseconds since Unix epoch
101    pub timestamp: i64,
102
103    /// Event attributes
104    pub attributes: HashMap<String, String>,
105}
106
107/// Span status
108#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
109pub struct SpanStatus {
110    /// Status code
111    pub code: StatusCode,
112
113    /// Status message (optional)
114    pub message: Option<String>,
115}
116
117/// Status code for spans
118#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
119pub enum StatusCode {
120    /// Unset (default)
121    Unset,
122
123    /// Ok (success)
124    Ok,
125
126    /// Error (failure)
127    Error,
128}
129
130impl StatusCode {
131    /// Convert from integer representation
132    pub fn from_i32(value: i32) -> Option<Self> {
133        match value {
134            0 => Some(Self::Unset),
135            1 => Some(Self::Ok),
136            2 => Some(Self::Error),
137            _ => None,
138        }
139    }
140
141    /// Convert to integer representation
142    pub fn to_i32(self) -> i32 {
143        self as i32
144    }
145}
146
147impl Span {
148    /// Create a new span
149    pub fn new(
150        trace_id: impl Into<String>,
151        span_id: impl Into<String>,
152        name: impl Into<String>,
153        start_time: i64,
154        end_time: i64,
155    ) -> Self {
156        Self {
157            trace_id: trace_id.into(),
158            span_id: span_id.into(),
159            parent_span_id: None,
160            name: name.into(),
161            kind: SpanKind::Internal,
162            start_time,
163            end_time,
164            attributes: HashMap::new(),
165            events: Vec::new(),
166            status: SpanStatus {
167                code: StatusCode::Unset,
168                message: None,
169            },
170            resource: None,
171        }
172    }
173
174    /// Set the parent span ID
175    pub fn with_parent(mut self, parent_span_id: impl Into<String>) -> Self {
176        self.parent_span_id = Some(parent_span_id.into());
177        self
178    }
179
180    /// Set the span kind
181    pub fn with_kind(mut self, kind: SpanKind) -> Self {
182        self.kind = kind;
183        self
184    }
185
186    /// Add an attribute to the span
187    pub fn with_attribute(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
188        self.attributes.insert(key.into(), value.into());
189        self
190    }
191
192    /// Add an event to the span
193    pub fn with_event(mut self, event: SpanEvent) -> Self {
194        self.events.push(event);
195        self
196    }
197
198    /// Set the span status
199    pub fn with_status(mut self, code: StatusCode, message: Option<String>) -> Self {
200        self.status = SpanStatus { code, message };
201        self
202    }
203
204    /// Calculate span duration in nanoseconds
205    pub fn duration_ns(&self) -> i64 {
206        self.end_time - self.start_time
207    }
208}
209
210impl Trace {
211    /// Create a new trace
212    pub fn new(trace_id: impl Into<String>) -> Self {
213        Self {
214            trace_id: trace_id.into(),
215            spans: Vec::new(),
216            resource: None,
217        }
218    }
219
220    /// Add a span to the trace
221    pub fn with_span(mut self, span: Span) -> Self {
222        self.spans.push(span);
223        self
224    }
225
226    /// Set the resource for the trace
227    pub fn with_resource(mut self, resource: super::Resource) -> Self {
228        self.resource = Some(resource);
229        self
230    }
231}
232
233#[cfg(test)]
234mod tests {
235    use super::*;
236
237    #[test]
238    fn test_span_creation() {
239        let span = Span::new("trace123", "span456", "http.request", 1000, 2000);
240        assert_eq!(span.trace_id, "trace123");
241        assert_eq!(span.span_id, "span456");
242        assert_eq!(span.name, "http.request");
243        assert_eq!(span.start_time, 1000);
244        assert_eq!(span.end_time, 2000);
245    }
246
247    #[test]
248    fn test_span_with_parent() {
249        let span = Span::new("trace123", "span456", "db.query", 1000, 2000).with_parent("span123");
250
251        assert_eq!(span.parent_span_id, Some("span123".to_string()));
252    }
253
254    #[test]
255    fn test_span_duration() {
256        let span = Span::new("trace123", "span456", "operation", 1000, 3500);
257        assert_eq!(span.duration_ns(), 2500);
258    }
259
260    #[test]
261    fn test_span_with_attributes() {
262        let span = Span::new("trace123", "span456", "http.request", 1000, 2000)
263            .with_attribute("http.method", "GET")
264            .with_attribute("http.url", "/api/users");
265
266        assert_eq!(span.attributes.len(), 2);
267        assert_eq!(span.attributes.get("http.method"), Some(&"GET".to_string()));
268    }
269
270    #[test]
271    fn test_trace_with_spans() {
272        let span1 = Span::new("trace123", "span1", "parent", 1000, 3000);
273        let span2 = Span::new("trace123", "span2", "child", 1500, 2500).with_parent("span1");
274
275        let trace = Trace::new("trace123").with_span(span1).with_span(span2);
276
277        assert_eq!(trace.spans.len(), 2);
278        assert_eq!(trace.trace_id, "trace123");
279    }
280}