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 {