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
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
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>,
/// default to true we will hookup to log error when panic, but panic will go on (which works
/// with test cases has `should_panic`)
pub panic_hook: bool,
/// By default this is false,
/// and panic hook will not consume the error (which works with should_panic).
/// If set to true, it will force a double panic which cannot be intercept by catch_unwind.
/// It's useful to force abort on panic under tokio spawn tasks.
///
/// Note that there's only one effective panic hook by default. The later once will override
/// those set previously.
pub force_abort_on_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 { panic_hook: true, ..Default::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
}
/// do not log error on panic
pub fn no_panic_hook(mut self) -> Self {
self.panic_hook = false;
self
}
/// By default this is false, and panic hook will not consume the error (which works with should_panic).
/// If `force_abort_on_panic` set to true, it will force a double panic which cannot be intercept by catch_unwind.
/// It's useful to force abort on panic under tokio spawn tasks.
///
/// # NOTE
///
/// this does not compatible with `should_panic` test cases.
///
/// there's only one effective panic hook by default. The later once will override
/// those set previously.
pub fn force_abort_on_panic(mut self) -> Self {
self.force_abort_on_panic = 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_hook.hash(&mut hasher);
self.force_abort_on_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);
}
}