use std::{ fs::{self, OpenOptions}, io::Write, sync::{OnceLock, mpsc}, thread, time::{SystemTime, UNIX_EPOCH}, }; use ansi_term::Color; use mtp::codec::{CommunicationType, CommunicationValue, DataTypeId, DataValue, Version}; use crate::rho::connection::MtpValueCompat; static LOGGER: OnceLock> = OnceLock::new(); #[allow(dead_code)] #[derive(Clone, Copy)] pub enum PrintType { Call, Client, App, Iota, Omikron, Omega, General, } struct LogMessage { timestamp_ms: u128, sender: Option, prefix: &'static str, kind: PrintType, is_error: bool, message: String, } pub fn startup() { let (tx, rx) = mpsc::channel::(); LOGGER.set(tx).expect("Logger already initialized"); thread::spawn(move || { let log_dir = crate::WORKING_DIR.join("logs"); fs::create_dir_all(&log_dir).expect("Failed to create log directory"); let start_ts = SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap() .as_secs(); let path = log_dir.join(format!("log_{}.txt", start_ts)); let mut file = OpenOptions::new() .create(true) .append(true) .open(path) .expect("Failed to open log file"); for msg in rx { let ts = fixed_box(&msg.timestamp_ms.to_string(), 13); let sender = match msg.sender { Some(id) => fixed_box(&id.to_string(), 19), _ => fixed_box("", 19), }; let line = format!("{} {} {} {}", ts, sender, msg.prefix, msg.message); println!("{}", colorize(msg.kind, msg.is_error).paint(&line)); let _ = writeln!(file, "{}", line); } }); } fn colorize(kind: PrintType, is_error: bool) -> Color { if is_error { return Color::Red; } match kind { PrintType::Call => Color::Purple, PrintType::Client => Color::Green, PrintType::App => Color::Green, PrintType::Iota => Color::Yellow, PrintType::Omikron => Color::Blue, PrintType::Omega => Color::Cyan, PrintType::General => Color::White, } } fn fixed_box(content: &str, width: usize) -> String { let s: String = content.chars().take(width).collect(); let len = s.chars().count(); if len < width { format!("[{}{}]", " ".repeat(width - len), s) } else { s } } pub fn log_internal( sender: i64, kind: PrintType, prefix: &'static str, is_error: bool, message: String, ) { let sender = if sender == 0 { None } else { Some(sender) }; if let Some(tx) = LOGGER.get() { let _ = tx.send(LogMessage { timestamp_ms: SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap() .as_millis(), sender, prefix, kind, is_error, message, }); } } #[macro_export] macro_rules! log { ($sender: expr, $kind:expr, $($arg:tt)*) => { $crate::util::logger::log_internal($sender, $kind, "", false, format!($($arg)*)) }; } #[macro_export] macro_rules! log_in { ($sender: expr, $kind:expr, $($arg:tt)*) => { $crate::util::logger::log_internal($sender, $kind, ">", false, format!($($arg)*)) }; } #[macro_export] macro_rules! log_out { ($sender:expr, $kind:expr, $($arg:tt)*) => { $crate::util::logger::log_internal($sender, $kind, "<", false, format!($($arg)*)) }; } #[macro_export] macro_rules! log_err { ($sender:expr, $kind:expr, $($arg:tt)*) => { $crate::util::logger::log_internal($sender, $kind, ">>", true, format!($($arg)*)) }; } // ******** COMMUNICATION VALUES ******** pub fn log_cv_internal( prefix: &'static str, cv: &CommunicationValue, print_type: Option, ) { let formatted = format_cv(cv); log_internal( cv.get_sender() as i64, print_type.unwrap_or(PrintType::General), prefix, false, formatted, ); } pub fn format_cv(cv: &CommunicationValue) -> String { let mut parts = Vec::new(); let sender = cv.get_sender(); let receiver = cv.get_receiver(); if sender > 0 && receiver > 0 { parts.push(format!("{} > {}", sender, receiver)); } else if sender > 0 { parts.push(format!("{}", sender)); } else if receiver > 0 { parts.push(format!("> {}", receiver)); } let comm_type = cv .get_comm_type_enum() .map(|kind| kind.to_string()) .unwrap_or_else(|| cv.get_type().to_string()); parts.push(format!("{} (id={})", comm_type, cv.get_id())); if cv.is_type(CommunicationType::Relay) { parts.push("".to_string()); return parts.join(": "); } let data = cv.data().unwrap_or(&[]); let formated_data = format_data_container( data.iter().map(|(k, v)| (*k, v.clone())).collect(), Version(3, 0), ); parts.push(format!("{}", formated_data)); parts.join(": ") } fn format_data_container(data: Vec<(DataTypeId, DataValue)>, version: Version) -> String { let parts: Vec = data .into_iter() .map(|(key, value)| { let key_str = key.to_string(); match value { DataValue::Str(s) => format!("{}=\"{}\"", key_str, s), DataValue::Container(inner) => { let inner_formatted = format_data_container(inner, version.clone()); format!("{}={{ {} }}", key_str, inner_formatted) } DataValue::Array(arr) => { let arr_formatted = format_array(arr, version.clone()); format!("{}=[{}]", key_str, arr_formatted) } DataValue::Bool(b) => format!("{}={}", key_str, b), DataValue::BoolTrue => format!("{}=true", key_str), DataValue::BoolFalse => format!("{}=false", key_str), DataValue::SignedNumber(num) => format!("{}={}", key_str, num), DataValue::UnsignedNumber(num) => format!("{}={}", key_str, num), DataValue::Bytes(bytes) => format!("{}=<{} bytes>", key_str, bytes.len()), _ => "".to_string(), } }) .collect(); parts.join(", ") } fn format_array(arr: Vec, version: Version) -> String { let parts: Vec = arr .into_iter() .map(|value| match value { DataValue::Str(s) => format!("\"{}\"", s), DataValue::Container(inner) => { let inner_formatted = format_data_container(inner, version.clone()); format!("{{ {} }}", inner_formatted) } DataValue::Array(inner_arr) => { let formatted = format_array(inner_arr, version.clone()); format!("[{}]", formatted) } DataValue::Bool(b) => b.to_string(), DataValue::BoolTrue => "true".to_string(), DataValue::BoolFalse => "false".to_string(), DataValue::SignedNumber(num) => num.to_string(), DataValue::UnsignedNumber(num) => num.to_string(), DataValue::Bytes(bytes) => format!("<{} bytes>", bytes.len()), _ => String::new(), }) .collect(); parts.join(", ") } #[macro_export] macro_rules! log_cv { ($kind:expr, $cv:expr) => { $crate::util::logger::log_cv_internal("", &$cv, Some($kind)) }; ($cv:expr) => { $crate::util::logger::log_cv_internal("", &$cv, None) }; } #[macro_export] macro_rules! log_cv_in { ($kind:expr, $cv:expr) => { $crate::util::logger::log_cv_internal("> ", &$cv, Some($kind)) }; ($cv:expr) => { $crate::util::logger::log_cv_internal("> ", &$cv, None) }; } #[macro_export] macro_rules! log_cv_out { ($kind:expr, $cv:expr) => { $crate::util::logger::log_cv_internal("< ", &$cv, Some($kind)) }; ($cv:expr) => { $crate::util::logger::log_cv_internal("< ", &$cv, None) }; }