Skip to main content

ri/observability/
tracing.rs

1//! Copyright © 2025-2026 Wenze Wei. All Rights Reserved.
2//!
3//! This file is part of Ri.
4//! The Ri project belongs to the Dunimd Team.
5//!
6//! Licensed under the Apache License, Version 2.0 (the "License");
7//! You may not use this file except in compliance with the License.
8//! You may obtain a copy of the License at
9//!
10//!     http://www.apache.org/licenses/LICENSE-2.0
11//!
12//! Unless required by applicable law or agreed to in writing, software
13//! distributed under the License is distributed on an "AS IS" BASIS,
14//! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15//! See the License for the specific language governing permissions and
16//! limitations under the License.
17
18#![allow(non_snake_case)]
19
20//! # Distributed Tracing
21//!
22//! This file implements a comprehensive distributed tracing system for the Ri framework. It provides
23//! tools for creating, managing, and propagating trace information across asynchronous operations
24//! and distributed systems. The tracing system follows the W3C Trace Context standard and integrates
25//! with tokio's context propagation mechanism.
26//!
27//! ## Key Components
28//!
29//! - **RiSpanId**: Unique identifier for a span
30//! - **RiTraceId**: Unique identifier for a trace
31//! - **RiSpanKind**: Enumeration of span types (Server, Client, Producer, Consumer, Internal)
32//! - **RiSpanStatus**: Status of a span (Ok, Error, Unset)
33//! - **RiSpan**: A single distributed tracing span with attributes, events, and status
34//! - **RiSpanEvent**: Timed events within a span
35//! - **RiTracingContext**: Thread-local tracing context for propagation
36//! - **RiTracer**: Distributed tracer for creating and managing spans
37//! - **RiTracerManager**: Manager for multiple tracer instances
38//! - **DefaultTracerManager**: Global tracer manager instance
39//!
40//! ## Design Principles
41//!
42//! 1. **W3C Trace Context Compliance**: Follows the W3C Trace Context standard for interoperability
43//! 2. **Async Context Propagation**: Integrates with tokio's context propagation mechanism
44//! 3. **Thread Safety**: Uses Arc and RwLock for safe concurrent access
45//! 4. **Sampling Support**: Configurable sampling rate to control overhead
46//! 5. **Hierarchical Spans**: Supports parent-child span relationships
47//! 6. **Baggage Support**: Allows carrying contextual information across spans
48//! 7. **Extensible**: Easy to add new span kinds and attributes
49//! 8. **Low Overhead**: Efficient implementation with minimal performance impact
50//! 9. **Global Access**: Provides a global tracer manager for easy access
51//! 10. **Serialization Support**: All tracing components are serializable for export
52//!
53//! ## Usage
54//!
55//! ```rust
56//! use ri::observability::{init_tracer, tracer, RiSpanKind, RiSpanStatus};
57//! use ri::core::RiResult;
58//!
59//! async fn example() -> RiResult<()> {
60//!     // Initialize the global tracer with 100% sampling rate
61//!     init_tracer(1.0);
62//!     
63//!     // Get the global tracer
64//!     let tracer = tracer();
65//!     
66//!     // Start a new trace
67//!     let trace_id = tracer.start_trace("example_trace").unwrap();
68//!     
69//!     // Start a child span
70//!     let span_id = tracer.start_span_from_context("child_span", RiSpanKind::Internal).unwrap();
71//!     
72//!     // Add an attribute to the span
73//!     tracer.span_mut(&span_id, |span| {
74//!         span.set_attribute("key".to_string(), "value".to_string());
75//!     })?;
76//!     
77//!     // Add an event to the span
78//!     tracer.span_mut(&span_id, |span| {
79//!         let mut attributes = FxHashMap::default();
80//!         attributes.insert("event_key".to_string(), "event_value".to_string());
81//!         span.add_event("example_event".to_string(), attributes);
82//!     })?;
83//!     
84//!     // End the child span with OK status
85//!     tracer.end_span(&span_id, RiSpanStatus::Ok)?;
86//!     
87//!     Ok(())
88//! }
89//! ```
90
91use serde::{Deserialize, Serialize};
92use std::cell::RefCell;
93use std::collections::HashMap as FxHashMap;
94use std::sync::{Arc, RwLock};
95use std::time::{Duration, SystemTime, UNIX_EPOCH};
96use uuid::Uuid;
97
98use crate::core::RiResult;
99use crate::core::RiError;
100use crate::core::lock::RwLockExtensions;
101
102#[cfg(feature = "pyo3")]
103use pyo3::prelude::*;
104
105/// Distributed tracing span ID
106#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
107pub struct RiSpanId(String);
108
109impl Default for RiSpanId {
110    fn default() -> Self {
111        Self::new()
112    }
113}
114
115impl RiSpanId {
116    pub fn new() -> Self {
117        Self(Uuid::new_v4().to_string())
118    }
119
120    pub fn from_string(s: String) -> Self {
121        Self(s)
122    }
123
124    pub fn as_str(&self) -> &str {
125        &self.0
126    }
127}
128
129/// Distributed tracing trace ID
130#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
131pub struct RiTraceId(String);
132
133impl Default for RiTraceId {
134    fn default() -> Self {
135        Self::new()
136    }
137}
138
139impl RiTraceId {
140    pub fn new() -> Self {
141        Self(Uuid::new_v4().to_string())
142    }
143
144    pub fn from_string(s: String) -> Self {
145        Self(s)
146    }
147
148    pub fn as_str(&self) -> &str {
149        &self.0
150    }
151}
152
153/// Span kind enumeration
154#[derive(Debug, Clone, Serialize, Deserialize)]
155pub enum RiSpanKind {
156    Server,
157    Client,
158    Producer,
159    Consumer,
160    Internal,
161}
162
163/// Span status
164#[derive(Debug, Clone, Serialize, Deserialize)]
165pub enum RiSpanStatus {
166    Ok,
167    Error(String),
168    Unset,
169}
170
171/// A distributed tracing span
172#[derive(Debug, Clone, Serialize, Deserialize)]
173pub struct RiSpan {
174    pub trace_id: RiTraceId,
175    pub span_id: RiSpanId,
176    pub parent_span_id: Option<RiSpanId>,
177    pub name: String,
178    pub kind: RiSpanKind,
179    pub start_time: u64, // microseconds since epoch
180    pub end_time: Option<u64>,
181    pub attributes: FxHashMap<String, String>,
182    pub events: Vec<RiSpanEvent>,
183    pub status: RiSpanStatus,
184}
185
186impl RiSpan {
187    pub fn new(
188        trace_id: RiTraceId,
189        parent_span_id: Option<RiSpanId>,
190        name: String,
191        kind: RiSpanKind,
192    ) -> Self {
193        let start_time = SystemTime::now()
194            .duration_since(UNIX_EPOCH)
195            .unwrap_or(Duration::from_secs(0))
196            .as_micros() as u64;
197
198        Self {
199            trace_id,
200            span_id: RiSpanId::new(),
201            parent_span_id,
202            name,
203            kind,
204            start_time,
205            end_time: None,
206            attributes: FxHashMap::default(),
207            events: Vec::new(),
208            status: RiSpanStatus::Unset,
209        }
210    }
211
212    pub fn set_attribute(&mut self, key: String, value: String) {
213        // Security: Mask sensitive attribute values
214        let safe_value = if Self::is_sensitive_attribute(&key) {
215            Self::mask_sensitive_value(&value)
216        } else {
217            value
218        };
219        self.attributes.insert(key, safe_value);
220    }
221
222    /// Checks if an attribute key is sensitive.
223    ///
224    /// # Security
225    ///
226    /// This method identifies sensitive attributes that should be masked
227    /// to prevent sensitive information leakage in traces.
228    fn is_sensitive_attribute(key: &str) -> bool {
229        let key_lower = key.to_lowercase();
230        let sensitive_patterns = [
231            "password",
232            "passwd",
233            "secret",
234            "key",
235            "token",
236            "auth",
237            "credential",
238            "api_key",
239            "apikey",
240            "private",
241            "session",
242            "cookie",
243            "authorization",
244            "bearer",
245        ];
246
247        for pattern in &sensitive_patterns {
248            if key_lower.contains(pattern) {
249                return true;
250            }
251        }
252        false
253    }
254
255    /// Masks a sensitive value for safe display.
256    ///
257    /// # Security
258    ///
259    /// Shows only first 2 and last 2 characters, with asterisks in between.
260    fn mask_sensitive_value(value: &str) -> String {
261        if value.len() <= 4 {
262            return "*".repeat(value.len().max(4));
263        }
264        
265        let first_chars = &value[..2];
266        let last_chars = &value[value.len()-2..];
267        let middle_len = value.len() - 4;
268        
269        format!("{}{}{}", first_chars, "*".repeat(middle_len), last_chars)
270    }
271
272    pub fn add_event(&mut self, name: String, attributes: FxHashMap<String, String>) {
273        let timestamp = SystemTime::now()
274            .duration_since(UNIX_EPOCH)
275            .unwrap_or(Duration::from_secs(0))
276            .as_micros() as u64;
277
278        self.events.push(RiSpanEvent {
279            name,
280            timestamp,
281            attributes,
282        });
283    }
284
285    pub fn end(&mut self, status: RiSpanStatus) {
286        let end_time = SystemTime::now()
287            .duration_since(UNIX_EPOCH)
288            .unwrap_or(Duration::from_secs(0))
289            .as_micros() as u64;
290
291        self.end_time = Some(end_time);
292        self.status = status;
293    }
294
295    pub fn duration(&self) -> Option<Duration> {
296        if let Some(end_time) = self.end_time {
297            let duration_micros = end_time.saturating_sub(self.start_time);
298            Some(Duration::from_micros(duration_micros))
299        } else {
300            None
301        }
302    }
303}
304
305/// Span event for recording timed occurrences
306#[derive(Debug, Clone, Serialize, Deserialize)]
307pub struct RiSpanEvent {
308    pub name: String,
309    pub timestamp: u64, // microseconds since epoch
310    pub attributes: FxHashMap<String, String>,
311}
312
313/// Thread-local tracing context
314#[derive(Debug, Clone)]
315pub struct RiTracingContext {
316    current_trace_id: Option<RiTraceId>,
317    current_span_id: Option<RiSpanId>,
318    baggage: FxHashMap<String, String>,
319}
320
321// Thread-local storage for tracing context
322thread_local! {
323    static CURRENTONTEXT: RefCell<Option<RiTracingContext>> = const { RefCell::new(None) };
324}
325
326impl Default for RiTracingContext {
327    fn default() -> Self {
328        Self::new()
329    }
330}
331
332impl RiTracingContext {
333    pub fn new() -> Self {
334        Self {
335            current_trace_id: None,
336            current_span_id: None,
337            baggage: FxHashMap::default(),
338        }
339    }
340
341    pub fn with_trace_id(mut self, trace_id: RiTraceId) -> Self {
342        self.current_trace_id = Some(trace_id);
343        self
344    }
345
346    pub fn with_span_id(mut self, span_id: RiSpanId) -> Self {
347        self.current_span_id = Some(span_id);
348        self
349    }
350
351    pub fn set_baggage(&mut self, key: String, value: String) {
352        self.baggage.insert(key, value);
353    }
354
355    pub fn get_baggage(&self, key: &str) -> Option<&String> {
356        self.baggage.get(key)
357    }
358
359    pub fn trace_id(&self) -> Option<&RiTraceId> {
360        self.current_trace_id.as_ref()
361    }
362
363    pub fn span_id(&self) -> Option<&RiSpanId> {
364        self.current_span_id.as_ref()
365    }
366
367    /// Set this context as the current thread-local context
368    pub fn set_as_current(&self) {
369        CURRENTONTEXT.with(|ctx| {
370            *ctx.borrow_mut() = Some(self.clone());
371        });
372    }
373
374    /// Get the current tracing context from thread-local storage
375    pub fn current() -> Option<Self> {
376        CURRENTONTEXT.with(|ctx| {
377            ctx.borrow().clone()
378        })
379    }
380
381    /// Create a new context with the same trace ID but new span ID
382    pub fn new_child(&self, span_id: RiSpanId) -> Self {
383        Self {
384            current_trace_id: self.current_trace_id.clone(),
385            current_span_id: Some(span_id),
386            baggage: self.baggage.clone(),
387        }
388    }
389}
390
391/// Sampling strategy enumeration
392#[derive(Debug, Clone, Serialize, Deserialize)]
393pub enum RiSamplingStrategy {
394    /// Fixed rate sampling (0.0 to 1.0)
395    Rate(f64),
396    /// Trace ID-based deterministic sampling
397    Deterministic(f64),
398    /// Adaptive sampling that adjusts based on load
399    Adaptive(f64),
400}
401
402/// Distributed tracer
403#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
404pub struct RiTracer {
405    spans: Arc<RwLock<FxHashMap<RiTraceId, Vec<RiSpan>>>>,
406    active_spans: Arc<RwLock<FxHashMap<RiSpanId, RiSpan>>>,
407    sampling_strategy: RiSamplingStrategy,
408    adaptive_window: Arc<RwLock<Vec<u64>>>,
409    max_adaptive_window: usize,
410}
411
412impl RiTracer {
413    pub fn new(sampling_rate: f64) -> Self {
414        Self {
415            spans: Arc::new(RwLock::new(FxHashMap::default())),
416            active_spans: Arc::new(RwLock::new(FxHashMap::default())),
417            sampling_strategy: RiSamplingStrategy::Rate(sampling_rate.clamp(0.0, 1.0)),
418            adaptive_window: Arc::new(RwLock::new(Vec::new())),
419            max_adaptive_window: 100,
420        }
421    }
422    
423    /// Create a new tracer with a custom sampling strategy
424    pub fn with_strategy(strategy: RiSamplingStrategy) -> Self {
425        Self {
426            spans: Arc::new(RwLock::new(FxHashMap::default())),
427            active_spans: Arc::new(RwLock::new(FxHashMap::default())),
428            sampling_strategy: strategy,
429            adaptive_window: Arc::new(RwLock::new(Vec::new())),
430            max_adaptive_window: 100,
431        }
432    }
433
434    /// Start a new trace and set it as current context
435    pub fn start_trace(&self, name: String) -> Option<RiTraceId> {
436        if !self.should_sample() {
437            return None;
438        }
439
440        let trace_id = RiTraceId::new();
441        let span = RiSpan::new(trace_id.clone(), None, name, RiSpanKind::Server);
442
443        let span_id = span.span_id.clone();
444        {
445            let mut active_spans = self.active_spans.write_safe("active spans for new trace").ok()?;
446            active_spans.insert(span_id.clone(), span);
447        }
448        {
449            let mut spans = self.spans.write_safe("spans for new trace").ok()?;
450            spans.insert(trace_id.clone(), Vec::new());
451        }
452
453        // Set current context
454        let context = RiTracingContext::new()
455            .with_trace_id(trace_id.clone())
456            .with_span_id(span_id);
457        context.set_as_current();
458
459        Some(trace_id)
460    }
461
462    /// Start a new span in existing trace, using current context if available
463    pub fn start_span(
464        &self,
465        trace_id: Option<&RiTraceId>,
466        parent_span_id: Option<RiSpanId>,
467        name: String,
468        kind: RiSpanKind,
469    ) -> Option<RiSpanId> {
470        // Try to get trace_id from current context if not provided
471        let resolved_trace_id = match trace_id {
472            Some(id) => id.clone(),
473            None => {
474                if let Some(context) = RiTracingContext::current() {
475                    if let Some(id) = context.trace_id() {
476                        id.clone()
477                    } else {
478                        return None;
479                    }
480                } else {
481                    return None;
482                }
483            }
484        };
485
486        // Try to get parent_span_id from current context if not provided
487        let resolved_parent_span_id = match parent_span_id {
488            Some(id) => Some(id.clone()),
489            None => RiTracingContext::current().and_then(|context| context.span_id().cloned()),
490        };
491
492        let spans = match self.spans.read_safe("spans for span check") {
493            Ok(s) => s,
494            Err(_) => return None,
495        };
496        if !spans.contains_key(&resolved_trace_id) {
497            return None;
498        }
499
500        let span = RiSpan::new(
501            resolved_trace_id.clone(),
502            resolved_parent_span_id,
503            name,
504            kind,
505        );
506
507        let span_id = span.span_id.clone();
508        {
509            let mut active_spans = self.active_spans.write_safe("active spans for new span").ok()?;
510            active_spans.insert(span_id.clone(), span);
511        }
512
513        // Update current context with new span
514        if let Some(context) = RiTracingContext::current() {
515            let new_context = context.new_child(span_id.clone());
516            new_context.set_as_current();
517        } else {
518            // Create new context if none exists
519            let context = RiTracingContext::new()
520                .with_trace_id(resolved_trace_id)
521                .with_span_id(span_id.clone());
522            context.set_as_current();
523        }
524
525        Some(span_id)
526    }
527
528    /// Start a new span using current context
529    pub fn start_span_from_context(&self, name: String, kind: RiSpanKind) -> Option<RiSpanId> {
530        self.start_span(None, None, name, kind)
531    }
532
533    /// End a span and restore parent span context if available
534    pub fn end_span(&self, span_id: &RiSpanId, status: RiSpanStatus) -> RiResult<()> {
535        let mut active_spans = self.active_spans.write_safe("active spans for end span")?;
536
537        if let Some(mut span) = active_spans.remove(span_id) {
538            span.end(status);
539
540            let trace_id = span.trace_id.clone();
541            let parent_span_id = span.parent_span_id.clone();
542            drop(active_spans);
543
544            {
545                let mut spans = self.spans.write_safe("spans for end span")?;
546                if let Some(spans_list) = spans.get_mut(&trace_id) {
547                    spans_list.push(span);
548                }
549            }
550
551            // Restore parent span context if available
552            if let Some(parent_span_id) = parent_span_id {
553                // Try to find parent span in active spans
554                let active_spans = self.active_spans.read_safe("active spans for parent check")?;
555                if active_spans.get(&parent_span_id).is_some() {
556                    let context = RiTracingContext::new()
557                        .with_trace_id(trace_id)
558                        .with_span_id(parent_span_id);
559                    context.set_as_current();
560                }
561            } else {
562                // No parent span, clear context
563                let context = RiTracingContext::new();
564                context.set_as_current();
565            }
566        }
567
568        Ok(())
569    }
570
571    /// Get span for modification
572    pub fn span_mut<F>(&self, span_id: &RiSpanId, f: F) -> RiResult<()>
573    where
574        F: FnOnce(&mut RiSpan),
575    {
576        let mut active_spans = self.active_spans.write_safe("active spans for span_mut")?;
577
578        if let Some(span) = active_spans.get_mut(span_id) {
579            f(span);
580            Ok(())
581        } else {
582            Err(crate::core::RiError::Other("Span not found".to_string()))
583        }
584    }
585
586    /// Export completed traces
587    pub fn export_traces(&self) -> FxHashMap<RiTraceId, Vec<RiSpan>> {
588        match self.spans.read_safe("spans for export") {
589            Ok(spans) => spans.clone(),
590            Err(_) => FxHashMap::default(),
591        }
592    }
593
594    /// Get active traces count
595    pub fn active_trace_count(&self) -> usize {
596        match self.spans.read_safe("spans for count") {
597            Ok(spans) => spans.len(),
598            Err(_) => 0,
599        }
600    }
601
602    /// Get active span count
603    pub fn active_span_count(&self) -> usize {
604        match self.active_spans.read_safe("active spans for count") {
605            Ok(active_spans) => active_spans.len(),
606            Err(_) => 0,
607        }
608    }
609
610    fn should_sample(&self) -> bool {
611        match &self.sampling_strategy {
612            RiSamplingStrategy::Rate(rate) => {
613                if *rate >= 1.0 {
614                    true
615                } else if *rate <= 0.0 {
616                    false
617                } else {
618                    use rand::Rng;
619                    let mut rng = rand::thread_rng();
620                    rng.gen::<f64>() < *rate
621                }
622            }
623            RiSamplingStrategy::Deterministic(rate) => {
624                if *rate >= 1.0 {
625                    true
626                } else if *rate <= 0.0 {
627                    false
628                } else {
629                    // Create a deterministic hash based on current time and thread ID
630                    let now = SystemTime::now()
631                        .duration_since(UNIX_EPOCH)
632                        .unwrap_or(Duration::from_secs(0))
633                        .as_nanos();
634                    // Get a numeric representation of the thread ID using hash
635                    let thread_id = format!("{:?}", std::thread::current().id())
636                        .as_bytes()
637                        .iter()
638                        .fold(0u64, |acc, &b| acc.wrapping_mul(31).wrapping_add(b as u64));
639                    let combined = now.wrapping_add(thread_id as u128);
640                    
641                    // Simple hash function
642                    let hash = (combined as u64).wrapping_mul(0x517cc1b727220a95);
643                    let hash_f64 = (hash as f64) / (u64::MAX as f64);
644                    
645                    hash_f64 < *rate
646                }
647            }
648            RiSamplingStrategy::Adaptive(target_rate) => {
649                if *target_rate >= 1.0 {
650                    true
651                } else if *target_rate <= 0.0 {
652                    false
653                } else {
654                    // Calculate current load based on active spans
655                    let active_count = match self.active_spans.read_safe("active spans for sampling") {
656                        Ok(active_spans) => active_spans.len() as f64,
657                        Err(_) => 0.0,
658                    };
659                    
660                    let mut window = match self.adaptive_window.write_safe("adaptive window for sampling") {
661                        Ok(w) => w,
662                        Err(_) => {
663                            // If we can't acquire the lock, default to high load (low sampling rate)
664                            return false;
665                        }
666                    };
667                    
668                    // Add current active count to window
669                    window.push(active_count as u64);
670                    if window.len() > self.max_adaptive_window {
671                        window.remove(0);
672                    }
673                    
674                    // Calculate average load over window
675                    let avg_load = if window.is_empty() {
676                        0.0
677                    } else {
678                        window.iter().sum::<u64>() as f64 / window.len() as f64
679                    };
680                    
681                    // Adaptive sampling: lower rate when load is high, higher when load is low
682                    const BASE_LOAD: f64 = 100.0;
683                    let adjusted_rate = target_rate * (1.0 + (BASE_LOAD - avg_load) / BASE_LOAD);
684                    let clamped_rate = adjusted_rate.clamp(0.01, 1.0);
685                    
686                    use rand::Rng;
687                    let mut rng = rand::thread_rng();
688                    rng.gen::<f64>() < clamped_rate
689                }
690            }
691        }
692    }
693
694}
695
696#[cfg(feature = "pyo3")]
697#[pyo3::prelude::pymethods]
698impl RiTracer {
699    /// Create a new tracer from Python with a sampling rate
700    #[new]
701    fn py_new(sampling_rate: f64) -> Self {
702        Self::new(sampling_rate)
703    }
704
705    /// Start a new trace from Python
706    #[pyo3(name = "start_trace")]
707    fn start_trace_impl(&self, name: String) -> PyResult<Option<String>> {
708        match self.start_trace(name) {
709            Some(trace_id) => Ok(Some(trace_id.as_str().to_string())),
710            None => Ok(None),
711        }
712    }
713
714    /// Start a new span from Python using current context
715    #[pyo3(name = "start_span_from_context")]
716    fn start_span_from_context_impl(&self, name: String, kind: String) -> PyResult<Option<String>> {
717        let span_kind = match kind.as_str() {
718            "Server" => RiSpanKind::Server,
719            "Client" => RiSpanKind::Client,
720            "Producer" => RiSpanKind::Producer,
721            "Consumer" => RiSpanKind::Consumer,
722            _ => RiSpanKind::Internal,
723        };
724
725        match self.start_span_from_context(name, span_kind) {
726            Some(span_id) => Ok(Some(span_id.as_str().to_string())),
727            None => Ok(None),
728        }
729    }
730
731    /// End a span from Python
732    #[pyo3(name = "end_span")]
733    fn end_span_impl(&self, span_id: String, status: String) -> PyResult<()> {
734        let span_id_obj = RiSpanId::from_string(span_id);
735        let span_status = match status.as_str() {
736            "Ok" => RiSpanStatus::Ok,
737            "Error" => RiSpanStatus::Error("Python error".to_string()),
738            _ => RiSpanStatus::Unset,
739        };
740
741        self.end_span(&span_id_obj, span_status)
742            .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("Failed to end span: {e}")))
743    }
744
745    /// Set span attribute from Python
746    #[pyo3(name = "span_set_attribute")]
747    fn span_set_attribute_impl(&self, span_id: String, key: String, value: String) -> PyResult<()> {
748        let span_id_obj = RiSpanId::from_string(span_id);
749        self.span_mut(&span_id_obj, |span| {
750            span.set_attribute(key, value);
751        })
752        .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("Failed to set attribute: {e}")))
753    }
754
755    /// Add span event from Python
756    #[pyo3(name = "span_add_event")]
757    fn span_add_event_impl(&self, span_id: String, name: String, attributes: FxHashMap<String, String>) -> PyResult<()> {
758        let span_id_obj = RiSpanId::from_string(span_id);
759        self.span_mut(&span_id_obj, |span| {
760            span.add_event(name, attributes);
761        })
762        .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("Failed to add event: {e}")))
763    }
764
765    /// Export traces from Python
766    #[pyo3(name = "export_traces")]
767    fn export_traces_impl(&self, py: pyo3::Python<'_>) -> PyResult<FxHashMap<String, Vec<pyo3::Py<pyo3::PyAny>>>> {
768        let traces = self.export_traces();
769        let mut result = FxHashMap::with_capacity(traces.len());
770
771        for (trace_id, spans) in traces {
772            let mut span_list = Vec::with_capacity(spans.len());
773            for span in spans {
774                let span_dict = pyo3::types::PyDict::new(py);
775                span_dict.set_item("trace_id", span.trace_id.as_str())?;
776                span_dict.set_item("span_id", span.span_id.as_str())?;
777                if let Some(parent_id) = &span.parent_span_id {
778                    span_dict.set_item("parent_span_id", parent_id.as_str())?;
779                }
780                span_dict.set_item("name", &span.name)?;
781                span_dict.set_item("kind", format!("{:?}", span.kind))?;
782                span_dict.set_item("start_time", span.start_time)?;
783                span_dict.set_item("end_time", span.end_time)?;
784                span_dict.set_item("attributes", span.attributes.clone())?;
785                span_dict.set_item("events", span.events.len())?;
786                span_dict.set_item("status", format!("{:?}", span.status))?;
787                span_list.push(span_dict.into());
788            }
789            result.insert(trace_id.as_str().to_string(), span_list);
790        }
791        Ok(result)
792    }
793
794    /// Get active trace count from Python
795    #[pyo3(name = "active_trace_count")]
796    fn active_trace_count_impl(&self) -> usize {
797        self.active_trace_count()
798    }
799
800    /// Get active span count from Python
801    #[pyo3(name = "active_span_count")]
802    fn active_span_count_impl(&self) -> usize {
803        self.active_span_count()
804    }
805}
806
807/// Tracer manager for managing multiple tracer instances
808pub struct RiTracerManager {
809    tracers: FxHashMap<String, Arc<RiTracer>>,
810    default_tracer: Option<String>,
811}
812
813impl Default for RiTracerManager {
814    fn default() -> Self {
815        Self::new()
816    }
817}
818
819impl RiTracerManager {
820    pub fn new() -> Self {
821        Self {
822            tracers: FxHashMap::default(),
823            default_tracer: None,
824        }
825    }
826
827    pub fn register_tracer(&mut self, name: &str, tracer: Arc<RiTracer>) {
828        self.tracers.insert(name.to_string(), tracer);
829        if self.default_tracer.is_none() {
830            self.default_tracer = Some(name.to_string());
831        }
832    }
833
834    #[allow(dead_code)]
835    pub fn get_tracer(&self, name: &str) -> Option<&Arc<RiTracer>> {
836        self.tracers.get(name)
837    }
838
839    pub fn get_default_tracer(&self) -> Option<&Arc<RiTracer>> {
840        if let Some(default_name) = &self.default_tracer {
841            self.tracers.get(default_name)
842        } else {
843            None
844        }
845    }
846
847    #[allow(dead_code)]
848    pub fn set_default_tracer(&mut self, name: &str) -> bool {
849        if self.tracers.contains_key(name) {
850            self.default_tracer = Some(name.to_string());
851            true
852        } else {
853            false
854        }
855    }
856
857    #[allow(dead_code)]
858    pub fn remove_tracer(&mut self, name: &str) -> bool {
859        let removed = self.tracers.remove(name).is_some();
860        if let Some(default_name) = &self.default_tracer {
861            if default_name == name {
862                self.default_tracer = None;
863            }
864        }
865        removed
866    }
867}
868
869/// Default tracer manager instance
870pub struct DefaultTracerManager {
871    inner: Arc<RwLock<RiTracerManager>>,
872}
873
874impl Default for DefaultTracerManager {
875    fn default() -> Self {
876        Self {
877            inner: Arc::new(RwLock::new(RiTracerManager::new())),
878        }
879    }
880}
881
882impl DefaultTracerManager {
883    #[allow(dead_code)]
884    pub fn new() -> Self {
885        Default::default()
886    }
887
888    pub async fn register_tracer(&self, name: &str, sampling_rate: f64) -> RiResult<()> {
889        let tracer = Arc::new(RiTracer::new(sampling_rate));
890        let mut manager = self.inner.write_safe("tracer manager for register")?;
891        manager.register_tracer(name, tracer);
892        Ok(())
893    }
894    
895    pub async fn register_tracer_with_strategy(&self, name: &str, strategy: RiSamplingStrategy) -> RiResult<()> {
896        let tracer = Arc::new(RiTracer::with_strategy(strategy));
897        let mut manager = self.inner.write_safe("tracer manager for register with strategy")?;
898        manager.register_tracer(name, tracer);
899        Ok(())
900    }
901
902    #[allow(dead_code)]
903    pub async fn get_tracer(&self, name: &str) -> RiResult<Option<Arc<RiTracer>>> {
904        let manager = self.inner.read_safe("tracer manager for get")?;
905        Ok(manager.get_tracer(name).cloned())
906    }
907
908    pub async fn get_default_tracer(&self) -> RiResult<Option<Arc<RiTracer>>> {
909        let manager = self.inner.read_safe("tracer manager for get default")?;
910        Ok(manager.get_default_tracer().cloned())
911    }
912
913    #[allow(dead_code)]
914    pub async fn set_default_tracer(&self, name: &str) -> RiResult<bool> {
915        let mut manager = self.inner.write_safe("tracer manager for set default")?;
916        Ok(manager.set_default_tracer(name))
917    }
918
919    #[allow(dead_code)]
920    pub async fn remove_tracer(&self, name: &str) -> RiResult<bool> {
921        let mut manager = self.inner.write_safe("tracer manager for remove")?;
922        Ok(manager.remove_tracer(name))
923    }
924}
925
926/// Global tracer manager instance
927pub static DEFAULT_TRACER_MANAGER: std::sync::LazyLock<DefaultTracerManager> = std::sync::LazyLock::new(DefaultTracerManager::default);
928
929/// Initialize global tracer with fixed rate (backward compatibility)
930pub fn init_tracer(sampling_rate: f64) {
931    let runtime = match tokio::runtime::Builder::new_current_thread()
932        .enable_all()
933        .build()
934    {
935        Ok(r) => r,
936        Err(e) => {
937            eprintln!("Failed to create tokio runtime: {}", e);
938            return;
939        }
940    };
941    
942    runtime.block_on(async {
943        if let Err(e) = DEFAULT_TRACER_MANAGER.register_tracer("default", sampling_rate).await {
944            eprintln!("Failed to register tracer: {}", e);
945        }
946    });
947}
948
949/// Initialize global tracer with custom sampling strategy
950pub fn init_tracer_with_strategy(strategy: RiSamplingStrategy) {
951    let rate = match strategy {
952        RiSamplingStrategy::Rate(rate) => rate,
953        RiSamplingStrategy::Deterministic(rate) => rate,
954        RiSamplingStrategy::Adaptive(rate) => rate,
955    };
956    
957    let runtime = match tokio::runtime::Builder::new_current_thread()
958        .enable_all()
959        .build()
960    {
961        Ok(r) => r,
962        Err(e) => {
963            eprintln!("Failed to create tokio runtime: {}", e);
964            return;
965        }
966    };
967    
968    runtime.block_on(async {
969        if let Err(e) = DEFAULT_TRACER_MANAGER.register_tracer("default", rate).await {
970            eprintln!("Failed to register tracer: {}", e);
971        }
972    });
973}
974
975/// Get global tracer (backward compatibility)
976pub fn tracer() -> Result<Arc<RiTracer>, Box<RiError>> {
977    let runtime = match tokio::runtime::Builder::new_current_thread()
978        .enable_all()
979        .build()
980    {
981        Ok(r) => r,
982        Err(e) => {
983            return Err(Box::new(RiError::Other(format!(
984                "Failed to create tokio runtime for tracer: {}",
985                e
986            ))));
987        }
988    };
989
990    runtime.block_on(async {
991        match DEFAULT_TRACER_MANAGER.get_default_tracer().await {
992            Ok(Some(tracer)) => Ok(tracer),
993            Ok(None) => {
994                Err(Box::new(RiError::Other(
995                    "Tracer not initialized".to_string(),
996                )))
997            }
998            Err(e) => Err(Box::new(RiError::Other(format!(
999                "Failed to get tracer: {}",
1000                e
1001            )))),
1002        }
1003    })
1004}