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}