Skip to main content

scirs2_core/profiling/
tracing_framework.rs

1//! # Tracing Framework for SciRS2 v0.2.0
2//!
3//! This module provides structured logging and tracing capabilities using the `tracing` crate.
4//! It enables zero-overhead instrumentation when disabled and comprehensive observability when enabled.
5//!
6//! # Features
7//!
8//! - **Structured Logging**: Rich, structured event logging with context
9//! - **Span Management**: Hierarchical span tracking for nested operations
10//! - **Multiple Outputs**: Console, file, JSON, and custom exporters
11//! - **Performance Zones**: Named zones for performance tracking
12//! - **Zero Overhead**: Compile-time elimination when feature is disabled
13//!
14//! # Example
15//!
16//! ```rust,no_run
17//! use scirs2_core::profiling::tracing_framework::{TracingConfig, init_tracing};
18//! use tracing::{info, span, Level};
19//!
20//! // Initialize tracing with default configuration
21//! let _guard = init_tracing(TracingConfig::default()).expect("Failed to initialize tracing");
22//!
23//! // Create a span for a computation
24//! let span = span!(Level::INFO, "matrix_multiply", size = 1000);
25//! let _enter = span.enter();
26//!
27//! info!("Starting matrix multiplication");
28//! // ... perform computation ...
29//! info!(result = "success", "Matrix multiplication completed");
30//! ```
31
32#[cfg(feature = "profiling_advanced")]
33use crate::CoreResult;
34#[cfg(feature = "profiling_advanced")]
35use std::path::PathBuf;
36#[cfg(feature = "profiling_advanced")]
37use tracing::Level;
38#[cfg(feature = "profiling_advanced")]
39use tracing_appender::non_blocking::WorkerGuard;
40#[cfg(feature = "profiling_advanced")]
41use tracing_subscriber::{
42    fmt, layer::SubscriberExt, registry::LookupSpan, util::SubscriberInitExt, EnvFilter, Layer,
43};
44
45/// Configuration for tracing framework
46#[cfg(feature = "profiling_advanced")]
47#[derive(Debug, Clone)]
48pub struct TracingConfig {
49    /// Output format: "compact", "pretty", "json"
50    pub format: TracingFormat,
51    /// Log level filter
52    pub level: Level,
53    /// Enable ANSI color codes
54    pub ansi_colors: bool,
55    /// Log to file
56    pub log_to_file: bool,
57    /// Log file path
58    pub log_file_path: Option<PathBuf>,
59    /// Log file rotation: "hourly", "daily", "never"
60    pub log_rotation: LogRotation,
61    /// Enable flame graph generation
62    pub enable_flame: bool,
63    /// Enable Chrome DevTools format
64    pub enable_chrome: bool,
65    /// Custom environment filter
66    pub env_filter: Option<String>,
67}
68
69/// Output format for tracing
70#[cfg(feature = "profiling_advanced")]
71#[derive(Debug, Clone, Copy, PartialEq, Eq)]
72pub enum TracingFormat {
73    /// Compact single-line format
74    Compact,
75    /// Pretty multi-line format with colors
76    Pretty,
77    /// JSON format for structured logging
78    Json,
79}
80
81/// Log rotation strategy
82#[cfg(feature = "profiling_advanced")]
83#[derive(Debug, Clone, Copy, PartialEq, Eq)]
84pub enum LogRotation {
85    /// Rotate logs hourly
86    Hourly,
87    /// Rotate logs daily
88    Daily,
89    /// No rotation
90    Never,
91}
92
93#[cfg(feature = "profiling_advanced")]
94impl Default for TracingConfig {
95    fn default() -> Self {
96        Self {
97            format: TracingFormat::Pretty,
98            level: Level::INFO,
99            ansi_colors: true,
100            log_to_file: false,
101            log_file_path: None,
102            log_rotation: LogRotation::Daily,
103            enable_flame: false,
104            enable_chrome: false,
105            env_filter: None,
106        }
107    }
108}
109
110#[cfg(feature = "profiling_advanced")]
111impl TracingConfig {
112    /// Create a production configuration (minimal overhead, JSON format)
113    pub fn production() -> Self {
114        Self {
115            format: TracingFormat::Json,
116            level: Level::WARN,
117            ansi_colors: false,
118            log_to_file: true,
119            log_file_path: Some(PathBuf::from("/var/log/scirs2")),
120            log_rotation: LogRotation::Daily,
121            enable_flame: false,
122            enable_chrome: false,
123            env_filter: Some("scirs2=warn,scirs2_core=warn".to_string()),
124        }
125    }
126
127    /// Create a development configuration (verbose, pretty format)
128    pub fn development() -> Self {
129        Self {
130            format: TracingFormat::Pretty,
131            level: Level::DEBUG,
132            ansi_colors: true,
133            log_to_file: false,
134            log_file_path: None,
135            log_rotation: LogRotation::Never,
136            enable_flame: true,
137            enable_chrome: false,
138            env_filter: Some("scirs2=debug".to_string()),
139        }
140    }
141
142    /// Create a configuration for benchmarking (flamegraph enabled)
143    pub fn benchmark() -> Self {
144        Self {
145            format: TracingFormat::Compact,
146            level: Level::INFO,
147            ansi_colors: false,
148            log_to_file: false,
149            log_file_path: None,
150            log_rotation: LogRotation::Never,
151            enable_flame: true,
152            enable_chrome: true,
153            env_filter: Some("scirs2=info".to_string()),
154        }
155    }
156
157    /// Set output format
158    pub fn with_format(mut self, format: TracingFormat) -> Self {
159        self.format = format;
160        self
161    }
162
163    /// Set log level
164    pub fn with_level(mut self, level: Level) -> Self {
165        self.level = level;
166        self
167    }
168
169    /// Enable file logging
170    pub fn with_file_logging(mut self, path: PathBuf, rotation: LogRotation) -> Self {
171        self.log_to_file = true;
172        self.log_file_path = Some(path);
173        self.log_rotation = rotation;
174        self
175    }
176
177    /// Enable flame graph generation
178    pub fn with_flame_graph(mut self, enable: bool) -> Self {
179        self.enable_flame = enable;
180        self
181    }
182
183    /// Set environment filter
184    pub fn with_env_filter(mut self, filter: String) -> Self {
185        self.env_filter = Some(filter);
186        self
187    }
188}
189
190/// Initialize the tracing framework
191///
192/// Returns a `WorkerGuard` that must be kept alive for the duration of the program.
193/// When dropped, it will flush any remaining logs.
194#[cfg(feature = "profiling_advanced")]
195pub fn init_tracing(config: TracingConfig) -> CoreResult<Option<WorkerGuard>> {
196    let env_filter = if let Some(ref filter) = config.env_filter {
197        EnvFilter::try_new(filter).map_err(|e| {
198            crate::CoreError::ConfigError(crate::error::ErrorContext::new(format!(
199                "Invalid env filter: {}",
200                e
201            )))
202        })?
203    } else {
204        EnvFilter::try_from_default_env()
205            .unwrap_or_else(|_| EnvFilter::new(config.level.to_string()))
206    };
207
208    let registry = tracing_subscriber::registry().with(env_filter);
209
210    // Configure file appender if enabled
211    let guard = if config.log_to_file {
212        let log_path = config.log_file_path.clone().ok_or_else(|| {
213            crate::CoreError::ConfigError(crate::error::ErrorContext::new(
214                "Log file path not specified".to_string(),
215            ))
216        })?;
217
218        let file_appender = match config.log_rotation {
219            LogRotation::Hourly => tracing_appender::rolling::hourly(&log_path, "scirs2.log"),
220            LogRotation::Daily => tracing_appender::rolling::daily(&log_path, "scirs2.log"),
221            LogRotation::Never => tracing_appender::rolling::never(&log_path, "scirs2.log"),
222        };
223
224        let (non_blocking, guard) = tracing_appender::non_blocking(file_appender);
225
226        let file_layer = fmt::layer()
227            .with_writer(non_blocking)
228            .with_ansi(false)
229            .json();
230
231        registry.with(file_layer).init();
232
233        Some(guard)
234    } else {
235        // Console output
236        match config.format {
237            TracingFormat::Compact => {
238                registry
239                    .with(fmt::layer().compact().with_ansi(config.ansi_colors))
240                    .init();
241            }
242            TracingFormat::Pretty => {
243                registry
244                    .with(fmt::layer().pretty().with_ansi(config.ansi_colors))
245                    .init();
246            }
247            TracingFormat::Json => {
248                registry.with(fmt::layer().json().with_ansi(false)).init();
249            }
250        }
251
252        None
253    };
254
255    Ok(guard)
256}
257
258/// Macro for creating a traced function
259///
260/// This macro automatically creates a span for the function and enters it.
261#[macro_export]
262#[cfg(feature = "profiling_advanced")]
263macro_rules! traced_function {
264    ($name:expr) => {
265        let _span = tracing::info_span!($name).entered();
266    };
267    ($name:expr, $($field:tt)*) => {
268        let _span = tracing::info_span!($name, $($field)*).entered();
269    };
270}
271
272/// Macro for creating a performance zone
273///
274/// This creates a span that can be used for performance analysis.
275#[macro_export]
276#[cfg(feature = "profiling_advanced")]
277macro_rules! perf_zone {
278    ($name:expr) => {
279        let _zone = tracing::trace_span!("perf", zone = $name).entered();
280    };
281    ($name:expr, $($field:tt)*) => {
282        let _zone = tracing::trace_span!("perf", zone = $name, $($field)*).entered();
283    };
284}
285
286/// Span guard for automatic span management
287#[cfg(feature = "profiling_advanced")]
288pub struct SpanGuard {
289    _span: tracing::span::EnteredSpan,
290}
291
292#[cfg(feature = "profiling_advanced")]
293impl SpanGuard {
294    /// Create a new span guard
295    pub fn new(name: &str) -> Self {
296        Self {
297            _span: tracing::info_span!(target: "scirs2::profiling", "{}", name).entered(),
298        }
299    }
300
301    /// Create a span guard with fields
302    pub fn with_fields(name: &str, fields: &[(&str, &dyn std::fmt::Display)]) -> Self {
303        let span = tracing::info_span!(target: "scirs2::profiling", "{}", name);
304        for (key, value) in fields {
305            span.record(*key, &tracing::field::display(value));
306        }
307        Self {
308            _span: span.entered(),
309        }
310    }
311}
312
313/// Performance zone marker for critical sections
314#[cfg(feature = "profiling_advanced")]
315pub struct PerfZone {
316    _span: tracing::span::EnteredSpan,
317    name: String,
318    start: std::time::Instant,
319}
320
321#[cfg(feature = "profiling_advanced")]
322impl PerfZone {
323    /// Start a new performance zone
324    pub fn start(name: &str) -> Self {
325        let span = tracing::trace_span!(
326            target: "scirs2::perf",
327            "perf_zone",
328            zone = name
329        )
330        .entered();
331
332        Self {
333            _span: span,
334            name: name.to_string(),
335            start: std::time::Instant::now(),
336        }
337    }
338
339    /// End the performance zone and log duration
340    pub fn end(self) {
341        let duration = self.start.elapsed();
342        tracing::info!(
343            target: "scirs2::perf",
344            zone = %self.name,
345            duration_us = duration.as_micros(),
346            "Performance zone completed"
347        );
348    }
349}
350
351#[cfg(feature = "profiling_advanced")]
352impl Drop for PerfZone {
353    fn drop(&mut self) {
354        let duration = self.start.elapsed();
355        tracing::debug!(
356            target: "scirs2::perf",
357            zone = %self.name,
358            duration_us = duration.as_micros(),
359            "Performance zone ended"
360        );
361    }
362}
363
364/// Stub implementations when profiling_advanced feature is disabled
365#[cfg(not(feature = "profiling_advanced"))]
366use crate::CoreResult;
367
368#[cfg(not(feature = "profiling_advanced"))]
369pub struct TracingConfig;
370
371#[cfg(not(feature = "profiling_advanced"))]
372impl TracingConfig {
373    pub fn default() -> Self {
374        Self
375    }
376    pub fn production() -> Self {
377        Self
378    }
379    pub fn development() -> Self {
380        Self
381    }
382    pub fn benchmark() -> Self {
383        Self
384    }
385}
386
387#[cfg(not(feature = "profiling_advanced"))]
388pub fn init_tracing(_config: TracingConfig) -> CoreResult<Option<()>> {
389    Ok(None)
390}
391
392#[cfg(test)]
393#[cfg(feature = "profiling_advanced")]
394mod tests {
395    use super::*;
396
397    #[test]
398    fn test_tracing_config_defaults() {
399        let config = TracingConfig::default();
400        assert_eq!(config.format, TracingFormat::Pretty);
401        assert_eq!(config.level, Level::INFO);
402        assert!(config.ansi_colors);
403        assert!(!config.log_to_file);
404    }
405
406    #[test]
407    fn test_production_config() {
408        let config = TracingConfig::production();
409        assert_eq!(config.format, TracingFormat::Json);
410        assert_eq!(config.level, Level::WARN);
411        assert!(!config.ansi_colors);
412        assert!(config.log_to_file);
413    }
414
415    #[test]
416    fn test_development_config() {
417        let config = TracingConfig::development();
418        assert_eq!(config.format, TracingFormat::Pretty);
419        assert_eq!(config.level, Level::DEBUG);
420        assert!(config.ansi_colors);
421        assert!(!config.log_to_file);
422        assert!(config.enable_flame);
423    }
424
425    #[test]
426    fn test_span_guard_creation() {
427        let _guard = SpanGuard::new("test_span");
428        // Should not panic
429    }
430
431    #[test]
432    fn test_perf_zone() {
433        let zone = PerfZone::start("test_zone");
434        std::thread::sleep(std::time::Duration::from_millis(10));
435        zone.end();
436        // Should not panic
437    }
438}