captains_log/
config.rs

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