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