use chrono::Local;
use lazy_static::lazy_static;
use std::collections::HashMap;
use std::path::Path;
use std::sync::{Mutex};
use std::{fs, io};
lazy_static! {
pub static ref WD_LOG_CONFIG: LogConfig = LogConfig::new();
}
#[derive(Eq, PartialEq, Hash, Copy, Clone)]
pub enum Level {
PANIC,
ERROR,
WARN,
INFO,
DEBUG,
}
impl Level {
pub fn as_u8(&self) -> u8 {
match *self {
PANIC => 1,
ERROR => 2,
WARN => 3,
INFO => 4,
DEBUG => 5,
}
}
pub fn to_string(&self) -> String {
match self {
Level::PANIC => String::from("PANIC"),
Level::ERROR => String::from("ERROR"),
Level::WARN => String::from("WARN"),
Level::INFO => String::from("INFO"),
Level::DEBUG => String::from("DEBUG"),
}
}
}
impl<T> From<T> for Level
where
T: ToString,
{
fn from(level: T) -> Self {
let level = level.to_string();
match level.to_lowercase().as_str() {
"panic" => PANIC,
"1" => PANIC,
"error" => ERROR,
"2" => ERROR,
"warn" => WARN,
"3" => WARN,
"info" => INFO,
"4" => INFO,
"debug" => DEBUG,
"5" => DEBUG,
_ => INFO,
}
}
}
pub const PANIC: Level = Level::PANIC;
pub const ERROR: Level = Level::ERROR;
pub const WARN: Level = Level::WARN;
pub const INFO: Level = Level::INFO;
pub const DEBUG: Level = Level::DEBUG;
pub struct LogConfig {
pub is_std_out: bool,
pub is_file_out: bool,
pub out: Mutex<Box<dyn io::Write + Send + Sync + 'static>>,
pub level: Level,
pub level_map: HashMap<Level, String>,
pub prefix: &'static str, pub print_time: bool, pub file_and_line: bool, }
impl LogConfig {
pub fn new() -> LogConfig {
let mut lp = HashMap::new();
lp.insert(DEBUG, "DEBUG".to_string());
lp.insert(INFO, "INFO ".to_string());
lp.insert(WARN, "WARN ".to_string());
lp.insert(ERROR, "ERROR".to_string());
lp.insert(PANIC, "PANIC".to_string());
LogConfig {
is_std_out: true,
out: Mutex::new(Box::new(io::stdout())),
level: DEBUG,
level_map: lp,
prefix: "wd_log",
print_time: true,
file_and_line: true,
is_file_out: false,
}
}
pub fn set_level(&mut self, level: Level) {
self.level = level;
}
pub fn close_output(&mut self){
self.is_std_out = false
}
pub fn set_prefix(&mut self, prefix: &'static str) {
self.prefix = prefix;
}
pub fn show_time(&mut self, ok: bool) {
self.print_time = ok;
}
pub fn show_file_line(&mut self, ok: bool) {
self.file_and_line = ok;
}
pub fn output_to_file<P: AsRef<Path>>(&mut self, path: P) -> io::Result<()> {
let file = fs::OpenOptions::new()
.create(true)
.write(true)
.append(true)
.open(path)?;
self.out = Mutex::new(Box::new(file));
self.is_file_out = true;
Ok(())
}
}
impl Drop for LogConfig {
fn drop(&mut self) {
if !self.is_std_out {
let mut file = self.out.lock().unwrap();
let _ = file.flush();
}
}
}
pub fn set_level(level: Level) {
#[allow(invalid_reference_casting)]
unsafe {
let log = &mut *(&*WD_LOG_CONFIG as *const LogConfig as *mut LogConfig);
log.set_level(level)
}
}
pub fn close_output(){
#[allow(invalid_reference_casting)]
unsafe {
let log = &mut *(&*WD_LOG_CONFIG as *const LogConfig as *mut LogConfig);
log.close_output();
}
}
pub fn set_prefix(prefix: &'static str) {
#[allow(invalid_reference_casting)]
unsafe {
let log = &mut *(&*WD_LOG_CONFIG as *const LogConfig as *mut LogConfig);
log.set_prefix(prefix);
}
}
pub fn show_time(show: bool) {
#[allow(invalid_reference_casting)]
unsafe {
let log = &mut *(&*WD_LOG_CONFIG as *const LogConfig as *mut LogConfig);
log.show_time(show)
}
}
pub fn show_file_line(show: bool) {
#[allow(invalid_reference_casting)]
unsafe {
let log = &mut *(&*WD_LOG_CONFIG as *const LogConfig as *mut LogConfig);
log.show_file_line(show)
}
}
pub fn output_to_file<P: AsRef<Path>>(path: P) -> io::Result<()> {
#[allow(invalid_reference_casting)]
unsafe {
let log = &mut *(&*WD_LOG_CONFIG as *const LogConfig as *mut LogConfig);
log.is_std_out = false;
log.output_to_file(path)
}
}
pub fn get_level() -> Level {
WD_LOG_CONFIG.level
}
pub fn get_prefix() -> &'static str {
#[allow(invalid_reference_casting)]
unsafe {
let log = &mut *(&*WD_LOG_CONFIG as *const LogConfig as *mut LogConfig);
log.prefix
}
}
pub fn output(level: Level, file: &str, line: u32, mut fmt: String) -> io::Result<()> {
let log = &WD_LOG_CONFIG;
if level.as_u8() > log.level.as_u8() {
return Ok(());
}
let t = if log.print_time {
Local::now().format("%Y-%m-%d %H:%M:%S%.3f ").to_string()
} else {
String::new()
};
let level_name = log.level_map.get(&level).unwrap();
let file_line = if file.len() > 0 && log.file_and_line {
format!("{}({}):", file, line)
} else {
String::new()
};
if log.is_std_out {
let mut fmt_last = "".to_string();
if let Some(s) = fmt.as_bytes().last() {
if *s == '\n' as u8 {
fmt_last.push(fmt.pop().unwrap())
}
}
let buf = match level.as_u8() {
1 => {
format!(
"\x1b[7;31m[{}{} {}]{} {}\x1b[0m{}",
t, level_name, log.prefix, file_line, fmt, fmt_last
)
}
2 => {
format!(
"\x1b[7;31m[{}{} {}]{} {}\x1b[0m{}",
t, level_name, log.prefix, file_line, fmt, fmt_last
)
}
3 => {
format!(
"\x1b[33m[{}{} {}]{} {}\x1b[0m{}",
t, level_name, log.prefix, file_line, fmt, fmt_last
)
}
4 => {
format!(
"\x1b[32m[{}{} {}]{} {}\x1b[0m{}",
t, level_name, log.prefix, file_line, fmt, fmt_last
)
}
5 => {
format!(
"\x1b[32m[{}{} {}]{} {}\x1b[0m{}",
t, level_name, log.prefix, file_line, fmt, fmt_last
)
}
_ => String::new(),
};
print!("{}", buf);
}
if !log.is_file_out {
return Ok(())
}
let buf = match level.as_u8() {
1 => {
format!("[{}{} {}]{} {}", t, level_name, log.prefix, file_line, fmt)
}
2 => {
format!("[{}{} {}]{} {}", t, level_name, log.prefix, file_line, fmt)
}
3 => {
format!("[{}{} {}]{} {}", t, level_name, log.prefix, file_line, fmt)
}
4 => {
format!("[{}{} {}]{} {}", t, level_name, log.prefix, file_line, fmt)
}
5 => {
format!("[{}{} {}]{} {}", t, level_name, log.prefix, file_line, fmt)
}
_ => String::new(),
};
let mut out = log.out.lock().unwrap();
out.write_all(buf.as_bytes())
}
#[cfg(test)]
mod test{
use lazy_static::lazy_static;
use crate::log_config::LogConfig;
lazy_static!{
pub static ref CONFIG:LogConfig = LogConfig::new();
}
#[test]
fn test(){
#[allow(invalid_reference_casting)]
unsafe {
let ptr = &mut *(&*CONFIG as *const LogConfig as *mut LogConfig);
ptr.prefix = "hello";
println!("config:{}",CONFIG.prefix)
}
}
}