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 module_name: &'static str,
18 level: Level,
19 get_level_name: fn (level: Level) -> &'static str,
20}
21
22impl LoggingConfig {
23 pub fn new(module_name: &'static str, level: Level) -> Self {
24 LoggingConfig {
25 module_name, level,
26 get_level_name: |level| {
27 match level {
28 Level::Error => "E: ",
29 Level::Warn => "W: ",
30 Level::Info => "I: ",
31 Level::Debug => "D: ",
32 Level::Trace => "T: ",
33 }
34 }
35 }
36 }
37
38 pub fn minimal(mut self) -> Self {
39 if self.level < Level::Debug {
40 self.get_level_name = |_: Level| "";
41 }
42 self
43 }
44
45 pub fn level_names(mut self, get: fn (level: Level) -> &'static str) -> Self {
46 self.get_level_name = get;
47 self
48 }
49
50 pub fn dispatch(self) -> Dispatch {
51 let stdout_dispatcher =
52 self.configure_formatter(Dispatch::new(), atty::is(atty::Stream::Stdout))
53 .filter(|metadata| metadata.level() >= Level::Info)
54 .chain(io::stdout());
55
56 let stderr_dispatcher =
57 self.configure_formatter(Dispatch::new(), atty::is(atty::Stream::Stderr))
58 .filter(|metadata| metadata.level() < Level::Info)
59 .chain(io::stderr());
60
61 Dispatch::new()
62 .level(if self.level >= Level::Debug {
63 LevelFilter::Warn
64 } else {
65 LevelFilter::Off
66 })
67 .level_for(self.module_name, self.level.to_level_filter())
68 .chain(stdout_dispatcher)
69 .chain(stderr_dispatcher)
70 }
71
72 pub fn build(self) -> Result<(), SetLoggerError> {
73 self.dispatch().apply()
74 }
75
76 fn configure_formatter(&self, dispatcher: Dispatch, colored_output: bool) -> Dispatch {
77 let max_level = self.level;
78 let get_level_name = self.get_level_name;
79
80 if self.level < Level::Debug {
81 dispatcher.format(move |out, message, record| {
82 let level = record.level();
83 let level_name = get_level_name(level);
84 let context = GlobalContext::get(max_level);
85
86 if colored_output {
87 let color = get_level_color(level);
88 write_log(out, level, format_args!(
89 "{color_prefix}{level_name}{context}{message}{color_suffix}",
90 color_prefix=color.prefix(), color_suffix=color.suffix(),
91 ));
92 } else {
93 write_log(out, level, format_args!("{level_name}{context}{message}"));
94 }
95 })
96 } else {
97 dispatcher.format(move |out, message, record| {
98 let time = chrono::Local::now().format("[%T%.3f]");
99 let level = record.level();
100 let level_name = get_level_name(level);
101 let context = GlobalContext::get(max_level);
102
103 let file = if let (Some(mut file), Some(line)) = (record.file(), record.line()) {
104 let mut file_width = 10;
105 let mut line_width = 3;
106 let mut line_extra_width = line / 1000;
107
108 while line_extra_width > 0 && file_width > 0 {
109 line_width += 1;
110 file_width -= 1;
111 line_extra_width /= 10;
112 }
113
114 if file.starts_with("src/") {
115 file = &file[4..];
116 }
117
118 if file.len() > file_width {
119 file = &file[file.len() - file_width..]
120 }
121
122 format!(" [{file:>file_width$}:{line:0line_width$}]",
123 file=file, file_width=file_width, line=line, line_width=line_width)
124 } else {
125 String::new()
126 };
127
128 if colored_output {
129 let color = get_level_color(level);
130 write_log(out, level, format_args!(
131 "{color_prefix}{time}{file} {level_name}{context}{message}{color_suffix}",
132 color_prefix=color.prefix(), color_suffix=color.suffix()
133 ));
134 } else {
135 write_log(out, level, format_args!("{time}{file} {level_name}{context}{message}"));
136 }
137 })
138 }
139 }
140}
141
142pub fn init(module_name: &'static str, level: Level) -> Result<(), SetLoggerError> {
143 LoggingConfig::new(module_name, level).build()
144}
145
146fn get_level_color(level: Level) -> Color {
147 match level {
148 Level::Error => Color::Red,
149 Level::Warn => Color::Yellow,
150 Level::Info => Color::Green,
151 Level::Debug => Color::Cyan,
152 Level::Trace => Color::Purple,
153 }
154}
155
156fn write_log(out: FormatCallback, level: Level, formatted_message: fmt::Arguments) {
157 lazy_static! {
158 static ref OUTPUT_MUTEX: Mutex<()> = Mutex::new(());
159 }
160
161 let _lock = OUTPUT_MUTEX.lock();
164
165 out.finish(formatted_message);
166
167 let _ = if level >= Level::Info {
168 io::stdout().flush()
169 } else {
170 io::stderr().flush()
171 };
172}