flatlake/
logging.rs

1use std::{
2    fmt::Debug,
3    fs::{create_dir_all, File, OpenOptions},
4    io::Write,
5    path::PathBuf,
6};
7
8use console::{Style, Term};
9use lazy_static::lazy_static;
10
11#[derive(Debug, Clone)]
12pub enum LogLevel {
13    Standard,
14    Verbose,
15}
16
17#[derive(Debug, Clone)]
18pub enum LogStyle {
19    Info,
20    Status,
21    Warning,
22    Error,
23    Success,
24}
25
26#[derive(Clone)]
27pub struct Logger {
28    log_level: LogLevel,
29    out: Option<Term>,
30    err: Term,
31    logfile: Option<PathBuf>,
32}
33
34macro_rules! plural {
35    ($len:expr) => {
36        match $len {
37            1 => "",
38            _ => "s",
39        }
40    };
41}
42
43impl Debug for Logger {
44    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45        f.debug_struct("Logger")
46            .field("log_level", &self.log_level)
47            .finish()
48    }
49}
50
51lazy_static! {
52    static ref STATUS: Style = Style::new().cyan().bold();
53    static ref WARN: Style = Style::new().yellow();
54    static ref ERROR: Style = Style::new().red();
55    static ref SUCCESS: Style = Style::new().green();
56}
57
58impl Logger {
59    pub fn new(log_level: LogLevel, use_terminal: bool, logfile: Option<PathBuf>) -> Self {
60        if let Some(filename) = &logfile {
61            if let Some(parent) = filename.parent() {
62                create_dir_all(parent).unwrap();
63            }
64
65            let mut file = File::create(filename).unwrap();
66            file.write_all("Flatlake logging initialized\n".as_bytes())
67                .expect("Logfile should be writable");
68        }
69
70        Self {
71            log_level,
72            out: if use_terminal {
73                Some(Term::stdout())
74            } else {
75                None
76            },
77            err: Term::stderr(),
78            logfile,
79        }
80    }
81
82    pub fn info<S: AsRef<str>>(&self, msg: S) {
83        self.log(msg, LogLevel::Standard, LogStyle::Info);
84    }
85
86    pub fn v_info<S: AsRef<str>>(&self, msg: S) {
87        self.log(msg, LogLevel::Verbose, LogStyle::Info);
88    }
89
90    pub fn status<S: AsRef<str>>(&self, msg: S) {
91        self.log(msg, LogLevel::Standard, LogStyle::Status);
92    }
93
94    pub fn v_status<S: AsRef<str>>(&self, msg: S) {
95        self.log(msg, LogLevel::Verbose, LogStyle::Status);
96    }
97
98    pub fn warn<S: AsRef<str>>(&self, msg: S) {
99        self.log(msg, LogLevel::Standard, LogStyle::Warning);
100    }
101
102    pub fn v_warn<S: AsRef<str>>(&self, msg: S) {
103        self.log(msg, LogLevel::Verbose, LogStyle::Warning);
104    }
105
106    pub fn error<S: AsRef<str>>(&self, msg: S) {
107        self.log(msg, LogLevel::Standard, LogStyle::Error);
108    }
109
110    pub fn success<S: AsRef<str>>(&self, msg: S) {
111        self.log(msg, LogLevel::Standard, LogStyle::Success);
112    }
113
114    pub fn log<S: AsRef<str>>(&self, msg: S, log_level: LogLevel, log_style: LogStyle) {
115        let log = match log_level {
116            LogLevel::Standard => true,
117            LogLevel::Verbose => matches!(self.log_level, LogLevel::Verbose),
118        };
119
120        if let Some(filename) = &self.logfile {
121            let mut file = OpenOptions::new()
122                .write(true)
123                .append(true)
124                .open(filename)
125                .unwrap();
126
127            writeln!(file, "[{log_style:?}]: {}", msg.as_ref()).unwrap();
128        }
129
130        if log {
131            if let Some(out) = &self.out {
132                // We currently aren't worried about logging failures.
133                match log_style {
134                    LogStyle::Info => {
135                        let _ = out.write_line(msg.as_ref());
136                    }
137                    LogStyle::Status => {
138                        let _ = out.write_line(&format!("\n{}", STATUS.apply_to(msg.as_ref())));
139                    }
140                    LogStyle::Warning => {
141                        let _ = self
142                            .err
143                            .write_line(&WARN.apply_to(msg.as_ref()).to_string());
144                    }
145                    LogStyle::Error => {
146                        let _ = self
147                            .err
148                            .write_line(&ERROR.apply_to(msg.as_ref()).to_string());
149                    }
150                    LogStyle::Success => {
151                        let _ = out.write_line(&SUCCESS.apply_to(msg.as_ref()).to_string());
152                    }
153                };
154            }
155        }
156    }
157}