1use notify_rust::Notification;
2use chrono::Local;
3use std::env;
4use std::fs;
5use std::io::Write;
6use backtrace::Backtrace;
7
8pub struct VersaLog {
9 mode: String,
10 tag: String,
11 showFile: bool,
12 showTag: bool,
13 notice: bool,
14 enableall: bool,
15 allsave: bool,
16 savelevels: Vec<String>,
17}
18
19static COLORS: &[(&str, &str)] = &[
20 ("INFO", "\x1b[32m"),
21 ("ERROR", "\x1b[31m"),
22 ("WARNING", "\x1b[33m"),
23 ("DEBUG", "\x1b[36m"),
24 ("CRITICAL", "\x1b[35m"),
25];
26
27static SYMBOLS: &[(&str, &str)] = &[
28 ("INFO", "[+]"),
29 ("ERROR", "[-]"),
30 ("WARNING", "[!]"),
31 ("DEBUG", "[D]"),
32 ("CRITICAL", "[C]"),
33];
34
35static RESET: &str = "\x1b[0m";
36
37static VALID_MODES: &[&str] = &["simple", "simple2", "detailed", "file"];
38static VALID_SAVE_LEVELS: &[&str] = &["INFO", "ERROR", "WARNING", "DEBUG", "CRITICAL"];
39
40pub fn NewVersaLog(mode: &str, show_file: bool, show_tag: bool, tag: &str, enable_all: bool, notice: bool, all_save: bool, save_levels: Vec<String>) -> VersaLog {
41 let mode = mode.to_lowercase();
42 let tag = tag.to_string();
43
44 if !VALID_MODES.contains(&mode.as_str()) {
45 panic!("Invalid mode '{}' specified. Valid modes are: simple, simple2, detailed, file", 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 = all_save;
52 let mut savelevels = save_levels;
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 if savelevels.is_empty() {
67 savelevels = VALID_SAVE_LEVELS.iter().map(|s| s.to_string()).collect();
68 } else {
69 for level in &savelevels {
70 if !VALID_SAVE_LEVELS.contains(&level.as_str()) {
71 panic!("Invalid saveLevels specified. Valid levels are: {:?}", VALID_SAVE_LEVELS);
72 }
73 }
74 }
75 }
76
77 VersaLog {
78 mode,
79 tag,
80 showFile,
81 showTag,
82 notice: notice_enabled,
83 enableall: enable_all,
84 allsave,
85 savelevels,
86 }
87}
88
89pub fn NewVersaLogSimple(mode: &str, tag: &str) -> VersaLog {
90 NewVersaLog(mode, false, false, tag, false, false, false, Vec::new())
91}
92
93pub fn NewVersaLogSimple2(mode: &str, tag: &str, enable_all: bool) -> VersaLog {
94 NewVersaLog(mode, false, false, tag, enable_all, false, false, Vec::new())
95}
96
97impl VersaLog {
98 pub fn log(&self, msg: String, level: String, tags: &[&str]) {
99 let level = level.to_uppercase();
100
101 let color = COLORS.iter()
102 .find(|(l, _)| *l == level)
103 .map(|(_, c)| *c)
104 .unwrap_or("");
105 let symbol = SYMBOLS.iter()
106 .find(|(l, _)| *l == level)
107 .map(|(_, s)| *s)
108 .unwrap_or("");
109
110 let caller = if self.showFile || self.mode == "file" {
111 self.get_caller()
112 } else {
113 String::new()
114 };
115
116 let final_tag = if !tags.is_empty() && !tags[0].is_empty() {
117 tags[0].to_string()
118 } else if self.showTag && !self.tag.is_empty() {
119 self.tag.clone()
120 } else {
121 String::new()
122 };
123
124 let (output, plain) = match self.mode.as_str() {
125 "simple" => {
126 if self.showFile {
127 if !final_tag.is_empty() {
128 let output = format!("[{}][{}]{}{}{} {}", caller, final_tag, color, symbol, RESET, msg);
129 let plain = format!("[{}][{}]{} {}", caller, final_tag, symbol, msg);
130 (output, plain)
131 } else {
132 let output = format!("[{}]{}{}{} {}", caller, color, symbol, RESET, msg);
133 let plain = format!("[{}]{} {}", caller, symbol, msg);
134 (output, plain)
135 }
136 } else {
137 if !final_tag.is_empty() {
138 let output = format!("[{}]{}{}{} {}", final_tag, color, symbol, RESET, msg);
139 let plain = format!("[{}]{} {}", final_tag, symbol, msg);
140 (output, plain)
141 } else {
142 let output = format!("{}{}{} {}", color, symbol, RESET, msg);
143 let plain = format!("{} {}", symbol, msg);
144 (output, plain)
145 }
146 }
147 },
148 "simple2" => {
149 let timestamp = self.get_time();
150 if self.showFile {
151 if !final_tag.is_empty() {
152 let output = format!("[{}] [{}][{}]{}{}{} {}", timestamp, caller, final_tag, color, symbol, RESET, msg);
153 let plain = format!("[{}] [{}][{}]{} {}", timestamp, caller, final_tag, symbol, msg);
154 (output, plain)
155 } else {
156 let output = format!("[{}] [{}]{}{}{} {}", timestamp, caller, color, symbol, RESET, msg);
157 let plain = format!("[{}] [{}]{} {}", timestamp, caller, symbol, msg);
158 (output, plain)
159 }
160 } else {
161 let output = format!("[{}] {}{}{} {}", timestamp, color, symbol, RESET, msg);
162 let plain = format!("[{}] {} {}", timestamp, symbol, msg);
163 (output, plain)
164 }
165 },
166 "file" => {
167 let output = format!("[{}]{}{}[{}]{}", caller, color, level, RESET, msg);
168 let plain = format!("[{}][{}] {}", caller, level, msg);
169 (output, plain)
170 },
171 _ => {
172 let timestamp = self.get_time();
173 let mut output = format!("[{}]{}{}[{}]", timestamp, color, level, RESET);
174 let mut plain = format!("[{}][{}]", timestamp, level);
175
176 if !final_tag.is_empty() {
177 output.push_str(&format!("[{}]", final_tag));
178 plain.push_str(&format!("[{}]", final_tag));
179 }
180
181 if self.showFile {
182 output.push_str(&format!("[{}]", caller));
183 plain.push_str(&format!("[{}]", caller));
184 }
185
186 output.push_str(&format!(" : {}", msg));
187 plain.push_str(&format!(" : {}", msg));
188
189 (output, plain)
190 }
191 };
192
193 println!("{}", output);
194 self.save_log(plain, level.clone());
195
196 if self.notice && (level == "ERROR" || level == "CRITICAL") {
197 let _ = Notification::new()
198 .summary(&format!("{} Log notice", level))
199 .body(&msg)
200 .show();
201 }
202 }
203
204 pub fn Info(&self, msg: &str, tags: &[&str]) {
205 self.log(msg.to_string(), "INFO".to_string(), tags);
206 }
207
208 pub fn Error(&self, msg: &str, tags: &[&str]) {
209 self.log(msg.to_string(), "ERROR".to_string(), tags);
210 }
211
212 pub fn Warning(&self, msg: &str, tags: &[&str]) {
213 self.log(msg.to_string(), "WARNING".to_string(), tags);
214 }
215
216 pub fn Debug(&self, msg: &str, tags: &[&str]) {
217 self.log(msg.to_string(), "DEBUG".to_string(), tags);
218 }
219
220 pub fn Critical(&self, msg: &str, tags: &[&str]) {
221 self.log(msg.to_string(), "CRITICAL".to_string(), tags);
222 }
223
224 pub fn info(&self, msg: &str, tags: &[&str]) {
225 self.Info(msg, tags);
226 }
227
228 pub fn error(&self, msg: &str, tags: &[&str]) {
229 self.Error(msg, tags);
230 }
231
232 pub fn warning(&self, msg: &str, tags: &[&str]) {
233 self.Warning(msg, tags);
234 }
235
236 pub fn debug(&self, msg: &str, tags: &[&str]) {
237 self.Debug(msg, tags);
238 }
239
240 pub fn critical(&self, msg: &str, tags: &[&str]) {
241 self.Critical(msg, tags);
242 }
243
244 pub fn Info_no_tag(&self, msg: &str) {
245 self.log(msg.to_string(), "INFO".to_string(), &[]);
246 }
247
248 pub fn Error_no_tag(&self, msg: &str) {
249 self.log(msg.to_string(), "ERROR".to_string(), &[]);
250 }
251
252 pub fn Warning_no_tag(&self, msg: &str) {
253 self.log(msg.to_string(), "WARNING".to_string(), &[]);
254 }
255
256 pub fn Debug_no_tag(&self, msg: &str) {
257 self.log(msg.to_string(), "DEBUG".to_string(), &[]);
258 }
259
260 pub fn Critical_no_tag(&self, msg: &str) {
261 self.log(msg.to_string(), "CRITICAL".to_string(), &[]);
262 }
263
264 fn get_time(&self) -> String {
265 Local::now().format("%Y-%m-%d %H:%M:%S").to_string()
266 }
267
268 fn get_caller(&self) -> String {
269 let bt = Backtrace::new();
270 if let Some(frame) = bt.frames().get(3) {
271 if let Some(symbol) = frame.symbols().first() {
272 if let Some(file) = symbol.filename() {
273 if let Some(file_name) = file.file_name() {
274 if let Some(line) = symbol.lineno() {
275 return format!("{}:{}", file_name.to_string_lossy(), line);
276 }
277 }
278 }
279 }
280 }
281 "unknown:0".to_string()
282 }
283
284 fn save_log(&self, log_text: String, level: String) {
285 if !self.allsave || !self.savelevels.contains(&level) {
286 return;
287 }
288
289 let cwd = env::current_dir().unwrap_or_else(|_| env::current_dir().unwrap());
290 let log_dir = cwd.join("log");
291
292 if !log_dir.exists() {
293 let _ = fs::create_dir_all(&log_dir);
294 }
295
296 let today = Local::now().format("%Y-%m-%d").to_string();
297 let log_file = log_dir.join(format!("{}.log", today));
298
299 let log_entry = format!("{}\n", log_text);
300 let _ = fs::OpenOptions::new()
301 .create(true)
302 .append(true)
303 .write(true)
304 .open(&log_file)
305 .and_then(|mut file| file.write_all(log_entry.as_bytes()));
306 }
307}