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