//! Forward log messages to logging webxdc use crate::chat::ChatId; use crate::config::Config; use crate::context::Context; use crate::events::EventType; use crate::message::{Message, MsgId, Viewtype}; use crate::param::Param; use crate::tools::time; use crate::webxdc::StatusUpdateItem; use async_channel::{self as channel, Receiver, Sender}; use serde_json::json; use tokio::task; #[derive(Debug)] pub(crate) struct DebugLogging { /// The message containing the logging xdc pub(crate) msg_id: MsgId, /// Handle to the background task responsible for sending pub(crate) loop_handle: task::JoinHandle<()>, /// Channel that log events should be sent to. /// A background loop will receive and handle them. pub(crate) sender: Sender, } impl DebugLogging { pub(crate) fn log_event(&self, event: EventType) { let event_data = DebugEventLogData { time: time(), msg_id: self.msg_id, event, }; self.sender.try_send(event_data).ok(); } } /// Store all information needed to log an event to a webxdc. pub struct DebugEventLogData { pub time: i64, pub msg_id: MsgId, pub event: EventType, } /// Creates a loop which forwards all log messages send into the channel to the associated /// logging xdc. pub async fn debug_logging_loop(context: &Context, events: Receiver) { while let Ok(DebugEventLogData { time, msg_id, event, }) = events.recv().await { match context .write_status_update_inner( &msg_id, &StatusUpdateItem { payload: json!({ "event": event, "time": time, }), info: None, href: None, summary: None, document: None, uid: None, notify: None, }, time, ) .await { Err(err) => { eprintln!("Can't log event to webxdc status update: {err:#}"); } Ok(serial) => { if let Some(serial) = serial { if !matches!(event, EventType::WebxdcStatusUpdate { .. }) { context.emit_event(EventType::WebxdcStatusUpdate { msg_id, status_update_serial: serial, }); } } else { // This should not happen as the update has no `uid`. error!(context, "Debug logging update is not created."); }; } } } } /// Set message as new logging webxdc if filename and chat_id fit pub async fn maybe_set_logging_xdc( context: &Context, msg: &Message, chat_id: ChatId, ) -> anyhow::Result<()> { maybe_set_logging_xdc_inner( context, msg.get_viewtype(), chat_id, msg.param.get(Param::Filename), msg.get_id(), ) .await?; Ok(()) } /// Set message as new logging webxdc if filename and chat_id fit pub async fn maybe_set_logging_xdc_inner( context: &Context, viewtype: Viewtype, chat_id: ChatId, filename: Option<&str>, msg_id: MsgId, ) -> anyhow::Result<()> { if viewtype == Viewtype::Webxdc && let Some(filename) = filename && filename.starts_with("debug_logging") && filename.ends_with(".xdc") && chat_id.is_self_talk(context).await? { set_debug_logging_xdc(context, Some(msg_id)).await?; } Ok(()) } /// Set the webxdc contained in the msg as the current logging xdc on the context and save it to db /// If id is a `None` value, disable debug logging pub(crate) async fn set_debug_logging_xdc(ctx: &Context, id: Option) -> anyhow::Result<()> { match id { Some(msg_id) => { ctx.sql .set_raw_config( Config::DebugLogging.as_ref(), Some(msg_id.to_string().as_ref()), ) .await?; { let debug_logging = &mut *ctx.debug_logging.write().expect("RwLock is poisoned"); match debug_logging { // Switch logging xdc Some(debug_logging) => debug_logging.msg_id = msg_id, // Bootstrap background loop for message forwarding None => { let (sender, debug_logging_recv) = channel::bounded(1000); let loop_handle = { let ctx = ctx.clone(); task::spawn(async move { debug_logging_loop(&ctx, debug_logging_recv).await }) }; *debug_logging = Some(DebugLogging { msg_id, loop_handle, sender, }); } } } info!(ctx, "replacing logging webxdc"); } // Delete current debug logging None => { ctx.sql .set_raw_config(Config::DebugLogging.as_ref(), None) .await?; *ctx.debug_logging.write().expect("RwLock is poisoned") = None; info!(ctx, "removing logging webxdc"); } } Ok(()) }