pub use log; use std::{cell::RefCell, rc::Rc}; const LOG_MESSAGES_BYTES_LIMIT: usize = 10 * 1000; pub struct LogCollector { messages: Vec, bytes_written: usize, bytes_limit: Option, limit_warning: bool, } impl Default for LogCollector { fn default() -> Self { Self { messages: Vec::new(), bytes_written: 0, bytes_limit: Some(LOG_MESSAGES_BYTES_LIMIT), limit_warning: false, } } } impl LogCollector { pub fn log(&mut self, message: &str) { let limit = match self.bytes_limit { Some(limit) => limit, None => { self.messages.push(message.to_string()); return; } }; let bytes_written = self.bytes_written.saturating_add(message.len()); if bytes_written >= limit { if !self.limit_warning { self.limit_warning = true; self.messages.push(String::from("Log truncated")); } } else { self.bytes_written = bytes_written; self.messages.push(message.to_string()); } } pub fn get_recorded_content(&self) -> &[String] { self.messages.as_slice() } pub fn new_ref() -> Rc> { Rc::new(RefCell::new(Self::default())) } pub fn new_ref_with_limit(bytes_limit: Option) -> Rc> { Rc::new(RefCell::new(Self { bytes_limit, ..Self::default() })) } } impl From for Vec { fn from(log_collector: LogCollector) -> Self { log_collector.messages } } /// Convenience macro to log a message with an `Option>>` #[macro_export] macro_rules! ic_logger_msg { ($log_collector:expr, $message:expr) => { $crate::log_collector::log::debug!( target: "solana_runtime::message_processor::stable_log", "{}", $message ); if let Some(log_collector) = $log_collector.as_ref() { if let Ok(mut log_collector) = log_collector.try_borrow_mut() { log_collector.log($message); } } }; ($log_collector:expr, $fmt:expr, $($arg:tt)*) => { $crate::log_collector::log::debug!( target: "solana_runtime::message_processor::stable_log", $fmt, $($arg)* ); if let Some(log_collector) = $log_collector.as_ref() { if let Ok(mut log_collector) = log_collector.try_borrow_mut() { log_collector.log(&format!($fmt, $($arg)*)); } } }; } /// Convenience macro to log a message with an `InvokeContext` #[macro_export] macro_rules! ic_msg { ($invoke_context:expr, $message:expr) => { $crate::ic_logger_msg!($invoke_context.get_log_collector(), $message) }; ($invoke_context:expr, $fmt:expr, $($arg:tt)*) => { $crate::ic_logger_msg!($invoke_context.get_log_collector(), $fmt, $($arg)*) }; } #[cfg(test)] pub(crate) mod tests { use super::*; #[test] fn test_log_messages_bytes_limit() { let mut lc = LogCollector::default(); for _i in 0..LOG_MESSAGES_BYTES_LIMIT * 2 { lc.log("x"); } let logs: Vec<_> = lc.into(); assert_eq!(logs.len(), LOG_MESSAGES_BYTES_LIMIT); for log in logs.iter().take(LOG_MESSAGES_BYTES_LIMIT - 1) { assert_eq!(*log, "x".to_string()); } assert_eq!(logs.last(), Some(&"Log truncated".to_string())); } }