refactor(log): in-place writer lifecycle and span-based core observability (#7889)

* refactor(log): migrate logging to tracing-estuary pipeline

Swap flexi_logger + clash-verge-logger wiring for the tracing-estuary
crate: buffered file sink with explicit panic/exit drains, live level
switching, and tokio-trace/chrome-trace behind crate features.

AsyncLogger moves in-house (VecDeque ring); file format and rotation
stay unchanged.

* fix(log): reset sidecar writer in place and re-pin service-ipc v2.6.7

Rebuilding the sidecar FileLogWriter on config changes leaked flexi's
unstoppable flusher thread plus an fd each time; the writer is now flushed
and reset in place, matching what the service crate does.
get_clash_logs_by_service drops the CompactString conversion layer now that
service-ipc v2.6.7 returns plain Vec<String>, and AsyncLogger is renamed
LogRing to match.

* refactor(log): span-based observability and log noise cleanup

Instrument spans carry the structure flat lines never could: the core-start
spine (mode, readiness/owner generations, pid, config file), proxy apply and
clear routes with guard generations, service IPC sessions with refusal codes
and outcomes, profile-switch terminal outcomes, subscription fetch strategies,
core upgrades with per-proxy version probes, backup flows, timer tasks, config
generation uids, and the startup setup flow; running-mode changes emit one
guarded transition event each.
~90 hand-written noise lines are gone or demoted (entry+success pairs, polled
getter echoes, phase brackets, window sub-steps, platform install chatter);
errors keep their anyhow chain, operation name, and item id, and swallowed
retry results surface per attempt.
This commit is contained in:
Tunglies
2026-09-08 05:51:04 +08:00
committed by GitHub
parent f8b69b5d62
commit 2f370d6490
43 changed files with 807 additions and 797 deletions
+3 -7
View File
@@ -11,7 +11,6 @@ use crate::{
},
};
use clash_verge_logging::{Type, logging, logging_error};
use compact_str::CompactString;
use serde_yaml_ng::Mapping;
use smartstring::alias::String;
use tokio::fs;
@@ -57,7 +56,6 @@ pub async fn change_clash_core(clash_core: String) -> CmdResult<Option<CommandFa
match CoreManager::global().restart_core().await {
Ok(_) => {
logging!(info, Type::Core, "core changed and restarted to {clash_core}");
handle::Handle::notice_message("config_core::change_success", clash_core);
handle::Handle::refresh_clash();
Ok(None)
@@ -66,7 +64,7 @@ pub async fn change_clash_core(clash_core: String) -> CmdResult<Option<CommandFa
let failed = err.context("core changed but failed to restart");
let error_msg: String = format!("{failed:#}").into();
handle::Handle::notice_message("config_core::change_error", error_msg.clone());
logging!(error, Type::Core, "{error_msg}");
logging!(error, Type::Core, "core changed but failed to restart: {error_msg}");
Ok(Some(proxy_aware_coded_error(&failed, "CORE_CHANGE_FAILED")))
}
}
@@ -108,7 +106,7 @@ pub async fn test_delay(url: String) -> CmdResult<u32> {
let result = match feat::test_delay(url).await {
Ok(delay) => delay,
Err(e) => {
logging!(error, Type::Cmd, "{}", e);
logging!(error, Type::Cmd, "get clash delay failed: {e:#}");
10000u32
}
};
@@ -147,8 +145,6 @@ pub async fn apply_dns_config(apply: bool) -> CmdResult {
logging!(error, Type::Config, "Failed to parse DNS config: {e}");
})?;
logging!(info, Type::Config, "Applying DNS config from file");
let mut patch = serde_yaml_ng::Mapping::new();
patch.insert("dns".into(), patch_config.into());
@@ -207,7 +203,7 @@ pub async fn validate_dns_config() -> CmdResult<ValidationOutcome> {
}
#[tauri::command]
pub async fn get_clash_logs() -> CmdResult<Vec<CompactString>> {
pub async fn get_clash_logs() -> CmdResult<Vec<std::string::String>> {
let logs = CoreManager::global().get_clash_logs().await.unwrap_or_default();
Ok(logs)
}
+1 -20
View File
@@ -1,7 +1,7 @@
use super::CmdResult;
use crate::cmd::StringifyErr as _;
use crate::core::{proxy_control, sysopt::Sysopt};
use clash_verge_logging::{Type, logging};
use gethostname::gethostname;
use network_interface::NetworkInterface;
use serde_yaml_ng::Mapping;
@@ -10,8 +10,6 @@ use tauri_plugin_clash_verge_sysinfo;
#[tauri::command]
pub async fn get_sys_proxy() -> CmdResult<Mapping> {
logging!(debug, Type::Network, "异步获取系统代理配置");
Sysopt::global().wait_idle().await;
// With no network service there is no proxy configured anywhere, which reads as disabled.
let sys_proxy = match Sysproxy::get_system_proxy() {
@@ -29,15 +27,6 @@ pub async fn get_sys_proxy() -> CmdResult<Mapping> {
map.insert("enable".into(), (*enable).into());
map.insert("server".into(), format!("{}:{}", host, port).into());
map.insert("bypass".into(), bypass.as_str().into());
logging!(
debug,
Type::Network,
"返回系统代理配置: enable={}, {}:{}",
sys_proxy.enable,
sys_proxy.host,
sys_proxy.port
);
Ok(map)
}
@@ -53,14 +42,6 @@ pub async fn get_auto_proxy() -> CmdResult<Mapping> {
let mut map = Mapping::new();
map.insert("enable".into(), (*enable).into());
map.insert("url".into(), url.as_str().into());
logging!(
debug,
Type::Network,
"返回自动代理配置(缓存): enable={}, url={}",
auto_proxy.enable,
auto_proxy.url
);
Ok(map)
}
+22 -31
View File
@@ -33,7 +33,6 @@ fn profile_import_error(err: &anyhow::Error) -> std::string::String {
#[tauri::command]
pub async fn get_profiles() -> CmdResult<SharedDraft<IProfiles>> {
logging!(debug, Type::Cmd, "获取配置文件列表");
let draft = Config::profiles().await;
let data = draft.data_arc();
Ok(data)
@@ -57,45 +56,39 @@ pub async fn enhance_profiles() -> CmdResult<ValidationOutcome> {
Ok(outcome)
}
Err(e) => {
logging!(error, Type::Cmd, "{}", e);
logging!(error, Type::Cmd, "enhance profiles failed: {e:#}");
Err(coded_error("PROFILE_ENHANCE_FAILED", e))
}
}
}
#[tauri::command]
#[tracing::instrument(skip_all, level = "info", fields(url = %help::mask_url(&url), uid = tracing::field::Empty))]
pub async fn import_profile(url: std::string::String, option: Option<PrfOption>) -> CmdResult {
logging!(info, Type::Cmd, "[导入订阅] 开始导入: {}", help::mask_url(&url));
let item = &mut match PrfItem::from_url(&url, None, None, option.as_ref()).await {
Ok(it) => {
logging!(info, Type::Cmd, "[导入订阅] 下载完成,开始保存配置");
it
}
Ok(it) => it,
Err(e) => {
logging!(error, Type::Cmd, "[导入订阅] 下载失败: {}", e);
logging!(error, Type::Cmd, "[导入订阅] 下载失败: {e:#}");
return Err(coded_error("PROFILE_IMPORT_FAILED", profile_import_error(&e)));
}
};
if let Err(e) = profiles_append_item_safe(item).await {
logging!(error, Type::Cmd, "[导入订阅] 保存配置失败: {}", e);
logging!(error, Type::Cmd, "[导入订阅] 保存配置失败: {e:#}");
return Err(coded_error("PROFILE_IMPORT_FAILED", e));
}
if let Err(e) = profiles_save_file_safe().await {
logging!(error, Type::Cmd, "[导入订阅] 保存配置文件失败: {}", e);
logging!(error, Type::Cmd, "[导入订阅] 保存配置文件失败: {e:#}");
return Err(coded_error("PROFILE_IMPORT_FAILED", e));
}
logging!(info, Type::Cmd, "[导入订阅] 配置文件保存成功");
logging_error!(Type::Timer, Timer::global().refresh().await);
if let Some(uid) = &item.uid {
logging!(info, Type::Cmd, "[导入订阅] 发送配置变更通知: {}", uid);
tracing::Span::current().record("uid", tracing::field::display(uid));
handle::Handle::notify_profile_changed(uid);
}
logging!(info, Type::Cmd, "[导入订阅] 导入完成: {}", help::mask_url(&url));
Ok(())
}
@@ -103,11 +96,17 @@ pub async fn import_profile(url: std::string::String, option: Option<PrfOption>)
pub async fn reorder_profile(active_id: String, over_id: String) -> CmdResult {
match profiles_reorder_safe(&active_id, &over_id).await {
Ok(_) => {
logging!(info, Type::Cmd, "重新排序配置文件");
logging!(info, Type::Cmd, "重新排序配置文件: {} -> {}", active_id, over_id);
Ok(())
}
Err(err) => {
logging!(error, Type::Cmd, "重新排序配置文件失败: {}", err);
logging!(
error,
Type::Cmd,
"重新排序配置文件失败: {} -> {}: {err:#}",
active_id,
over_id
);
Err(coded_error("PROFILE_REORDER_FAILED", err))
}
}
@@ -122,7 +121,6 @@ pub async fn create_profile(item: PrfItem, file_data: Option<String>) -> CmdResu
.with_error_code("PROFILE_CREATE_FAILED")?;
logging_error!(Type::Timer, Timer::global().refresh().await);
if let Some(uid) = &item.uid {
logging!(info, Type::Cmd, "[创建订阅] 发送配置变更通知: {}", uid);
handle::Handle::notify_profile_changed(uid);
}
Ok(())
@@ -136,7 +134,7 @@ pub async fn update_profile(index: String, option: Option<PrfOption>) -> CmdResu
match feat::update_profile(&index, option.as_ref(), true).await {
Ok(_) => Ok(()),
Err(e) => {
logging!(error, Type::Cmd, "{}", e);
logging!(error, Type::Cmd, "update profile failed: {e:#}");
Err(coded_error("PROFILE_UPDATE_FAILED", e))
}
}
@@ -183,16 +181,15 @@ pub async fn delete_profile(index: String) -> CmdResult {
drop(profile_write_guard);
if let Err(e) = Tray::global().update_tooltip().await {
logging!(warn, Type::Cmd, "Warning: 异步更新托盘提示失败: {e}");
logging!(warn, Type::Cmd, "异步更新托盘提示失败: {e:#}");
}
if let Err(e) = Tray::global().update_menu().await {
logging!(warn, Type::Cmd, "Warning: 异步更新托盘菜单失败: {e}");
logging!(warn, Type::Cmd, "异步更新托盘菜单失败: {e:#}");
}
if should_update {
handle::Handle::refresh_clash();
if let Some(current) = current.as_ref() {
logging!(info, Type::Cmd, "[删除订阅] 发送配置变更通知: {}", current);
handle::Handle::notify_profile_changed(current);
}
}
@@ -212,10 +209,9 @@ async fn restore_previous_profile(prev_profile: &String) -> CmdResult<()> {
Config::profiles().await.apply();
crate::process::AsyncHandler::spawn(|| async move {
if let Err(e) = profiles_save_file_safe().await {
logging!(warn, Type::Cmd, "Warning: 异步保存恢复配置文件失败: {e}");
logging!(warn, Type::Cmd, "异步保存恢复配置文件失败: {e:#}");
}
});
logging!(info, Type::Cmd, "成功恢复到之前的配置");
Ok(())
}
@@ -244,13 +240,12 @@ async fn handle_success(current_value: Option<&String>) -> CmdResult<ValidationO
profiles::activate_selected_nodes();
if let Err(e) = profiles_save_file_safe().await {
logging!(warn, Type::Cmd, "Warning: 异步保存配置文件失败: {e}");
logging!(warn, Type::Cmd, "异步保存配置文件失败: {e:#}");
}
if let Some(current) = current_value
&& WindowManager::get_main_window().is_some()
{
logging!(info, Type::Cmd, "向前端发送配置变更事件: {}", current);
handle::Handle::notify_profile_changed(current);
}
@@ -313,6 +308,7 @@ async fn perform_config_update(
}
#[tauri::command]
#[tracing::instrument(skip_all, level = "info", fields(target = ?profiles.current))]
pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult<ValidationOutcome> {
if CURRENT_SWITCHING_PROFILE
.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
@@ -325,10 +321,7 @@ pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult<ValidationO
let target_profile = profiles.current.as_ref();
logging!(info, Type::Cmd, "开始修改配置文件,目标profile: {:?}", target_profile);
let previous_profile = Config::profiles().await.data_arc().current.clone();
logging!(info, Type::Cmd, "当前配置: {:?}", previous_profile);
Config::profiles().await.edit_draft(|d| d.patch_config(&profiles));
@@ -338,8 +331,6 @@ pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult<ValidationO
}
pub async fn patch_profiles_config_by_profile_index(profile_index: String) -> CmdResult<ValidationOutcome> {
logging!(info, Type::Cmd, "切换配置到: {}", profile_index);
let profiles = IProfiles {
current: Some(profile_index),
items: None,
@@ -377,7 +368,7 @@ pub async fn patch_profile(index: String, profile: PrfItem) -> CmdResult {
crate::process::AsyncHandler::spawn(move || async move {
logging!(info, Type::Timer, "Timer update settings changed, refreshing timer...");
if let Err(e) = crate::core::Timer::global().refresh().await {
logging!(error, Type::Timer, "Failed to refresh timer: {}", e);
logging!(error, Type::Timer, "Failed to refresh timer: {e:#}");
} else {
crate::core::handle::Handle::notify_timer_updated(&index);
}
+2 -2
View File
@@ -87,10 +87,10 @@ async fn run_tray_sync_loop() {
loop {
match Tray::global().update_menu().await {
Ok(_) => {
logging!(info, Type::Cmd, "Tray proxy selection synced successfully");
logging!(debug, Type::Cmd, "Tray proxy selection synced successfully");
}
Err(e) => {
logging!(error, Type::Cmd, "Failed to sync tray proxy selection: {e}");
logging!(error, Type::Cmd, "Failed to sync tray proxy selection: {e:#}");
}
}
+1 -1
View File
@@ -94,7 +94,7 @@ pub async fn update_proxy_chain_config_in_runtime(proxy_chain_config: Option<ser
"Failed to apply runtime proxy chain config: {}",
outcome
),
Err(err) => logging!(error, Type::Core, "Failed to apply runtime proxy chain config: {}", err),
Err(err) => logging!(error, Type::Core, "Failed to apply runtime proxy chain config: {err:#}"),
}
Ok(())
+3 -21
View File
@@ -66,14 +66,6 @@ pub async fn save_profile_file(index: String, file_data: Option<String>) -> CmdR
// 保存新的配置文件
fs::write(&file_path, &file_data).await.stringify_err()?;
logging!(
info,
Type::Config,
"[cmd配置save] 开始验证配置文件: {}, 是否为merge文件: {}",
file_path_str,
is_merge_file
);
let changes_applied = handle_saved_profile_file(
&file_path_str,
&file_path,
@@ -144,18 +136,8 @@ async fn handle_saved_profile_file(
(ValidationNoticeTarget::Runtime, "YAML配置文件")
};
logging!(
info,
Type::Config,
"[cmd配置save] 开始{}验证: {}",
file_type,
file_path_str
);
match CoreConfigValidator::validate_config_file_outcome(file_path_str, Some(is_merge_file)).await {
Ok(outcome) if outcome.is_valid() => {
logging!(info, Type::Config, "[cmd配置save] 文件验证通过: {}", file_path_str);
}
Ok(outcome) if outcome.is_valid() => {}
Ok(outcome) => {
logging!(warn, Type::Config, "[cmd配置save] 文件验证失败: {}", outcome);
restore_original(file_path, original_content, original_existed).await?;
@@ -163,7 +145,7 @@ async fn handle_saved_profile_file(
return Ok(outcome);
}
Err(e) => {
logging!(error, Type::Config, "[cmd配置save] 验证过程发生错误: {}", e);
logging!(error, Type::Config, "[cmd配置save] 验证过程发生错误: {e:#}");
restore_original(file_path, original_content, original_existed).await?;
return Err(e.to_string().into());
}
@@ -190,7 +172,7 @@ async fn handle_saved_profile_file(
Ok(outcome)
}
Err(err) => {
logging!(error, Type::Config, "[cmd配置save] 运行时配置应用错误: {}", err);
logging!(error, Type::Config, "[cmd配置save] 运行时配置应用错误: {err:#}");
restore_original(file_path, original_content, original_existed).await?;
Err(err.to_string().into())
}