use super::span::{Span, SpanBuilder, SpanContext};
use std::sync::{Arc, Mutex};
#[derive(Debug, Clone)]
pub struct Tracer {
service_name: String,
spans: Arc<Mutex<Vec<Span>>>,
current_context: Arc<Mutex<Option<SpanContext>>>,
}
impl Tracer {
pub fn new(service_name: impl Into<String>) -> Self {
Self {
service_name: service_name.into(),
spans: Arc::new(Mutex::new(Vec::new())),
current_context: Arc::new(Mutex::new(None)),
}
}
pub fn service_name(&self) -> &str {
&self.service_name
}
pub fn start_span(&self, name: impl Into<String>) -> SpanBuilder {
let context = {
let current = self.current_context.lock().unwrap();
match &*current {
Some(ctx) => ctx.child(),
None => SpanContext::new(),
}
};
SpanBuilder::new(name, context).service_name(self.service_name.clone())
}
pub fn start_span_with_context(
&self,
name: impl Into<String>,
parent: &SpanContext,
) -> SpanBuilder {
SpanBuilder::new(name, parent.child()).service_name(self.service_name.clone())
}
pub fn start_root_span(&self, name: impl Into<String>) -> SpanBuilder {
SpanBuilder::new(name, SpanContext::new()).service_name(self.service_name.clone())
}
pub fn set_current_context(&self, context: SpanContext) {
*self.current_context.lock().unwrap() = Some(context);
}
pub fn clear_current_context(&self) {
*self.current_context.lock().unwrap() = None;
}
pub fn current_context(&self) -> Option<SpanContext> {
self.current_context.lock().unwrap().clone()
}
pub fn record_span(&self, span: Span) {
self.spans.lock().unwrap().push(span);
}
pub fn spans(&self) -> Vec<Span> {
self.spans.lock().unwrap().clone()
}
pub fn drain_spans(&self) -> Vec<Span> {
std::mem::take(&mut *self.spans.lock().unwrap())
}
pub fn span_count(&self) -> usize {
self.spans.lock().unwrap().len()
}
pub fn clear_spans(&self) {
self.spans.lock().unwrap().clear();
}
pub fn in_span<T, F>(&self, name: impl Into<String>, f: F) -> T
where
F: FnOnce(&mut Span) -> T,
{
let mut span = self.start_span(name).start();
let result = f(&mut span);
span.end();
self.record_span(span);
result
}
pub async fn in_span_async<T, F, Fut>(&self, name: impl Into<String>, f: F) -> T
where
F: FnOnce(Span) -> Fut,
Fut: std::future::Future<Output = (T, Span)>,
{
let span = self.start_span(name).start();
let (result, mut span) = f(span).await;
span.end();
self.record_span(span);
result
}
}
#[allow(dead_code)]
pub struct SpanGuard {
span: Option<Span>,
tracer: Tracer,
}
#[allow(dead_code)]
impl SpanGuard {
pub fn new(span: Span, tracer: Tracer) -> Self {
Self {
span: Some(span),
tracer,
}
}
pub fn span(&self) -> &Span {
self.span.as_ref().unwrap()
}
pub fn span_mut(&mut self) -> &mut Span {
self.span.as_mut().unwrap()
}
pub fn event(&mut self, name: impl Into<String>) {
if let Some(span) = &mut self.span {
span.event(name);
}
}
pub fn set_ok(&mut self) {
if let Some(span) = &mut self.span {
span.set_ok();
}
}
pub fn set_error(&mut self, message: impl Into<String>) {
if let Some(span) = &mut self.span {
span.set_error(message);
}
}
pub fn finish(mut self) {
if let Some(mut span) = self.span.take() {
span.end();
self.tracer.record_span(span);
}
}
}
impl Drop for SpanGuard {
fn drop(&mut self) {
if let Some(mut span) = self.span.take() {
span.end();
self.tracer.record_span(span);
}
}
}
#[allow(dead_code)]
pub trait TracerExt {
fn guarded_span(&self, name: impl Into<String>) -> SpanGuard;
}
impl TracerExt for Tracer {
fn guarded_span(&self, name: impl Into<String>) -> SpanGuard {
let span = self.start_span(name).start();
SpanGuard::new(span, self.clone())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tracer_creation() {
let tracer = Tracer::new("test-service");
assert_eq!(tracer.service_name(), "test-service");
assert_eq!(tracer.span_count(), 0);
}
#[test]
fn test_tracer_start_span() {
let tracer = Tracer::new("test-service");
let span = tracer.start_span("operation").start();
assert_eq!(span.name, "operation");
assert_eq!(span.service_name, Some("test-service".to_string()));
}
#[test]
fn test_tracer_record_span() {
let tracer = Tracer::new("test-service");
let mut span = tracer.start_span("operation").start();
span.end();
tracer.record_span(span);
assert_eq!(tracer.span_count(), 1);
let spans = tracer.drain_spans();
assert_eq!(spans.len(), 1);
assert_eq!(tracer.span_count(), 0);
}
#[test]
fn test_tracer_in_span() {
let tracer = Tracer::new("test-service");
let result = tracer.in_span("compute", |span| {
span.set_attribute("input", 42);
42 * 2
});
assert_eq!(result, 84);
assert_eq!(tracer.span_count(), 1);
let spans = tracer.drain_spans();
assert_eq!(spans[0].name, "compute");
assert!(spans[0].is_ended());
}
#[test]
fn test_tracer_context_propagation() {
let tracer = Tracer::new("test-service");
let root = tracer.start_root_span("root").start();
tracer.set_current_context(root.context.clone());
let child = tracer.start_span("child").start();
assert_eq!(child.parent_span_id(), Some(root.span_id()));
assert_eq!(child.trace_id(), root.trace_id());
tracer.clear_current_context();
let new_root = tracer.start_span("new_root").start();
assert!(new_root.parent_span_id().is_none());
}
#[test]
fn test_span_guard() {
let tracer = Tracer::new("test-service");
{
let mut guard = tracer.guarded_span("guarded_operation");
guard.event("started");
guard.set_ok();
}
assert_eq!(tracer.span_count(), 1);
let spans = tracer.drain_spans();
assert!(spans[0].is_ended());
assert_eq!(spans[0].events.len(), 1);
}
#[test]
fn test_span_guard_manual_finish() {
let tracer = Tracer::new("test-service");
let guard = tracer.guarded_span("operation");
guard.finish();
assert_eq!(tracer.span_count(), 1);
}
#[tokio::test]
async fn test_tracer_in_span_async() {
let tracer = Tracer::new("test-service");
let result = tracer
.in_span_async("async_operation", |mut span| async move {
span.event("processing");
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
(42, span)
})
.await;
assert_eq!(result, 42);
assert_eq!(tracer.span_count(), 1);
let spans = tracer.drain_spans();
assert!(spans[0].duration_us.unwrap() >= 10_000);
}
}