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        if let Some(filename) = filename {
120            if 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        }
127    }
128    Ok(())
129}
130
131/// Set the webxdc contained in the msg as the current logging xdc on the context and save it to db
132/// If id is a `None` value, disable debug logging
133pub(crate) async fn set_debug_logging_xdc(ctx: &Context, id: Option<MsgId>) -> anyhow::Result<()> {
134    match id {
135        Some(msg_id) => {
136            ctx.sql
137                .set_raw_config(
138                    Config::DebugLogging.as_ref(),
139                    Some(msg_id.to_string().as_ref()),
140                )
141                .await?;
142            {
143                let debug_logging = &mut *ctx.debug_logging.write().expect("RwLock is poisoned");
144                match debug_logging {
145                    // Switch logging xdc
146                    Some(debug_logging) => debug_logging.msg_id = msg_id,
147                    // Bootstrap background loop for message forwarding
148                    None => {
149                        let (sender, debug_logging_recv) = channel::bounded(1000);
150                        let loop_handle = {
151                            let ctx = ctx.clone();
152                            task::spawn(async move {
153                                debug_logging_loop(&ctx, debug_logging_recv).await
154                            })
155                        };
156                        *debug_logging = Some(DebugLogging {
157                            msg_id,
158                            loop_handle,
159                            sender,
160                        });
161                    }
162                }
163            }
164            info!(ctx, "replacing logging webxdc");
165        }
166        // Delete current debug logging
167        None => {
168            ctx.sql
169                .set_raw_config(Config::DebugLogging.as_ref(), None)
170                .await?;
171            *ctx.debug_logging.write().expect("RwLock is poisoned") = None;
172            info!(ctx, "removing logging webxdc");
173        }
174    }
175    Ok(())
176}