versalogrs/
lib.rs

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}