use std::panic::PanicHookInfo;
use std::sync::{Arc, Mutex, Once};
use crate::integration::Integration;
use crate::options::ClientOptions;
use crate::protocol::ErrorEvent;
type Extractor = Box<dyn Fn(&PanicHookInfo<'_>) -> Option<ErrorEvent> + Send + Sync>;
static INSTALL: Once = Once::new();
static EXTRACTORS: Mutex<Vec<Extractor>> = Mutex::new(Vec::new());
#[derive(Default)]
pub struct PanicIntegration {
extractors: Mutex<Vec<Extractor>>,
}
impl PanicIntegration {
pub fn new() -> Self {
PanicIntegration::default()
}
pub fn add_extractor<F>(self, f: F) -> Self
where
F: Fn(&PanicHookInfo<'_>) -> Option<ErrorEvent> + Send + Sync + 'static,
{
if let Ok(mut v) = self.extractors.lock() {
v.push(Box::new(f));
}
self
}
}
impl Integration for PanicIntegration {
fn name(&self) -> &'static str {
"panic"
}
fn setup(&self, _options: &mut ClientOptions) {
if let (Ok(mut local), Ok(mut global)) = (self.extractors.lock(), EXTRACTORS.lock()) {
global.append(&mut local);
}
register_panic_hook();
}
}
pub fn message_from_panic_info<'a>(info: &'a PanicHookInfo<'_>) -> &'a str {
match info.payload().downcast_ref::<&str>() {
Some(s) => s,
None => match info.payload().downcast_ref::<String>() {
Some(s) => s.as_str(),
None => "Box<dyn Any>",
},
}
}
pub fn panic_handler(info: &PanicHookInfo<'_>) {
let event = build_event(info);
let hub = crate::hub::Hub::current();
hub.mark_session_crashed();
hub.capture_event(event);
hub.flush(std::time::Duration::from_secs(2));
}
fn build_event(info: &PanicHookInfo<'_>) -> ErrorEvent {
if let Ok(extractors) = EXTRACTORS.lock() {
for extractor in extractors.iter() {
if let Some(event) = extractor(info) {
return event;
}
}
}
let message = message_from_panic_info(info);
let location = info
.location()
.map(|l| format!(" at {}:{}", l.file(), l.line()))
.unwrap_or_default();
let full = format!("{message}{location}");
let opts = ClientOptions::default();
let frames = crate::backtrace::current_frames(&opts);
crate::event::event_from_panic(&full, frames)
}
pub fn register_panic_hook() {
INSTALL.call_once(|| {
let prev = std::panic::take_hook();
let prev: Arc<dyn Fn(&PanicHookInfo<'_>) + Sync + Send> = Arc::from(prev);
std::panic::set_hook(Box::new(move |info| {
panic_handler(info);
prev(info);
}));
});
}