sentry_eyre/
lib.rs

1// 🐻‍❄️👀 sentry-eyre: Sentry integration for `eyre`.
2// Copyright (c) 2023-2026 Noel Towa <cutie@floofy.dev>
3//
4// Permission is hereby granted, free of charge, to any person obtaining a copy
5// of this software and associated documentation files (the "Software"), to deal
6// in the Software without restriction, including without limitation the rights
7// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8// copies of the Software, and to permit persons to whom the Software is
9// furnished to do so, subject to the following conditions:
10//
11// The above copyright notice and this permission notice shall be included in all
12// copies or substantial portions of the Software.
13//
14// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20// SOFTWARE.
21//
22//! **sentry-eyre** is a integration to capture [`eyre::Report`](https://docs.rs/eyre/latest/eyre/struct.Report.html)s. This crate
23//! was inspired by the `sentry-anyhow` integration, and does a similar API but distinct enough to not create any issues.
24//!
25//! ## Usage
26//! ```no_run
27//! use eyre::Result;
28//! use sentry::{ClientOptions, init, types::Dsn};
29//! use sentry_eyre::capture_report;
30//!
31//! fn method_that_might_fail() -> Result<()> {
32//!     Err(eyre::eyre!("this method has failed."))
33//! }
34//!
35//! if let Err(e) = method_that_might_fail() {
36//!     capture_report(&e);
37//! }
38//! ```
39
40#![doc(html_logo_url = "https://cdn.floofy.dev/images/Noel.png")]
41#![doc(html_favicon_url = "https://cdn.floofy.dev/images/Noel.png")]
42#![cfg_attr(any(noeldoc, docsrs), feature(doc_cfg))]
43
44use eyre::Report;
45use sentry_core::{protocol::Event, types::Uuid, Hub};
46use std::error::Error;
47
48/// Captures a [`Report`] and sends it to Sentry. Refer to the top-level
49/// module documentation on how to use this method.
50pub fn capture_report(report: &Report) -> Uuid {
51    Hub::with_active(|hub| hub.capture_report(report))
52}
53
54/// Utility function to represent a Sentry [`Event`] from a [`Report`]. This shouldn't
55/// be consumed directly unless you want access to the created [`Event`] from a [`Report`].
56pub fn event_from_report(report: &Report) -> Event<'static> {
57    let err: &dyn Error = report.as_ref();
58
59    // It's not mutated for not(feature = "stable-backtrace")
60    #[allow(unused_mut)]
61    let mut event = sentry_core::event_from_error(err);
62
63    #[cfg(feature = "stable-backtrace")]
64    {
65        // exception records are sorted in reverse
66        if let Some(exc) = event.exception.iter_mut().last() {
67            use stable_eyre::BacktraceExt;
68            if let Some(backtrace) = report.backtrace() {
69                exc.stacktrace = sentry_backtrace::parse_stacktrace(&format!("{backtrace:#?}"));
70            }
71        }
72    }
73
74    event
75}
76
77/// Extension trait to implement a `capture_report` method on any implementations.
78pub trait CaptureReportExt: private::Sealed {
79    /// Captures a [`Report`] and sends it to Sentry. Refer to the top-level
80    /// module documentation on how to use this method.
81    fn capture_report(&self, report: &Report) -> Uuid;
82}
83
84impl CaptureReportExt for Hub {
85    fn capture_report(&self, report: &Report) -> Uuid {
86        self.capture_event(event_from_report(report))
87    }
88}
89
90mod private {
91    pub trait Sealed {}
92
93    impl Sealed for sentry_core::Hub {}
94}
95
96#[cfg(all(feature = "stable-backtrace", test))]
97mod tests {
98    use super::*;
99    use std::sync::Once;
100
101    static INIT: Once = Once::new();
102
103    fn init_test_environment() {
104        INIT.call_once(|| {
105            std::env::set_var("RUST_BACKTRACE", "1");
106            stable_eyre::install().unwrap();
107        });
108    }
109
110    #[test]
111    fn test_event_from_report_with_backtrace() {
112        init_test_environment();
113
114        let event = event_from_report(&eyre::eyre!("Oh jeez"));
115
116        let stacktrace = event.exception[0].stacktrace.as_ref().unwrap();
117        let found_test_fn = stacktrace
118            .frames
119            .iter()
120            .find(|frame| match &frame.function {
121                Some(f) => f.contains("test_event_from_report_with_backtrace"),
122                None => false,
123            });
124
125        assert!(found_test_fn.is_some());
126    }
127
128    #[test]
129    fn test_capture_eyre_uses_event_from_report_helper() {
130        init_test_environment();
131
132        let err = &eyre::eyre!("Oh jeez");
133
134        let event = event_from_report(err);
135        let events = sentry::test::with_captured_events(|| {
136            capture_report(err);
137        });
138
139        assert_eq!(event.exception, events[0].exception);
140    }
141}