1
//! Support for using `tor-error` with the `tracing` crate.
2

            
3
use crate::ErrorKind;
4

            
5
#[doc(hidden)]
6
pub use static_assertions;
7
#[doc(hidden)]
8
pub use tracing::{Level, event};
9

            
10
use paste::paste;
11

            
12
impl ErrorKind {
13
    /// Return true if this [`ErrorKind`] should always be logged as
14
    /// a warning (or more severe).
15
8250
    pub fn is_always_a_warning(&self) -> bool {
16
8250
        matches!(self, ErrorKind::Internal | ErrorKind::BadApiUsage)
17
8250
    }
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]
52
macro_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 (!)
99
macro_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

            
142
define_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

            
152
define_report_macros! {
153
    /// Log a report for `err` at level
154
    LEVEL
155
    ///
156
    $ [@raw] warn
157
      [@raw] error
158
}
159

            
160
#[cfg(test)]
161
mod 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
}