use std::panic::{self, PanicHookInfo};
use std::sync::Arc;
use crate::backtrace::capture_backtrace_skip;
use crate::client::BugwatchClient;
use crate::types::{ExceptionInfo, Level};
pub fn install_panic_hook(client: Arc<BugwatchClient>) {
let previous_hook = panic::take_hook();
let debug = client.is_debug();
let client_clone = client.clone();
panic::set_hook(Box::new(move |panic_info| {
capture_panic(&client_clone, panic_info);
previous_hook(panic_info);
}));
if debug {
tracing::info!("[Bugwatch] Panic hook installed");
}
}
pub fn install_panic_hook_with_abort(client: Arc<BugwatchClient>) {
let client_clone = client.clone();
panic::set_hook(Box::new(move |panic_info| {
capture_panic(&client_clone, panic_info);
eprintln!("{}", panic_info);
std::process::abort();
}));
}
fn capture_panic(client: &BugwatchClient, panic_info: &PanicHookInfo<'_>) {
let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let message = if let Some(s) = panic_info.payload().downcast_ref::<&str>() {
s.to_string()
} else if let Some(s) = panic_info.payload().downcast_ref::<String>() {
s.clone()
} else {
"Unknown panic".to_string()
};
let location = panic_info.location().map(|loc| {
format!("{}:{}:{}", loc.file(), loc.line(), loc.column())
});
let stacktrace = capture_backtrace_skip(5);
let exception = ExceptionInfo {
error_type: "panic".to_string(),
value: message,
stacktrace,
module: None,
};
let mut tags = std::collections::HashMap::new();
tags.insert("mechanism".to_string(), "panic_hook".to_string());
if let Some(loc) = location {
tags.insert("panic.location".to_string(), loc);
}
let _ = client.capture_exception_internal(exception, Level::Fatal, Some(tags), None);
let _ = client.flush();
}));
}
pub struct PanicGuard {
client: Arc<BugwatchClient>,
panicking: bool,
}
impl PanicGuard {
pub fn new(client: Arc<BugwatchClient>) -> Self {
Self {
client,
panicking: false,
}
}
pub fn enter(&mut self) {
self.panicking = std::thread::panicking();
}
}
impl Drop for PanicGuard {
fn drop(&mut self) {
if std::thread::panicking() && !self.panicking {
let client = self.client.clone();
let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(move || {
let exception = ExceptionInfo::new("panic", "Panic detected in guarded section");
let mut tags = std::collections::HashMap::new();
tags.insert("mechanism".to_string(), "panic_guard".to_string());
let _ = client.capture_exception_internal(
exception,
Level::Fatal,
Some(tags),
None,
);
let _ = client.flush();
}));
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::transport::NoopTransport;
use crate::types::BugwatchOptions;
#[test]
fn test_panic_guard_no_panic() {
let options = BugwatchOptions::new("test-key");
let client = Arc::new(BugwatchClient::with_transport(
options,
Box::new(NoopTransport),
));
{
let _guard = PanicGuard::new(client.clone());
}
}
}