captains_log/
config.rs

1use crate::buf_file_impl::LogBufFile;
2use crate::console_impl::LogConsole;
3use crate::file_impl::LogRawFile;
4use crate::log_impl::setup_log;
5use crate::{
6    formatter::{FormatRecord, TimeFormatter},
7    log_impl::LogSink,
8    time::Timer,
9};
10use log::{Level, LevelFilter, Record};
11use std::hash::{DefaultHasher, Hash, Hasher};
12use std::path::Path;
13
14/// Global config to setup logger
15/// See crate::recipe for usage
16#[derive(Default)]
17pub struct Builder {
18    /// When dynamic==true,
19    ///   Can safely re-initialize GlobalLogger even it exists,
20    ///   useful to setup different types of logger in test suits.
21    /// When dynamic==false,
22    ///   Only initialize once, logger sinks setting cannot be change afterwards.
23    ///   More efficient for production environment.
24    pub dynamic: bool,
25
26    /// Listen for signal of log-rotate
27    /// NOTE: Once logger started to listen signal, does not support dynamic reconfigure.
28    pub rotation_signals: Vec<i32>,
29
30    /// Hookup to log error when panic
31    pub panic: bool,
32
33    /// Whether to exit program after panic
34    pub continue_when_panic: bool,
35
36    /// Different types of log sink
37    pub(crate) sinks: Vec<Box<dyn SinkConfigTrait>>,
38}
39
40impl Builder {
41    pub fn new() -> Self {
42        Self::default()
43    }
44
45    /// For test cases, set dynamic=true and turn Off signal.
46    /// Call this with pre-set recipe for convenient.
47    pub fn test(mut self) -> Self {
48        self.dynamic = true;
49        self.rotation_signals.clear();
50        self
51    }
52
53    /// Add log-rotate signal
54    pub fn signal(mut self, signal: i32) -> Self {
55        self.rotation_signals.push(signal);
56        self
57    }
58
59    /// Add raw file sink that supports multiprocess atomic append
60    pub fn raw_file(mut self, config: LogRawFile) -> Self {
61        self.sinks.push(Box::new(config));
62        self
63    }
64
65    /// Add buffered file sink which merged I/O and delay flush
66    pub fn buf_file(mut self, config: LogBufFile) -> Self {
67        self.sinks.push(Box::new(config));
68        self
69    }
70
71    /// Add console sink
72    pub fn console(mut self, config: LogConsole) -> Self {
73        self.sinks.push(Box::new(config));
74        self
75    }
76
77    #[cfg(feature = "syslog")]
78    /// Add syslog sink
79    pub fn syslog(mut self, config: crate::Syslog) -> Self {
80        self.sinks.push(Box::new(config));
81        self
82    }
83
84    /// Return the max log level in the log sinks
85    pub fn get_max_level(&self) -> LevelFilter {
86        let mut max_level = Level::Error;
87        for sink in &self.sinks {
88            let level = sink.get_level();
89            if level > max_level {
90                max_level = level;
91            }
92        }
93        return max_level.to_level_filter();
94    }
95
96    /// Calculate checksum of the setting for init() comparison
97    pub(crate) fn cal_checksum(&self) -> u64 {
98        let mut hasher = Box::new(DefaultHasher::new()) as Box<dyn Hasher>;
99        self.dynamic.hash(&mut hasher);
100        self.rotation_signals.hash(&mut hasher);
101        self.panic.hash(&mut hasher);
102        self.continue_when_panic.hash(&mut hasher);
103        for sink in &self.sinks {
104            sink.write_hash(&mut hasher);
105        }
106        hasher.finish()
107    }
108
109    /// Setup global logger.
110    /// Equals to setup_log(builder)
111    pub fn build(self) -> Result<(), ()> {
112        setup_log(self)
113    }
114}
115
116pub(crate) trait SinkConfigTrait {
117    /// get max log level of the sink
118    fn get_level(&self) -> Level;
119    /// Only file sink has path
120    #[allow(dead_code)]
121    fn get_file_path(&self) -> Option<Box<Path>>;
122    /// Calculate hash for config comparison
123    fn write_hash(&self, hasher: &mut Box<dyn Hasher>);
124    /// Build an actual sink from config
125    fn build(&self) -> LogSink;
126}
127
128pub type FormatFunc = fn(FormatRecord) -> String;
129
130/// Custom formatter which adds into a log sink
131#[derive(Clone, Hash)]
132pub struct LogFormat {
133    time_fmt: &'static str,
134    format_fn: FormatFunc,
135}
136
137impl LogFormat {
138    /// # Arguments
139    ///
140    /// time_fmt: refer to chrono::format::strftime.
141    ///
142    /// format_fn:
143    /// Since std::fmt only support compile time format,
144    /// you have to write a static function to format the log line
145    ///
146    /// # Example
147    /// ```
148    /// use captains_log::{LogRawFile, LogFormat, FormatRecord};
149    /// fn format_f(r: FormatRecord) -> String {
150    ///     let time = r.time();
151    ///     let level = r.level();
152    ///     let msg = r.msg();
153    ///     let req_id = r.key("req_id");
154    ///     format!("[{time}][{level}] {msg}{req_id}\n").to_string()
155    /// }
156    /// let log_format = LogFormat::new("%Y-%m-%d %H:%M:%S%.6f", format_f);
157    /// let log_sink = LogRawFile::new("/tmp", "test.log", log::Level::Info, log_format);
158    /// ```
159
160    pub const fn new(time_fmt: &'static str, format_fn: FormatFunc) -> Self {
161        Self { time_fmt, format_fn }
162    }
163
164    #[inline(always)]
165    pub(crate) fn process(&self, now: &Timer, record: &Record) -> String {
166        let time = TimeFormatter { now, fmt_str: self.time_fmt };
167        let r = FormatRecord { record, time };
168        return (self.format_fn)(r);
169    }
170}