1use std::{
7 fs, env,
8 process, thread,
9 path::PathBuf,
10 fmt::{Display, Formatter, Result as FmtResult},
11 sync::{RwLock, Mutex, Arc},
12 panic::{self, PanicInfo}
13};
14use backtrace::Backtrace;
15use chrono::{Timelike, Datelike, Local};
16use colored::{Colorize, ColoredString};
17use lazy_static::lazy_static;
18
19lazy_static! {
20 static ref PANIC_LOG: RwLock<Option<Log>> = RwLock::new(None);
21}
22
23#[derive(Clone)]
29pub struct Log {
30 name: String,
31 folder: String,
32 path: PathBuf,
33 main_log_path: Option<PathBuf>,
34 runlock: Arc<Mutex<()>>
35}
36
37impl Log {
38 pub fn new(log_name: &str, folder: &str) -> Log {
45 let path = PathBuf::from(format!("{}/.local/share/{folder}", env::var("HOME").expect("Where the hell is your home folder?!")));
46 fs::create_dir_all(&path).unwrap_or(());
47
48 Log {
49 name: log_name.to_string(),
50 folder: folder.to_string(),
51 path,
52 main_log_path: None,
53 runlock: Arc::default()
54 }
55 }
56
57 pub fn main_log(mut self, main_log: bool) -> Log {
62 if main_log {
63 let user = env::var("USER").unwrap();
64 let main_log_path = PathBuf::from(format!("/tmp/{}-{user}", self.folder));
65 fs::create_dir_all(&main_log_path).unwrap_or(());
66 self.main_log_path = Some(main_log_path);
67 }
68
69 self
70 }
71
72 pub fn line<T: Display>(&self, level: LogLevel, text: T, print_stdout: bool) {
76 let run = self.runlock.lock().unwrap();
77 if let LogLevel::Debug(false) = level {
78 return;
79 }
80
81 let log_path = self.log_path();
82 let mut log = fs::read_to_string(&log_path).unwrap_or_default();
83 let text_str = text.to_string();
84
85 for line in text_str.lines() {
86 let now = Local::now();
87 let msg = format!("[{}:{:02}:{:02}] [{level}]: {line}\n", now.hour(), now.minute(), now.second());
88 if print_stdout { print!("{}", level.colorize(&msg)); }
89 log.push_str(&msg);
90 }
91
92 fs::write(log_path, &log).expect("Unable to write to log file!");
93
94 if let Some(main_log_path) = self.main_log_path() {
95 let mut main_log = fs::read_to_string(&main_log_path).unwrap_or_default();
96 for line in text_str.lines() {
97 let now = Local::now();
98 let msg = format!("[{}:{:02}:{:02}] [{level}]: {line}\n", now.hour(), now.minute(), now.second());
99 main_log.push_str(&msg);
100 }
101
102 fs::write(main_log_path, main_log).expect("Unable to write to log file!");
103 }
104
105 drop(run);
106 }
107
108 pub fn line_basic<T: Display>(&self, text: T, print_stdout: bool) { self.line(LogLevel::Info, text, print_stdout); }
110
111 pub fn report_panics(&self, report: bool) {
117 if report {
118 *(PANIC_LOG.write().unwrap()) = Some(self.clone());
119 panic::set_hook(Box::new(panic_handler));
120 }
121 else {
122 *(PANIC_LOG.write().unwrap()) = None;
123 drop(panic::take_hook());
124 }
125 }
126
127 pub fn main_log_path(&self) -> Option<PathBuf> {
131 self.main_log_path.as_ref()
132 .map(|path| path.join("app.log"))
133 }
134
135 pub fn log_path(&self) -> PathBuf {
137 let now = Local::now();
138 let path = format!("{}-{}-{}-{}.log", self.name, now.year(), now.month(), now.day());
139 self.path.join(path)
140 }
141}
142
143#[derive(Copy, Clone)]
145pub enum LogLevel {
146 Info,
148
149 Debug(bool),
151
152 Warn,
154
155 Error,
159
160 Fatal
162}
163
164impl LogLevel {
165 fn colorize(&self, input: &str) -> ColoredString {
166 match self {
167 Self::Debug(_) => input.cyan(),
168 Self::Info => input.green(),
169 Self::Warn => input.bright_yellow(),
170 Self::Error => input.bright_red(),
171 Self::Fatal => input.bright_red().on_black()
172 }
173 }
174}
175
176impl Display for LogLevel {
177 fn fmt(&self, f: &mut Formatter) -> FmtResult {
178 match *self {
179 LogLevel::Info => write!(f, "INFO"),
180 LogLevel::Debug(_) => write!(f, "DEBUG"),
181 LogLevel::Warn => write!(f, "WARN"),
182 LogLevel::Error => write!(f, "ERROR"),
183 LogLevel::Fatal => write!(f, "FATAL")
184 }
185 }
186}
187
188fn panic_handler(info: &PanicInfo) {
189 let backtrace = format!("{:?}", Backtrace::new());
190 let maybe_log = PANIC_LOG.read().unwrap();
191 let panic_log = if let Some(ref log) = &*maybe_log {
192 log
193 }
194 else {
195 eprintln!("Internal Error");
196 process::exit(101);
197 };
198
199 let cur_thread = thread::current();
200 let thread_disp = if let Some(name) = cur_thread.name() {
201 name.to_string()
202 }
203 else {
204 format!("{:?}", cur_thread.id())
205 };
206
207 if let Some(loc) = info.location() {
208 panic_log.line(LogLevel::Fatal, format!("Thread '{thread_disp}' panicked at {loc}"), true);
209 }
210 else {
211 panic_log.line(LogLevel::Fatal, format!("Thread '{thread_disp}' panicked"), true);
212 }
213
214 if let Some(payload) = info.payload().downcast_ref::<String>() {
215 panic_log.line(LogLevel::Fatal, format!("Error: {payload}"), true);
216 }
217
218 panic_log.line(LogLevel::Fatal, format!("Backtrace:\n{backtrace}"), true);
219}