1use notify_rust::Notification;
2use chrono::Local;
3use std::env;
4use std::fs;
5use std::io::Write;
6
7pub struct VersaLog {
8 mode: String,
9 tag: String,
10 showFile: bool,
11 showTag: bool,
12 notice: bool,
13 enableall: bool,
14 allsave: bool,
15 savelevels: Vec<String>,
16}
17
18static COLORS: &[(&str, &str)] = &[
19 ("INFO", "\x1b[32m"),
20 ("ERROR", "\x1b[31m"),
21 ("WARNING", "\x1b[33m"),
22 ("DEBUG", "\x1b[36m"),
23 ("CRITICAL", "\x1b[35m"),
24];
25
26static SYMBOLS: &[(&str, &str)] = &[
27 ("INFO", "[+]"),
28 ("ERROR", "[-]"),
29 ("WARNING", "[!]"),
30 ("DEBUG", "[D]"),
31 ("CRITICAL", "[C]"),
32];
33
34static RESET: &str = "\x1b[0m";
35
36static VALID_MODES: &[&str] = &["simple", "simple2", "detailed", "file"];
37static VALID_SAVE_LEVELS: &[&str] = &["INFO", "ERROR", "WARNING", "DEBUG", "CRITICAL"];
38
39pub fn NewVersaLog(mode: &str, show_file: bool, show_tag: bool, tag: &str, enable_all: bool, notice: bool) -> VersaLog {
41 let mode = mode.to_string();
42 let tag = tag.to_string();
43
44 if !VALID_MODES.contains(&mode.as_str()) {
45 panic!("Invalid mode: {}", mode);
46 }
47
48 let mut showFile = show_file;
49 let mut showTag = show_tag;
50 let mut notice_enabled = notice;
51 let mut allsave = false;
52 let mut savelevels = Vec::new();
53
54 if enable_all {
55 showFile = true;
56 showTag = true;
57 notice_enabled = true;
58 allsave = true;
59 }
60
61 if mode == "file" {
62 showFile = true;
63 }
64
65 if allsave {
66 savelevels = VALID_SAVE_LEVELS.iter().map(|s| s.to_string()).collect();
67 }
68
69 VersaLog {
70 mode,
71 tag,
72 showFile,
73 showTag,
74 notice: notice_enabled,
75 enableall: enable_all,
76 allsave,
77 savelevels,
78 }
79}
80
81pub fn NewVersaLogSimple(mode: &str, tag: &str) -> VersaLog {
83 NewVersaLog(mode, false, false, tag, false, false)
84}
85
86impl VersaLog {
87 pub fn log(&self, msg: String, level: String, tag: Option<String>) {
88 let level = level.to_uppercase();
89
90 let color = COLORS.iter()
91 .find(|(l, _)| *l == level)
92 .map(|(_, c)| *c)
93 .unwrap_or("");
94 let symbol = SYMBOLS.iter()
95 .find(|(l, _)| *l == level)
96 .map(|(_, s)| *s)
97 .unwrap_or("");
98
99 let caller = if self.showFile || self.mode == "file" {
100 self.get_caller()
101 } else {
102 String::new()
103 };
104
105 let final_tag = tag.unwrap_or_else(|| {
106 if self.showTag && !self.tag.is_empty() {
107 self.tag.clone()
108 } else {
109 String::new()
110 }
111 });
112
113 let (output, plain) = match self.mode.as_str() {
114 "simple" => {
115 if self.showFile {
116 if !final_tag.is_empty() {
117 let output = format!("[{}][{}]{}{}{} {}", caller, final_tag, color, symbol, RESET, msg);
118 let plain = format!("[{}][{}]{} {}", caller, final_tag, symbol, msg);
119 (output, plain)
120 } else {
121 let output = format!("[{}]{}{}{} {}", caller, color, symbol, RESET, msg);
122 let plain = format!("[{}]{} {}", caller, symbol, msg);
123 (output, plain)
124 }
125 } else {
126 if !final_tag.is_empty() {
127 let output = format!("[{}]{}{}{} {}", final_tag, color, symbol, RESET, msg);
128 let plain = format!("[{}]{} {}", final_tag, symbol, msg);
129 (output, plain)
130 } else {
131 let output = format!("{}{}{} {}", color, symbol, RESET, msg);
132 let plain = format!("{} {}", symbol, msg);
133 (output, plain)
134 }
135 }
136 },
137 "simple2" => {
138 let timestamp = self.get_time();
139 if self.showFile {
140 if !final_tag.is_empty() {
141 let output = format!("[{}] [{}][{}]{}{}{} {}", timestamp, caller, final_tag, color, symbol, RESET, msg);
142 let plain = format!("[{}] [{}][{}]{} {}", timestamp, caller, final_tag, symbol, msg);
143 (output, plain)
144 } else {
145 let output = format!("[{}] [{}]{}{}{} {}", timestamp, caller, color, symbol, RESET, msg);
146 let plain = format!("[{}] [{}]{} {}", timestamp, caller, symbol, msg);
147 (output, plain)
148 }
149 } else {
150 let output = format!("[{}] {}{}{} {}", timestamp, color, symbol, RESET, msg);
151 let plain = format!("[{}] {} {}", timestamp, symbol, msg);
152 (output, plain)
153 }
154 },
155 "file" => {
156 let output = format!("[{}]{}{}[{}]{}", caller, color, level, RESET, msg);
157 let plain = format!("[{}][{}] {}", caller, level, msg);
158 (output, plain)
159 },
160 _ => {
161 let timestamp = self.get_time();
162 let mut output = format!("[{}]{}{}[{}]", timestamp, color, level, RESET);
163 let mut plain = format!("[{}][{}]", timestamp, level);
164
165 if !final_tag.is_empty() {
166 output.push_str(&format!("[{}]", final_tag));
167 plain.push_str(&format!("[{}]", final_tag));
168 }
169
170 if self.showFile {
171 output.push_str(&format!("[{}]", caller));
172 plain.push_str(&format!("[{}]", caller));
173 }
174
175 output.push_str(&format!(" : {}", msg));
176 plain.push_str(&format!(" : {}", msg));
177
178 (output, plain)
179 }
180 };
181
182 println!("{}", output);
183 self.save_log(plain, level.clone());
184
185 if self.notice && (level == "ERROR" || level == "CRITICAL") {
186 let _ = Notification::new()
187 .summary(&format!("{} Log notice", level))
188 .body(&msg)
189 .show();
190 }
191 }
192
193 pub fn Info(&self, msg: &str, tag: Option<&str>) {
194 self.log(msg.to_string(), "INFO".to_string(), tag.map(|s| s.to_string()));
195 }
196
197 pub fn Error(&self, msg: &str, tag: Option<&str>) {
198 self.log(msg.to_string(), "ERROR".to_string(), tag.map(|s| s.to_string()));
199 }
200
201 pub fn Warning(&self, msg: &str, tag: Option<&str>) {
202 self.log(msg.to_string(), "WARNING".to_string(), tag.map(|s| s.to_string()));
203 }
204
205 pub fn Debug(&self, msg: &str, tag: Option<&str>) {
206 self.log(msg.to_string(), "DEBUG".to_string(), tag.map(|s| s.to_string()));
207 }
208
209 pub fn Critical(&self, msg: &str, tag: Option<&str>) {
210 self.log(msg.to_string(), "CRITICAL".to_string(), tag.map(|s| s.to_string()));
211 }
212
213 pub fn info(&self, msg: &str, tag: Option<&str>) {
215 self.Info(msg, tag);
216 }
217
218 pub fn error(&self, msg: &str, tag: Option<&str>) {
219 self.Error(msg, tag);
220 }
221
222 pub fn warning(&self, msg: &str, tag: Option<&str>) {
223 self.Warning(msg, tag);
224 }
225
226 pub fn debug(&self, msg: &str, tag: Option<&str>) {
227 self.Debug(msg, tag);
228 }
229
230 pub fn critical(&self, msg: &str, tag: Option<&str>) {
231 self.Critical(msg, tag);
232 }
233
234 fn get_time(&self) -> String {
235 Local::now().format("%Y-%m-%d %H:%M:%S").to_string()
236 }
237
238 fn get_caller(&self) -> String {
239 "main:0".to_string()
240 }
241
242 fn save_log(&self, log_text: String, _level: String) {
243 let cwd = env::current_dir().unwrap_or_else(|_| env::current_dir().unwrap());
244 let log_dir = cwd.join("log");
245
246 if !log_dir.exists() {
247 let _ = fs::create_dir_all(&log_dir);
248 }
249
250 let today = Local::now().format("%Y-%m-%d").to_string();
251 let log_file = log_dir.join(format!("{}.log", today));
252
253 let log_entry = format!("{}\n", log_text);
254 let _ = fs::OpenOptions::new()
255 .create(true)
256 .append(true)
257 .write(true)
258 .open(&log_file)
259 .and_then(|mut file| file.write_all(log_entry.as_bytes()));
260 }
261}