technimal_logger/
logger.rs

1use crossbeam_channel::{unbounded, Sender};
2use once_cell::sync::Lazy;
3use std::{
4    io::stderr,
5    sync::{
6        atomic::{AtomicU64, AtomicUsize, Ordering},
7        Mutex,
8    },
9    thread,
10    time::{Duration, Instant},
11};
12
13static LOG_MESSAGE_BUFFER_SIZE: Lazy<AtomicUsize> = Lazy::new(|| AtomicUsize::new(1_000_000));
14static LOG_MESSAGE_FLUSH_INTERVAL: Lazy<AtomicU64> = Lazy::new(|| AtomicU64::new(500_000_000));
15pub static LOGGER_HANDLER: Lazy<Mutex<Option<thread::JoinHandle<()>>>> =
16    Lazy::new(|| Mutex::new(None));
17
18pub static LOG_SENDER: Lazy<Sender<LogMessage>> = Lazy::new(|| {
19    let (sender, receiver) = unbounded();
20    let mut message_queue: Vec<String> =
21        Vec::with_capacity(LOG_MESSAGE_BUFFER_SIZE.load(Ordering::SeqCst));
22    let msg_buffer_size = LOG_MESSAGE_BUFFER_SIZE.load(Ordering::SeqCst);
23    let mut last_flush_time = Instant::now();
24
25    *LOGGER_HANDLER.lock().unwrap() = Some(thread::spawn(move || {
26        use humantime::format_rfc3339_micros;
27        while let Ok(msg) = receiver.recv() {
28            match msg {
29                LogMessage::LazyMessage(lazy_message) => {
30                    let message = lazy_message.eval();
31                    let new_msg_length = message.len();
32                    let buffer_size = message_queue.len();
33                    let timestamp = Instant::now();
34                    message_queue.push(message);
35
36                    if (buffer_size + new_msg_length > msg_buffer_size)
37                        || (timestamp - last_flush_time > Duration::from_secs(1))
38                    // flush if the buffer is full or the time interval is passed
39                    {
40                        let output = message_queue.join("");
41                        let _ = std::io::Write::write_all(
42                            &mut stderr(),
43                            output.as_bytes(),
44                        );
45
46                        message_queue.clear();
47                        last_flush_time = Instant::now();
48                    }
49                }
50                LogMessage::FlushingMessage(lazy_message) => {
51                    let message = lazy_message.eval();
52                    message_queue.push(message);
53
54                    let output = message_queue.join("");
55                    println!("{}", output);
56
57                    message_queue.clear();
58                    last_flush_time = Instant::now();
59                }
60                LogMessage::Flush => {
61                    let output = message_queue.join("");
62                    println!("{}", output);
63                    message_queue.clear();
64                    last_flush_time = Instant::now();
65                }
66                LogMessage::Close => {
67                    let output = message_queue.join("");
68                    println!("{}", output);
69                    break;
70                }
71            }
72        }
73    }));
74    sender
75});
76
77pub enum LogLevel {
78    Error = 1,
79    Warn = 2,
80    Info = 3,
81    Debug = 4,
82    Trace = 5,
83}
84
85impl LogLevel {
86    pub fn as_str(&self) -> &str {
87        match self {
88            LogLevel::Trace => "TRACE",
89            LogLevel::Debug => "DEBUG",
90            LogLevel::Info => "INFO ",
91            LogLevel::Error => "ERROR",
92            LogLevel::Warn => "WARN ",
93        }
94    }
95}
96
97pub struct LoggerGuard;
98
99impl Drop for LoggerGuard {
100    fn drop(&mut self) {
101        Logger::finalize();
102    }
103}
104
105pub struct Logger {}
106
107impl Logger {
108    pub fn finalize() {
109        let _ = LOG_SENDER.try_send(LogMessage::Close);
110        if let Some(handler) = LOGGER_HANDLER.lock().unwrap().take() {
111            let _ = handler.join();
112        }
113    }
114
115    pub fn initialize() -> Logger {
116        LOG_MESSAGE_BUFFER_SIZE.store(1_000_000, Ordering::Relaxed);
117        LOG_MESSAGE_FLUSH_INTERVAL.store(1_000_000, Ordering::Relaxed);
118        Logger {}
119    }
120
121    pub fn launch(self) -> LoggerGuard {
122        LoggerGuard {}
123    }
124}
125
126pub enum LogMessage {
127    LazyMessage(LazyMessage),
128    FlushingMessage(LazyMessage),
129    Flush,
130    Close,
131}
132
133pub struct LazyMessage {
134    data: Box<dyn (FnOnce() -> String) + Send + 'static>,
135}
136
137impl LazyMessage {
138    pub fn new<F>(data: F) -> LazyMessage
139    where
140        F: (FnOnce() -> String) + Send + 'static,
141    {
142        LazyMessage {
143            data: Box::new(data),
144        }
145    }
146
147    pub fn eval(self) -> String {
148        (self.data)()
149    }
150}
151
152#[macro_export]
153macro_rules! flush {
154    () => {{
155        $crate::LOG_SENDER
156            .try_send($crate::LogMessage::Flush)
157            .unwrap();
158    }};
159}
160
161#[macro_export]
162macro_rules! log_with_level {
163    ($level:expr, $fmt:expr, $($arg:expr),* $(,)?) => {{
164        $crate::log_fn!($level, $fmt, $($arg),*);
165    }};
166
167    ($level:expr, $msg:expr) => {{
168        $crate::log_fn!($level, $msg);
169    }};
170}
171
172#[macro_export]
173macro_rules! trace {
174    ( $($args:tt)* ) => {
175        $crate::log_with_level!($crate::LogLevel::Trace, $($args)* )
176    };
177}
178
179#[macro_export]
180macro_rules! debug {
181    ( $($args:tt)* ) => {
182        $crate::log_with_level!($crate::LogLevel::Debug, $($args)* )
183    };
184}
185
186#[macro_export]
187macro_rules! info {
188    ( $($args:tt)* ) => {
189        $crate::log_with_level!($crate::LogLevel::Info, $($args)* )
190    };
191}
192
193#[macro_export]
194macro_rules! warn {
195    ( $($args:tt)* ) => {
196        $crate::log_with_level!($crate::LogLevel::Warn, $($args)* )
197    };
198}
199
200#[macro_export]
201macro_rules! error {
202    ( $($args:tt)* ) => {
203        $crate::log_with_level!($crate::LogLevel::Error, $($args)* )
204    };
205}
206
207#[macro_export]
208macro_rules! log_fn {
209    ($level:expr, $fmt:expr, $($arg:expr),* $(,)?) => {{
210        let timestamp = humantime::format_rfc3339_micros(std::time::SystemTime::now()).to_string();
211        let func = move || {
212            let cmd = $crate::concat_strs_derive::concat_strs!(
213                &timestamp, ' ',
214                $level.as_str(),
215                " [",
216                &format!("{}:{}", file!(), line!()),
217                "] ",
218                &format!($fmt, $($arg),*),
219                "\n"
220            );
221            cmd
222        };
223        $crate::LOG_SENDER.try_send($crate::LogMessage::LazyMessage($crate::LazyMessage::new(func))).unwrap();
224    }};
225
226    ($level:expr, $msg:expr) => {{
227        let timestamp = humantime::format_rfc3339_micros(std::time::SystemTime::now()).to_string();
228        let func = move || {
229            let cmd = $crate::concat_strs_derive::concat_strs!(
230                &timestamp, ' ',
231                $level.as_str(),
232                " [",
233                &format!("{}:{}", file!(), line!()),
234                "] ",
235                $msg,
236                "\n"
237            );
238            cmd
239        };
240        $crate::LOG_SENDER.try_send($crate::LogMessage::LazyMessage($crate::LazyMessage::new(func))).unwrap();
241    }};
242}
243
244#[cfg(test)]
245mod tests {
246    use crate::Logger;
247
248    #[test]
249    fn test_logger() {
250        let _guard = Logger::initialize().launch();
251
252        error!("Hello");
253        error!("Hello");
254        error!("Hello");
255        error!("Hello");
256        error!("Hello");
257        error!("Hello");
258        error!("Hello");
259        let cmd = "x";
260        let v = "y";
261        error!("{} -> {}{v}", cmd, v);
262        error!("Hello2");
263        error!("Hello2");
264        error!("Hello2");
265        error!("Hello2");
266        error!("Hello2");
267        warn!("Hello");
268        info!("Hello");
269        debug!("Hello");
270        debug!("message {} {}", 1, 2);
271        trace!("Hello");
272
273        // drop(_guard);
274    }
275}