use std::cmp::PartialEq;
use chrono::Local;
use std::fs::{File, OpenOptions, create_dir_all, rename, remove_file};
use std::io::Write;
use std::sync::{Mutex, Arc};
use std::path::{Path, PathBuf};
use once_cell::sync::Lazy;
use pathdiff::diff_paths;
use backtrace;
#[derive(Clone, Copy, Debug)]
pub enum LogFileSaveType {
LogFileSaveTypeDays,
LogFileSaveTypeHours,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum LogLevel {
LogLevelInfo,
LogLevelWarning,
LogLevelError,
LogLevelDebug,
LogLevelTrace,
}
impl LogLevel {
pub fn to_str(&self) -> &'static str {
match self {
LogLevel::LogLevelInfo => "INFO",
LogLevel::LogLevelWarning => "WARNING",
LogLevel::LogLevelError => "ERROR",
LogLevel::LogLevelDebug => "DEBUG",
LogLevel::LogLevelTrace => "TRACE",
}
}
}
#[derive(Clone, Debug)]
pub struct LogConfig {
project_name: String,
enable_save_log_file: bool,
log_dir: String,
log_level: LogLevel,
file_save_days_max: u64,
}
impl Default for LogConfig {
fn default() -> Self {
let project_name = "default";
let log_dir = if cfg!(target_os = "linux") {
format!("/var/log/{}", project_name)
} else {
"./logs".to_string()
};
LogConfig {
project_name: project_name.to_string(),
enable_save_log_file: false,
log_dir,
log_level: LogLevel::LogLevelTrace,
file_save_days_max: 7,
}
}
}
pub struct LoggerManager {
config: Arc<LogConfig>,
file: Option<Mutex<File>>,
current_log_path: Mutex<PathBuf>,
}
impl LoggerManager {
pub fn default() -> Self {
let config = GLOBAL_LOG_CONFIG.lock().unwrap().clone();
LoggerManager::initialize_logger(config)
}
pub fn new(module_name: &str) -> Self {
let global_config = GLOBAL_LOG_CONFIG.lock().unwrap().clone();
let mut new_config = (*global_config).clone();
new_config.project_name = module_name.to_string();
LoggerManager::initialize_logger(new_config.into())
}
fn initialize_logger(config: Arc<LogConfig>) -> Self {
let file = if config.enable_save_log_file == true {
let file_path = LoggerManager::get_log_file_path(&config);
let log_dir = Path::new(&file_path).parent().unwrap();
create_dir_all(log_dir).expect("Failed to create log directory");
let file = OpenOptions::new()
.create(true)
.write(true)
.append(true)
.open(&file_path)
.expect("Failed to open log file");
LoggerManager::create_symlink(&file_path, &config);
Some(Mutex::new(file))
} else {
None
};
LoggerManager {
config,
file,
current_log_path: Mutex::new(PathBuf::new()),
}
}
fn get_log_file_path(config: &LogConfig) -> String {
let now = Local::now();
let date_folder = now.format("%Y-%m-%d").to_string();
let hour = now.format("%H").to_string();
format!("{}/{}/{}/{}_{}.log", config.log_dir, config.project_name, date_folder, date_folder, hour)
}
fn create_symlink(target: &str, config: &LogConfig) {
let log_dir = format!("{}/{}/run.log", config.log_dir, config.project_name);
let link_path = Path::new(&log_dir);
let target_path = Path::new(target);
let relative_target = diff_paths(target_path, link_path.parent().unwrap()).unwrap();
if let Ok(existing_target) = std::fs::read_link(&link_path) {
if existing_target == relative_target {
return; }
}
let _ = remove_file(&link_path);
#[cfg(target_family = "unix")]
{
if let Err(e) = std::os::unix::fs::symlink(&relative_target, &link_path) {
eprintln!("Failed to create symlink: {}", e);
}
}
#[cfg(target_family = "windows")]
{
if let Err(e) = std::os::windows::fs::symlink_file(&relative_target, &link_path) {
eprintln!("Failed to create symlink: {}", e);
}
}
}
fn rotate_files(&self) {
let now = Local::now();
let date_folder = now.format("%Y-%m-%d").to_string();
let hour = now.format("%H").to_string();
let log_dir = Path::new(&self.config.log_dir).join(&self.config.project_name).join(&date_folder);
for i in (0..self.config.file_save_days_max).rev() {
let src = log_dir.join(format!("{}_{}.log", date_folder, i));
let dst = log_dir.join(format!("{}_{}.log", date_folder, i + 1));
if src.exists() {
let _ = rename(src, dst);
}
}
let current_log_path = format!("{}/{}_{}.log", log_dir.display(), date_folder, hour);
let mut log_path = self.current_log_path.lock().unwrap();
*log_path = PathBuf::from(¤t_log_path);
let file = OpenOptions::new()
.create(true)
.write(true)
.append(true)
.open(¤t_log_path)
.expect("Failed to open log file");
if let Some(ref file_lock) = self.file {
let mut file_guard = file_lock.lock().unwrap();
*file_guard = file;
}
LoggerManager::create_symlink(¤t_log_path, &self.config);
}
fn should_rotate(&self) -> bool {
let now = Local::now();
let log_path = self.current_log_path.lock().unwrap();
let log_dir = Path::new(&self.config.log_dir)
.join(&self.config.project_name)
.join(now.format("%Y-%m-%d").to_string());
if log_path.starts_with(&log_dir) {
false
} else {
true
}
}
fn get_caller_info() -> String {
let backtrace = backtrace::Backtrace::new();
let exclude_list = [
std::file!(),
"backtrace::",
"rs_box::rs_box_log::rs_box_log::",
];
for frame in backtrace.frames().iter().skip(1) { for symbol in frame.symbols() {
if let Some(name) = symbol.name() {
let name_str = name.to_string();
if !exclude_list.iter().any(|&exclude| name_str.contains(exclude)) {
let parts: Vec<&str> = name_str.split("::").collect();
if parts.len() > 2 {
let method_name = parts[parts.len() - 2];
let package_name = parts[..parts.len() - 2].join("::");
return format!(
"[package--->{} method--->{} line--->{}]",
package_name,
method_name,
symbol.lineno().unwrap_or(0)
);
}
}
}
}
}
"unknown".to_string()
}
fn log_format(&self, level: LogLevel, message: &str) {
if level as u8 <= self.config.log_level as u8 {
let now = chrono::Utc::now();
let color_code = match level {
LogLevel::LogLevelInfo => "\x1b[32m", LogLevel::LogLevelWarning => "\x1b[33m", LogLevel::LogLevelError => "\x1b[31m", LogLevel::LogLevelDebug => "\x1b[36m", LogLevel::LogLevelTrace => "\x1b[34m", };
let reset_code = "\x1b[0m";
let location_info = LoggerManager::get_caller_info();
let mut log_message = format!(
"[{}] {}[{}]{} {} [{}]\n",
now.format("%Y-%m-%d %H:%M:%S %:z"),
color_code,
level.to_str(),
reset_code,
location_info,
message,
);
if level == LogLevel::LogLevelInfo || level == LogLevel::LogLevelWarning {
log_message = format!(
"[{}] {}[{}]{} [{}]\n",
now.format("%Y-%m-%d %H:%M:%S %:z"),
color_code,
level.to_str(),
reset_code,
message,
);
}
if let Some(ref file) = self.file {
if self.should_rotate() {
self.rotate_files();
}
let mut file = file.lock().unwrap();
file.write_all(log_message.as_bytes()).unwrap();
} else {
print!("{}", log_message);
}
}
}
pub fn log_info_f(&self, message: &str) {
self.log_format(LogLevel::LogLevelInfo, message);
}
pub fn log_warning_f(&self, message: &str) {
self.log_format(LogLevel::LogLevelWarning, message);
}
pub fn log_error_f(&self, message: &str) {
self.log_format(LogLevel::LogLevelError, message);
}
pub fn log_debug_f(&self, message: &str) {
self.log_format(LogLevel::LogLevelDebug, message);
}
pub fn log_trace_f(&self, message: &str) {
self.log_format(LogLevel::LogLevelTrace, message);
}
}
static GLOBAL_LOG_CONFIG: Lazy<Mutex<Arc<LogConfig>>> = Lazy::new(|| Mutex::new(Arc::new(LogConfig::default())));
static DEFAULT_LOGGER: Lazy<Mutex<LoggerManager>> = Lazy::new(|| Mutex::new(LoggerManager::default()));
pub fn setup_log_tools(project_name: &str, enable_save_log_file: bool, log_dir: &str, log_level: LogLevel, file_save_days_max: u64) {
let log_dir = if log_dir.is_empty() {
if cfg!(target_os = "linux") {
format!("/var/log/{}", project_name)
} else {
"./logs".to_string()
}
} else {
log_dir.to_string()
};
let new_config = Arc::new(LogConfig {
project_name: project_name.to_string(),
enable_save_log_file,
log_dir,
log_level,
file_save_days_max,
});
{
let mut config = GLOBAL_LOG_CONFIG.lock().unwrap();
*config = new_config.clone();
}
{
let mut logger = DEFAULT_LOGGER.lock().unwrap();
*logger = LoggerManager::initialize_logger(new_config);
}
}
pub fn with_default_logger<F>(log_function: F)
where
F: FnOnce(&LoggerManager),
{
let logger = DEFAULT_LOGGER.lock().unwrap();
log_function(&*logger);
}
pub fn log_info(message: &str) {
with_default_logger(|logger| logger.log_info_f(message));
}
pub fn log_warning(message: &str) {
with_default_logger(|logger| logger.log_warning_f(message));
}
pub fn log_error(message: &str) {
with_default_logger(|logger| logger.log_error_f(message));
}
pub fn log_debug(message: &str) {
with_default_logger(|logger| logger.log_debug_f(message));
}
pub fn log_trace(message: &str) {
with_default_logger(|logger| logger.log_trace_f(message));
}
#[macro_export]
macro_rules! log_infof {
($($arg:tt)*) => {
$crate::with_default_logger(|logger| {
logger.log_infof(&format!($($arg)*));
})
};
}
#[macro_export]
macro_rules! log_warningf {
($($arg:tt)*) => {
$crate::with_default_logger(|logger| {
logger.log_warningf(&format!($($arg)*));
})
};
}
#[macro_export]
macro_rules! log_errorf {
($($arg:tt)*) => {
$crate::with_default_logger(|logger| {
logger.log_errorf(&format!($($arg)*));
})
};
}
#[macro_export]
macro_rules! log_debugf {
($($arg:tt)*) => {
$crate::with_default_logger(|logger| {
logger.log_debugf(&format!($($arg)*));
})
};
}
#[macro_export]
macro_rules! log_tracef {
($($arg:tt)*) => {
$crate::with_default_logger(|logger| {
logger.log_tracef(&format!($($arg)*));
})
};
}