use std::sync::Arc;
use std::time::Instant;
use uuid::Uuid;
use crate::callbacks::tracing::span::{make_span, SpanId, SpanKind, SpanStatus, TraceSpan};
use crate::callbacks::tracing::TracingBackend;
type SpanStack = std::cell::RefCell<Vec<SpanId>>;
#[allow(clippy::missing_const_for_thread_local)]
mod span_stack_tls {
use super::SpanStack;
std::thread_local! {
pub(super) static SPAN_STACK: SpanStack = const { std::cell::RefCell::new(Vec::new()) };
}
}
tokio::task_local! {
pub(super) static ASYNC_SPAN_STACK: SpanStack;
}
pub async fn init_task_span_stack() {
let _ = ASYNC_SPAN_STACK
.scope(std::cell::RefCell::new(Vec::new()), async {})
.await;
}
fn get_current_span_id() -> Option<SpanId> {
if let Ok(id) = ASYNC_SPAN_STACK.try_with(|stack: &SpanStack| stack.borrow().last().cloned()) {
return id;
}
span_stack_tls::SPAN_STACK.with(|s| s.borrow().last().cloned())
}
fn push_span_id(id: SpanId) {
let id_clone = id.clone();
if ASYNC_SPAN_STACK
.try_with(|stack: &SpanStack| stack.borrow_mut().push(id))
.is_ok()
{
return;
}
span_stack_tls::SPAN_STACK.with(|s| s.borrow_mut().push(id_clone));
}
fn pop_span_id_if_matches(span_id: &str) {
if ASYNC_SPAN_STACK
.try_with(|stack: &SpanStack| {
let mut s = stack.borrow_mut();
if s.last().map(|id: &String| id.as_str()) == Some(span_id) {
s.pop();
}
})
.is_ok()
{
return;
}
span_stack_tls::SPAN_STACK.with(|s| {
let mut stack = s.borrow_mut();
if stack.last().map(|id: &String| id.as_str()) == Some(span_id) {
stack.pop();
}
});
}
pub fn clear_span_stack() {
span_stack_tls::SPAN_STACK.with(|s| s.borrow_mut().clear());
let _ = ASYNC_SPAN_STACK.try_with(|stack: &SpanStack| stack.borrow_mut().clear());
}
pub struct Tracer {
backend: Arc<dyn TracingBackend>,
}
impl Tracer {
pub fn new(backend: Arc<dyn TracingBackend>) -> Self {
Self { backend }
}
pub fn start(&self, name: &str, kind: SpanKind) -> SpanGuard {
let id = Uuid::now_v7().to_string();
let span = make_span(id.clone(), None, name, kind);
self.backend.start_span(&span);
push_span_id(id.clone());
SpanGuard {
span,
backend: Arc::clone(&self.backend),
start_instant: Instant::now(),
dropped: false,
}
}
pub fn start_child(&self, name: &str, kind: SpanKind) -> SpanGuard {
let parent_id = get_current_span_id();
let id = Uuid::now_v7().to_string();
let span = make_span(id.clone(), parent_id, name, kind);
self.backend.start_span(&span);
push_span_id(id.clone());
SpanGuard {
span,
backend: Arc::clone(&self.backend),
start_instant: Instant::now(),
dropped: false,
}
}
pub fn start_child_with_parent(
&self,
name: &str,
kind: SpanKind,
parent_id: SpanId,
) -> SpanGuard {
let id = Uuid::now_v7().to_string();
let span = make_span(id.clone(), Some(parent_id), name, kind);
self.backend.start_span(&span);
push_span_id(id.clone());
SpanGuard {
span,
backend: Arc::clone(&self.backend),
start_instant: Instant::now(),
dropped: false,
}
}
pub fn current_span_id(&self) -> Option<SpanId> {
get_current_span_id()
}
pub fn flush(&self) {
self.backend.flush();
}
fn end_span(backend: &Arc<dyn TracingBackend>, span: &TraceSpan) {
backend.end_span(span);
pop_span_id_if_matches(&span.id);
}
}
impl Clone for Tracer {
fn clone(&self) -> Self {
Self {
backend: Arc::clone(&self.backend),
}
}
}
pub struct SpanGuard {
span: TraceSpan,
backend: Arc<dyn TracingBackend>,
start_instant: Instant,
dropped: bool,
}
impl SpanGuard {
pub fn id(&self) -> &str {
&self.span.id
}
pub fn parent_id(&self) -> Option<&str> {
self.span.parent_id.as_deref()
}
pub fn with_tokens(mut self, usage: crate::callbacks::tracing::SpanTokenUsage) -> Self {
self.span.tokens = Some(usage);
self
}
pub fn with_cost(mut self, cost: f64) -> Self {
self.span.cost = Some(cost);
self
}
pub fn with_metadata(mut self, key: &str, value: serde_json::Value) -> Self {
if let Some(obj) = self.span.metadata.as_object_mut() {
obj.insert(key.to_string(), value);
}
self
}
pub fn set_error(&mut self, msg: &str) {
self.span.status = SpanStatus::Error(msg.to_string());
}
pub fn end(mut self) {
if !self.dropped {
self.span.end_time = Some(chrono::Utc::now().to_rfc3339());
self.span.latency_ms = Some(self.start_instant.elapsed().as_millis() as u64);
Tracer::end_span(&self.backend, &self.span);
self.dropped = true;
}
}
}
impl Drop for SpanGuard {
fn drop(&mut self) {
if !self.dropped {
self.span.end_time = Some(chrono::Utc::now().to_rfc3339());
self.span.latency_ms = Some(self.start_instant.elapsed().as_millis() as u64);
Tracer::end_span(&self.backend, &self.span);
self.dropped = true;
}
}
}