deltachat/
log.rs

1//! # Logging.
2
3#![allow(missing_docs)]
4
5use crate::context::Context;
6
7macro_rules! info {
8    ($ctx:expr,  $msg:expr) => {
9        info!($ctx, $msg,)
10    };
11    ($ctx:expr, $msg:expr, $($args:expr),* $(,)?) => {{
12        let formatted = format!($msg, $($args),*);
13        let full = format!("{file}:{line}: {msg}",
14                           file = file!(),
15                           line = line!(),
16                           msg = &formatted);
17        $ctx.emit_event($crate::EventType::Info(full));
18    }};
19}
20
21pub(crate) use info;
22
23// Workaround for <https://github.com/rust-lang/rust/issues/133708>.
24#[macro_use]
25mod warn_macro_mod {
26    macro_rules! warn_macro {
27        ($ctx:expr, $msg:expr) => {
28            warn_macro!($ctx, $msg,)
29        };
30        ($ctx:expr, $msg:expr, $($args:expr),* $(,)?) => {{
31            let formatted = format!($msg, $($args),*);
32            let full = format!("{file}:{line}: {msg}",
33                               file = file!(),
34                               line = line!(),
35                               msg = &formatted);
36            $ctx.emit_event($crate::EventType::Warning(full));
37        }};
38    }
39
40    pub(crate) use warn_macro;
41}
42
43pub(crate) use warn_macro_mod::warn_macro as warn;
44
45macro_rules! error {
46    ($ctx:expr, $msg:expr) => {
47        error!($ctx, $msg,)
48    };
49    ($ctx:expr, $msg:expr, $($args:expr),* $(,)?) => {{
50        let formatted = format!($msg, $($args),*);
51        $ctx.set_last_error(&formatted);
52        $ctx.emit_event($crate::EventType::Error(formatted));
53    }};
54}
55
56pub(crate) use error;
57
58impl Context {
59    /// Set last error string.
60    /// Implemented as blocking as used from macros in different, not always async blocks.
61    pub fn set_last_error(&self, error: &str) {
62        let mut last_error = self.last_error.write();
63        *last_error = error.to_string();
64    }
65
66    /// Get last error string.
67    pub fn get_last_error(&self) -> String {
68        let last_error = &*self.last_error.read();
69        last_error.clone()
70    }
71
72    pub fn set_migration_error(&self, error: &str) {
73        let mut migration_error = self.migration_error.write();
74        *migration_error = Some(error.to_string());
75    }
76
77    pub fn get_migration_error(&self) -> Option<String> {
78        let migration_error = &*self.migration_error.read();
79        migration_error.clone()
80    }
81}
82
83pub trait LogExt<T, E>
84where
85    Self: std::marker::Sized,
86{
87    /// Emits a warning if the receiver contains an Err value.
88    ///
89    /// Thanks to the [track_caller](https://blog.rust-lang.org/2020/08/27/Rust-1.46.0.html#track_caller)
90    /// feature, the location of the caller is printed to the log, just like with the warn!() macro.
91    ///
92    /// Unfortunately, the track_caller feature does not work on async functions (as of Rust 1.50).
93    /// Once it is, you can add `#[track_caller]` to helper functions that use one of the log helpers here
94    /// so that the location of the caller can be seen in the log. (this won't work with the macros,
95    /// like warn!(), since the file!() and line!() macros don't work with track_caller)
96    /// See <https://github.com/rust-lang/rust/issues/78840> for progress on this.
97    #[track_caller]
98    fn log_err(self, context: &Context) -> Result<T, E>;
99}
100
101impl<T, E: std::fmt::Display> LogExt<T, E> for Result<T, E> {
102    #[track_caller]
103    fn log_err(self, context: &Context) -> Result<T, E> {
104        if let Err(e) = &self {
105            let location = std::panic::Location::caller();
106
107            // We are using Anyhow's .context() and to show the inner error, too, we need the {:#}:
108            let full = format!(
109                "{file}:{line}: {e:#}",
110                file = location.file(),
111                line = location.line(),
112                e = e
113            );
114            // We can't use the warn!() macro here as the file!() and line!() macros
115            // don't work with #[track_caller]
116            context.emit_event(crate::EventType::Warning(full));
117        };
118        self
119    }
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125
126    use anyhow::Result;
127
128    use crate::test_utils::TestContext;
129
130    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
131    async fn test_get_last_error() -> Result<()> {
132        let t = TestContext::new().await;
133
134        assert_eq!(t.get_last_error(), "");
135
136        error!(t, "foo-error");
137        assert_eq!(t.get_last_error(), "foo-error");
138
139        warn!(t, "foo-warning");
140        assert_eq!(t.get_last_error(), "foo-error");
141
142        info!(t, "foo-info");
143        assert_eq!(t.get_last_error(), "foo-error");
144
145        error!(t, "bar-error");
146        error!(t, "baz-error");
147        assert_eq!(t.get_last_error(), "baz-error");
148
149        Ok(())
150    }
151}