captains_log/config.rs
1use crate::log_impl::setup_log;
2use crate::{
3 formatter::{FormatRecord, TimeFormatter},
4 log_impl::LogSink,
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(crate) sinks: Vec<Box<dyn SinkConfigTrait>>,
35}
36
37impl Builder {
38 pub fn new() -> Self {
39 Self::default()
40 }
41
42 /// For test cases, set dynamic=true and turn Off signal.
43 /// Call this with pre-set recipe for convenient.
44 pub fn test(mut self) -> Self {
45 self.dynamic = true;
46 self.rotation_signals.clear();
47 self
48 }
49
50 /// Add log-rotate signal
51 pub fn signal(mut self, signal: i32) -> Self {
52 self.rotation_signals.push(signal);
53 self
54 }
55
56 /// Add different types of log sink config, can be called multiple times.
57 pub fn add_sink<S: SinkConfigTrait>(mut self, config: S) -> 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 /// Calculate checksum of the setting for init() comparison
75 pub(crate) fn cal_checksum(&self) -> u64 {
76 let mut hasher = Box::new(DefaultHasher::new()) as Box<dyn Hasher>;
77 self.dynamic.hash(&mut hasher);
78 self.rotation_signals.hash(&mut hasher);
79 self.panic.hash(&mut hasher);
80 self.continue_when_panic.hash(&mut hasher);
81 for sink in &self.sinks {
82 sink.write_hash(&mut hasher);
83 }
84 hasher.finish()
85 }
86
87 /// Setup global logger.
88 /// Equals to setup_log(builder)
89 pub fn build(self) -> Result<(), ()> {
90 setup_log(self)
91 }
92}
93
94pub(crate) trait SinkConfigBuild {
95 /// Build an actual sink from config
96 fn build(&self) -> LogSink;
97}
98
99#[allow(private_bounds)]
100pub trait SinkConfigTrait: 'static + SinkConfigBuild {
101 /// get max log level of the sink
102 fn get_level(&self) -> Level;
103 /// Only file sink has path
104 #[allow(dead_code)]
105 fn get_file_path(&self) -> Option<Box<Path>>;
106 /// Calculate hash for config comparison
107 fn write_hash(&self, hasher: &mut Box<dyn Hasher>);
108}
109
110pub type FormatFunc = fn(FormatRecord) -> String;
111
112/// Custom formatter which adds into a log sink
113#[derive(Clone, Hash)]
114pub struct LogFormat {
115 time_fmt: &'static str,
116 format_fn: FormatFunc,
117}
118
119impl LogFormat {
120 /// # Arguments
121 ///
122 /// time_fmt: refer to chrono::format::strftime.
123 ///
124 /// format_fn:
125 /// Since std::fmt only support compile time format,
126 /// you have to write a static function to format the log line
127 ///
128 /// # Example
129 /// ```
130 /// use captains_log::{LogRawFile, LogFormat, FormatRecord};
131 /// fn format_f(r: FormatRecord) -> String {
132 /// let time = r.time();
133 /// let level = r.level();
134 /// let msg = r.msg();
135 /// let req_id = r.key("req_id");
136 /// format!("[{time}][{level}] {msg}{req_id}\n").to_string()
137 /// }
138 /// let log_format = LogFormat::new("%Y-%m-%d %H:%M:%S%.6f", format_f);
139 /// let log_sink = LogRawFile::new("/tmp", "test.log", log::Level::Info, log_format);
140 /// ```
141
142 pub const fn new(time_fmt: &'static str, format_fn: FormatFunc) -> Self {
143 Self { time_fmt, format_fn }
144 }
145
146 #[inline(always)]
147 pub(crate) fn process(&self, now: &Timer, record: &Record) -> String {
148 let time = TimeFormatter { now, fmt_str: self.time_fmt };
149 let r = FormatRecord { record, time };
150 return (self.format_fn)(r);
151 }
152}