xbp 0.5.1

XBP is a build pack and deployment management tool to deploy, rust, nextjs etc and manage the NGINX configs below it
Documentation
//! Logs command module for viewing XBP logs
//!
//! This module provides functionality to view, filter, and manage XBP log files

use crate::logging::XbpLogger;

/// View XBP logs with various filtering options
pub async fn view_logs(
    command_filter: Option<String>,
    lines: Option<usize>,
    follow: bool,
    list_files: bool,
    debug: bool,
) -> Result<(), String> {
    let logger: XbpLogger = XbpLogger::new(debug).await?;

    if list_files {
        return list_log_files(&logger).await;
    }

    if follow {
        return follow_logs(&logger, command_filter, debug).await;
    }

    view_recent_logs(&logger, command_filter, lines.unwrap_or(50)).await
}

/// List all available log files
async fn list_log_files(logger: &XbpLogger) -> Result<(), String> {
    println!("\x1b[96m📋 Available XBP Log Files\x1b[0m");
    println!("Log directory: {}", logger.log_dir().display());
    println!();

    let files = logger.list_log_files().await?;

    if files.is_empty() {
        println!("\x1b[93mNo log files found.\x1b[0m");
        return Ok(());
    }

    for file in files {
        let metadata: std::fs::Metadata =
            std::fs::metadata(&file).map_err(|e| format!("Failed to read file metadata: {}", e))?;

        let size_kb: u64 = metadata.len() / 1024;
        let modified: std::time::SystemTime = metadata
            .modified()
            .map_err(|e| format!("Failed to get modification time: {}", e))?;

        let modified_str: chrono::format::DelayedFormat<chrono::format::StrftimeItems<'_>> =
            chrono::DateTime::<chrono::Local>::from(modified).format("%Y-%m-%d %H:%M:%S");

        println!(
            "  \x1b[94m{}\x1b[0m",
            file.file_name().unwrap().to_string_lossy()
        );
        println!("    Size: {} KB, Modified: {}", size_kb, modified_str);
    }

    Ok(())
}

/// View recent log entries
async fn view_recent_logs(
    logger: &XbpLogger,
    command_filter: Option<String>,
    lines: usize,
) -> Result<(), String> {
    let files = logger.list_log_files().await?;

    if files.is_empty() {
        println!("\x1b[93mNo log files found.\x1b[0m");
        return Ok(());
    }

    // Find the most recent general log file or command-specific file
    let target_file: Option<std::path::PathBuf> = if let Some(command) = &command_filter {
        files
            .iter()
            .find(|f| {
                f.file_stem()
                    .unwrap()
                    .to_string_lossy()
                    .starts_with(command)
            })
            .or_else(|| files.last())
            .cloned()
    } else {
        files
            .iter()
            .find(|f| f.file_stem().unwrap().to_string_lossy().starts_with("xbp-"))
            .or_else(|| files.last())
            .cloned()
    };

    let file: std::path::PathBuf = target_file.ok_or("No suitable log file found")?;

    println!("\x1b[96m📖 Viewing logs from: {}\x1b[0m", file.display());
    if let Some(cmd) = &command_filter {
        println!("Filter: command = {}", cmd);
    }
    println!("Showing last {} lines", lines);
    println!("{}", "".repeat(80));

    let log_lines: Vec<String> = logger.read_recent_logs(&file, lines).await?;

    for line in log_lines {
        if let Some(filter) = &command_filter {
            if !line.contains(&format!("command={}", filter)) {
                continue;
            }
        }

        // Colorize log output
        let colored_line = colorize_log_line(&line);
        println!("{}", colored_line);
    }

    Ok(())
}

/// Follow logs in real-time (simplified version)
async fn follow_logs(
    logger: &XbpLogger,
    command_filter: Option<String>,
    _debug: bool,
) -> Result<(), String> {
    println!("\x1b[96m👁️  Following XBP logs (Press Ctrl+C to stop)\x1b[0m");
    if let Some(cmd) = &command_filter {
        println!("Filter: command = {}", cmd);
    }
    println!("{}", "".repeat(80));

    // For simplicity, we'll just show the most recent logs and exit
    // In a full implementation, you'd use file watching or polling
    view_recent_logs(logger, command_filter, 20).await?;

    println!("\x1b[93mNote: Real-time following not implemented yet. Showing recent logs.\x1b[0m");

    Ok(())
}

/// Colorize log lines based on log level
fn colorize_log_line(line: &str) -> String {
    if line.contains("level=ERROR") {
        format!("\x1b[91m{}\x1b[0m", line)
    } else if line.contains("level=WARN") {
        format!("\x1b[93m{}\x1b[0m", line)
    } else if line.contains("level=SUCCESS") {
        format!("\x1b[92m{}\x1b[0m", line)
    } else if line.contains("level=DEBUG") {
        format!("\x1b[90m{}\x1b[0m", line)
    } else {
        format!("\x1b[94m{}\x1b[0m", line)
    }
}

/// Parse command line arguments for logs command
pub fn parse_logs_args(args: &[String]) -> (Option<String>, Option<usize>, bool, bool) {
    let mut command_filter: Option<String> = None;
    let mut lines: Option<usize> = None;
    let mut follow: bool = false;
    let mut list_files: bool = false;

    let mut i: usize = 0;
    while i < args.len() {
        match args[i].as_str() {
            "--command" | "-c" => {
                if i + 1 < args.len() {
                    command_filter = Some(args[i + 1].clone());
                    i += 2;
                } else {
                    i += 1;
                }
            }
            "--lines" | "-n" => {
                if i + 1 < args.len() {
                    if let Ok(n) = args[i + 1].parse::<usize>() {
                        lines = Some(n);
                    }
                    i += 2;
                } else {
                    i += 1;
                }
            }
            "--follow" | "-f" => {
                follow = true;
                i += 1;
            }
            "--list" | "-l" => {
                list_files = true;
                i += 1;
            }
            _ => {
                i += 1;
            }
        }
    }

    (command_filter, lines, follow, list_files)
}