1use crossbeam_channel::{
2 Receiver,
3 RecvTimeoutError,
4 Sender,
5 TrySendError,
6 bounded,
7 unbounded, };
9pub use log::{
10 Level,
11 LevelFilter,
12 Record,
13 debug,
14 error,
15 info,
16 log,
17 log_enabled,
18 logger,
19 trace,
20 warn,
21 };
23use log::{
24 Log,
25 Metadata,
26 SetLoggerError,
27 set_boxed_logger,
28 set_max_level, };
30use logmsg::LogMsg;
31use std::{
32 borrow::Cow,
33 fmt::Display,
34 io::Error as IoError,
35 sync::atomic::{
36 AtomicBool,
37 Ordering, },
39 time::{Duration, Instant},
40};
41mod logmsg;
42
43enum LoggerInput {
44 LogMsg(LogMsg),
45 Flush,
46}
47
48enum LoggerOutput {
49 Flushed,
50}
51
52pub trait FtLogFormat: Send + Sync {
53 fn msg(
56 &self,
57 record: &Record,
58 ) -> Box<dyn Send + Sync + Display>;
59}
60
61pub struct FtLogFormatter;
62impl FtLogFormat for FtLogFormatter {
63 #[inline]
65 fn msg(
66 &self,
67 record: &Record,
68 ) -> Box<dyn Send + Sync + Display> {
69 Box::new(Message {
70 level: record.level(),
71 file: record
72 .file_static()
73 .map(Cow::Borrowed)
74 .or_else(|| record.file().map(|s| Cow::Owned(s.to_owned())))
75 .unwrap_or(Cow::Borrowed("")),
76 line: record.line(),
77 args: record
78 .args()
79 .as_str()
80 .map(Cow::Borrowed)
81 .unwrap_or_else(|| Cow::Owned(record.args().to_string())),
82 })
83 }
84}
85
86struct Message {
87 level: Level,
88 file: Cow<'static, str>,
89 line: Option<u32>,
90 args: Cow<'static, str>,
91}
92
93impl Display for Message {
94 fn fmt(
95 &self,
96 f: &mut std::fmt::Formatter<'_>,
97 ) -> std::fmt::Result {
98 f.write_str(&format!(
99 "{} [{}:{}] {}",
100 self.level,
101 self.file,
102 self.line.map_or(0, |f| f),
103 self.args
104 ))
105 }
106}
107
108pub struct LoggerGuard {
113 queue: Sender<LoggerInput>,
114 notification: Receiver<LoggerOutput>,
115}
116impl Drop for LoggerGuard {
117 fn drop(&mut self) {
118 self.queue
119 .send(LoggerInput::Flush)
120 .expect("logger queue closed when flushing, this is a bug");
121 self.notification
122 .recv()
123 .expect("logger notification closed, this is a bug");
124 }
125}
126pub struct Logger {
128 format: Box<dyn FtLogFormat>,
129 level: LevelFilter,
130 queue: Sender<LoggerInput>,
131 notification: Receiver<LoggerOutput>,
132 stopped: AtomicBool,
133}
134
135impl Logger {
136 pub fn init(self) -> Result<LoggerGuard, SetLoggerError> {
137 let guard = LoggerGuard {
138 queue: self.queue.clone(),
139 notification: self.notification.clone(),
140 };
141
142 set_max_level(self.level);
143 let boxed = Box::new(self);
144 set_boxed_logger(boxed).map(|_| guard)
145 }
146}
147
148impl Log for Logger {
149 #[inline]
150 fn enabled(
151 &self,
152 metadata: &Metadata,
153 ) -> bool {
154 self.level >= metadata.level()
155 }
156
157 fn log(
158 &self,
159 record: &Record,
160 ) {
161 let msg = self.format.msg(record);
162 let msg = LoggerInput::LogMsg(LogMsg {
163 time: std::time::SystemTime::now(),
164 msg,
165 });
166 match self.queue.try_send(msg) {
167 Err(TrySendError::Full(_)) => {}
168 Err(TrySendError::Disconnected(_)) => {
169 let stop = self.stopped.load(Ordering::SeqCst);
170 if !stop {
171 eprintln!("logger queue closed when logging, this is a bug");
172 self.stopped.store(true, Ordering::SeqCst)
173 }
174 }
175 _ => (),
176 }
177 }
178
179 fn flush(&self) {
180 let _ = self
181 .queue
182 .send(LoggerInput::Flush)
183 .map_err(|e| eprintln!("logger queue closed when flushing, this is a bug: {e}"));
184 }
185}
186
187pub struct Builder {
188 format: Box<dyn FtLogFormat>,
189 level: Option<LevelFilter>,
190 root_level: Option<LevelFilter>,
191}
192
193#[inline]
195pub fn builder() -> Builder {
196 Builder::new()
197}
198
199impl Builder {
200 #[inline]
201 pub fn new() -> Builder {
202 Builder {
203 format: Box::new(FtLogFormatter),
204 level: None,
205 root_level: None,
206 }
207 }
208
209 #[inline]
211 pub fn format<F: FtLogFormat + 'static>(
212 mut self,
213 format: F,
214 ) -> Builder {
215 self.format = Box::new(format);
216 self
217 }
218
219 #[inline]
220 pub fn max_log_level(
223 mut self,
224 level: LevelFilter,
225 ) -> Builder {
226 self.level = Some(level);
227 self
228 }
229
230 #[inline]
231 pub fn root_log_level(
235 mut self,
236 level: LevelFilter,
237 ) -> Builder {
238 self.root_level = Some(level);
239 self
240 }
241
242 pub fn build(self) -> Result<Logger, IoError> {
247 let global_level = self.level.unwrap_or(LevelFilter::Info);
248 let root_level = self.root_level.unwrap_or(global_level);
249 if global_level < root_level {
250 warn!("Logs with level more verbose than {global_level} will be ignored");
251 }
252
253 let (sync_sender, receiver) = unbounded();
254 let (notification_sender, notification_receiver) = bounded(1);
255 std::thread::Builder::new()
256 .name("logger".to_string())
257 .spawn(move || {
258 let mut last_flush = Instant::now();
259 let timeout = Duration::from_millis(100);
260 loop {
261 match receiver.recv_timeout(timeout) {
262 Ok(LoggerInput::LogMsg(msg)) => {
263 msg.write();
264 }
265 Ok(LoggerInput::Flush) => {
266 let max = receiver.len();
267 'queue: for _ in 1..=max {
268 if let Ok(LoggerInput::LogMsg(msg)) = receiver.try_recv() {
269 msg.write();
270 } else {
271 break 'queue;
272 }
273 }
274 notification_sender
275 .send(LoggerOutput::Flushed)
276 .expect("logger notification failed");
277 }
278 Err(RecvTimeoutError::Timeout) => {
279 if last_flush.elapsed() > Duration::from_millis(1000) {
280 last_flush = Instant::now();
281 }
282 }
283 Err(e) => {
284 eprintln!(
285 "sender closed without sending a Quit first, this is a bug, {e}"
286 );
287 }
288 }
289 }
290 })?;
291 Ok(Logger {
292 format: self.format,
293 level: global_level,
294 queue: sync_sender,
295 notification: notification_receiver,
296 stopped: AtomicBool::new(false),
297 })
298 }
299
300 pub fn try_init(self) -> Result<LoggerGuard, Box<dyn std::error::Error>> {
302 let logger = self.build()?;
303 Ok(logger.init()?)
304 }
305}
306
307impl Default for Builder {
308 #[inline]
309 fn default() -> Self {
310 Builder::new()
311 }
312}