Skip to main content

asupersync/
tracing_compat.rs

1//! Tracing compatibility layer for structured logging and spans.
2//!
3//! This module provides a unified interface for tracing that works whether or not
4//! the `tracing-integration` feature is enabled:
5//!
6//! - **With feature enabled**: Re-exports from the `tracing` crate for full functionality.
7//! - **Without feature**: No-op macros that compile to nothing for zero runtime overhead.
8//!
9//! # Usage
10//!
11//! ```rust,ignore
12//! use asupersync::tracing_compat::{info, debug, trace, span, Level};
13//!
14//! // These compile to no-ops when tracing-integration is disabled
15//! info!("Starting operation");
16//! debug!(task_id = ?id, "Task spawned");
17//!
18//! let _span = span!(Level::INFO, "my_operation");
19//! ```
20//!
21//! # Feature Flag
22//!
23//! Enable tracing by adding the feature to your `Cargo.toml`:
24//!
25//! ```toml
26//! asupersync = { version = "0.1", features = ["tracing-integration"] }
27//! ```
28
29#[cfg(feature = "tracing-integration")]
30pub use tracing::{
31    Instrument, Level, Span, debug, debug_span, error, error_span, event, info, info_span, span,
32    trace, trace_span, warn, warn_span,
33};
34
35#[cfg(feature = "proc-macros")]
36pub use asupersync_macros::instrument;
37
38// When tracing is disabled, provide no-op macros
39#[cfg(not(feature = "tracing-integration"))]
40mod noop {
41    //! No-op implementations when tracing is disabled.
42    //!
43    //! These macros expand to nothing, ensuring zero compile-time and runtime cost.
44
45    /// No-op trace-level logging macro.
46    #[macro_export]
47    macro_rules! trace {
48        ($($arg:tt)*) => {};
49    }
50
51    /// No-op debug-level logging macro.
52    #[macro_export]
53    macro_rules! debug {
54        ($($arg:tt)*) => {};
55    }
56
57    /// No-op info-level logging macro.
58    #[macro_export]
59    macro_rules! info {
60        ($($arg:tt)*) => {};
61    }
62
63    /// No-op warn-level logging macro.
64    #[macro_export]
65    macro_rules! warn {
66        ($($arg:tt)*) => {};
67    }
68
69    /// No-op error-level logging macro.
70    #[macro_export]
71    macro_rules! error {
72        ($($arg:tt)*) => {};
73    }
74
75    /// No-op event macro.
76    #[macro_export]
77    macro_rules! event {
78        ($($arg:tt)*) => {};
79    }
80
81    /// No-op span macro that returns a `NoopSpan`.
82    #[macro_export]
83    macro_rules! span {
84        ($($arg:tt)*) => {
85            $crate::tracing_compat::NoopSpan
86        };
87    }
88
89    /// No-op trace_span macro.
90    #[macro_export]
91    macro_rules! trace_span {
92        ($($arg:tt)*) => {
93            $crate::tracing_compat::NoopSpan
94        };
95    }
96
97    /// No-op debug_span macro.
98    #[macro_export]
99    macro_rules! debug_span {
100        ($($arg:tt)*) => {
101            $crate::tracing_compat::NoopSpan
102        };
103    }
104
105    /// No-op info_span macro.
106    #[macro_export]
107    macro_rules! info_span {
108        ($($arg:tt)*) => {
109            $crate::tracing_compat::NoopSpan
110        };
111    }
112
113    /// No-op warn_span macro.
114    #[macro_export]
115    macro_rules! warn_span {
116        ($($arg:tt)*) => {
117            $crate::tracing_compat::NoopSpan
118        };
119    }
120
121    /// No-op error_span macro.
122    #[macro_export]
123    macro_rules! error_span {
124        ($($arg:tt)*) => {
125            $crate::tracing_compat::NoopSpan
126        };
127    }
128
129    // Re-export the macros at module level
130    pub use crate::{
131        debug, debug_span, error, error_span, event, info, info_span, span, trace, trace_span,
132        warn, warn_span,
133    };
134}
135
136#[cfg(not(feature = "tracing-integration"))]
137pub use noop::*;
138
139/// A no-op span that does nothing.
140///
141/// When tracing is disabled, span macros return this type. It implements
142/// the necessary methods to allow code like `span.enter()` to compile
143/// without the tracing feature.
144#[cfg(not(feature = "tracing-integration"))]
145#[derive(Debug, Clone, Copy)]
146pub struct NoopSpan;
147
148#[cfg(not(feature = "tracing-integration"))]
149impl NoopSpan {
150    /// Returns a no-op guard that does nothing on drop.
151    #[inline]
152    #[must_use]
153    pub fn enter(&self) -> NoopGuard {
154        NoopGuard
155    }
156
157    /// Returns self (no-op).
158    #[inline]
159    #[must_use]
160    pub fn entered(self) -> Self {
161        self
162    }
163
164    /// Returns self (no-op).
165    #[inline]
166    #[must_use]
167    pub fn or_current(self) -> Self {
168        self
169    }
170
171    /// Returns self (no-op).
172    #[inline]
173    pub fn follows_from(&self, _span: &Self) {}
174
175    /// Returns true (always "enabled" to avoid branch differences).
176    #[inline]
177    #[must_use]
178    pub fn is_disabled(&self) -> bool {
179        true
180    }
181
182    /// Records a value (no-op).
183    #[inline]
184    pub fn record<V>(&self, _field: &str, _value: V) {}
185
186    /// Returns a no-op span (current span is always a no-op when disabled).
187    #[inline]
188    #[must_use]
189    pub fn current() -> Self {
190        Self
191    }
192
193    /// Returns a no-op span (none is always a no-op when disabled).
194    #[inline]
195    #[must_use]
196    pub fn none() -> Self {
197        Self
198    }
199}
200
201/// A no-op span guard that does nothing on drop.
202#[cfg(not(feature = "tracing-integration"))]
203#[derive(Debug)]
204pub struct NoopGuard;
205
206/// No-op level type for when tracing is disabled.
207#[cfg(not(feature = "tracing-integration"))]
208#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
209pub struct Level;
210
211#[cfg(not(feature = "tracing-integration"))]
212impl Level {
213    /// Trace level (most verbose).
214    pub const TRACE: Self = Self;
215    /// Debug level.
216    pub const DEBUG: Self = Self;
217    /// Info level.
218    pub const INFO: Self = Self;
219    /// Warn level.
220    pub const WARN: Self = Self;
221    /// Error level (least verbose).
222    pub const ERROR: Self = Self;
223}
224
225/// Alias for `NoopSpan` when tracing is disabled.
226#[cfg(not(feature = "tracing-integration"))]
227pub type Span = NoopSpan;
228
229/// No-op `Instrument` trait when tracing is disabled.
230///
231/// This trait is implemented for all `Future` types and does nothing,
232/// allowing code using `.instrument(span)` to compile without the feature.
233#[cfg(not(feature = "tracing-integration"))]
234pub trait Instrument: Sized {
235    /// Instruments this future with a span (no-op when disabled).
236    #[must_use]
237    fn instrument(self, _span: NoopSpan) -> Self {
238        self
239    }
240
241    /// Instruments this future with the current span (no-op when disabled).
242    #[must_use]
243    fn in_current_span(self) -> Self {
244        self
245    }
246}
247
248#[cfg(not(feature = "tracing-integration"))]
249impl<T> Instrument for T {}
250
251#[cfg(test)]
252mod tests {
253    #![allow(
254        clippy::pedantic,
255        clippy::nursery,
256        clippy::expect_fun_call,
257        clippy::map_unwrap_or,
258        clippy::cast_possible_wrap,
259        clippy::future_not_send
260    )]
261    use super::*;
262    use crate::test_utils::init_test_logging;
263    #[cfg(feature = "proc-macros")]
264    use futures_lite::future::block_on;
265
266    fn init_test(test_name: &str) {
267        init_test_logging();
268        crate::test_phase!(test_name);
269    }
270
271    #[cfg(feature = "proc-macros")]
272    #[super::instrument]
273    fn instrumented_sync_add(task_id: u32, label: &str) -> usize {
274        usize::try_from(task_id).expect("task_id fits usize") + label.len()
275    }
276
277    #[cfg(feature = "proc-macros")]
278    struct InstrumentedWorker;
279
280    #[cfg(feature = "proc-macros")]
281    impl InstrumentedWorker {
282        #[super::instrument(level = "debug", skip(self))]
283        fn tick(&self, value: usize) -> usize {
284            value + 1
285        }
286    }
287
288    #[cfg(feature = "proc-macros")]
289    #[super::instrument(name = "instrumented_async_len", level = "trace", skip(secret))]
290    async fn instrumented_async_len(secret: String, visible: usize) -> usize {
291        visible + secret.len()
292    }
293
294    #[test]
295    fn test_noop_macros_compile() {
296        init_test("test_noop_macros_compile");
297        // These should all compile and do nothing
298        trace!("trace message");
299        debug!("debug message");
300        info!("info message");
301        warn!("warn message");
302        error!("error message");
303
304        trace!(field = "value", "trace with field");
305        debug!(count = 42, "debug with field");
306        info!(name = "test", "info with field");
307        crate::test_complete!("test_noop_macros_compile");
308    }
309
310    #[test]
311    fn test_noop_span_compile() {
312        init_test("test_noop_span_compile");
313        let span = span!(Level::INFO, "test_span");
314        let _guard = span.enter();
315
316        let span2 = info_span!("info_span");
317        let _entered = span2.entered();
318
319        let span3 = debug_span!("debug_span", task_id = 42);
320        span3.record("field", "value");
321        crate::test_complete!("test_noop_span_compile");
322    }
323
324    #[test]
325    fn test_noop_level_constants() {
326        init_test("test_noop_level_constants");
327        // Verify that Level constants are accessible — tested further in
328        // `level_equality_and_ordering`.
329        crate::test_complete!("test_noop_level_constants");
330    }
331
332    #[test]
333    fn test_noop_span_methods() {
334        init_test("test_noop_span_methods");
335        #[cfg(not(feature = "tracing-integration"))]
336        {
337            let span = NoopSpan;
338            let disabled = span.is_disabled();
339            crate::assert_with_log!(disabled, "noop span should be disabled", true, disabled);
340
341            let current = NoopSpan::current();
342            let none = NoopSpan::none();
343            let _ = current.or_current();
344            none.follows_from(&span);
345        }
346        crate::test_complete!("test_noop_span_methods");
347    }
348
349    #[test]
350    fn noop_guard_debug() {
351        #[cfg(not(feature = "tracing-integration"))]
352        {
353            let guard = NoopGuard;
354            let dbg = format!("{guard:?}");
355            assert!(dbg.contains("NoopGuard"), "{dbg}");
356        }
357    }
358
359    #[test]
360    fn noop_span_debug_clone_copy() {
361        #[cfg(not(feature = "tracing-integration"))]
362        {
363            let span = NoopSpan;
364            let dbg = format!("{span:?}");
365            assert!(dbg.contains("NoopSpan"), "{dbg}");
366
367            let copied = span;
368            let cloned = span;
369            assert!(copied.is_disabled());
370            assert!(cloned.is_disabled());
371        }
372    }
373
374    #[test]
375    fn level_equality_and_ordering() {
376        #[cfg(not(feature = "tracing-integration"))]
377        {
378            // All noop levels are the same unit struct
379            assert_eq!(Level::TRACE, Level::DEBUG);
380            assert_eq!(Level::INFO, Level::WARN);
381            assert_eq!(Level::WARN, Level::ERROR);
382
383            // Ordering is consistent
384            assert!(Level::TRACE <= Level::ERROR);
385
386            // Debug
387            let dbg = format!("{:?}", Level::INFO);
388            assert!(dbg.contains("Level"), "{dbg}");
389
390            // Clone/Copy
391            let l = Level::INFO;
392            let copied = l;
393            let cloned = l;
394            assert_eq!(copied, cloned);
395        }
396    }
397
398    #[test]
399    fn span_type_alias_works() {
400        #[cfg(not(feature = "tracing-integration"))]
401        {
402            let span: Span = NoopSpan::current();
403            let _guard = span.enter();
404        }
405    }
406
407    #[test]
408    fn all_span_macros_compile() {
409        #[cfg(not(feature = "tracing-integration"))]
410        {
411            let _ = trace_span!("t");
412            let _ = debug_span!("d");
413            let _ = info_span!("i");
414            let _ = warn_span!("w");
415            let _ = error_span!("e");
416        }
417    }
418
419    #[test]
420    fn instrument_trait_noop() {
421        #[cfg(not(feature = "tracing-integration"))]
422        {
423            let fut = async { 42 };
424            let instrumented = fut.instrument(NoopSpan);
425            // Can also use in_current_span
426            drop(async { 1 }.in_current_span());
427            drop(instrumented);
428        }
429    }
430
431    #[cfg(feature = "proc-macros")]
432    #[test]
433    fn instrument_attribute_wraps_sync_functions() {
434        init_test("instrument_attribute_wraps_sync_functions");
435        let value = instrumented_sync_add(7, "abc");
436        crate::assert_with_log!(value == 10, "sync result", 10usize, value);
437        crate::test_complete!("instrument_attribute_wraps_sync_functions");
438    }
439
440    #[cfg(feature = "proc-macros")]
441    #[test]
442    fn instrument_attribute_wraps_methods() {
443        init_test("instrument_attribute_wraps_methods");
444        let worker = InstrumentedWorker;
445        let value = worker.tick(9);
446        crate::assert_with_log!(value == 10, "method result", 10usize, value);
447        crate::test_complete!("instrument_attribute_wraps_methods");
448    }
449
450    #[cfg(feature = "proc-macros")]
451    #[test]
452    fn instrument_attribute_wraps_async_functions() {
453        init_test("instrument_attribute_wraps_async_functions");
454        let value = block_on(instrumented_async_len("secret".to_string(), 4));
455        crate::assert_with_log!(value == 10, "async result", 10usize, value);
456        crate::test_complete!("instrument_attribute_wraps_async_functions");
457    }
458}