1//! Support for using `tor-error` with the `tracing` crate.
23use crate::ErrorKind;
45#[doc(hidden)]
6pub use static_assertions;
7#[doc(hidden)]
8pub use tracing::{event, Level};
910use paste::paste;
1112impl ErrorKind {
13/// Return true if this [`ErrorKind`] should always be logged as
14 /// a warning (or more severe).
15pub fn is_always_a_warning(&self) -> bool {
16matches!(self, ErrorKind::Internal | ErrorKind::BadApiUsage)
17 }
18}
1920#[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.
26match s.as_bytes() {
27 [.., b'.', b'.', b'.'] => Ok(()),
28 [.., b' ' | b'.' | b':'] => Err(()),
29_ => Ok(()),
30 }
31 .is_ok()
32}
3334/// 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 {
75use $crate::{tracing as tr, HasKind as _, };
76let err = $err;
77if 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 };
8485 ($level:expr, $err:expr, $fmt:literal) => {
86$crate::event_report!($level, $err, $fmt, )
87 };
8889 (@raw $level:expr, $err:expr, $fmt:literal $(, $arg:expr)* $(,)?) => {
90 {
91use $crate::{tracing as tr, ErrorReport as _};
92tr::static_assertions::const_assert!(
93 tr::fmt_ending_ok($fmt)
94 );
95tr::event!(
96$level,
97concat!($fmt, ": {}"),
98 $($arg ,)*
99 ($err).report()
100 )
101 }
102 }
103}
104105/// 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
118119$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!",
133r#"(err, "Cheese exhausted (ephemeral)");"#)]
134 #[doc = concat!(stringify!($level), "_report!",
135r#"(err, "Unable to parse message {:?}", msg);"#)]
136/// # }
137 /// ```
138#[macro_export]
139macro_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} )* } }
147148define_report_macros! {
149/// Log a report for `err` at level
150LEVEL
151/// (or higher if it is a bug).
152153$ [] trace
154 [] debug
155 [] info
156}
157158define_report_macros! {
159/// Log a report for `err` at level
160LEVEL
161///
162$ [@raw] warn
163 [@raw] error
164}
165166#[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 @@ -->
181use crate::report::ErrorReport;
182use thiserror::Error;
183use tracing_test::traced_test;
184185#[derive(Error, Debug)]
186 #[error("my error")]
187struct MyError;
188189#[test]
190 #[traced_test]
191fn warn_report() {
192let me = MyError;
193let _ = me.report();
194warn_report!(me, "reporting unwrapped");
195196let ae = anyhow::Error::from(me).context("context");
197let _ = ae.report();
198warn_report!(ae, "reporting anyhow");
199 }
200}