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}