aces/
logging.rs

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        // FIXME these should be configurable by the user
27        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}