deltachat/
log.rs

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