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