1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
//! Sentry supported panic handler.
#[cfg(doc)]
use crate::{shutdown, Shutdown};
use crate::{Event, Level, Value};
#[cfg(doc)]
use std::process::abort;
use std::{
collections::BTreeMap,
convert::TryFrom,
panic::{self, PanicInfo},
};
/// Panic handler to send an [`Event`] with the current stacktrace to Sentry.
///
/// `before_send` is a callback that is able to modify the [`Event`] before it
/// is captures.
///
/// `hook` is a callback that is run after the [`Event`] is captured.
///
/// # Notes
/// This will not work properly if used with `panic = "abort"` because
/// [`Shutdown`] is never unwound. To fix this make sure you make the panic
/// handler itself call [`shutdown`].
///
/// Rust doesn't allow panics inside of a panicking thread and reacts with an
/// [`abort`]: if a custom transport or a before-send callback was registered
/// that can panic, it might lead to any [`panic!`] being an [`abort`] instead.
///
/// # Examples
/// ```should_panic
/// # use anyhow::Result;
/// # use sentry_contrib_native::{Options, set_hook};
/// fn main() -> Result<()> {
/// // pass original panic handler provided by rust to retain it's functionality
/// set_hook(None, Some(std::panic::take_hook()));
/// // it can also be removed
/// set_hook(None, None);
/// // the `Event` sent by a panic can also be modified
/// set_hook(
/// Some(Box::new(|mut event| {
/// // do something with the event and then return it
/// event
/// })),
/// None,
/// );
///
/// let _shutdown = Options::new().init()?;
///
/// panic!("application panicked")
/// }
/// ```
/// If you are using `panic = "abort"` make sure to call [`shutdown`] inside the
/// panic handler.
/// ```
/// # use sentry_contrib_native::{set_hook, shutdown};
/// set_hook(None, Some(Box::new(|_| shutdown())));
/// ```
pub fn set_hook(
before_send: Option<Box<dyn Fn(Event) -> Event + 'static + Send + Sync>>,
hook: Option<Box<dyn Fn(&PanicInfo) + 'static + Send + Sync>>,
) {
panic::set_hook(Box::new(move |panic_info| {
let mut event = Event::new_message(
Level::Error,
Some("rust panic".into()),
panic_info.to_string(),
);
if let Some(location) = panic_info.location() {
let mut extra = BTreeMap::new();
extra.insert("file", Value::from(location.file()));
if let Ok(line) = i32::try_from(location.line()) {
extra.insert("line", line.into());
}
if let Ok(column) = i32::try_from(location.column()) {
extra.insert("column", column.into());
}
event.insert("extra", extra);
}
event.add_stacktrace(0);
if let Some(before_send) = &before_send {
event = before_send(event);
}
event.capture();
if let Some(hook) = &hook {
hook(panic_info);
}
}));
}
#[cfg(test)]
#[rusty_fork::fork_test(timeout_ms = 60000)]
fn hook() {
use std::{
sync::atomic::{AtomicBool, Ordering},
thread,
};
static BEFORE_SEND: AtomicBool = AtomicBool::new(false);
static HOOK: AtomicBool = AtomicBool::new(false);
set_hook(None, None);
set_hook(
Some(Box::new(|event| {
BEFORE_SEND.store(true, Ordering::SeqCst);
event
})),
Some(Box::new(|_| HOOK.store(true, Ordering::SeqCst))),
);
thread::spawn(|| panic!("this panic is a test"))
.join()
.unwrap_err();
assert!(BEFORE_SEND.load(Ordering::SeqCst));
assert!(HOOK.load(Ordering::SeqCst));
}