captains_log/
config.rs

1use crate::log_impl::setup_log;
2use crate::{
3    console_impl::LoggerSinkConsole,
4    file_impl::LoggerSinkFile,
5    formatter::{FormatRecord, TimeFormatter},
6    log_impl::LoggerSink,
7    time::Timer,
8};
9use log::{Level, LevelFilter, Record};
10use std::path::Path;
11
12/// Global config to setup logger
13/// See crate::recipe for usage
14
15#[derive(Default)]
16pub struct Builder {
17    /// When dynamic==true,
18    ///   Can safely re-initialize GlobalLogger even it exists,
19    ///   useful to setup different types of logger in test suits.
20    /// When dynamic==false,
21    ///   Only initialize once, logger sinks setting cannot be change afterwards.
22    ///   More efficient for production environment.
23    pub dynamic: bool,
24
25    /// Listen for signal of log-rotate
26    /// NOTE: Once logger started to listen signal, does not support dynamic reconfigure.
27    pub rotation_signals: Vec<i32>,
28
29    /// Hookup to log error when panic
30    pub panic: bool,
31
32    /// Whether to exit program after panic
33    pub continue_when_panic: bool,
34
35    /// Different types of log sink
36    pub sinks: Vec<Box<dyn SinkConfigTrait>>,
37}
38
39impl Builder {
40    pub fn new() -> Self {
41        Self::default()
42    }
43
44    /// Add log-rotate signal
45    pub fn signal(mut self, signal: i32) -> Self {
46        self.rotation_signals.push(signal);
47        self
48    }
49
50    /// Add file sink
51    pub fn file(mut self, config: LogFile) -> Self {
52        self.sinks.push(Box::new(config));
53        self
54    }
55
56    /// Add console sink
57    pub fn console(mut self, config: LogConsole) -> Self {
58        self.sinks.push(Box::new(config));
59        self
60    }
61
62    /// Return the max log level in the log sinks
63    pub fn get_max_level(&self) -> LevelFilter {
64        let mut max_level = Level::Error;
65        for sink in &self.sinks {
66            let level = sink.get_level();
67            if level > max_level {
68                max_level = level;
69            }
70        }
71        return max_level.to_level_filter();
72    }
73
74    /// Setup global logger.
75    /// Equals to setup_log(builder)
76    pub fn build(self) -> Result<(), ()> {
77        setup_log(self)
78    }
79}
80
81pub trait SinkConfigTrait {
82    fn get_level(&self) -> Level;
83    /// Only LogFile has path
84    fn get_file_path(&self) -> Option<Box<Path>>;
85    fn build(&self) -> LoggerSink;
86}
87
88pub type FormatFunc = fn(FormatRecord) -> String;
89
90#[derive(Clone)]
91/// Custom formatter which adds into a log sink
92pub struct LogFormat {
93    time_fmt: String,
94    format_fn: FormatFunc,
95}
96
97impl LogFormat {
98    /// # Arguments
99    ///
100    /// time_fmt: refer to chrono::format::strftime.
101    ///
102    /// format_fn:
103    /// Since std::fmt only support compile time format,
104    /// you have to write a static function to format the log line
105    ///
106    /// # Example
107    /// ```
108    /// use captains_log::{LogFile, LogFormat, FormatRecord};
109    /// fn format_f(r: FormatRecord) -> String {
110    ///     let time = r.time();
111    ///     let level = r.level();
112    ///     let msg = r.msg();
113    ///     let req_id = r.key("req_id");
114    ///     format!("[{time}][{level}] {msg}{req_id}\n").to_string()
115    /// }
116    /// let log_format = LogFormat::new("%Y-%m-%d %H:%M:%S%.6f", format_f);
117    /// let log_sink = LogFile::new("/tmp", "test.log", log::Level::Info, log_format);
118    /// ```
119
120    pub fn new(time_fmt: &str, format_fn: FormatFunc) -> Self {
121        Self { time_fmt: time_fmt.to_string(), format_fn }
122    }
123
124    #[inline(always)]
125    pub(crate) fn process(&self, now: &Timer, record: &Record) -> String {
126        let time = TimeFormatter { now, fmt_str: &self.time_fmt };
127        let r = FormatRecord { record, time };
128        return (self.format_fn)(r);
129    }
130}
131
132/// Config for file sink
133pub struct LogFile {
134    /// Directory path
135    pub dir: String,
136
137    /// max log level in this file
138    pub level: Level,
139
140    /// filename
141    pub name: String,
142
143    pub format: LogFormat,
144
145    /// path: dir/name
146    pub file_path: Box<Path>,
147}
148
149impl LogFile {
150    pub fn new(dir: &str, name: &str, level: Level, format: LogFormat) -> Self {
151        let file_path = Path::new(dir).join(Path::new(name)).into_boxed_path();
152        Self { dir: dir.to_string(), name: name.to_string(), level, format, file_path }
153    }
154}
155
156impl SinkConfigTrait for LogFile {
157    fn get_level(&self) -> Level {
158        self.level
159    }
160
161    fn get_file_path(&self) -> Option<Box<Path>> {
162        Some(self.file_path.clone())
163    }
164
165    fn build(&self) -> LoggerSink {
166        LoggerSink::File(LoggerSinkFile::new(self))
167    }
168}
169
170#[derive(Copy, Clone, Debug)]
171#[repr(u8)]
172pub enum ConsoleTarget {
173    Stdout = 1,
174    Stderr = 2,
175}
176
177pub struct LogConsole {
178    pub target: ConsoleTarget,
179
180    /// max log level in this file
181    pub level: Level,
182
183    pub format: LogFormat,
184}
185
186impl LogConsole {
187    pub fn new(target: ConsoleTarget, level: Level, format: LogFormat) -> Self {
188        Self { target, level, format }
189    }
190}
191
192impl SinkConfigTrait for LogConsole {
193    fn get_level(&self) -> Level {
194        self.level
195    }
196
197    fn get_file_path(&self) -> Option<Box<Path>> {
198        None
199    }
200
201    fn build(&self) -> LoggerSink {
202        LoggerSink::Console(LoggerSinkConsole::new(self))
203    }
204}