Skip to main content

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        if builder.panic_hook {
185            // panic hook can be set multiple times
186            // there's only one effective panic hook by default. The later once will override
187            // those set previously.
188            if !builder.force_abort_on_panic {
189                std::panic::set_hook(Box::new(panic_hook_log));
190            } else {
191                std::panic::set_hook(Box::new(panic_hook_force_exit));
192            }
193        }
194        let signals = builder.rotation_signals.clone();
195        if !signals.is_empty() && !logger.signal_listener.swap(true, Ordering::SeqCst) {
196            thread::spawn(move || {
197                GLOBAL_LOGGER.get_logger().listener_for_signal(signals);
198            });
199        }
200        Ok(logger)
201    } else {
202        Ok(GLOBAL_LOGGER.get_logger())
203    }
204}
205
206/// Return the GlobalLogger after initialized.
207pub fn get_global_logger() -> Option<&'static GlobalLogger> {
208    GLOBAL_LOGGER.try_get_logger()
209}
210
211/// Global static structure to hold the logger
212pub struct GlobalLogger {
213    /// checksum for config comparison
214    config_checksum: AtomicU64,
215    /// Global static needs initialization when declaring,
216    /// default to be empty
217    inner: Option<LoggerInner>,
218    signal_listener: AtomicBool,
219    #[cfg(feature = "tracing")]
220    tracing_inited: AtomicBool,
221}
222
223enum LoggerInnerSink {
224    Once(Vec<LogSink>),
225    // using ArcSwap has more cost
226    Dyn(ArcSwap<Vec<LogSink>>),
227}
228
229struct LoggerInner {
230    sinks: LoggerInnerSink,
231}
232
233unsafe impl Send for LoggerInner {}
234unsafe impl Sync for LoggerInner {}
235
236impl LoggerInner {
237    #[inline]
238    fn new(dynamic: bool, sinks: Vec<LogSink>) -> Self {
239        let sinks = if dynamic {
240            LoggerInnerSink::Dyn(ArcSwap::new(Arc::new(sinks)))
241        } else {
242            LoggerInnerSink::Once(sinks)
243        };
244        Self { sinks }
245    }
246
247    #[inline]
248    fn set(&self, sinks: Vec<LogSink>) -> std::io::Result<()> {
249        match &self.sinks {
250            LoggerInnerSink::Once(_) => {
251                let e = Error::other("previous logger does not init with dynamic=true");
252                return Err(e);
253            }
254            LoggerInnerSink::Dyn(d) => {
255                d.store(Arc::new(sinks));
256            }
257        }
258        Ok(())
259    }
260}
261
262impl GlobalLogger {
263    fn listener_for_signal(&self, signals: Vec<i32>) {
264        println!("signal_listener started");
265        let mut signals = Signals::new(&signals).unwrap();
266        for __sig in signals.forever() {
267            let _ = self.reopen();
268        }
269        println!("signal_listener exit");
270    }
271
272    /// On program/test Initialize
273    fn open(&self) -> std::io::Result<()> {
274        if let Some(inner) = self.inner.as_ref() {
275            match &inner.sinks {
276                LoggerInnerSink::Once(inner) => {
277                    for sink in inner.iter() {
278                        sink.open()?;
279                    }
280                }
281                LoggerInnerSink::Dyn(inner) => {
282                    let sinks = inner.load();
283                    for sink in sinks.iter() {
284                        sink.open()?;
285                    }
286                }
287            }
288        }
289        println!("log sinks opened");
290        Ok(())
291    }
292
293    /// Reopen file. This is a signal handler, also can be called manually.
294    pub fn reopen(&self) -> std::io::Result<()> {
295        if let Some(inner) = self.inner.as_ref() {
296            match &inner.sinks {
297                LoggerInnerSink::Once(inner) => {
298                    for sink in inner.iter() {
299                        sink.reopen()?;
300                    }
301                }
302                LoggerInnerSink::Dyn(inner) => {
303                    let sinks = inner.load();
304                    for sink in sinks.iter() {
305                        sink.reopen()?;
306                    }
307                }
308            }
309        }
310        println!("log sinks re-opened");
311        Ok(())
312    }
313
314    /// Return Some(true) to skip, Some(false) to reinit, None to init
315    #[inline]
316    fn check_the_same(&self, builder: &Builder) -> Option<bool> {
317        if self.inner.is_some() {
318            return Some(self.config_checksum.load(Ordering::Acquire) == builder.cal_checksum());
319        }
320        None
321    }
322
323    /// Re-configure the logger sink
324    fn reinit(&self, builder: &Builder) -> std::io::Result<()> {
325        let sinks = builder.build_sinks()?;
326        if let Some(inner) = self.inner.as_ref() {
327            inner.set(sinks)?;
328            self.config_checksum.store(builder.cal_checksum(), Ordering::Release);
329        } else {
330            unreachable!();
331        }
332        Ok(())
333    }
334
335    fn init(&mut self, builder: &Builder) -> std::io::Result<()> {
336        let sinks = builder.build_sinks()?;
337        assert!(self.inner.is_none());
338        self.inner.replace(LoggerInner::new(builder.dynamic, sinks));
339        self.config_checksum.store(builder.cal_checksum(), Ordering::Release);
340        Ok(())
341    }
342
343    #[cfg(feature = "tracing")]
344    #[inline]
345    fn init_tracing_global(&'static self) -> Result<(), Error> {
346        let dist = self.tracing_dispatch::<TracingText>()?;
347        if let Err(_) = dispatcher::set_global_default(dist) {
348            let e = Error::other("tracing global dispatcher already exists");
349            eprintln!("{:?}", e);
350            return Err(e);
351        }
352        self.tracing_inited.store(true, Ordering::SeqCst);
353        Ok(())
354    }
355
356    #[cfg(feature = "tracing")]
357    #[cfg_attr(docsrs, doc(cfg(feature = "tracing")))]
358    /// Initialize a layer for tracing. Use this when you stacking multiple tracing layers.
359    ///
360    /// For usage, checkout the doc in [crate::tracing_bridge]
361    ///
362    /// # NOTE:
363    ///
364    /// In order to prevent duplicate output, it will fail if out tracing global subscriber
365    /// has been initialized.
366    pub fn tracing_layer<F: TracingFormatter>(
367        &'static self,
368    ) -> std::io::Result<CaptainsLogLayer<F>> {
369        if self.tracing_inited.load(Ordering::SeqCst) {
370            let e = Error::other("global tracing dispatcher exists");
371            eprintln!("{:?}", e);
372            return Err(e);
373        }
374        return Ok(CaptainsLogLayer::<F>::new(self));
375    }
376
377    #[cfg(feature = "tracing")]
378    #[cfg_attr(docsrs, doc(cfg(feature = "tracing")))]
379    /// Initialize a tracing Dispatch, you can set_global_default() or use in a scope.
380    ///
381    /// For usage, checkout the doc in [crate::tracing_bridge]
382    ///
383    /// # NOTE:
384    ///
385    /// In order to prevent duplicate output, it will fail if out tracing global subscriber
386    /// has been initialized.
387    pub fn tracing_dispatch<F: TracingFormatter>(&'static self) -> std::io::Result<Dispatch> {
388        if self.tracing_inited.load(Ordering::SeqCst) {
389            let e = Error::other("global tracing dispatcher exists");
390            eprintln!("{:?}", e);
391            return Err(e);
392        }
393        return Ok(Dispatch::new(
394            tracing_subscriber::registry().with(self.tracing_layer::<F>().unwrap()),
395        ));
396    }
397}
398
399impl log::Log for GlobalLogger {
400    #[inline(always)]
401    fn enabled(&self, _m: &log::Metadata) -> bool {
402        true
403    }
404
405    #[inline(always)]
406    fn log(&self, r: &log::Record) {
407        let now = Timer::new();
408        if let Some(inner) = self.inner.as_ref() {
409            match &inner.sinks {
410                LoggerInnerSink::Once(inner) => {
411                    for sink in inner.iter() {
412                        sink.log(&now, r);
413                    }
414                }
415                LoggerInnerSink::Dyn(inner) => {
416                    let sinks = inner.load();
417                    for sink in sinks.iter() {
418                        sink.log(&now, r);
419                    }
420                }
421            }
422        }
423    }
424
425    /// Can be call manually on program shutdown (If you have a buffered log sink)
426    ///
427    /// # Example
428    ///
429    /// ``` rust
430    /// log::logger().flush();
431    /// ```
432    fn flush(&self) {
433        if let Some(inner) = self.inner.as_ref() {
434            match &inner.sinks {
435                LoggerInnerSink::Once(inner) => {
436                    for sink in inner.iter() {
437                        sink.flush();
438                    }
439                }
440                LoggerInnerSink::Dyn(inner) => {
441                    let sinks = inner.load();
442                    for sink in sinks.iter() {
443                        sink.flush();
444                    }
445                }
446            }
447        }
448    }
449}
450
451static GLOBAL_LOGGER: GlobalLoggerStatic = GlobalLoggerStatic::new();
452
453/// log handle for panic hook
454#[doc(hidden)]
455pub fn log_panic(info: &std::panic::PanicHookInfo) {
456    let bt = Backtrace::new();
457    let mut record = log::Record::builder();
458    record.level(log::Level::Error);
459    if let Some(loc) = info.location() {
460        record.file(Some(loc.file())).line(Some(loc.line()));
461    }
462    log::logger().log(&record.args(format_args!("panic occur: {}\ntrace: {:?}", info, bt)).build());
463    eprint!("panic occur: {} at {:?}\ntrace: {:?}", info, info.location(), bt);
464}
465
466#[inline(always)]
467fn panic_hook_force_exit(info: &std::panic::PanicHookInfo) {
468    log_panic(info);
469    log::logger().flush();
470    let msg = format!("{}", info).to_string();
471    std::panic::resume_unwind(Box::new(msg));
472}
473
474#[inline(always)]
475fn panic_hook_log(info: &std::panic::PanicHookInfo) {
476    log_panic(info);
477    log::logger().flush();
478}