1#[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#[cfg(feature = "profiling_advanced")]
47#[derive(Debug, Clone)]
48pub struct TracingConfig {
49 pub format: TracingFormat,
51 pub level: Level,
53 pub ansi_colors: bool,
55 pub log_to_file: bool,
57 pub log_file_path: Option<PathBuf>,
59 pub log_rotation: LogRotation,
61 pub enable_flame: bool,
63 pub enable_chrome: bool,
65 pub env_filter: Option<String>,
67}
68
69#[cfg(feature = "profiling_advanced")]
71#[derive(Debug, Clone, Copy, PartialEq, Eq)]
72pub enum TracingFormat {
73 Compact,
75 Pretty,
77 Json,
79}
80
81#[cfg(feature = "profiling_advanced")]
83#[derive(Debug, Clone, Copy, PartialEq, Eq)]
84pub enum LogRotation {
85 Hourly,
87 Daily,
89 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 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 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 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 pub fn with_format(mut self, format: TracingFormat) -> Self {
159 self.format = format;
160 self
161 }
162
163 pub fn with_level(mut self, level: Level) -> Self {
165 self.level = level;
166 self
167 }
168
169 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 pub fn with_flame_graph(mut self, enable: bool) -> Self {
179 self.enable_flame = enable;
180 self
181 }
182
183 pub fn with_env_filter(mut self, filter: String) -> Self {
185 self.env_filter = Some(filter);
186 self
187 }
188}
189
190#[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 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 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_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_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#[cfg(feature = "profiling_advanced")]
288pub struct SpanGuard {
289 _span: tracing::span::EnteredSpan,
290}
291
292#[cfg(feature = "profiling_advanced")]
293impl SpanGuard {
294 pub fn new(name: &str) -> Self {
296 Self {
297 _span: tracing::info_span!(target: "scirs2::profiling", "{}", name).entered(),
298 }
299 }
300
301 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#[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 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 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#[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 }
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 }
438}