From 6b6d69a8ef41f6208169b4c637f9bea5217a1faa Mon Sep 17 00:00:00 2001 From: sigoden Date: Sat, 11 May 2024 17:13:31 +0800 Subject: [PATCH] feat: add log-file option (#383) --- Cargo.toml | 2 +- README.md | 9 ++++++--- src/args.rs | 14 ++++++++++++++ src/logger.rs | 53 ++++++++++++++++++++++++++++++++++++++++----------- src/main.rs | 2 +- 5 files changed, 64 insertions(+), 16 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b38095c..f4620f8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,7 @@ lazy_static = "1.4" uuid = { version = "1.7", features = ["v4", "fast-rng"] } urlencoding = "2.1" xml-rs = "0.8" -log = "0.4" +log = { version = "0.4", features = ["std"] } socket2 = "0.5" async-stream = "0.3" walkdir = "2.3" diff --git a/README.md b/README.md index 5eb2949..0982c2f 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,7 @@ Options: --render-spa Serve SPA(Single Page Application) --assets Set the path to the assets directory for overriding the built-in assets --log-format Customize http log format + --log-file Specify the file to save logs to, other than stdout/stderr --compress Set zip compress level [default: low] [possible values: none, low, medium, high] --completions Print shell completion script for [possible values: bash, elvish, fish, powershell, zsh] --tls-cert Path to an SSL/TLS certificate to serve with HTTPS @@ -329,7 +330,7 @@ All options can be set using environment variables prefixed with `DUFS_`. --config DUFS_CONFIG=config.yaml -b, --bind DUFS_BIND=0.0.0.0 -p, --port DUFS_PORT=5000 - --path-prefix DUFS_PATH_PREFIX=/static + --path-prefix DUFS_PATH_PREFIX=/dufs --hidden DUFS_HIDDEN=tmp,*.log,*.lock -a, --auth DUFS_AUTH="admin:admin@/:rw|@/" -A, --allow-all DUFS_ALLOW_ALL=true @@ -342,9 +343,10 @@ All options can be set using environment variables prefixed with `DUFS_`. --render-index DUFS_RENDER_INDEX=true --render-try-index DUFS_RENDER_TRY_INDEX=true --render-spa DUFS_RENDER_SPA=true - --assets DUFS_ASSETS=/assets + --assets DUFS_ASSETS=./assets --log-format DUFS_LOG_FORMAT="" - --compress DUFS_COMPRESS="low" + --log-file DUFS_LOG_FILE=./dufs.log + --compress DUFS_COMPRESS=low --tls-cert DUFS_TLS_CERT=cert.pem --tls-key DUFS_TLS_KEY=key.pem ``` @@ -380,6 +382,7 @@ render-try-index: true render-spa: true assets: ./assets/ log-format: '$remote_addr "$request" $status $http_user_agent' +log-file: ./dufs.log compress: low tls-cert: tests/data/cert.pem tls-key: tests/data/key_pkcs1.pem diff --git a/src/args.rs b/src/args.rs index f26dbca..78aa080 100644 --- a/src/args.rs +++ b/src/args.rs @@ -197,6 +197,15 @@ pub fn build_cli() -> Command { .value_name("format") .help("Customize http log format"), ) + .arg( + Arg::new("log-file") + .env("DUFS_LOG_FILE") + .hide_env(true) + .long("log-file") + .value_name("file") + .value_parser(value_parser!(PathBuf)) + .help("Specify the file to save logs to, other than stdout/stderr"), + ) .arg( Arg::new("compress") .env("DUFS_COMPRESS") @@ -280,6 +289,7 @@ pub struct Args { #[serde(deserialize_with = "deserialize_log_http")] #[serde(rename = "log-format")] pub http_logger: HttpLogger, + pub log_file: Option, pub compress: Compress, pub tls_cert: Option, pub tls_key: Option, @@ -392,6 +402,10 @@ impl Args { args.http_logger = log_format.parse()?; } + if let Some(log_file) = matches.get_one::("log-file") { + args.log_file = Some(log_file.clone()); + } + if let Some(compress) = matches.get_one::("compress") { args.compress = *compress; } diff --git a/src/logger.rs b/src/logger.rs index bfa808e..d60f35c 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -1,8 +1,14 @@ +use anyhow::{Context, Result}; use chrono::{Local, SecondsFormat}; -use log::{Level, Metadata, Record}; -use log::{LevelFilter, SetLoggerError}; +use log::{Level, LevelFilter, Metadata, Record}; +use std::fs::{File, OpenOptions}; +use std::io::Write; +use std::path::PathBuf; +use std::sync::Mutex; -struct SimpleLogger; +struct SimpleLogger { + file: Option>, +} impl log::Log for SimpleLogger { fn enabled(&self, metadata: &Metadata) -> bool { @@ -12,10 +18,20 @@ impl log::Log for SimpleLogger { fn log(&self, record: &Record) { if self.enabled(record.metadata()) { let timestamp = Local::now().to_rfc3339_opts(SecondsFormat::Secs, true); - if record.level() < Level::Info { - eprintln!("{} {} - {}", timestamp, record.level(), record.args()); - } else { - println!("{} {} - {}", timestamp, record.level(), record.args()); + let text = format!("{} {} - {}", timestamp, record.level(), record.args()); + match &self.file { + Some(file) => { + if let Ok(mut file) = file.lock() { + let _ = writeln!(file, "{text}"); + } + } + None => { + if record.level() < Level::Info { + eprintln!("{text}"); + } else { + println!("{text}"); + } + } } } } @@ -23,8 +39,23 @@ impl log::Log for SimpleLogger { fn flush(&self) {} } -static LOGGER: SimpleLogger = SimpleLogger; - -pub fn init() -> Result<(), SetLoggerError> { - log::set_logger(&LOGGER).map(|()| log::set_max_level(LevelFilter::Info)) +pub fn init(log_file: Option) -> Result<()> { + let file = match log_file { + None => None, + Some(log_file) => { + let file = OpenOptions::new() + .create(true) + .append(true) + .open(&log_file) + .with_context(|| { + format!("Failed to open the log file at '{}'", log_file.display()) + })?; + Some(Mutex::new(file)) + } + }; + let logger = SimpleLogger { file }; + log::set_boxed_logger(Box::new(logger)) + .map(|_| log::set_max_level(LevelFilter::Info)) + .with_context(|| "Failed to init logger")?; + Ok(()) } diff --git a/src/main.rs b/src/main.rs index 298de04..b32029b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -37,7 +37,6 @@ use tokio_rustls::{rustls::ServerConfig, TlsAcceptor}; #[tokio::main] async fn main() -> Result<()> { - logger::init().map_err(|e| anyhow!("Failed to init logger, {e}"))?; let cmd = build_cli(); let matches = cmd.get_matches(); if let Some(generator) = matches.get_one::("completions") { @@ -46,6 +45,7 @@ async fn main() -> Result<()> { return Ok(()); } let mut args = Args::parse(matches)?; + logger::init(args.log_file.clone()).map_err(|e| anyhow!("Failed to init logger, {e}"))?; let (new_addrs, print_addrs) = check_addrs(&args)?; args.addrs = new_addrs; let running = Arc::new(AtomicBool::new(true));