nonblock_logger/
lib.rs

1#[doc(hidden)]
2pub extern crate chrono;
3#[doc(hidden)]
4pub extern crate crossbeam_channel;
5#[cfg(any(feature = "color"))]
6extern crate yansi;
7// re-export log crate
8#[allow(unused_imports)]
9#[macro_use]
10pub extern crate log;
11
12#[macro_use]
13#[doc(hidden)]
14pub mod macros;
15mod consumer;
16mod error;
17mod filter;
18mod formater;
19
20// re-export macros
21pub use log::{debug, error, info, log, log_enabled, trace, warn};
22
23pub use consumer::{BaseConsumer, Consumer, Outputer};
24pub use error::Error;
25pub use filter::{BaseFilter, Filter};
26#[cfg(any(feature = "color"))]
27pub use formater::color::{ColoredFg, ColoredFgWith, ColoredFixedLevel, ColoredLogConfig};
28pub use formater::{current_thread_name, BaseFormater, FixedLevel, Formater};
29
30use crossbeam_channel as channel;
31
32use log::{set_logger, set_max_level, Level, Log, Metadata, Record, SetLoggerError};
33use std::sync::{
34    atomic::{AtomicBool, Ordering},
35    Arc,
36};
37use std::{fmt, mem, thread};
38
39static mut LOGGER: Option<NonblockLoggerGlobal> = None;
40
41const NAME: &str = "log";
42
43pub struct NonblockLogger {
44    name: Option<String>,
45    filter: Box<dyn Filter>,
46    formater: Box<dyn Formater>,
47    consumer: Option<Box<dyn Consumer>>,
48    sendfn: Box<dyn Fn(&NonblockLogger, Option<Message>) + Send + Sync + 'static>,
49    sender: Sender,
50    receiver: Option<Receiver>,
51    exited: AtomicBool,
52    quiet: bool,
53}
54
55pub type Sender = channel::Sender<Option<Message>>;
56pub type Receiver = channel::Receiver<Option<Message>>;
57
58#[derive(Debug, Clone)]
59pub struct Message {
60    pub content: String,
61    pub level: Level,
62}
63
64impl Message {
65    pub fn new(content: String, level: Level) -> Self {
66        Self { content, level }
67    }
68}
69
70impl NonblockLogger {
71    pub fn new() -> Self {
72        let (mp, mc) = channel::unbounded();
73        Self::new2(mp, mc)
74    }
75
76    pub fn with_capacity(cap: usize) -> Self {
77        let (mp, mc) = channel::bounded(cap);
78        Self::new2(mp, mc)
79    }
80
81    fn new2(mp: Sender, mc: Receiver) -> Self {
82        Self {
83            name: None,
84            sender: mp,
85            receiver: Some(mc),
86            sendfn: Box::new(sendfn) as _,
87            exited: AtomicBool::new(false),
88            quiet: false,
89            filter: BaseFilter::new().boxed().unwrap(),
90            formater: BaseFormater::new().boxed(),
91            consumer: Some(BaseConsumer::new().boxed().unwrap()),
92        }
93    }
94
95    pub fn sendfn<F>(mut self, sendfn: F) -> Self
96    where
97        F: Fn(&NonblockLogger, Option<Message>) + Send + Sync + 'static,
98    {
99        self.sendfn = Box::new(sendfn) as _;
100        self
101    }
102
103    pub fn formater<F: Formater>(mut self, formater: F) -> Self {
104        self.formater = formater.boxed();
105        self
106    }
107
108    pub fn filter<F: Filter>(mut self, filter: F) -> Result<Self, Error> {
109        self.filter = filter.boxed()?;
110        Ok(self)
111    }
112
113    pub fn consumer<C: Consumer>(mut self, consumer: C) -> Result<Self, Error> {
114        self.consumer = Some(consumer.boxed()?);
115        Ok(self)
116    }
117
118    pub fn name<S: Into<String>>(mut self, name: S) -> Self {
119        self.name = Some(name.into());
120        self
121    }
122
123    pub fn name_get(&self) -> Option<&String> {
124        self.name.as_ref()
125    }
126
127    /// Don't panic if failed to send message to the consumer thread
128    pub fn quiet(mut self) -> Self {
129        self.quiet = true;
130        self
131    }
132
133    pub fn quiet_get(&self) -> bool {
134        self.quiet
135    }
136
137    fn log_to_channel(mut self) -> Result<Receiver, SetLoggerError> {
138        let mc = mem::replace(&mut self.receiver, None).expect("NonblockLogger's receiver is None!");
139        set_max_level(self.filter.maxlevel());
140        let nob = NonblockLoggerGlobal(Arc::new(self));
141
142        unsafe {
143            LOGGER = Some(nob);
144            assert!(LOGGER.is_some());
145            let np = LOGGER.as_ref().unwrap() as _;
146            set_logger(np)?;
147        }
148
149        Ok(mc)
150    }
151
152    pub fn spawn(mut self) -> Result<JoinHandle, Error> {
153        let name = mem::replace(&mut self.name, None).unwrap_or_else(|| NAME.into());
154        let mut consumer = mem::replace(&mut self.consumer, None).unwrap();
155        let mc = self.log_to_channel()?;
156
157        thread::Builder::new()
158            .name(name)
159            .spawn(move || {
160                consumer.consume(mc);
161                Self::global().map(|g| g.exit());
162            })
163            .map(|jh| JoinHandle::new(Self::global().unwrap(), jh))
164            .map_err(Error::from)
165    }
166
167    pub fn log_to_stdout(mut self) -> Result<JoinHandle, Error> {
168        let maxlevel = self.filter.maxlevel();
169        self.consumer = Some(BaseConsumer::stdout(maxlevel).boxed()?);
170
171        self.spawn()
172    }
173
174    pub fn log_to_stderr(mut self) -> Result<JoinHandle, Error> {
175        let maxlevel = self.filter.maxlevel();
176        self.consumer = Some(BaseConsumer::stderr(maxlevel).boxed()?);
177
178        self.spawn()
179    }
180}
181
182impl NonblockLogger {
183    pub fn global() -> Option<&'static Self> {
184        unsafe { LOGGER.as_ref().map(|g| g.0.as_ref()) }
185    }
186
187    pub fn send_exit(&self) {
188        (*self.sendfn)(self, None)
189    }
190
191    pub fn exit(&self) {
192        self.exited.store(true, Ordering::SeqCst)
193    }
194
195    pub fn exited(&self) -> bool {
196        self.exited.load(Ordering::Relaxed)
197    }
198
199    pub fn messages_in_channel(&self) -> usize {
200        self.sender.len()
201    }
202}
203
204// if channel is full, send will block, but try_send don't
205fn sendfn(logger: &NonblockLogger, msg: Option<Message>) {
206    let res = logger.sender.try_send(msg);
207
208    if let Err(e) = &res {
209        if logger.quiet {
210            return;
211        }
212
213        use crossbeam_channel::TrySendError::*;
214        let is_some = match e {
215            Full(t) => t,
216            Disconnected(t) => t,
217        }
218        .is_some();
219
220        let e = if is_some {
221            "NonblockLogger send log message falied!"
222        } else {
223            "NonblockLogger send exit message falied!"
224        };
225
226        res.expect(e);
227    }
228}
229
230pub fn messages_in_channel() -> usize {
231    NonblockLogger::global().map(|g| g.messages_in_channel()).unwrap_or(0)
232}
233
234pub struct JoinHandle {
235    logger: &'static NonblockLogger,
236    join_handle: Option<thread::JoinHandle<()>>,
237}
238
239impl JoinHandle {
240    fn new(logger: &'static NonblockLogger, join_handle: thread::JoinHandle<()>) -> Self {
241        Self {
242            logger,
243            join_handle: Some(join_handle),
244        }
245    }
246
247    /// wait the log thread exit, can be called multiple times, but only takes effect for the first time.
248    pub fn join(&mut self) {
249        mem::replace(&mut self.join_handle, None).map(|h| {
250            self.logger.send_exit();
251            h.join().ok()
252        });
253    }
254}
255
256impl Drop for JoinHandle {
257    fn drop(&mut self) {
258        #[cfg(any(feature = "dbg"))]
259        dbg!(self.join_handle.is_some());
260        self.join()
261    }
262}
263
264struct NonblockLoggerGlobal(Arc<NonblockLogger>);
265
266impl Log for NonblockLoggerGlobal {
267    fn flush(&self) {}
268
269    fn enabled(&self, metadata: &Metadata) -> bool {
270        self.0.filter.enabled(metadata)
271    }
272
273    fn log(&self, record: &Record) {
274        let g = &self.0;
275
276        if g.filter.log(record) {
277            let content = g.formater.format(record);
278            let message = Message::new(content, record.level());
279
280            (*g.sendfn)(g, Some(message))
281        }
282    }
283}
284
285impl fmt::Debug for NonblockLogger {
286    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
287        fmt.debug_struct("NonblockLogger")
288            .field("name", &self.name)
289            .field("exited", &self.exited)
290            .finish()
291    }
292}
293
294#[cfg(test)]
295mod tests {
296    #[test]
297    fn it_works() {
298        assert_eq!(2 + 2, 4);
299    }
300}