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
//! This crate provides a wrapper over `log` crate that allows you
//! to specify the type of messages and automatically suppress
//! types of messages that are overwhelmingly sent.
//!
//! Basic usage:
//! ```
//! clilog::info!(I01TEST, "test message");
//! ```
//! when message tagged `I01TEST` is sent over 20 times, a tip will be
//! printed and further such messages will be suppressed.
//!
//! At the end, you can optionally print a statistics of how many messages
//! are suppressed.
//! (TODO: not implemented yet.)

use std::sync::Mutex;
use std::collections::HashMap;

pub use log;
pub use paste;

lazy_static::lazy_static! {
    static ref PRINT_COUNT: Mutex<HashMap<(log::Level, &'static str), u64>>
        = Mutex::new(HashMap::new());
}
pub const MAX_PRINT_COUNT: u64 = 20;

/// convenient shortcut that you can call in your `main()`
/// to initialize logging with stderr color output.
pub fn init_stderr_color_debug() {
    use simplelog::*;
    TermLogger::init(
        LevelFilter::Debug,
        ConfigBuilder::new()
            .set_location_level(LevelFilter::Debug)
            .set_thread_level(LevelFilter::Trace)
            .build(),
        TerminalMode::Stderr,
        ColorChoice::Auto,
    ).unwrap();
}

/// get and increment by 1 the number of occurrence for a specific
/// kind of message. this is not intended to be used separately,
/// but is expected to be called from our macro expansion.
pub fn obtain_count(typ: log::Level, id: &'static str) -> u64 {
    let mut print_counts = PRINT_COUNT.lock().unwrap();
    match print_counts.get_mut(&(typ, id)) {
        Some(v) => {
            *v += 1;
            *v
        },
        None => {
            print_counts.insert((typ, id), 1);
            1
        }
    }
}

/// general logging macro that can either log normally, or
/// check the count before logging.
/// this is the basis of other macros like `info`, `warn`, etc.
#[macro_export]
macro_rules! log_monitor {
    ($typ:ident, $id:ident, $fmt:expr $(,$param:expr)*) => {{
        let count = $crate::obtain_count(
            $crate::paste::paste!($crate::log::Level::[<$typ:camel>]),
            stringify!($id));
        if count <= $crate::MAX_PRINT_COUNT {
            $crate::log::$typ!(concat!("(", stringify!($id), ") ", $fmt)
                               $(,$param)*);
        }
        if count == $crate::MAX_PRINT_COUNT {
            $crate::log::$typ!(
                concat!("Further ",
                        // stringify will not work properly with paste inside.
                        stringify!($typ),
                        " (", stringify!($id), ") will be suppressed."));
        }
    }};
    ($typ:ident, $fmt:expr $(,$param:expr)*) => {{
        $crate::log::$typ!($fmt $(,$param)*);
    }}
}

// unstable yet:

// macro_rules! define_log {
//     ($($n:ident),+) => {$(
//         #[macro_export]
//         macro_rules! $n {
//             ($$($$p:tt),+) => ($crate::log_monitor!($n $$(,$$p)+))
//         }
//     )+}
// }

// define_log!(info, warn, error);

#[macro_export]
macro_rules! info {
    ($t:tt $(,$p:expr)*) => ($crate::log_monitor!(info, $t $(,$p)*))
}

#[macro_export]
macro_rules! warn {
    ($t:tt $(,$p:expr)*) => ($crate::log_monitor!(warn, $t $(,$p)*))
}

#[macro_export]
macro_rules! error {
    ($t:tt $(,$p:expr)*) => ($crate::log_monitor!(error, $t $(,$p)*))
}

#[macro_export]
macro_rules! debug {
    ($t:tt $(,$p:expr)*) => ($crate::log_monitor!(debug, $t $(,$p)*))
}

#[macro_export]
macro_rules! trace {
    ($t:tt $(,$p:expr)*) => ($crate::log_monitor!(trace, $t $(,$p)*))
}

mod logging_timer;
pub use logging_timer::*;