From b469bee352f5b330d94d9a52ec97a20ccfb49214 Mon Sep 17 00:00:00 2001 From: Tunglies <77394545+Tunglies@users.noreply.github.com> Date: Mon, 21 Sep 2026 15:57:31 +0800 Subject: [PATCH] fix(logging): include paths in file failure logs --- Changelog.md | 1 + src-tauri/src/cmd/clash.rs | 16 +++++++++--- src-tauri/src/config/profiles.rs | 10 ++++++-- src-tauri/src/core/service.rs | 14 ++++++---- src-tauri/src/core/updater.rs | 24 +++++++++++------ src-tauri/src/core/validate.rs | 8 +++--- src-tauri/src/utils/help.rs | 8 ++++-- src-tauri/src/utils/init.rs | 41 ++++++++++++++++++++++-------- src-tauri/src/utils/resolve/dns.rs | 11 ++++---- src-tauri/src/utils/schtasks.rs | 14 +++++++--- src-tauri/src/utils/startup.rs | 2 +- 11 files changed, 104 insertions(+), 45 deletions(-) diff --git a/Changelog.md b/Changelog.md index 41de3940e..dc1a4f799 100644 --- a/Changelog.md +++ b/Changelog.md @@ -4,6 +4,7 @@ 🐞 修复问题 - 回退 2.5.4 引入的 DNS 覆写保护,修复重启后开关自动关闭 +- 优化文件错误日志,显示具体文件路径和失败原因 diff --git a/src-tauri/src/cmd/clash.rs b/src-tauri/src/cmd/clash.rs index 6bba2b789..30a258081 100644 --- a/src-tauri/src/cmd/clash.rs +++ b/src-tauri/src/cmd/clash.rs @@ -133,16 +133,26 @@ pub async fn apply_dns_config(apply: bool) -> CmdResult { let dns_path = dirs::app_home_dir().stringify_err()?.join(constants::files::DNS_CONFIG); if !dns_path.exists() { - logging!(warn, Type::Config, "DNS config file not found"); + logging!(warn, Type::Config, "DNS config file not found: {}", dns_path.display()); return Err("DNS config file not found".into()); } let dns_yaml = fs::read_to_string(&dns_path).await.stringify_err_log(|e| { - logging!(error, Type::Config, "Failed to read DNS config: {e}"); + logging!( + error, + Type::Config, + "Failed to read DNS config {}: {e}", + dns_path.display() + ); })?; let patch_config = serde_yaml_ng::from_str::(&dns_yaml).stringify_err_log(|e| { - logging!(error, Type::Config, "Failed to parse DNS config: {e}"); + logging!( + error, + Type::Config, + "Failed to parse DNS config {}: {e}", + dns_path.display() + ); })?; let mut patch = serde_yaml_ng::Mapping::new(); diff --git a/src-tauri/src/config/profiles.rs b/src-tauri/src/config/profiles.rs index 2116eadfc..0be175e08 100644 --- a/src-tauri/src/config/profiles.rs +++ b/src-tauri/src/config/profiles.rs @@ -68,8 +68,14 @@ impl ProfileDeletePlan { return; }; for file in self.files { - if let Err(error) = dir.join(file.as_str()).remove_if_exists().await { - logging!(warn, Type::Config, "清理已删除订阅文件失败: {file} - {error}"); + let path = dir.join(file.as_str()); + if let Err(error) = path.remove_if_exists().await { + logging!( + warn, + Type::Config, + "Failed to remove deleted profile file {}: {error:#}", + path.display() + ); } } } diff --git a/src-tauri/src/core/service.rs b/src-tauri/src/core/service.rs index 26409513e..ec7195a75 100644 --- a/src-tauri/src/core/service.rs +++ b/src-tauri/src/core/service.rs @@ -1351,8 +1351,9 @@ async fn sync_runtime_providers_by_service() -> Result { logging!( warn, Type::Service, - "provider cache {} was not read: {error:#}", - declared.provider.destination + "failed to read provider cache {} into {}: {error:#}", + declared.provider.destination, + temp.display() ); outcome.pending += 1; remove_temp(&temp).await; @@ -1442,8 +1443,9 @@ async fn publish_fetched( logging!( warn, Type::Service, - "provider cache {} was not published: {error}", - cache.declared.provider.destination + "failed to rename provider cache {} to {}: {error}", + cache.temp.display(), + target.display() ); remove_temp(&cache.temp).await; } @@ -1587,7 +1589,9 @@ fn has_settled(mtime_ns: Option) -> bool { // Exclusive creation protects existing files from truncation and cleanup. async fn create_sync_temp(target: &Path) -> Result<(PathBuf, tokio::fs::File)> { if let Some(parent) = target.parent() { - tokio::fs::create_dir_all(parent).await?; + tokio::fs::create_dir_all(parent) + .await + .with_context(|| format!("failed to create provider cache directory {}", parent.display()))?; } let name = target .file_name() diff --git a/src-tauri/src/core/updater.rs b/src-tauri/src/core/updater.rs index 4f53a3221..657a35048 100644 --- a/src-tauri/src/core/updater.rs +++ b/src-tauri/src/core/updater.rs @@ -1,5 +1,5 @@ use crate::{config::Config, singleton, utils::dirs}; -use anyhow::Result; +use anyhow::{Context as _, Result}; use chrono::Utc; use clash_verge_logging::{Type, logging}; use parking_lot::RwLock; @@ -47,17 +47,20 @@ impl SilentUpdater { fn write_cache(bytes: &[u8], version: &str) -> Result<()> { let cache_dir = Self::cache_dir()?; - std::fs::create_dir_all(&cache_dir)?; + std::fs::create_dir_all(&cache_dir) + .with_context(|| format!("failed to create update cache directory {}", cache_dir.display()))?; let bin_path = cache_dir.join("pending_update.bin"); - std::fs::write(&bin_path, bytes)?; + std::fs::write(&bin_path, bytes) + .with_context(|| format!("failed to write update cache {}", bin_path.display()))?; let meta = UpdateCacheMeta { version: version.to_string(), downloaded_at: Utc::now().to_rfc3339(), }; let meta_path = cache_dir.join("pending_update.json"); - std::fs::write(&meta_path, serde_json::to_string_pretty(&meta)?)?; + std::fs::write(&meta_path, serde_json::to_string_pretty(&meta)?) + .with_context(|| format!("failed to write update metadata {}", meta_path.display()))?; logging!( info, @@ -71,7 +74,7 @@ impl SilentUpdater { fn read_cache_bytes() -> Result> { let bin_path = Self::cache_dir()?.join("pending_update.bin"); - Ok(std::fs::read(bin_path)?) + std::fs::read(&bin_path).with_context(|| format!("failed to read update cache {}", bin_path.display())) } fn read_cache_meta() -> Result { @@ -85,7 +88,12 @@ impl SilentUpdater { && cache_dir.exists() { if let Err(e) = std::fs::remove_dir_all(&cache_dir) { - logging!(warn, Type::System, "Failed to delete update cache: {e}"); + logging!( + warn, + Type::System, + "Failed to delete update cache {}: {e}", + cache_dir.display() + ); } else { logging!(info, Type::System, "Update cache deleted"); } @@ -176,7 +184,7 @@ impl SilentUpdater { logging!( warn, Type::System, - "Failed to read cached update bytes: {e}, cleaning up" + "Failed to read cached update bytes: {e:#}, cleaning up" ); Self::delete_cache(); return false; @@ -456,7 +464,7 @@ impl SilentUpdater { .await?; if let Err(e) = Self::write_cache(&bytes, &version) { - logging!(warn, Type::System, "Silent updater: failed to write cache: {e}"); + logging!(warn, Type::System, "Silent updater: failed to write cache: {e:#}"); } *self.pending_bytes.write() = Some(bytes); diff --git a/src-tauri/src/core/validate.rs b/src-tauri/src/core/validate.rs index 586f84e96..90517bb44 100644 --- a/src-tauri/src/core/validate.rs +++ b/src-tauri/src/core/validate.rs @@ -226,7 +226,7 @@ impl CoreConfigValidator { Ok(content) => content, Err(err) => { let error_msg: String = format!("Failed to read file: {err}").into(); - logging!(error, Type::Validate, "无法读取文件: {}", error_msg); + logging!(error, Type::Validate, "Failed to read file {config_path}: {err}"); return Ok(ValidationOutcome::invalid_from_message(error_msg)); } }; @@ -238,7 +238,7 @@ impl CoreConfigValidator { } Err(err) => { let error_msg: String = format!("YAML syntax error: {err}").into(); - logging!(error, Type::Validate, "YAML语法错误: {}", error_msg); + logging!(error, Type::Validate, "YAML syntax error in {config_path}: {err}"); Ok(ValidationOutcome::invalid_from_message(error_msg)) } } @@ -249,7 +249,7 @@ impl CoreConfigValidator { Ok(content) => content, Err(err) => { let error_msg: String = format!("Failed to read script file: {err}").into(); - logging!(warn, Type::Validate, "脚本语法错误: {}", err); + logging!(warn, Type::Validate, "Failed to read script file {path}: {err}"); return Ok(ValidationOutcome::invalid_from_message(error_msg)); } }; @@ -291,7 +291,7 @@ impl CoreConfigValidator { } Err(err) => { let error_msg: String = format!("Script syntax error: {err}").into(); - logging!(warn, Type::Validate, "脚本语法错误: {}", err); + logging!(warn, Type::Validate, "Script syntax error in {path}: {err}"); Ok(ValidationOutcome::invalid_from_message(error_msg)) } } diff --git a/src-tauri/src/utils/help.rs b/src-tauri/src/utils/help.rs index 09a95e775..70224ec96 100644 --- a/src-tauri/src/utils/help.rs +++ b/src-tauri/src/utils/help.rs @@ -16,9 +16,13 @@ pub async fn read_yaml(path: &Path) -> Result { bail!("file not found \"{}\"", path.display()); } - let yaml_str = tokio::fs::read_to_string(path).await?; + let yaml_str = tokio::fs::read_to_string(path) + .await + .with_context(|| format!("failed to read YAML file {}", path.display()))?; - Ok(with_encryption(|| async { serde_yaml_ng::from_str::(&yaml_str) }).await?) + with_encryption(|| async { serde_yaml_ng::from_str::(&yaml_str) }) + .await + .with_context(|| format!("failed to parse YAML file {}", path.display())) } pub async fn read_mapping(path: &Path) -> Result { diff --git a/src-tauri/src/utils/init.rs b/src-tauri/src/utils/init.rs index 3b7d5df21..80e9bfeaf 100644 --- a/src-tauri/src/utils/init.rs +++ b/src-tauri/src/utils/init.rs @@ -9,7 +9,7 @@ use crate::{ help, }, }; -use anyhow::Result; +use anyhow::{Context as _, Result}; use chrono::{Local, TimeZone as _}; use clash_verge_logging::Type; #[cfg(any(target_os = "macos", target_os = "windows"))] @@ -28,8 +28,15 @@ async fn delete_snapshot_logs(log_dir: &Path) -> Result<()> { ]; for temp_dir in temp_dirs.iter().filter(|d| d.exists()) { - let mut entries = fs::read_dir(temp_dir).await?; - while let Some(entry) = entries.next_entry().await? { + let mut entries = fs::read_dir(temp_dir) + .await + .with_context(|| format!("failed to read log snapshot directory {}", temp_dir.display()))?; + while let Some(entry) = entries.next_entry().await.with_context(|| { + format!( + "failed to read next entry in log snapshot directory {}", + temp_dir.display() + ) + })? { let path = entry.path(); if path.extension().and_then(|s| s.to_str()) == Some("log") { let _ = path.remove_if_exists().await; @@ -109,15 +116,28 @@ pub async fn delete_log() -> Result<()> { }; if log_dir.exists() { - let mut log_read_dir = fs::read_dir(&log_dir).await?; - while let Some(entry) = log_read_dir.next_entry().await? { + let mut log_read_dir = fs::read_dir(&log_dir) + .await + .with_context(|| format!("failed to read log directory {}", log_dir.display()))?; + while let Some(entry) = log_read_dir + .next_entry() + .await + .with_context(|| format!("failed to read next entry in log directory {}", log_dir.display()))? + { std::mem::drop(process_file(entry).await); } } if service_log_dir.exists() { - let mut service_log_read_dir = fs::read_dir(service_log_dir).await?; - while let Some(entry) = service_log_read_dir.next_entry().await? { + let mut service_log_read_dir = fs::read_dir(&service_log_dir) + .await + .with_context(|| format!("failed to read service log directory {}", service_log_dir.display()))?; + while let Some(entry) = service_log_read_dir.next_entry().await.with_context(|| { + format!( + "failed to read next entry in service log directory {}", + service_log_dir.display() + ) + })? { std::mem::drop(process_file(entry).await); } } @@ -624,10 +644,9 @@ async fn handle_copy(src: &PathBuf, dest: &PathBuf, file: &str) { logging!( error, Type::Setup, - "failed to copy resources '{}' to '{:?}', {}", - file, - dest, - err + "failed to copy resource {} to {}: {err}", + src.display(), + dest.display() ); } }; diff --git a/src-tauri/src/utils/resolve/dns.rs b/src-tauri/src/utils/resolve/dns.rs index 3992ed1dd..35e53be1e 100644 --- a/src-tauri/src/utils/resolve/dns.rs +++ b/src-tauri/src/utils/resolve/dns.rs @@ -1,3 +1,4 @@ +use anyhow::Context as _; use clash_verge_logging::{Type, logging}; #[cfg(target_os = "macos")] use std::path::Path; @@ -9,7 +10,7 @@ const DNS_STATE_FILE: &str = ".original_dns.txt"; fn dns_state_dir() -> anyhow::Result { // The DNS scripts persist .original_dns.txt relative to their working directory. let dir = crate::utils::dirs::app_home_dir()?; - std::fs::create_dir_all(&dir)?; + std::fs::create_dir_all(&dir).with_context(|| format!("failed to create DNS state directory {}", dir.display()))?; Ok(dir) } @@ -37,14 +38,14 @@ pub async fn set_public_dns(dns_server: String) { }; let script = resource_dir.join("set_dns.sh"); if !script.exists() { - logging!(error, Type::Config, "set_dns.sh not found"); + logging!(error, Type::Config, "DNS script not found: {}", script.display()); return; } let script = script.to_string_lossy().into_owned(); let state_dir = match dns_state_dir() { Ok(dir) => dir, Err(e) => { - logging!(error, Type::Config, "Failed to get DNS state directory: {}", e); + logging!(error, Type::Config, "Failed to get DNS state directory: {e:#}"); return; } }; @@ -85,14 +86,14 @@ pub async fn restore_public_dns() { }; let script = resource_dir.join("unset_dns.sh"); if !script.exists() { - logging!(error, Type::Config, "unset_dns.sh not found"); + logging!(error, Type::Config, "DNS script not found: {}", script.display()); return; } let script = script.to_string_lossy().into_owned(); let state_dir = match dns_state_dir() { Ok(dir) => dir, Err(e) => { - logging!(error, Type::Config, "Failed to get DNS state directory: {}", e); + logging!(error, Type::Config, "Failed to get DNS state directory: {e:#}"); return; } }; diff --git a/src-tauri/src/utils/schtasks.rs b/src-tauri/src/utils/schtasks.rs index ee09040a4..9f457a729 100644 --- a/src-tauri/src/utils/schtasks.rs +++ b/src-tauri/src/utils/schtasks.rs @@ -1,5 +1,5 @@ use crate::utils::dirs::{self, PathBufExec as _}; -use anyhow::{Result, anyhow}; +use anyhow::{Context as _, Result, anyhow}; use clash_verge_logging::{Type, logging}; use std::fs; use std::os::windows::process::CommandExt as _; @@ -100,8 +100,14 @@ async fn cleanup_legacy_shortcuts() -> Result<()> { let old_shortcut = startup_dir.join("Clash-Verge.lnk"); let new_shortcut = startup_dir.join("Clash Verge.lnk"); - old_shortcut.remove_if_exists().await?; - new_shortcut.remove_if_exists().await?; + old_shortcut + .remove_if_exists() + .await + .with_context(|| format!("failed to remove startup shortcut {}", old_shortcut.display()))?; + new_shortcut + .remove_if_exists() + .await + .with_context(|| format!("failed to remove startup shortcut {}", new_shortcut.display()))?; Ok(()) } @@ -327,7 +333,7 @@ pub async fn set_auto_launch(is_enable: bool, is_admin: bool) -> Result<()> { let other = if is_admin { TaskMode::User } else { TaskMode::Admin }; if let Err(err) = cleanup_legacy_shortcuts().await { - logging!(warn, Type::Setup, "Failed to cleanup legacy startup shortcuts: {}", err); + logging!(warn, Type::Setup, "Failed to cleanup legacy startup shortcuts: {err:#}"); } if is_enable { diff --git a/src-tauri/src/utils/startup.rs b/src-tauri/src/utils/startup.rs index 31d8348f2..efd20c90a 100644 --- a/src-tauri/src/utils/startup.rs +++ b/src-tauri/src/utils/startup.rs @@ -14,7 +14,7 @@ pub(crate) fn report_error(error: &anyhow::Error) { eprintln!("[clash-verge] startup failed: {detail}"); let log_result = startup_log_path().and_then(|path| { - append_error(&path, &detail)?; + append_error(&path, &detail).with_context(|| format!("startup log {}", path.display()))?; Ok(path) }); let message = match log_result {