captains_log/
log_impl.rs

1use crate::{
2    config::Builder, console_impl::LoggerSinkConsole, file_impl::LoggerSinkFile, time::Timer,
3};
4use arc_swap::ArcSwap;
5use backtrace::Backtrace;
6use lazy_static::lazy_static;
7use log::*;
8use parking_lot::Mutex;
9use signal_hook::iterator::Signals;
10use std::mem::transmute;
11use std::sync::{
12    Arc,
13    atomic::{AtomicBool, Ordering},
14};
15use std::thread;
16
17#[enum_dispatch]
18pub(crate) trait LoggerSinkTrait {
19    fn reopen(&self) -> std::io::Result<()>;
20
21    fn log(&self, now: &Timer, r: &Record);
22}
23
24#[enum_dispatch(LoggerSinkTrait)]
25pub enum LoggerSink {
26    File(LoggerSinkFile),
27    Console(LoggerSinkConsole),
28}
29
30/// Global static structure to hold the logger
31struct GlobalLogger {
32    /// checksum for config comparison
33    config_checksum: u64,
34    /// Global static needs initialization when declaring,
35    /// default to be empty
36    inner: Option<LoggerInner>,
37    signal_listener: AtomicBool,
38}
39
40enum LoggerInner {
41    Once(Vec<LoggerSink>),
42    // using ArcSwap has more cost
43    Dyn(ArcSwap<Vec<LoggerSink>>),
44}
45
46#[inline(always)]
47fn panic_or_error() {
48    #[cfg(debug_assertions)]
49    {
50        panic!("GlobalLogger cannot be initialized twice on dynamic==false");
51    }
52    #[cfg(not(debug_assertions))]
53    {
54        eprintln!("GlobalLogger cannot be initialized twice on dynamic==false");
55    }
56}
57
58impl LoggerInner {
59    #[allow(dead_code)]
60    fn set(&self, sinks: Vec<LoggerSink>) {
61        match &self {
62            Self::Once(_) => {
63                panic_or_error();
64            }
65            Self::Dyn(d) => {
66                d.store(Arc::new(sinks));
67            }
68        }
69    }
70}
71
72impl GlobalLogger {
73    pub fn reopen(&mut self) -> std::io::Result<()> {
74        if let Some(inner) = self.inner.as_ref() {
75            match &inner {
76                LoggerInner::Once(inner) => {
77                    for sink in inner.iter() {
78                        sink.reopen()?;
79                    }
80                }
81                LoggerInner::Dyn(inner) => {
82                    let sinks = inner.load();
83                    for sink in sinks.iter() {
84                        sink.reopen()?;
85                    }
86                }
87            }
88        }
89        println!("log sinks re-opened");
90        Ok(())
91    }
92
93    #[allow(dead_code)]
94    fn init(&mut self, builder: &Builder) -> std::io::Result<bool> {
95        let new_checksum = builder.cal_checksum();
96        if self.inner.is_some() {
97            if self.config_checksum == new_checksum {
98                // Config is the same, no need to reinit
99                self.reopen()?;
100                return Ok(true);
101            }
102            if !builder.dynamic {
103                panic_or_error();
104                return Ok(false);
105            }
106        }
107        let mut sinks = Vec::new();
108        for config in &builder.sinks {
109            let logger_sink = config.build();
110            logger_sink.reopen()?;
111            sinks.push(logger_sink);
112        }
113
114        if let Some(inner) = self.inner.as_ref() {
115            inner.set(sinks);
116        } else {
117            if builder.dynamic {
118                self.inner.replace(LoggerInner::Dyn(ArcSwap::new(Arc::new(sinks))));
119            } else {
120                self.inner.replace(LoggerInner::Once(sinks));
121            }
122        }
123        self.config_checksum = new_checksum;
124
125        let _ = unsafe { set_logger(transmute::<&Self, &'static Self>(self)) };
126
127        // panic hook can be set multiple times
128        if builder.continue_when_panic {
129            std::panic::set_hook(Box::new(panic_no_exit_hook));
130        } else {
131            std::panic::set_hook(Box::new(panic_and_exit_hook));
132        }
133        Ok(true)
134    }
135}
136
137impl Log for GlobalLogger {
138    #[inline(always)]
139    fn enabled(&self, _m: &Metadata) -> bool {
140        true
141    }
142
143    #[inline(always)]
144    fn log(&self, r: &Record) {
145        let now = Timer::new();
146        if let Some(inner) = self.inner.as_ref() {
147            match &inner {
148                LoggerInner::Once(inner) => {
149                    for sink in inner.iter() {
150                        sink.log(&now, r);
151                    }
152                }
153                LoggerInner::Dyn(inner) => {
154                    let sinks = inner.load();
155                    for sink in sinks.iter() {
156                        sink.log(&now, r);
157                    }
158                }
159            }
160        }
161    }
162
163    fn flush(&self) {}
164}
165
166lazy_static! {
167    // Mutex only access on init and reopen, bypassed while logging,
168    // because crate log only use const raw pointer to access GlobalLogger.
169    static ref GLOBAL_LOGGER: Mutex<GlobalLogger> = Mutex::new(GlobalLogger {
170        config_checksum: 0,
171        inner: None ,
172        signal_listener: AtomicBool::new(false),
173    });
174}
175
176/// log handle for panic hook
177#[doc(hidden)]
178pub fn log_panic(info: &std::panic::PanicHookInfo) {
179    let bt = Backtrace::new();
180    let mut record = log::Record::builder();
181    record.level(log::Level::Error);
182    if let Some(loc) = info.location() {
183        record.file(Some(loc.file())).line(Some(loc.line()));
184    }
185    log::logger().log(&record.args(format_args!("panic occur: {}\ntrace: {:?}", info, bt)).build());
186    eprint!("panic occur: {} at {:?}\ntrace: {:?}", info, info.location(), bt);
187}
188
189#[inline(always)]
190fn panic_and_exit_hook(info: &std::panic::PanicHookInfo) {
191    log_panic(info);
192    let msg = format!("{}", info).to_string();
193    std::panic::resume_unwind(Box::new(msg));
194}
195
196#[inline(always)]
197fn panic_no_exit_hook(info: &std::panic::PanicHookInfo) {
198    log_panic(info);
199    eprint!("not debug version, so don't exit process");
200}
201
202fn signal_listener(signals: Vec<i32>) {
203    let started;
204    {
205        let global_logger = GLOBAL_LOGGER.lock();
206        started = global_logger.signal_listener.swap(true, Ordering::SeqCst);
207    }
208    if started {
209        // NOTE: Once logger started to listen signal, does not support dynamic reconfigure.
210        eprintln!("signal listener already started");
211        return;
212    }
213    thread::spawn(move || {
214        let mut signals = Signals::new(&signals).unwrap();
215        for __sig in signals.forever() {
216            {
217                let mut global_logger = GLOBAL_LOGGER.lock();
218                let _ = global_logger.reopen();
219            }
220        }
221    });
222}
223
224/// Initialize global logger from Builder
225pub fn setup_log(builder: Builder) -> Result<(), ()> {
226    {
227        let mut global_logger = GLOBAL_LOGGER.lock();
228
229        match global_logger.init(&builder) {
230            Err(e) => {
231                println!("Initialize logger failed: {:?}", e);
232                return Err(());
233            }
234            Ok(false) => return Err(()),
235            Ok(true) => {}
236        }
237        set_max_level(builder.get_max_level());
238    }
239    let signals = builder.rotation_signals.clone();
240    if signals.len() > 0 {
241        signal_listener(signals);
242    }
243    Ok(())
244}