#[cfg(feature = "lab-stack-traces")]
use backtrace::Backtrace;
#[cfg(feature = "lab-stack-traces")]
use rustc_demangle::demangle;
#[derive(Debug, Clone)]
pub struct StackTraceConfig {
pub max_frames: usize,
pub include_file_line: bool,
pub skip_frames: usize,
}
impl Default for StackTraceConfig {
fn default() -> Self {
Self {
max_frames: 50, include_file_line: cfg!(debug_assertions),
skip_frames: 2, }
}
}
pub fn capture_stack_trace(config: &StackTraceConfig) -> String {
#[cfg(feature = "lab-stack-traces")]
{
let bt = Backtrace::new();
format_backtrace(&bt, config)
}
#[cfg(not(feature = "lab-stack-traces"))]
{
let _ = config; "<stack traces disabled: enable 'lab-stack-traces' feature>".to_string()
}
}
pub fn capture_stack_trace_default() -> String {
capture_stack_trace(&StackTraceConfig::default())
}
#[cfg(feature = "lab-stack-traces")]
fn format_backtrace(bt: &Backtrace, config: &StackTraceConfig) -> String {
use std::fmt::Write;
let mut output = String::new();
let frames: Vec<_> = bt.frames().iter().collect();
let start = config.skip_frames.min(frames.len());
let end = if config.max_frames == 0 {
frames.len()
} else {
start.saturating_add(config.max_frames).min(frames.len())
};
if start >= frames.len() {
return " <all frames skipped>".to_string();
}
for (i, frame) in frames[start..end].iter().enumerate() {
let frame_num = i + 1;
let mut frame_info = format!(" {:2}: ", frame_num);
let symbols = frame.symbols();
if symbols.is_empty() {
writeln!(frame_info, "<unknown>").unwrap();
} else {
let symbol = &symbols[0];
if let Some(name) = symbol.name() {
let name_str = format!("{}", name);
let demangled = demangle(&name_str);
write!(frame_info, "{}", demangled).unwrap();
} else {
write!(frame_info, "<unknown>").unwrap();
}
if config.include_file_line {
if let (Some(filename), Some(line)) = (symbol.filename(), symbol.lineno()) {
if let Some(filename_str) = filename.to_str() {
write!(frame_info, "\n at {}:{}", filename_str, line).unwrap();
}
}
}
writeln!(frame_info).unwrap();
}
output.push_str(&frame_info);
}
if config.max_frames > 0 && end < frames.len() {
let remaining = frames.len() - end;
writeln!(output, " ... ({} more frames)", remaining).unwrap();
}
output
}
pub fn capture_stack_trace_depth(max_frames: usize) -> String {
let config = StackTraceConfig {
max_frames,
..Default::default()
};
capture_stack_trace(&config)
}
pub fn capture_stack_trace_minimal() -> String {
let config = StackTraceConfig {
max_frames: 10,
include_file_line: false,
skip_frames: 2,
};
capture_stack_trace(&config)
}
#[cfg(all(test, feature = "lab-stack-traces"))]
mod tests {
use super::*;
#[test]
fn test_stack_trace_capture() {
let trace = capture_stack_trace_default();
assert!(trace.contains("1: "));
assert!(!trace.is_empty());
assert!(trace.contains('\n'));
}
#[test]
fn test_stack_trace_config() {
let config = StackTraceConfig {
max_frames: 5,
include_file_line: true,
skip_frames: 1,
};
let trace = capture_stack_trace(&config);
let frame_count = trace.matches(": ").count();
assert!(
frame_count <= 5,
"Frame count {} exceeds max 5",
frame_count
);
}
#[test]
fn test_minimal_trace() {
let trace = capture_stack_trace_minimal();
let frame_count = trace.matches(": ").count();
assert!(frame_count <= 10);
assert!(!trace.is_empty());
}
#[test]
fn test_depth_limit() {
let trace = capture_stack_trace_depth(3);
let frame_count = trace.matches(": ").count();
assert!(frame_count <= 3);
}
#[test]
fn test_large_depth_does_not_overflow() {
let trace = capture_stack_trace_depth(usize::MAX);
assert!(!trace.is_empty());
}
#[test]
fn test_skip_frames() {
let config = StackTraceConfig {
max_frames: 0, include_file_line: false,
skip_frames: 5, };
let trace = capture_stack_trace(&config);
if !trace.contains("<all frames skipped>") {
assert!(trace.contains("1: ")); }
}
#[test]
fn test_feature_disabled_fallback() {
let config = StackTraceConfig::default();
let _trace = capture_stack_trace(&config);
assert!(true);
}
}
#[cfg(all(test, not(feature = "lab-stack-traces")))]
mod tests_feature_disabled {
use super::*;
#[test]
fn test_disabled_feature_returns_disabled_feature_diagnostic() {
let trace = capture_stack_trace_default();
assert_eq!(
trace,
"<stack traces disabled: enable 'lab-stack-traces' feature>"
);
}
}