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}