captains_log/
log_impl.rs

1use crate::{buf_file_impl::LogSinkBufFile, console_impl::LogSinkConsole, file_impl::LogSinkFile};
2use crate::{config::Builder, time::Timer};
3use arc_swap::ArcSwap;
4use backtrace::Backtrace;
5use signal_hook::iterator::Signals;
6use std::cell::UnsafeCell;
7use std::io::Error;
8use std::mem::transmute;
9use std::sync::{
10    atomic::{AtomicBool, AtomicU64, Ordering},
11    Arc,
12};
13use std::thread;
14
15#[cfg(feature = "tracing")]
16use crate::tracing_bridge::{CaptainsLogLayer, TracingFormatter, TracingText};
17#[cfg(feature = "tracing")]
18use tracing::{dispatcher, Dispatch};
19#[cfg(feature = "tracing")]
20use tracing_subscriber::prelude::*;
21
22#[enum_dispatch]
23pub(crate) trait LogSinkTrait {
24    fn open(&self) -> std::io::Result<()>;
25
26    fn reopen(&self) -> std::io::Result<()>;
27
28    fn log(&self, now: &Timer, r: &log::Record);
29
30    fn flush(&self);
31}
32
33#[enum_dispatch(LogSinkTrait)]
34pub enum LogSink {
35    File(LogSinkFile),
36    BufFile(LogSinkBufFile),
37    Console(LogSinkConsole),
38    #[cfg(feature = "syslog")]
39    Syslog(crate::syslog::LogSinkSyslog),
40    #[cfg(feature = "ringfile")]
41    RingFile(crate::ringfile::LogSinkRingFile),
42}
43
44struct GlobalLoggerStatic {
45    logger: UnsafeCell<GlobalLogger>,
46    lock: AtomicBool,
47    inited: AtomicBool,
48}
49
50struct GlobalLoggerGuard<'a>(&'a GlobalLoggerStatic);
51
52impl Drop for GlobalLoggerGuard<'_> {
53    fn drop(&mut self) {
54        self.0.unlock();
55    }
56}
57
58impl GlobalLoggerStatic {
59    const fn new() -> Self {
60        Self {
61            logger: UnsafeCell::new(GlobalLogger {
62                config_checksum: AtomicU64::new(0),
63                inner: None,
64                signal_listener: AtomicBool::new(false),
65                #[cfg(feature = "tracing")]
66                tracing_inited: AtomicBool::new(false),
67            }),
68            lock: AtomicBool::new(false),
69            inited: AtomicBool::new(false),
70        }
71    }
72
73    #[inline(always)]
74    fn get_logger_mut(&self) -> &mut GlobalLogger {
75        unsafe { transmute(self.logger.get()) }
76    }
77
78    /// Assume already setup, for internal use.
79    #[inline(always)]
80    fn get_logger(&'static self) -> &'static GlobalLogger {
81        unsafe { transmute(self.logger.get()) }
82    }
83
84    /// This is the safe version for public use.
85    #[inline(always)]
86    fn try_get_logger(&'static self) -> Option<&'static GlobalLogger> {
87        let logger = self.get_logger();
88        if self.inited.load(Ordering::SeqCst) {
89            return Some(logger);
90        } else {
91            None
92        }
93    }
94
95    #[inline]
96    fn lock<'a>(&'a self) -> GlobalLoggerGuard<'a> {
97        while self
98            .lock
99            .compare_exchange_weak(false, true, Ordering::Acquire, Ordering::Relaxed)
100            .is_err()
101        {
102            // Normally this does not contend, if your test does not run concurrently.
103            std::thread::yield_now();
104        }
105        GlobalLoggerGuard(self)
106    }
107
108    fn unlock(&self) {
109        self.lock.store(false, Ordering::SeqCst);
110    }
111
112    /// Return Ok(false) when reinit, Ok(true) when first init, Err for error
113    fn try_setup(&'static self, builder: &Builder) -> Result<bool, Error> {
114        let _guard = self.lock();
115        let res = { self.get_logger().check_the_same(builder) };
116        match res {
117            Some(true) => {
118                // checksum is the same
119                if let Err(e) = self.get_logger().open() {
120                    eprintln!("failed to open log sink: {:?}", e);
121                    return Err(e);
122                }
123                // reset the log level
124                log::set_max_level(builder.get_max_level());
125                return Ok(false);
126            }
127            Some(false) => {
128                // checksum is not the same
129                if !builder.dynamic {
130                    let e = Error::other("log config differs but dynamic=false");
131                    eprintln!("{:?}", e);
132                    return Err(e);
133                }
134                let logger = self.get_logger();
135                if let Err(e) = logger.reinit(builder) {
136                    eprintln!("{:?}", e);
137                    return Err(e);
138                }
139                // reset the log level
140                log::set_max_level(builder.get_max_level());
141                #[cfg(feature = "tracing")]
142                {
143                    if builder.tracing_global {
144                        logger.init_tracing_global()?;
145                    }
146                }
147                return Ok(false);
148            }
149            None => {
150                // first init
151                let res = { self.get_logger_mut().init(builder) };
152                res?;
153                #[cfg(feature = "tracing")]
154                {
155                    let logger = self.get_logger();
156                    if builder.tracing_global {
157                        logger.init_tracing_global()?;
158                    }
159                }
160                self.inited.store(true, Ordering::SeqCst);
161                return Ok(true);
162            }
163        }
164    }
165}
166
167unsafe impl Send for GlobalLoggerStatic {}
168unsafe impl Sync for GlobalLoggerStatic {}
169
170/// Initialize global logger from Builder
171///
172/// **NOTE**: You can call this function multiple times when **builder.dynamic=true**,
173/// but **cannot mixed used captains_log with other logger implement**, because log::set_logger()
174/// cannot be called twice.
175pub fn setup_log(builder: Builder) -> Result<&'static GlobalLogger, Error> {
176    if GLOBAL_LOGGER.try_setup(&builder)? {
177        let logger = GLOBAL_LOGGER.get_logger();
178        // Set logger can only be called once
179        if let Err(e) = log::set_logger(logger) {
180            eprintln!("log::set_logger return error: {:?}", e);
181            return Err(Error::other(format!("log::set_logger() failed: {:?}", e)));
182        }
183        log::set_max_level(builder.get_max_level());
184        // panic hook can be set multiple times
185        if builder.continue_when_panic {
186            std::panic::set_hook(Box::new(panic_no_exit_hook));
187        } else {
188            std::panic::set_hook(Box::new(panic_and_exit_hook));
189        }
190        let signals = builder.rotation_signals.clone();
191        if signals.len() > 0 {
192            if false == logger.signal_listener.swap(true, Ordering::SeqCst) {
193                thread::spawn(move || {
194                    GLOBAL_LOGGER.get_logger().listener_for_signal(signals);
195                });
196            }
197        }
198        Ok(logger)
199    } else {
200        Ok(GLOBAL_LOGGER.get_logger())
201    }
202}
203
204/// Return the GlobalLogger after initialized.
205pub fn get_global_logger() -> Option<&'static GlobalLogger> {
206    GLOBAL_LOGGER.try_get_logger()
207}
208
209/// Global static structure to hold the logger
210pub struct GlobalLogger {
211    /// checksum for config comparison
212    config_checksum: AtomicU64,
213    /// Global static needs initialization when declaring,
214    /// default to be empty
215    inner: Option<LoggerInner>,
216    signal_listener: AtomicBool,
217    #[cfg(feature = "tracing")]
218    tracing_inited: AtomicBool,
219}
220
221enum LoggerInnerSink {
222    Once(Vec<LogSink>),
223    // using ArcSwap has more cost
224    Dyn(ArcSwap<Vec<LogSink>>),
225}
226
227struct LoggerInner {
228    sinks: LoggerInnerSink,
229}
230
231unsafe impl Send for LoggerInner {}
232unsafe impl Sync for LoggerInner {}
233
234impl LoggerInner {
235    #[inline]
236    fn new(dynamic: bool, sinks: Vec<LogSink>) -> Self {
237        let sinks = if dynamic {
238            LoggerInnerSink::Dyn(ArcSwap::new(Arc::new(sinks)))
239        } else {
240            LoggerInnerSink::Once(sinks)
241        };
242        Self { sinks }
243    }
244
245    #[inline]
246    fn set(&self, sinks: Vec<LogSink>) -> std::io::Result<()> {
247        match &self.sinks {
248            LoggerInnerSink::Once(_) => {
249                let e = Error::other("previous logger does not init with dynamic=true");
250                return Err(e);
251            }
252            LoggerInnerSink::Dyn(d) => {
253                d.store(Arc::new(sinks));
254            }
255        }
256        Ok(())
257    }
258}
259
260impl GlobalLogger {
261    fn listener_for_signal(&self, signals: Vec<i32>) {
262        println!("signal_listener started");
263        let mut signals = Signals::new(&signals).unwrap();
264        for __sig in signals.forever() {
265            let _ = self.reopen();
266        }
267        println!("signal_listener exit");
268    }
269
270    /// On program/test Initialize
271    fn open(&self) -> std::io::Result<()> {
272        if let Some(inner) = self.inner.as_ref() {
273            match &inner.sinks {
274                LoggerInnerSink::Once(inner) => {
275                    for sink in inner.iter() {
276                        sink.open()?;
277                    }
278                }
279                LoggerInnerSink::Dyn(inner) => {
280                    let sinks = inner.load();
281                    for sink in sinks.iter() {
282                        sink.open()?;
283                    }
284                }
285            }
286        }
287        println!("log sinks opened");
288        Ok(())
289    }
290
291    /// Reopen file. This is a signal handler, also can be called manually.
292    pub fn reopen(&self) -> std::io::Result<()> {
293        if let Some(inner) = self.inner.as_ref() {
294            match &inner.sinks {
295                LoggerInnerSink::Once(inner) => {
296                    for sink in inner.iter() {
297                        sink.reopen()?;
298                    }
299                }
300                LoggerInnerSink::Dyn(inner) => {
301                    let sinks = inner.load();
302                    for sink in sinks.iter() {
303                        sink.reopen()?;
304                    }
305                }
306            }
307        }
308        println!("log sinks re-opened");
309        Ok(())
310    }
311
312    /// Return Some(true) to skip, Some(false) to reinit, None to init
313    #[inline]
314    fn check_the_same(&self, builder: &Builder) -> Option<bool> {
315        if self.inner.is_some() {
316            return Some(self.config_checksum.load(Ordering::Acquire) == builder.cal_checksum());
317        }
318        None
319    }
320
321    /// Re-configure the logger sink
322    fn reinit(&self, builder: &Builder) -> std::io::Result<()> {
323        let sinks = builder.build_sinks()?;
324        if let Some(inner) = self.inner.as_ref() {
325            inner.set(sinks)?;
326            self.config_checksum.store(builder.cal_checksum(), Ordering::Release);
327        } else {
328            unreachable!();
329        }
330        Ok(())
331    }
332
333    fn init(&mut self, builder: &Builder) -> std::io::Result<()> {
334        let sinks = builder.build_sinks()?;
335        assert!(self.inner.is_none());
336        self.inner.replace(LoggerInner::new(builder.dynamic, sinks));
337        self.config_checksum.store(builder.cal_checksum(), Ordering::Release);
338        Ok(())
339    }
340
341    #[cfg(feature = "tracing")]
342    #[inline]
343    fn init_tracing_global(&'static self) -> Result<(), Error> {
344        let dist = self.tracing_dispatch::<TracingText>()?;
345        if let Err(_) = dispatcher::set_global_default(dist) {
346            let e = Error::other("tracing global dispatcher already exists");
347            eprintln!("{:?}", e);
348            return Err(e);
349        }
350        self.tracing_inited.store(true, Ordering::SeqCst);
351        Ok(())
352    }
353
354    #[cfg(feature = "tracing")]
355    #[cfg_attr(docsrs, doc(cfg(feature = "tracing")))]
356    /// Initialize a layer for tracing. Use this when you stacking multiple tracing layers.
357    ///
358    /// For usage, checkout the doc in [crate::tracing_bridge]
359    ///
360    /// # NOTE:
361    ///
362    /// In order to prevent duplicate output, it will fail if out tracing global subscriber
363    /// has been initialized.
364    pub fn tracing_layer<F: TracingFormatter>(
365        &'static self,
366    ) -> std::io::Result<CaptainsLogLayer<F>> {
367        if self.tracing_inited.load(Ordering::SeqCst) {
368            let e = Error::other("global tracing dispatcher exists");
369            eprintln!("{:?}", e);
370            return Err(e);
371        }
372        return Ok(CaptainsLogLayer::<F>::new(self));
373    }
374
375    #[cfg(feature = "tracing")]
376    #[cfg_attr(docsrs, doc(cfg(feature = "tracing")))]
377    /// Initialize a tracing Dispatch, you can set_global_default() or use in a scope.
378    ///
379    /// For usage, checkout the doc in [crate::tracing_bridge]
380    ///
381    /// # NOTE:
382    ///
383    /// In order to prevent duplicate output, it will fail if out tracing global subscriber
384    /// has been initialized.
385    pub fn tracing_dispatch<F: TracingFormatter>(&'static self) -> std::io::Result<Dispatch> {
386        if self.tracing_inited.load(Ordering::SeqCst) {
387            let e = Error::other("global tracing dispatcher exists");
388            eprintln!("{:?}", e);
389            return Err(e);
390        }
391        return Ok(Dispatch::new(
392            tracing_subscriber::registry().with(self.tracing_layer::<F>().unwrap()),
393        ));
394    }
395}
396
397impl log::Log for GlobalLogger {
398    #[inline(always)]
399    fn enabled(&self, _m: &log::Metadata) -> bool {
400        true
401    }
402
403    #[inline(always)]
404    fn log(&self, r: &log::Record) {
405        let now = Timer::new();
406        if let Some(inner) = self.inner.as_ref() {
407            match &inner.sinks {
408                LoggerInnerSink::Once(inner) => {
409                    for sink in inner.iter() {
410                        sink.log(&now, r);
411                    }
412                }
413                LoggerInnerSink::Dyn(inner) => {
414                    let sinks = inner.load();
415                    for sink in sinks.iter() {
416                        sink.log(&now, r);
417                    }
418                }
419            }
420        }
421    }
422
423    /// Can be call manually on program shutdown (If you have a buffered log sink)
424    ///
425    /// # Example
426    ///
427    /// ``` rust
428    /// log::logger().flush();
429    /// ```
430    fn flush(&self) {
431        if let Some(inner) = self.inner.as_ref() {
432            match &inner.sinks {
433                LoggerInnerSink::Once(inner) => {
434                    for sink in inner.iter() {
435                        sink.flush();
436                    }
437                }
438                LoggerInnerSink::Dyn(inner) => {
439                    let sinks = inner.load();
440                    for sink in sinks.iter() {
441                        sink.flush();
442                    }
443                }
444            }
445        }
446    }
447}
448
449static GLOBAL_LOGGER: GlobalLoggerStatic = GlobalLoggerStatic::new();
450
451/// log handle for panic hook
452#[doc(hidden)]
453pub fn log_panic(info: &std::panic::PanicHookInfo) {
454    let bt = Backtrace::new();
455    let mut record = log::Record::builder();
456    record.level(log::Level::Error);
457    if let Some(loc) = info.location() {
458        record.file(Some(loc.file())).line(Some(loc.line()));
459    }
460    log::logger().log(&record.args(format_args!("panic occur: {}\ntrace: {:?}", info, bt)).build());
461    eprint!("panic occur: {} at {:?}\ntrace: {:?}", info, info.location(), bt);
462}
463
464#[inline(always)]
465fn panic_and_exit_hook(info: &std::panic::PanicHookInfo) {
466    log_panic(info);
467    log::logger().flush();
468    let msg = format!("{}", info).to_string();
469    std::panic::resume_unwind(Box::new(msg));
470}
471
472#[inline(always)]
473fn panic_no_exit_hook(info: &std::panic::PanicHookInfo) {
474    log_panic(info);
475    eprint!("not debug version, so don't exit process");
476    log::logger().flush();
477}