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::{event, Level};
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
2304
    pub fn is_always_a_warning(&self) -> bool {
16
2304
        matches!(self, ErrorKind::Internal | ErrorKind::BadApiUsage)
17
2304
    }
18
}
19

            
20
#[doc(hidden)]
21
/// Return true if a given string has an ending that makes sense for our
22
/// formats.
23
pub 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]
72
macro_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 (!)
114
macro_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

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

            
158
define_report_macros! {
159
    /// Log a report for `err` at level
160
    LEVEL
161
    ///
162
    $ [@raw] warn
163
      [@raw] error
164
}
165

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