use std::{env::temp_dir, fs::File, io::Write, str::FromStr};
use colored::{ColoredString, Colorize};
use log::{warn, Level};
use crate::initializer::PackxelInitializer;
#[derive(Debug)]
struct FileWriter {
file: File,
}
#[derive(Debug, Default)]
pub struct PackxelLogger {
log_level: Option<Level>,
print_lines: bool,
file_writer: Option<FileWriter>,
log_file_path: String,
}
#[derive(Debug)]
pub struct PackxelLoggerBuilder {
log_level: Option<Level>,
print_lines: bool,
initializer: Option<PackxelInitializer>,
file_writer: bool,
}
impl PackxelLoggerBuilder {
fn new(initializer: Option<PackxelInitializer>) -> Self {
Self {
log_level: None,
print_lines: false,
initializer,
file_writer: true,
}
}
fn with_default_log_level(mut self) -> PackxelLoggerBuilder {
let log_level = get_level_from_env();
self.log_level = log_level;
self
}
pub fn with_log_level(mut self, log_level: Level) -> PackxelLoggerBuilder {
self.log_level = Some(log_level);
self
}
pub fn with_print_lines(mut self, print_lines: bool) -> PackxelLoggerBuilder {
self.print_lines = print_lines;
self
}
pub fn with_file_writer(mut self, file_writer: bool) -> PackxelLoggerBuilder {
self.file_writer = file_writer;
self
}
fn try_build(self) -> std::io::Result<PackxelLogger> {
let file_writer;
let mut log_file_path;
if self.initializer.is_some() {
let initializer = self.initializer.unwrap();
log_file_path = initializer.get_log_file();
} else {
log_file_path = temp_dir();
log_file_path.push("data.log");
}
let file = File::options()
.append(true)
.create(true)
.open(&log_file_path)
.expect("Unable to open file");
let log_file_path = log_file_path
.to_str()
.expect("Failed to get log file path from path buffer")
.to_string();
if self.file_writer {
file_writer = Some(FileWriter { file })
} else {
file_writer = None
}
Ok(PackxelLogger {
log_level: self.log_level,
print_lines: self.print_lines,
file_writer,
log_file_path,
})
}
fn build(self) -> PackxelLogger {
self.try_build().expect("Failed to build logger.")
}
}
impl PackxelLogger {
pub fn init_with_builder(builder: PackxelLoggerBuilder) -> Result<(), log::SetLoggerError> {
log::set_boxed_logger(Box::new(builder.build()))
.map(|()| log::set_max_level(log::LevelFilter::Trace))
}
pub fn init(initializer: PackxelInitializer) -> Result<(), log::SetLoggerError> {
log::set_boxed_logger(Box::new(
PackxelLoggerBuilder::new(Some(initializer))
.with_default_log_level()
.build(),
))
.map(|()| log::set_max_level(log::LevelFilter::Trace))
}
fn get_colored_string(level: Level) -> ColoredString {
match level {
Level::Error => format!("{} ", level).as_str().red(),
Level::Warn => format!("{} ", level).as_str().yellow(),
Level::Info => format!("{} ", level).as_str().green(),
Level::Debug => format!("{} ", level).as_str().blue(),
Level::Trace => format!("{} ", level).as_str().clear(),
}
}
}
fn get_level_from_env() -> Option<Level> {
let log_level = std::env::var("RUST_LOG").unwrap_or("".to_string());
let log_level = Level::from_str(&log_level);
let log_level = match log_level {
Ok(log_level) => Some(log_level),
Err(_) => None,
};
log_level
}
fn rotate_file<S: AsRef<str>>(buffer: &mut &File, log_file_path: S) -> std::io::Result<bool> {
let file_metadata = buffer.metadata()?;
let file_size = file_metadata.len();
let one_mb = 1000 * 1000;
if file_size > 9 * one_mb {
warn!("Log file is getting large. Will be rotating file.")
}
if file_size > 10 * one_mb {
let new_file_name = format!(
"{}-{}.old",
chrono::Local::now().timestamp(),
log_file_path.as_ref()
);
std::fs::copy(log_file_path.as_ref(), new_file_name)?;
{
todo!("Seek to start of file")
}
}
Ok(false)
}
impl log::Log for PackxelLogger {
fn enabled(&self, metadata: &log::Metadata) -> bool {
if self.log_level.is_none() {
return false;
}
return metadata.level() <= self.log_level.unwrap();
}
fn log(&self, record: &log::Record) {
let timestamp = chrono::Local::now().format("%Y-%m-%d %H:%M:%S");
let level = Self::get_colored_string(record.level());
if self.enabled(record.metadata()) {
if self.file_writer.is_some() {
let file_writer = self.file_writer.as_ref().unwrap();
let output = format!(
"{} {} {}\n",
level.clone().clear(),
timestamp,
record.args()
);
let mut file = &file_writer.file;
file.write(output.as_bytes())
.expect("Unable to write to file");
match rotate_file(&mut &file_writer.file, &self.log_file_path) {
Ok(rotated) => {
if rotated {
println!("Rotated");
}
}
Err(_) => {
log::error!("Failed to rotate log file");
}
};
}
print!("{}", level);
print!("{}\t", timestamp);
if record.level() == Level::Trace || self.print_lines {
println!(
"{}:{} - {}",
record.file().unwrap_or("unknown"),
record.line().unwrap_or(0),
record.args(),
);
} else {
println!("{}", record.args());
}
}
}
fn flush(&self) {}
}