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