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
+5 -1
View File
@@ -43,7 +43,11 @@ impl IClashTemp {
Self(Self::guard(map))
}
Err(err) => {
logging!(error, Type::Config, "{err}");
logging!(
error,
Type::Config,
"failed to load clash config, using template: {err:#}"
);
Self::template()
}
}
+8 -7
View File
@@ -144,16 +144,16 @@ impl Config {
.await?;
return Ok(Some(("config_validate::boot_error", error_msg)));
}
logging!(info, Type::Config, "生成运行时配置成功");
logging!(debug, Type::Config, "生成运行时配置成功");
let config_result = Self::generate_file(ConfigType::Run).await;
if config_result.is_ok() {
logging!(info, Type::Config, "开始验证配置");
logging!(debug, Type::Config, "开始验证配置");
match CoreConfigValidator::global().validate_config_outcome().await {
Ok(outcome) if outcome.is_valid() => {
logging!(info, Type::Config, "配置验证成功");
logging!(debug, Type::Config, "配置验证成功");
Ok(None)
}
Ok(outcome) => {
@@ -170,7 +170,7 @@ impl Config {
Ok(Some(("config_validate::boot_error", error_msg)))
}
Err(err) => {
logging!(warn, Type::Config, "验证过程执行失败: {}", err);
logging!(warn, Type::Config, "验证过程执行失败: {err:#}");
CoreManager::global()
.use_default_config("config_validate::process_terminated", "")
.await?;
@@ -178,7 +178,8 @@ impl Config {
}
}
} else {
logging!(warn, Type::Config, "生成配置文件失败,使用默认配置");
let error_msg = config_result.err().map(|err| err.to_string()).unwrap_or_default();
logging!(warn, Type::Config, "生成配置文件失败,使用默认配置: {error_msg}");
CoreManager::global()
.use_default_config("config_validate::error", "")
.await?;
@@ -250,13 +251,13 @@ impl Config {
.retry(backoff)
.await
{
logging!(error, Type::Setup, "Config init verification failed: {}", e);
logging!(error, Type::Setup, "Config init verification failed: {e:#}");
}
}
/// Commits drafts during exit/restart/shutdown so user changes are not lost.
pub async fn apply_all_and_save_file() {
logging!(info, Type::Config, "save all draft data");
logging!(debug, Type::Config, "save all draft data");
let save_clash_task = AsyncHandler::spawn(|| async {
let clash = Self::clash().await;
clash.apply();
+1 -1
View File
@@ -262,7 +262,7 @@ async fn owned_service_core_uses_port(port: u16) -> bool {
logging!(
warn,
Type::Service,
"Current user's service core is active but its mixed proxy port is unavailable: {error}; \
"Current user's service core is active but its mixed proxy port is unavailable: {error:#}; \
preserving the selected port until core replacement resolves ownership"
);
true
+6 -6
View File
@@ -93,7 +93,7 @@ impl IProfiles {
let path = match dirs::profiles_path() {
Ok(p) => p,
Err(err) => {
logging!(error, Type::Config, "{err}");
logging!(error, Type::Config, "failed to get profiles path: {err:#}");
return Self::default();
}
};
@@ -124,12 +124,12 @@ impl IProfiles {
}
if home_changed && let Err(err) = profiles.save_file().await {
logging!(error, Type::Config, "无法保存已清理的 profiles.yaml: {err}");
logging!(error, Type::Config, "无法保存已清理的 profiles.yaml: {err:#}");
}
profiles
}
Err(err) => {
logging!(error, Type::Config, "{err}");
logging!(error, Type::Config, "failed to read profiles.yaml: {err:#}");
Self::default()
}
}
@@ -599,7 +599,7 @@ async fn fetch_proxies_with_timeout() -> Result<Proxies> {
match handle::Handle::mihomo().get_proxies().await {
Ok(proxies) => return proxies,
Err(err) => {
logging!(debug, Type::Config, "mihomo proxies are not ready yet: {err}");
logging!(debug, Type::Config, "mihomo proxies are not ready yet: {err:#}");
tokio::time::sleep(Duration::from_millis(500)).await;
}
}
@@ -999,7 +999,7 @@ pub(crate) async fn restore_selected_nodes() {
}
fn activate_selected_nodes_with(repair: SelectionRepair) -> tokio::sync::oneshot::Receiver<()> {
logging!(info, Type::Config, "starting activating selected nodes");
logging!(debug, Type::Config, "starting activating selected nodes");
let mut active_task = ACTIVATE_SELECTED_TASK.lock();
let generation = ACTIVATE_SELECTED_GENERATION.fetch_add(1, Ordering::AcqRel) + 1;
let previous_task = active_task.take();
@@ -1043,7 +1043,7 @@ fn activate_selected_nodes_with(repair: SelectionRepair) -> tokio::sync::oneshot
handle::Handle::refresh_clash();
}
update_tray_after_activation(generation).await;
logging!(info, Type::Config, "activating selected nodes done!");
logging!(debug, Type::Config, "activating selected nodes done!");
}
});
*active_task = Some(handle);
+4 -4
View File
@@ -262,13 +262,13 @@ impl IVerge {
}
if needs_fix {
logging!(info, Type::Config, "正在保存修正后的配置文件...");
logging!(debug, Type::Config, "正在保存修正后的配置文件...");
help::save_yaml(&config_path, &config, Some("# Clash Verge Config")).await?;
logging!(info, Type::Config, "配置文件修正完成,需要重新加载配置");
Self::reload_config_after_fix(config).await;
} else {
logging!(info, Type::Config, "clash_core配置验证通过: {:?}", config.clash_core);
logging!(debug, Type::Config, "clash_core配置验证通过: {:?}", config.clash_core);
}
Ok(())
@@ -305,12 +305,12 @@ impl IVerge {
config
}
Err(err) => {
logging!(error, Type::Config, "{err}");
logging!(error, Type::Config, "failed to read verge config: {err:#}");
Self::template()
}
},
Err(err) => {
logging!(error, Type::Config, "{err}");
logging!(error, Type::Config, "failed to get verge config path: {err:#}");
Self::template()
}
}