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    /// Return the max log level in the log sinks
78    pub fn get_max_level(&self) -> LevelFilter {
79        let mut max_level = Level::Error;
80        for sink in &self.sinks {
81            let level = sink.get_level();
82            if level > max_level {
83                max_level = level;
84            }
85        }
86        return max_level.to_level_filter();
87    }
88
89    /// Calculate checksum of the setting for init() comparison
90    pub(crate) fn cal_checksum(&self) -> u64 {
91        let mut hasher = Box::new(DefaultHasher::new()) as Box<dyn Hasher>;
92        self.dynamic.hash(&mut hasher);
93        self.rotation_signals.hash(&mut hasher);
94        self.panic.hash(&mut hasher);
95        self.continue_when_panic.hash(&mut hasher);
96        for sink in &self.sinks {
97            sink.write_hash(&mut hasher);
98        }
99        hasher.finish()
100    }
101
102    /// Setup global logger.
103    /// Equals to setup_log(builder)
104    pub fn build(self) -> Result<(), ()> {
105        setup_log(self)
106    }
107}
108
109pub(crate) trait SinkConfigTrait {
110    /// get max log level of the sink
111    fn get_level(&self) -> Level;
112    /// Only file sink has path
113    #[allow(dead_code)]
114    fn get_file_path(&self) -> Option<Box<Path>>;
115    /// Calculate hash for config comparison
116    fn write_hash(&self, hasher: &mut Box<dyn Hasher>);
117    /// Build an actual sink from config
118    fn build(&self) -> LogSink;
119}
120
121pub type FormatFunc = fn(FormatRecord) -> String;
122
123/// Custom formatter which adds into a log sink
124#[derive(Clone, Hash)]
125pub struct LogFormat {
126    time_fmt: &'static str,
127    format_fn: FormatFunc,
128}
129
130impl LogFormat {
131    /// # Arguments
132    ///
133    /// time_fmt: refer to chrono::format::strftime.
134    ///
135    /// format_fn:
136    /// Since std::fmt only support compile time format,
137    /// you have to write a static function to format the log line
138    ///
139    /// # Example
140    /// ```
141    /// use captains_log::{LogRawFile, LogFormat, FormatRecord};
142    /// fn format_f(r: FormatRecord) -> String {
143    ///     let time = r.time();
144    ///     let level = r.level();
145    ///     let msg = r.msg();
146    ///     let req_id = r.key("req_id");
147    ///     format!("[{time}][{level}] {msg}{req_id}\n").to_string()
148    /// }
149    /// let log_format = LogFormat::new("%Y-%m-%d %H:%M:%S%.6f", format_f);
150    /// let log_sink = LogRawFile::new("/tmp", "test.log", log::Level::Info, log_format);
151    /// ```
152
153    pub const fn new(time_fmt: &'static str, format_fn: FormatFunc) -> Self {
154        Self { time_fmt, format_fn }
155    }
156
157    #[inline(always)]
158    pub(crate) fn process(&self, now: &Timer, record: &Record) -> String {
159        let time = TimeFormatter { now, fmt_str: self.time_fmt };
160        let r = FormatRecord { record, time };
161        return (self.format_fn)(r);
162    }
163}