veecle_telemetry/
span.rs

1//! Distributed tracing spans for tracking units of work.
2//!
3//! This module provides the core span implementation for distributed tracing.
4//! Spans represent units of work within a trace and can be nested to show
5//! relationships between different operations.
6//!
7//! # Key Concepts
8//!
9//! - **Span**: A unit of work within a trace, with a name and optional attributes
10//! - **Span Context**: The trace and span IDs that identify a span within a trace
11//! - **Span Guards**: RAII guards that automatically handle span entry/exit
12//! - **Current Span**: Thread-local tracking of the currently active span
13//!
14//! # Basic Usage
15//!
16//! ```rust
17//! use veecle_telemetry::{CurrentSpan, span};
18//!
19//! // Create and enter a span
20//! let span = span!("operation", user_id = 123);
21//! let _guard = span.entered();
22//!
23//! // Add events to the current span
24//! CurrentSpan::add_event("checkpoint", &[]);
25//!
26//! // Span is automatically exited when guard is dropped
27//! ```
28//!
29//! # Span Lifecycle
30//!
31//! 1. **Creation**: Spans are created with a name and optional attributes
32//! 2. **Entry**: Spans are entered to make them the current active span
33//! 3. **Events**: Events and attributes can be added to active spans
34//! 4. **Exit**: Spans are exited when no longer active
35//! 5. **Close**: Spans are closed when their work is complete
36//!
37//! # Nesting
38//!
39//! Spans can be nested to show relationships:
40//!
41//! ```rust
42//! use veecle_telemetry::span;
43//!
44//! let parent = span!("parent_operation");
45//! let _parent_guard = parent.entered();
46//!
47//! // This span will automatically be a child of the parent
48//! let child = span!("child_operation");
49//! let _child_guard = child.entered();
50//! ```
51
52#[cfg(feature = "enable")]
53use core::cell::Cell;
54use core::marker::PhantomData;
55#[cfg(all(feature = "std", feature = "enable"))]
56use std::thread_local;
57
58use crate::SpanContext;
59#[cfg(feature = "enable")]
60use crate::collector::get_collector;
61use crate::id::SpanId;
62#[cfg(feature = "enable")]
63use crate::protocol::{
64    SpanAddEventMessage, SpanAddLinkMessage, SpanCloseMessage, SpanCreateMessage, SpanEnterMessage,
65    SpanExitMessage, SpanSetAttributeMessage,
66};
67#[cfg(feature = "enable")]
68use crate::time::now;
69use crate::value::KeyValue;
70
71#[cfg(feature = "enable")]
72thread_local! {
73    pub(crate) static CURRENT_SPAN: Cell<Option<SpanContext>> = const { Cell::new(None) };
74}
75
76/// A distributed tracing span representing a unit of work.
77///
78/// Spans are the fundamental building blocks of distributed tracing.
79/// They represent a unit of work within a trace and can be nested to show relationships between different operations.
80///
81/// # Examples
82///
83/// ```rust
84/// use veecle_telemetry::{KeyValue, Span, Value};
85///
86/// // Create a span with attributes
87/// let span = Span::new("database_query", &[
88///     KeyValue::new("table", Value::String("users".into())),
89///     KeyValue::new("operation", Value::String("SELECT".into())),
90/// ]);
91///
92/// // Enter the span to make it active
93/// let _guard = span.enter();
94///
95/// // Add events to the span
96/// span.add_event("query_executed", &[]);
97/// ```
98///
99/// # Conditional Compilation
100///
101/// When the `enable` feature is disabled, spans compile to no-ops with zero runtime overhead.
102#[must_use]
103#[derive(Default, Debug)]
104pub struct Span {
105    #[cfg(feature = "enable")]
106    pub(crate) inner: Option<SpanInner>,
107}
108
109#[cfg(feature = "enable")]
110#[derive(Debug)]
111pub(crate) struct SpanInner {
112    pub(crate) context: SpanContext,
113}
114
115/// Utilities for working with the currently active span.
116///
117/// This struct provides static methods for interacting with the current span
118/// in the thread-local context.
119/// It allows adding events, links, and attributes to the currently active span without needing a direct reference to
120/// it.
121///
122/// # Examples
123///
124/// ```rust
125/// use veecle_telemetry::{CurrentSpan, span};
126///
127/// let span = span!("operation");
128/// let _guard = span.entered();
129///
130/// // Add an event to the current span
131/// CurrentSpan::add_event("milestone", &[]);
132/// ```
133#[derive(Default, Debug)]
134pub struct CurrentSpan;
135
136impl Span {
137    /// Creates a no-op span that performs no tracing operations.
138    ///
139    /// This is useful for creating spans that may be conditionally enabled
140    /// or when telemetry is completely disabled.
141    #[inline]
142    pub fn noop() -> Self {
143        Self {
144            #[cfg(feature = "enable")]
145            inner: None,
146        }
147    }
148
149    /// Creates a new span as a child of the current span.
150    ///
151    /// If there is no current span, this returns a no-op span.
152    /// Uses [`Span::root`] to create a root span with a specific context.
153    ///
154    /// # Arguments
155    ///
156    /// * `name` - The name of the span
157    /// * `attributes` - Key-value attributes to attach to the span
158    ///
159    /// # Examples
160    ///
161    /// ```rust
162    /// use veecle_telemetry::{KeyValue, Span, Value};
163    ///
164    /// let span = Span::new("operation", &[KeyValue::new("user_id", Value::I64(123))]);
165    /// ```
166    pub fn new(name: &'static str, attributes: &'_ [KeyValue<'static>]) -> Self {
167        #[cfg(not(feature = "enable"))]
168        {
169            let _ = (name, attributes);
170            Self::noop()
171        }
172
173        #[cfg(feature = "enable")]
174        {
175            if let Some(parent) = CURRENT_SPAN.get() {
176                Self::new_inner(name, parent, attributes)
177            } else {
178                Self::noop()
179            }
180        }
181    }
182
183    /// Creates a new root span with the given context.
184    ///
185    /// Unlike `new()`, this method does not use the current span from `CURRENT_SPAN`
186    /// and creates a true root span with no parent using the provided `SpanContext` directly.
187    ///
188    /// # Examples
189    ///
190    /// ```
191    /// use veecle_telemetry::{Span, SpanContext};
192    ///
193    /// let context = SpanContext::generate();
194    /// let span = Span::root("root_span", context, &[]);
195    /// ```
196    pub fn root(
197        name: &'static str,
198        mut span_context: SpanContext,
199        attributes: &'_ [KeyValue<'static>],
200    ) -> Self {
201        // Ensure no parent_id will be set.
202        span_context.span_id = SpanId(0);
203
204        #[cfg(not(feature = "enable"))]
205        {
206            let _ = (name, attributes);
207            Self::noop()
208        }
209
210        #[cfg(feature = "enable")]
211        {
212            Self::new_inner(name, span_context, attributes)
213        }
214    }
215
216    /// Enters this span, making it the current active span.
217    ///
218    /// This method returns a guard that will automatically exit the span when dropped.
219    /// The guard borrows the span, so the span must remain alive while the guard exists.
220    ///
221    /// # Examples
222    ///
223    /// ```rust
224    /// use veecle_telemetry::Span;
225    ///
226    /// let span = Span::new("operation", &[]);
227    /// let _guard = span.enter();
228    /// // span is now active
229    /// // span is automatically exited when _guard is dropped
230    /// ```
231    pub fn enter(&'_ self) -> SpanGuardRef<'_> {
232        #[cfg(not(feature = "enable"))]
233        {
234            SpanGuardRef::noop()
235        }
236
237        #[cfg(feature = "enable")]
238        {
239            let Some(context) = self.inner.as_ref().map(|inner| inner.context) else {
240                return SpanGuardRef::noop();
241            };
242
243            self.do_enter();
244            CURRENT_SPAN
245                .try_with(|current| {
246                    let parent = current.get();
247                    current.set(Some(context));
248
249                    SpanGuardRef::new(self, parent)
250                })
251                .unwrap_or(SpanGuardRef::noop())
252        }
253    }
254
255    /// Enters this span by taking ownership of it.
256    ///
257    /// This method consumes the span and returns a guard that owns the span.
258    /// The span will be automatically exited and closed when the guard is dropped.
259    ///
260    /// # Examples
261    ///
262    /// ```rust
263    /// use veecle_telemetry::Span;
264    ///
265    /// let span = Span::new("operation", &[]);
266    /// let _guard = span.entered();
267    /// // span is now active and owned by the guard
268    /// // span is automatically exited and closed when _guard is dropped
269    /// ```
270    pub fn entered(self) -> SpanGuard {
271        #[cfg(not(feature = "enable"))]
272        {
273            SpanGuard::noop()
274        }
275
276        #[cfg(feature = "enable")]
277        {
278            let Some(context) = self.inner.as_ref().map(|inner| inner.context) else {
279                return SpanGuard::noop();
280            };
281
282            self.do_enter();
283            CURRENT_SPAN
284                .try_with(|current| {
285                    let parent = current.get();
286                    current.set(Some(context));
287
288                    SpanGuard::new(self, parent)
289                })
290                .unwrap_or(SpanGuard::noop())
291        }
292    }
293
294    /// Adds an event to this span.
295    ///
296    /// Events represent point-in-time occurrences within a span's lifetime.
297    /// They can include additional attributes for context.
298    ///
299    /// # Arguments
300    ///
301    /// * `name` - The name of the event
302    /// * `attributes` - Key-value attributes providing additional context
303    ///
304    /// # Examples
305    ///
306    /// ```rust
307    /// use veecle_telemetry::{KeyValue, Span, Value};
308    ///
309    /// let span = Span::new("database_query", &[]);
310    /// span.add_event("query_started", &[]);
311    /// span.add_event("query_completed", &[KeyValue::new("rows_returned", Value::I64(42))]);
312    /// ```
313    pub fn add_event(&self, name: &'static str, attributes: &'_ [KeyValue<'static>]) {
314        #[cfg(not(feature = "enable"))]
315        {
316            let _ = (name, attributes);
317        }
318
319        #[cfg(feature = "enable")]
320        {
321            if let Some(inner) = &self.inner {
322                get_collector().span_event(SpanAddEventMessage {
323                    trace_id: inner.context.trace_id,
324                    span_id: inner.context.span_id,
325                    name: name.into(),
326                    time_unix_nano: now().as_nanos(),
327                    attributes: attributes.into(),
328                });
329            }
330        }
331    }
332
333    /// Creates a link from this span to another span.
334    ///
335    /// Links connect spans across different traces, allowing you to represent
336    /// relationships between spans that are not parent-child relationships.
337    ///
338    /// # Examples
339    ///
340    /// ```
341    /// use veecle_telemetry::{Span, SpanContext, SpanId, TraceId};
342    ///
343    /// let span = Span::new("my_span", &[]);
344    /// let external_context = SpanContext::new(TraceId(0x123), SpanId(0x456));
345    /// span.add_link(external_context);
346    /// ```
347    pub fn add_link(&self, link: SpanContext) {
348        #[cfg(not(feature = "enable"))]
349        {
350            let _ = link;
351        }
352
353        #[cfg(feature = "enable")]
354        {
355            if let Some(inner) = self.inner.as_ref() {
356                get_collector().span_link(SpanAddLinkMessage {
357                    trace_id: inner.context.trace_id,
358                    span_id: inner.context.span_id,
359                    link,
360                });
361            }
362        }
363    }
364
365    /// Adds an attribute to this span.
366    ///
367    /// Attributes provide additional context about the work being performed
368    /// in the span. They can be set at any time during the span's lifetime.
369    ///
370    /// # Arguments
371    ///
372    /// * `attribute` - The key-value attribute to set
373    ///
374    /// # Examples
375    ///
376    /// ```rust
377    /// use veecle_telemetry::{KeyValue, Span, Value};
378    ///
379    /// let span = Span::new("user_operation", &[]);
380    /// span.set_attribute(KeyValue::new("user_id", Value::I64(123)));
381    /// span.set_attribute(KeyValue::new("operation_type", Value::String("update".into())));
382    /// ```
383    pub fn set_attribute(&self, attribute: KeyValue<'static>) {
384        #[cfg(not(feature = "enable"))]
385        {
386            let _ = attribute;
387        }
388
389        #[cfg(feature = "enable")]
390        {
391            if let Some(inner) = self.inner.as_ref() {
392                get_collector().span_attribute(SpanSetAttributeMessage {
393                    trace_id: inner.context.trace_id,
394                    span_id: inner.context.span_id,
395                    attribute,
396                });
397            }
398        }
399    }
400}
401
402impl CurrentSpan {
403    /// Adds an event to the current span.
404    ///
405    /// Events represent point-in-time occurrences within a span's lifetime.
406    ///
407    /// # Arguments
408    ///
409    /// * `name` - The name of the event
410    /// * `attributes` - Key-value attributes providing additional context
411    ///
412    /// # Examples
413    ///
414    /// ```rust
415    /// use veecle_telemetry::{CurrentSpan, KeyValue, Value, span};
416    ///
417    /// let _guard = span!("operation").entered();
418    /// CurrentSpan::add_event("checkpoint", &[]);
419    /// CurrentSpan::add_event("milestone", &[KeyValue::new("progress", 75)]);
420    /// ```
421    ///
422    /// Does nothing if there's no active span.
423    pub fn add_event(name: &'static str, attributes: &'_ [KeyValue<'static>]) {
424        #[cfg(not(feature = "enable"))]
425        {
426            let _ = (name, attributes);
427        }
428
429        #[cfg(feature = "enable")]
430        {
431            if let Some(context) = SpanContext::current() {
432                get_collector().span_event(SpanAddEventMessage {
433                    trace_id: context.trace_id,
434                    span_id: context.span_id,
435                    name: name.into(),
436                    time_unix_nano: now().as_nanos(),
437                    attributes: attributes.into(),
438                });
439            }
440        }
441    }
442
443    /// Creates a link from the current span to another span.
444    /// Does nothing if there's no active span.
445    ///
446    /// Links connect spans across different traces, allowing you to represent
447    /// relationships between spans that are not parent-child relationships.
448    ///
449    /// # Examples
450    ///
451    /// ```
452    /// use veecle_telemetry::{CurrentSpan, Span, SpanContext, SpanId, TraceId};
453    ///
454    /// let _guard = Span::new("my_span", &[]).entered();
455    ///
456    /// let external_context = SpanContext::new(TraceId(0x123), SpanId(0x456));
457    /// CurrentSpan::add_link(external_context);
458    /// ```
459    pub fn add_link(link: SpanContext) {
460        #[cfg(not(feature = "enable"))]
461        {
462            let _ = link;
463        }
464
465        #[cfg(feature = "enable")]
466        {
467            if let Some(context) = SpanContext::current() {
468                get_collector().span_link(SpanAddLinkMessage {
469                    trace_id: context.trace_id,
470                    span_id: context.span_id,
471                    link,
472                });
473            }
474        }
475    }
476
477    /// Sets an attribute on the current span.
478    ///
479    /// Attributes provide additional context about the work being performed
480    /// in the span.
481    ///
482    /// # Arguments
483    ///
484    /// * `attribute` - The key-value attribute to set
485    ///
486    /// # Examples
487    ///
488    /// ```rust
489    /// use veecle_telemetry::{CurrentSpan, KeyValue, Value, span};
490    ///
491    /// let _guard = span!("operation").entered();
492    /// CurrentSpan::set_attribute(KeyValue::new("user_id", 123));
493    /// CurrentSpan::set_attribute(KeyValue::new("status", "success"));
494    /// ```
495    ///
496    /// Does nothing if there's no active span.
497    pub fn set_attribute(attribute: KeyValue<'static>) {
498        #[cfg(not(feature = "enable"))]
499        {
500            let _ = attribute;
501        }
502
503        #[cfg(feature = "enable")]
504        {
505            if let Some(context) = SpanContext::current() {
506                get_collector().span_attribute(SpanSetAttributeMessage {
507                    trace_id: context.trace_id,
508                    span_id: context.span_id,
509                    attribute,
510                });
511            }
512        }
513    }
514}
515
516#[cfg(feature = "enable")]
517impl Span {
518    fn new_inner(
519        name: &'static str,
520        parent: SpanContext,
521        attributes: &'_ [KeyValue<'static>],
522    ) -> Self {
523        let span_id = SpanId::next_id();
524        let context = SpanContext::new(parent.trace_id, span_id);
525
526        let parent_id = if parent.span_id == SpanId(0) {
527            None
528        } else {
529            Some(parent.span_id)
530        };
531
532        get_collector().new_span(SpanCreateMessage {
533            trace_id: context.trace_id,
534            span_id: context.span_id,
535            parent_span_id: parent_id,
536            name: name.into(),
537            start_time_unix_nano: now().as_nanos(),
538            attributes: attributes.into(),
539        });
540
541        Self {
542            inner: Some(SpanInner { context }),
543        }
544    }
545
546    fn do_enter(&self) {
547        #[cfg(feature = "enable")]
548        if let Some(inner) = self.inner.as_ref() {
549            let timestamp = now();
550            get_collector().enter_span(SpanEnterMessage {
551                trace_id: inner.context.trace_id,
552                span_id: inner.context.span_id,
553                time_unix_nano: timestamp.0,
554            });
555        }
556    }
557
558    fn do_exit(&self) {
559        #[cfg(feature = "enable")]
560        if let Some(inner) = self.inner.as_ref() {
561            let timestamp = now();
562            get_collector().exit_span(SpanExitMessage {
563                trace_id: inner.context.trace_id,
564                span_id: inner.context.span_id,
565                time_unix_nano: timestamp.0,
566            });
567        }
568    }
569}
570
571impl Drop for Span {
572    fn drop(&mut self) {
573        #[cfg(feature = "enable")]
574        if let Some(inner) = self.inner.take() {
575            let timestamp = now();
576            get_collector().close_span(SpanCloseMessage {
577                trace_id: inner.context.trace_id,
578                span_id: inner.context.span_id,
579                end_time_unix_nano: timestamp.0,
580            });
581        }
582    }
583}
584
585/// Exits and drops the span when this is dropped.
586#[derive(Debug)]
587pub struct SpanGuard {
588    #[cfg(feature = "enable")]
589    pub(crate) inner: Option<SpanGuardInner>,
590
591    /// ```compile_fail
592    /// use veecle_telemetry::span::*;
593    /// trait AssertSend: Send {}
594    ///
595    /// impl AssertSend for SpanGuard {}
596    /// ```
597    _not_send: PhantomNotSend,
598}
599
600#[cfg(feature = "enable")]
601#[derive(Debug)]
602pub(crate) struct SpanGuardInner {
603    span: Span,
604    parent: Option<SpanContext>,
605}
606
607impl SpanGuard {
608    pub(crate) fn noop() -> Self {
609        Self {
610            #[cfg(feature = "enable")]
611            inner: None,
612            _not_send: PhantomNotSend,
613        }
614    }
615
616    #[cfg(feature = "enable")]
617    pub(crate) fn new(span: Span, parent: Option<SpanContext>) -> Self {
618        Self {
619            #[cfg(feature = "enable")]
620            inner: Some(SpanGuardInner { span, parent }),
621            _not_send: PhantomNotSend,
622        }
623    }
624}
625
626impl Drop for SpanGuard {
627    fn drop(&mut self) {
628        #[cfg(feature = "enable")]
629        if let Some(inner) = self.inner.take() {
630            let _ = CURRENT_SPAN.try_with(|current| current.replace(inner.parent));
631            inner.span.do_exit();
632        }
633    }
634}
635
636/// Exits the span when dropped.
637#[derive(Debug)]
638pub struct SpanGuardRef<'a> {
639    #[cfg(feature = "enable")]
640    pub(crate) inner: Option<SpanGuardRefInner<'a>>,
641
642    _phantom: PhantomData<&'a ()>,
643}
644
645#[cfg(feature = "enable")]
646#[derive(Debug)]
647pub(crate) struct SpanGuardRefInner<'a> {
648    span: &'a Span,
649    parent: Option<SpanContext>,
650}
651
652impl<'a> SpanGuardRef<'a> {
653    pub(crate) fn noop() -> Self {
654        Self {
655            #[cfg(feature = "enable")]
656            inner: None,
657            _phantom: PhantomData,
658        }
659    }
660
661    #[cfg(feature = "enable")]
662    pub(crate) fn new(span: &'a Span, parent: Option<SpanContext>) -> Self {
663        Self {
664            #[cfg(feature = "enable")]
665            inner: Some(SpanGuardRefInner { span, parent }),
666            _phantom: PhantomData,
667        }
668    }
669}
670
671impl Drop for SpanGuardRef<'_> {
672    fn drop(&mut self) {
673        #[cfg(feature = "enable")]
674        if let Some(inner) = self.inner.take() {
675            let _ = CURRENT_SPAN.try_with(|current| current.replace(inner.parent));
676            inner.span.do_exit();
677        }
678    }
679}
680
681/// Technically, `SpanGuard` _can_ implement both `Send` *and*
682/// `Sync` safely. It doesn't, because it has a `PhantomNotSend` field,
683/// specifically added in order to make it `!Send`.
684///
685/// Sending an `SpanGuard` guard between threads cannot cause memory unsafety.
686/// However, it *would* result in incorrect behavior, so we add a
687/// `PhantomNotSend` to prevent it from being sent between threads. This is
688/// because it must be *dropped* on the same thread that it was created;
689/// otherwise, the span will never be exited on the thread where it was entered,
690/// and it will attempt to exit the span on a thread that may never have entered
691/// it. However, we still want them to be `Sync` so that a struct holding an
692/// `Entered` guard can be `Sync`.
693///
694/// Thus, this is totally safe.
695#[derive(Debug)]
696struct PhantomNotSend {
697    ghost: PhantomData<*mut ()>,
698}
699
700#[allow(non_upper_case_globals)]
701const PhantomNotSend: PhantomNotSend = PhantomNotSend { ghost: PhantomData };
702
703/// # Safety:
704///
705/// Trivially safe, as `PhantomNotSend` doesn't have any API.
706unsafe impl Sync for PhantomNotSend {}
707
708#[cfg(all(test, feature = "std"))]
709mod tests {
710    use super::*;
711    use crate::{SpanContext, SpanId, TraceId};
712
713    #[test]
714    fn span_noop() {
715        let span = Span::noop();
716        assert!(span.inner.is_none());
717    }
718
719    #[test]
720    fn span_new_without_parent() {
721        CURRENT_SPAN.set(None);
722
723        let span = Span::new("test_span", &[]);
724        assert!(span.inner.is_none());
725    }
726
727    #[test]
728    fn span_new_with_parent() {
729        let parent_context = SpanContext::generate();
730        CURRENT_SPAN.set(Some(parent_context));
731
732        let span = Span::new("child_span", &[]);
733        let inner = span.inner.as_ref().unwrap();
734        assert_eq!(inner.context.trace_id, parent_context.trace_id);
735        assert_ne!(inner.context.span_id, parent_context.span_id);
736
737        CURRENT_SPAN.set(None);
738    }
739
740    #[test]
741    fn span_root() {
742        let root_context = SpanContext::generate();
743        assert_eq!(root_context.span_id, SpanId(0));
744
745        let span = Span::root("root_span", root_context, &[]);
746        let inner = span.inner.as_ref().unwrap();
747        assert_eq!(inner.context.trace_id, root_context.trace_id);
748        assert_ne!(inner.context.span_id, SpanId(0));
749    }
750
751    #[test]
752    fn span_context_from_span() {
753        let root_context = SpanContext::generate();
754        let span = Span::root("test_span", root_context, &[]);
755
756        let extracted_context = SpanContext::from_span(&span);
757        let context = extracted_context.unwrap();
758        assert_eq!(context.trace_id, root_context.trace_id);
759    }
760
761    #[test]
762    fn span_context_from_noop_span() {
763        let span = Span::noop();
764        let extracted_context = SpanContext::from_span(&span);
765        assert!(extracted_context.is_none());
766    }
767
768    #[test]
769    fn span_enter_and_current_context() {
770        CURRENT_SPAN.set(None);
771
772        assert!(SpanContext::current().is_none());
773
774        let root_context = SpanContext::generate();
775        let span = Span::root("test_span", root_context, &[]);
776
777        {
778            let _guard = span.enter();
779            let current_context = SpanContext::current();
780            let context = current_context.unwrap();
781            assert_eq!(context.trace_id, root_context.trace_id);
782        }
783
784        // After guard is dropped, should be back to no current context
785        assert!(SpanContext::current().is_none());
786    }
787
788    #[test]
789    fn span_entered_guard() {
790        CURRENT_SPAN.set(None);
791
792        let root_context = SpanContext::generate();
793        let span = Span::root("test_span", root_context, &[]);
794
795        {
796            let _guard = span.entered();
797            // Should have current context while guard exists
798            let current_context = SpanContext::current();
799            assert!(current_context.is_some());
800        }
801
802        // Should be cleared after guard is dropped
803        assert!(SpanContext::current().is_none());
804    }
805
806    #[test]
807    fn noop_span_operations() {
808        let noop_span = Span::noop();
809
810        {
811            let _guard = noop_span.enter();
812            assert!(SpanContext::current().is_none());
813        }
814
815        let _entered_guard = noop_span.entered();
816        assert!(SpanContext::current().is_none());
817    }
818
819    #[test]
820    fn nested_spans() {
821        CURRENT_SPAN.set(None);
822
823        let root_context = SpanContext::generate();
824        let _root_guard = Span::root("test_span", root_context, &[]).entered();
825
826        let child_span = Span::new("child", &[]);
827        let child_inner = child_span.inner.as_ref().unwrap();
828        assert_eq!(child_inner.context.trace_id, root_context.trace_id);
829        assert_ne!(child_inner.context.span_id, root_context.span_id);
830    }
831
832    #[test]
833    fn span_event() {
834        let context = SpanContext::generate();
835        let span = Span::root("test_span", context, &[]);
836
837        let event_attributes = [KeyValue::new("event_key", "event_value")];
838
839        span.add_event("test_event", &event_attributes);
840
841        let noop_span = Span::noop();
842        noop_span.add_event("noop_event", &event_attributes);
843    }
844
845    #[test]
846    fn span_link() {
847        let context = SpanContext::generate();
848        let span = Span::root("test_span", context, &[]);
849
850        let link_context = SpanContext::new(TraceId(0), SpanId(0));
851        span.add_link(link_context);
852
853        let noop_span = Span::noop();
854        noop_span.add_link(link_context);
855    }
856
857    #[test]
858    fn span_attribute() {
859        let context = SpanContext::generate();
860        let span = Span::root("test_span", context, &[]);
861
862        let attribute = KeyValue::new("test_key", "test_value");
863        span.set_attribute(attribute.clone());
864
865        let noop_span = Span::noop();
866        noop_span.set_attribute(attribute);
867    }
868
869    #[test]
870    fn span_methods_with_entered_span() {
871        let context = SpanContext::generate();
872        let span = Span::root("test_span", context, &[]);
873
874        let _guard = span.enter();
875
876        // All these should work while span is entered
877        span.add_event("entered_event", &[]);
878        span.add_link(SpanContext::new(TraceId(0), SpanId(0)));
879        span.set_attribute(KeyValue::new("entered_key", true));
880    }
881
882    #[test]
883    fn current_span_event_with_active_span() {
884        CURRENT_SPAN.set(None);
885
886        let context = SpanContext::generate();
887        let _root_guard = Span::root("test_span", context, &[]).entered();
888
889        let event_attributes = [KeyValue::new("current_event_key", "current_event_value")];
890        CurrentSpan::add_event("current_test_event", &event_attributes);
891    }
892
893    #[test]
894    fn current_span_link_with_active_span() {
895        CURRENT_SPAN.set(None);
896
897        let context = SpanContext::generate();
898        let _root_guard = Span::root("test_span", context, &[]).entered();
899
900        let link_context = SpanContext::new(TraceId(0), SpanId(0));
901        CurrentSpan::add_link(link_context);
902    }
903
904    #[test]
905    fn current_span_attribute_with_active_span() {
906        CURRENT_SPAN.set(None);
907
908        let context = SpanContext::generate();
909        let span = Span::root("test_span", context, &[]);
910
911        let _guard = span.enter();
912        let attribute = KeyValue::new("current_attr_key", "current_attr_value");
913        CurrentSpan::set_attribute(attribute);
914    }
915}