use std::{
fs, env,
process, thread,
path::PathBuf,
fmt::{Display, Formatter, Result as FmtResult},
sync::{RwLock, Mutex, Arc},
panic::{self, 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,
folder: String,
path: PathBuf,
main_log_path: Option<PathBuf>,
runlock: Arc<Mutex<()>>
}
impl Log {
pub fn new(log_name: &str, folder: &str) -> Log {
let path = PathBuf::from(format!("{}/.local/share/{folder}", env::var("HOME").expect("Where the hell is your home folder?!")));
fs::create_dir_all(&path).unwrap_or(());
Log {
name: log_name.to_string(),
folder: folder.to_string(),
path,
main_log_path: None,
runlock: Arc::default()
}
}
pub fn main_log(mut self, main_log: bool) -> Log {
if main_log {
let user = env::var("USER").unwrap();
let main_log_path = PathBuf::from(format!("/tmp/{}-{user}", self.folder));
fs::create_dir_all(&main_log_path).unwrap_or(());
self.main_log_path = Some(main_log_path);
}
self
}
pub fn line<T: Display>(&self, level: LogLevel, text: T, print_stdout: bool) {
let run = self.runlock.lock().unwrap();
if let LogLevel::Debug(false) = level {
return;
}
let log_path = self.log_path();
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 let Some(main_log_path) = self.main_log_path() {
let mut main_log = fs::read_to_string(&main_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(main_log_path, main_log).expect("Unable to write to log file!");
}
drop(run);
}
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());
}
}
pub fn main_log_path(&self) -> Option<PathBuf> {
self.main_log_path.as_ref()
.map(|path| path.join("app.log"))
}
pub fn log_path(&self) -> PathBuf {
let now = Local::now();
let path = format!("{}-{}-{}-{}.log", self.name, now.year(), now.month(), now.day());
self.path.join(path)
}
}
#[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 thread_disp = if let Some(name) = cur_thread.name() {
name.to_string()
}
else {
format!("{:?}", cur_thread.id())
};
if let Some(loc) = info.location() {
panic_log.line(LogLevel::Fatal, format!("Thread '{thread_disp}' panicked at {loc}"), true);
}
else {
panic_log.line(LogLevel::Fatal, format!("Thread '{thread_disp}' panicked"), true);
}
if let Some(payload) = info.payload().downcast_ref::<String>() {
panic_log.line(LogLevel::Fatal, format!("Error: {payload}"), true);
}
panic_log.line(LogLevel::Fatal, format!("Backtrace:\n{backtrace}"), true);
}