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 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}