app_frame/
error.rs

1/// Enables logging of errors, to move forward without returning the error.
2pub trait LogError<T>: Sized {
3    ////////////////////////////////////////
4    // Converts to option, logged as error
5
6    /// Logs if there was an error and converts the result into an option
7    fn log(self) -> Option<T> {
8        self.log_as(tracing::log::Level::Error)
9    }
10    /// Logs if there was an error with a message and converts the result into an option
11    fn log_context(self, ctx: &str) -> Option<T> {
12        self.log_context_as(tracing::log::Level::Error, ctx)
13    }
14    /// Lazily logs if there was an error with a message and converts the result into an option
15    fn log_with_context<Ctx: Fn() -> String>(self, ctx: Ctx) -> Option<T> {
16        self.log_with_context_as(tracing::log::Level::Error, ctx)
17    }
18
19    ////////////////////////////////////////
20    // Converts to option, with customizable log level
21
22    /// Logs if there was an error and converts the result into an option
23    fn log_as(self, level: tracing::log::Level) -> Option<T>;
24    /// Logs if there was an error with a message at the provided log level, and converts the result into an option
25    fn log_context_as(self, level: tracing::log::Level, ctx: &str) -> Option<T> {
26        self.log_with_context_as(level, || ctx.into())
27    }
28    /// Lazily logs if there was an error with a message at the provided log level, and converts the result into an option
29    fn log_with_context_as<Ctx: Fn() -> String>(
30        self,
31        level: tracing::log::Level,
32        ctx: Ctx,
33    ) -> Option<T>;
34
35    ////////////////////////////////////////
36    // Passthrough functions that do not change the result, logged as error
37
38    /// Logs if there was an error and returns the result unchanged
39    fn log_passthrough(self) -> Self {
40        self.log_as_passthrough(tracing::log::Level::Error)
41    }
42    /// Logs if there was an error with a message and returns the result unchanged
43    fn log_context_passthrough(self, ctx: &str) -> Self {
44        self.log_context_as_passthrough(tracing::log::Level::Error, ctx)
45    }
46    /// Lazily logs if there was an error with a message and returns the result unchanged
47    fn log_with_context_passthrough<Ctx: Fn() -> String>(self, ctx: Ctx) -> Self {
48        self.log_with_context_as_passthrough(tracing::log::Level::Error, ctx)
49    }
50
51    ////////////////////////////////////////
52    // Passthrough functions that do not change the result, with customizable log level
53
54    /// Logs if there was an error and returns the result unchanged
55    fn log_as_passthrough(self, level: tracing::log::Level) -> Self;
56    /// Logs if there was an error with a message at the provided log level, and returns the result unchanged
57    fn log_context_as_passthrough(self, level: tracing::log::Level, ctx: &str) -> Self {
58        self.log_with_context_as_passthrough(level, || ctx.into())
59    }
60    /// Lazily logs if there was an error with a message at the provided log level, and returns the result unchanged
61    fn log_with_context_as_passthrough<Ctx: Fn() -> String>(
62        self,
63        level: tracing::log::Level,
64        ctx: Ctx,
65    ) -> Self;
66}
67
68impl<T, E: std::fmt::Display + 'static> LogError<T> for Result<T, E> {
69    fn log_as(self, level: tracing::log::Level) -> Option<T> {
70        self.log_as_passthrough(level).ok()
71    }
72
73    fn log_with_context_as<Ctx: Fn() -> String>(
74        self,
75        level: tracing::log::Level,
76        ctx: Ctx,
77    ) -> Option<T> {
78        self.log_with_context_as_passthrough(level, ctx).ok()
79    }
80
81    fn log_as_passthrough(self, level: tracing::log::Level) -> Self {
82        self.map_err(|e| {
83            let es = display_error(&e);
84            log!(level, "{es}");
85            e
86        })
87    }
88
89    fn log_with_context_as_passthrough<Ctx: Fn() -> String>(
90        self,
91        level: tracing::log::Level,
92        ctx: Ctx,
93    ) -> Self {
94        self.map_err(|e| {
95            let ctx = ctx();
96            let es = display_error(&e);
97            log!(level, "error: `{ctx}` - {es}");
98            e
99        })
100    }
101}
102
103macro_rules! log {
104    ($level:expr, $($args:tt),*) => {
105        match $level {
106            tracing::log::Level::Error => tracing::error!($($args),*),
107            tracing::log::Level::Warn => tracing::warn!($($args),*),
108            tracing::log::Level::Info => tracing::info!($($args),*),
109            tracing::log::Level::Debug => tracing::debug!($($args),*),
110            tracing::log::Level::Trace => tracing::trace!($($args),*),
111        };
112    };
113}
114pub(crate) use log;
115
116/// use this to make sure you have a descriptive message including a stack trace
117/// for anyhow errors, and otherwise just display the normal string for other
118/// errors.
119pub fn display_error<E: std::fmt::Display + 'static>(e: &E) -> String {
120    match (e as &dyn std::any::Any).downcast_ref::<anyhow::Error>() {
121        Some(nehau) => {
122            let mut s = String::new();
123            format_anyhow(nehau, &mut s).unwrap();
124            s
125        }
126        None => format!("{e}"),
127    }
128}
129
130fn format_anyhow<W: std::fmt::Write>(e: &anyhow::Error, f: &mut W) -> std::fmt::Result {
131    write!(f, "{}", e)?;
132    for i in e.chain().skip(1) {
133        write!(f, ", caused by: {}", i)?;
134    }
135    write!(f, "\nstack backtrace:\n{}", e.backtrace())
136}