Skip to main content

lc_callbacks/tracing/
backend.rs

1use std::sync::Mutex;
2
3use super::span::{build_tree, TraceSpan};
4use super::TracingBackend;
5
6// ---------------------------------------------------------------------------
7// In-memory backend
8// ---------------------------------------------------------------------------
9
10/// In-memory tracing backend for development and testing.
11pub struct InMemoryTracingBackend {
12    spans: Mutex<Vec<TraceSpan>>,
13}
14
15impl InMemoryTracingBackend {
16    /// Create a new empty in-memory backend.
17    pub fn new() -> Self {
18        Self {
19            spans: Mutex::new(Vec::new()),
20        }
21    }
22
23    /// Return a snapshot of all recorded spans.
24    pub fn spans(&self) -> Vec<TraceSpan> {
25        self.spans.lock().unwrap_or_else(|e| e.into_inner()).clone()
26    }
27
28    /// Remove all recorded spans.
29    pub fn clear(&self) {
30        self.spans.lock().unwrap_or_else(|e| e.into_inner()).clear();
31    }
32
33    /// Get the full trace tree rooted at `root_id`.
34    ///
35    /// Returns `None` if no span with that ID exists.
36    pub fn trace_tree(&self, root_id: &str) -> Option<super::TraceNode> {
37        let spans = self.spans.lock().unwrap_or_else(|e| e.into_inner());
38        let root = spans.iter().find(|s| s.id == root_id)?;
39        Some(build_tree(root, &spans))
40    }
41}
42
43impl Default for InMemoryTracingBackend {
44    fn default() -> Self {
45        Self::new()
46    }
47}
48
49impl TracingBackend for InMemoryTracingBackend {
50    fn start_span(&self, span: &TraceSpan) {
51        self.spans
52            .lock()
53            .unwrap_or_else(|e| e.into_inner())
54            .push(span.clone());
55    }
56
57    fn end_span(&self, span: &TraceSpan) {
58        let mut spans = self.spans.lock().unwrap_or_else(|e| e.into_inner());
59        if let Some(existing) = spans.iter_mut().find(|s| s.id == span.id) {
60            *existing = span.clone();
61        }
62    }
63
64    fn flush(&self) {
65        // In-memory backend has nothing to flush.
66    }
67}
68
69// ---------------------------------------------------------------------------
70// Console / logging backend
71// ---------------------------------------------------------------------------
72
73/// Console logging backend that prints span lifecycle events.
74pub struct ConsoleTracingBackend;
75
76impl TracingBackend for ConsoleTracingBackend {
77    fn start_span(&self, span: &TraceSpan) {
78        println!("[TRACE START] {} ({})", span.name, span.kind);
79    }
80
81    fn end_span(&self, span: &TraceSpan) {
82        let latency = span.latency_ms.unwrap_or(0);
83        let status_str = match &span.status {
84            super::SpanStatus::Ok => "OK".to_string(),
85            super::SpanStatus::Error(e) => format!("ERROR: {}", e),
86        };
87        println!(
88            "[TRACE END]   {} latency={}ms status={}",
89            span.name, latency, status_str
90        );
91    }
92
93    fn flush(&self) {}
94}
95
96// ---------------------------------------------------------------------------
97// OpenTelemetry backend (feature-gated)
98// ---------------------------------------------------------------------------
99
100/// OpenTelemetry tracing backend (requires `opentelemetry` feature).
101///
102/// Converts framework trace spans into OTel spans via the global tracer.
103#[cfg(feature = "opentelemetry")]
104pub struct OtelTracingBackend {
105    tracer: opentelemetry::global::BoxedTracer,
106    spans: Mutex<Vec<opentelemetry::global::BoxedSpan>>,
107}
108
109#[cfg(feature = "opentelemetry")]
110impl OtelTracingBackend {
111    /// Create a new backend with the given tracer.
112    pub fn new(tracer: opentelemetry::global::BoxedTracer) -> Self {
113        Self {
114            tracer,
115            spans: Mutex::new(Vec::new()),
116        }
117    }
118
119    /// Create a backend using the global tracer provider.
120    pub fn from_global(name: &str) -> Self {
121        Self::new(opentelemetry::global::tracer(name.to_string()))
122    }
123}
124
125#[cfg(feature = "opentelemetry")]
126impl TracingBackend for OtelTracingBackend {
127    fn start_span(&self, span: &TraceSpan) {
128        use opentelemetry::trace::Tracer as OtelTracer;
129        let otel_span = OtelTracer::start(&self.tracer, span.name.clone());
130        self.spans
131            .lock()
132            .unwrap_or_else(|e| e.into_inner())
133            .push(otel_span);
134    }
135
136    fn end_span(&self, _span: &TraceSpan) {
137        use opentelemetry::trace::Span as OtelSpan;
138        if let Some(mut s) = self.spans.lock().unwrap_or_else(|e| e.into_inner()).pop() {
139            OtelSpan::end(&mut s);
140        }
141    }
142
143    fn flush(&self) {}
144}