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}