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}