mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-11-03 21:52:11 +08:00
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:
@@ -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,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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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:#}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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(())
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
+48
-223
@@ -1,236 +1,61 @@
|
||||
use std::{
|
||||
str::FromStr as _,
|
||||
sync::{
|
||||
Arc,
|
||||
atomic::{AtomicU64, AtomicUsize, Ordering},
|
||||
},
|
||||
};
|
||||
|
||||
use anyhow::{Result, bail};
|
||||
use clash_verge_logging::{Type, logging};
|
||||
use compact_str::CompactString;
|
||||
use flexi_logger::{
|
||||
Cleanup, Criterion, DeferredNow, FileSpec, LogSpecBuilder, LogSpecification, LoggerHandle,
|
||||
writers::{FileLogWriter, FileLogWriterBuilder, LogWriter as _},
|
||||
};
|
||||
use log::{Level, LevelFilter, Record};
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
use anyhow::Result;
|
||||
use clash_verge_logging::{LoggerConfig, Type, logging};
|
||||
|
||||
use crate::{
|
||||
core::{CoreManager, manager::RunningMode, service},
|
||||
singleton,
|
||||
utils::dirs::{self, sidecar_log_dir},
|
||||
utils::dirs,
|
||||
};
|
||||
|
||||
pub use clash_verge_logging::Logger;
|
||||
|
||||
const fn should_sync_service_writer(running_mode: RunningMode) -> bool {
|
||||
matches!(running_mode, RunningMode::Service)
|
||||
}
|
||||
|
||||
pub struct Logger {
|
||||
handle: Arc<Mutex<Option<LoggerHandle>>>,
|
||||
sidecar_file_writer: Arc<RwLock<Option<FileLogWriter>>>,
|
||||
log_level: Arc<RwLock<LevelFilter>>,
|
||||
log_max_size: AtomicU64,
|
||||
log_max_count: AtomicUsize,
|
||||
}
|
||||
|
||||
impl Default for Logger {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
handle: Arc::new(Mutex::new(None)),
|
||||
sidecar_file_writer: Arc::new(RwLock::new(None)),
|
||||
log_level: Arc::new(RwLock::new(LevelFilter::Info)),
|
||||
log_max_size: AtomicU64::new(128),
|
||||
log_max_count: AtomicUsize::new(8),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
singleton!(Logger, LOGGER);
|
||||
|
||||
impl Logger {
|
||||
fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub async fn init(&self) -> Result<()> {
|
||||
let (log_level, log_max_size, log_max_count) = {
|
||||
let verge_guard = crate::config::Config::verge().await;
|
||||
let verge = verge_guard.latest_arc();
|
||||
(
|
||||
verge.get_log_level(),
|
||||
verge.app_log_max_size.unwrap_or(128),
|
||||
verge.app_log_max_count.unwrap_or(8),
|
||||
)
|
||||
};
|
||||
let log_level = std::env::var("RUST_LOG")
|
||||
.ok()
|
||||
.and_then(|v| log::LevelFilter::from_str(&v).ok())
|
||||
.unwrap_or(log_level);
|
||||
*self.log_level.write() = log_level;
|
||||
self.log_max_size.store(log_max_size, Ordering::SeqCst);
|
||||
self.log_max_count.store(log_max_count, Ordering::SeqCst);
|
||||
|
||||
#[cfg(not(any(feature = "tauri-dev", feature = "tokio-trace")))]
|
||||
{
|
||||
let log_spec = Self::generate_log_spec(log_level);
|
||||
let log_dir = dirs::app_logs_dir()?;
|
||||
let logger = flexi_logger::Logger::with(log_spec)
|
||||
.log_to_file(FileSpec::default().directory(log_dir).basename(""))
|
||||
.duplicate_to_stdout(log_level.into())
|
||||
.format(clash_verge_logger::console_format)
|
||||
.format_for_files(clash_verge_logger::file_format_with_level)
|
||||
.rotate(
|
||||
Criterion::Size(log_max_size * 1024),
|
||||
flexi_logger::Naming::TimestampsCustomFormat {
|
||||
current_infix: Some("latest"),
|
||||
format: "%Y-%m-%d_%H-%M-%S",
|
||||
},
|
||||
Cleanup::KeepLogFiles(log_max_count),
|
||||
);
|
||||
|
||||
let mut filter_modules = vec!["wry", "tokio_tungstenite", "tungstenite"];
|
||||
#[cfg(not(feature = "tracing"))]
|
||||
filter_modules.push("tauri");
|
||||
#[cfg(feature = "tracing")]
|
||||
filter_modules.extend(["tauri_plugin_mihomo", "kode_bridge"]);
|
||||
let logger = logger.filter(Box::new(clash_verge_logging::ModuleFilter::new(
|
||||
filter_modules,
|
||||
Some(vec!["tauri_plugin_mihomo"]),
|
||||
)));
|
||||
|
||||
let handle = logger.start()?;
|
||||
*self.handle.lock() = Some(handle);
|
||||
}
|
||||
|
||||
let sidecar_file_writer = self.generate_sidecar_writer()?;
|
||||
*self.sidecar_file_writer.write() = Some(sidecar_file_writer);
|
||||
|
||||
std::panic::set_hook(Box::new(move |info| {
|
||||
// Capture both common panic payload types instead of logging String payloads as unknown.
|
||||
// This global hook covers panics after logger init; early setup panics are handled separately.
|
||||
let payload = info
|
||||
.payload()
|
||||
.downcast_ref::<&str>()
|
||||
.map(|s| (*s).to_string())
|
||||
.or_else(|| info.payload().downcast_ref::<String>().cloned())
|
||||
.unwrap_or_else(|| "Unknown panic payload".to_string());
|
||||
let location = info
|
||||
.location()
|
||||
.map(|loc| format!("{}:{}", loc.file(), loc.line()))
|
||||
.unwrap_or_else(|| "Unknown location".to_string());
|
||||
logging!(error, Type::System, "Panic occurred at {}: {}", location, payload);
|
||||
if let Some(h) = Self::global().handle.lock().as_ref() {
|
||||
h.flush();
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
}
|
||||
}));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn generate_log_spec(log_level: LevelFilter) -> LogSpecification {
|
||||
let mut spec = LogSpecBuilder::new();
|
||||
let log_level = std::env::var("RUST_LOG")
|
||||
.ok()
|
||||
.and_then(|v| log::LevelFilter::from_str(&v).ok())
|
||||
.unwrap_or(log_level);
|
||||
spec.default(log_level);
|
||||
#[cfg(feature = "tracing")]
|
||||
spec.module("tauri", log::LevelFilter::Debug)
|
||||
.module("wry", log::LevelFilter::Off)
|
||||
.module("tauri_plugin_mihomo", log::LevelFilter::Off);
|
||||
spec.build()
|
||||
}
|
||||
|
||||
fn generate_file_log_writer(&self) -> Result<FileLogWriterBuilder> {
|
||||
let log_dir = dirs::app_logs_dir()?;
|
||||
let log_max_size = self.log_max_size.load(Ordering::SeqCst);
|
||||
let log_max_count = self.log_max_count.load(Ordering::SeqCst);
|
||||
let flwb = FileLogWriter::builder(FileSpec::default().directory(log_dir).basename("")).rotate(
|
||||
Criterion::Size(log_max_size * 1024),
|
||||
flexi_logger::Naming::TimestampsCustomFormat {
|
||||
current_infix: Some("latest"),
|
||||
format: "%Y-%m-%d_%H-%M-%S",
|
||||
},
|
||||
Cleanup::KeepLogFiles(log_max_count),
|
||||
);
|
||||
Ok(flwb)
|
||||
}
|
||||
|
||||
/// only update app log level
|
||||
pub fn update_log_level(&self, level: LevelFilter) -> Result<()> {
|
||||
*self.log_level.write() = level;
|
||||
let log_level = self.log_level.read().to_owned();
|
||||
if let Some(handle) = self.handle.lock().as_mut() {
|
||||
let log_spec = Self::generate_log_spec(log_level);
|
||||
handle.set_new_spec(log_spec);
|
||||
handle.adapt_duplication_to_stdout(log_level.into())?;
|
||||
} else {
|
||||
bail!("failed to get logger handle, make sure it init");
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// update app and mihomo core log config
|
||||
pub async fn update_log_config(&self, log_max_size: u64, log_max_count: usize) -> Result<()> {
|
||||
self.log_max_size.store(log_max_size, Ordering::SeqCst);
|
||||
self.log_max_count.store(log_max_count, Ordering::SeqCst);
|
||||
if let Some(handle) = self.handle.lock().as_ref() {
|
||||
let log_file_writer = self.generate_file_log_writer()?;
|
||||
handle.reset_flw(&log_file_writer)?;
|
||||
} else {
|
||||
bail!("failed to get logger handle, make sure it init");
|
||||
};
|
||||
let sidecar_writer = self.generate_sidecar_writer()?;
|
||||
*self.sidecar_file_writer.write() = Some(sidecar_writer);
|
||||
|
||||
// The service writer is auxiliary to the local logger. Synchronize it only
|
||||
// for an active service session and do not roll back local settings on failure.
|
||||
if should_sync_service_writer(*CoreManager::global().get_running_mode())
|
||||
&& let Err(error) = service::update_writer_by_service(&clash_verge_service_ipc::WriterConfig {
|
||||
directory: String::new(),
|
||||
max_log_size: log_max_size * 1024,
|
||||
max_log_files: log_max_count,
|
||||
})
|
||||
.await
|
||||
{
|
||||
logging!(warn, Type::Service, "failed to update service writer config: {error:#}");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn generate_sidecar_writer(&self) -> Result<FileLogWriter> {
|
||||
let sidecar_log_dir = sidecar_log_dir()?;
|
||||
let log_max_size = self.log_max_size.load(Ordering::SeqCst);
|
||||
let log_max_count = self.log_max_count.load(Ordering::SeqCst);
|
||||
Ok(FileLogWriter::builder(
|
||||
FileSpec::default()
|
||||
.directory(sidecar_log_dir)
|
||||
.basename("sidecar")
|
||||
.suppress_timestamp(),
|
||||
async fn logger_config() -> Result<LoggerConfig> {
|
||||
let (level, max_size, max_count) = {
|
||||
let verge_guard = crate::config::Config::verge().await;
|
||||
let verge = verge_guard.latest_arc();
|
||||
(
|
||||
verge.get_log_level(),
|
||||
verge.app_log_max_size.unwrap_or(128),
|
||||
verge.app_log_max_count.unwrap_or(8),
|
||||
)
|
||||
.format(clash_verge_logger::file_format_without_level)
|
||||
.rotate(
|
||||
Criterion::Size(log_max_size * 1024),
|
||||
flexi_logger::Naming::TimestampsCustomFormat {
|
||||
current_infix: Some("latest"),
|
||||
format: "%Y-%m-%d_%H-%M-%S",
|
||||
},
|
||||
Cleanup::KeepLogFiles(log_max_count),
|
||||
)
|
||||
.try_build()?)
|
||||
};
|
||||
Ok(LoggerConfig {
|
||||
level,
|
||||
max_size_kb: max_size,
|
||||
max_count,
|
||||
log_dir: dirs::app_logs_dir()?,
|
||||
sidecar_log_dir: dirs::sidecar_log_dir()?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Initializes the logging stack. The devtools plugin installs its own global
|
||||
/// subscriber; in that mode only the sidecar writer and panic hook are set up.
|
||||
pub async fn init() -> Result<()> {
|
||||
let cfg = logger_config().await?;
|
||||
#[cfg(not(feature = "tauri-dev"))]
|
||||
Logger::global().init(&cfg)?;
|
||||
Logger::global().init_sidecar(&cfg)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn update_log_config(log_max_size: u64, log_max_count: usize) -> Result<()> {
|
||||
Logger::global().update_log_config(log_max_size, log_max_count)?;
|
||||
|
||||
// The service writer is auxiliary to the local logger. Synchronize it only
|
||||
// for an active service session and do not roll back local settings on failure.
|
||||
if should_sync_service_writer(*CoreManager::global().get_running_mode())
|
||||
&& let Err(error) = service::update_writer_by_service(&clash_verge_service_ipc::WriterConfig {
|
||||
directory: String::new(),
|
||||
max_log_size: log_max_size * 1024,
|
||||
max_log_files: log_max_count,
|
||||
})
|
||||
.await
|
||||
{
|
||||
logging!(warn, Type::Service, "failed to update service writer config: {error:#}");
|
||||
}
|
||||
|
||||
pub fn writer_sidecar_log(&self, level: Level, message: &CompactString) {
|
||||
if let Some(writer) = self.sidecar_file_writer.read().as_ref() {
|
||||
let mut now = DeferredNow::default();
|
||||
let args = format_args!("{}", message);
|
||||
let record = Record::builder().args(args).level(level).target("sidecar").build();
|
||||
let _ = writer.write(&mut now, &record);
|
||||
} else {
|
||||
logging!(error, Type::System, "failed to get sidecar file log writer");
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ async fn ask_to_stage(path: &std::path::Path) -> StageAttempt {
|
||||
match crate::core::service::stage_runtime_by_service(path).await {
|
||||
Ok(StageRequest::Answered(outcome)) => StageAttempt::Answered(outcome),
|
||||
Ok(StageRequest::Refused { code, message }) => {
|
||||
let message = message.to_string().into();
|
||||
let message = message.into();
|
||||
if StageRequest::is_about_the_bundle(code) {
|
||||
StageAttempt::RefusedTheBundle(message)
|
||||
} else {
|
||||
@@ -103,6 +103,7 @@ async fn ask_to_stage(path: &std::path::Path) -> StageAttempt {
|
||||
|
||||
/// Confirms one silent request because the service may commit before its reply is lost.
|
||||
/// Re-staging an already committed bundle is idempotent and bounded by `confirm_within`.
|
||||
#[tracing::instrument(skip_all, level = "debug", fields(confirm_within = ?confirm_within, asks = 1))]
|
||||
async fn stage_with_confirmation<Ask, Fut>(confirm_within: Duration, ask: Ask) -> StageAttempt
|
||||
where
|
||||
Ask: Fn() -> Fut,
|
||||
@@ -117,6 +118,7 @@ where
|
||||
Type::Core,
|
||||
"Staging did not answer ({first}); asking once more before replacing the core"
|
||||
);
|
||||
tracing::Span::current().record("asks", 2);
|
||||
match tokio::time::timeout(confirm_within, ask()).await {
|
||||
Ok(StageAttempt::Unanswered(again)) => {
|
||||
StageAttempt::Unanswered(format!("{first}; asked again: {again}").into())
|
||||
@@ -150,6 +152,7 @@ impl CoreManager {
|
||||
self.update_config_with_force(true).await
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all, level = "info", fields(force))]
|
||||
pub async fn update_config_with_force(&self, force: bool) -> Result<ValidationOutcome> {
|
||||
if handle::Handle::global().is_exiting() {
|
||||
return Ok(ValidationOutcome::Skipped {
|
||||
@@ -158,7 +161,7 @@ impl CoreManager {
|
||||
}
|
||||
|
||||
if !self.try_start_config_update() {
|
||||
logging!(info, Type::Core, "Configuration update is already running");
|
||||
logging!(debug, Type::Core, "Configuration update is already running");
|
||||
return Ok(ValidationOutcome::Busy);
|
||||
}
|
||||
defer! {
|
||||
@@ -179,6 +182,7 @@ impl CoreManager {
|
||||
self.perform_config_update(None).await
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all, level = "info", fields(profile = ?candidate.current, outcome = tracing::field::Empty))]
|
||||
pub(crate) async fn update_config_forced_with_profiles(
|
||||
&self,
|
||||
candidate: &IProfiles,
|
||||
@@ -202,17 +206,23 @@ impl CoreManager {
|
||||
{
|
||||
Ok(outcome) => outcome,
|
||||
Err(error) => {
|
||||
tracing::Span::current().record("outcome", "rolled_back");
|
||||
self.restore_profile_config(rollback).await?;
|
||||
return Err(error);
|
||||
}
|
||||
};
|
||||
if !outcome.is_valid() {
|
||||
tracing::Span::current().record("outcome", "invalid");
|
||||
crate::config::profiles::restore_selected_nodes().await;
|
||||
return Ok(Err(outcome));
|
||||
}
|
||||
match candidate.save_file().await {
|
||||
Ok(()) => Ok(Ok(guard)),
|
||||
Ok(()) => {
|
||||
tracing::Span::current().record("outcome", "committed");
|
||||
Ok(Ok(guard))
|
||||
}
|
||||
Err(error) => {
|
||||
tracing::Span::current().record("outcome", "save_rolled_back");
|
||||
self.restore_profile_config(rollback).await?;
|
||||
Err(error)
|
||||
}
|
||||
@@ -273,7 +283,7 @@ impl CoreManager {
|
||||
F: FnOnce(&mut IRuntime),
|
||||
{
|
||||
if !self.try_start_config_update() {
|
||||
logging!(info, Type::Core, "Configuration update is already running");
|
||||
logging!(debug, Type::Core, "Configuration update is already running");
|
||||
return Ok(ValidationOutcome::Busy);
|
||||
}
|
||||
defer! {
|
||||
@@ -315,6 +325,7 @@ impl CoreManager {
|
||||
|
||||
/// Applies through the service, replacing the core when in-place staging is unavailable.
|
||||
/// Caller must hold `lifecycle_lock`.
|
||||
#[tracing::instrument(skip_all, level = "info", fields(path = %path.display(), outcome = tracing::field::Empty))]
|
||||
async fn apply_config_by_service(&self, path: &std::path::Path) -> Result<()> {
|
||||
match plan_config_application(&self.attempt_staging(path).await) {
|
||||
ConfigApplication::Fail(message) => {
|
||||
@@ -328,7 +339,7 @@ impl CoreManager {
|
||||
}
|
||||
ConfigApplication::ReloadFrom(staged) => match self.reload_config(&staged).await {
|
||||
Ok(()) => {
|
||||
logging!(info, Type::Core, "Configuration staged and applied by service");
|
||||
tracing::Span::current().record("outcome", "staged");
|
||||
return Ok(());
|
||||
}
|
||||
Err(err) => logging!(
|
||||
@@ -341,7 +352,7 @@ impl CoreManager {
|
||||
}
|
||||
|
||||
self.replace_service_core_with_config(path).await?;
|
||||
logging!(info, Type::Core, "Configuration materialized and applied by service");
|
||||
tracing::Span::current().record("outcome", "replaced");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -353,9 +364,10 @@ impl CoreManager {
|
||||
}
|
||||
|
||||
/// Reload the Core from `path`, and replace the Core if it will not take it.
|
||||
#[tracing::instrument(skip_all, level = "info", fields(outcome = tracing::field::Empty))]
|
||||
async fn reload_or_restart(&self, path: &str) -> Result<()> {
|
||||
let Err(err) = self.reload_config(path).await else {
|
||||
logging!(info, Type::Core, "Configuration applied");
|
||||
tracing::Span::current().record("outcome", "reloaded");
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
@@ -366,7 +378,7 @@ impl CoreManager {
|
||||
);
|
||||
match self.restart_core_during_config_update().await {
|
||||
Ok(_) => {
|
||||
logging!(info, Type::Core, "Configuration applied after restart");
|
||||
tracing::Span::current().record("outcome", "restarted");
|
||||
Ok(())
|
||||
}
|
||||
Err(err) => {
|
||||
|
||||
@@ -317,6 +317,7 @@ impl CoreManager {
|
||||
.await
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all, level = "info", fields(status = tracing::field::Empty, tun_disabled = false, readiness_generation = tracing::field::Empty))]
|
||||
pub async fn continue_with_sidecar(&self) -> Result<()> {
|
||||
if !self.try_start_config_update() {
|
||||
anyhow::bail!("configuration update is already running");
|
||||
@@ -329,6 +330,7 @@ impl CoreManager {
|
||||
let config_write = Config::lock_config_write().await;
|
||||
let _life = self.lifecycle_lock.lock().await;
|
||||
let status = SERVICE_MANAGER.current().await;
|
||||
tracing::Span::current().record("status", tracing::field::debug(&status));
|
||||
let mode = self.get_running_mode();
|
||||
if !can_allow_sidecar_for_session(&mode, &status) {
|
||||
anyhow::bail!("Sidecar continuation is not allowed from {mode:?} / {status:?}");
|
||||
@@ -342,6 +344,7 @@ impl CoreManager {
|
||||
.state()
|
||||
.tun_should_be_disabled(tun_enabled)
|
||||
{
|
||||
tracing::Span::current().record("tun_disabled", true);
|
||||
Config::disable_tun_and_persist().await?;
|
||||
}
|
||||
Config::generate().await
|
||||
@@ -361,6 +364,7 @@ impl CoreManager {
|
||||
{
|
||||
anyhow::bail!("Sidecar did not become ready");
|
||||
}
|
||||
tracing::Span::current().record("readiness_generation", self.current_core_readiness_generation());
|
||||
self.apply_proxy_after_start().await
|
||||
}
|
||||
.await;
|
||||
@@ -376,6 +380,7 @@ impl CoreManager {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all, level = "info", fields(readiness_generation = tracing::field::Empty))]
|
||||
pub async fn uninstall_service_and_start_sidecar(&self) -> Result<()> {
|
||||
if !self.try_start_config_update() {
|
||||
anyhow::bail!("configuration update is already running");
|
||||
@@ -413,6 +418,7 @@ impl CoreManager {
|
||||
{
|
||||
anyhow::bail!("Sidecar did not become ready after service uninstall");
|
||||
}
|
||||
tracing::Span::current().record("readiness_generation", self.current_core_readiness_generation());
|
||||
self.apply_proxy_after_start().await
|
||||
},
|
||||
)
|
||||
@@ -456,6 +462,7 @@ impl CoreManager {
|
||||
.await
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all, level = "info", fields(mode = ?*self.get_running_mode(), readiness_generation = self.current_core_readiness_generation(), owner_generation = crate::core::service::owner_monitor_generation()))]
|
||||
pub(crate) async fn apply_proxy_after_start(&self) -> Result<()> {
|
||||
let expectation = ProxyRestoreExpectation::capture(
|
||||
*self.get_running_mode(),
|
||||
@@ -519,6 +526,7 @@ impl CoreManager {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all, level = "info", fields(decision = tracing::field::Empty))]
|
||||
async fn start_core_inner(&self) -> Result<()> {
|
||||
if Handle::global().is_exiting() {
|
||||
return Ok(());
|
||||
@@ -526,7 +534,7 @@ impl CoreManager {
|
||||
|
||||
if !matches!(*self.get_running_mode(), RunningMode::NotRunning) {
|
||||
logging!(
|
||||
info,
|
||||
debug,
|
||||
Type::Core,
|
||||
"start_core called while a core is running; treated as no-op"
|
||||
);
|
||||
@@ -534,6 +542,7 @@ impl CoreManager {
|
||||
}
|
||||
|
||||
let startup = self.prepare_startup().await;
|
||||
tracing::Span::current().record("decision", tracing::field::debug(&startup));
|
||||
if matches!(startup, StartupDecision::Wait) {
|
||||
self.rollback_failed_start().await;
|
||||
return Ok(());
|
||||
@@ -563,6 +572,7 @@ impl CoreManager {
|
||||
result
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all, level = "info", fields(mode = ?*self.get_running_mode()))]
|
||||
pub async fn stop_core(&self) -> Result<()> {
|
||||
let _life = self.lifecycle_lock.lock().await;
|
||||
self.controlled_stop_core_inner().await
|
||||
@@ -594,7 +604,7 @@ impl CoreManager {
|
||||
}
|
||||
|
||||
async fn stop_core_unprepared_inner(&self) -> Result<()> {
|
||||
CLASH_LOGGER.clear_logs().await;
|
||||
CLASH_LOGGER.clear_logs();
|
||||
match *self.get_running_mode() {
|
||||
RunningMode::Service => self.stop_core_by_service().await,
|
||||
RunningMode::Sidecar => {
|
||||
@@ -605,6 +615,7 @@ impl CoreManager {
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all, level = "info")]
|
||||
pub async fn restart_core(&self) -> Result<()> {
|
||||
if !self.try_start_config_update() {
|
||||
anyhow::bail!("configuration update is already running");
|
||||
@@ -617,7 +628,6 @@ impl CoreManager {
|
||||
|
||||
pub(crate) async fn restart_core_during_config_update(&self) -> Result<()> {
|
||||
let _life = self.lifecycle_lock.lock().await;
|
||||
logging!(info, Type::Core, "Restarting core");
|
||||
let proxy_intent = self.proxy_stop_intent().await;
|
||||
run_core_replacement_transition(
|
||||
|| self.controlled_stop_core_with_intent(proxy_intent),
|
||||
@@ -635,6 +645,7 @@ impl CoreManager {
|
||||
.await
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all, level = "info", fields(core = %clash_core))]
|
||||
pub async fn change_core(&self, clash_core: &String) -> Result<()> {
|
||||
if !IVerge::VALID_CLASH_CORES.contains(&clash_core.as_str()) {
|
||||
anyhow::bail!("invalid clash core: {clash_core}");
|
||||
@@ -757,6 +768,7 @@ impl CoreManager {
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
#[tracing::instrument(skip_all, level = "debug", fields(outcome = tracing::field::Empty))]
|
||||
async fn try_handoff_sidecar_to_service(&self) -> HandoffOutcome {
|
||||
// Before probing: a probe reply records an observation, which clears sidecar_allowed.
|
||||
if crate::core::runstate::RUN_STATE.state().sidecar_allowed {
|
||||
|
||||
@@ -4,8 +4,7 @@ mod state;
|
||||
|
||||
use anyhow::Result;
|
||||
use arc_swap::{ArcSwap, ArcSwapOption};
|
||||
use clash_verge_logger::AsyncLogger;
|
||||
use clash_verge_logging::{Type, logging};
|
||||
use clash_verge_logging::{LogRing, Type, logging};
|
||||
use once_cell::sync::Lazy;
|
||||
use std::{
|
||||
fmt,
|
||||
@@ -22,7 +21,7 @@ use crate::singleton;
|
||||
#[cfg(target_os = "windows")]
|
||||
use std::os::windows::io::OwnedHandle;
|
||||
|
||||
pub(crate) static CLASH_LOGGER: Lazy<Arc<AsyncLogger>> = Lazy::new(|| Arc::new(AsyncLogger::new()));
|
||||
pub(crate) static CLASH_LOGGER: Lazy<Arc<LogRing>> = Lazy::new(|| Arc::new(LogRing::new()));
|
||||
|
||||
tokio::task_local! {
|
||||
static PROFILE_SELECTIONS_PENDING_COMMIT: bool;
|
||||
@@ -161,6 +160,10 @@ impl CoreManager {
|
||||
/// Run State derives PAC availability and the outward mode mirror from this; callers must
|
||||
/// not set those alongside.
|
||||
pub fn core_started(&self, mode: RunningMode) {
|
||||
let previous = *self.get_running_mode();
|
||||
if previous != mode {
|
||||
logging!(info, Type::Core, "Core running mode changed: {previous} -> {mode}");
|
||||
}
|
||||
self.run_state.core_started(mode);
|
||||
}
|
||||
|
||||
@@ -169,6 +172,10 @@ impl CoreManager {
|
||||
/// Core readiness is invalidated here rather than inside Run State because readiness
|
||||
/// belongs to the process this manager supervises.
|
||||
pub fn core_stopped(&self) {
|
||||
let previous = *self.get_running_mode();
|
||||
if !matches!(previous, RunningMode::NotRunning) {
|
||||
logging!(info, Type::Core, "Core running mode changed: {previous} -> NotRunning");
|
||||
}
|
||||
self.invalidate_core_readiness();
|
||||
self.run_state.core_stopped();
|
||||
}
|
||||
@@ -244,6 +251,7 @@ impl CoreManager {
|
||||
self.config_update_in_progress.store(false, Ordering::Release);
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all, level = "info", fields(retries = 0))]
|
||||
pub async fn init(&self) -> Result<bool> {
|
||||
const MAX_PORT_FALLBACK_RETRIES: usize = 3;
|
||||
|
||||
@@ -266,10 +274,11 @@ impl CoreManager {
|
||||
match crate::config::Config::resolve_startup_mixed_port().await {
|
||||
Ok(true) => {
|
||||
retries += 1;
|
||||
tracing::Span::current().record("retries", retries);
|
||||
logging!(
|
||||
warn,
|
||||
Type::Core,
|
||||
"Retrying core startup after mixed proxy port fallback ({}/{})",
|
||||
"Retrying core startup after mixed proxy port fallback ({}/{}): {start_error:#}",
|
||||
retries,
|
||||
MAX_PORT_FALLBACK_RETRIES
|
||||
);
|
||||
|
||||
@@ -10,7 +10,6 @@ use crate::{
|
||||
};
|
||||
use anyhow::{Context as _, Result};
|
||||
use clash_verge_logging::Type;
|
||||
use compact_str::CompactString;
|
||||
use log::Level;
|
||||
use scopeguard::defer;
|
||||
use std::path::Path;
|
||||
@@ -49,7 +48,16 @@ where
|
||||
for attempt in 0..max_attempts {
|
||||
match probe().await {
|
||||
Ok(()) => return Ok(()),
|
||||
Err(error) => last_error = Some(error),
|
||||
Err(error) => {
|
||||
logging!(
|
||||
debug,
|
||||
Type::Core,
|
||||
"sidecar readiness probe {}/{} failed: {error:#}",
|
||||
attempt + 1,
|
||||
max_attempts
|
||||
);
|
||||
last_error = Some(error);
|
||||
}
|
||||
}
|
||||
if attempt + 1 < max_attempts {
|
||||
tokio::time::sleep(retry_delay).await;
|
||||
@@ -80,16 +88,16 @@ use {
|
||||
};
|
||||
|
||||
impl CoreManager {
|
||||
pub async fn get_clash_logs(&self) -> Result<Vec<CompactString>> {
|
||||
pub async fn get_clash_logs(&self) -> Result<Vec<String>> {
|
||||
match *self.get_running_mode() {
|
||||
RunningMode::Service => service::get_clash_logs_by_service().await,
|
||||
RunningMode::Sidecar => Ok(CLASH_LOGGER.get_logs().await),
|
||||
RunningMode::Sidecar => Ok(CLASH_LOGGER.get_logs()),
|
||||
RunningMode::NotRunning => Ok(Vec::new()),
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all, level = "info", fields(pid = tracing::field::Empty))]
|
||||
pub(super) async fn start_core_by_sidecar(&self) -> Result<()> {
|
||||
logging!(info, Type::Core, "Starting core in sidecar mode");
|
||||
self.core_stopped();
|
||||
|
||||
let sidecar_ipc = dirs::sidecar_ipc_path()?;
|
||||
@@ -158,7 +166,7 @@ impl CoreManager {
|
||||
};
|
||||
|
||||
let pid = child.pid();
|
||||
logging!(trace, Type::Core, "Sidecar started with PID: {}", pid);
|
||||
tracing::Span::current().record("pid", pid);
|
||||
|
||||
let readiness = poll_sidecar_readiness(SIDECAR_READINESS_ATTEMPTS, SIDECAR_READINESS_INTERVAL, || async {
|
||||
tokio::time::timeout(SIDECAR_READINESS_PROBE_TIMEOUT, async {
|
||||
@@ -192,22 +200,22 @@ impl CoreManager {
|
||||
match event {
|
||||
tauri_plugin_shell::process::CommandEvent::Stdout(line)
|
||||
| tauri_plugin_shell::process::CommandEvent::Stderr(line) => {
|
||||
let message = CompactString::from(&*String::from_utf8_lossy(&line));
|
||||
let message = String::from_utf8_lossy(&line).into_owned();
|
||||
Logger::global().writer_sidecar_log(Level::Error, &message);
|
||||
CLASH_LOGGER.append_log(message).await;
|
||||
CLASH_LOGGER.append_log(message);
|
||||
}
|
||||
tauri_plugin_shell::process::CommandEvent::Terminated(term) => {
|
||||
let manager = Self::global();
|
||||
let _ = manager.invalidate_core_readiness_if(core_readiness_generation);
|
||||
let message = if let Some(code) = term.code {
|
||||
CompactString::from(format!("Process terminated with code: {}", code))
|
||||
format!("Process terminated with code: {}", code)
|
||||
} else if let Some(signal) = term.signal {
|
||||
CompactString::from(format!("Process terminated by signal: {}", signal))
|
||||
format!("Process terminated by signal: {}", signal)
|
||||
} else {
|
||||
CompactString::from("Process terminated")
|
||||
String::from("Process terminated")
|
||||
};
|
||||
Logger::global().writer_sidecar_log(Level::Info, &message);
|
||||
CLASH_LOGGER.clear_logs().await;
|
||||
CLASH_LOGGER.clear_logs();
|
||||
manager.clear_terminated_sidecar(pid).await;
|
||||
break;
|
||||
}
|
||||
@@ -222,7 +230,6 @@ impl CoreManager {
|
||||
/// Terminates the sidecar after its caller has successfully cleared the
|
||||
/// system proxy.
|
||||
pub(super) fn stop_core_by_sidecar_unprepared(&self) {
|
||||
logging!(info, Type::Core, "Stopping sidecar");
|
||||
defer! {
|
||||
self.core_stopped();
|
||||
}
|
||||
@@ -234,27 +241,16 @@ impl CoreManager {
|
||||
// Setting the job handle to None clears the stored handle and
|
||||
// closes the previous Windows job handle in `set_job_handle`.
|
||||
self.set_job_handle(None);
|
||||
logging!(
|
||||
trace,
|
||||
Type::Core,
|
||||
"Closed job handle for sidecar process (PID: {})",
|
||||
pid
|
||||
);
|
||||
let _ = pid;
|
||||
}
|
||||
|
||||
let result = child.kill();
|
||||
logging!(
|
||||
trace,
|
||||
Type::Core,
|
||||
"Sidecar stopped (PID: {:?}, Result: {:?})",
|
||||
pid,
|
||||
result
|
||||
);
|
||||
if let Err(error) = child.kill() {
|
||||
logging!(warn, Type::Core, "failed to terminate sidecar PID {pid}: {error:#}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn start_core_by_service(&self) -> Result<()> {
|
||||
logging!(info, Type::Core, "Starting core in service mode");
|
||||
self.core_starting();
|
||||
let service_ipc = dirs::ipc_path()?;
|
||||
let config_file = Config::generate_file(crate::config::ConfigType::Run).await?;
|
||||
@@ -265,6 +261,7 @@ impl CoreManager {
|
||||
self.start_core_by_service_with_config(&config_file).await
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all, level = "info", fields(config_file = %config_file.display()))]
|
||||
pub(super) async fn start_core_by_service_with_config(&self, config_file: &Path) -> Result<()> {
|
||||
// 交接时等待 sidecar 释放 ext-controller 通道。
|
||||
#[cfg(target_os = "windows")]
|
||||
@@ -283,10 +280,9 @@ impl CoreManager {
|
||||
logging!(
|
||||
warn,
|
||||
Type::Core,
|
||||
"service start attempt {}/{} failed: {}",
|
||||
"service start attempt {}/{} failed: {e:#}",
|
||||
attempt + 1,
|
||||
timing::SERVICE_START_RETRIES,
|
||||
e
|
||||
timing::SERVICE_START_RETRIES
|
||||
);
|
||||
last_err = Some(e);
|
||||
tokio::time::sleep(timing::SERVICE_START_RETRY_DELAY).await;
|
||||
@@ -307,7 +303,6 @@ impl CoreManager {
|
||||
}
|
||||
|
||||
pub(super) async fn stop_core_by_service(&self) -> Result<()> {
|
||||
logging!(info, Type::Core, "Stopping service");
|
||||
service::stop_core_by_service().await?;
|
||||
self.core_stopped();
|
||||
Ok(())
|
||||
|
||||
@@ -43,6 +43,11 @@ pub fn start() {
|
||||
};
|
||||
CFRunLoop::get_main().add_source(&source, unsafe { kCFRunLoopCommonModes });
|
||||
ARMED.store(true, Ordering::Release);
|
||||
logging!(
|
||||
debug,
|
||||
Type::Core,
|
||||
"network watch armed; the system proxy is re-applied on network changes"
|
||||
);
|
||||
}
|
||||
|
||||
fn on_change(_: SCDynamicStore, _: CFArray<CFString>, (): &mut ()) {
|
||||
|
||||
@@ -471,10 +471,13 @@ fn table_effect(result: &Result<()>) -> TableEffect<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all, level = "info", fields(route = tracing::field::Empty))]
|
||||
pub async fn apply() -> Result<()> {
|
||||
let running_mode = CoreManager::global().get_running_mode();
|
||||
let verge = Config::verge().await.latest_arc();
|
||||
let result = match proxy_backend_route(cfg!(target_os = "macos"), &running_mode) {
|
||||
let route = proxy_backend_route(cfg!(target_os = "macos"), &running_mode);
|
||||
tracing::Span::current().record("route", tracing::field::debug(&route));
|
||||
let result = match route {
|
||||
ProxyBackendRoute::Local => match Sysopt::global().update_sysproxy().await {
|
||||
Ok(()) => Ok(()),
|
||||
Err(error) => Err(classify_local_apply_failure(error).await),
|
||||
@@ -520,7 +523,7 @@ pub async fn clear() -> Result<()> {
|
||||
|
||||
/// A failed system call is usually a transient RPC hiccup, so give it a few tries.
|
||||
async fn clear_with_retry() -> Result<()> {
|
||||
for _ in 1..CLEAR_ATTEMPTS {
|
||||
for attempt in 1..CLEAR_ATTEMPTS {
|
||||
match clear_inner().await {
|
||||
Err(error)
|
||||
if matches!(
|
||||
@@ -531,7 +534,7 @@ async fn clear_with_retry() -> Result<()> {
|
||||
logging!(
|
||||
warn,
|
||||
Type::Core,
|
||||
"clearing the system proxy failed; retrying: {error:#}"
|
||||
"clearing the system proxy failed (attempt {attempt}/{CLEAR_ATTEMPTS}); retrying: {error:#}"
|
||||
);
|
||||
tokio::time::sleep(CLEAR_RETRY_DELAY).await;
|
||||
}
|
||||
@@ -541,9 +544,12 @@ async fn clear_with_retry() -> Result<()> {
|
||||
clear_inner().await
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all, level = "info", fields(route = tracing::field::Empty))]
|
||||
async fn clear_inner() -> Result<()> {
|
||||
let running_mode = CoreManager::global().get_running_mode();
|
||||
match proxy_backend_route(cfg!(target_os = "macos"), &running_mode) {
|
||||
let route = proxy_backend_route(cfg!(target_os = "macos"), &running_mode);
|
||||
tracing::Span::current().record("route", tracing::field::debug(&route));
|
||||
match route {
|
||||
ProxyBackendRoute::Local => Sysopt::global().reset_sysproxy().await.map_err(classify_local_failure),
|
||||
ProxyBackendRoute::Service => {
|
||||
SERVICE_PROXY_OPERATIONS
|
||||
@@ -599,13 +605,16 @@ pub async fn refresh_guard() -> Result<()> {
|
||||
.await
|
||||
{
|
||||
Ok(true) => consecutive_failures = 0,
|
||||
Ok(false) => break,
|
||||
Ok(false) => {
|
||||
logging!(debug, Type::Core, "proxy guard superseded (generation {generation})");
|
||||
break;
|
||||
}
|
||||
Err(error) => {
|
||||
consecutive_failures += 1;
|
||||
logging!(
|
||||
warn,
|
||||
Type::Core,
|
||||
"failed to refresh system proxy through Service ({}/{}): {:#}",
|
||||
"failed to refresh system proxy through Service (generation {generation}, {}/{}): {:#}",
|
||||
consecutive_failures,
|
||||
GUARD_FAILURES_BEFORE_STOPPING,
|
||||
error
|
||||
|
||||
@@ -23,7 +23,6 @@ use clash_verge_service_ipc::{
|
||||
MacosProxyConfig, OwnerSessionProof, ProxyApplyOutcome, RuntimeBundle, ServiceErrorCode, StageRuntimeOutcome,
|
||||
StartClashRequest, WriterConfig,
|
||||
};
|
||||
use compact_str::CompactString;
|
||||
use once_cell::sync::Lazy;
|
||||
use parking_lot::Mutex;
|
||||
use std::{
|
||||
@@ -74,13 +73,19 @@ pub(crate) fn clear_active_service_session() {
|
||||
}
|
||||
|
||||
/// Probes staging support without failing startup when the fast path is unavailable.
|
||||
#[tracing::instrument(skip_all, level = "info", fields(supported = tracing::field::Empty))]
|
||||
async fn probe_runtime_staging_support() -> bool {
|
||||
match clash_verge_service_ipc::get_version().await {
|
||||
Ok(response) if response.code == 0 => response
|
||||
.data
|
||||
.as_ref()
|
||||
.is_some_and(clash_verge_service_ipc::ProtocolInfo::supports_runtime_staging),
|
||||
Ok(response) if response.code == 0 => {
|
||||
let supported = response
|
||||
.data
|
||||
.as_ref()
|
||||
.is_some_and(clash_verge_service_ipc::ProtocolInfo::supports_runtime_staging);
|
||||
tracing::Span::current().record("supported", supported);
|
||||
supported
|
||||
}
|
||||
Ok(response) => {
|
||||
tracing::Span::current().record("supported", false);
|
||||
logging!(
|
||||
warn,
|
||||
Type::Service,
|
||||
@@ -91,6 +96,7 @@ async fn probe_runtime_staging_support() -> bool {
|
||||
false
|
||||
}
|
||||
Err(error) => {
|
||||
tracing::Span::current().record("supported", false);
|
||||
logging!(
|
||||
warn,
|
||||
Type::Service,
|
||||
@@ -818,7 +824,7 @@ async fn collect_service_runtime_bundle(config_file: &Path) -> Result<RuntimeBun
|
||||
|
||||
/// A staging response whose refusal code tells callers whether a fresh start can help.
|
||||
pub(super) enum StageRequest {
|
||||
Refused { code: u16, message: CompactString },
|
||||
Refused { code: u16, message: String },
|
||||
Answered(StageRuntimeOutcome),
|
||||
}
|
||||
|
||||
@@ -841,7 +847,7 @@ pub(super) async fn stage_runtime_by_service(config_file: &Path) -> Result<Stage
|
||||
if response.code > 0 {
|
||||
return Ok(StageRequest::Refused {
|
||||
code: response.code,
|
||||
message: response.message.into(),
|
||||
message: response.message,
|
||||
});
|
||||
}
|
||||
response
|
||||
@@ -851,8 +857,8 @@ pub(super) async fn stage_runtime_by_service(config_file: &Path) -> Result<Stage
|
||||
}
|
||||
|
||||
/// 尝试使用服务启动core
|
||||
#[tracing::instrument(skip_all, level = "info", fields(generation = tracing::field::Empty, staging = tracing::field::Empty, code = tracing::field::Empty, outcome = tracing::field::Empty))]
|
||||
pub(super) async fn start_with_existing_service(config_file: &Path) -> Result<()> {
|
||||
logging!(info, Type::Service, "尝试使用现有服务启动核心");
|
||||
clear_active_service_session();
|
||||
|
||||
let credentials = current_owner_credentials()?;
|
||||
@@ -867,14 +873,23 @@ pub(super) async fn start_with_existing_service(config_file: &Path) -> Result<()
|
||||
let response = match clash_verge_service_ipc::start_clash(&credentials, &request).await {
|
||||
Ok(response) => response,
|
||||
Err(error) => {
|
||||
tracing::Span::current().record("outcome", "ipc-unreachable");
|
||||
start_owner_monitor();
|
||||
return Err(error).context("无法连接到Clash Verge Service");
|
||||
}
|
||||
};
|
||||
|
||||
if response.code > 0 {
|
||||
tracing::Span::current().record("code", response.code);
|
||||
tracing::Span::current().record("outcome", "refused");
|
||||
let err_msg = response.message;
|
||||
logging!(error, Type::Service, "启动核心失败: {}", err_msg);
|
||||
logging!(
|
||||
error,
|
||||
Type::Service,
|
||||
"启动核心失败 (code {}): {}",
|
||||
response.code,
|
||||
err_msg
|
||||
);
|
||||
start_owner_monitor();
|
||||
bail!(
|
||||
"failed to start Service core at {}: {err_msg}",
|
||||
@@ -883,7 +898,9 @@ pub(super) async fn start_with_existing_service(config_file: &Path) -> Result<()
|
||||
}
|
||||
|
||||
let result = response.data.context("Clash Verge Service 未返回会话信息")?;
|
||||
tracing::Span::current().record("generation", result.session.generation);
|
||||
let supports_runtime_staging = probe_runtime_staging_support().await;
|
||||
tracing::Span::current().record("staging", supports_runtime_staging);
|
||||
*ACTIVE_SERVICE_SESSION.lock() = Some(ActiveServiceSession {
|
||||
proof: OwnerSessionProof {
|
||||
generation: result.session.generation,
|
||||
@@ -894,14 +911,18 @@ pub(super) async fn start_with_existing_service(config_file: &Path) -> Result<()
|
||||
|
||||
// PAC follows the Running Mode; the caller opens it via `core_started(Service)`.
|
||||
start_owner_monitor();
|
||||
logging!(info, Type::Service, "服务成功启动核心");
|
||||
tracing::Span::current().record("outcome", "started");
|
||||
logging!(
|
||||
info,
|
||||
Type::Service,
|
||||
"服务成功启动核心 (session generation {})",
|
||||
result.session.generation
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// 以服务启动core
|
||||
pub(super) async fn run_core_by_service(config_file: &Path) -> Result<()> {
|
||||
logging!(info, Type::Service, "正在尝试通过服务启动核心");
|
||||
|
||||
SERVICE_MANAGER.refresh().await?;
|
||||
|
||||
let status = SERVICE_MANAGER.current().await;
|
||||
@@ -922,9 +943,8 @@ where
|
||||
(captured, operation().await)
|
||||
}
|
||||
|
||||
pub(super) async fn get_clash_logs_by_service() -> Result<Vec<CompactString>> {
|
||||
logging!(info, Type::Service, "正在获取服务模式下的 Clash 日志");
|
||||
|
||||
pub(super) async fn get_clash_logs_by_service() -> Result<Vec<String>> {
|
||||
// Frontend-polled: no per-call logging here.
|
||||
let credentials = current_owner_credentials()?;
|
||||
let (generation, response) = capture_generation_before(&OWNER_MONITOR_GENERATION, || {
|
||||
clash_verge_service_ipc::get_clash_logs(&credentials)
|
||||
@@ -937,11 +957,9 @@ pub(super) async fn get_clash_logs_by_service() -> Result<Vec<CompactString>> {
|
||||
recover_after_owner_loss(generation, OwnerRecoveryReason::Displaced).await;
|
||||
}
|
||||
let err_msg = response.message;
|
||||
logging!(error, Type::Service, "获取服务模式下的 Clash 日志失败: {}", err_msg);
|
||||
bail!(err_msg);
|
||||
}
|
||||
|
||||
logging!(info, Type::Service, "成功获取服务模式下的 Clash 日志");
|
||||
Ok(response.data.unwrap_or_default())
|
||||
}
|
||||
|
||||
@@ -970,8 +988,8 @@ pub(crate) async fn get_clash_log_snapshot_by_service() -> Result<String> {
|
||||
}
|
||||
|
||||
/// 通过服务停止core
|
||||
#[tracing::instrument(skip_all, level = "info", fields(code = tracing::field::Empty, outcome = tracing::field::Empty))]
|
||||
pub(super) async fn stop_core_by_service() -> Result<()> {
|
||||
logging!(info, Type::Service, "通过服务停止核心 (IPC)");
|
||||
cancel_owner_monitors();
|
||||
|
||||
let credentials = match current_owner_credentials() {
|
||||
@@ -1007,11 +1025,20 @@ pub(super) async fn stop_core_by_service() -> Result<()> {
|
||||
start_owner_monitor();
|
||||
}
|
||||
let err_msg = response.message;
|
||||
logging!(error, Type::Service, "停止核心失败: {}", err_msg);
|
||||
tracing::Span::current().record("code", response.code);
|
||||
tracing::Span::current().record("outcome", "refused");
|
||||
logging!(
|
||||
error,
|
||||
Type::Service,
|
||||
"停止核心失败 (code {}): {}",
|
||||
response.code,
|
||||
err_msg
|
||||
);
|
||||
bail!(err_msg);
|
||||
}
|
||||
|
||||
clear_active_service_session();
|
||||
tracing::Span::current().record("outcome", "stopped");
|
||||
logging!(info, Type::Service, "服务成功停止核心");
|
||||
Ok(())
|
||||
}
|
||||
@@ -1023,6 +1050,13 @@ pub(crate) async fn update_writer_by_service(writer: &WriterConfig) -> Result<()
|
||||
.await
|
||||
.context("无法连接到Clash Verge Service")?;
|
||||
if response.code > 0 {
|
||||
logging!(
|
||||
warn,
|
||||
Type::Service,
|
||||
"update writer rejected by service: code={}, {}",
|
||||
response.code,
|
||||
response.message
|
||||
);
|
||||
bail!(response.message);
|
||||
}
|
||||
Ok(())
|
||||
@@ -1042,6 +1076,13 @@ pub(super) async fn set_system_proxy_by_service_with_session(
|
||||
.await
|
||||
.context("无法连接到Clash Verge Service")?;
|
||||
if response.code > 0 {
|
||||
logging!(
|
||||
warn,
|
||||
Type::Service,
|
||||
"set system proxy rejected by service: code={}, {}",
|
||||
response.code,
|
||||
response.message
|
||||
);
|
||||
bail!(response.message);
|
||||
}
|
||||
response.data.context("Clash Verge Service 未返回系统代理结果")
|
||||
@@ -1074,13 +1115,24 @@ const SUSTAINED_OWNER_SAMPLES: u8 = 3;
|
||||
fn start_owner_monitor() {
|
||||
let generation = OWNER_MONITOR_GENERATION.fetch_add(1, Ordering::AcqRel) + 1;
|
||||
AsyncHandler::spawn(move || async move {
|
||||
logging!(debug, Type::Service, "owner monitor started (generation {generation})");
|
||||
let mut watch = OwnerWatch::new();
|
||||
loop {
|
||||
tokio::time::sleep(OWNER_MONITOR_INTERVAL).await;
|
||||
if OWNER_MONITOR_GENERATION.load(Ordering::Acquire) != generation {
|
||||
logging!(
|
||||
debug,
|
||||
Type::Service,
|
||||
"owner monitor superseded (generation {generation})"
|
||||
);
|
||||
break;
|
||||
}
|
||||
if !matches!(*CoreManager::global().get_running_mode(), RunningMode::Service) {
|
||||
logging!(
|
||||
debug,
|
||||
Type::Service,
|
||||
"owner monitor stopped; core no longer in service mode (generation {generation})"
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1091,7 +1143,7 @@ fn start_owner_monitor() {
|
||||
logging!(
|
||||
warn,
|
||||
Type::Service,
|
||||
"service owner status unavailable for {SUSTAINED_OWNER_SAMPLES} samples; \
|
||||
"service owner status unavailable for {SUSTAINED_OWNER_SAMPLES} samples (generation {generation}); \
|
||||
preserving local proxy state while the core endpoint still answers"
|
||||
);
|
||||
}
|
||||
@@ -1199,6 +1251,7 @@ fn claim_owner_recovery_generation(generation: &AtomicU64, captured_generation:
|
||||
.map(|_| recovery_generation)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all, level = "info", fields(reason = ?reason))]
|
||||
async fn recover_after_owner_loss_while_locked(reason: OwnerRecoveryReason) {
|
||||
logging!(
|
||||
warn,
|
||||
@@ -1215,10 +1268,15 @@ async fn recover_after_owner_loss_while_locked(reason: OwnerRecoveryReason) {
|
||||
}
|
||||
|
||||
let mut last_error = None;
|
||||
for _ in 0..3 {
|
||||
for attempt in 1..=3 {
|
||||
match proxy_control::clear().await {
|
||||
Ok(()) => return,
|
||||
Err(error) => {
|
||||
logging!(
|
||||
warn,
|
||||
Type::Service,
|
||||
"proxy clear attempt {attempt}/3 after owner loss failed: {error:#}"
|
||||
);
|
||||
last_error = Some(error);
|
||||
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||
}
|
||||
@@ -1228,24 +1286,35 @@ async fn recover_after_owner_loss_while_locked(reason: OwnerRecoveryReason) {
|
||||
logging!(
|
||||
error,
|
||||
Type::Service,
|
||||
"failed to clear local proxy after owner loss: {error}"
|
||||
"failed to clear local proxy after owner loss: {error:#}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Waits for a repaired service, preserving readable rejection details but classifying sustained
|
||||
/// silence as unavailable.
|
||||
#[tracing::instrument(skip_all, level = "info", fields(attempts = tracing::field::Empty, interval_ms = tracing::field::Empty, outcome = tracing::field::Empty))]
|
||||
async fn wait_for_service_ipc() -> Result<()> {
|
||||
const CONTEXT: &str = "service IPC did not become available";
|
||||
let config = ServiceManager::config();
|
||||
let span = tracing::Span::current();
|
||||
span.record("attempts", config.max_retries);
|
||||
span.record("interval_ms", config.retry_delay.as_millis() as u64);
|
||||
|
||||
match RUN_STATE.await_ready(config.max_retries, config.retry_delay).await {
|
||||
Ok(_) => Ok(()),
|
||||
Ok(_) => {
|
||||
tracing::Span::current().record("outcome", "ready");
|
||||
Ok(())
|
||||
}
|
||||
Err(ReadyWaitError::Unreachable(error)) => {
|
||||
tracing::Span::current().record("outcome", "unreachable");
|
||||
RUN_STATE.observe(ServiceHealth::Unavailable(format!("{CONTEXT}: {error:#}")));
|
||||
Err(error).context(CONTEXT)
|
||||
}
|
||||
Err(ReadyWaitError::Rejected(error)) => Err(error).context(CONTEXT),
|
||||
Err(ReadyWaitError::Rejected(error)) => {
|
||||
tracing::Span::current().record("outcome", "rejected");
|
||||
Err(error).context(CONTEXT)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -132,7 +132,7 @@ impl Timer {
|
||||
|
||||
if let Err(e) = self.refresh().await {
|
||||
self.initialized.store(false, Ordering::SeqCst);
|
||||
logging_error!(Type::Timer, "Failed to initialize timer: {}", e);
|
||||
logging!(error, Type::Timer, "Failed to initialize timer: {e:#}");
|
||||
return Err(e);
|
||||
}
|
||||
|
||||
@@ -424,7 +424,7 @@ impl Timer {
|
||||
}
|
||||
|
||||
fn spawn_update_task(uid: String, command_tx: mpsc::UnboundedSender<TimerCommand>) {
|
||||
logging!(info, Type::Timer, "Starting timer task: uid={}", uid);
|
||||
logging!(debug, Type::Timer, "Starting timer task: uid={}", uid);
|
||||
AsyncHandler::spawn(move || async move {
|
||||
Self::wait_until_resolve_done(Duration::from_millis(5000)).await;
|
||||
Self::async_task(&uid).await;
|
||||
@@ -466,8 +466,8 @@ impl Timer {
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all, level = "info", fields(uid = %uid))]
|
||||
async fn async_task(uid: &String) {
|
||||
let task_start = std::time::Instant::now();
|
||||
logging!(debug, Type::Timer, "Running timer task for profile: {}", uid);
|
||||
|
||||
run_timer_profile_update_transition(
|
||||
@@ -475,15 +475,9 @@ impl Timer {
|
||||
|| feat::update_profile(uid, None, false),
|
||||
|result| match result {
|
||||
Ok(_) => {
|
||||
logging!(
|
||||
info,
|
||||
Type::Timer,
|
||||
"Timer task completed for uid: {} (took {}ms)",
|
||||
uid,
|
||||
task_start.elapsed().as_millis()
|
||||
);
|
||||
logging!(debug, Type::Timer, "timer task completed for uid: {}", uid);
|
||||
}
|
||||
Err(e) => logging_error!(Type::Timer, "Failed to update profile uid {}: {}", uid, e),
|
||||
Err(e) => logging_error!(Type::Timer, "Failed to update profile uid {}: {e:#}", uid),
|
||||
},
|
||||
|| Self::emit_update_event(uid, false),
|
||||
)
|
||||
|
||||
@@ -551,7 +551,7 @@ async fn apply_builtin_scripts(mut config: Mapping, clash_core: Option<String>,
|
||||
config = res_config;
|
||||
}
|
||||
Err(err) => {
|
||||
logging!(error, Type::Core, "builtin script error `{err}`");
|
||||
logging!(error, Type::Core, "builtin script error `{err:#}`");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -678,7 +678,7 @@ async fn apply_dns_settings(mut config: Mapping, enable_dns_settings: bool) -> M
|
||||
&& hosts_value.is_mapping()
|
||||
{
|
||||
config.insert("hosts".into(), hosts_value.clone());
|
||||
logging!(info, Type::Core, "apply hosts configuration");
|
||||
logging!(debug, Type::Core, "apply hosts configuration");
|
||||
}
|
||||
|
||||
if let Some(dns_value) = dns_config.get("dns") {
|
||||
@@ -686,13 +686,13 @@ async fn apply_dns_settings(mut config: Mapping, enable_dns_settings: bool) -> M
|
||||
let mut dns_mapping = dns_mapping.clone();
|
||||
ensure_fake_ip_range6(&mut dns_mapping);
|
||||
config.insert("dns".into(), dns_mapping.into());
|
||||
logging!(info, Type::Core, "apply dns_config.yaml (dns section)");
|
||||
logging!(debug, Type::Core, "apply dns_config.yaml (dns section)");
|
||||
}
|
||||
} else {
|
||||
let mut dns_config = dns_config;
|
||||
ensure_fake_ip_range6(&mut dns_config);
|
||||
config.insert("dns".into(), dns_config.into());
|
||||
logging!(info, Type::Core, "apply dns_config.yaml");
|
||||
logging!(debug, Type::Core, "apply dns_config.yaml");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::process::AsyncHandler;
|
||||
use super::field::{use_lowercase, use_lowercase_owned};
|
||||
use anyhow::{Error, Result};
|
||||
use boa_engine::{Context, JsString, JsValue, Source, native_function::NativeFunction};
|
||||
use clash_verge_logging::{Type, logging_error};
|
||||
use clash_verge_logging::{Type, logging};
|
||||
use parking_lot::Mutex;
|
||||
use serde_yaml_ng::Mapping;
|
||||
use smartstring::alias::String;
|
||||
@@ -131,7 +131,11 @@ fn use_script_sync(script: String, config: &Mapping, name: &String) -> Result<(M
|
||||
outputs
|
||||
.lock()
|
||||
.push(("exception".into(), "Script execution failed".into()));
|
||||
logging_error!(Type::Config, "Script execution error: {}. Script name: {}", err, name);
|
||||
logging!(
|
||||
error,
|
||||
Type::Config,
|
||||
"Script execution error: {err:#}. Script name: {name}"
|
||||
);
|
||||
Ok((config, outputs.lock().to_vec()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ async fn finalize_restored_verge_config(
|
||||
}
|
||||
.await;
|
||||
if let Err(err) = result {
|
||||
logging!(error, Type::Backup, "Failed to turn the system proxy off: {err:#?}");
|
||||
logging!(error, Type::Backup, "Failed to turn the system proxy off: {err:#}");
|
||||
if SystemProxyStateUnknown::is_in(&err) {
|
||||
return Err(err);
|
||||
}
|
||||
@@ -79,7 +79,7 @@ async fn finalize_restored_verge_config(
|
||||
|
||||
// Run configuration side effects without rewriting the already-restored file.
|
||||
if let Err(err) = super::patch_verge(&restored, true).await {
|
||||
logging!(error, Type::Backup, "Failed to apply restored verge config: {err:#?}");
|
||||
logging!(error, Type::Backup, "Failed to apply restored verge config: {err:#}");
|
||||
// Propagate unknown proxy state; ordinary side-effect failures stay logged.
|
||||
if SystemProxyStateUnknown::is_in(&err) {
|
||||
return Err(err);
|
||||
@@ -88,9 +88,10 @@ async fn finalize_restored_verge_config(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all, level = "info")]
|
||||
pub async fn create_backup_and_upload_webdav() -> Result<()> {
|
||||
let (file_name, temp_file_path) = backup::create_backup().await.map_err(|err| {
|
||||
logging!(error, Type::Backup, "Failed to create backup: {err:#?}");
|
||||
logging!(error, Type::Backup, "Failed to create backup: {err:#}");
|
||||
err
|
||||
})?;
|
||||
|
||||
@@ -98,13 +99,13 @@ pub async fn create_backup_and_upload_webdav() -> Result<()> {
|
||||
.upload(temp_file_path.clone(), file_name)
|
||||
.await
|
||||
{
|
||||
logging!(error, Type::Backup, "Failed to upload to WebDAV: {err:#?}");
|
||||
logging!(error, Type::Backup, "Failed to upload to WebDAV: {err:#}");
|
||||
backup::WebDavClient::global().reset();
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
if let Err(err) = temp_file_path.remove_if_exists().await {
|
||||
logging!(warn, Type::Backup, "Failed to remove temp file: {err:#?}");
|
||||
logging!(warn, Type::Backup, "Failed to remove temp file: {err:#}");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -112,18 +113,24 @@ pub async fn create_backup_and_upload_webdav() -> Result<()> {
|
||||
|
||||
pub async fn list_wevdav_backup() -> Result<Vec<ListFile>> {
|
||||
backup::WebDavClient::global().list().await.map_err(|err| {
|
||||
logging!(error, Type::Backup, "Failed to list WebDAV backup files: {err:#?}");
|
||||
logging!(error, Type::Backup, "Failed to list WebDAV backup files: {err:#}");
|
||||
err
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn delete_webdav_backup(filename: String) -> Result<()> {
|
||||
let name = filename.to_string();
|
||||
backup::WebDavClient::global().delete(filename).await.map_err(|err| {
|
||||
logging!(error, Type::Backup, "Failed to delete WebDAV backup file: {err:#?}");
|
||||
logging!(
|
||||
error,
|
||||
Type::Backup,
|
||||
"Failed to delete WebDAV backup file {name}: {err:#}"
|
||||
);
|
||||
err
|
||||
})
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all, level = "info", fields(filename = %filename))]
|
||||
pub async fn restore_webdav_backup(filename: String) -> Result<()> {
|
||||
let verge = Config::verge().await;
|
||||
let verge_data = verge.latest_arc();
|
||||
@@ -134,11 +141,16 @@ pub async fn restore_webdav_backup(filename: String) -> Result<()> {
|
||||
let backup_storage_path = app_home_dir()
|
||||
.map_err(|e| anyhow::anyhow!("Failed to get app home dir: {e}"))?
|
||||
.join(filename.as_str());
|
||||
let name = filename.to_string();
|
||||
backup::WebDavClient::global()
|
||||
.download(filename, backup_storage_path.clone())
|
||||
.await
|
||||
.map_err(|err| {
|
||||
logging!(error, Type::Backup, "Failed to download WebDAV backup file: {err:#?}");
|
||||
logging!(
|
||||
error,
|
||||
Type::Backup,
|
||||
"Failed to download WebDAV backup file {name}: {err:#}"
|
||||
);
|
||||
err
|
||||
})?;
|
||||
|
||||
@@ -163,7 +175,7 @@ where
|
||||
F: FnOnce(&str) -> String,
|
||||
{
|
||||
let (file_name, temp_file_path) = backup::create_backup().await.map_err(|err| {
|
||||
logging!(error, Type::Backup, "Failed to create local backup: {err:#?}");
|
||||
logging!(error, Type::Backup, "Failed to create local backup: {err:#}");
|
||||
err
|
||||
})?;
|
||||
|
||||
@@ -172,12 +184,17 @@ where
|
||||
let target_path = backup_dir.join(final_name.as_str());
|
||||
|
||||
if let Err(err) = move_file(temp_file_path.clone(), target_path.clone()).await {
|
||||
logging!(error, Type::Backup, "Failed to move local backup file: {err:#?}");
|
||||
logging!(
|
||||
error,
|
||||
Type::Backup,
|
||||
"Failed to move local backup file to {}: {err:#}",
|
||||
target_path.display()
|
||||
);
|
||||
if let Err(clean_err) = temp_file_path.remove_if_exists().await {
|
||||
logging!(
|
||||
warn,
|
||||
Type::Backup,
|
||||
"Failed to remove temp backup file after move error: {clean_err:#?}"
|
||||
"Failed to remove temp backup file after move error: {clean_err:#}"
|
||||
);
|
||||
}
|
||||
return Err(err);
|
||||
@@ -186,6 +203,7 @@ where
|
||||
Ok(final_name)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all, level = "info", fields(source = %source))]
|
||||
pub async fn import_local_backup(source: String) -> Result<String> {
|
||||
let source_path = PathBuf::from(source.as_str());
|
||||
if !source_path.exists() {
|
||||
@@ -226,7 +244,7 @@ pub async fn import_local_backup(source: String) -> Result<String> {
|
||||
|
||||
fs::copy(&source_path, &target_path)
|
||||
.await
|
||||
.map_err(|err| anyhow!("Failed to import backup file: {err:#?}"))?;
|
||||
.map_err(|err| anyhow!("Failed to import backup file: {err:#}"))?;
|
||||
|
||||
Ok(file_name.to_string().into())
|
||||
}
|
||||
@@ -243,14 +261,14 @@ async fn move_file(from: PathBuf, to: PathBuf) -> Result<()> {
|
||||
logging!(
|
||||
warn,
|
||||
Type::Backup,
|
||||
"Failed to rename backup file directly, fallback to copy/remove: {rename_err:#?}"
|
||||
"Failed to rename backup file directly, fallback to copy/remove: {rename_err}"
|
||||
);
|
||||
fs::copy(&from, &to)
|
||||
.await
|
||||
.map_err(|err| anyhow!("Failed to copy backup file: {err:#?}"))?;
|
||||
.map_err(|err| anyhow!("Failed to copy backup file: {err:#}"))?;
|
||||
fs::remove_file(&from)
|
||||
.await
|
||||
.map_err(|err| anyhow!("Failed to remove temp backup file: {err:#?}"))?;
|
||||
.map_err(|err| anyhow!("Failed to remove temp backup file: {err:#}"))?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -295,13 +313,14 @@ pub async fn delete_local_backup(filename: String) -> Result<()> {
|
||||
let backup_dir = local_backup_dir()?;
|
||||
let target_path = backup_dir.join(filename.as_str());
|
||||
if !target_path.exists() {
|
||||
logging!(warn, Type::Backup, "Local backup file not found: {}", filename);
|
||||
logging!(debug, Type::Backup, "Local backup file not found: {}", filename);
|
||||
return Ok(());
|
||||
}
|
||||
target_path.remove_if_exists().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all, level = "info", fields(filename = %filename))]
|
||||
pub async fn restore_local_backup(filename: String) -> Result<()> {
|
||||
let backup_dir = local_backup_dir()?;
|
||||
let target_path = backup_dir.join(filename.as_str());
|
||||
@@ -327,6 +346,7 @@ pub async fn restore_local_backup(filename: String) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all, level = "info", fields(filename = %filename, destination = %destination))]
|
||||
pub async fn export_local_backup(filename: String, destination: String) -> Result<()> {
|
||||
let backup_dir = local_backup_dir()?;
|
||||
let source_path = backup_dir.join(filename.as_str());
|
||||
@@ -342,6 +362,6 @@ pub async fn export_local_backup(filename: String, destination: String) -> Resul
|
||||
fs::copy(&source_path, &dest_path)
|
||||
.await
|
||||
.map(|_| ())
|
||||
.map_err(|err| anyhow!("Failed to export backup file: {err:#?}"))?;
|
||||
.map_err(|err| anyhow!("Failed to export backup file: {err:#}"))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ pub async fn restart_clash_core() {
|
||||
}
|
||||
Err(err) => {
|
||||
handle::Handle::notice_message("set_config::error", format!("{err:#}"));
|
||||
logging!(error, Type::Core, "{err:#}");
|
||||
logging!(error, Type::Core, "restart core failed: {err:#}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,7 +42,6 @@ pub async fn restart_app() {
|
||||
|
||||
Config::apply_all_and_save_file().await;
|
||||
|
||||
logging!(info, Type::System, "开始异步清理资源");
|
||||
let cleanup_result = clean_async().await;
|
||||
|
||||
logging!(
|
||||
@@ -85,9 +84,8 @@ pub async fn change_clash_mode(mode: String) -> Result<(), String> {
|
||||
let json_value = serde_json::json!({
|
||||
"mode": mode
|
||||
});
|
||||
logging!(debug, Type::Core, "change clash mode to {mode}");
|
||||
if let Err(err) = handle::Handle::mihomo().patch_base_config(&json_value).await {
|
||||
logging!(error, Type::Core, "{err}");
|
||||
logging!(error, Type::Core, "change clash mode failed: {err}");
|
||||
return Err(err.to_string().into());
|
||||
}
|
||||
|
||||
@@ -111,6 +109,7 @@ pub async fn change_clash_mode(mode: String) -> Result<(), String> {
|
||||
|
||||
/// Test delay to a URL through proxy.
|
||||
/// HTTPS: measures TLS handshake time. HTTP: measures HEAD round-trip time.
|
||||
#[tracing::instrument(skip_all, level = "trace", fields(url = %url))]
|
||||
pub async fn test_delay(url: String) -> anyhow::Result<u32> {
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
config::{Config, IVerge},
|
||||
core::{CoreManager, autostart, handle, hotkey, logger::Logger, proxy_control, tray},
|
||||
core::{CoreManager, autostart, handle, hotkey, logger, proxy_control, tray},
|
||||
module::{auto_backup::AutoBackupManager, lightweight},
|
||||
};
|
||||
use anyhow::Result;
|
||||
@@ -267,12 +267,12 @@ async fn process_terminated_flags(update_flags: UpdateFlags, patch: &IVerge) ->
|
||||
}
|
||||
}
|
||||
if update_flags.contains(UpdateFlags::LOG_LEVEL) {
|
||||
Logger::global().update_log_level(patch.get_log_level())?;
|
||||
logger::Logger::global().update_log_level(patch.get_log_level())?;
|
||||
}
|
||||
if update_flags.contains(UpdateFlags::LOG_FILE) {
|
||||
let log_max_size = patch.app_log_max_size.unwrap_or(128);
|
||||
let log_max_count = patch.app_log_max_count.unwrap_or(8);
|
||||
Logger::global().update_log_config(log_max_size, log_max_count).await?;
|
||||
logger::update_log_config(log_max_size, log_max_count).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -317,7 +317,6 @@ pub(super) async fn apply_verge_patch_locked(
|
||||
if !not_save_file {
|
||||
// 分离数据获取和异步调用
|
||||
let verge_data = verge.data_arc();
|
||||
logging!(debug, Type::Setup, "Saving Verge configuration to file...");
|
||||
verge_data.save_file().await?;
|
||||
}
|
||||
Ok(())
|
||||
|
||||
@@ -45,6 +45,7 @@ pub struct CoreUpgradeReport {
|
||||
pub async fn upgrade_core(force: bool) -> Result<CoreUpgradeReport> {
|
||||
let _serialized = UPGRADE_LOCK.lock().await;
|
||||
let core = Config::verge().await.latest_arc().get_valid_clash_core();
|
||||
tracing::Span::current().record("core", tracing::field::display(&core));
|
||||
let alpha = core.ends_with("-alpha");
|
||||
let target = managed_core_path(&core)?;
|
||||
|
||||
@@ -55,11 +56,10 @@ pub async fn upgrade_core(force: bool) -> Result<CoreUpgradeReport> {
|
||||
});
|
||||
|
||||
let (proxy, latest) = resolve_latest_version(alpha).await?;
|
||||
logging!(
|
||||
info,
|
||||
Type::Core,
|
||||
"core upgrade: {core} installed={installed:?} latest={latest:?} via {proxy:?}"
|
||||
);
|
||||
let span = tracing::Span::current();
|
||||
span.record("from", tracing::field::display(&installed));
|
||||
span.record("to", tracing::field::display(&latest));
|
||||
logging!(debug, Type::Core, "core upgrade: latest version resolved via {proxy:?}");
|
||||
|
||||
if !force && installed == latest {
|
||||
return Ok(CoreUpgradeReport {
|
||||
@@ -111,7 +111,6 @@ pub async fn upgrade_core(force: bool) -> Result<CoreUpgradeReport> {
|
||||
#[cfg(windows)]
|
||||
let _ = std::fs::remove_file(target.with_extension("old"));
|
||||
|
||||
logging!(info, Type::Core, "core upgrade: {core} now at {latest}");
|
||||
Ok(CoreUpgradeReport {
|
||||
upgraded: true,
|
||||
from: installed,
|
||||
@@ -175,6 +174,7 @@ fn asset_base_name(alpha: bool) -> Result<&'static str> {
|
||||
}
|
||||
|
||||
/// Returns the proxy that reached GitHub so the package download reuses it.
|
||||
#[tracing::instrument(skip_all, level = "debug", fields(alpha))]
|
||||
async fn resolve_latest_version(alpha: bool) -> Result<(ProxyType, std::string::String)> {
|
||||
let url = if alpha {
|
||||
format!("{ALPHA_BASE_URL}/version.txt")
|
||||
@@ -193,13 +193,33 @@ async fn resolve_latest_version(alpha: bool) -> Result<(ProxyType, std::string::
|
||||
// An error page answered with 200 must not become a version, nor reach the URL.
|
||||
let version = response.text().trim().to_owned();
|
||||
if !is_usable_version(&version) {
|
||||
logging!(
|
||||
warn,
|
||||
Type::Core,
|
||||
"core upgrade: {url} returned an unusable version: {version:?}"
|
||||
);
|
||||
last_error = Some(anyhow!("{url} returned an unusable version"));
|
||||
continue;
|
||||
}
|
||||
return Ok((proxy, version));
|
||||
}
|
||||
Ok(response) => last_error = Some(anyhow!("{url} returned status {}", response.status())),
|
||||
Err(error) => last_error = Some(error.context(format!("{proxy:?} could not reach {url}"))),
|
||||
Ok(response) => {
|
||||
logging!(
|
||||
debug,
|
||||
Type::Core,
|
||||
"core upgrade: version probe via {proxy:?}: status {}",
|
||||
response.status()
|
||||
);
|
||||
last_error = Some(anyhow!("{url} returned status {}", response.status()));
|
||||
}
|
||||
Err(error) => {
|
||||
logging!(
|
||||
debug,
|
||||
Type::Core,
|
||||
"core upgrade: version probe via {proxy:?} failed: {error:#}"
|
||||
);
|
||||
last_error = Some(error.context(format!("{proxy:?} could not reach {url}")));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -157,13 +157,7 @@ pub async fn copy_icon_file(path: String, icon_info: IconInfo) -> CmdResult<Stri
|
||||
previous_ico.remove_if_exists().await.unwrap_or_default();
|
||||
}
|
||||
|
||||
logging!(
|
||||
info,
|
||||
Type::Cmd,
|
||||
"Copying icon file path: {:?} -> file dist: {:?}",
|
||||
path,
|
||||
dest_path
|
||||
);
|
||||
logging!(debug, Type::Cmd, "copying icon file: {path} -> {}", dest_path.display());
|
||||
|
||||
match fs::copy(file_path, &dest_path).await {
|
||||
Ok(_) => Ok(dest_path.to_string_lossy().into()),
|
||||
|
||||
@@ -5,15 +5,18 @@ use crate::{
|
||||
utils::help::{mask_err, mask_url},
|
||||
};
|
||||
use anyhow::{Result, bail};
|
||||
use clash_verge_logging::{Type, logging, logging_error};
|
||||
use clash_verge_logging::{Type, logging};
|
||||
use smartstring::alias::String;
|
||||
|
||||
/// Toggle proxy profile
|
||||
pub async fn toggle_proxy_profile(profile_index: String) {
|
||||
logging_error!(
|
||||
Type::Config,
|
||||
cmd::patch_profiles_config_by_profile_index(profile_index).await
|
||||
);
|
||||
if let Err(err) = cmd::patch_profiles_config_by_profile_index(profile_index.clone()).await {
|
||||
logging!(
|
||||
error,
|
||||
Type::Config,
|
||||
"toggle proxy profile {profile_index} failed: {err}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Tell the profile which node this group is on now.
|
||||
@@ -25,7 +28,7 @@ async fn record_switched_node(group_name: &str, proxy_name: &str) {
|
||||
if let Err(error) = crate::config::profiles::record_selected_node(group_name, proxy_name).await {
|
||||
logging!(
|
||||
warn,
|
||||
Type::Tray,
|
||||
Type::Config,
|
||||
"切换代理成功但未能记录到配置: {} -> {}, 错误: {error:#}",
|
||||
group_name,
|
||||
proxy_name
|
||||
@@ -39,21 +42,13 @@ pub async fn switch_proxy_node(group_name: &str, proxy_name: &str) {
|
||||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
logging!(info, Type::Tray, "切换代理成功: {} -> {}", group_name, proxy_name);
|
||||
record_switched_node(group_name, proxy_name).await;
|
||||
handle::Handle::refresh_proxy_config();
|
||||
let _ = tray::Tray::global().update_menu().await;
|
||||
return;
|
||||
}
|
||||
Err(err) => {
|
||||
logging!(
|
||||
error,
|
||||
Type::Tray,
|
||||
"切换代理失败: {} -> {}, 错误: {:?}",
|
||||
group_name,
|
||||
proxy_name,
|
||||
err
|
||||
);
|
||||
logging!(error, Type::Tray, "切换代理失败: {err:?}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,14 +62,7 @@ pub async fn switch_proxy_node(group_name: &str, proxy_name: &str) {
|
||||
let _ = tray::Tray::global().update_menu().await;
|
||||
}
|
||||
Err(err) => {
|
||||
logging!(
|
||||
error,
|
||||
Type::Tray,
|
||||
"代理切换最终失败: {} -> {}, 错误: {:?}",
|
||||
group_name,
|
||||
proxy_name,
|
||||
err
|
||||
);
|
||||
logging!(error, Type::Tray, "代理切换最终失败: {err:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -86,20 +74,19 @@ async fn should_update_profile(uid: &String, ignore_auto_update: bool) -> Result
|
||||
let is_remote = item.itype.as_ref().is_some_and(|s| s == "remote");
|
||||
|
||||
if !is_remote {
|
||||
logging!(info, Type::Config, "[订阅更新] {uid} 不是远程订阅,跳过更新");
|
||||
logging!(info, Type::Config, "[订阅更新] 不是远程订阅,跳过更新");
|
||||
Ok(None)
|
||||
} else if item.url.is_none() {
|
||||
logging!(warn, Type::Config, "Warning: [订阅更新] {uid} 缺少URL,无法更新");
|
||||
logging!(warn, Type::Config, "[订阅更新] 缺少URL,无法更新");
|
||||
bail!("failed to get the profile item url");
|
||||
} else if !ignore_auto_update && !item.option.as_ref().and_then(|o| o.allow_auto_update).unwrap_or(true) {
|
||||
logging!(info, Type::Config, "[订阅更新] {} 禁止自动更新,跳过更新", uid);
|
||||
logging!(info, Type::Config, "[订阅更新] 禁止自动更新,跳过更新");
|
||||
Ok(None)
|
||||
} else {
|
||||
logging!(
|
||||
info,
|
||||
Type::Config,
|
||||
"[订阅更新] {} 是远程订阅,URL: {}",
|
||||
uid,
|
||||
"[订阅更新] 远程订阅,URL: {}",
|
||||
mask_url(
|
||||
item.url
|
||||
.as_ref()
|
||||
@@ -113,6 +100,7 @@ async fn should_update_profile(uid: &String, ignore_auto_update: bool) -> Result
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all, level = "info", fields(uid = %uid, strategy = tracing::field::Empty))]
|
||||
async fn perform_profile_update(
|
||||
uid: &String,
|
||||
url: &String,
|
||||
@@ -120,7 +108,6 @@ async fn perform_profile_update(
|
||||
option: Option<&PrfOption>,
|
||||
is_mannual_trigger: bool,
|
||||
) -> Result<()> {
|
||||
logging!(info, Type::Config, "[订阅更新] 开始下载新的订阅内容");
|
||||
let mut merged_opt = PrfOption::merge(opt, option);
|
||||
let profiles = Config::profiles().await;
|
||||
let profiles_arc = profiles.latest_arc();
|
||||
@@ -140,8 +127,8 @@ async fn perform_profile_update(
|
||||
logging!(
|
||||
warn,
|
||||
Type::Config,
|
||||
"Warning: [订阅更新] 正常更新失败: {},尝试使用Clash代理更新",
|
||||
mask_err(&err.to_string())
|
||||
"[订阅更新] 直接更新失败: {},尝试使用Clash代理更新",
|
||||
mask_err(&format!("{err:#}"))
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -160,8 +147,8 @@ async fn perform_profile_update(
|
||||
logging!(
|
||||
warn,
|
||||
Type::Config,
|
||||
"Warning: [订阅更新] Clash代理更新失败: {},尝试使用系统代理更新",
|
||||
mask_err(&err.to_string())
|
||||
"[订阅更新] Clash代理更新失败: {},尝试使用系统代理更新",
|
||||
mask_err(&format!("{err:#}"))
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -180,8 +167,8 @@ async fn perform_profile_update(
|
||||
logging!(
|
||||
warn,
|
||||
Type::Config,
|
||||
"Warning: [订阅更新] 系统代理更新失败: {},所有重试均已失败",
|
||||
mask_err(&err.to_string())
|
||||
"[订阅更新] 系统代理更新失败: {},所有重试均已失败",
|
||||
mask_err(&format!("{err:#}"))
|
||||
);
|
||||
err
|
||||
}
|
||||
@@ -194,8 +181,8 @@ async fn perform_profile_update(
|
||||
bail!(last_err)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all, level = "info", fields(uid = %uid, manual = is_mannual_trigger))]
|
||||
pub async fn update_profile(uid: &String, option: Option<&PrfOption>, is_mannual_trigger: bool) -> Result<()> {
|
||||
logging!(info, Type::Config, "[订阅更新] 开始更新订阅 {}", uid);
|
||||
let url_opt = should_update_profile(uid, is_mannual_trigger).await?;
|
||||
|
||||
let profile_persisted = match url_opt {
|
||||
@@ -210,10 +197,9 @@ pub async fn update_profile(uid: &String, option: Option<&PrfOption>, is_mannual
|
||||
let should_refresh = is_current || (!profile_persisted && is_mannual_trigger);
|
||||
|
||||
if should_refresh {
|
||||
logging!(info, Type::Config, "[订阅更新] 更新内核配置");
|
||||
logging!(debug, Type::Config, "[订阅更新] 更新内核配置");
|
||||
match CoreManager::global().update_config_with_force(is_mannual_trigger).await {
|
||||
Ok(outcome) if outcome.is_valid() => {
|
||||
logging!(info, Type::Config, "[订阅更新] 更新成功");
|
||||
handle::Handle::refresh_clash();
|
||||
}
|
||||
Ok(outcome @ (ValidationOutcome::Skipped { .. } | ValidationOutcome::Busy)) if !is_mannual_trigger => {
|
||||
|
||||
@@ -23,7 +23,11 @@ pub async fn toggle_system_proxy() -> Option<bool> {
|
||||
&& auto_close_connection
|
||||
&& let Err(err) = handle::Handle::mihomo().close_all_connections().await
|
||||
{
|
||||
logging!(error, Type::ProxyMode, "Failed to close all connections: {err}");
|
||||
logging!(
|
||||
error,
|
||||
Type::ProxyMode,
|
||||
"toggle system proxy: failed to close all connections: {err}"
|
||||
);
|
||||
}
|
||||
|
||||
let requested = !current;
|
||||
@@ -42,7 +46,7 @@ pub async fn toggle_system_proxy() -> Option<bool> {
|
||||
match patch_result {
|
||||
Ok(_) => Some(requested),
|
||||
Err(err) => {
|
||||
logging!(error, Type::ProxyMode, "{err:#}");
|
||||
logging!(error, Type::ProxyMode, "toggle system proxy failed: {err:#}");
|
||||
report_toggle_failure(&err).await;
|
||||
None
|
||||
}
|
||||
@@ -84,7 +88,7 @@ pub async fn toggle_tun_mode(not_save_file: Option<bool>) -> bool {
|
||||
Config::verge().await.latest_arc().enable_tun_mode.unwrap_or(false)
|
||||
}
|
||||
Err(err) => {
|
||||
logging!(error, Type::ProxyMode, "{err:#}");
|
||||
logging!(error, Type::ProxyMode, "toggle tun mode failed: {err:#}");
|
||||
current
|
||||
}
|
||||
}
|
||||
@@ -133,7 +137,7 @@ pub async fn copy_clash_env() {
|
||||
}
|
||||
};
|
||||
|
||||
if clipboard.write_text(&export_text).is_err() {
|
||||
logging!(error, Type::ProxyMode, "Failed to write to clipboard");
|
||||
if let Err(err) = clipboard.write_text(&export_text) {
|
||||
logging!(error, Type::ProxyMode, "Failed to write to clipboard: {err}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,11 +88,11 @@ async fn restore_dns_after_core_stop() -> bool {
|
||||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
logging!(info, Type::Window, "DNS设置已恢复");
|
||||
logging!(debug, Type::Window, "DNS设置已恢复");
|
||||
true
|
||||
}
|
||||
Err(_) => {
|
||||
logging!(warn, Type::Window, "Warning: 恢复DNS设置超时");
|
||||
logging!(warn, Type::Window, "恢复DNS设置超时");
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -117,7 +117,6 @@ pub async fn quit() -> clash_verge_signal::ShutdownOutcome {
|
||||
|
||||
Config::apply_all_and_save_file().await;
|
||||
|
||||
logging!(info, Type::System, "开始异步清理资源");
|
||||
let cleanup_result = clean_async().await;
|
||||
|
||||
logging!(
|
||||
@@ -143,21 +142,12 @@ pub async fn quit() -> clash_verge_signal::ShutdownOutcome {
|
||||
}
|
||||
|
||||
pub async fn clean_async() -> CleanupResult {
|
||||
logging!(
|
||||
info,
|
||||
Type::System,
|
||||
"Starting interactive cleanup; controlled core stop will be awaited to completion"
|
||||
);
|
||||
|
||||
let stop_error = Mutex::new(None);
|
||||
let mut result = run_interactive_cleanup_transition(
|
||||
|| async {
|
||||
logging!(info, Type::System, "Stopping core for interactive quit or restart");
|
||||
logging!(debug, Type::System, "Stopping core for interactive quit or restart");
|
||||
match CoreManager::global().stop_core().await {
|
||||
Ok(()) => {
|
||||
logging!(info, Type::Window, "Core stopped for interactive quit or restart");
|
||||
true
|
||||
}
|
||||
Ok(()) => true,
|
||||
Err(error) => {
|
||||
logging!(
|
||||
warn,
|
||||
@@ -199,13 +189,10 @@ pub async fn clean_session_ending_best_effort() -> CleanupResult {
|
||||
|
||||
let result = run_session_ending_cleanup_transition(
|
||||
|| async {
|
||||
logging!(info, Type::System, "Clearing all WebSocket connections before stopping core");
|
||||
let _ = handle::Handle::mihomo().clear_all_ws_connections();
|
||||
logging!(info, Type::System, "Stopping core during session-ending best-effort cleanup");
|
||||
match CoreManager::global().stop_core().await {
|
||||
let _ = handle::Handle::mihomo().clear_all_ws_connections();
|
||||
match CoreManager::global().stop_core().await {
|
||||
Ok(()) => {
|
||||
logging!(info, Type::Window, "Core stopped during session-ending best-effort cleanup");
|
||||
true
|
||||
true
|
||||
}
|
||||
Err(error) => {
|
||||
logging!(
|
||||
|
||||
@@ -33,7 +33,7 @@ mod app_init {
|
||||
/// Initialize singleton monitoring for other instances
|
||||
pub fn init_singleton_check() -> Result<server::SingletonDisposition> {
|
||||
AsyncHandler::block_on(async move {
|
||||
logging!(info, Type::Setup, "开始检查单例实例...");
|
||||
logging!(debug, Type::Setup, "开始检查单例实例...");
|
||||
server::check_singleton().await
|
||||
})
|
||||
}
|
||||
@@ -74,7 +74,7 @@ mod app_init {
|
||||
pub fn setup_deep_links(app: &tauri::App) {
|
||||
#[cfg(any(target_os = "linux", all(debug_assertions, windows)))]
|
||||
{
|
||||
logging!(info, Type::Setup, "注册深层链接...");
|
||||
logging!(debug, Type::Setup, "注册深层链接...");
|
||||
let _ = app.deep_link().register_all();
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@ mod app_init {
|
||||
|
||||
/// Setup window state management
|
||||
pub fn setup_window_state(app: &tauri::App) -> Result<(), Box<dyn std::error::Error>> {
|
||||
logging!(info, Type::Setup, "初始化窗口状态管理...");
|
||||
logging!(debug, Type::Setup, "初始化窗口状态管理...");
|
||||
let window_state_plugin = tauri_plugin_window_state::Builder::new()
|
||||
.with_filename(files::WINDOW_STATE)
|
||||
.with_state_flags(tauri_plugin_window_state::StateFlags::default())
|
||||
@@ -280,10 +280,10 @@ pub fn run() -> std::process::ExitCode {
|
||||
.expect("failed to set global app handle");
|
||||
|
||||
if let Err(e) = resolve::init_work_dir_and_logger() {
|
||||
logging!(error, Type::Setup, "Failed to init work dir/logger: {}", e);
|
||||
logging!(error, Type::Setup, "Failed to init work dir/logger: {e:#}");
|
||||
}
|
||||
|
||||
logging!(info, Type::Setup, "开始应用初始化...");
|
||||
logging!(debug, Type::Setup, "开始应用初始化...");
|
||||
if let Err(e) = app_init::setup_autostart(app) {
|
||||
logging!(error, Type::Setup, "Failed to setup autostart: {}", e);
|
||||
}
|
||||
@@ -302,7 +302,6 @@ pub fn run() -> std::process::ExitCode {
|
||||
resolve::resolve_setup_async();
|
||||
resolve::resolve_setup_sync();
|
||||
resolve::init_signal();
|
||||
logging!(info, Type::Setup, "初始化已启动");
|
||||
})) {
|
||||
log_setup_panic("window-core", panic);
|
||||
}
|
||||
@@ -470,6 +469,7 @@ pub fn run() -> std::process::ExitCode {
|
||||
);
|
||||
}
|
||||
logging!(info, Type::System, "Application exited");
|
||||
crate::core::logger::Logger::global().flush_logs();
|
||||
}),
|
||||
#[allow(unused_variables)]
|
||||
tauri::RunEvent::ExitRequested { api, code, .. } => {
|
||||
|
||||
@@ -34,8 +34,5 @@ fn main() -> ExitCode {
|
||||
let tokio_handle = tokio_runtime.handle();
|
||||
tauri::async_runtime::set(tokio_handle.clone());
|
||||
|
||||
#[cfg(feature = "tokio-trace")]
|
||||
console_subscriber::init();
|
||||
|
||||
app_lib::run()
|
||||
}
|
||||
|
||||
@@ -329,7 +329,7 @@ impl PathBufExec for PathBuf {
|
||||
async fn remove_if_exists(&self) -> Result<()> {
|
||||
if self.exists() {
|
||||
tokio::fs::remove_file(self).await?;
|
||||
logging!(info, Type::File, "Removed file: {:?}", self);
|
||||
logging!(debug, Type::File, "Removed file: {:?}", self);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ async fn delete_snapshot_logs(log_dir: &Path) -> Result<()> {
|
||||
let path = entry.path();
|
||||
if path.extension().and_then(|s| s.to_str()) == Some("log") {
|
||||
let _ = path.remove_if_exists().await;
|
||||
logging!(info, Type::Setup, "delete snapshot log file: {}", path.display());
|
||||
logging!(debug, Type::Setup, "delete snapshot log file: {}", path.display());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -69,7 +69,7 @@ pub async fn delete_log() -> Result<()> {
|
||||
_ => return Ok(()),
|
||||
};
|
||||
|
||||
logging!(info, Type::Setup, "try to delete log files, day: {}", day);
|
||||
logging!(debug, Type::Setup, "try to delete log files, day: {}", day);
|
||||
|
||||
let parse_time_str = |s: &str| {
|
||||
let sa: Vec<&str> = s.split('-').collect();
|
||||
@@ -102,7 +102,7 @@ pub async fn delete_log() -> Result<()> {
|
||||
let duration = now.signed_duration_since(file_time);
|
||||
if duration.num_days() > day {
|
||||
let _ = file.path().remove_if_exists().await;
|
||||
logging!(info, Type::Setup, "delete log file: {}", file_name);
|
||||
logging!(debug, Type::Setup, "delete log file: {}", file_name);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
@@ -284,7 +284,7 @@ async fn migrate_legacy_macos_logs() -> Result<()> {
|
||||
|
||||
if is_logs_dir_writable(&log_dir).await {
|
||||
if let Err(e) = migrate_legacy_macos_service_logs(&log_dir).await {
|
||||
logging!(warn, Type::Setup, "Failed to migrate legacy macOS service logs: {}", e);
|
||||
logging!(warn, Type::Setup, "Failed to migrate legacy macOS service logs: {e:#}");
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
@@ -489,9 +489,9 @@ pub async fn init_config() -> Result<()> {
|
||||
|
||||
AsyncHandler::spawn(|| async {
|
||||
if let Err(e) = delete_log().await {
|
||||
logging!(warn, Type::Setup, "Failed to clean old logs: {}", e);
|
||||
logging!(warn, Type::Setup, "Failed to clean old logs: {e:#}");
|
||||
}
|
||||
logging!(info, Type::Setup, "后台日志清理任务完成");
|
||||
logging!(debug, Type::Setup, "后台日志清理任务完成");
|
||||
});
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -27,7 +27,7 @@ pub async fn set_public_dns(dns_server: String) {
|
||||
use tauri_plugin_shell::ShellExt as _;
|
||||
let app_handle = handle::Handle::app_handle();
|
||||
|
||||
logging!(info, Type::Config, "try to set system dns");
|
||||
logging!(debug, Type::Config, "try to set system dns");
|
||||
let resource_dir = match dirs::app_resources_dir() {
|
||||
Ok(dir) => dir,
|
||||
Err(e) => {
|
||||
@@ -75,7 +75,7 @@ pub async fn restore_public_dns() {
|
||||
use crate::{core::handle, utils::dirs};
|
||||
use tauri_plugin_shell::ShellExt as _;
|
||||
let app_handle = handle::Handle::app_handle();
|
||||
logging!(info, Type::Config, "try to unset system dns");
|
||||
logging!(debug, Type::Config, "try to unset system dns");
|
||||
let resource_dir = match dirs::app_resources_dir() {
|
||||
Ok(dir) => dir,
|
||||
Err(e) => {
|
||||
|
||||
@@ -8,7 +8,7 @@ use crate::{
|
||||
CoreManager, Timer,
|
||||
handle::Handle,
|
||||
hotkey::Hotkey,
|
||||
logger::Logger,
|
||||
logger,
|
||||
service::{SERVICE_MANAGER, ServiceManager},
|
||||
tray::Tray,
|
||||
},
|
||||
@@ -31,7 +31,7 @@ pub(crate) fn init_work_dir_and_logger() -> anyhow::Result<()> {
|
||||
AsyncHandler::block_on(async {
|
||||
init_work_config().await;
|
||||
logging!(info, Type::Setup, "Initializing logger");
|
||||
Logger::global().init().await?;
|
||||
logger::init().await?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
@@ -46,50 +46,53 @@ pub(crate) fn resolve_setup_sync() {
|
||||
}
|
||||
|
||||
pub(crate) fn resolve_setup_async() {
|
||||
AsyncHandler::spawn(|| async {
|
||||
logging!(info, Type::ClashVergeRev, "Version: {}", env!("CARGO_PKG_VERSION"));
|
||||
AsyncHandler::spawn(resolve_setup);
|
||||
}
|
||||
|
||||
// Migrate before windows or timers can change the loaded config.
|
||||
logging_error!(Type::Setup, init::migrate_short_update_intervals().await);
|
||||
#[tracing::instrument(skip_all, level = "info")]
|
||||
async fn resolve_setup() {
|
||||
logging!(info, Type::ClashVergeRev, "Version: {}", env!("CARGO_PKG_VERSION"));
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
resolve_dock_show().await;
|
||||
init_startup_script().await;
|
||||
init_service_manager().await;
|
||||
let config_initialized = init_verge_config_before_window().await;
|
||||
init_window().await;
|
||||
feat::reconcile_startup_tun_availability().await;
|
||||
init_resources().await;
|
||||
if let Err(e) = init::init_dns_config().await {
|
||||
logging!(warn, Type::Setup, "DNS config initialization failed: {}", e);
|
||||
}
|
||||
if config_initialized {
|
||||
init_verge_config().await;
|
||||
}
|
||||
Config::verify_config_initialization().await;
|
||||
// Migrate before windows or timers can change the loaded config.
|
||||
logging_error!(Type::Setup, init::migrate_short_update_intervals().await);
|
||||
|
||||
// Live before the core starts, so a service appearing mid-start is not missed.
|
||||
#[cfg(target_os = "macos")]
|
||||
crate::core::network_watch::start();
|
||||
#[cfg(target_os = "macos")]
|
||||
resolve_dock_show().await;
|
||||
init_startup_script().await;
|
||||
init_service_manager().await;
|
||||
let config_initialized = init_verge_config_before_window().await;
|
||||
init_window().await;
|
||||
feat::reconcile_startup_tun_availability().await;
|
||||
init_resources().await;
|
||||
if let Err(e) = init::init_dns_config().await {
|
||||
logging!(warn, Type::Setup, "DNS config initialization failed: {}", e);
|
||||
}
|
||||
if config_initialized {
|
||||
init_verge_config().await;
|
||||
}
|
||||
Config::verify_config_initialization().await;
|
||||
|
||||
let core_init = AsyncHandler::spawn(|| async {
|
||||
init_core_manager().await;
|
||||
});
|
||||
// Live before the core starts, so a service appearing mid-start is not missed.
|
||||
#[cfg(target_os = "macos")]
|
||||
crate::core::network_watch::start();
|
||||
|
||||
let _ = futures::join!(
|
||||
core_init,
|
||||
init_tray(),
|
||||
init_timer(),
|
||||
init_hotkey(),
|
||||
init_auto_lightweight_boot(),
|
||||
init_auto_backup(),
|
||||
init_silent_updater(),
|
||||
);
|
||||
|
||||
Handle::refresh_clash();
|
||||
refresh_tray_menu().await;
|
||||
resolve_done();
|
||||
let core_init = AsyncHandler::spawn(|| async {
|
||||
init_core_manager().await;
|
||||
});
|
||||
|
||||
let _ = futures::join!(
|
||||
core_init,
|
||||
init_tray(),
|
||||
init_timer(),
|
||||
init_hotkey(),
|
||||
init_auto_lightweight_boot(),
|
||||
init_auto_backup(),
|
||||
init_silent_updater(),
|
||||
);
|
||||
|
||||
Handle::refresh_clash();
|
||||
refresh_tray_menu().await;
|
||||
resolve_done();
|
||||
}
|
||||
|
||||
pub async fn resolve_reset_async() -> Result<(), anyhow::Error> {
|
||||
@@ -165,7 +168,7 @@ async fn init_silent_updater() {
|
||||
use crate::core::SilentUpdater;
|
||||
use crate::core::handle::Handle;
|
||||
|
||||
logging!(info, Type::Setup, "Initializing silent updater...");
|
||||
logging!(debug, Type::Setup, "Initializing silent updater...");
|
||||
|
||||
let app_handle = Handle::app_handle();
|
||||
|
||||
@@ -184,7 +187,7 @@ async fn init_silent_updater() {
|
||||
}
|
||||
|
||||
pub(crate) fn init_signal() {
|
||||
logging!(info, Type::Setup, "Initializing signal handlers...");
|
||||
logging!(debug, Type::Setup, "Initializing signal handlers...");
|
||||
clash_verge_signal::register(feat::quit);
|
||||
}
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ async fn import_subscription(url: &str, name: Option<&String>) {
|
||||
}
|
||||
|
||||
if let Err(e) = profiles::profiles_save_file_safe().await {
|
||||
logging!(error, Type::Config, "failed to save imported subscription: {}", e);
|
||||
logging!(error, Type::Config, "failed to save imported subscription: {e:#}");
|
||||
handle::Handle::notice_message("import_sub_url::error", e.to_string());
|
||||
return;
|
||||
}
|
||||
@@ -152,7 +152,7 @@ async fn refresh_core_config() {
|
||||
handle::Handle::notice_message("config_validate::error", message);
|
||||
}
|
||||
Err(err) => {
|
||||
logging!(error, Type::Config, "Apply config error: {}", err);
|
||||
logging!(error, Type::Config, "Apply config error: {err:#}");
|
||||
handle::Handle::notice_message("update_failed", format!("{err}"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ pub struct WindowManager;
|
||||
impl WindowManager {
|
||||
#[cfg(target_os = "macos")]
|
||||
fn set_macos_activation_policy_regular() {
|
||||
logging!(info, Type::Window, "应用 macOS 特定的激活策略");
|
||||
logging!(debug, Type::Window, "应用 macOS 特定的激活策略");
|
||||
handle::Handle::global().set_activation_policy_regular();
|
||||
}
|
||||
|
||||
@@ -86,16 +86,16 @@ impl WindowManager {
|
||||
return WindowOperationResult::NoAction;
|
||||
}
|
||||
|
||||
logging!(info, Type::Window, "开始智能显示主窗口");
|
||||
logging!(debug, Type::Window, "开始智能显示主窗口");
|
||||
logging!(debug, Type::Window, "{}", Self::get_window_status_info());
|
||||
|
||||
let current_state = Self::get_main_window_state();
|
||||
|
||||
match current_state {
|
||||
WindowState::NotExist => {
|
||||
logging!(info, Type::Window, "窗口不存在,创建新窗口");
|
||||
logging!(debug, Type::Window, "窗口不存在,创建新窗口");
|
||||
if Self::create_window(true).await {
|
||||
logging!(info, Type::Window, "窗口创建成功");
|
||||
logging!(debug, Type::Window, "窗口创建成功");
|
||||
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
|
||||
WindowOperationResult::Created
|
||||
} else {
|
||||
@@ -104,13 +104,13 @@ impl WindowManager {
|
||||
}
|
||||
}
|
||||
WindowState::VisibleFocused => {
|
||||
logging!(info, Type::Window, "窗口已经可见且有焦点,无需操作");
|
||||
logging!(debug, Type::Window, "窗口已经可见且有焦点,无需操作");
|
||||
WindowOperationResult::NoAction
|
||||
}
|
||||
WindowState::VisibleUnfocused | WindowState::Minimized | WindowState::Hidden => {
|
||||
let (window, state_after_check) = Self::get_main_window_with_state();
|
||||
if state_after_check == WindowState::VisibleFocused {
|
||||
logging!(info, Type::Window, "窗口在检查期间已变为可见和有焦点状态");
|
||||
logging!(debug, Type::Window, "窗口在检查期间已变为可见和有焦点状态");
|
||||
return WindowOperationResult::NoAction;
|
||||
}
|
||||
if let Some(window) = window {
|
||||
@@ -139,7 +139,7 @@ impl WindowManager {
|
||||
}
|
||||
|
||||
async fn handle_not_exist_toggle() -> WindowOperationResult {
|
||||
logging!(info, Type::Window, "窗口不存在,将创建新窗口");
|
||||
logging!(debug, Type::Window, "窗口不存在,将创建新窗口");
|
||||
if Self::create_window(true).await {
|
||||
WindowOperationResult::Created
|
||||
} else {
|
||||
@@ -148,7 +148,7 @@ impl WindowManager {
|
||||
}
|
||||
|
||||
fn hide_main_window(window: Option<&WebviewWindow<Wry>>) -> WindowOperationResult {
|
||||
logging!(info, Type::Window, "窗口可见,将隐藏窗口");
|
||||
logging!(debug, Type::Window, "窗口可见,将隐藏窗口");
|
||||
if let Some(window) = window {
|
||||
match window.close() {
|
||||
Ok(_) => {
|
||||
@@ -167,7 +167,7 @@ impl WindowManager {
|
||||
}
|
||||
|
||||
fn activate_existing_main_window(window: Option<&WebviewWindow<Wry>>) -> WindowOperationResult {
|
||||
logging!(info, Type::Window, "窗口存在但被隐藏或最小化,将激活窗口");
|
||||
logging!(debug, Type::Window, "窗口存在但被隐藏或最小化,将激活窗口");
|
||||
if let Some(window) = window {
|
||||
Self::activate_window(window)
|
||||
} else {
|
||||
@@ -177,7 +177,7 @@ impl WindowManager {
|
||||
}
|
||||
|
||||
fn activate_window(window: &WebviewWindow<Wry>) -> WindowOperationResult {
|
||||
logging!(info, Type::Window, "开始激活窗口");
|
||||
logging!(debug, Type::Window, "开始激活窗口");
|
||||
#[cfg(target_os = "macos")]
|
||||
Self::set_macos_activation_policy_regular();
|
||||
|
||||
@@ -196,7 +196,7 @@ impl WindowManager {
|
||||
let mut operations_successful = true;
|
||||
|
||||
if window.is_minimized().unwrap_or(false) {
|
||||
logging!(info, Type::Window, "窗口已最小化,正在取消最小化");
|
||||
logging!(debug, Type::Window, "窗口已最小化,正在取消最小化");
|
||||
if let Err(e) = window.unminimize() {
|
||||
logging!(warn, Type::Window, "取消最小化失败: {}", e);
|
||||
operations_successful = false;
|
||||
@@ -248,7 +248,7 @@ impl WindowManager {
|
||||
/// Keep new windows hidden until the frontend overlay renders, avoiding a theme flash.
|
||||
pub fn create_window(should_create: bool) -> Pin<Box<dyn Future<Output = bool> + Send>> {
|
||||
Box::pin(async move {
|
||||
logging!(info, Type::Window, "开始创建主窗口, should_create={}", should_create);
|
||||
logging!(debug, Type::Window, "开始创建主窗口, should_create={}", should_create);
|
||||
|
||||
if !should_create {
|
||||
return false;
|
||||
@@ -277,7 +277,7 @@ impl WindowManager {
|
||||
logging!(info, Type::Window, "窗口已摧毁");
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
logging!(info, Type::Window, "应用 macOS 特定的激活策略");
|
||||
logging!(debug, Type::Window, "应用 macOS 特定的激活策略");
|
||||
handle::Handle::global().set_activation_policy_accessory();
|
||||
}
|
||||
return WindowOperationResult::Destroyed;
|
||||
|
||||
Reference in New Issue
Block a user