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