use std::{
fs, env, panic,
process, thread,
fmt::{Display, Formatter, Result as FmtResult},
sync::RwLock,
panic::PanicInfo
};
use backtrace::Backtrace;
use chrono::{Timelike, Datelike, Local};
use colored::{Colorize, ColoredString};
use lazy_static::lazy_static;
lazy_static! {
static ref PANIC_LOG: RwLock<Option<Log>> = RwLock::new(None);
}
#[derive(Clone)]
pub struct Log {
name: String,
path: String,
folder: String,
main_log: bool,
main_log_path: String
}
impl Log {
pub fn get(log_name: &str, folder: &str) -> Log {
let path = format!("{}/.local/share/{}", env::var("HOME").expect("Where the hell is your home folder?!"), folder);
fs::create_dir_all(&path).unwrap_or(());
Log {
name: log_name.to_string(),
path: path.to_string(),
folder: folder.to_string(),
main_log: false,
main_log_path: String::new()
}
}
pub fn main_log(mut self, main_log: bool) -> Log {
self.main_log = main_log;
if main_log {
let user = env::var("USER").unwrap();
self.main_log_path = format!("/tmp/{}-{user}", self.folder);
fs::create_dir_all(&self.main_log_path).unwrap_or(());
}
self
}
pub fn line<T: Display>(&self, level: LogLevel, text: T, print_stdout: bool) {
if let LogLevel::Debug(false) = level {
return;
}
let log_path = format!("{}/{}", self.path, self.get_log_name());
let mut log = fs::read_to_string(&log_path).unwrap_or_default();
let text_str = text.to_string();
for line in text_str.lines() {
let now = Local::now();
let msg = format!("[{}:{:02}:{:02}] [{level}]: {line}\n", now.hour(), now.minute(), now.second());
if print_stdout { print!("{}", level.colorize(&msg)); }
log.push_str(&msg);
}
fs::write(log_path, &log).expect("Unable to write to log file!");
if self.main_log {
let log_path = format!("{}/app.log", self.main_log_path);
let mut main_log = fs::read_to_string(&log_path).unwrap_or_default();
for line in text_str.lines() {
let now = Local::now();
let msg = format!("[{}:{:02}:{:02}] [{level}]: {line}\n", now.hour(), now.minute(), now.second());
main_log.push_str(&msg);
}
fs::write(log_path, main_log).expect("Unable to write to log file!");
}
}
pub fn line_basic<T: Display>(&self, text: T, print_stdout: bool) { self.line(LogLevel::Info, text, print_stdout); }
pub fn report_panics(&self, report: bool) {
if report {
*(PANIC_LOG.write().unwrap()) = Some(self.clone());
panic::set_hook(Box::new(panic_handler));
}
else {
*(PANIC_LOG.write().unwrap()) = None;
drop(panic::take_hook());
}
}
fn get_log_name(&self) -> String {
let now = Local::now();
format!("{}-{}-{}-{}.log", self.name, now.year(), now.month(), now.day())
}
}
#[derive(Copy, Clone)]
pub enum LogLevel {
Info,
Debug(bool),
Warn,
Error,
Fatal
}
impl LogLevel {
fn colorize(&self, input: &str) -> ColoredString {
match self {
Self::Debug(_) => input.cyan(),
Self::Info => input.green(),
Self::Warn => input.bright_yellow(),
Self::Error => input.bright_red(),
Self::Fatal => input.bright_red().on_black()
}
}
}
impl Display for LogLevel {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
match *self {
LogLevel::Info => write!(f, "INFO"),
LogLevel::Debug(_) => write!(f, "DEBUG"),
LogLevel::Warn => write!(f, "WARN"),
LogLevel::Error => write!(f, "ERROR"),
LogLevel::Fatal => write!(f, "FATAL")
}
}
}
fn panic_handler(info: &PanicInfo) {
let backtrace = format!("{:?}", Backtrace::new());
let maybe_log = PANIC_LOG.read().unwrap();
let panic_log = if let Some(ref log) = &*maybe_log {
log
}
else {
eprintln!("Internal Error");
process::exit(101);
};
let cur_thread = thread::current();
let name = cur_thread.name();
let id = cur_thread.id();
let thread_disp = if let Some(n) = name {
n.to_string()
}
else {
format!("{id:?}")
};
let location = if let Some(loc) = info.location() {
format!("at {}:{}:{}", loc.file(),loc.line(),loc.column())
}
else {
String::new()
};
panic_log.line(LogLevel::Fatal, format!("Thread '{thread_disp}' panicked {location}"), true);
let msg = match (info.payload().downcast_ref::<&str>(), info.payload().downcast_ref::<String>()) {
(Some(s), _) => s.to_string(),
(_, Some(s)) => s.to_string(),
(None, None) => String::new(),
};
if !msg.is_empty() {
panic_log.line(LogLevel::Fatal, format!("Error: {msg}"), true);
}
panic_log.line(LogLevel::Fatal, format!("Backtrace:\n{backtrace}"), true);
}