| use tracing::{info, warn, error}; |
| use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter}; |
| use std::fs; |
| use std::path::PathBuf; |
| use crate::modules::account::get_data_dir; |
|
|
| |
| struct LocalTimer; |
|
|
| impl tracing_subscriber::fmt::time::FormatTime for LocalTimer { |
| fn format_time(&self, w: &mut tracing_subscriber::fmt::format::Writer<'_>) -> std::fmt::Result { |
| let now = chrono::Local::now(); |
| write!(w, "{}", now.to_rfc3339()) |
| } |
| } |
|
|
| pub fn get_log_dir() -> Result<PathBuf, String> { |
| let data_dir = get_data_dir()?; |
| let log_dir = data_dir.join("logs"); |
| |
| if !log_dir.exists() { |
| fs::create_dir_all(&log_dir).map_err(|e| format!("Failed to create log directory: {}", e))?; |
| } |
| |
| Ok(log_dir) |
| } |
|
|
| |
| pub fn init_logger() { |
| |
| let _ = tracing_log::LogTracer::init(); |
| |
| let log_dir = match get_log_dir() { |
| Ok(dir) => dir, |
| Err(e) => { |
| eprintln!("Failed to initialize log directory: {}", e); |
| return; |
| } |
| }; |
| |
| |
| |
| let file_appender = tracing_appender::rolling::daily(log_dir, "app.log"); |
| let (non_blocking, _guard) = tracing_appender::non_blocking(file_appender); |
| |
| |
| let console_layer = fmt::Layer::new() |
| .with_target(false) |
| .with_thread_ids(false) |
| .with_level(true) |
| .with_timer(LocalTimer); |
| |
| |
| let file_layer = fmt::Layer::new() |
| .with_writer(non_blocking) |
| .with_ansi(false) |
| .with_target(true) |
| .with_level(true) |
| .with_timer(LocalTimer); |
|
|
| |
| let filter_layer = EnvFilter::try_from_default_env() |
| .unwrap_or_else(|_| EnvFilter::new("info")); |
|
|
| |
| let bridge_layer = crate::modules::log_bridge::TauriLogBridgeLayer::new(); |
|
|
| |
| let _ = tracing_subscriber::registry() |
| .with(filter_layer) |
| .with(console_layer) |
| .with(file_layer) |
| .with(bridge_layer) |
| .try_init(); |
|
|
| |
| |
| std::mem::forget(_guard); |
| |
| info!("Log system initialized (Console + File persistence)"); |
| |
| |
| if let Err(e) = cleanup_old_logs(7) { |
| warn!("Failed to cleanup old logs: {}", e); |
| } |
| } |
|
|
| |
| pub fn cleanup_old_logs(days_to_keep: u64) -> Result<(), String> { |
| use std::time::{SystemTime, UNIX_EPOCH}; |
| |
| let log_dir = get_log_dir()?; |
| if !log_dir.exists() { |
| return Ok(()); |
| } |
|
|
| |
| const MAX_TOTAL_SIZE_BYTES: u64 = 1024 * 1024 * 1024; |
| const TARGET_SIZE_BYTES: u64 = 512 * 1024 * 1024; |
| |
| let now = SystemTime::now() |
| .duration_since(UNIX_EPOCH) |
| .map_err(|e| format!("Failed to get system time: {}", e))? |
| .as_secs(); |
| |
| let cutoff_time = now.saturating_sub(days_to_keep * 24 * 60 * 60); |
| |
| let mut entries_info = Vec::new(); |
| let entries = fs::read_dir(&log_dir) |
| .map_err(|e| format!("Failed to read log directory: {}", e))?; |
| |
| for entry in entries { |
| if let Ok(entry) = entry { |
| let path = entry.path(); |
| if !path.is_file() { |
| continue; |
| } |
| |
| if let Ok(metadata) = fs::metadata(&path) { |
| let modified = metadata.modified().unwrap_or(SystemTime::now()); |
| let modified_secs = modified |
| .duration_since(UNIX_EPOCH) |
| .map(|d| d.as_secs()) |
| .unwrap_or(0); |
| |
| let size = metadata.len(); |
| entries_info.push((path, size, modified_secs)); |
| } |
| } |
| } |
|
|
| let mut deleted_count = 0; |
| let mut total_size_freed = 0u64; |
|
|
| |
| let mut remaining_entries = Vec::new(); |
| for (path, size, modified_secs) in entries_info { |
| if modified_secs < cutoff_time { |
| if let Err(e) = fs::remove_file(&path) { |
| warn!("Failed to delete old log file {:?}: {}", path, e); |
| remaining_entries.push((path, size, modified_secs)); |
| } else { |
| deleted_count += 1; |
| total_size_freed += size; |
| info!("Deleted old log file (expired): {:?}", path.file_name()); |
| } |
| } else { |
| remaining_entries.push((path, size, modified_secs)); |
| } |
| } |
|
|
| |
| let mut current_total_size: u64 = remaining_entries.iter().map(|(_, size, _)| *size).sum(); |
| |
| if current_total_size > MAX_TOTAL_SIZE_BYTES { |
| info!("Log directory size ({} MB) exceeds limit (1024 MB), starting size-based cleanup...", current_total_size / 1024 / 1024); |
| |
| |
| remaining_entries.sort_by_key(|(_, _, modified)| *modified); |
| |
| for (path, size, _) in remaining_entries { |
| if current_total_size <= TARGET_SIZE_BYTES { |
| break; |
| } |
| |
| |
| if let Err(e) = fs::remove_file(&path) { |
| warn!("Failed to delete log file during size cleanup {:?}: {}", path, e); |
| } else { |
| deleted_count += 1; |
| total_size_freed += size; |
| current_total_size -= size; |
| info!("Deleted log file (size limit): {:?}", path.file_name()); |
| } |
| } |
| } |
| |
| if deleted_count > 0 { |
| let size_mb = total_size_freed as f64 / 1024.0 / 1024.0; |
| info!( |
| "Log cleanup completed: deleted {} files, freed {:.2} MB space", |
| deleted_count, size_mb |
| ); |
| } |
| |
| Ok(()) |
| } |
|
|
| |
| pub fn clear_logs() -> Result<(), String> { |
| let log_dir = get_log_dir()?; |
| if log_dir.exists() { |
| |
| let entries = fs::read_dir(&log_dir).map_err(|e| format!("Failed to read log directory: {}", e))?; |
| for entry in entries { |
| if let Ok(entry) = entry { |
| let path = entry.path(); |
| if path.is_file() { |
| |
| let _ = fs::OpenOptions::new() |
| .write(true) |
| .truncate(true) |
| .open(path); |
| } |
| } |
| } |
| } |
| Ok(()) |
| } |
|
|
| |
| pub fn log_info(message: &str) { |
| info!("{}", message); |
| } |
|
|
| |
| pub fn log_warn(message: &str) { |
| warn!("{}", message); |
| } |
|
|
| |
| pub fn log_error(message: &str) { |
| error!("{}", message); |
| } |
|
|