diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 73885d218..b2892a55c 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -1098,8 +1098,9 @@ dependencies = [ "backoff", "base64 0.22.1", "boa_engine", - "cfg-if", "chrono", + "clash_verge_logger", + "clash_verge_service_ipc", "console-subscriber", "criterion", "dashmap 6.1.0", @@ -1114,12 +1115,10 @@ dependencies = [ "hex", "hmac", "isahc", - "kode-bridge", "libc", "log", "nanoid", "network-interface", - "nu-ansi-term", "once_cell", "open", "parking_lot 0.12.5", @@ -1162,6 +1161,27 @@ dependencies = [ "zip 5.1.1", ] +[[package]] +name = "clash_verge_logger" +version = "0.1.0" +source = "git+https://github.com/clash-verge-rev/clash-verge-logger#d3033b152cbf45fd04d9dee48d7aa9371dbfe99c" +dependencies = [ + "flexi_logger", + "log", + "nu-ansi-term", +] + +[[package]] +name = "clash_verge_service_ipc" +version = "2.0.7" +source = "git+https://github.com/clash-verge-rev/clash-verge-service-ipc#119aef152cc4d172d597429cfa704c1915b7c395" +dependencies = [ + "serde", + "strum", + "strum_macros", + "windows-service", +] + [[package]] name = "clipboard-win" version = "5.4.1" @@ -1932,12 +1952,6 @@ dependencies = [ "const-random", ] -[[package]] -name = "doctest-file" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aac81fa3e28d21450aa4d2ac065992ba96a1d7303efbce51a95f4fd175b67562" - [[package]] name = "document-features" version = "0.2.11" @@ -3523,21 +3537,6 @@ dependencies = [ "serde_derive", ] -[[package]] -name = "interprocess" -version = "2.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d941b405bd2322993887859a8ee6ac9134945a24ec5ec763a8a962fc64dfec2d" -dependencies = [ - "doctest-file", - "futures-core", - "libc", - "recvmsg", - "tokio", - "widestring", - "windows-sys 0.52.0", -] - [[package]] name = "intrusive-collections" version = "0.9.7" @@ -3774,33 +3773,6 @@ dependencies = [ "unicode-segmentation", ] -[[package]] -name = "kode-bridge" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "414f864db42e2b780d3812cdb45f6715d311b177fbf27185be812b711fa5f939" -dependencies = [ - "bytes", - "futures", - "http 1.3.1", - "httparse", - "interprocess", - "libc", - "parking_lot 0.12.5", - "pin-project-lite", - "rand 0.9.2", - "serde", - "serde_json", - "thiserror 2.0.16", - "tokio", - "tokio-stream", - "tokio-util", - "toml 0.9.7", - "tracing", - "url", - "widestring", -] - [[package]] name = "kuchikiki" version = "0.8.8-speedreader" @@ -5772,12 +5744,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "recvmsg" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3edd4d5d42c92f0a659926464d4cce56b562761267ecf0f469d85b7de384175" - [[package]] name = "redox_syscall" version = "0.2.16" @@ -6804,6 +6770,24 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "strum" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" + +[[package]] +name = "strum_macros" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "subtle" version = "2.6.1" @@ -9127,6 +9111,17 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows-service" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "193cae8e647981c35bc947fdd57ba7928b1fa0d4a79305f6dd2dc55221ac35ac" +dependencies = [ + "bitflags 2.9.4", + "widestring", + "windows-sys 0.59.0", +] + [[package]] name = "windows-strings" version = "0.1.0" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 0d57e7a44..a371897c2 100755 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -71,7 +71,6 @@ hmac = "0.12.1" sha2 = "0.10.9" hex = "0.4.3" scopeguard = "1.2.0" -kode-bridge = "0.3.3" dashmap = "6.1.0" tauri-plugin-notification = "2.3.1" tokio-stream = "0.1.17" @@ -82,11 +81,11 @@ isahc = { version = "1.7.2", default-features = false, features = [ backoff = { version = "0.4.0", features = ["tokio"] } tauri-plugin-http = "2.5.2" flexi_logger = "0.31.5" -cfg-if = "1.0.3" -nu-ansi-term = { version = "0.50.1", optional = true } console-subscriber = { version = "0.4.1", optional = true } tauri-plugin-devtools = { version = "2.0.1" } tauri-plugin-mihomo = { git = "https://github.com/clash-verge-rev/tauri-plugin-mihomo" } +clash_verge_logger = { version = "0.1.0", git = "https://github.com/clash-verge-rev/clash-verge-logger" } +clash_verge_service_ipc = { version = "2.0.7", git = "https://github.com/clash-verge-rev/clash-verge-service-ipc" } [target.'cfg(windows)'.dependencies] @@ -118,7 +117,7 @@ tauri-plugin-updater = "2.9.0" [features] default = ["custom-protocol"] custom-protocol = ["tauri/custom-protocol"] -verge-dev = ["nu-ansi-term"] +verge-dev = ["clash_verge_logger/color"] tauri-dev = [] tokio-trace = ["console-subscriber"] diff --git a/src-tauri/src/core/core.rs b/src-tauri/src/core/core.rs index 4ae10993b..eca22d29b 100644 --- a/src-tauri/src/core/core.rs +++ b/src-tauri/src/core/core.rs @@ -1,5 +1,8 @@ use crate::AsyncHandler; use crate::core::logger::Logger; +use crate::process::CommandChildGuard; +use crate::utils::init::sidecar_writer; +use crate::utils::logging::SharedWriter; use crate::{ config::*, core::{ @@ -14,16 +17,12 @@ use crate::{ }, }; use anyhow::Result; -use chrono::Local; +use flexi_logger::DeferredNow; +use flexi_logger::writers::LogWriter; +use log::Record; use parking_lot::Mutex; -use std::{ - fmt, - fs::{File, create_dir_all}, - io::Write, - path::PathBuf, - sync::Arc, -}; -use tauri_plugin_shell::{ShellExt, process::CommandChild}; +use std::{fmt, path::PathBuf, sync::Arc}; +use tauri_plugin_shell::ShellExt; // TODO: // - 重构,提升模式切换速度 @@ -32,7 +31,7 @@ use tauri_plugin_shell::{ShellExt, process::CommandChild}; #[derive(Debug)] pub struct CoreManager { running: Arc>, - child_sidecar: Arc>>, + child_sidecar: Arc>>, } /// 内核运行模式 @@ -468,7 +467,7 @@ impl CoreManager { for pid in pids { // 跳过当前管理的进程 if let Some(current) = current_pid - && pid == current + && Some(pid) == current { logging!( debug, @@ -741,14 +740,6 @@ impl CoreManager { let clash_core = Config::verge().await.latest_ref().get_valid_clash_core(); let config_dir = dirs::app_home_dir()?; - let service_log_dir = dirs::app_home_dir()?.join("logs").join("service"); - create_dir_all(&service_log_dir)?; - - let now = Local::now(); - let timestamp = now.format("%Y%m%d_%H%M%S").to_string(); - - let log_path = service_log_dir.join(format!("sidecar_{timestamp}.log")); - let (mut rx, child) = app_handle .shell() .sidecar(&clash_core)? @@ -768,27 +759,60 @@ impl CoreManager { "Started core by sidecar pid: {}", pid ); - *self.child_sidecar.lock() = Some(child); + *self.child_sidecar.lock() = Some(CommandChildGuard::new(child)); self.set_running_mode(RunningMode::Sidecar); - let mut log_file = std::io::BufWriter::new(File::create(log_path)?); + let shared_writer: SharedWriter = + Arc::new(tokio::sync::Mutex::new(sidecar_writer().await?)); + AsyncHandler::spawn(|| async move { while let Some(event) = rx.recv().await { + let w = shared_writer.lock().await; match event { tauri_plugin_shell::process::CommandEvent::Stdout(line) => { + let mut now = DeferredNow::default(); + let line_str = String::from_utf8_lossy(&line); + let arg = format_args!("{}", line_str); + let record = Record::builder() + .args(arg) + .level(log::Level::Error) + .target("sidecar") + .build(); + let _ = w.write(&mut now, &record); + let line = String::from_utf8_lossy(&line); Logger::global().append_log(line.to_string()); - if let Err(e) = writeln!(log_file, "{}", line) { - eprintln!("[Sidecar] write stdout failed: {e}"); - } } tauri_plugin_shell::process::CommandEvent::Stderr(line) => { + let mut now = DeferredNow::default(); + let line_str = String::from_utf8_lossy(&line); + let arg = format_args!("{}", line_str); + let record = Record::builder() + .args(arg) + .level(log::Level::Error) + .target("sidecar") + .build(); + let _ = w.write(&mut now, &record); + let line = String::from_utf8_lossy(&line); Logger::global().append_log(line.to_string()); - let _ = writeln!(log_file, "[stderr] {}", line); } tauri_plugin_shell::process::CommandEvent::Terminated(term) => { - let _ = writeln!(log_file, "[terminated] {:?}", term); + let mut now = DeferredNow::default(); + let output_str = if let Some(code) = term.code { + format!("Process terminated with code: {}", code) + } else if let Some(signal) = term.signal { + format!("Process terminated by signal: {}", signal) + } else { + "Process terminated".to_string() + }; + let arg = format_args!("{}", output_str); + let record = Record::builder() + .args(arg) + .level(log::Level::Info) + .target("sidecar") + .build(); + let _ = w.write(&mut now, &record); break; } _ => {} @@ -803,12 +827,12 @@ impl CoreManager { if let Some(child) = self.child_sidecar.lock().take() { let pid = child.pid(); - child.kill()?; + drop(child); logging!( trace, Type::Core, true, - "Stopped core by sidecar pid: {}", + "Stopped core by sidecar pid: {:?}", pid ); } diff --git a/src-tauri/src/core/service.rs b/src-tauri/src/core/service.rs index 2f6062fc6..f2faac46c 100644 --- a/src-tauri/src/core/service.rs +++ b/src-tauri/src/core/service.rs @@ -381,6 +381,7 @@ pub(super) async fn start_with_existing_service(config_file: &PathBuf) -> Result "bin_path": dirs::path_to_str(&bin_path)?, "config_dir": dirs::path_to_str(&dirs::app_home_dir()?)?, "config_file": dirs::path_to_str(config_file)?, + // TODO 迁移 Service日志后删除 "log_file": dirs::path_to_str(&dirs::service_log_file()?)?, }); diff --git a/src-tauri/src/process/guard.rs b/src-tauri/src/process/guard.rs new file mode 100644 index 000000000..e4a48909d --- /dev/null +++ b/src-tauri/src/process/guard.rs @@ -0,0 +1,30 @@ +use anyhow::Result; +use tauri_plugin_shell::process::CommandChild; + +#[derive(Debug)] +pub struct CommandChildGuard(Option); + +impl Drop for CommandChildGuard { + fn drop(&mut self) { + if let Err(err) = self.kill() { + log::error!(target: "app", "Failed to kill child process: {}", err); + } + } +} + +impl CommandChildGuard { + pub fn new(child: CommandChild) -> Self { + Self(Some(child)) + } + + pub fn kill(&mut self) -> Result<()> { + if let Some(child) = self.0.take() { + let _ = child.kill(); + } + Ok(()) + } + + pub fn pid(&self) -> Option { + self.0.as_ref().map(|c| c.pid()) + } +} diff --git a/src-tauri/src/process/mod.rs b/src-tauri/src/process/mod.rs index ac83ecc0d..77f99e78e 100644 --- a/src-tauri/src/process/mod.rs +++ b/src-tauri/src/process/mod.rs @@ -1,2 +1,4 @@ mod async_handler; pub use async_handler::AsyncHandler; +mod guard; +pub use guard::CommandChildGuard; diff --git a/src-tauri/src/utils/dirs.rs b/src-tauri/src/utils/dirs.rs index 714e644c3..f7d12aa6d 100644 --- a/src-tauri/src/utils/dirs.rs +++ b/src-tauri/src/utils/dirs.rs @@ -144,6 +144,7 @@ pub fn service_path() -> Result { Ok(res_dir.join("clash-verge-service.exe")) } +// TODO 迁移 Service日志后删除 pub fn service_log_file() -> Result { use chrono::Local; @@ -158,6 +159,20 @@ pub fn service_log_file() -> Result { Ok(log_file) } +pub fn sidecar_log_dir() -> Result { + let log_dir = app_logs_dir()?.join("sidecar"); + let _ = std::fs::create_dir_all(&log_dir); + + Ok(log_dir) +} + +pub fn service_log_dir() -> Result { + let log_dir = app_logs_dir()?.join("service"); + let _ = std::fs::create_dir_all(&log_dir); + + Ok(log_dir) +} + pub fn path_to_str(path: &PathBuf) -> Result<&str> { let path_str = path .as_os_str() diff --git a/src-tauri/src/utils/init.rs b/src-tauri/src/utils/init.rs index 09b356e0f..c2286de8d 100644 --- a/src-tauri/src/utils/init.rs +++ b/src-tauri/src/utils/init.rs @@ -1,19 +1,23 @@ -cfg_if::cfg_if! { - if #[cfg(not(feature = "tauri-dev"))] { - use crate::utils::logging::{console_colored_format, file_format, NoExternModule}; - use flexi_logger::{Cleanup, Criterion, Duplicate, FileSpec, LogSpecification, Logger}; - } -} - +#[cfg(not(feature = "tauri-dev"))] +use crate::utils::logging::NoModuleFilter; use crate::{ config::*, core::handle, logging, process::AsyncHandler, - utils::{dirs, help, logging::Type}, + utils::{ + dirs::{self, service_log_dir, sidecar_log_dir}, + help, + logging::Type, + }, }; use anyhow::Result; use chrono::{Local, TimeZone}; +use clash_verge_service_ipc::WriterConfig; +use flexi_logger::writers::FileLogWriter; +use flexi_logger::{Cleanup, Criterion, FileSpec}; +#[cfg(not(feature = "tauri-dev"))] +use flexi_logger::{Duplicate, LogSpecBuilder, Logger}; use std::{path::PathBuf, str::FromStr}; use tauri_plugin_shell::ShellExt; use tokio::fs; @@ -34,11 +38,13 @@ pub async fn init_logger() -> Result<()> { }; let log_dir = dirs::app_logs_dir()?; - let logger = Logger::with(LogSpecification::from(log_level)) + let spec = LogSpecBuilder::new().default(log_level).build(); + + let logger = Logger::with(spec) .log_to_file(FileSpec::default().directory(log_dir).basename("")) .duplicate_to_stdout(Duplicate::Debug) - .format(console_colored_format) - .format_for_files(file_format) + .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 { @@ -47,7 +53,7 @@ pub async fn init_logger() -> Result<()> { }, Cleanup::KeepLogFiles(log_max_count), ) - .filter(Box::new(NoExternModule)); + .filter(Box::new(NoModuleFilter(&["wry", "tauri"]))); let _handle = logger.start()?; @@ -59,6 +65,54 @@ pub async fn init_logger() -> Result<()> { Ok(()) } +pub async fn sidecar_writer() -> Result { + let (log_max_size, log_max_count) = { + let verge_guard = Config::verge().await; + let verge = verge_guard.latest_ref(); + ( + verge.app_log_max_size.unwrap_or(128), + verge.app_log_max_count.unwrap_or(8), + ) + }; + let sidecar_log_dir = sidecar_log_dir()?; + Ok(FileLogWriter::builder( + FileSpec::default() + .directory(sidecar_log_dir) + .basename("sidecar") + .suppress_timestamp(), + ) + .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()?) +} + +// TODO 后续迁移新 service 时使用 +#[allow(dead_code)] +pub async fn service_writer_config() -> Result { + let (log_max_size, log_max_count) = { + let verge_guard = Config::verge().await; + let verge = verge_guard.latest_ref(); + ( + verge.app_log_max_size.unwrap_or(128), + verge.app_log_max_count.unwrap_or(8), + ) + }; + let service_log_dir = dirs::path_to_str(&service_log_dir()?)?.to_string(); + + Ok(WriterConfig { + directory: service_log_dir, + max_log_size: log_max_size * 1024, + max_log_files: log_max_count, + }) +} + // TODO flexi_logger 提供了最大保留天数,或许我们应该用内置删除log文件 /// 删除log文件 pub async fn delete_log() -> Result<()> { diff --git a/src-tauri/src/utils/logging.rs b/src-tauri/src/utils/logging.rs index 7f95b9b4c..b0ba0fe34 100644 --- a/src-tauri/src/utils/logging.rs +++ b/src-tauri/src/utils/logging.rs @@ -1,15 +1,12 @@ -cfg_if::cfg_if! { - if #[cfg(feature = "tauri-dev")] { - use std::fmt; - } else { - #[cfg(feature = "verge-dev")] - use nu_ansi_term::Color; - use std::{fmt, io::Write, thread}; - use flexi_logger::DeferredNow; - use log::{LevelFilter, Record}; - use flexi_logger::filter::LogLineFilter; - } -} +use flexi_logger::writers::FileLogWriter; +#[cfg(not(feature = "tauri-dev"))] +use flexi_logger::{DeferredNow, filter::LogLineFilter}; +#[cfg(not(feature = "tauri-dev"))] +use log::Record; +use std::{fmt, sync::Arc}; +use tokio::sync::Mutex; + +pub type SharedWriter = Arc>; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Type { @@ -28,7 +25,6 @@ pub enum Type { Lightweight, Network, ProxyMode, - // Ipc, // Cache, ClashVergeRev, } @@ -51,7 +47,6 @@ impl fmt::Display for Type { Type::Lightweight => write!(f, "[Lightweight]"), Type::Network => write!(f, "[Network]"), Type::ProxyMode => write!(f, "[ProxMode]"), - // Type::Ipc => write!(f, "[IPC]"), // Type::Cache => write!(f, "[Cache]"), Type::ClashVergeRev => write!(f, "[ClashVergeRev]"), } @@ -173,75 +168,36 @@ macro_rules! logging_error { } #[cfg(not(feature = "tauri-dev"))] -static IGNORE_MODULES: &[&str] = &["tauri", "wry"]; +pub struct NoModuleFilter<'a>(pub &'a [&'a str]); + #[cfg(not(feature = "tauri-dev"))] -pub struct NoExternModule; +impl<'a> NoModuleFilter<'a> { + #[inline] + pub fn filter(&self, record: &Record) -> bool { + if let Some(module) = record.module_path() { + for blocked in self.0 { + if module.len() >= blocked.len() + && module.as_bytes()[..blocked.len()] == blocked.as_bytes()[..] + { + return false; + } + } + } + true + } +} + #[cfg(not(feature = "tauri-dev"))] -impl LogLineFilter for NoExternModule { +impl<'a> LogLineFilter for NoModuleFilter<'a> { fn write( &self, now: &mut DeferredNow, record: &Record, - log_line_writer: &dyn flexi_logger::filter::LogLineWriter, + writer: &dyn flexi_logger::filter::LogLineWriter, ) -> std::io::Result<()> { - let module_path = record.module_path().unwrap_or_default(); - if IGNORE_MODULES.iter().any(|m| module_path.starts_with(m)) { - Ok(()) - } else { - log_line_writer.write(now, record) + if !self.filter(record) { + return Ok(()); } + writer.write(now, record) } } - -#[cfg(not(feature = "tauri-dev"))] -pub fn get_log_level(log_level: &LevelFilter) -> String { - #[cfg(feature = "verge-dev")] - match log_level { - LevelFilter::Off => Color::Fixed(8).paint("OFF").to_string(), - LevelFilter::Error => Color::Red.paint("ERROR").to_string(), - LevelFilter::Warn => Color::Yellow.paint("WARN ").to_string(), - LevelFilter::Info => Color::Green.paint("INFO ").to_string(), - LevelFilter::Debug => Color::Blue.paint("DEBUG").to_string(), - LevelFilter::Trace => Color::Purple.paint("TRACE").to_string(), - } - #[cfg(not(feature = "verge-dev"))] - log_level.to_string() -} - -#[cfg(not(feature = "tauri-dev"))] -pub fn console_colored_format( - w: &mut dyn Write, - now: &mut DeferredNow, - record: &log::Record, -) -> std::io::Result<()> { - let current_thread = thread::current(); - let thread_name = current_thread.name().unwrap_or("unnamed"); - - let level = get_log_level(&record.level().to_level_filter()); - let line = record.line().unwrap_or(0); - write!( - w, - "[{}] {} [{}:{}] T[{}] {}", - now.format("%H:%M:%S%.3f"), - level, - record.module_path().unwrap_or(""), - line, - thread_name, - record.args(), - ) -} - -#[cfg(not(feature = "tauri-dev"))] -pub fn file_format( - w: &mut dyn Write, - now: &mut DeferredNow, - record: &Record, -) -> std::io::Result<()> { - write!( - w, - "[{}] {} {}", - now.format("%Y-%m-%d %H:%M:%S%.3f"), - record.level(), - record.args(), - ) -}