versalogrs/
lib.rs

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
39// Goのバージョンと同様の簡潔なAPI
40pub 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
81// より簡潔なコンストラクタ(デフォルト値付き)
82pub 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    // 下位互換性のための小文字メソッド
214    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}