mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-10-31 11:12:07 +08:00
fix(logging): include paths in file failure logs
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
<summary><strong> 🐞 修复问题 </strong></summary>
|
||||
|
||||
- 回退 2.5.4 引入的 DNS 覆写保护,修复重启后开关自动关闭
|
||||
- 优化文件错误日志,显示具体文件路径和失败原因
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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()
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user