deltachat/
debug_logging.rs

1//! Forward log messages to logging webxdc
2use crate::chat::ChatId;
3use crate::config::Config;
4use crate::context::Context;
5use crate::events::EventType;
6use crate::message::{Message, MsgId, Viewtype};
7use crate::param::Param;
8use crate::tools::time;
9use crate::webxdc::StatusUpdateItem;
10use async_channel::{self as channel, Receiver, Sender};
11use serde_json::json;
12use tokio::task;
13
14#[derive(Debug)]
15pub(crate) struct DebugLogging {
16    /// The message containing the logging xdc
17    pub(crate) msg_id: MsgId,
18    /// Handle to the background task responsible for sending
19    pub(crate) loop_handle: task::JoinHandle<()>,
20    /// Channel that log events should be sent to.
21    /// A background loop will receive and handle them.
22    pub(crate) sender: Sender<DebugEventLogData>,
23}
24
25impl DebugLogging {
26    pub(crate) fn log_event(&self, event: EventType) {
27        let event_data = DebugEventLogData {
28            time: time(),
29            msg_id: self.msg_id,
30            event,
31        };
32
33        self.sender.try_send(event_data).ok();
34    }
35}
36
37/// Store all information needed to log an event to a webxdc.
38pub struct DebugEventLogData {
39    pub time: i64,
40    pub msg_id: MsgId,
41    pub event: EventType,
42}
43
44/// Creates a loop which forwards all log messages send into the channel to the associated
45/// logging xdc.
46pub async fn debug_logging_loop(context: &Context, events: Receiver<DebugEventLogData>) {
47    while let Ok(DebugEventLogData {
48        time,
49        msg_id,
50        event,
51    }) = events.recv().await
52    {
53        match context
54            .write_status_update_inner(
55                &msg_id,
56                &StatusUpdateItem {
57                    payload: json!({
58                        "event": event,
59                        "time": time,
60                    }),
61                    info: None,
62                    href: None,
63                    summary: None,
64                    document: None,
65                    uid: None,
66                    notify: None,
67                },
68                time,
69            )
70            .await
71        {
72            Err(err) => {
73                eprintln!("Can't log event to webxdc status update: {err:#}");
74            }
75            Ok(serial) => {
76                if let Some(serial) = serial {
77                    if !matches!(event, EventType::WebxdcStatusUpdate { .. }) {
78                        context.emit_event(EventType::WebxdcStatusUpdate {
79                            msg_id,
80                            status_update_serial: serial,
81                        });
82                    }
83                } else {
84                    // This should not happen as the update has no `uid`.
85                    error!(context, "Debug logging update is not created.");
86                };
87            }
88        }
89    }
90}
91
92/// Set message as new logging webxdc if filename and chat_id fit
93pub async fn maybe_set_logging_xdc(
94    context: &Context,
95    msg: &Message,
96    chat_id: ChatId,
97) -> anyhow::Result<()> {
98    maybe_set_logging_xdc_inner(
99        context,
100        msg.get_viewtype(),
101        chat_id,
102        msg.param.get(Param::Filename),
103        msg.get_id(),
104    )
105    .await?;
106
107    Ok(())
108}
109
110/// Set message as new logging webxdc if filename and chat_id fit
111pub async fn maybe_set_logging_xdc_inner(
112    context: &Context,
113    viewtype: Viewtype,
114    chat_id: ChatId,
115    filename: Option<&str>,
116    msg_id: MsgId,
117) -> anyhow::Result<()> {
118    if viewtype == Viewtype::Webxdc
119        && let Some(filename) = filename
120        && filename.starts_with("debug_logging")
121        && filename.ends_with(".xdc")
122        && chat_id.is_self_talk(context).await?
123    {
124        set_debug_logging_xdc(context, Some(msg_id)).await?;
125    }
126    Ok(())
127}
128
129/// Set the webxdc contained in the msg as the current logging xdc on the context and save it to db
130/// If id is a `None` value, disable debug logging
131pub(crate) async fn set_debug_logging_xdc(ctx: &Context, id: Option<MsgId>) -> anyhow::Result<()> {
132    match id {
133        Some(msg_id) => {
134            ctx.sql
135                .set_raw_config(
136                    Config::DebugLogging.as_ref(),
137                    Some(msg_id.to_string().as_ref()),
138                )
139                .await?;
140            {
141                let debug_logging = &mut *ctx.debug_logging.write().expect("RwLock is poisoned");
142                match debug_logging {
143                    // Switch logging xdc
144                    Some(debug_logging) => debug_logging.msg_id = msg_id,
145                    // Bootstrap background loop for message forwarding
146                    None => {
147                        let (sender, debug_logging_recv) = channel::bounded(1000);
148                        let loop_handle = {
149                            let ctx = ctx.clone();
150                            task::spawn(async move {
151                                debug_logging_loop(&ctx, debug_logging_recv).await
152                            })
153                        };
154                        *debug_logging = Some(DebugLogging {
155                            msg_id,
156                            loop_handle,
157                            sender,
158                        });
159                    }
160                }
161            }
162            info!(ctx, "replacing logging webxdc");
163        }
164        // Delete current debug logging
165        None => {
166            ctx.sql
167                .set_raw_config(Config::DebugLogging.as_ref(), None)
168                .await?;
169            *ctx.debug_logging.write().expect("RwLock is poisoned") = None;
170            info!(ctx, "removing logging webxdc");
171        }
172    }
173    Ok(())
174}