easy_logging/
lib.rs

1mod context;
2
3use std::fmt;
4use std::io::{self, Write};
5use std::sync::Mutex;
6
7use ansi_term::Color;
8use fern::{Dispatch, FormatCallback};
9use lazy_static::lazy_static;
10use log::{Level, LevelFilter, SetLoggerError};
11
12pub use fern;
13pub use log;
14pub use crate::context::GlobalContext;
15
16pub struct LoggingConfig {
17    minimal: bool,
18    modules: Vec<ModuleConfig>,
19    get_level_name: fn (level: Level) -> &'static str,
20}
21
22impl LoggingConfig {
23    pub fn new(main_module_name: &'static str, level: Level) -> Self {
24        LoggingConfig {
25            minimal: false,
26            modules: vec![ModuleConfig {
27                name: main_module_name,
28                level,
29            }],
30            get_level_name: |level| {
31                match level {
32                    Level::Error => "E: ",
33                    Level::Warn  => "W: ",
34                    Level::Info  => "I: ",
35                    Level::Debug => "D: ",
36                    Level::Trace => "T: ",
37                }
38            }
39        }
40    }
41
42    pub fn level_for(mut self, name: &'static str, level: Level) -> Self {
43        self.modules.push(ModuleConfig {name, level});
44        self
45    }
46
47    pub fn level_names(mut self, get: fn (level: Level) -> &'static str) -> Self {
48        self.get_level_name = get;
49        self
50    }
51
52    pub fn minimal(mut self) -> Self {
53        self.minimal = true;
54        self
55    }
56
57    pub fn dispatch(self) -> Dispatch {
58        let stdout_dispatcher =
59            self.configure_formatter(Dispatch::new(), atty::is(atty::Stream::Stdout))
60            .filter(|metadata| metadata.level() >= Level::Info)
61            .chain(io::stdout());
62
63        let stderr_dispatcher =
64            self.configure_formatter(Dispatch::new(), atty::is(atty::Stream::Stderr))
65            .filter(|metadata| metadata.level() < Level::Info)
66            .chain(io::stderr());
67
68        let base_level = self.modules.first().unwrap().level;
69        let mut dispatch = Dispatch::new()
70            .level(if base_level >= Level::Debug {
71                LevelFilter::Warn
72            } else {
73                LevelFilter::Off
74            })
75            .chain(stdout_dispatcher)
76            .chain(stderr_dispatcher);
77
78        for module in self.modules {
79            dispatch = dispatch.level_for(module.name, module.level.to_level_filter());
80        }
81
82        dispatch
83    }
84
85    pub fn build(self) -> Result<(), SetLoggerError> {
86        self.dispatch().apply()
87    }
88
89    fn configure_formatter(&self, dispatcher: Dispatch, colored_output: bool) -> Dispatch {
90        let base_level = self.modules.first().unwrap().level;
91
92        let mut get_level_name = self.get_level_name;
93        if self.minimal && base_level < Level::Debug {
94            get_level_name = |_: Level| "";
95        }
96
97        if base_level < Level::Debug {
98            dispatcher.format(move |out, message, record| {
99                let level = record.level();
100                let level_name = get_level_name(level);
101                let context = GlobalContext::get(base_level);
102
103                if colored_output {
104                    let color = get_level_color(level);
105                    write_log(out, level, format_args!(
106                        "{color_prefix}{level_name}{context}{message}{color_suffix}",
107                        color_prefix=color.prefix(), color_suffix=color.suffix(),
108                    ));
109                } else {
110                    write_log(out, level, format_args!("{level_name}{context}{message}"));
111                }
112            })
113        } else {
114            dispatcher.format(move |out, message, record| {
115                let time = chrono::Local::now().format("[%T%.3f]");
116                let level = record.level();
117                let level_name = get_level_name(level);
118                let context = GlobalContext::get(base_level);
119
120                let file = if let (Some(mut file), Some(line)) = (record.file(), record.line()) {
121                    let mut file_width = 10;
122                    let mut line_width = 3;
123                    let mut line_extra_width = line / 1000;
124
125                    while line_extra_width > 0 && file_width > 0 {
126                        line_width += 1;
127                        file_width -= 1;
128                        line_extra_width /= 10;
129                    }
130
131                    if file.starts_with("src/") {
132                        file = &file[4..];
133                    }
134
135                    if file.len() > file_width {
136                        file = &file[file.len() - file_width..]
137                    }
138
139                    format!(" [{file:>file_width$}:{line:0line_width$}]",
140                            file=file, file_width=file_width, line=line, line_width=line_width)
141                } else {
142                    String::new()
143                };
144
145                if colored_output {
146                    let color = get_level_color(level);
147                    write_log(out, level, format_args!(
148                        "{color_prefix}{time}{file} {level_name}{context}{message}{color_suffix}",
149                        color_prefix=color.prefix(), color_suffix=color.suffix()
150                    ));
151                } else {
152                    write_log(out, level, format_args!("{time}{file} {level_name}{context}{message}"));
153                }
154            })
155        }
156    }
157}
158
159pub fn init(main_module_name: &'static str, level: Level) -> Result<(), SetLoggerError> {
160    LoggingConfig::new(main_module_name, level).build()
161}
162
163struct ModuleConfig {
164    name: &'static str,
165    level: Level,
166}
167
168fn get_level_color(level: Level) -> Color {
169    match level {
170        Level::Error => Color::Red,
171        Level::Warn  => Color::Yellow,
172        Level::Info  => Color::Green,
173        Level::Debug => Color::Cyan,
174        Level::Trace => Color::Purple,
175    }
176}
177
178fn write_log(out: FormatCallback, level: Level, formatted_message: fmt::Arguments) {
179    lazy_static! {
180        static ref OUTPUT_MUTEX: Mutex<()> = Mutex::new(());
181    }
182
183    // Since we write into stdout and stderr we should guard any write with a mutex to not get the
184    // output interleaved.
185    let _lock = OUTPUT_MUTEX.lock();
186
187    out.finish(formatted_message);
188
189    let _ = if level >= Level::Info {
190        io::stdout().flush()
191    } else {
192        io::stderr().flush()
193    };
194}