From c0f42ebe8c0b18e49370ae810b13fba1bdbbb811 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9F=A9=E5=98=89=E4=B9=90?= Date: Sun, 7 Jun 2026 23:32:22 +0800 Subject: [PATCH] fix: improve log manager (#2329) --- .../src/platform/logging/log_manager.rs | 145 ++++++++++++++++-- 1 file changed, 136 insertions(+), 9 deletions(-) diff --git a/easytier-contrib/easytier-ohrs/src/platform/logging/log_manager.rs b/easytier-contrib/easytier-ohrs/src/platform/logging/log_manager.rs index e9ab2cde..440afe0d 100644 --- a/easytier-contrib/easytier-ohrs/src/platform/logging/log_manager.rs +++ b/easytier-contrib/easytier-ohrs/src/platform/logging/log_manager.rs @@ -1,7 +1,7 @@ use napi_derive_ohos::napi; use once_cell::sync::Lazy; use std::collections::VecDeque; -use std::fs::{self, OpenOptions}; +use std::fs::{self, Metadata, OpenOptions}; use std::io::Write; use std::path::{Path, PathBuf}; use std::sync::Mutex; @@ -14,6 +14,16 @@ const LOG_FILE_SUFFIX: &str = ".log"; const MAX_LOG_FILES: usize = 10; const MAX_MEMORY_LINES: usize = 500; +#[derive(Debug, Clone)] +#[napi(object)] +pub struct LogFileInfo { + pub file_name: String, + pub display_name: String, + pub size_bytes: i64, + pub modified_ms: i64, + pub active: bool, +} + #[derive(Clone)] struct LogOptions { core_log: bool, @@ -100,6 +110,55 @@ fn sorted_log_files(dir: &Path) -> Vec { files } +fn current_log_state() -> Option<(PathBuf, Option)> { + LOG_MANAGER.lock().ok().and_then(|guard| { + guard + .log_dir + .clone() + .map(|dir| (dir, guard.active_file.clone())) + }) +} + +fn file_name(path: &Path) -> Option { + path.file_name() + .and_then(|value| value.to_str()) + .map(|value| value.to_string()) +} + +fn latest_process_log_file(dir: &Path, process_name: &str) -> Option { + let suffix = format!("-{}{}", sanitize_name(process_name), LOG_FILE_SUFFIX); + sorted_log_files(dir).into_iter().rev().find(|path| { + path.file_name() + .and_then(|value| value.to_str()) + .map(|value| value.ends_with(&suffix)) + .unwrap_or(false) + }) +} + +fn modified_millis(metadata: &Metadata) -> i64 { + metadata + .modified() + .ok() + .and_then(|time| time.duration_since(UNIX_EPOCH).ok()) + .map(|duration| duration.as_millis().min(i64::MAX as u128) as i64) + .unwrap_or(0) +} + +fn resolve_log_file(dir: &Path, requested_name: &str) -> Option { + if requested_name.contains('/') + || requested_name.contains('\\') + || requested_name.contains("..") + { + return None; + } + sorted_log_files(dir).into_iter().find(|path| { + path.file_name() + .and_then(|value| value.to_str()) + .map(|value| value == requested_name) + .unwrap_or(false) + }) +} + fn cleanup_old_logs(dir: &Path) { let files = sorted_log_files(dir); let overflow = files.len().saturating_sub(MAX_LOG_FILES); @@ -187,14 +246,28 @@ pub fn init_log_manager(root_dir: String, process_name: String) -> bool { return true; } - let active_file = dir.join(format!( - "{}{}-{}-{}{}", - LOG_FILE_PREFIX, - now_millis(), - std::process::id(), - sanitize_name(&process_name), - LOG_FILE_SUFFIX - )); + let sanitized_process_name = sanitize_name(&process_name); + let active_file = if sanitized_process_name == "ui" { + dir.join(format!( + "{}{}-{}-{}{}", + LOG_FILE_PREFIX, + now_millis(), + std::process::id(), + sanitized_process_name, + LOG_FILE_SUFFIX + )) + } else if let Some(path) = latest_process_log_file(&dir, "ui") { + path + } else { + dir.join(format!( + "{}{}-{}-{}{}", + LOG_FILE_PREFIX, + now_millis(), + std::process::id(), + sanitized_process_name, + LOG_FILE_SUFFIX + )) + }; if OpenOptions::new() .create(true) .append(true) @@ -231,6 +304,60 @@ pub fn drain_log_lines() -> Vec { .unwrap_or_default() } +#[napi] +pub fn list_log_files() -> Vec { + let Some((log_dir, active_file)) = current_log_state() else { + return Vec::new(); + }; + + let active_name = active_file.as_ref().and_then(|path| file_name(path)); + let mut files = sorted_log_files(&log_dir); + files.reverse(); + files + .into_iter() + .filter_map(|path| { + let file_name = file_name(&path)?; + let active = active_name + .as_ref() + .map(|name| name == &file_name) + .unwrap_or(false); + let metadata = fs::metadata(&path).ok(); + Some(LogFileInfo { + file_name, + display_name: if active { + "当前启动日志".to_string() + } else { + "历史日志".to_string() + }, + size_bytes: metadata + .as_ref() + .map(|value| value.len().min(i64::MAX as u64) as i64) + .unwrap_or(0), + modified_ms: metadata.as_ref().map(modified_millis).unwrap_or_default(), + active, + }) + }) + .collect() +} + +#[napi] +pub fn read_log_file(file_name: String) -> Option { + let (log_dir, _) = current_log_state()?; + let path = resolve_log_file(&log_dir, &file_name)?; + fs::read_to_string(path).ok() +} + +#[napi] +pub fn export_log_file(file_name: String, target_path: String) -> bool { + let Some((log_dir, _)) = current_log_state() else { + return false; + }; + let Some(path) = resolve_log_file(&log_dir, &file_name) else { + return false; + }; + fs::copy(path, target_path).is_ok() +} + #[napi] pub fn export_log_archive(target_path: String) -> bool { let log_dir = LOG_MANAGER