1use std::path::PathBuf;
2use fern::colors::{Color, ColoredLevelConfig};
3
4macro_rules! error_pre_log {
5 ($lgr:expr,$($arg:tt)*) => (
6 eprintln!("{}{}{}",
7 $lgr.console_prefix(log::Level::Error),
8 format_args!($($arg)*),
9 $lgr.console_suffix()));
10}
11
12#[derive(Default)]
13pub struct Logger {
14 app_name: String,
15 dispatcher: Option<fern::Dispatch>,
16 level_colors: ColoredLevelConfig,
17 message_colors: ColoredLevelConfig,
18 directory: Option<PathBuf>,
19}
20
21impl Logger {
22 pub fn new<S: AsRef<str>>(app_name: S) -> Self {
23 let app_name = app_name.as_ref().to_owned();
24 let dispatcher = Some(fern::Dispatch::new());
25
26 let level_colors = ColoredLevelConfig::new()
28 .trace(Color::Blue)
29 .debug(Color::Yellow)
30 .info(Color::BrightGreen)
31 .warn(Color::BrightMagenta)
32 .error(Color::BrightRed);
33
34 let message_colors = ColoredLevelConfig::new()
35 .trace(Color::White)
36 .debug(Color::White)
37 .info(Color::BrightWhite)
38 .warn(Color::BrightWhite)
39 .error(Color::BrightWhite);
40
41 Self { app_name, dispatcher, level_colors, message_colors, directory: None }
42 }
43
44 pub fn with_console(mut self, level: log::LevelFilter) -> Self {
45 let level_colors = self.level_colors;
46 let message_colors = self.message_colors;
47
48 let dispatcher = self.dispatcher.unwrap().chain(
49 fern::Dispatch::new()
50 .format(move |out, message, record| match record.level() {
51 log::Level::Info => out.finish(format_args!("{}.", message)),
52 _ => out.finish(format_args!(
53 "[{}]\t\x1B[{}m{}.\x1B[0m",
54 level_colors.color(record.level()),
55 message_colors.get_color(&record.level()).to_fg_str(),
56 message
57 )),
58 })
59 .level(level)
60 .chain(std::io::stdout()),
61 );
62
63 self.dispatcher = Some(dispatcher);
64 self
65 }
66
67 pub fn with_explicit_directory<S: AsRef<str>>(mut self, dirname: S) -> Self {
68 let path = PathBuf::from(dirname.as_ref());
69
70 if path.is_dir() {
71 self.directory = Some(path);
72 } else if path.exists() {
73 error_pre_log!(
74 self,
75 "Can't use \"{}\" as a logging directory, because it exists and isn't a directory.",
76 path.display(),
77 );
78 self.directory = None;
79 } else if let Err(err) = std::fs::create_dir(&path) {
80 error_pre_log!(self, "Can't create \"{}\" directory: {}.", path.display(), err);
81 self.directory = None;
82 } else {
83 self.directory = Some(path);
84 }
85
86 self
87 }
88
89 pub fn with_file<S: AsRef<str>>(mut self, filename: S, level: log::LevelFilter) -> Self {
90 if self.directory.is_none() {
91 let path = PathBuf::from("log");
92
93 if path.is_dir() {
94 self.directory = Some(path);
95 } else {
96 error_pre_log!(
97 self,
98 "Logging to file is disabled, because directory \"log\" doesn't \
99 exist...\n\tCreate this directory or run '{} --log-dir <LOG_DIR> ...'.",
100 self.app_name
101 );
102 }
103 }
104
105 if let Some(ref mut path) = self.directory {
106 let path = path.join(filename.as_ref());
107
108 let log_file = std::fs::OpenOptions::new()
109 .write(true)
110 .create(true)
111 .truncate(true)
112 .append(false)
113 .open(path);
114
115 match log_file {
116 Ok(log_file) => {
117 let dispatcher = self.dispatcher.unwrap().chain(
118 fern::Dispatch::new()
119 .format(move |out, message, record| {
120 out.finish(format_args!(
121 "[{}][{}] {}.",
122 record.target(),
123 record.level(),
124 message,
125 ))
126 })
127 .level(level)
128 .chain(log_file),
129 );
130 self.dispatcher = Some(dispatcher);
131 }
132 Err(err) => {
133 error_pre_log!(self, "{}.", err);
134 }
135 }
136 }
137
138 self
139 }
140
141 pub fn get_directory(&self) -> Option<&PathBuf> {
142 self.directory.as_ref()
143 }
144
145 pub fn apply(&mut self) {
146 if let Some(dispatcher) = self.dispatcher.take() {
147 dispatcher.apply().unwrap_or_else(|err| error_pre_log!(self, "{}.", err));
148 } else {
149 error_pre_log!(self, "Logger can't be applied (probably it has already been applied).");
150 }
151 }
152
153 fn console_prefix(&self, level: log::Level) -> String {
154 format!(
155 "[{}]\t\x1B[{}m",
156 self.level_colors.color(level),
157 self.message_colors.get_color(&level).to_fg_str()
158 )
159 }
160
161 fn console_suffix(&self) -> &str {
162 "\x1B[0m"
163 }
164}