use std::{
panic::PanicHookInfo,
sync::{Mutex, Once},
};
#[cfg(with_metrics)]
mod metrics {
use std::sync::LazyLock;
use prometheus::IntCounter;
use crate::prometheus_util::register_int_counter;
pub(super) static PANICS: LazyLock<IntCounter> =
LazyLock::new(|| register_int_counter("linera_panics_total", "Number of panics observed"));
}
type PanicHook = Box<dyn Fn(&PanicHookInfo<'_>) + Sync + Send>;
static PREVIOUS_HOOK: Mutex<Option<PanicHook>> = Mutex::new(None);
static INIT: Once = Once::new();
pub fn init() {
INIT.call_once(|| {
*PREVIOUS_HOOK.lock().expect("hook mutex is never poisoned") =
Some(std::panic::take_hook());
std::panic::set_hook(Box::new(report_panic));
});
}
fn payload_message<'a>(info: &'a PanicHookInfo<'_>) -> &'a str {
let payload = info.payload();
payload
.downcast_ref::<&str>()
.copied()
.or_else(|| payload.downcast_ref::<String>().map(String::as_str))
.unwrap_or("<non-string payload>")
}
fn report_panic(info: &PanicHookInfo<'_>) {
#[cfg(with_metrics)]
metrics::PANICS.inc();
let thread = std::thread::current();
tracing::error!(
thread = thread.name().unwrap_or("<unnamed>"),
location = info.location().map(tracing::field::display),
message = payload_message(info),
"Panic",
);
if let Ok(guard) = PREVIOUS_HOOK.lock() {
if let Some(previous) = guard.as_ref() {
previous(info);
}
}
}
#[cfg(test)]
mod tests {
use std::{
panic::AssertUnwindSafe,
sync::atomic::{AtomicUsize, Ordering},
};
use super::*;
#[test]
fn test_init_is_idempotent() {
static DELEGATIONS: AtomicUsize = AtomicUsize::new(0);
std::panic::set_hook(Box::new(|_| {
DELEGATIONS.fetch_add(1, Ordering::SeqCst);
}));
init();
init();
let panicked = std::panic::catch_unwind(AssertUnwindSafe(|| panic!("boom"))).is_err();
assert!(panicked);
assert_eq!(
DELEGATIONS.load(Ordering::SeqCst),
1,
"the hook installed before `init` ran exactly once",
);
}
}