use std::backtrace::Backtrace;
use std::panic::PanicHookInfo;
use crate::report::{EventKind, Frame};
pub(crate) fn frames_from_backtrace(bt: &Backtrace) -> Vec<Frame> {
bt.to_string()
.lines()
.map(str::trim)
.filter(|l| !l.is_empty())
.map(|line| Frame {
address: None,
symbol: Some(line.to_owned()),
location: None,
})
.collect()
}
thread_local! {
static CAPTURING: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
}
pub(crate) fn install_hook() {
let previous = std::panic::take_hook();
std::panic::set_hook(Box::new(move |info: &PanicHookInfo<'_>| {
let reentered = CAPTURING.with(|c| c.replace(true));
if !reentered {
let message = panic_message(info);
let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let bt = Backtrace::force_capture();
crate::Capture::new(EventKind::Panic, message)
.backtrace_frames(frames_from_backtrace(&bt))
.emit()
}));
CAPTURING.with(|c| c.set(false));
}
previous(info);
}));
}
fn panic_message(info: &PanicHookInfo<'_>) -> String {
let payload = info.payload();
let body = payload
.downcast_ref::<&str>()
.map(|s| (*s).to_owned())
.or_else(|| payload.downcast_ref::<String>().cloned())
.unwrap_or_else(|| "panic".to_owned());
match info.location() {
Some(loc) => format!("{body} (at {}:{}:{})", loc.file(), loc.line(), loc.column()),
None => body,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn frames_from_backtrace_yields_symbol_frames() {
let bt = Backtrace::force_capture();
let frames = frames_from_backtrace(&bt);
for f in &frames {
assert!(f.symbol.is_some());
}
}
}