use std::panic::{self, PanicHookInfo};
pub fn install_hook() -> bool {
use std::sync::Once;
static INSTALL_HOOK_ONCE: Once = Once::new();
let mut first_install = false;
INSTALL_HOOK_ONCE.call_once(|| {
let previous = panic::take_hook();
panic::set_hook(Box::new(move |panic_info| {
super::metrics::panics::total().inc();
log_panic(panic_info);
previous(panic_info);
}));
first_install = true;
});
first_install
}
fn log_panic(panic_info: &PanicHookInfo<'_>) {
let location = panic_info.location();
let payload = panic_payload_as_str(panic_info);
#[cfg(feature = "logging")]
if crate::telemetry::is_initialized() {
crate::telemetry::log::error!(
"panic occurred";
"payload" => payload,
"location" => ?location,
);
return;
}
let location_str = location
.map(|l| format!("{}:{}:{}", l.file(), l.line(), l.column()))
.unwrap_or_else(|| "<unknown>".to_string());
let json_output = serde_json::json!({
"level": "error",
"msg": "panic occurred",
"payload": payload,
"location": location_str
});
eprintln!("{}", json_output);
}
fn panic_payload_as_str<'a>(panic_info: &'a PanicHookInfo<'_>) -> &'a str {
let payload = panic_info.payload();
if let Some(s) = payload.downcast_ref::<&str>() {
s
} else if let Some(s) = payload.downcast_ref::<String>() {
s.as_str()
} else {
"<non-string panic payload>"
}
}