smirrors 0.1.0

Automatic mirror list updater for Linux distributions
Documentation
use anyhow::{Context, Result};
use std::path::PathBuf;
use tracing::Level;
use tracing_subscriber::{
    fmt::{self, format::FmtSpan},
    layer::SubscriberExt,
    util::SubscriberInitExt,
    EnvFilter, Layer,
};

/// Initialize the logging system with specified level and format
pub fn init_logger(level: &str, format: &str, log_file: Option<PathBuf>) -> Result<()> {
    let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| {
        EnvFilter::new(level)
    });

    let registry = tracing_subscriber::registry().with(filter);

    match format {
        "json" => {
            if let Some(file_path) = log_file {
                let file = std::fs::OpenOptions::new()
                    .create(true)
                    .append(true)
                    .open(&file_path)
                    .context("Failed to open log file")?;

                let file_layer = fmt::layer()
                    .json()
                    .with_writer(std::sync::Arc::new(file))
                    .with_span_events(FmtSpan::CLOSE);

                registry.with(file_layer).init();
            } else {
                let stdout_layer = fmt::layer()
                    .json()
                    .with_span_events(FmtSpan::CLOSE);

                registry.with(stdout_layer).init();
            }
        }
        "compact" => {
            if let Some(file_path) = log_file {
                let file = std::fs::OpenOptions::new()
                    .create(true)
                    .append(true)
                    .open(&file_path)
                    .context("Failed to open log file")?;

                let file_layer = fmt::layer()
                    .compact()
                    .with_writer(std::sync::Arc::new(file))
                    .with_span_events(FmtSpan::CLOSE);

                registry.with(file_layer).init();
            } else {
                let stdout_layer = fmt::layer()
                    .compact()
                    .with_span_events(FmtSpan::CLOSE);

                registry.with(stdout_layer).init();
            }
        }
        _ => {
            if let Some(file_path) = log_file {
                let file = std::fs::OpenOptions::new()
                    .create(true)
                    .append(true)
                    .open(&file_path)
                    .context("Failed to open log file")?;

                let file_layer = fmt::layer()
                    .pretty()
                    .with_writer(std::sync::Arc::new(file))
                    .with_span_events(FmtSpan::CLOSE);

                registry.with(file_layer).init();
            } else {
                let stdout_layer = fmt::layer()
                    .pretty()
                    .with_span_events(FmtSpan::CLOSE);

                registry.with(stdout_layer).init();
            }
        }
    }

    Ok(())
}

/// Parse log level string to tracing Level
pub fn parse_level(level: &str) -> Level {
    match level.to_lowercase().as_str() {
        "trace" => Level::TRACE,
        "debug" => Level::DEBUG,
        "info" => Level::INFO,
        "warn" | "warning" => Level::WARN,
        "error" => Level::ERROR,
        _ => Level::INFO,
    }
}

/// Get default log file path based on privileges
pub fn default_log_path() -> Result<PathBuf> {
    if nix::unistd::geteuid().is_root() {
        Ok(PathBuf::from("/var/log/smirrors/smirrors.log"))
    } else {
        let dirs = directories::ProjectDirs::from("com", "smirrors", "smirrors")
            .context("Could not determine project directories")?;

        let log_dir = dirs.data_local_dir().join("logs");
        std::fs::create_dir_all(&log_dir)
            .context("Failed to create log directory")?;

        Ok(log_dir.join("smirrors.log"))
    }
}

/// Initialize logger for daemon/service mode
pub fn init_service_logger() -> Result<()> {
    let log_path = default_log_path()?;

    // Ensure log directory exists
    if let Some(parent) = log_path.parent() {
        std::fs::create_dir_all(parent)
            .context("Failed to create log directory")?;
    }

    init_logger("info", "json", Some(log_path))
}

/// Initialize logger for CLI mode (no file output)
pub fn init_cli_logger(level: &str) -> Result<()> {
    init_logger(level, "pretty", None)
}