sentry_core/
error.rs

1use std::error::Error;
2
3use crate::protocol::{Event, Exception, Level};
4use crate::types::Uuid;
5use crate::Hub;
6
7impl Hub {
8    /// Capture any `std::error::Error`.
9    ///
10    /// See the global [`capture_error`](fn.capture_error.html)
11    /// for more documentation.
12    #[allow(unused)]
13    pub fn capture_error<E: Error + ?Sized>(&self, error: &E) -> Uuid {
14        with_client_impl! {{
15            self.inner.with(|stack| {
16                let top = stack.top();
17                if top.client.is_some() {
18                    let event = event_from_error(error);
19                    self.capture_event(event)
20                } else {
21                    Uuid::nil()
22                }
23            })
24        }}
25    }
26}
27
28/// Captures a `std::error::Error`.
29///
30/// Creates an event from the given error and sends it to the current hub.
31/// A chain of errors will be resolved as well, and sorted oldest to newest, as
32/// described in the [sentry event payloads].
33///
34/// # Examples
35///
36/// ```
37/// let err = "NaN".parse::<usize>().unwrap_err();
38///
39/// # let events = sentry::test::with_captured_events(|| {
40/// sentry::capture_error(&err);
41/// # });
42/// # let captured_event = events.into_iter().next().unwrap();
43///
44/// assert_eq!(captured_event.exception.len(), 1);
45/// assert_eq!(&captured_event.exception[0].ty, "ParseIntError");
46/// ```
47///
48/// [sentry event payloads]: https://develop.sentry.dev/sdk/event-payloads/exception/
49#[allow(unused_variables)]
50pub fn capture_error<E: Error + ?Sized>(error: &E) -> Uuid {
51    Hub::with_active(|hub| hub.capture_error(error))
52}
53
54/// Create a sentry `Event` from a `std::error::Error`.
55///
56/// A chain of errors will be resolved as well, and sorted oldest to newest, as
57/// described in the [sentry event payloads].
58///
59/// # Examples
60///
61/// ```
62/// use thiserror::Error;
63///
64/// #[derive(Debug, Error)]
65/// #[error("inner")]
66/// struct InnerError;
67///
68/// #[derive(Debug, Error)]
69/// #[error("outer")]
70/// struct OuterError(#[from] InnerError);
71///
72/// let event = sentry::event_from_error(&OuterError(InnerError));
73/// assert_eq!(event.level, sentry::protocol::Level::Error);
74/// assert_eq!(event.exception.len(), 2);
75/// assert_eq!(&event.exception[0].ty, "InnerError");
76/// assert_eq!(event.exception[0].value, Some("inner".into()));
77/// assert_eq!(&event.exception[1].ty, "OuterError");
78/// assert_eq!(event.exception[1].value, Some("outer".into()));
79/// ```
80///
81/// [sentry event payloads]: https://develop.sentry.dev/sdk/event-payloads/exception/
82pub fn event_from_error<E: Error + ?Sized>(err: &E) -> Event<'static> {
83    let mut exceptions = vec![exception_from_error(err)];
84
85    let mut source = err.source();
86    while let Some(err) = source {
87        exceptions.push(exception_from_error(err));
88        source = err.source();
89    }
90
91    exceptions.reverse();
92    Event {
93        exception: exceptions.into(),
94        level: Level::Error,
95        ..Default::default()
96    }
97}
98
99fn exception_from_error<E: Error + ?Sized>(err: &E) -> Exception {
100    let dbg = format!("{err:?}");
101    let value = err.to_string();
102
103    // A generic `anyhow::msg` will just `Debug::fmt` the `String` that you feed
104    // it. Trying to parse the type name from that will result in a leading quote
105    // and the first word, so quite useless.
106    // To work around this, we check if the `Debug::fmt` of the complete error
107    // matches its `Display::fmt`, in which case there is no type to parse and
108    // we will just be using `Error`.
109    let ty = if dbg == format!("{value:?}") {
110        String::from("Error")
111    } else {
112        parse_type_from_debug(&dbg).to_owned()
113    };
114    Exception {
115        ty,
116        value: Some(err.to_string()),
117        ..Default::default()
118    }
119}
120
121/// Parse the types name from `Debug` output.
122///
123/// # Examples
124///
125/// ```
126/// use sentry::parse_type_from_debug;
127///
128/// let err = format!("{:?}", "NaN".parse::<usize>().unwrap_err());
129/// assert_eq!(parse_type_from_debug(&err), "ParseIntError");
130/// ```
131pub fn parse_type_from_debug(d: &str) -> &str {
132    d.split(&[' ', '(', '{', '\r', '\n'][..])
133        .next()
134        .unwrap()
135        .trim()
136}
137
138#[test]
139fn test_parse_type_from_debug() {
140    use parse_type_from_debug as parse;
141    #[derive(Debug)]
142    struct MyStruct;
143    let err = format!("{MyStruct:?}");
144    assert_eq!(parse(&err), "MyStruct");
145
146    let err = format!("{:?}", "NaN".parse::<usize>().unwrap_err());
147    assert_eq!(parse(&err), "ParseIntError");
148
149    let err = format!(
150        "{:?}",
151        sentry_types::ParseDsnError::from(sentry_types::ParseProjectIdError::EmptyValue)
152    );
153    assert_eq!(parse(&err), "InvalidProjectId");
154
155    // `anyhow` is using extended debug formatting
156    let err = format!(
157        "{:#?}",
158        anyhow::Error::from("NaN".parse::<usize>().unwrap_err())
159    );
160    assert_eq!(parse(&err), "ParseIntError");
161}
162
163#[test]
164fn test_parse_anyhow_as_error() {
165    let anyhow_err = anyhow::anyhow!("Ooops, something bad happened");
166    let err: &dyn Error = anyhow_err.as_ref();
167
168    let exc = exception_from_error(err);
169
170    assert_eq!(&exc.ty, "Error");
171    assert_eq!(exc.value.as_deref(), Some("Ooops, something bad happened"));
172}