#![allow(dead_code)]
use std::env::consts::OS;
use std::io;
use std::{fs::{self, OpenOptions}, io::Write, path::Path, time::{SystemTime, UNIX_EPOCH}};
use chrono::prelude::*;
use regex::Regex;
const SECS_1_DAY: u64 = 86400;
const NEW_LINE_WINDOWS: &str = "\r\n";
const NEW_LINE_LINUX: &str = "\n";
const PATH_SPLIT_WINDOWS: &str = r"\";
const PATH_SPLIT_LINUX: &str = "/";
macro_rules! toString {
($s:expr) => { $s.to_string() }
}
macro_rules! toAmpStr {
($s:expr) => { toString!($s).as_str() }
}
pub struct Logger {
path: String,
file_name_format: String,
line_date_format: String,
days_keep: Option<u64>,
}
impl Logger {
pub fn new(path: String, file_name_format: String, line_date_format: String, days_keep: Option<u64>) -> Result<Logger, io::Error> {
let new_line = match OS {
"linux" => NEW_LINE_LINUX,
"windows" => NEW_LINE_WINDOWS,
_ => return Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported OS")),
};
if !Path::new(path.as_str()).exists() { _ = match fs::create_dir(path.as_str()) { Ok(x) => x,
Err(e) => {return Err(e)}
};
}
let now: DateTime<Local> = Local::now();
let log_file_name = path.clone() + toAmpStr!(now.format(file_name_format.as_str()));
let mut file = match OpenOptions::new().append(true).create(true).open(log_file_name.as_str()) {
Ok(x) => x, Err(e) => {return Err(e)}
};
_ = match file.write_all(new_line.as_bytes()) { Ok(x) => x,
Err(e) => {return Err(e)}
};
Ok(Logger {path, file_name_format, line_date_format, days_keep})
}
pub fn write_log(&self, line: &str) -> bool {
let new_line = match OS {
"linux" => NEW_LINE_LINUX,
"windows" => NEW_LINE_WINDOWS,
_ => return false,
};
let now: DateTime<Local> = Local::now();
let time = now.format(self.line_date_format.as_str()).to_string();
let log_file_name = self.path.clone() + &now.format(self.file_name_format.as_str()).to_string();
let mut file = match OpenOptions::new().append(true).create(true).open(&log_file_name) {
Ok(x) => x,
Err(_) => return false,
};
if cfg!(debug_assertions) {
println!("{line}");
}
let log_entry = format!("{}{}{}", time, line, new_line);
if file.write_all(log_entry.as_bytes()).is_ok() && file.flush().is_ok() {
file.sync_all().unwrap();
true
} else {
false
}
}
pub fn log_clean(&self, filter: Option<&str>) {
let path_split = match OS {
"linux" => PATH_SPLIT_LINUX,
"windows" => PATH_SPLIT_WINDOWS,
_ => return
};
let paths = match fs::read_dir(&self.path) {
Ok(paths) => paths,
Err(e) => {
self.write_log(&format!("Error = Log cleaner, could not read directory: {e}"));
return;
}
};
if self.days_keep.is_none() {
return;
}
let file_filter = filter.unwrap_or("");
let now = SystemTime::now();
let current_time = now.duration_since(UNIX_EPOCH).unwrap().as_secs();
let threshold = current_time - (self.days_keep.unwrap() * 86400);
let regex = if !file_filter.is_empty() {
Some(Regex::new(file_filter).unwrap())
} else {
None
};
for entry in paths.filter_map(Result::ok) {
let file_name = match entry.file_name().into_string() {
Ok(name) => name,
Err(_) => {
self.write_log("Error = Log cleaner, could not convert file name");
continue;
}
};
if let Some(ref regex) = regex {
if !regex.is_match(&file_name) {
continue;
}
}
let file_path = self.path.clone() + path_split + &file_name;
if Path::new(&file_path).is_dir() {
continue;
}
let metadata = match fs::metadata(&file_path) {
Ok(metadata) => metadata,
Err(e) => {
self.write_log(&format!("Error = Log cleaner, could not read metadata from file {file_name} | {e}"));
continue;
}
};
let modified_time = match metadata.modified() {
Ok(modified) => match modified.duration_since(UNIX_EPOCH) {
Ok(duration) => duration.as_secs(),
Err(e) => {
self.write_log(&format!("Error = Log cleaner, could not get modified time for file {file_name} | {e}"));
continue;
}
},
Err(e) => {
self.write_log(&format!("Error = Log cleaner, could not read modified time from file {file_name} | {e}"));
continue;
}
};
if modified_time < threshold {
if let Err(e) = fs::remove_file(&file_path) {
self.write_log(&format!("Error = Log cleaner, could not delete file {file_name} | {e}"));
}
}
}
}
}