1use async_channel::{Receiver, Sender, bounded, unbounded};
2pub use log::{
3 Level, LevelFilter, Record, debug, error, info, log, log_enabled, logger, trace, warn,
4};
5use log::{Log, Metadata, SetLoggerError, set_boxed_logger, set_max_level};
6use logmsg::LogMsg;
7use std::{
8 borrow::Cow,
9 io::Error as IoError,
11 sync::atomic::{AtomicBool, Ordering},
12};
13mod logmsg;
14
15enum LoggerInput {
16 LogMsg(LogMsg),
17 Flush,
18}
19
20enum LoggerOutput {
21 Flushed,
22}
23
24pub struct LoggerGuard {
29 queue: Sender<LoggerInput>,
30 notification: Receiver<LoggerOutput>,
31}
32
33impl Drop for LoggerGuard {
34 fn drop(&mut self) {
35 let _ = self.queue.send_blocking(LoggerInput::Flush);
36 let _ = self.notification.recv_blocking();
37 }
38}
39
40pub struct Logger {
42 level: LevelFilter,
43 queue: Sender<LoggerInput>,
44 notification: Receiver<LoggerOutput>,
45 stopped: AtomicBool,
46}
47
48impl Logger {
49 pub fn init(self) -> Result<LoggerGuard, SetLoggerError> {
50 let guard = LoggerGuard {
51 queue: self.queue.clone(),
52 notification: self.notification.clone(),
53 };
54
55 set_max_level(self.level);
56 let boxed = Box::new(self);
57 set_boxed_logger(boxed).map(|_| guard)
58 }
59}
60
61impl Log for Logger {
62 #[inline]
63 fn enabled(
64 &self,
65 metadata: &Metadata,
66 ) -> bool {
67 self.level >= metadata.level()
68 }
69
70 fn log(
71 &self,
72 record: &Record,
73 ) {
74 let msg = LogMsg {
75 time: std::time::SystemTime::now(),
76 level: record.level(),
77 file: record
78 .file_static()
79 .map(Cow::Borrowed)
80 .or_else(|| record.file().map(|s| Cow::Owned(s.to_owned())))
81 .unwrap_or(Cow::Borrowed("")),
82 line: record.line(),
83 args: record
84 .args()
85 .as_str()
86 .map(Cow::Borrowed)
87 .unwrap_or_else(|| Cow::Owned(record.args().to_string())),
88 };
89
90 match self.queue.try_send(LoggerInput::LogMsg(msg)) {
91 Err(async_channel::TrySendError::Full(_)) => {}
92 Err(async_channel::TrySendError::Closed(_)) => {
93 let stop = self.stopped.load(Ordering::SeqCst);
94 if !stop {
95 eprintln!("logger queue closed when logging, this is a bug");
96 self.stopped.store(true, Ordering::SeqCst);
97 }
98 }
99 Ok(_) => {}
100 }
101 }
102
103 fn flush(&self) {
104 let _ = self
105 .queue
106 .send_blocking(LoggerInput::Flush)
107 .map_err(|e| eprintln!("logger queue closed when flushing, this is a bug: {e}"));
108 }
109}
110
111struct BoundedChannelOption {
112 size: usize,
113}
114
115pub struct Builder {
116 level: Option<LevelFilter>,
117 root_level: Option<LevelFilter>,
118 bounded_channel_option: Option<BoundedChannelOption>,
119}
120
121#[inline]
123pub fn builder() -> Builder {
124 Builder::new()
125}
126
127impl Builder {
128 #[inline]
129 pub fn new() -> Builder {
135 Builder {
136 level: None,
137 root_level: None,
138 bounded_channel_option: Some(BoundedChannelOption { size: 100_000 }),
139 }
140 }
141
142 #[inline]
149 pub fn unbounded(mut self) -> Builder {
150 self.bounded_channel_option = None;
151 self
152 }
153
154 #[inline]
155 pub fn max_log_level(
159 mut self,
160 level: LevelFilter,
161 ) -> Builder {
162 self.level = Some(level);
163 self
164 }
165
166 #[inline]
167 pub fn root_log_level(
171 mut self,
172 level: LevelFilter,
173 ) -> Builder {
174 self.root_level = Some(level);
175 self
176 }
177
178 pub fn build(self) -> Result<Logger, IoError> {
183 let global_level = self.level.unwrap_or(LevelFilter::Info);
184 let root_level = self.root_level.unwrap_or(global_level);
185 if global_level < root_level {
186 warn!("Logs with level more verbose than {global_level} will be ignored");
187 }
188
189 let (queue, rx) = match self.bounded_channel_option {
190 Some(BoundedChannelOption { size }) => bounded(size),
191 None => unbounded(),
192 };
193
194 let (notification_tx, notification) = bounded(1);
195
196 std::thread::spawn(move || {
197 let mut batch = Vec::with_capacity(100);
198 let mut last_flush = std::time::Instant::now();
199 let batch_timeout = std::time::Duration::from_millis(100);
200
201 while let Ok(msg) = rx.recv_blocking() {
202 match msg {
203 LoggerInput::LogMsg(msg) => {
204 batch.push(msg);
205 if batch.len() >= 100 || last_flush.elapsed() > batch_timeout {
206 for msg in batch.drain(..) {
207 msg.write();
208 }
209 last_flush = std::time::Instant::now();
210 }
211 }
212 LoggerInput::Flush => {
213 for msg in batch.drain(..) {
214 msg.write();
215 }
216 let _ = notification_tx.send_blocking(LoggerOutput::Flushed);
217 }
218 }
219 }
220 });
221
222 Ok(Logger {
223 level: global_level,
224 queue,
225 notification,
226 stopped: AtomicBool::new(false),
227 })
228 }
229
230 pub fn try_init(self) -> Result<LoggerGuard, Box<dyn std::error::Error>> {
232 let logger = self.build()?;
233 Ok(logger.init()?)
234 }
235}
236
237impl Default for Builder {
238 #[inline]
239 fn default() -> Self {
240 Builder::new()
241 }
242}