Skip to main content

lc_callbacks/tracing/
backend.rs

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