lc_callbacks/tracing/
tracer.rs1use std::sync::Arc;
2use std::time::Instant;
3
4use uuid::Uuid;
5
6use crate::tracing::span::{make_span, SpanId, SpanKind, SpanStatus, TraceSpan};
7use crate::tracing::TracingBackend;
8
9type SpanStack = std::cell::RefCell<Vec<SpanId>>;
11
12#[allow(clippy::missing_const_for_thread_local)]
14mod span_stack_tls {
15 use super::SpanStack;
16
17 std::thread_local! {
18 pub(super) static SPAN_STACK: SpanStack = const { std::cell::RefCell::new(Vec::new()) };
19 }
20}
21
22tokio::task_local! {
24 pub(super) static ASYNC_SPAN_STACK: SpanStack;
25}
26
27pub async fn init_task_span_stack() {
40 let _ = ASYNC_SPAN_STACK
41 .scope(std::cell::RefCell::new(Vec::new()), async {})
42 .await;
43}
44
45fn get_current_span_id() -> Option<SpanId> {
47 if let Ok(id) = ASYNC_SPAN_STACK.try_with(|stack: &SpanStack| stack.borrow().last().cloned()) {
49 return id;
50 }
51 span_stack_tls::SPAN_STACK.with(|s| s.borrow().last().cloned())
53}
54
55fn push_span_id(id: SpanId) {
57 let id_clone = id.clone();
58 if ASYNC_SPAN_STACK
59 .try_with(|stack: &SpanStack| stack.borrow_mut().push(id))
60 .is_ok()
61 {
62 return;
63 }
64 span_stack_tls::SPAN_STACK.with(|s| s.borrow_mut().push(id_clone));
65}
66
67fn pop_span_id_if_matches(span_id: &str) {
69 if ASYNC_SPAN_STACK
70 .try_with(|stack: &SpanStack| {
71 let mut s = stack.borrow_mut();
72 if s.last().map(|id: &String| id.as_str()) == Some(span_id) {
73 s.pop();
74 }
75 })
76 .is_ok()
77 {
78 return;
79 }
80 span_stack_tls::SPAN_STACK.with(|s| {
81 let mut stack = s.borrow_mut();
82 if stack.last().map(|id: &String| id.as_str()) == Some(span_id) {
83 stack.pop();
84 }
85 });
86}
87
88pub fn clear_span_stack() {
93 span_stack_tls::SPAN_STACK.with(|s| s.borrow_mut().clear());
94 let _ = ASYNC_SPAN_STACK.try_with(|stack: &SpanStack| stack.borrow_mut().clear());
95}
96
97pub struct Tracer {
99 backend: Arc<dyn TracingBackend>,
100}
101
102impl Tracer {
103 pub fn new(backend: Arc<dyn TracingBackend>) -> Self {
105 Self { backend }
106 }
107
108 pub fn start(&self, name: &str, kind: SpanKind) -> SpanGuard {
110 let id = Uuid::now_v7().to_string();
111 let span = make_span(id.clone(), None, name, kind);
112
113 self.backend.start_span(&span);
114
115 push_span_id(id.clone());
117
118 SpanGuard {
119 span,
120 backend: Arc::clone(&self.backend),
121 start_instant: Instant::now(),
122 dropped: false,
123 }
124 }
125
126 pub fn start_child(&self, name: &str, kind: SpanKind) -> SpanGuard {
130 let parent_id = get_current_span_id();
131 let id = Uuid::now_v7().to_string();
132 let span = make_span(id.clone(), parent_id, name, kind);
133
134 self.backend.start_span(&span);
135
136 push_span_id(id.clone());
138
139 SpanGuard {
140 span,
141 backend: Arc::clone(&self.backend),
142 start_instant: Instant::now(),
143 dropped: false,
144 }
145 }
146
147 pub fn start_child_with_parent(
149 &self,
150 name: &str,
151 kind: SpanKind,
152 parent_id: SpanId,
153 ) -> SpanGuard {
154 let id = Uuid::now_v7().to_string();
155 let span = make_span(id.clone(), Some(parent_id), name, kind);
156
157 self.backend.start_span(&span);
158
159 push_span_id(id.clone());
160
161 SpanGuard {
162 span,
163 backend: Arc::clone(&self.backend),
164 start_instant: Instant::now(),
165 dropped: false,
166 }
167 }
168
169 pub fn current_span_id(&self) -> Option<SpanId> {
171 get_current_span_id()
172 }
173
174 pub fn flush(&self) {
176 self.backend.flush();
177 }
178
179 fn end_span(backend: &Arc<dyn TracingBackend>, span: &TraceSpan) {
181 backend.end_span(span);
182 pop_span_id_if_matches(&span.id);
184 }
185}
186
187impl Clone for Tracer {
188 fn clone(&self) -> Self {
189 Self {
190 backend: Arc::clone(&self.backend),
191 }
192 }
193}
194
195pub struct SpanGuard {
203 span: TraceSpan,
204 backend: Arc<dyn TracingBackend>,
205 start_instant: Instant,
206 dropped: bool,
208}
209
210impl SpanGuard {
211 pub fn id(&self) -> &str {
213 &self.span.id
214 }
215
216 pub fn parent_id(&self) -> Option<&str> {
218 self.span.parent_id.as_deref()
219 }
220
221 pub fn with_tokens(mut self, usage: crate::tracing::SpanTokenUsage) -> Self {
223 self.span.tokens = Some(usage);
224 self
225 }
226
227 pub fn with_cost(mut self, cost: f64) -> Self {
229 self.span.cost = Some(cost);
230 self
231 }
232
233 pub fn with_metadata(mut self, key: &str, value: serde_json::Value) -> Self {
235 if let Some(obj) = self.span.metadata.as_object_mut() {
236 obj.insert(key.to_string(), value);
237 }
238 self
239 }
240
241 pub fn set_error(&mut self, msg: &str) {
243 self.span.status = SpanStatus::Error(msg.to_string());
244 }
245
246 pub fn end(mut self) {
248 if !self.dropped {
249 self.span.end_time = Some(chrono::Utc::now().to_rfc3339());
250 self.span.latency_ms = Some(self.start_instant.elapsed().as_millis() as u64);
251 Tracer::end_span(&self.backend, &self.span);
252 self.dropped = true;
253 }
254 }
255}
256
257impl Drop for SpanGuard {
258 fn drop(&mut self) {
259 if !self.dropped {
260 self.span.end_time = Some(chrono::Utc::now().to_rfc3339());
261 self.span.latency_ms = Some(self.start_instant.elapsed().as_millis() as u64);
262 Tracer::end_span(&self.backend, &self.span);
263 self.dropped = true;
264 }
265 }
266}