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"
--render-spa Serve SPA(Single Page Application)
--assets <path> Set the path to the assets directory for overriding the built-in assets
--log-format <format> Customize http log format
+ --log-file <file> Specify the file to save logs to, other than stdout/stderr
--compress <level> Set zip compress level [default: low] [possible values: none, low, medium, high]
--completions <shell> Print shell completion script for <shell> [possible values: bash, elvish, fish, powershell, zsh]
--tls-cert <path> Path to an SSL/TLS certificate to serve with HTTPS
--config <file> DUFS_CONFIG=config.yaml
-b, --bind <addrs> DUFS_BIND=0.0.0.0
-p, --port <port> DUFS_PORT=5000
- --path-prefix <path> DUFS_PATH_PREFIX=/static
+ --path-prefix <path> DUFS_PATH_PREFIX=/dufs
--hidden <value> DUFS_HIDDEN=tmp,*.log,*.lock
-a, --auth <rules> DUFS_AUTH="admin:admin@/:rw|@/"
-A, --allow-all DUFS_ALLOW_ALL=true
--render-index DUFS_RENDER_INDEX=true
--render-try-index DUFS_RENDER_TRY_INDEX=true
--render-spa DUFS_RENDER_SPA=true
- --assets <path> DUFS_ASSETS=/assets
+ --assets <path> DUFS_ASSETS=./assets
--log-format <format> DUFS_LOG_FORMAT=""
- --compress <compress> DUFS_COMPRESS="low"
+ --log-file <file> DUFS_LOG_FILE=./dufs.log
+ --compress <compress> DUFS_COMPRESS=low
--tls-cert <path> DUFS_TLS_CERT=cert.pem
--tls-key <path> DUFS_TLS_KEY=key.pem
```
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
.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")
#[serde(deserialize_with = "deserialize_log_http")]
#[serde(rename = "log-format")]
pub http_logger: HttpLogger,
+ pub log_file: Option<PathBuf>,
pub compress: Compress,
pub tls_cert: Option<PathBuf>,
pub tls_key: Option<PathBuf>,
args.http_logger = log_format.parse()?;
}
+ if let Some(log_file) = matches.get_one::<PathBuf>("log-file") {
+ args.log_file = Some(log_file.clone());
+ }
+
if let Some(compress) = matches.get_one::<Compress>("compress") {
args.compress = *compress;
}
+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<Mutex<File>>,
+}
impl log::Log for SimpleLogger {
fn enabled(&self, metadata: &Metadata) -> bool {
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}");
+ }
+ }
}
}
}
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<PathBuf>) -> 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(())
}
#[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::<Shell>("completions") {
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));