1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
use std::{borrow::Cow, error, fmt}; use crossbeam::channel::{unbounded, Sender}; use log::{LevelFilter, Log, Metadata, Record}; use once_cell::sync::OnceCell; static INNER_LOGGER: OnceCell<Box<dyn Log>> = OnceCell::new(); struct ThreadedLogger { logger: &'static dyn Log, sender: Sender<Box<dyn FnOnce() + Send>>, } impl Log for ThreadedLogger { fn enabled(&self, metadata: &Metadata) -> bool { self.logger.enabled(metadata) } fn log(&self, record: &Record) { let level = record.metadata().level(); let target = record.metadata().target().to_owned(); let args_str = if let Some(s) = record.args().as_str() { Cow::Borrowed(s) } else { Cow::Owned(fmt::format(*record.args())) }; let module_path = if let Some(s) = record.module_path_static() { Some(Cow::Borrowed(s)) } else { record.module_path().map(|s| Cow::Owned(s.to_owned())) }; let file = if let Some(s) = record.file_static() { Some(Cow::Borrowed(s)) } else { record.file().map(|s| Cow::Owned(s.to_owned())) }; let line = record.line(); let logger_ = self.logger.clone(); let log = move || { let metadata = Metadata::builder().level(level).target(&target).build(); logger_.log( &Record::builder() .metadata(metadata) .args(format_args!("{}", args_str)) .module_path(module_path.as_deref()) .file(file.as_deref()) .line(line) .build(), ); }; self.sender.send(Box::new(log)).ok(); } fn flush(&self) { self.logger.flush() } } pub fn try_init( logger: impl Log + 'static, max_level: LevelFilter, ) -> Result<(), ThreadedLoggerError> { let (sender, receiver) = unbounded(); INNER_LOGGER .set(Box::new(logger)) .map_err(|_| ThreadedLoggerError(()))?; let threaded_logger = ThreadedLogger { logger: unsafe { INNER_LOGGER.get_unchecked() }, sender, }; let r = log::set_boxed_logger(Box::new(threaded_logger)).map_err(|_| ThreadedLoggerError(())); if r.is_ok() { log::set_max_level(max_level); } tokio::task::spawn_blocking(move || loop { if let Ok(log) = receiver.recv() { log(); } }); r } pub fn init(logger: impl Log + 'static, max_level: LevelFilter) { try_init(logger, max_level).unwrap(); } #[derive(Debug)] pub struct ThreadedLoggerError(()); impl fmt::Display for ThreadedLoggerError { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt.write_str("attempted to set a logger more than once") } } impl error::Error for ThreadedLoggerError {} #[cfg(test)] mod tests { use super::*; #[tokio::test] async fn threaded_env_logger() { let logger = env_logger::builder().build(); let filter = logger.filter(); init(logger, filter); let now = std::time::Instant::now(); for i in 0..100000 { log::info!("{}", i); } let t = now.elapsed().as_micros(); println!("time elapsed: {}µs", t); } }