sentry-panic 0.47.0

Sentry integration for capturing panics.
Documentation
//! The Sentry Panic handler integration.
//!
//! The `PanicIntegration`, which is enabled by default in `sentry`, installs a
//! panic handler that will automatically dispatch all errors to Sentry that
//! are caused by a panic.
//! Additionally, panics are forwarded to the previously registered panic hook.
//!
//! # Configuration
//!
//! The panic integration can be configured with an additional extractor, which
//! might optionally create a sentry `Event` out of a `PanicInfo`.
//!
//! ```
//! let integration = sentry_panic::PanicIntegration::default().add_extractor(|info| None);
//! ```

#![doc(html_favicon_url = "https://sentry-brand.storage.googleapis.com/favicon.ico")]
#![doc(html_logo_url = "https://sentry-brand.storage.googleapis.com/sentry-glyph-black.png")]
#![warn(missing_docs)]
#![deny(unsafe_code)]

#[allow(deprecated)] // `PanicHookInfo` is only available in Rust 1.81+.
use std::panic::{self, PanicInfo};
use std::sync::Once;

use sentry_backtrace::current_stacktrace;
use sentry_core::protocol::{Event, Exception, Level, Mechanism};
use sentry_core::{ClientOptions, Integration};

/// A panic handler that sends to Sentry.
///
/// This panic handler reports panics to Sentry. It also attempts to prevent
/// double faults in some cases where it's known to be unsafe to invoke the
/// Sentry panic handler.
#[allow(deprecated)] // `PanicHookInfo` is only available in Rust 1.81+.
pub fn panic_handler(info: &PanicInfo<'_>) {
    sentry_core::with_integration(|integration: &PanicIntegration, hub| {
        hub.capture_event(integration.event_from_panic_info(info));
        if let Some(client) = hub.client() {
            client.flush(None);
        }
    });
}

#[allow(deprecated)] // `PanicHookInfo` is only available in Rust 1.81+.
type PanicExtractor = dyn Fn(&PanicInfo<'_>) -> Option<Event<'static>> + Send + Sync;

/// The Sentry Panic handler Integration.
#[derive(Default)]
pub struct PanicIntegration {
    extractors: Vec<Box<PanicExtractor>>,
}

impl std::fmt::Debug for PanicIntegration {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("PanicIntegration")
            .field("extractors", &self.extractors.len())
            .finish()
    }
}

static INIT: Once = Once::new();

impl Integration for PanicIntegration {
    fn name(&self) -> &'static str {
        "panic"
    }

    fn setup(&self, _cfg: &mut ClientOptions) {
        INIT.call_once(|| {
            let next = panic::take_hook();
            panic::set_hook(Box::new(move |info| {
                panic_handler(info);
                next(info);
            }));
        });
    }
}

/// Extract the message of a panic.
#[allow(deprecated)] // `PanicHookInfo` is only available in Rust 1.81+.
pub fn message_from_panic_info<'a>(info: &'a PanicInfo<'_>) -> &'a str {
    match info.payload().downcast_ref::<&'static str>() {
        Some(s) => s,
        None => match info.payload().downcast_ref::<String>() {
            Some(s) => &s[..],
            None => "Box<Any>",
        },
    }
}

impl PanicIntegration {
    /// Creates a new Panic Integration.
    pub fn new() -> Self {
        Self::default()
    }

    /// Registers a new extractor.
    #[must_use]
    #[allow(deprecated)] // `PanicHookInfo` is only available in Rust 1.81+.
    pub fn add_extractor<F>(mut self, f: F) -> Self
    where
        F: Fn(&PanicInfo<'_>) -> Option<Event<'static>> + Send + Sync + 'static,
    {
        self.extractors.push(Box::new(f));
        self
    }

    /// Creates an event from the given panic info.
    ///
    /// The stacktrace is calculated from the current frame.
    #[allow(deprecated)] // `PanicHookInfo` is only available in Rust 1.81+.
    pub fn event_from_panic_info(&self, info: &PanicInfo<'_>) -> Event<'static> {
        for extractor in &self.extractors {
            if let Some(event) = extractor(info) {
                return event;
            }
        }

        // TODO: We would ideally want to downcast to `std::error:Error` here
        // and use `event_from_error`, but that way we won‘t get meaningful
        // backtraces yet.

        let msg = message_from_panic_info(info);
        Event {
            exception: vec![Exception {
                ty: "panic".into(),
                mechanism: Some(Mechanism {
                    ty: "panic".into(),
                    handled: Some(false),
                    ..Default::default()
                }),
                value: Some(msg.to_string()),
                stacktrace: current_stacktrace(),
                ..Default::default()
            }]
            .into(),
            level: Level::Fatal,
            ..Default::default()
        }
    }
}