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