vx_cli/
tracing_setup.rs

1// Tracing setup using community best practices
2// Uses tracing-indicatif for automatic progress bars on spans
3
4use std::sync::OnceLock;
5use tracing_indicatif::IndicatifLayer;
6use tracing_subscriber::layer::SubscriberExt;
7use tracing_subscriber::util::SubscriberInitExt;
8
9// Use dynamic typing for the layer to avoid generic complexity
10#[allow(dead_code)]
11static INDICATIF_LAYER: OnceLock<IndicatifLayer<tracing_subscriber::Registry>> = OnceLock::new();
12
13/// Setup tracing with default settings
14pub fn setup_tracing() {
15    init_tracing(false);
16}
17
18/// Initialize tracing with indicatif progress bars
19/// This follows Rust community best practices for structured logging
20pub fn init_tracing(verbose: bool) {
21    use std::sync::Once;
22    static INIT: Once = Once::new();
23
24    INIT.call_once(|| {
25        let indicatif_layer = IndicatifLayer::new();
26
27        // Note: We can't store the layer globally due to generic constraints
28        // This is fine as tracing-indicatif manages progress bars automatically
29
30        let env_filter = if verbose {
31            tracing_subscriber::EnvFilter::new("vx=debug,info")
32        } else {
33            tracing_subscriber::EnvFilter::new("vx=info,warn,error")
34        };
35
36        tracing_subscriber::registry()
37            .with(env_filter)
38            .with(
39                tracing_subscriber::fmt::layer()
40                    .with_writer(indicatif_layer.get_stderr_writer())
41                    .with_target(false)
42                    .with_level(verbose),
43            )
44            .with(indicatif_layer)
45            .try_init()
46            .ok(); // Ignore errors if already initialized
47    });
48}
49
50/// Get the global indicatif layer for manual progress bar operations
51/// Note: Due to generic constraints, we use a simpler approach
52pub fn get_indicatif_layer() -> Option<()> {
53    // For now, return None as we rely on automatic span-based progress bars
54    None
55}
56
57/// Macro for creating instrumented async functions with progress bars
58/// This replaces our custom progress decorators with standard tracing spans
59#[macro_export]
60macro_rules! progress_span {
61    ($name:expr, $($field:tt)*) => {
62        tracing::info_span!($name, $($field)*)
63    };
64}
65
66/// Macro for executing operations with automatic progress indication
67/// Uses tracing spans which automatically get progress bars via tracing-indicatif
68#[macro_export]
69macro_rules! with_progress_span {
70    ($name:expr, $operation:expr) => {{
71        let span = tracing::info_span!($name);
72        async move {
73            let _enter = span.enter();
74            $operation.await
75        }
76    }};
77}
78
79/// Enhanced macro that adds success/error events to spans
80#[macro_export]
81macro_rules! with_progress_events {
82    ($name:expr, $success_msg:expr, $error_msg:expr, $operation:expr) => {{
83        let span = tracing::info_span!($name);
84        async move {
85            let _enter = span.enter();
86            match $operation.await {
87                Ok(result) => {
88                    tracing::info!($success_msg);
89                    Ok(result)
90                }
91                Err(error) => {
92                    tracing::error!("{}: {}", $error_msg, error);
93                    Err(error)
94                }
95            }
96        }
97    }};
98}
99
100/// Utility for manual progress bar creation when spans aren't sufficient
101pub fn create_manual_progress_bar(len: u64, message: &str) -> indicatif::ProgressBar {
102    use indicatif::{ProgressBar, ProgressStyle};
103
104    let pb = ProgressBar::new(len);
105    pb.set_style(
106        ProgressStyle::with_template(
107            "{spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {pos}/{len} {msg}",
108        )
109        .unwrap()
110        .progress_chars("#>-"),
111    );
112    pb.set_message(message.to_string());
113
114    // Register with indicatif layer if available
115    if let Some(_layer) = get_indicatif_layer() {
116        // The layer will automatically manage this progress bar
117    }
118
119    pb
120}
121
122/// Suspend all progress bars temporarily (useful for user input)
123pub fn suspend_progress_bars<F, R>(f: F) -> R
124where
125    F: FnOnce() -> R,
126{
127    if let Some(_layer) = get_indicatif_layer() {
128        tracing_indicatif::suspend_tracing_indicatif(f)
129    } else {
130        f()
131    }
132}
133
134#[cfg(test)]
135mod tests {
136    use super::*;
137
138    #[tokio::test]
139    async fn test_progress_span_macro() {
140        init_tracing(true);
141
142        let result = with_progress_span!("test_operation", async {
143            // Use a shorter sleep to reduce test time and potential timing issues
144            tokio::time::sleep(std::time::Duration::from_millis(10)).await;
145            Ok::<_, anyhow::Error>(42)
146        })
147        .await;
148
149        assert!(result.is_ok());
150        assert_eq!(result.unwrap(), 42);
151    }
152
153    #[tokio::test]
154    async fn test_progress_events_macro() {
155        init_tracing(true);
156
157        let result = with_progress_events!(
158            "test_operation_with_events",
159            "Operation completed successfully",
160            "Operation failed",
161            async {
162                // Use a shorter sleep to reduce test time and potential timing issues
163                tokio::time::sleep(std::time::Duration::from_millis(5)).await;
164                Ok::<_, anyhow::Error>("success")
165            }
166        )
167        .await;
168
169        assert!(result.is_ok());
170        assert_eq!(result.unwrap(), "success");
171    }
172}