use std::cell::RefCell;
use std::sync::atomic::{AtomicUsize, Ordering};
static NEXT_TRACE_ID: AtomicUsize = AtomicUsize::new(0);
#[derive(Clone, Debug)]
pub struct TraceInfo {
pub id: usize,
pub file: &'static str,
pub line: u32,
pub message: String,
}
thread_local! {
static TRACE_STACK: RefCell<Vec<TraceInfo>> = const { RefCell::new(Vec::new()) };
}
#[doc(hidden)]
pub struct ScopedTraceGuard {
id: usize,
_phantom: std::marker::PhantomData<*mut ()>,
}
impl ScopedTraceGuard {
#[doc(hidden)]
#[track_caller]
pub fn new(message: String) -> Self {
let caller = std::panic::Location::caller();
let id = NEXT_TRACE_ID.fetch_add(1, Ordering::Relaxed);
TRACE_STACK.with(|stack| {
if let Ok(mut s) = stack.try_borrow_mut() {
s.push(TraceInfo { id, file: caller.file(), line: caller.line(), message });
}
});
Self { id, _phantom: std::marker::PhantomData }
}
}
impl Drop for ScopedTraceGuard {
fn drop(&mut self) {
TRACE_STACK.with(|stack| {
if let Ok(mut s) = stack.try_borrow_mut() {
if let Some(pos) = s.iter().rposition(|t| t.id == self.id) {
s.remove(pos);
}
}
});
}
}
pub fn get_scoped_traces() -> Vec<TraceInfo> {
TRACE_STACK.with(|stack| stack.try_borrow().map(|s| s.clone()).unwrap_or_default())
}
#[cfg(test)]
pub(crate) mod test_helpers {
use super::*;
use std::cell::Cell;
thread_local! {
pub static CAPTURED_TRACES_IN_HOOK: RefCell<Vec<TraceInfo>> = const { RefCell::new(Vec::new()) };
pub static USE_CAPTURE_HOOK: Cell<bool> = const { Cell::new(false) };
}
pub fn enable_capture_in_hook(enable: bool) {
USE_CAPTURE_HOOK.with(|v| v.set(enable));
}
pub fn get_captured_traces_in_hook() -> Vec<TraceInfo> {
CAPTURED_TRACES_IN_HOOK.with(|v| v.borrow().clone())
}
pub fn clear_captured_traces_in_hook() {
CAPTURED_TRACES_IN_HOOK.with(|v| v.borrow_mut().clear());
}
}
#[cfg(test)]
mod tests {
use super::test_helpers::*;
use super::*;
#[test]
fn test_scoped_trace_fatal() {
enable_capture_in_hook(true);
clear_captured_traces_in_hook();
crate::internal::test_outcome::TestOutcome::init_current_test_outcome();
let _ = std::panic::catch_unwind(|| {
let _guard = ScopedTraceGuard::new("Second trace".to_string());
panic!("Intentional panic");
});
let captured = get_captured_traces_in_hook();
assert_eq!(captured.len(), 1);
assert_eq!(captured[0].message, "Second trace");
enable_capture_in_hook(false);
}
}