use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Mutex;
use tracing_chrome::FlushGuard;
use tracing_subscriber::prelude::*;
static GUARD: Mutex<Option<FlushGuard>> = Mutex::new(None);
static SESSION_USED: AtomicBool = AtomicBool::new(false);
#[derive(Debug)]
pub enum TracingError {
AlreadyActive,
SessionExhausted,
SubscriberInstallFailed(String),
}
impl std::fmt::Display for TracingError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::AlreadyActive => write!(f, "trace capture already active"),
Self::SessionExhausted => write!(
f,
"trace session already used (only one session per process lifetime)"
),
Self::SubscriberInstallFailed(e) => {
write!(f, "failed to install tracing subscriber: {e}")
}
}
}
}
impl std::error::Error for TracingError {}
pub fn start_tracing(path: &str) -> Result<(), TracingError> {
let mut lock = GUARD.lock().unwrap_or_else(|e| e.into_inner());
if lock.is_some() {
return Err(TracingError::AlreadyActive);
}
if SESSION_USED.load(Ordering::Relaxed) {
return Err(TracingError::SessionExhausted);
}
let (chrome_layer, guard) = tracing_chrome::ChromeLayerBuilder::new()
.file(path)
.include_args(true)
.build();
let subscriber = tracing_subscriber::registry().with(chrome_layer);
tracing::subscriber::set_global_default(subscriber)
.map_err(|e| TracingError::SubscriberInstallFailed(e.to_string()))?;
SESSION_USED.store(true, Ordering::Relaxed);
*lock = Some(guard);
Ok(())
}
pub fn stop_tracing() {
let mut lock = GUARD.lock().unwrap_or_else(|e| e.into_inner());
lock.take();
}
pub fn is_tracing_active() -> bool {
GUARD.lock().unwrap_or_else(|e| e.into_inner()).is_some()
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::Path;
#[test]
fn test_trace_lifecycle() {
let dir = std::env::temp_dir();
let path = dir.join("hal_test_trace_lifecycle.json");
let path_str = path.to_str().unwrap();
let _ = std::fs::remove_file(&path);
assert!(!is_tracing_active());
start_tracing(path_str).expect("start_tracing should succeed");
assert!(is_tracing_active());
let err = start_tracing(path_str).unwrap_err();
assert!(
matches!(err, TracingError::AlreadyActive),
"expected AlreadyActive, got: {err:?}"
);
{
let _span = tracing::trace_span!("test_span", key = "value").entered();
}
stop_tracing();
assert!(!is_tracing_active());
assert!(Path::new(path_str).exists());
let content = std::fs::read_to_string(&path).unwrap();
assert!(!content.is_empty(), "trace file should not be empty");
let err = start_tracing(path_str).unwrap_err();
assert!(
matches!(err, TracingError::SessionExhausted),
"expected SessionExhausted, got: {err:?}"
);
let _ = std::fs::remove_file(&path);
}
}