mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-10-28 09:12:08 +08:00
242 lines
9.2 KiB
Rust
242 lines
9.2 KiB
Rust
use std::{
|
|
str::FromStr as _,
|
|
sync::{
|
|
Arc,
|
|
atomic::{AtomicU64, AtomicUsize, Ordering},
|
|
},
|
|
};
|
|
|
|
use anyhow::{Result, bail};
|
|
use clash_verge_logging::{Type, logging};
|
|
use clash_verge_service_ipc::WriterConfig;
|
|
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, service_log_dir, 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_ipc_path_exists() && service::is_service_available().await.is_ok() {
|
|
let service_log_dir = dirs::path_to_str(&service_log_dir()?)?.into();
|
|
clash_verge_service_ipc::update_writer(&WriterConfig {
|
|
directory: service_log_dir,
|
|
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");
|
|
}
|
|
}
|
|
|
|
pub fn service_writer_config(&self) -> Result<WriterConfig> {
|
|
let service_log_dir = dirs::path_to_str(&service_log_dir()?)?.into();
|
|
let log_max_size = self.log_max_size.load(Ordering::SeqCst);
|
|
let log_max_count = self.log_max_count.load(Ordering::SeqCst);
|
|
let writer_config = WriterConfig {
|
|
directory: service_log_dir,
|
|
max_log_size: log_max_size * 1024,
|
|
max_log_files: log_max_count,
|
|
};
|
|
|
|
Ok(writer_config)
|
|
}
|
|
}
|