mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-10-22 19:52:14 +08:00
* feat(service): isolate macOS launch and owner runtime * ci(windows): run owner security tests * chore(deps): use kode-bridge v0.4.1 tag * fix(macos): classify installed service state * fix(macos): classify marked unavailable service * fix(macos): gate core startup on service migration * fix(macos): scope service init to supported platforms * feat(macos): prompt before migrating installed service * fix(macos): shorten sidecar IPC path * build(service): pin helper protocol and artifacts * fix(core): require confirmed service and sidecar readiness * fix(macos): harden launch roots and singleton state * fix(ui): refresh migration state before restart * fix(macos): linearize service migration lifecycle * fix(ui): keep service migration state fresh * feat(service): track authenticated owner sessions * fix(service): preserve newer owner sessions * feat(macos): route service proxy changes through helper * fix(macos): serialize service proxy guard * fix(macos): close service proxy owner lifecycle * fix(macos): close remaining proxy stop races * fix(app): do not cancel controlled service stop * fix(lifecycle): keep controlled updates cancellation-safe * fix(macos): stage dev core for service * test(macos): exercise staged core cleanup * fix(dev): await controlled macOS exit * chore: update changelog * fix(macos): recover sidecar after service install failure Allow the existing sidecar to continue when service installation leaves the manager in InstallRequired, then settle the session state as SidecarAllowed. Show neutral status text while the migration dialog waits for its final state. * fix(macos): stage dev service tools before elevation * fix(macos): localize launch guard prompts * fix(service): address cross-platform integration issues * feat: support seamless service migration * fix: pass Windows service installer path safely * fix(prebuild): derive service release from Cargo dependency * fix(dev): preserve service installation state * fix: resolve clippy warnings in service lifecycle
227 lines
8.6 KiB
Rust
227 lines
8.6 KiB
Rust
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 crate::{
|
|
core::service,
|
|
singleton,
|
|
utils::dirs::{self, sidecar_log_dir},
|
|
};
|
|
|
|
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::NoModuleFilter(filter_modules)));
|
|
|
|
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);
|
|
|
|
// update service writer config
|
|
if service::is_service_available().await {
|
|
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?;
|
|
}
|
|
|
|
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(),
|
|
)
|
|
.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()?)
|
|
}
|
|
|
|
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");
|
|
}
|
|
}
|
|
}
|