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//!
13//! # Basic Usage
14//!
15//! ```rust
16//! use veecle_telemetry::{span, CurrentSpan};
17//!
18//! // Create and enter a span
19//! let span = span!("operation", user_id = 123);
20//! let _guard = span.entered();
21//!
22//! // Add events to the current span
23//! CurrentSpan::add_event("checkpoint", &[]);
24//!
25//! // Span is automatically exited when guard is dropped
26//! ```
27//!
28//! # Span Lifecycle
29//!
30//! 1. **Creation**: Spans are created with a name and optional attributes
31//! 2. **Entry**: Spans are entered to make them the current active span
32//! 3. **Events**: Events and attributes can be added to active spans
33//! 4. **Exit**: Spans are exited when no longer active
34//! 5. **Close**: Spans are closed when their work is complete
35//!
36//! # Nesting
37//!
38//! Spans can be nested to show relationships:
39//!
40//! ```rust
41//! use veecle_telemetry::span;
42//!
43//! let parent = span!("parent_operation");
44//! let _parent_guard = parent.entered();
45//!
46//! // This span will automatically be a child of the parent
47//! let child = span!("child_operation");
48//! let _child_guard = child.entered();
49//! ```
50
51use core::marker::PhantomData;
52
53use crate::SpanContext;
54#[cfg(feature = "enable")]
55use crate::collector::get_collector;
56#[cfg(feature = "enable")]
57use crate::id::SpanId;
58use crate::protocol::transient::KeyValue;
59
60/// A distributed tracing span representing a unit of work.
61///
62/// Spans are the fundamental building blocks of distributed tracing.
63/// They represent a unit of work within a trace and can be nested to show relationships between different operations.
64///
65/// # Examples
66///
67/// ```rust
68/// use veecle_telemetry::Span;
69/// use veecle_telemetry::protocol::transient::{KeyValue, Value};
70///
71/// // Create a span with attributes
72/// let span = Span::new("database_query", &[
73///     KeyValue::new("table", "users"),
74///     KeyValue::new("operation", "SELECT"),
75/// ]);
76///
77/// // Enter the span to make it active
78/// let _guard = span.enter();
79///
80/// // Add events to the span
81/// span.add_event("query_executed", &[]);
82/// ```
83///
84/// # Conditional Compilation
85///
86/// When the `enable` feature is disabled, spans compile to no-ops with zero runtime overhead.
87#[must_use]
88#[derive(Default, Debug)]
89pub struct Span {
90    #[cfg(feature = "enable")]
91    pub(crate) span_id: Option<SpanId>,
92}
93
94/// Utilities for working with the currently active span.
95///
96/// This struct provides static methods for interacting with the current span
97/// in the thread-local context.
98/// It allows adding events, links, and attributes to the currently active span without needing a direct reference to
99/// it.
100///
101/// # Examples
102///
103/// ```rust
104/// use veecle_telemetry::{CurrentSpan, span};
105///
106/// let span = span!("operation");
107/// let _guard = span.entered();
108///
109/// // Add an event to the current span
110/// CurrentSpan::add_event("milestone", &[]);
111/// ```
112#[derive(Default, Debug)]
113pub struct CurrentSpan;
114
115impl Span {
116    /// Creates a no-op span that performs no tracing operations.
117    ///
118    /// This is useful for creating spans that may be conditionally enabled
119    /// or when telemetry is completely disabled.
120    #[inline]
121    pub fn noop() -> Self {
122        Self {
123            #[cfg(feature = "enable")]
124            span_id: None,
125        }
126    }
127
128    /// Creates a new span as a child of the current span.
129    ///
130    /// If there is no current span, this returns a new root span.
131    ///
132    /// # Arguments
133    ///
134    /// * `name` - The name of the span
135    /// * `attributes` - Key-value attributes to attach to the span
136    ///
137    /// # Examples
138    ///
139    /// ```rust
140    /// use veecle_telemetry::Span;
141    /// use veecle_telemetry::protocol::transient::{KeyValue, Value};
142    ///
143    /// let span = Span::new("operation", &[KeyValue::new("user_id", Value::I64(123))]);
144    /// ```
145    pub fn new<'a>(name: &'a str, attributes: &'a [KeyValue<'a>]) -> Self {
146        #[cfg(not(feature = "enable"))]
147        {
148            let _ = (name, attributes);
149            Self::noop()
150        }
151
152        #[cfg(feature = "enable")]
153        {
154            Self::new_inner(name, attributes)
155        }
156    }
157
158    /// Creates a [`SpanContext`] from this [`Span`].
159    /// For a noop span, this function will return `None`.
160    ///
161    /// # Examples
162    ///
163    /// ```
164    /// use veecle_telemetry::Span;
165    ///
166    /// let span = Span::new("root_span", &[]);
167    /// assert!(span.context().is_some());
168    /// ```
169    pub fn context(&self) -> Option<SpanContext> {
170        #[cfg(not(feature = "enable"))]
171        {
172            None
173        }
174
175        #[cfg(feature = "enable")]
176        {
177            self.span_id
178                .map(|span_id| SpanContext::new(get_collector().process_id(), span_id))
179        }
180    }
181
182    /// Enters this span, making it the current active span.
183    ///
184    /// This method returns a guard that will automatically exit the span when dropped.
185    /// The guard borrows the span, so the span must remain alive while the guard exists.
186    ///
187    /// # Examples
188    ///
189    /// ```rust
190    /// use veecle_telemetry::Span;
191    ///
192    /// let span = Span::new("operation", &[]);
193    /// let _guard = span.enter();
194    /// // span is now active
195    /// // span is automatically exited when _guard is dropped
196    /// ```
197    pub fn enter(&'_ self) -> SpanGuardRef<'_> {
198        #[cfg(not(feature = "enable"))]
199        {
200            SpanGuardRef::noop()
201        }
202
203        #[cfg(feature = "enable")]
204        {
205            self.do_enter();
206            SpanGuardRef::new(self)
207        }
208    }
209
210    /// Enters this span by taking ownership of it.
211    ///
212    /// This method consumes the span and returns a guard that owns the span.
213    /// The span will be automatically exited and closed when the guard is dropped.
214    ///
215    /// # Examples
216    ///
217    /// ```rust
218    /// use veecle_telemetry::Span;
219    ///
220    /// let span = Span::new("operation", &[]);
221    /// let _guard = span.entered();
222    /// // span is now active and owned by the guard
223    /// // span is automatically exited and closed when _guard is dropped
224    /// ```
225    pub fn entered(self) -> SpanGuard {
226        #[cfg(not(feature = "enable"))]
227        {
228            SpanGuard::noop()
229        }
230
231        #[cfg(feature = "enable")]
232        {
233            self.do_enter();
234            SpanGuard::new(self)
235        }
236    }
237
238    /// Adds an event to this span.
239    ///
240    /// Events represent point-in-time occurrences within a span's lifetime.
241    /// They can include additional attributes for context.
242    ///
243    /// # Arguments
244    ///
245    /// * `name` - The name of the event
246    /// * `attributes` - Key-value attributes providing additional context
247    ///
248    /// # Examples
249    ///
250    /// ```rust
251    /// use veecle_telemetry::Span;
252    /// use veecle_telemetry::protocol::transient::{KeyValue, Value};
253    ///
254    /// let span = Span::new("database_query", &[]);
255    /// span.add_event("query_started", &[]);
256    /// span.add_event("query_completed", &[KeyValue::new("rows_returned", Value::I64(42))]);
257    /// ```
258    pub fn add_event<'a>(&self, name: &'a str, attributes: &'a [KeyValue<'a>]) {
259        #[cfg(not(feature = "enable"))]
260        {
261            let _ = (name, attributes);
262        }
263
264        #[cfg(feature = "enable")]
265        {
266            if let Some(span_id) = self.span_id {
267                get_collector().span_event(Some(span_id), name, attributes);
268            }
269        }
270    }
271
272    /// Creates a link from this span to another span.
273    ///
274    /// Links connect spans across different traces, allowing you to represent
275    /// relationships between spans that are not parent-child relationships.
276    ///
277    /// # Examples
278    ///
279    /// ```
280    /// use veecle_telemetry::{Span, SpanContext, SpanId, ProcessId};
281    ///
282    /// let span = Span::new("my_span", &[]);
283    /// let external_context = SpanContext::new(ProcessId::from_raw(0x123), SpanId(0x456));
284    /// span.add_link(external_context);
285    /// ```
286    pub fn add_link(&self, link: SpanContext) {
287        #[cfg(not(feature = "enable"))]
288        {
289            let _ = link;
290        }
291
292        #[cfg(feature = "enable")]
293        {
294            if let Some(span_id) = self.span_id {
295                get_collector().span_link(Some(span_id), link);
296            }
297        }
298    }
299
300    /// Adds an attribute to this span.
301    ///
302    /// Attributes provide additional context about the work being performed
303    /// in the span. They can be set at any time during the span's lifetime.
304    ///
305    /// # Arguments
306    ///
307    /// * `attribute` - The key-value attribute to set
308    ///
309    /// # Examples
310    ///
311    /// ```rust
312    /// use veecle_telemetry::Span;
313    /// use veecle_telemetry::protocol::transient::{KeyValue, Value};
314    ///
315    /// let span = Span::new("user_operation", &[]);
316    /// span.set_attribute(KeyValue::new("user_id", 123));
317    /// span.set_attribute(KeyValue::new("operation_type", "update"));
318    /// ```
319    pub fn set_attribute<'a>(&self, attribute: KeyValue<'a>) {
320        #[cfg(not(feature = "enable"))]
321        {
322            let _ = attribute;
323        }
324
325        #[cfg(feature = "enable")]
326        {
327            if let Some(span_id) = self.span_id {
328                get_collector().span_attribute(Some(span_id), attribute);
329            }
330        }
331    }
332}
333
334impl CurrentSpan {
335    /// Adds an event to the current span.
336    ///
337    /// Events represent point-in-time occurrences within a span's lifetime.
338    ///
339    /// # Arguments
340    ///
341    /// * `name` - The name of the event
342    /// * `attributes` - Key-value attributes providing additional context
343    ///
344    /// # Examples
345    ///
346    /// ```rust
347    /// use veecle_telemetry::{CurrentSpan, span};
348    /// use veecle_telemetry::protocol::transient::KeyValue;
349    ///
350    /// let _guard = span!("operation").entered();
351    /// CurrentSpan::add_event("checkpoint", &[]);
352    /// CurrentSpan::add_event("milestone", &[KeyValue::new("progress", 75)]);
353    /// ```
354    pub fn add_event<'a>(name: &'a str, attributes: &'a [KeyValue<'a>]) {
355        #[cfg(not(feature = "enable"))]
356        {
357            let _ = (name, attributes);
358        }
359
360        #[cfg(feature = "enable")]
361        {
362            get_collector().span_event(None, name, attributes);
363        }
364    }
365
366    /// Creates a link from the current span to another span.
367    ///
368    /// Links connect spans across different traces, allowing you to represent
369    /// relationships between spans that are not parent-child relationships.
370    ///
371    /// # Examples
372    ///
373    /// ```
374    /// use veecle_telemetry::{CurrentSpan, Span, SpanContext, SpanId, ProcessId};
375    ///
376    /// let _guard = Span::new("my_span", &[]).entered();
377    ///
378    /// let external_context = SpanContext::new(ProcessId::from_raw(0x123), SpanId(0x456));
379    /// CurrentSpan::add_link(external_context);
380    /// ```
381    pub fn add_link(link: SpanContext) {
382        #[cfg(not(feature = "enable"))]
383        {
384            let _ = link;
385        }
386
387        #[cfg(feature = "enable")]
388        {
389            get_collector().span_link(None, link);
390        }
391    }
392
393    /// Sets an attribute on the current span.
394    ///
395    /// Attributes provide additional context about the work being performed
396    /// in the span.
397    ///
398    /// # Arguments
399    ///
400    /// * `attribute` - The key-value attribute to set
401    ///
402    /// # Examples
403    ///
404    /// ```rust
405    /// use veecle_telemetry::{CurrentSpan, span};
406    /// use veecle_telemetry::protocol::transient::KeyValue;
407    ///
408    /// let _guard = span!("operation").entered();
409    /// CurrentSpan::set_attribute(KeyValue::new("user_id", 123));
410    /// CurrentSpan::set_attribute(KeyValue::new("status", "success"));
411    /// ```
412    pub fn set_attribute<'a>(attribute: KeyValue<'a>) {
413        #[cfg(not(feature = "enable"))]
414        {
415            let _ = attribute;
416        }
417
418        #[cfg(feature = "enable")]
419        {
420            get_collector().span_attribute(None, attribute);
421        }
422    }
423}
424
425#[cfg(feature = "enable")]
426impl Span {
427    fn new_inner<'a>(name: &'a str, attributes: &'a [KeyValue<'a>]) -> Self {
428        let span_id = SpanId::next_id();
429
430        get_collector().new_span(span_id, name, attributes);
431
432        Self {
433            span_id: Some(span_id),
434        }
435    }
436
437    fn do_enter(&self) {
438        #[cfg(feature = "enable")]
439        if let Some(span_id) = self.span_id {
440            get_collector().enter_span(span_id);
441        }
442    }
443
444    fn do_exit(&self) {
445        #[cfg(feature = "enable")]
446        if let Some(span_id) = self.span_id {
447            get_collector().exit_span(span_id);
448        }
449    }
450}
451
452impl Drop for Span {
453    fn drop(&mut self) {
454        #[cfg(feature = "enable")]
455        if let Some(span_id) = self.span_id.take() {
456            get_collector().close_span(span_id);
457        }
458    }
459}
460
461/// Exits and drops the span when this is dropped.
462#[derive(Debug)]
463pub struct SpanGuard {
464    #[cfg(feature = "enable")]
465    pub(crate) inner: Option<SpanGuardInner>,
466
467    /// ```compile_fail
468    /// use veecle_telemetry::span::*;
469    /// trait AssertSend: Send {}
470    ///
471    /// impl AssertSend for SpanGuard {}
472    /// ```
473    _not_send: PhantomNotSend,
474}
475
476#[cfg(feature = "enable")]
477#[derive(Debug)]
478pub(crate) struct SpanGuardInner {
479    span: Span,
480}
481
482impl SpanGuard {
483    #[cfg(not(feature = "enable"))]
484    pub(crate) fn noop() -> Self {
485        Self {
486            #[cfg(feature = "enable")]
487            inner: None,
488            _not_send: PhantomNotSend,
489        }
490    }
491
492    #[cfg(feature = "enable")]
493    pub(crate) fn new(span: Span) -> Self {
494        Self {
495            #[cfg(feature = "enable")]
496            inner: Some(SpanGuardInner { span }),
497            _not_send: PhantomNotSend,
498        }
499    }
500}
501
502impl Drop for SpanGuard {
503    fn drop(&mut self) {
504        #[cfg(feature = "enable")]
505        if let Some(inner) = self.inner.take() {
506            inner.span.do_exit();
507        }
508    }
509}
510
511/// Exits the span when dropped.
512#[derive(Debug)]
513pub struct SpanGuardRef<'a> {
514    #[cfg(feature = "enable")]
515    pub(crate) inner: Option<SpanGuardRefInner<'a>>,
516
517    _phantom: PhantomData<&'a ()>,
518}
519
520#[cfg(feature = "enable")]
521#[derive(Debug)]
522pub(crate) struct SpanGuardRefInner<'a> {
523    span: &'a Span,
524}
525
526impl<'a> SpanGuardRef<'a> {
527    #[cfg(not(feature = "enable"))]
528    pub(crate) fn noop() -> Self {
529        Self {
530            #[cfg(feature = "enable")]
531            inner: None,
532            _phantom: PhantomData,
533        }
534    }
535
536    #[cfg(feature = "enable")]
537    pub(crate) fn new(span: &'a Span) -> Self {
538        Self {
539            #[cfg(feature = "enable")]
540            inner: Some(SpanGuardRefInner { span }),
541            _phantom: PhantomData,
542        }
543    }
544}
545
546impl Drop for SpanGuardRef<'_> {
547    fn drop(&mut self) {
548        #[cfg(feature = "enable")]
549        if let Some(inner) = self.inner.take() {
550            inner.span.do_exit();
551        }
552    }
553}
554
555/// Technically, `SpanGuard` _can_ implement both `Send` *and*
556/// `Sync` safely. It doesn't, because it has a `PhantomNotSend` field,
557/// specifically added in order to make it `!Send`.
558///
559/// Sending an `SpanGuard` guard between threads cannot cause memory unsafety.
560/// However, it *would* result in incorrect behavior, so we add a
561/// `PhantomNotSend` to prevent it from being sent between threads. This is
562/// because it must be *dropped* on the same thread that it was created;
563/// otherwise, the span will never be exited on the thread where it was entered,
564/// and it will attempt to exit the span on a thread that may never have entered
565/// it. However, we still want them to be `Sync` so that a struct holding an
566/// `Entered` guard can be `Sync`.
567///
568/// Thus, this is totally safe.
569#[derive(Debug)]
570struct PhantomNotSend {
571    ghost: PhantomData<*mut ()>,
572}
573
574#[allow(non_upper_case_globals)]
575const PhantomNotSend: PhantomNotSend = PhantomNotSend { ghost: PhantomData };
576
577/// # Safety:
578///
579/// Trivially safe, as `PhantomNotSend` doesn't have any API.
580unsafe impl Sync for PhantomNotSend {}
581
582#[cfg(all(test, feature = "std"))]
583mod tests {
584    use super::*;
585    use crate::protocol::transient::{KeyValue, ProcessId, SpanContext, SpanId};
586
587    #[test]
588    fn span_noop() {
589        let span = Span::noop();
590        assert!(span.span_id.is_none());
591    }
592
593    #[test]
594    fn span_context_from_span() {
595        let span = Span::new("test_span", &[]);
596
597        let extracted_context = span.context();
598        let context = extracted_context.unwrap();
599        assert_eq!(context.process_id, get_collector().process_id());
600    }
601
602    #[test]
603    fn span_context_from_noop_span() {
604        let span = Span::noop();
605        let extracted_context = span.context();
606        assert!(extracted_context.is_none());
607    }
608
609    #[test]
610    fn span_event() {
611        let span = Span::new("test_span", &[]);
612
613        let event_attributes = [KeyValue::new("event_key", "event_value")];
614
615        span.add_event("test_event", &event_attributes);
616
617        let noop_span = Span::noop();
618        noop_span.add_event("noop_event", &event_attributes);
619    }
620
621    #[test]
622    fn span_link() {
623        let span = Span::new("test_span", &[]);
624
625        let link_context = SpanContext::new(ProcessId::from_raw(0), SpanId(0));
626        span.add_link(link_context);
627
628        let noop_span = Span::noop();
629        noop_span.add_link(link_context);
630    }
631
632    #[test]
633    fn span_attribute() {
634        let span = Span::new("test_span", &[]);
635
636        let attribute = KeyValue::new("test_key", "test_value");
637        span.set_attribute(attribute.clone());
638
639        let noop_span = Span::noop();
640        noop_span.set_attribute(attribute);
641    }
642
643    #[test]
644    fn span_methods_with_entered_span() {
645        let span = Span::new("test_span", &[]);
646
647        let _guard = span.enter();
648
649        // All these should work while span is entered
650        span.add_event("entered_event", &[]);
651        span.add_link(SpanContext::new(ProcessId::from_raw(0), SpanId(0)));
652        span.set_attribute(KeyValue::new("entered_key", true));
653    }
654
655    #[test]
656    fn current_span_event_with_active_span() {
657        let _root_guard = Span::new("test_span", &[]).entered();
658
659        let event_attributes = [KeyValue::new("current_event_key", "current_event_value")];
660        CurrentSpan::add_event("current_test_event", &event_attributes);
661    }
662
663    #[test]
664    fn current_span_link_with_active_span() {
665        let _root_guard = Span::new("test_span", &[]).entered();
666
667        let link_context = SpanContext::new(ProcessId::from_raw(0), SpanId(0));
668        CurrentSpan::add_link(link_context);
669    }
670
671    #[test]
672    fn current_span_attribute_with_active_span() {
673        let span = Span::new("test_span", &[]);
674
675        let _guard = span.enter();
676        let attribute = KeyValue::new("current_attr_key", "current_attr_value");
677        CurrentSpan::set_attribute(attribute);
678    }
679}