technimal_logger/
logger.rs1use 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 {
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 ×tamp, ' ',
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 ×tamp, ' ',
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 }
275}