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