use core::panic::Location;
use std::panic::PanicHookInfo;
use std::sync::OnceLock;
use std::backtrace::{Backtrace, BacktraceStatus};
pub trait PanicRecorder {
fn capture(location: &Location, message: &str, backtrace: Backtrace);
}
pub struct Exception;
impl PanicRecorder for Exception {
#[track_caller]
fn capture(location: &Location, message: &str, backtrace: Backtrace) {
if let BacktraceStatus::Captured = backtrace.status() {
tracing::error!(
exception.location = %location,
exception.stacktrace = %backtrace,
exception.message = message,
exception.type = "Rust Panic",
"exception",
);
} else {
tracing::error!(
exception.location = %location,
exception.message = message,
exception.type = "Rust Panic",
"exception",
);
}
}
}
pub struct Error;
impl PanicRecorder for Error {
#[track_caller]
fn capture(location: &Location, message: &str, backtrace: Backtrace) {
if let BacktraceStatus::Captured = backtrace.status() {
tracing::error!(
error.location = %location,
error.stack = %backtrace,
error.message = message,
error.type = "Rust Panic",
"exception",
);
} else {
tracing::error!(
error.location = %location,
error.message = message,
error.type = "Rust Panic",
"exception",
);
}
}
}
fn propagate_status(message: &str) {
use opentelemetry::trace::Status;
use tracing_opentelemetry::OpenTelemetrySpanExt;
let span = tracing::Span::current();
span.set_status(Status::Error {
description: message.to_owned().into(),
})
}
pub fn panic_hook(panic: &PanicHookInfo<'_>) {
panic_hook_as::<Exception>(panic)
}
pub fn panic_hook_as<P: PanicRecorder>(panic: &PanicHookInfo<'_>) {
const DEFAULT_MESSAGE: &'static str = "panic occurred";
let location = match panic.location() {
Some(location) => location,
None => Location::caller(),
};
let msg = match panic.payload().downcast_ref::<&'static str>() {
Some(message) => message,
None => match panic.payload().downcast_ref::<String>() {
Some(message) => message.as_str(),
None => &DEFAULT_MESSAGE,
}
};
propagate_status(msg);
let backtrace = Backtrace::force_capture();
P::capture(location, msg, backtrace);
}
pub fn install_panic_hook() {
install_panic_hook_as(Exception)
}
pub fn install_panic_hook_as<T: PanicRecorder>(_: T) {
static ONCE: OnceLock<()> = OnceLock::new();
ONCE.get_or_init(|| {
let next = std::panic::take_hook();
std::panic::set_hook(Box::new(move |info| {
panic_hook_as::<T>(info);
next(info);
}));
});
}