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::{event, Level};
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#[doc(hidden)]
21/// Return true if a given string has an ending that makes sense for our
22/// formats.
23pub const fn fmt_ending_ok(s: &str) -> bool {
24    // This implementation is slightly roundabout because we need this function
25    // to be const.
26    match s.as_bytes() {
27        [.., b'.', b'.', b'.'] => Ok(()),
28        [.., b' ' | b'.' | b':'] => Err(()),
29        _ => Ok(()),
30    }
31    .is_ok()
32}
33
34/// Log a [`Report`](crate::Report) of a provided error at a given level, or a
35/// higher level if appropriate.
36///
37/// (If [`ErrorKind::is_always_a_warning`] returns true for the error's kind, we
38/// log it at WARN, unless this event is already at level WARN or ERROR.)
39///
40/// We require that the format string _not_ end with ' ', ',' or ':'; if it doesn't,
41/// we produce a compile-time error.
42///
43/// # Examples
44///
45/// ```
46/// # // this is what implements HasKind in this crate.
47/// # fn demo(err: &futures::task::SpawnError) {
48/// # let num = 7;
49/// use tor_error::event_report;
50/// use tracing::Level;
51///
52/// event_report!(Level::DEBUG, err, "Couldn't chew gum while walking");
53///
54/// event_report!(Level::TRACE, err, "Ephemeral error on attempt #{}", num);
55/// # }
56/// ```
57///
58/// # Limitations
59///
60/// This macro does not support the full range of syntaxes supported by
61/// [`tracing::event!`].
62///
63/// The compile-time error produced when the format string has a bad ending is
64/// kind of confusing.  This is a limitation of the `static_assertions` crate.
65//
66// NOTE: We need this fancy conditional here because tracing::event! insists on
67// getting a const expression for its `Level`.  So we can do
68// `if cond {debug!(..)} else {warn!(..)}`,
69// but we can't do
70// `event!(if cond {DEBUG} else {WARN}, ..)`.
71#[macro_export]
72macro_rules! event_report {
73    ($level:expr, $err:expr, $fmt:literal, $($arg:expr),* $(,)?) => {
74        {
75            use $crate::{tracing as tr, HasKind as _, };
76            let err = $err;
77            if err.kind().is_always_a_warning() && tr::Level::WARN < $level {
78                $crate::event_report!(@raw tr::Level::WARN, err, $fmt, $($arg),*);
79            } else {
80                $crate::event_report!(@raw $level, err, $fmt, $($arg),*);
81            }
82        }
83    };
84
85    ($level:expr, $err:expr, $fmt:literal) => {
86        $crate::event_report!($level, $err, $fmt, )
87    };
88
89    (@raw $level:expr, $err:expr, $fmt:literal $(, $arg:expr)* $(,)?) => {
90        {
91            use $crate::{tracing as tr, ErrorReport as _};
92            tr::static_assertions::const_assert!(
93                tr::fmt_ending_ok($fmt)
94            );
95            tr::event!(
96                $level,
97                concat!($fmt, ": {}"),
98                $($arg ,)*
99                ($err).report()
100            )
101        }
102    }
103}
104
105/// Define a macro `$level_report`
106///
107/// The title line for the doc comment will be
108/// ``$title_1 `LEVEL` $title_2``
109///
110/// A standard body, containing a set of examples, will be provided.
111///
112/// You must pass a dollar sign for `D`, because there is no dollar escaping mechanism
113/// for macro_rules macros in stable Rust (!)
114macro_rules! define_report_macros { {
115    # $title_1:tt
116    LEVEL
117    # $title_2:tt
118
119    $D:tt
120    $( [$($flag:tt)*] $level:ident )*
121} => { $( paste!{
122    # $title_1
123    #[doc = concat!("`", stringify!( [< $level:upper >] ), "`")]
124    # $title_2
125    ///
126    /// # Examples:
127    ///
128    /// ```
129    /// # fn demo(err: &futures::task::SpawnError) {
130    /// # let msg = ();
131    #[doc = concat!("use tor_error::", stringify!($level), "_report;")]
132    #[doc = concat!(stringify!($level), "_report!",
133                    r#"(err, "Cheese exhausted (ephemeral)");"#)]
134    #[doc = concat!(stringify!($level), "_report!",
135                    r#"(err, "Unable to parse message {:?}", msg);"#)]
136    /// # }
137    /// ```
138    #[macro_export]
139    macro_rules! [< $level _report >] {
140        ( $D err:expr, $D ($D rest:expr),+ $D (,)? ) => {
141            $D crate::event_report!($($flag)*
142                                    $D crate::tracing::Level::[< $level:upper >],
143                                    $D err, $D ($D rest),+)
144        }
145    }
146} )* } }
147
148define_report_macros! {
149    /// Log a report for `err` at level
150    LEVEL
151    /// (or higher if it is a bug).
152
153    $ [] trace
154      [] debug
155      [] info
156}
157
158define_report_macros! {
159    /// Log a report for `err` at level
160    LEVEL
161    ///
162    $ [@raw] warn
163      [@raw] error
164}
165
166#[cfg(test)]
167mod test {
168    // @@ begin test lint list maintained by maint/add_warning @@
169    #![allow(clippy::bool_assert_comparison)]
170    #![allow(clippy::clone_on_copy)]
171    #![allow(clippy::dbg_macro)]
172    #![allow(clippy::mixed_attributes_style)]
173    #![allow(clippy::print_stderr)]
174    #![allow(clippy::print_stdout)]
175    #![allow(clippy::single_char_pattern)]
176    #![allow(clippy::unwrap_used)]
177    #![allow(clippy::unchecked_duration_subtraction)]
178    #![allow(clippy::useless_vec)]
179    #![allow(clippy::needless_pass_by_value)]
180    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
181    use crate::report::ErrorReport;
182    use thiserror::Error;
183    use tracing_test::traced_test;
184
185    #[derive(Error, Debug)]
186    #[error("my error")]
187    struct MyError;
188
189    #[test]
190    #[traced_test]
191    fn warn_report() {
192        let me = MyError;
193        let _ = me.report();
194        warn_report!(me, "reporting unwrapped");
195
196        let ae = anyhow::Error::from(me).context("context");
197        let _ = ae.report();
198        warn_report!(ae, "reporting anyhow");
199    }
200}