solana_svm_log_collector/
lib.rs

1#![cfg_attr(
2    not(feature = "agave-unstable-api"),
3    deprecated(
4        since = "3.1.0",
5        note = "This crate has been marked for formal inclusion in the Agave Unstable API. From \
6                v4.0.0 onward, the `agave-unstable-api` crate feature must be specified to \
7                acknowledge use of an interface that may break without warning."
8    )
9)]
10pub use log;
11use std::{cell::RefCell, rc::Rc};
12
13const LOG_MESSAGES_BYTES_LIMIT: usize = 10 * 1000;
14
15pub struct LogCollector {
16    pub messages: Vec<String>,
17    pub bytes_written: usize,
18    pub bytes_limit: Option<usize>,
19    pub limit_warning: bool,
20}
21
22impl Default for LogCollector {
23    fn default() -> Self {
24        Self {
25            messages: Vec::new(),
26            bytes_written: 0,
27            bytes_limit: Some(LOG_MESSAGES_BYTES_LIMIT),
28            limit_warning: false,
29        }
30    }
31}
32
33impl LogCollector {
34    pub fn log(&mut self, message: &str) {
35        let Some(limit) = self.bytes_limit else {
36            self.messages.push(message.to_string());
37            return;
38        };
39
40        let bytes_written = self.bytes_written.saturating_add(message.len());
41        if bytes_written >= limit {
42            if !self.limit_warning {
43                self.limit_warning = true;
44                self.messages.push(String::from("Log truncated"));
45            }
46        } else {
47            self.bytes_written = bytes_written;
48            self.messages.push(message.to_string());
49        }
50    }
51
52    pub fn get_recorded_content(&self) -> &[String] {
53        self.messages.as_slice()
54    }
55
56    pub fn new_ref() -> Rc<RefCell<Self>> {
57        Rc::new(RefCell::new(Self::default()))
58    }
59
60    pub fn new_ref_with_limit(bytes_limit: Option<usize>) -> Rc<RefCell<Self>> {
61        Rc::new(RefCell::new(Self {
62            bytes_limit,
63            ..Self::default()
64        }))
65    }
66
67    pub fn into_messages(self) -> Vec<String> {
68        self.messages
69    }
70}
71
72/// Convenience macro to log a message with an `Option<Rc<RefCell<LogCollector>>>`
73#[macro_export]
74macro_rules! ic_logger_msg {
75    ($log_collector:expr, $message:expr) => {
76        $crate::log::debug!(
77            target: "solana_runtime::message_processor::stable_log",
78            "{}",
79            $message
80        );
81        if let Some(log_collector) = $log_collector.as_ref() {
82            if let Ok(mut log_collector) = log_collector.try_borrow_mut() {
83                log_collector.log($message);
84            }
85        }
86    };
87    ($log_collector:expr, $fmt:expr, $($arg:tt)*) => {
88        $crate::log::debug!(
89            target: "solana_runtime::message_processor::stable_log",
90            $fmt,
91            $($arg)*
92        );
93        if let Some(log_collector) = $log_collector.as_ref() {
94            if let Ok(mut log_collector) = log_collector.try_borrow_mut() {
95                log_collector.log(&format!($fmt, $($arg)*));
96            }
97        }
98    };
99}
100
101/// Convenience macro to log a message with an `InvokeContext`
102#[macro_export]
103macro_rules! ic_msg {
104    ($invoke_context:expr, $message:expr) => {
105        $crate::ic_logger_msg!($invoke_context.get_log_collector(), $message)
106    };
107    ($invoke_context:expr, $fmt:expr, $($arg:tt)*) => {
108        $crate::ic_logger_msg!($invoke_context.get_log_collector(), $fmt, $($arg)*)
109    };
110}
111
112#[cfg(test)]
113pub(crate) mod tests {
114    use super::*;
115
116    #[test]
117    fn test_log_messages_bytes_limit() {
118        let mut lc = LogCollector::default();
119
120        for _i in 0..LOG_MESSAGES_BYTES_LIMIT * 2 {
121            lc.log("x");
122        }
123
124        let logs: Vec<_> = lc.into_messages();
125        assert_eq!(logs.len(), LOG_MESSAGES_BYTES_LIMIT);
126        for log in logs.iter().take(LOG_MESSAGES_BYTES_LIMIT - 1) {
127            assert_eq!(*log, "x".to_string());
128        }
129        assert_eq!(logs.last(), Some(&"Log truncated".to_string()));
130    }
131}