diff options
author | Ashelyn Rose <git@ashen.earth> | 2025-03-01 14:03:56 -0700 |
---|---|---|
committer | Ashelyn Rose <git@ashen.earth> | 2025-03-01 14:03:56 -0700 |
commit | 89bf5a8b4f85583795b9211eaca485d6fc633389 (patch) | |
tree | 18375298e6906f0644b9ed5b4d9c974d8e609cfe | |
parent | 99e5f7e3ff51aebc6796d1b7cf852367eb35d8d5 (diff) |
Refactor logging
-rw-r--r-- | src/main.rs | 4 | ||||
-rw-r--r-- | src/system/aggregator.rs | 34 | ||||
-rw-r--r-- | src/system/log.rs | 57 | ||||
-rw-r--r-- | src/system/mod.rs | 29 | ||||
-rw-r--r-- | src/system/plugin.rs | 8 | ||||
-rw-r--r-- | src/system/plugin/autoproxy.rs | 16 | ||||
-rw-r--r-- | src/system/plugin/prefixes.rs | 16 |
7 files changed, 129 insertions, 35 deletions
diff --git a/src/main.rs b/src/main.rs index c795e1c..98d4754 100644 --- a/src/main.rs +++ b/src/main.rs @@ -63,8 +63,8 @@ fn main() { join_handles.push((system_name.clone(), handle)); } - // crossterm::execute!(io::stdout(), EnterAlternateScreen).unwrap(); - // crossterm::execute!(io::stdout(), DisableLineWrap).unwrap(); + crossterm::execute!(io::stdout(), EnterAlternateScreen).unwrap(); + crossterm::execute!(io::stdout(), DisableLineWrap).unwrap(); loop { // Wait for an event from one of the threads diff --git a/src/system/aggregator.rs b/src/system/aggregator.rs index 822c698..552ea8e 100644 --- a/src/system/aggregator.rs +++ b/src/system/aggregator.rs @@ -13,26 +13,44 @@ enum IncomingMessage { Partial {message: MessageUpdate, timestamp: Timestamp, seen_by: DiscordToken}, } +pub enum MemberEvent { + Message(Message, DiscordToken), + GatewayConnect(DiscordToken), + GatewayDisconnect(DiscordToken), + GatewayError(DiscordToken), +} + impl MessageAggregator { - pub fn start(system: &System) -> Receiver<(Message, DiscordToken)> { + pub fn start(system: &System) -> Receiver<MemberEvent> { let incoming_channel = channel::<IncomingMessage>(32); - let outgoing_channel = channel::<(Message, DiscordToken)>(32); + let outgoing_channel = channel::<MemberEvent>(32); // Start incoming task for each member for member in system.members.iter() { let followed_user = system.followed_user.clone(); let sender = incoming_channel.0.clone(); let member = member.clone(); + let outgoing_channel = outgoing_channel.0.clone(); tokio::spawn(async move { loop { let next_event = {member.shard.lock().await.next_event().await}; match next_event { - Err(err) => println!("Error"), + Err(source) => { + if source.is_fatal() { + let _ = outgoing_channel.send(MemberEvent::GatewayDisconnect(member.discord_token.clone())).await; + return + } else { + let _ = outgoing_channel.send(MemberEvent::GatewayError(member.discord_token.clone())).await; + } + }, + Ok(twilight_gateway::Event::Ready(ready)) => { + let _ = outgoing_channel.send(MemberEvent::GatewayConnect(member.discord_token.clone())).await; + }, Ok(twilight_gateway::Event::MessageCreate(message)) => { if message.author.id == followed_user { - sender.send(IncomingMessage::Complete { + let _ = sender.send(IncomingMessage::Complete { message: message.0.clone(), timestamp: message.timestamp.clone(), seen_by: member.discord_token.clone() @@ -41,7 +59,7 @@ impl MessageAggregator { }, Ok(twilight_gateway::Event::MessageUpdate(message)) => { if message.author.is_some() && {message.author.as_ref().unwrap().id == followed_user} { - sender.send(IncomingMessage::Partial { + let _ = sender.send(IncomingMessage::Partial { message: (*message).clone(), timestamp: message.edited_timestamp.unwrap_or(message.timestamp.expect("No message timestamp")), seen_by: member.discord_token.clone() @@ -66,7 +84,7 @@ impl MessageAggregator { Some(IncomingMessage::Complete { message, timestamp, seen_by }) => { if let None = message_cache.get(&message.id) { message_cache.put(message.id.clone(), (message.clone(), timestamp, seen_by.clone())); - sender.send((message, seen_by)).await; + sender.send(MemberEvent::Message(message, seen_by)).await; } }, @@ -76,13 +94,13 @@ impl MessageAggregator { let mut updated_message = previous_message.clone(); updated_message.content = message.content.unwrap_or(updated_message.content); message_cache.put(message.id.clone(), (updated_message.clone(), timestamp, seen_by.clone())); - sender.send((updated_message, seen_by)).await; + sender.send(MemberEvent::Message(updated_message, seen_by)).await; } } else { let client = system.members.iter().find(|m| m.discord_token == seen_by).map(|m| m.client.clone()).expect("Could not find client"); if let Ok(updated_message) = client.lock().await.message(message.channel_id, message.id).await.unwrap().model().await.map(|r|r.clone()) { message_cache.put(message.id.clone(), (updated_message.clone(), timestamp, seen_by.clone())); - sender.send((updated_message, seen_by)).await; + sender.send(MemberEvent::Message(updated_message, seen_by)).await; }; } diff --git a/src/system/log.rs b/src/system/log.rs new file mode 100644 index 0000000..c80963f --- /dev/null +++ b/src/system/log.rs @@ -0,0 +1,57 @@ +use crate::SystemUiEvent; +use std::{collections::HashMap, sync::{mpsc::Sender, Arc}}; + +#[derive(Clone)] +pub struct Logger { + system_name: String, + ui_sender : Sender<(String, SystemUiEvent)>, + name_lookup: Arc<HashMap<String, String>>, +} + +impl Logger { + pub fn new(system_name: String, system_config: crate::config::System, ui_sender: Sender<(String, SystemUiEvent)>) -> Self { + Self { + system_name, + ui_sender, + name_lookup: Arc::new(system_config.members.iter().map(|member| (member.discord_token.clone(), member.name.clone())).collect::<HashMap<_,_>>()), + } + } + + pub async fn log_connect(&self, member_token: String) { + let member_name = self.name_lookup.get(&member_token).expect("Member not found").clone(); + let _ = self.ui_sender.send((self.system_name.clone(), SystemUiEvent::GatewayConnect(member_name))); + + } + + pub async fn log_disconnect(&self, member_token: String) { + let member_name = self.name_lookup.get(&member_token).expect("Member not found").clone(); + let _ = self.ui_sender.send((self.system_name.clone(), SystemUiEvent::GatewayDisconnect(member_name))); + } + + pub async fn log_err(&self, member_token: Option<String>, message: String) { + let member = member_token.map(|token| self.name_lookup.get(&token).cloned()).flatten(); + + let _ = self.ui_sender.send(( + self.system_name.clone(), + SystemUiEvent::LogLine(if let Some(member_name) = member { + format!("{member_name}: Error: {message}") + } else { + format!("{message}") + }) + )); + } + + pub async fn log_line(&self, member_token: Option<String>, message: String) { + let member = member_token.map(|token| self.name_lookup.get(&token).cloned()).flatten(); + + let _ = self.ui_sender.send(( + self.system_name.clone(), + SystemUiEvent::LogLine(if let Some(member_name) = member { + format!("{member_name}: {message}") + } else { + format!("{message}") + }) + )); + } +} + diff --git a/src/system/mod.rs b/src/system/mod.rs index f5327e3..8e642ea 100644 --- a/src/system/mod.rs +++ b/src/system/mod.rs @@ -2,10 +2,12 @@ mod aggregator; mod types; mod plugin; mod util; +mod log; +use aggregator::MemberEvent; +use log::Logger; use twilight_gateway::{Intents, Shard, ShardId}; use twilight_http::Client; -use twilight_model::channel::Message; pub use types::SystemThreadCommand; use crate::SystemUiEvent; use std::{num::NonZeroU64, sync::Arc}; @@ -35,6 +37,7 @@ impl Manager { }; let mut message_receiver = aggregator::MessageAggregator::start(&system); + let logger = Logger::new(system_name.clone(), system_config.clone(), ui_sender.clone()); let mut plugins : Vec<Box<dyn SeancePlugin>> = vec![ Box::new(plugin::ProxyPrefixes), @@ -44,15 +47,24 @@ impl Manager { loop { match message_receiver.recv().await { None => (), - Some((message, seen_by)) => { - println!("Checking message: {}", message.content.clone()); + Some(MemberEvent::GatewayConnect(member_token)) => { + logger.log_connect(member_token).await; + }, + Some(MemberEvent::GatewayError(member_token)) => { + logger.log_err(Some(member_token), "Non-fatal gateway error".to_string()).await; + }, + Some(MemberEvent::GatewayDisconnect(member_token)) => { + logger.log_disconnect(member_token).await; + }, + Some(MemberEvent::Message(message, seen_by)) => { if message.content.starts_with(&system.command_prefix) { + logger.log_line(None, format!("Handling command: {}", message.content)).await; for plugin in &mut plugins { - match plugin.handle_command(&system, &message).await { + match plugin.handle_command(&logger, &system, &message).await { plugin::CommandOutcome::Skipped => continue, plugin::CommandOutcome::Handled => break, plugin::CommandOutcome::Errored {message} => { - println!("Error: {message}"); + logger.log_err(None, message).await; break }, } @@ -60,13 +72,12 @@ impl Manager { } else { let mut message_response = Response::Noop { delete_source: false }; for plugin in &plugins { - plugin.handle_message(&system, &message, &mut message_response).await; + plugin.handle_message(&logger, &system, &message, &mut message_response).await; } match message_response.clone() { Response::Noop { delete_source } => { if delete_source { - println!("Deleting source message"); let client = system.members.iter().find(|m| m.discord_token == seen_by).map(|m| m.client.clone()) .expect("No such client"); @@ -77,12 +88,12 @@ impl Manager { Response::Proxy { member, content } => { if let Ok(new_message) = util::duplicate_message(&member.client, &message, content.as_str()).await { if let Err(err) = {member.client.lock().await.delete_message(message.channel_id, message.id).await.map(|_| ()).map_err(|err| err.to_string()).clone() } { - println!("Error proxying message: {err}"); + logger.log_err(Some(member.discord_token), format!("Could not proxy message: {err}")).await; {let _ = member.client.lock().await.delete_message(new_message.channel_id, new_message.id).await;} } for plugin in &plugins { - plugin.post_response(&system, &new_message, message.channel_id, &message_response).await; + plugin.post_response(&logger, &system, &new_message, message.channel_id, &message_response).await; } } }, diff --git a/src/system/plugin.rs b/src/system/plugin.rs index c458fd4..d3c6764 100644 --- a/src/system/plugin.rs +++ b/src/system/plugin.rs @@ -4,6 +4,8 @@ mod prefixes; use async_trait::async_trait; use twilight_model::{channel::{Channel, Message}, id::{marker::ChannelMarker, Id}}; + +use super::log::Logger; use crate::system::types::{System, Response}; pub use prefixes::ProxyPrefixes; @@ -11,11 +13,11 @@ pub use autoproxy::Autoproxy; #[async_trait] pub trait SeancePlugin { - async fn handle_command(&self, system: &System, message: &Message) -> CommandOutcome; + async fn handle_command(&self, logger: &Logger, system: &System, message: &Message) -> CommandOutcome; - async fn handle_message(&self, system: &System, message: &Message, response: &mut Response); + async fn handle_message(&self, logger: &Logger, system: &System, message: &Message, response: &mut Response); - async fn post_response(&self, system: &System, message: &Message, channel: Id<ChannelMarker>, response: &Response); + async fn post_response(&self, logger: &Logger, system: &System, message: &Message, channel: Id<ChannelMarker>, response: &Response); } pub enum CommandOutcome { diff --git a/src/system/plugin/autoproxy.rs b/src/system/plugin/autoproxy.rs index cd24e0b..bba25a5 100644 --- a/src/system/plugin/autoproxy.rs +++ b/src/system/plugin/autoproxy.rs @@ -6,6 +6,7 @@ use crate::system::types::{System, Member, Response}; use super::{CommandOutcome, SeancePlugin}; use tokio::time::sleep; use std::time::Duration; +use super::Logger; pub struct Autoproxy { current_state: Arc<Mutex<InnerState>>, @@ -29,7 +30,7 @@ impl Autoproxy { #[async_trait] impl SeancePlugin for Autoproxy { - async fn handle_command(&self, system: &System, message: &Message) -> CommandOutcome { + async fn handle_command(&self, logger: &Logger, system: &System, message: &Message) -> CommandOutcome { if message.content.starts_with(format!("{}auto ", system.command_prefix).as_str()) { let args = message.content.replace(format!("{}auto ", system.command_prefix).as_str(), ""); let mut remainder = args.split_whitespace(); @@ -51,7 +52,7 @@ impl SeancePlugin for Autoproxy { } } - async fn handle_message(&self, system: &System, message: &Message, response: &mut Response) { + async fn handle_message(&self, logger: &Logger, system: &System, message: &Message, response: &mut Response) { if message.content.starts_with("\\") { if message.content.starts_with("\\\\") { if let InnerState::LatchActive {current_member: _, last_message: _} = {self.current_state.lock().await.clone()} { @@ -69,12 +70,14 @@ impl SeancePlugin for Autoproxy { InnerState::Off => return, InnerState::LatchInactive => return, InnerState::Member { current_member } => { + logger.log_line(Some(current_member.discord_token.clone()), "Proxying via member mode autoproxy".to_string()).await; *response = Response::Proxy { member: current_member.clone(), content: message.content.clone(), } }, InnerState::LatchActive { current_member, last_message: _ } => { + logger.log_line(Some(current_member.discord_token.clone()), "Proxying via autoproxy latch".to_string()).await; *response = Response::Proxy { member: current_member.clone(), content: message.content.clone(), @@ -85,7 +88,7 @@ impl SeancePlugin for Autoproxy { } } - async fn post_response(&self, system: &System, message: &Message, channel: Id<ChannelMarker>, response: &Response) { + async fn post_response(&self, logger: &Logger, system: &System, message: &Message, channel: Id<ChannelMarker>, response: &Response) { match response { Response::Noop { delete_source } => return, Response::Proxy { member, content } => { @@ -94,6 +97,7 @@ impl SeancePlugin for Autoproxy { InnerState::Off => return, InnerState::Member { current_member } => return, InnerState::LatchInactive => { + logger.log_line(Some(member.discord_token.clone()), "Setting autoproxy latch".to_string()).await; {*self.current_state.lock().await = InnerState::LatchActive { current_member: member.clone(), last_message: message.timestamp, @@ -115,6 +119,7 @@ impl SeancePlugin for Autoproxy { }); }, InnerState::LatchActive { current_member: _, last_message: _ } => { + logger.log_line(Some(member.discord_token.clone()), "Setting autoproxy latch".to_string()).await; {*self.current_state.lock().await = InnerState::LatchActive { current_member: member.clone(), last_message: message.timestamp, @@ -123,14 +128,19 @@ impl SeancePlugin for Autoproxy { let state_arc = self.current_state.clone(); let sent_member = member.clone(); let sent_timestamp = message.timestamp.clone(); + let logger = logger.clone(); tokio::spawn(async move { sleep(Duration::from_secs(15 * 60)).await; let current_state = {state_arc.lock().await.clone()}; + logger.log_line(Some(sent_member.discord_token.clone()), "Latch timeout".to_string()).await; if let InnerState::LatchActive { current_member, last_message } = current_state { if sent_member.discord_token == current_member.discord_token && sent_timestamp.as_micros() == last_message.as_micros() { + logger.log_line(Some(sent_member.discord_token.clone()), "Latch expired".to_string()).await; {*state_arc.lock().await = InnerState::LatchInactive}; + } else { + logger.log_line(Some(sent_member.discord_token.clone()), "Timeout no longer valid".to_string()).await; } } }); diff --git a/src/system/plugin/prefixes.rs b/src/system/plugin/prefixes.rs index a513573..609dafd 100644 --- a/src/system/plugin/prefixes.rs +++ b/src/system/plugin/prefixes.rs @@ -1,6 +1,6 @@ use async_trait::async_trait; use twilight_model::id::{marker::ChannelMarker, Id}; -use crate::system::types::Response; +use crate::system::{log::Logger, types::Response}; use super::{CommandOutcome, SeancePlugin}; @@ -8,23 +8,19 @@ pub struct ProxyPrefixes; #[async_trait] impl SeancePlugin for ProxyPrefixes { - async fn handle_command(&self, _system: &crate::system::types::System, _message: &twilight_model::channel::Message) -> CommandOutcome { + async fn handle_command(&self, _logger: &Logger, _system: &crate::system::types::System, _message: &twilight_model::channel::Message) -> CommandOutcome { CommandOutcome::Skipped } - async fn handle_message(&self, system: &crate::system::types::System, message: &twilight_model::channel::Message, response: &mut crate::system::types::Response) { + async fn handle_message(&self, logger: &Logger, system: &crate::system::types::System, message: &twilight_model::channel::Message, response: &mut crate::system::types::Response) { if let Response::Noop { delete_source: _ } = response { for member in &system.members { - println!("Checking member prefix: {:?}", member.message_pattern); match member.message_pattern.captures(message.content.as_str()) { - None => { - println!("Nope"); - continue; - }, + None => continue, Some(captures) => match captures.name("content") { None => continue, Some(matched_content) => { - println!("Matched member prefix: {:?}", member.message_pattern); + logger.log_line(Some(member.discord_token.clone()), "Matched prefix".to_string()).await; *response = Response::Proxy { member: member.clone(), content: matched_content.as_str().to_string() }; return }, @@ -34,7 +30,7 @@ impl SeancePlugin for ProxyPrefixes { } } - async fn post_response(&self, _system: &crate::system::types::System, _message: &twilight_model::channel::Message, _channel: Id<ChannelMarker>, _response: &crate::system::types::Response) { + async fn post_response(&self, _logger: &Logger, _system: &crate::system::types::System, _message: &twilight_model::channel::Message, _channel: Id<ChannelMarker>, _response: &crate::system::types::Response) { return } } |