c2pdf/
logging.rs

1//! Module for logging on a separate thread
2use std::{
3  sync::{Arc, Mutex},
4  thread::{self, JoinHandle},
5};
6
7/// Message for the logger
8///
9/// This includes the [`LoggerMessage::Complete`] and [`LoggerMessage::Abort`] signals, as well as [`LoggerMessage::Message`], which contains a message to log
10pub enum LoggerMessage {
11  /// Message to log
12  Message(String),
13  /// Complete signal
14  Complete,
15  /// Abort signal
16  Abort,
17}
18
19/// Main logger struct
20///
21/// Allows for logging to stdout from a separate thread
22#[derive(Clone)]
23pub struct Logger {
24  tx: crossbeam_channel::Sender<LoggerMessage>,
25  handle: Arc<Mutex<Option<JoinHandle<()>>>>,
26}
27
28impl Logger {
29  /// Creates a new [`Logger`]
30  pub fn new(
31    channel: (
32      crossbeam_channel::Sender<LoggerMessage>,
33      crossbeam_channel::Receiver<LoggerMessage>,
34    ),
35  ) -> Logger {
36    let (tx, rx) = channel;
37    let handle = thread::spawn(move || {
38      loop {
39        let msg = if let Ok(msg) = rx.recv() {
40          match msg {
41            LoggerMessage::Message(msg) => msg,
42            LoggerMessage::Complete => continue,
43            LoggerMessage::Abort => break,
44          }
45        } else {
46          continue;
47        };
48        println!("{}", msg);
49      }
50    });
51    Logger {
52      tx,
53      handle: Arc::new(Mutex::new(Some(handle))),
54    }
55  }
56  /// Creates a new logger without creating a logging thread
57  pub fn new_without_logging_thread(sender: crossbeam_channel::Sender<LoggerMessage>) -> Logger {
58    Logger {
59      tx: sender,
60      handle: Arc::new(Mutex::new(None)),
61    }
62  }
63  /// Logs a string
64  pub fn log_message(&self, item: String) {
65    self.send_raw_message(LoggerMessage::Message(item));
66  }
67  /// Sends a raw message to the logger thread
68  pub fn send_raw_message(&self, msg: LoggerMessage) {
69    self.tx.send(msg).unwrap()
70  }
71  /// Waits for all threads to finish
72  pub fn finish(self) -> std::thread::Result<()> {
73    // Logger thread should exit once it processes this signal
74    self.tx.send(LoggerMessage::Abort).unwrap();
75    let mut lock = self.handle.lock().unwrap();
76    let handle = lock.take().unwrap();
77    handle.join()
78  }
79}
80impl log::Log for Logger {
81  fn enabled(&self, metadata: &log::Metadata) -> bool {
82    if metadata.target() == "c2pdf" {
83      metadata.level() <= log::Level::Trace
84    } else {
85      false
86    }
87  }
88  fn log(&self, record: &log::Record) {
89    if self.enabled(record.metadata()) {
90      self.log_message(record.args().to_string());
91    }
92  }
93
94  fn flush(&self) {}
95}
96#[cfg(test)]
97mod tests {
98  use log::info;
99
100  use super::*;
101  #[test]
102  fn it_works() {
103    let logger = Logger::new(crossbeam_channel::unbounded());
104    info!("Hello world!!");
105    _ = logger.finish();
106  }
107}