deltachat/
log.rs

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