fix(logging): include paths in file failure logs

This commit is contained in:
Tunglies
2026-09-21 15:57:31 +08:00
parent df79ed3efb
commit b469bee352
11 changed files with 104 additions and 45 deletions
+1
View File
@@ -4,6 +4,7 @@
<summary><strong> 🐞 修复问题 </strong></summary>
- 回退 2.5.4 引入的 DNS 覆写保护,修复重启后开关自动关闭
- 优化文件错误日志,显示具体文件路径和失败原因
</details>
+13 -3
View File
@@ -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::<serde_yaml_ng::Mapping>(&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();
+8 -2
View File
@@ -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()
);
}
}
}
+9 -5
View File
@@ -1351,8 +1351,9 @@ async fn sync_runtime_providers_by_service() -> Result<ProviderSync> {
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<u64>) -> 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()
+16 -8
View File
@@ -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<Vec<u8>> {
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<UpdateCacheMeta> {
@@ -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);
+4 -4
View File
@@ -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))
}
}
+6 -2
View File
@@ -16,9 +16,13 @@ pub async fn read_yaml<T: DeserializeOwned>(path: &Path) -> Result<T> {
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::<T>(&yaml_str) }).await?)
with_encryption(|| async { serde_yaml_ng::from_str::<T>(&yaml_str) })
.await
.with_context(|| format!("failed to parse YAML file {}", path.display()))
}
pub async fn read_mapping(path: &Path) -> Result<Mapping> {
+30 -11
View File
@@ -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()
);
}
};
+6 -5
View File
@@ -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<PathBuf> {
// 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;
}
};
+10 -4
View File
@@ -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 {
+1 -1
View File
@@ -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 {