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