janus_plugin/
debug.rs

1/// Utilities for writing messages to the Janus log.
2use chrono::{DateTime, Local};
3use colored::{Color, Colorize};
4use std::ffi::CString;
5use std::fmt::Write;
6use std::fmt;
7use janus_plugin_sys as ffi;
8pub use ffi::janus_log_level as JANUS_LOG_LEVEL;
9
10#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
11/// A Janus log level. Lower is more severe.
12pub enum LogLevel {
13    Fatal = 1,
14    Err = 2,
15    Warn = 3,
16    Info = 4,
17    Verb = 5,
18    Huge = 6,
19    Dbg = 7,
20}
21
22impl LogLevel {
23    /// The color associated with each log level's label (if colors are enabled.)
24    fn color(self) -> Option<Color> {
25        match self {
26            LogLevel::Fatal => Some(Color::Magenta),
27            LogLevel::Err => Some(Color::Red),
28            LogLevel::Warn => Some(Color::Yellow),
29            _ => None,
30        }
31    }
32}
33
34#[derive(Debug, Clone)]
35pub struct LogParameters {
36    pub log_timestamps: bool,
37    pub log_colors: bool,
38    pub clock: fn() -> DateTime<Local>,
39}
40
41impl Default for LogParameters {
42    fn default() -> Self {
43        unsafe {
44            Self {
45                log_timestamps: ffi::janus_log_timestamps == 1,
46                log_colors: ffi::janus_log_colors == 1,
47                clock: Local::now,
48            }
49        }
50    }
51}
52
53/// Writes a message at the given log level to the Janus log, using the provided parameters to control
54/// how the log message is formatted.
55pub fn log(level: LogLevel, message: fmt::Arguments, params: LogParameters) {
56    unsafe {
57        let output = CString::new(print_log(level, message, params)).expect("Null character in log message :(");
58        ffi::janus_vprintf(output.as_ptr())
59    }
60}
61
62/// Prints a message at the given log level into an owned string, using the provided parameters to control
63/// how the log message is formatted.
64pub fn print_log(level: LogLevel, message: fmt::Arguments, params: LogParameters) -> String {
65    let mut output = String::with_capacity(150); // reasonably conservative size for typical messages
66    if params.log_timestamps {
67        write!(output, "{} ", (params.clock)().format("[%a %b %e %T %Y]")).unwrap();
68    }
69    if level <= LogLevel::Warn {
70        let prefix = format!("[{:?}] ", level).to_uppercase();
71        let prefix = match level.color() {
72            Some(c) if params.log_colors => format!("{}", prefix.color(c)),
73            _ => prefix,
74        };
75        write!(output, "{}", prefix).unwrap();
76    }
77    output.write_fmt(message).expect("Error constructing log message!");
78    output.push('\n');
79    output
80}
81
82#[macro_export]
83macro_rules! janus_log_enabled {
84    ($lvl:expr) => (($lvl as i32) <= unsafe { $crate::debug::JANUS_LOG_LEVEL })
85}
86
87#[macro_export]
88macro_rules! janus_log {
89    ($lvl:expr, $($arg:tt)+) => ({
90        let lvl = $lvl;
91        if $crate::janus_log_enabled!(lvl) {
92            $crate::debug::log(lvl, format_args!($($arg)+), $crate::debug::LogParameters::default())
93        }
94    })
95}
96
97#[macro_export]
98macro_rules! janus_fatal {
99    ($($arg:tt)+) => ($crate::janus_log!($crate::debug::LogLevel::Fatal, $($arg)+))
100}
101
102#[macro_export]
103macro_rules! janus_err {
104    ($($arg:tt)+) => ($crate::janus_log!($crate::debug::LogLevel::Err, $($arg)+))
105}
106
107#[macro_export]
108macro_rules! janus_warn {
109    ($($arg:tt)+) => ($crate::janus_log!($crate::debug::LogLevel::Warn, $($arg)+))
110}
111
112#[macro_export]
113macro_rules! janus_info {
114    ($($arg:tt)+) => ($crate::janus_log!($crate::debug::LogLevel::Info, $($arg)+))
115}
116
117#[macro_export]
118macro_rules! janus_verb {
119    ($($arg:tt)+) => ($crate::janus_log!($crate::debug::LogLevel::Verb, $($arg)+))
120}
121
122#[macro_export]
123macro_rules! janus_huge {
124    ($($arg:tt)+) => ($crate::janus_log!($crate::debug::LogLevel::Huge, $($arg)+))
125}
126
127#[macro_export]
128macro_rules! janus_dbg {
129    ($($arg:tt)+) => ($crate::janus_log!($crate::debug::LogLevel::Dbg, $($arg)+))
130}
131
132#[cfg(test)]
133mod tests {
134
135    use super::*;
136    use chrono::TimeZone;
137
138    fn fixed_clock(year: i32, month: u32, day: u32, hour: u32, min: u32, sec: u32) -> DateTime<Local> {
139        Local.ymd(year, month, day).and_hms(hour, min, sec)
140    }
141
142    #[test]
143    fn log_format_correctness() {
144        assert_eq!(
145            "[Tue Oct 10 01:37:46 2017] [WARN] Test message.\n",
146            print_log(
147                LogLevel::Warn,
148                format_args!("{}", "Test message."),
149                LogParameters {
150                    log_timestamps: true,
151                    ..default_log_parameters()
152                }
153            )
154        )
155    }
156
157    #[test]
158    fn log_colored_output() {
159        assert_eq!(
160            "\u{1b}[35m[FATAL] \u{1b}[0mCrash!\n",
161            print_log(
162                LogLevel::Fatal,
163                format_args!("{}", "Crash!"),
164                LogParameters {
165                    log_colors: true,
166                    ..default_log_parameters()
167                }
168            )
169        );
170
171        assert_eq!(
172            "\u{1b}[31m[ERR] \u{1b}[0mAn error occurred!\n",
173            print_log(
174                LogLevel::Err,
175                format_args!("{}", "An error occurred!"),
176                LogParameters {
177                    log_colors: true,
178                    ..default_log_parameters()
179                }
180            )
181        );
182
183        assert_eq!(
184            "\u{1b}[33m[WARN] \u{1b}[0mAttention!\n",
185            print_log(
186                LogLevel::Warn,
187                format_args!("{}", "Attention!"),
188                LogParameters {
189                    log_colors: true,
190                    ..default_log_parameters()
191                }
192            )
193        );
194
195        assert_eq!(
196            "Just a message.\n",
197            print_log(
198                LogLevel::Info,
199                format_args!("{}", "Just a message."),
200                LogParameters {
201                    log_colors: true,
202                    ..default_log_parameters()
203                }
204            )
205        );
206    }
207
208    fn default_log_parameters() -> LogParameters {
209        LogParameters {
210            log_timestamps: false,
211            log_colors: false,
212            clock: || fixed_clock(2017, 10, 10, 1, 37, 46),
213        }
214    }
215}