1pub use log::{debug, error, info, log, log_enabled, trace, warn};
9
10use chrono::Local;
11use crossbeam_channel::{bounded, Receiver};
12use libc::c_int;
13use log::LevelFilter;
14use log4rs::append::console::ConsoleAppender;
15use log4rs::append::file::FileAppender;
16use log4rs::config::{Appender, Config, Logger, Root};
17use log4rs::encode::pattern::PatternEncoder;
18use std::env;
19use std::fs;
20use std::io::Error;
21use std::str::FromStr;
22use std::sync::Once;
23use std::thread;
24use std::vec::Vec;
25
26pub enum LogFavour<'a> {
27 Stdout(&'a str),
28 File(&'a str),
29}
30
31#[derive(Debug, Clone)]
32struct Directive {
33 name: String,
35 level: LevelFilter,
37}
38
39static INIT_LOG: Once = Once::new();
40
41fn notify(signals: &[c_int]) -> Result<Receiver<c_int>, Error> {
42 let (s, r) = bounded(100);
43 let mut signals = signal_hook::iterator::Signals::new(signals)?;
44 thread::spawn(move || {
45 for signal in signals.forever() {
46 let _ = s.send(signal);
47 }
48 });
49 Ok(r)
50}
51
52pub fn init_config(favour: &LogFavour) {
53 INIT_LOG.call_once(|| {
54 let directives: Vec<Directive> = match env::var("RUST_LOG") {
56 Ok(s) => parse_env(&s),
57 Err(_) => Vec::new(),
58 };
59
60 match favour {
61 LogFavour::Stdout(service_name) => {
62 let config = config_console_appender(service_name, directives);
63 log4rs::init_config(config).unwrap();
64 }
65 LogFavour::File(service_name) => {
66 let log_name = format!("logs/{}.log", service_name);
68 let directives_clone = directives.clone();
69 let config = config_file_appender(&log_name, directives_clone);
70 let handle = log4rs::init_config(config).unwrap();
71
72 let signal = notify(&[signal_hook::consts::SIGUSR1]).unwrap();
74
75 let service_name_clone = service_name.to_string();
80 thread::spawn(move || {
81 loop {
82 signal.recv().unwrap();
84
85 let time_stamp = Local::now().format("_%Y-%m-%d_%H-%M-%S");
87 let log_rotate_name =
88 format!("logs/{}{}.log", &service_name_clone, time_stamp);
89 if let Err(e) = fs::rename(&log_name, log_rotate_name) {
90 warn!("logrotate failed because of {:?}", e.kind());
91 continue;
92 }
93
94 let directives_clone = directives.clone();
96 let new_config = config_file_appender(&log_name, directives_clone);
97 handle.set_config(new_config);
98 }
99 });
100 }
101 }
102 });
103}
104
105pub fn init() {
107 init_config(&LogFavour::Stdout(""));
108}
109
110pub fn silent() {
112 INIT_LOG.call_once(|| {
113 let config = Config::builder()
114 .build(Root::builder().build(LevelFilter::Off))
115 .unwrap();
116 log4rs::init_config(config).unwrap();
117 });
118}
119
120fn parse_env(env: &str) -> Vec<Directive> {
122 let mut directives = Vec::new();
123
124 for s in env.split(',') {
125 if s.is_empty() {
126 continue;
127 }
128 let mut parts = s.split('=');
129 let (log_level, name) = match (parts.next(), parts.next().map(str::trim), parts.next()) {
130 (Some(part0), None, None) => match LevelFilter::from_str(part0) {
131 Ok(num) => {
132 println!(
133 "warning: log level '{}' need explicit crate or module name.",
134 num
135 );
136 continue;
137 }
138 Err(_) => (LevelFilter::Info, part0),
139 },
140 (Some(part0), Some(""), None) => (LevelFilter::Info, part0),
141 (Some(part0), Some(part1), None) => match LevelFilter::from_str(part1) {
142 Ok(num) => (num, part0),
143 _ => {
144 println!(
145 "warning: invalid logging spec '{}', \
146 ignoring it",
147 part1
148 );
149 continue;
150 }
151 },
152 _ => {
153 println!(
154 "warning: invalid logging spec '{}', \
155 ignoring it",
156 s
157 );
158 continue;
159 }
160 };
161
162 if !name.is_empty() {
163 directives.push(Directive {
164 name: name.to_string(),
165 level: log_level,
166 });
167 }
168 }
169
170 directives
171}
172
173fn create_loggers(directives: Vec<Directive>, appender: &str) -> Vec<Logger> {
174 let mut loggers = Vec::new();
175
176 if directives.is_empty() {
177 return loggers;
178 }
179
180 for directive in directives {
182 let appender_clone = appender.to_string();
183 let logger = Logger::builder()
184 .appender(appender_clone)
185 .additive(false)
186 .build(directive.name, directive.level);
187 loggers.push(logger);
188 }
189
190 loggers
191}
192
193fn config_file_appender(file_path: &str, directives: Vec<Directive>) -> Config {
195 let pattern = "{{\"_CMB_LOG_SPEC_VERSION\": \"2.0\",\"method\": \"{t:20.20} - {L:5}\", \"type\": \"BASETYPE\", \"level\": \"{l:5}\", \"tid\": \"{I}\", \"ts\": \"{d(%Y-%m-%dT%H:%M:%S%.9fZ)}\", \"content\": \"{m}\"}}{n}";
196 let requests = FileAppender::builder()
197 .encoder(Box::new(PatternEncoder::new(&pattern)))
198 .build(file_path)
199 .unwrap();
200
201 let mut config_builder =
202 Config::builder().appender(Appender::builder().build("requests", Box::new(requests)));
203
204 let loggers = create_loggers(directives, "requests");
205
206 if !loggers.is_empty() {
208 config_builder = config_builder.loggers(loggers.into_iter());
209 }
210
211 config_builder
213 .build(
214 Root::builder()
215 .appender("requests")
216 .build(LevelFilter::Info),
217 )
218 .unwrap()
219}
220
221fn config_console_appender(_service_name: &str, directives: Vec<Directive>) -> Config {
223 let pattern = "{{\"_CMB_LOG_SPEC_VERSION\": \"2.0\",\"method\": \"{t:20.20} - {L:5}\", \"type\": \"BASETYPE\", \"level\": \"{l:5}\", \"tid\": \"{I}\", \"ts\": \"{d(%Y-%m-%dT%H:%M:%S%.9fZ)}\", \"content\": \"{m}\"}}{n}";
224 let stdout = ConsoleAppender::builder()
225 .encoder(Box::new(PatternEncoder::new(&pattern)))
226 .build();
227
228 let mut config_builder =
229 Config::builder().appender(Appender::builder().build("stdout", Box::new(stdout)));
230
231 let loggers = create_loggers(directives, "stdout");
232
233 if !loggers.is_empty() {
235 config_builder = config_builder.loggers(loggers.into_iter());
236 }
237
238 config_builder
240 .build(Root::builder().appender("stdout").build(LevelFilter::Info))
241 .unwrap()
242}
243
244#[cfg(test)]
245mod tests {
246
247 use super::parse_env;
248 use log::LevelFilter;
249
250 #[test]
251 fn parse_env_valid() {
252 let directives = parse_env("crate1::mod1,crate1::mod2=debug,crate2=trace");
253 assert_eq!(directives.len(), 3);
254 assert_eq!(directives[0].name, "crate1::mod1".to_string());
255 assert_eq!(directives[0].level, LevelFilter::Info);
256
257 assert_eq!(directives[1].name, "crate1::mod2".to_string());
258 assert_eq!(directives[1].level, LevelFilter::Debug);
259
260 assert_eq!(directives[2].name, "crate2".to_string());
261 assert_eq!(directives[2].level, LevelFilter::Trace);
262 }
263
264 #[test]
265 fn parse_env_invalid_crate() {
266 let directives = parse_env("crate1::mod=warn=info,crate2=warn");
267 assert_eq!(directives.len(), 1);
268 assert_eq!(directives[0].name, "crate2".to_string());
269 assert_eq!(directives[0].level, LevelFilter::Warn);
270 }
271
272 #[test]
273 fn parse_env_invalid_level() {
274 let directives = parse_env("crate1::mod=wrong,crate2=error");
275 assert_eq!(directives.len(), 1);
276 assert_eq!(directives[0].name, "crate2".to_string());
277 assert_eq!(directives[0].level, LevelFilter::Error);
278 }
279
280 #[test]
281 fn parse_env_empty() {
282 let directives = parse_env("crate1::mod=,=trace");
283 assert_eq!(directives.len(), 1);
284 assert_eq!(directives[0].name, "crate1::mod".to_string());
285 assert_eq!(directives[0].level, LevelFilter::Info);
286 }
287}