stderr/log/
mod.rs

1#![allow(non_upper_case_globals)]
2#[macro_use]
3mod macros;
4mod lvl;
5pub use self::lvl::LogLvl;
6use super::StaticMut;
7
8use time::{now, Tm};
9use std::sync::atomic::{AtomicBool, ATOMIC_BOOL_INIT, Ordering};
10use std::collections::BTreeSet as Set;
11use std::collections::btree_set::Iter;
12use std::env::var;
13use std::env::args;
14/// `"LOG"`
15pub static mut ENV_VAR_KEY: &'static str = "LOG";
16/// `["-log", "--log"]`
17pub static mut CLI_OPTION_KEYS: [&'static str; 2] = ["-log", "--log"];
18
19lazy_static!{
20    static ref LOGGER:StaticMut<Logger>=StaticMut::new(Logger::default());
21    static ref LogFmterDefault:StaticMut<LogFmter> =StaticMut::new(LogFmter::default());
22}
23static LogFmterInitialized: AtomicBool = ATOMIC_BOOL_INIT;
24
25/// Logger
26#[derive(Debug,Default)]
27pub struct Logger {
28    initialized: AtomicBool,
29    enabled: AtomicBool,
30    max_lvl: LogLvl,
31    mod_paths: Set<String>,
32    without_cli_options: AtomicBool,
33}
34
35impl Logger {
36    /// `info/*`
37    pub fn set_info_all() {
38        Self::initialized_set(true);
39        Self::enable_set(true);
40        let mut logger = LOGGER.as_mut();
41        logger.mod_paths.insert("*".to_string());
42        logger.max_lvl = LogLvl::Info;
43    }
44    ///` Logger::enable_set(true)`
45    pub fn open() {
46        Self::enable_set(true);
47    }
48    /// `Logger::enable_set(false)`
49    pub fn close() {
50        Self::enable_set(false);
51    }
52    pub fn initialized() -> bool {
53        LOGGER.as_ref().initialized.load(Ordering::Relaxed)
54    }
55    pub fn initialized_set(b: bool) {
56        LOGGER.as_ref().initialized.store(b, Ordering::SeqCst);
57    }
58    pub fn enable() -> bool {
59        LOGGER.as_ref().enabled.load(Ordering::Relaxed)
60    }
61    pub fn enable_set(b: bool) {
62        LOGGER.as_ref().enabled.store(b, Ordering::SeqCst);
63    }
64    pub fn without_cli_options() -> bool {
65        LOGGER.as_ref().without_cli_options.load(Ordering::Relaxed)
66    }
67    pub fn without_cli_options_set(b: bool) {
68        LOGGER
69            .as_ref()
70            .without_cli_options
71            .store(b, Ordering::SeqCst);
72    }
73    pub fn max_lvl() -> &'static LogLvl {
74        &LOGGER.as_ref().max_lvl
75    }
76    /// `mod_path[s]`
77    pub fn mps() -> Iter<'static, String> {
78        LOGGER.as_ref().mod_paths.iter()
79    }
80    /// The current time in the local timezone
81    pub fn now() -> Tm {
82        now()
83    }
84    /// `Logger` init by `crate_name` and `env::var("LOG")`,then `Logger::open()`
85    ///
86    /// `Logger::init(pkg!());` or `logger_int!()`
87    ///
88    /// **Notice**: `Logger` only init once time, other will be ignored.
89    fn cli_options(cli_option_keys: &[&'static str]) -> Option<String> {
90        let mut args: Vec<String> = args().skip(1).collect();
91        let idx = args.as_slice()
92            .iter()
93            .position(|s| cli_option_keys.iter().any(|ss| ss == &s.as_str()));
94        // println!("cli_options: {:?} -> {:?}", idx, args);
95        if let Some(idx) = idx {
96            if args.len() >= idx + 2 {
97                // println!("cli_options/args[idx+1 = {}]: {:?}",idx+1, args[idx + 1]);
98                return Some(args.remove(idx + 1));
99            }
100        }
101        None
102    }
103    pub fn init(crate_name: &'static str) {
104        if Self::initialized() {
105            return;
106        }
107        // println!("LOGER_before_env: {:?}\nenv::var({:?}): {:?}",
108        //          LOGGER.get(),
109        //          ENV_VAR_KEY,
110        //          var(ENV_VAR_KEY));
111        if let Ok(s) = var(unsafe { ENV_VAR_KEY }) {
112            Self::init_with_str(crate_name, s);
113        }
114        // println!("LOGER_after_env: {:?}\ncli::cli_options({:?}): {:?}",
115        //          LOGGER.get(),
116        //          CLI_OPTION_KEYS,
117        //          Self::cli_options(&CLI_OPTION_KEYS[..]));
118        if !Self::initialized() && !Self::without_cli_options() {
119            if let Some(s) = Self::cli_options(unsafe { &CLI_OPTION_KEYS[..] }) {
120                Self::init_with_str(crate_name, s);
121            }
122        }
123        if !Self::initialized() {
124            Self::set_info_all();
125        }
126        // println!("LOGER_after_cli: {:?}", LOGGER.get());
127    }
128    /// `Logger` init by `crate_name` and `var`, then `Logger::open()` if `var` is valid
129    ///
130    ///`Logger::init_with_str(pkg!(),"fht2p");`
131    ///
132    /// **Notice**: `Logger` only init once time, other will be ignored.
133    pub fn init_with_str<S: Into<String>>(mut crate_name: &'static str, var: S) {
134        // no_input    -> info/*
135        // /           -> all/pkg
136        // lvl/mods    -> lvl/mods
137        let mut logger = LOGGER.as_mut();
138        // avoid init second
139        if logger.initialized.load(Ordering::Relaxed) {
140            return;
141        }
142        logger.initialized.store(true, Ordering::SeqCst);
143        // Compatible with previous ``Logger::init(module_path!());
144        if crate_name.contains(':') {
145            let sep_idx = crate_name.find(':').unwrap();
146            crate_name = &crate_name[..sep_idx];
147        }
148
149        let s = var.into();
150        let s = s.trim();
151        let sep_idx = s.find('/');
152        if sep_idx.is_none() {
153            //invalid input
154            return;
155        }
156        let sep_idx = sep_idx.unwrap();
157        let (lvl_str, mut mps_str) = (&s[..sep_idx], &s[sep_idx + 1..]);
158        // println!("lvl_str -> mps_str: {:?} -> {:?}", lvl_str, mps_str);
159        if mps_str.is_empty() {
160            mps_str = crate_name; // "" -> crate_name
161        }
162        // println!("{:?}", LogLvl::from_str(lvl_str));
163        if let Some(lvl) = LogLvl::from_str(lvl_str) {
164            logger.max_lvl = lvl;
165        } else {
166            return;
167        }
168        let mps: Vec<&str> = mps_str
169            .split(',')
170            .map(|ss| ss.trim().trim_matches(':'))
171            .filter(|ss| !ss.is_empty())
172            .collect();
173        // println!("mps: {:?}", mps);
174        if mps.contains(&"*") {
175            logger.mod_paths.insert("*".to_string());
176        } else {
177            mps.into_iter()
178                .map(|ss| logger.mod_paths.insert(ss.to_string()))
179                .last();
180        }
181        Self::open();
182        // println!("LOGER: {:?}", logger);
183    }
184    ///Log message occurs at current module and current LogLvl whether need output
185    pub fn filter(mod_path: &str, lvl: LogLvl) -> bool {
186        let logger = LOGGER.as_ref();
187        // println!("LOGER::filter(mp: {:?},lvl: {:?}): {:?}",
188        //          mod_path,
189        //          lvl,
190        //          logger);
191        if !logger.enabled.load(Ordering::Relaxed) {
192            return false;
193        }
194        if lvl > logger.max_lvl {
195            return false;
196        }
197        //  * match all, avoid panic because of out of index
198        if logger.mod_paths.contains("*") || logger.mod_paths.contains(mod_path) {
199            return true;
200        }
201        for path in &logger.mod_paths {
202            if mod_path.starts_with(path) && (&mod_path[0..path.len() + 1]).ends_with(':') {
203                return true;
204            }
205        }
206        false
207    }
208}
209
210use std::fmt::Arguments;
211/// Log Location
212#[derive(Debug)]
213pub struct LogLoc {
214    mod_path: &'static str,
215    line: u32,
216    column: u32,
217    file: &'static str,
218    time_local: Tm,
219    time_utc: Tm,
220}
221impl LogLoc {
222    /// Call it by `loc!()`
223    pub fn new(mod_path: &'static str, line: u32, column: u32, file: &'static str) -> Self {
224        let time_local = now();
225        LogLoc {
226            mod_path: mod_path,
227            line: line,
228            column: column,
229            file: file,
230            time_local: time_local,
231            time_utc: time_local.to_utc(),
232        }
233    }
234    /// `mod_path`
235    pub fn mp(&self) -> &'static str {
236        self.mod_path
237    }
238    pub fn line(&self) -> &u32 {
239        &self.line
240    }
241    pub fn column(&self) -> &u32 {
242        &self.column
243    }
244    pub fn file(&self) -> &'static str {
245        self.file
246    }
247    pub fn time_local(&self) -> &Tm {
248        &self.time_local
249    }
250    pub fn time_utc(&self) -> &Tm {
251        &self.time_utc
252    }
253}
254
255use std::fmt;
256impl fmt::Display for LogLoc {
257    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
258        write!(f, "{}:{}:{}", self.mp(), self.line(), self.column())
259    }
260}
261
262/// Log Message
263#[derive(Debug)]
264pub struct LogMsg<'a> {
265    loc: LogLoc,
266    msg: Arguments<'a>,
267    lvl: LogLvl,
268}
269impl<'a> LogMsg<'a> {
270    pub fn new(loc: LogLoc, message: Arguments<'a>, lvl: LogLvl) -> Self {
271        LogMsg {
272            loc: loc,
273            msg: message,
274            lvl: lvl,
275        }
276    }
277    pub fn loc(&self) -> &LogLoc {
278        &self.loc
279    }
280    pub fn msg(&'a self) -> &'a Arguments {
281        &self.msg
282    }
283    pub fn lvl(&'a self) -> &'a LogLvl {
284        &self.lvl
285    }
286    /// Format `&self` by `LogFmter`
287    pub fn call(&self) -> String {
288        LogFmter::call(self)
289    }
290}
291
292/// Log Formater
293pub struct LogFmter(Box<Fn(&LogMsg) -> String>);
294
295impl LogFmter {
296    /// Set only once, default is [`fmter`](fn.fmter.html)
297    ///
298    ///`
299    /// LogFmter::set(fmter);
300    ///`
301    pub fn set<F: IntoLogFmter>(f: F) {
302        if LogFmterInitialized.load(Ordering::Relaxed) {
303            return;
304        }
305        LogFmterInitialized.store(true, Ordering::SeqCst);
306        LogFmterDefault.set(f.into())
307    }
308    /// Format `&LogMesg` by `LogFmter`
309    pub fn call(msg: &LogMsg) -> String {
310        (LogFmterDefault.as_ref().0)(msg)
311    }
312}
313
314/// `[Debug!]#main:6:4 ..`
315pub fn fmter(msg: &LogMsg) -> String {
316    format!("[{}!]#{}:{}:{} {}",
317            msg.lvl(),
318            msg.loc().mp(),
319            msg.loc().line(),
320            msg.loc().column(),
321            msg.msg())
322}
323
324///`[2017-05-30 13:10:00 Debug!]#main:12:8 ..`
325pub fn fmter_with_time(msg: &LogMsg) -> String {
326    let t = msg.loc().time_local();
327    format!("[{:04}-{:02}-{:02} {:02}:{:02}:{:02} {}!]#{}:{}:{} {}",
328            t.tm_year + 1900,
329            t.tm_mon + 1,
330            t.tm_mday,
331            t.tm_hour,
332            t.tm_min,
333            t.tm_sec,
334            msg.lvl(),
335            msg.loc().mp(),
336            msg.loc().line(),
337            msg.loc().column(),
338            msg.msg())
339}
340
341impl Default for LogFmter {
342    fn default() -> LogFmter {
343        LogFmter(Box::new(fmter))
344    }
345}
346
347/**
348 Format `LogMsg` by `Fn(&LogMsg) -> String + 'static + Send`
349
350 Default as [`fmter`](fn.fmter.html), you can instead by [`fmter_with_time`](fn.fmter_with_time.html) or write a `fn` by youself.
351*/
352pub trait IntoLogFmter {
353    fn into(self) -> LogFmter;
354}
355
356impl<F: Fn(&LogMsg) -> String + 'static + Send> IntoLogFmter for F {
357    fn into(self) -> LogFmter {
358        LogFmter(Box::new(self))
359    }
360}