1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
/// Enables logging of errors, to move forward without returning the error.
pub trait LogError<T>: Sized {
    ////////////////////////////////////////
    // Converts to option, logged as error

    /// Logs if there was an error and converts the result into an option
    fn log(self) -> Option<T> {
        self.log_as(tracing::log::Level::Error)
    }
    /// Logs if there was an error with a message and converts the result into an option
    fn log_context(self, ctx: &str) -> Option<T> {
        self.log_context_as(tracing::log::Level::Error, ctx)
    }
    /// Lazily logs if there was an error with a message and converts the result into an option
    fn log_with_context<Ctx: Fn() -> String>(self, ctx: Ctx) -> Option<T> {
        self.log_with_context_as(tracing::log::Level::Error, ctx)
    }

    ////////////////////////////////////////
    // Converts to option, with customizable log level

    /// Logs if there was an error and converts the result into an option
    fn log_as(self, level: tracing::log::Level) -> Option<T>;
    /// Logs if there was an error with a message at the provided log level, and converts the result into an option
    fn log_context_as(self, level: tracing::log::Level, ctx: &str) -> Option<T> {
        self.log_with_context_as(level, || ctx.into())
    }
    /// Lazily logs if there was an error with a message at the provided log level, and converts the result into an option
    fn log_with_context_as<Ctx: Fn() -> String>(
        self,
        level: tracing::log::Level,
        ctx: Ctx,
    ) -> Option<T>;

    ////////////////////////////////////////
    // Passthrough functions that do not change the result, logged as error

    /// Logs if there was an error and returns the result unchanged
    fn log_passthrough(self) -> Self {
        self.log_as_passthrough(tracing::log::Level::Error)
    }
    /// Logs if there was an error with a message and returns the result unchanged
    fn log_context_passthrough(self, ctx: &str) -> Self {
        self.log_context_as_passthrough(tracing::log::Level::Error, ctx)
    }
    /// Lazily logs if there was an error with a message and returns the result unchanged
    fn log_with_context_passthrough<Ctx: Fn() -> String>(self, ctx: Ctx) -> Self {
        self.log_with_context_as_passthrough(tracing::log::Level::Error, ctx)
    }

    ////////////////////////////////////////
    // Passthrough functions that do not change the result, with customizable log level

    /// Logs if there was an error and returns the result unchanged
    fn log_as_passthrough(self, level: tracing::log::Level) -> Self;
    /// Logs if there was an error with a message at the provided log level, and returns the result unchanged
    fn log_context_as_passthrough(self, level: tracing::log::Level, ctx: &str) -> Self {
        self.log_with_context_as_passthrough(level, || ctx.into())
    }
    /// Lazily logs if there was an error with a message at the provided log level, and returns the result unchanged
    fn log_with_context_as_passthrough<Ctx: Fn() -> String>(
        self,
        level: tracing::log::Level,
        ctx: Ctx,
    ) -> Self;
}

impl<T, E: std::fmt::Display + 'static> LogError<T> for Result<T, E> {
    fn log_as(self, level: tracing::log::Level) -> Option<T> {
        self.log_as_passthrough(level).ok()
    }

    fn log_with_context_as<Ctx: Fn() -> String>(
        self,
        level: tracing::log::Level,
        ctx: Ctx,
    ) -> Option<T> {
        self.log_with_context_as_passthrough(level, ctx).ok()
    }

    fn log_as_passthrough(self, level: tracing::log::Level) -> Self {
        self.map_err(|e| {
            let es = display_error(&e);
            log!(level, "{es}");
            e
        })
    }

    fn log_with_context_as_passthrough<Ctx: Fn() -> String>(
        self,
        level: tracing::log::Level,
        ctx: Ctx,
    ) -> Self {
        self.map_err(|e| {
            let ctx = ctx();
            let es = display_error(&e);
            log!(level, "error: `{ctx}` - {es}");
            e
        })
    }
}

macro_rules! log {
    ($level:expr, $($args:tt),*) => {
        match $level {
            tracing::log::Level::Error => tracing::error!($($args),*),
            tracing::log::Level::Warn => tracing::warn!($($args),*),
            tracing::log::Level::Info => tracing::info!($($args),*),
            tracing::log::Level::Debug => tracing::debug!($($args),*),
            tracing::log::Level::Trace => tracing::trace!($($args),*),
        };
    };
}
pub(crate) use log;

/// use this to make sure you have a descriptive message including a stack trace
/// for anyhow errors, and otherwise just display the normal string for other
/// errors.
pub fn display_error<E: std::fmt::Display + 'static>(e: &E) -> String {
    match (e as &dyn std::any::Any).downcast_ref::<anyhow::Error>() {
        Some(nehau) => {
            let mut s = String::new();
            format_anyhow(nehau, &mut s).unwrap();
            s
        }
        None => format!("{e}"),
    }
}

fn format_anyhow<W: std::fmt::Write>(e: &anyhow::Error, f: &mut W) -> std::fmt::Result {
    write!(f, "{}", e)?;
    for i in e.chain().skip(1) {
        write!(f, ", caused by: {}", i)?;
    }
    write!(f, "\nstack backtrace:\n{}", e.backtrace())
}