From 98df5c1801468ae66dcdf9db6f70104285a119d8 Mon Sep 17 00:00:00 2001 From: Yuwei Ba Date: Sat, 21 Oct 2023 04:32:19 +1100 Subject: [PATCH] support logging to file (#132) --- Cargo.lock | 23 ++++++++++++++++++ clash/src/main.rs | 4 +--- clash_lib/Cargo.toml | 1 + clash_lib/src/app/logging.rs | 45 ++++++++++++++++++++++++++++++++---- clash_lib/src/lib.rs | 17 ++++++++++---- 5 files changed, 78 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b12e7fb4e..fe5e5df12 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -792,6 +792,7 @@ dependencies = [ "tower", "tower-http", "tracing", + "tracing-appender", "tracing-opentelemetry", "tracing-oslog", "tracing-subscriber", @@ -3616,8 +3617,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "426f806f4089c493dcac0d24c29c01e2c38baf8e30f1b716ee37e83d200b18fe" dependencies = [ "deranged", + "itoa", "serde", "time-core", + "time-macros", ] [[package]] @@ -3626,6 +3629,15 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" +[[package]] +name = "time-macros" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" +dependencies = [ + "time-core", +] + [[package]] name = "tinytemplate" version = "1.2.1" @@ -3889,6 +3901,17 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-appender" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09d48f71a791638519505cefafe162606f706c25592e4bde4d97600c0195312e" +dependencies = [ + "crossbeam-channel", + "time", + "tracing-subscriber", +] + [[package]] name = "tracing-attributes" version = "0.1.27" diff --git a/clash/src/main.rs b/clash/src/main.rs index 4ceb3e433..9c5c6c080 100644 --- a/clash/src/main.rs +++ b/clash/src/main.rs @@ -18,9 +18,6 @@ struct Cli { default_value = "config.yaml" )] config: PathBuf, - - #[clap(short, long, action)] - test: bool, } fn main() { @@ -36,6 +33,7 @@ fn main() { ), cwd: cli.directory.map(|x| x.to_string_lossy().to_string()), rt: Some(TokioRuntime::MultiThread), + log_file: None, }) .unwrap(); } diff --git a/clash_lib/Cargo.toml b/clash_lib/Cargo.toml index df97abd6e..6ebba3bd6 100644 --- a/clash_lib/Cargo.toml +++ b/clash_lib/Cargo.toml @@ -87,6 +87,7 @@ tokio-tungstenite = "0.20.0" tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } tracing-oslog = "0.1" +tracing-appender = "0.2.2" shadowsocks = { git = "https://github.com/Watfaq/shadowsocks-rust.git", optional = true, features=["aead-cipher-2022"], rev = "2021741" } diff --git a/clash_lib/src/app/logging.rs b/clash_lib/src/app/logging.rs index 0c4ca4b4e..7128d7b44 100644 --- a/clash_lib/src/app/logging.rs +++ b/clash_lib/src/app/logging.rs @@ -6,6 +6,8 @@ use serde::Serialize; use tokio::sync::broadcast::Sender; use tracing::debug; +use tracing_appender::non_blocking::NonBlocking; +use tracing_appender::non_blocking::WorkerGuard; use tracing_oslog::OsLogger; use tracing_subscriber::filter::Directive; use tracing_subscriber::prelude::*; @@ -68,10 +70,34 @@ where } } -pub fn setup_logging(level: LogLevel, collector: EventCollector) -> anyhow::Result<()> { +struct W(Option); + +impl std::io::Write for W { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + match self.0 { + Some(ref mut w) => w.write(buf), + None => Ok(buf.len()), + } + } + + fn flush(&mut self) -> std::io::Result<()> { + match self.0 { + Some(ref mut w) => w.flush(), + None => Ok(()), + } + } +} + +pub fn setup_logging( + level: LogLevel, + collector: EventCollector, + cwd: &str, + log_file: Option, +) -> anyhow::Result> { #[cfg(feature = "tracing")] { console_subscriber::init(); + Ok(None) } #[cfg(not(feature = "tracing"))] { @@ -104,6 +130,14 @@ pub fn setup_logging(level: LogLevel, collector: EventCollector) -> anyhow::Resu None }; + let (appender, g) = if let Some(log_file) = log_file { + let file_appender = tracing_appender::rolling::daily(cwd, log_file); + let (non_blocking, guard) = tracing_appender::non_blocking(file_appender); + (Some(non_blocking), Some(guard)) + } else { + (None, None) + }; + let subscriber = tracing_subscriber::registry() .with(jaeger) .with(filter) @@ -114,7 +148,10 @@ pub fn setup_logging(level: LogLevel, collector: EventCollector) -> anyhow::Resu .pretty() .with_file(true) .with_line_number(true) - .with_writer(std::io::stdout), + .with_writer(std::io::stdout) + .with_writer(move || -> Box { + Box::new(W(appender.clone())) + }), ) .with(ios_os_log); @@ -124,9 +161,9 @@ pub fn setup_logging(level: LogLevel, collector: EventCollector) -> anyhow::Resu if let Ok(jager_endpiont) = std::env::var("JAGER_ENDPOINT") { debug!("jager endpoint: {}", jager_endpiont); } - } - Ok(()) + Ok(g) + } } struct EventVisitor<'a>(&'a mut Vec); diff --git a/clash_lib/src/lib.rs b/clash_lib/src/lib.rs index 46ad15842..c820685c1 100644 --- a/clash_lib/src/lib.rs +++ b/clash_lib/src/lib.rs @@ -61,6 +61,7 @@ pub struct Options { pub config: Config, pub cwd: Option, pub rt: Option, + pub log_file: Option, } pub enum TokioRuntime { @@ -128,19 +129,24 @@ async fn start_async(opts: Options) -> Result<(), Error> { Config::Str(s) => s.parse::()?.try_into()?, }; + let cwd = opts.cwd.unwrap_or_else(|| ".".to_string()); + let cwd = std::path::Path::new(&cwd); + let (log_tx, _) = broadcast::channel(100); let log_collector = app::logging::EventCollector::new(vec![log_tx.clone()]); - app::logging::setup_logging(config.general.log_level, log_collector) - .map_err(|x| Error::InvalidConfig(format!("failed to setup logging: {}", x.to_string())))?; + let _g = app::logging::setup_logging( + config.general.log_level, + log_collector, + cwd.to_str().unwrap(), + opts.log_file, + ) + .map_err(|x| Error::InvalidConfig(format!("failed to setup logging: {}", x.to_string())))?; let mut tasks = Vec::::new(); let mut runners = Vec::new(); - let cwd = opts.cwd.unwrap_or_else(|| ".".to_string()); - let cwd = std::path::Path::new(&cwd); - let system_resolver = Arc::new(SystemResolver::new().map_err(|x| Error::DNSError(x.to_string()))?); let client = new_http_client(system_resolver).map_err(|x| Error::DNSError(x.to_string()))?; @@ -293,6 +299,7 @@ mod tests { config: Config::Str(conf.to_string()), cwd: None, rt: None, + log_file: None, }) .unwrap() });