tor_error/
tracing.rs

1//! Support for using `tor-error` with the `tracing` crate.
2
3use crate::ErrorKind;
4
5#[doc(hidden)]
6pub use static_assertions;
7#[doc(hidden)]
8pub use tracing::{Level, event};
9
10use paste::paste;
11
12impl ErrorKind {
13    /// Return true if this [`ErrorKind`] should always be logged as
14    /// a warning (or more severe).
15    pub fn is_always_a_warning(&self) -> bool {
16        matches!(self, ErrorKind::Internal | ErrorKind::BadApiUsage)
17    }
18}
19
20/// Log a [`Report`](crate::Report) of a provided error at a given level, or a
21/// higher level if appropriate.
22///
23/// (If [`ErrorKind::is_always_a_warning`] returns true for the error's kind, we
24/// log it at WARN, unless this event is already at level WARN or ERROR.)
25///
26/// # Examples
27///
28/// ```
29/// # // this is what implements HasKind in this crate.
30/// # fn demo(err: &futures::task::SpawnError) {
31/// # let num = 7;
32/// use tor_error::event_report;
33/// use tracing::Level;
34///
35/// event_report!(Level::DEBUG, err, "Couldn't chew gum while walking");
36///
37/// event_report!(Level::TRACE, err, attempt = %num, "Ephemeral error");
38/// # }
39/// ```
40///
41/// # Limitations
42///
43/// This macro does not support the full range of syntaxes supported by
44/// [`tracing::event!`].
45//
46// NOTE: We need this fancy conditional here because tracing::event! insists on
47// getting a const expression for its `Level`.  So we can do
48// `if cond {debug!(..)} else {warn!(..)}`,
49// but we can't do
50// `event!(if cond {DEBUG} else {WARN}, ..)`.
51#[macro_export]
52macro_rules! event_report {
53    ($level:expr, $err:expr) => {
54        $crate::event_report!($level, $err,)
55    };
56
57    ($level:expr, $err:expr, $($arg:tt)*) => {
58        {
59            use $crate::{tracing as tr, HasKind as _, };
60            let err = $err;
61            if err.kind().is_always_a_warning() && tr::Level::WARN < $level {
62                $crate::event_report!(@raw tr::Level::WARN, err, $($arg)*);
63            } else {
64                $crate::event_report!(@raw $level, err, $($arg)*);
65            }
66        }
67    };
68
69    (@raw $level:expr, $err:expr) => {
70        $crate::event_report!(@raw $level, $err,)
71    };
72
73    (@raw $level:expr, $err:expr, $($arg:tt)*) => {
74        {
75            use $crate::tracing as tr;
76            use ::std::ops::Deref as _;
77
78            tr::event!(
79                $level,
80                // some types like `anyhow::Error` can deref to a `dyn Error`, and we cast as
81                // `&dyn Error` so that it is handled as an error type by a tracing field
82                // visitor (see `Visit::record_error()` from `tracing-core`)
83                error = ((&($err)).deref() as &dyn std::error::Error),
84                $($arg)*
85            )
86        }
87    }
88}
89
90/// Define a macro `$level_report`
91///
92/// The title line for the doc comment will be
93/// ``$title_1 `LEVEL` $title_2``
94///
95/// A standard body, containing a set of examples, will be provided.
96///
97/// You must pass a dollar sign for `D`, because there is no dollar escaping mechanism
98/// for macro_rules macros in stable Rust (!)
99macro_rules! define_report_macros { {
100    # $title_1:tt
101    LEVEL
102    # $title_2:tt
103
104    $D:tt
105    $( [$($flag:tt)*] $level:ident )*
106} => { $( paste!{
107    # $title_1
108    #[doc = concat!("`", stringify!( [< $level:upper >] ), "`")]
109    # $title_2
110    ///
111    /// # Examples:
112    ///
113    /// ```
114    /// # fn demo(err: &futures::task::SpawnError) {
115    /// # let msg = ();
116    #[doc = concat!("use tor_error::", stringify!($level), "_report;")]
117    #[doc = concat!(stringify!($level), "_report!",
118                    r#"(err, "Cheese exhausted (ephemeral)");"#)]
119    #[doc = concat!(stringify!($level), "_report!",
120                    r#"(err, "Unable to parse message {:?}", msg);"#)]
121    /// # }
122    /// ```
123    #[macro_export]
124    macro_rules! [< $level _report >] {
125        ( $D err:expr ) => {
126            // would be nice to do a `$D crate::[< $level _report >]!($D err,)` here,
127            // but apparently this isn't allowed:
128            // https://github.com/rust-lang/rust/pull/52234
129            $D crate::event_report!($($flag)*
130                                    $D crate::tracing::Level::[< $level:upper >],
131                                    $D err)
132        };
133
134        ( $D err:expr, $D ($D rest:tt)* ) => {
135            $D crate::event_report!($($flag)*
136                                    $D crate::tracing::Level::[< $level:upper >],
137                                    $D err, $D ($D rest)*)
138        }
139    }
140} )* } }
141
142define_report_macros! {
143    /// Log a report for `err` at level
144    LEVEL
145    /// (or higher if it is a bug).
146
147    $ [] trace
148      [] debug
149      [] info
150}
151
152define_report_macros! {
153    /// Log a report for `err` at level
154    LEVEL
155    ///
156    $ [@raw] warn
157      [@raw] error
158}
159
160#[cfg(test)]
161mod test {
162    // @@ begin test lint list maintained by maint/add_warning @@
163    #![allow(clippy::bool_assert_comparison)]
164    #![allow(clippy::clone_on_copy)]
165    #![allow(clippy::dbg_macro)]
166    #![allow(clippy::mixed_attributes_style)]
167    #![allow(clippy::print_stderr)]
168    #![allow(clippy::print_stdout)]
169    #![allow(clippy::single_char_pattern)]
170    #![allow(clippy::unwrap_used)]
171    #![allow(clippy::unchecked_duration_subtraction)]
172    #![allow(clippy::useless_vec)]
173    #![allow(clippy::needless_pass_by_value)]
174    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
175
176    use crate::internal;
177    use crate::report::ErrorReport;
178    use thiserror::Error;
179    use tracing_test::traced_test;
180
181    #[derive(Error, Debug)]
182    #[error("my error")]
183    struct MyError;
184
185    #[test]
186    #[traced_test]
187    // i really don't think that this test is too complicated
188    #[allow(clippy::cognitive_complexity)]
189    fn warn_report() {
190        let me = MyError;
191        let _ = me.report();
192        warn_report!(me, "reporting unwrapped");
193
194        let ae = anyhow::Error::from(me).context("context");
195        let _ = ae.report();
196        warn_report!(ae, "reporting anyhow");
197
198        let ie = internal!("Foo was not initialized");
199        let _ = ie.report();
200        warn_report!(ie);
201    }
202}