use log::{self, Log, Metadata, Record, SetLoggerError, LevelFilter, Level};
use time::{self, OffsetDateTime, format_description, error};
use colored::{Color, Colorize };
use std::collections::HashMap;
use std::sync::Mutex;
use std::fs::{File, OpenOptions};
use std::io::Write;
use std::io::ErrorKind;
const DE_TIME_PASE_FORMAT:&str = "[year]-[month]-[day] [hour]:[minute]:[second] [offset_hour \
sign:mandatory]:[offset_minute]:[offset_second]";
#[derive(Debug)]
struct ColorOption {
use_color: bool,
level_to_color: HashMap<Level, (Color,Color)>
}
impl ColorOption { fn new(use_color: bool, level_to_color: HashMap<Level, (Color,Color)>) -> Self {
ColorOption {
use_color,
level_to_color
}
}
fn set_use_color(&mut self, use_color: bool) {
if use_color != self.use_color {
self.use_color = use_color;
}
}
fn get_use_color(&self) -> bool {
self.use_color
}
fn get_color_from_level(&self, l: Level) -> (Color,Color) {
match self.level_to_color.get(&l) {
Some(c) => *c,
None => panic!("get_color_from_level error !!!") }
}
}
#[derive(Debug)]
struct TimeOption {
time_parse_formt: String,
use_local: bool,
log_time: bool
}
impl TimeOption {
fn new() -> Self {
TimeOption {
time_parse_formt: String::from(DE_TIME_PASE_FORMAT),
use_local: true,
log_time: true
}
}
fn get_time(&self) -> Result<String, error::Error>{ if self.enabled() {
let now = if self.use_local {
OffsetDateTime::now_local()?
} else {
OffsetDateTime::now_utc()
};
let time_format = format_description::parse(&self.time_parse_formt)?;
Ok(now.format(&time_format)?)
} else {
Ok(String::new())
}
}
fn set_time_format(&mut self, f: &str) {
self.time_parse_formt = String::from(f);
}
fn enabled(&self) -> bool {
self.log_time
}
fn set_use_local(&mut self, is_use: bool) {
if is_use != self.use_local {
self.use_local = is_use
}
}
fn set_log_time(&mut self, is_open: bool) {
if is_open != self.log_time {
self.log_time = is_open;
}
}
fn resolve_time_error(result: Result<String, error::Error>) -> String{ match result {
Ok(t) => t,
Err(error::Error::IndeterminateOffset(e)) => {
eprintln!("{e}");
String::new()
},
_ => String::new()
}
}
}
#[derive(Debug)]
struct DestOption {
use_dest: bool,
dest_out: Option<String>,
output_stream: String,
}
impl DestOption {
fn new() -> Self {
DestOption {
use_dest: false,
dest_out: None,
output_stream: String::new()
}
}
fn new_with_dest(dest: &str) -> Self {
DestOption {
use_dest: true,
dest_out: Some(String::from(dest)),
output_stream: String::new()
}
}
fn is_use_dest(&self) -> bool {
self.use_dest
}
fn set_use_dest(&mut self, is_open: bool) {
if self.use_dest != is_open {
self.use_dest = is_open;
}
}
fn push_output(&mut self, stream: &str) {
self.output_stream.push_str(stream);
}
}
#[derive(Debug)]
pub struct ELogger{
time_option: TimeOption,
color_option: ColorOption,
dest: Mutex<DestOption> }
impl Log for ELogger {
fn enabled(&self, _metadata: &Metadata) -> bool {
true
}
fn log(&self, record: &Record) {
let l = record.level();
let (f_color, _) = self.get_color(l);
let l = format!("[{l}]");
let time = self.get_time();
let output = format!("{}\r\n{}\r\n", time, record.args());
match self.is_use_dest() {
true => {
let output = format!("{} {}", l, output);
self.push_output(&output);
self.flush();
},
false => {
let l = l.color(f_color);
let output = format!("{} {}", l, output);
println!("{output}");
}
};
}
fn flush(&self){
let mut guard = self.dest.lock().unwrap();
match &guard.dest_out { Some(file_name) => {
let mut file = open_file(file_name);
let output = guard.output_stream.clone();
if let Ok(_) = file.write(output.as_bytes()) {
};
},
None => panic!("can't read file without filename") };
guard.output_stream.clear();
}
}
fn open_file(file_name: &str) -> File {
match OpenOptions::new().append(true).open(file_name) {
Ok(f) => f,
Err(e) => match e.kind() {
ErrorKind::NotFound => {
create_file(file_name) },
_ => panic!("unexpected error")
}
}
}
fn create_file(file_name: &str) -> File {
match OpenOptions::new().create_new(true).append(true).open(file_name) {
Ok(f) => f,
Err(e) => match e.kind() {
ErrorKind::AlreadyExists => panic!("file already exists"),
_ => panic!("unexpected error")
}
}
}
impl ELogger {
pub fn new() -> Self {
let default_level = LevelFilter::Info;
log::set_max_level(default_level);
let default_color_map = HashMap::from([
(Level::Trace, (Color::White, Color::BrightBlack)),
(Level::Debug, (Color::BrightWhite, Color::Black)),
(Level::Info, (Color::BrightBlue, Color::Black)),
(Level::Warn, (Color::BrightYellow, Color::Black)),
(Level::Error, (Color::BrightRed, Color::Black)),
]);
ELogger{
time_option: TimeOption::new(),
color_option: ColorOption::new(true, default_color_map),
dest: Mutex::new(DestOption::new())
}
}
pub fn new_dest(dest: &str) -> Self {
let s = Self::new();
*s.dest.lock().unwrap() = DestOption::new_with_dest(dest);
s
}
pub fn set_max_level(self, l: Level) -> Self {
log::set_max_level(l.to_level_filter());
self
}
pub fn set_time_format(mut self, format: &str) -> Self {
self.time_option.set_time_format(format);
self
}
pub fn get_time(&self) -> String {
TimeOption::resolve_time_error(self.time_option.get_time())
}
pub fn set_use_local(mut self, is_use: bool) -> Self {
self.time_option.set_use_local(is_use);
self
}
pub fn set_log_time(mut self, is_open: bool) -> Self {
self.time_option.set_log_time(is_open);
self
}
pub fn enable_use_color(mut self) -> Self {
self.color_option.set_use_color(true);
self
}
pub fn disable_use_color(mut self) -> Self {
self.color_option.set_use_color(false);
self
}
pub fn is_use_color(&self) -> bool {
self.color_option.get_use_color()
}
pub fn get_color(&self, l: Level) -> (Color,Color) {
self.color_option.get_color_from_level(l)
}
pub fn is_use_dest(&self) -> bool {
self.dest.lock().unwrap().is_use_dest() }
pub fn push_output(&self, stream: &str) {
self.dest.lock().unwrap().push_output(stream) }
pub fn set_use_dest(self, is_open: bool) -> Self {
self.dest.lock().unwrap().set_use_dest(is_open);
self
}
#[must_use]
pub fn init(self) -> Result<(), SetLoggerError> {
log::set_boxed_logger(Box::new(self))
}
}
pub fn quick_init() -> Result<(), SetLoggerError> {
ELogger::new().init()
}
pub fn init_use_dest(dest: &str) -> Result<(), SetLoggerError> {
ELogger::new_dest(dest).init()
}
#[cfg(test)]
mod tests {
}