use std::sync::Arc;
use std::time::Instant;
use uuid::Uuid;
use crate::tracing::span::{make_span, SpanId, SpanKind, SpanStatus, TraceSpan};
use crate::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::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_gen_ai_request_model(mut self, model: impl Into<String>) -> Self {
self.span.gen_ai_request_model = Some(model.into());
self
}
pub fn with_gen_ai_response_model(mut self, model: impl Into<String>) -> Self {
self.span.gen_ai_response_model = Some(model.into());
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());
}
fn finalize_cost(&mut self) {
if self.span.cost.is_some() {
return;
}
let tokens = match &self.span.tokens {
Some(t) => t.clone(),
None => return,
};
let model = match self
.span
.gen_ai_request_model
.as_deref()
.or(self.span.gen_ai_response_model.as_deref())
{
Some(m) => m.to_string(),
None => return,
};
self.span.cost = crate::pricing::estimate_cost_usd(
tokens.prompt_tokens,
tokens.completion_tokens,
&model,
);
}
pub fn end(mut self) {
if !self.dropped {
self.finalize_cost();
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.finalize_cost();
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;
}
}
}