ruwebframe 0.1.7

a simple webframe for rust actix-web, based on rudi and rbatis.
Documentation
// src\rutool\log_split
use std::fs::{self, File};
use std::io::{self, BufRead, BufReader, Write};

/// 日志分流器:从日志文件中提取指定级别的日志行,写入独立文件
pub struct LogSplit {
    pub source: String,
    pub target: String,
    pub level: String,
}

impl LogSplit {
    pub fn new(source: &str, target: &str) -> Self {
        LogSplit {
            source: source.to_string(),
            target: target.to_string(),
            level: "ERROR".to_string(),
        }
    }

    pub fn with_level(mut self, level: &str) -> Self {
        self.level = level.to_string();
        self
    }

    /// 覆盖模式:将匹配级别的行写入目标文件
    pub fn split(&self) -> io::Result<usize> {
        let source_file = File::open(&self.source)?;
        let reader = BufReader::new(source_file);
        let pattern = format!("[{}]", self.level.to_uppercase());

        let matched: Vec<String> = reader
            .lines()
            .filter_map(|l| l.ok())
            .filter(|l| l.contains(&pattern))
            .collect();

        let count = matched.len();
        if count > 0 {
            let mut target_file = File::create(&self.target)?;
            for line in &matched {
                writeln!(target_file, "{}", line)?;
            }
        }
        Ok(count)
    }

    /// 追加模式:将错误日志追加到目标文件(不覆盖)
    pub fn split_append(&self) -> io::Result<usize> {
        let source_file = File::open(&self.source)?;
        let reader = BufReader::new(source_file);
        let pattern = format!("[{}]", self.level.to_uppercase());

        let matched: Vec<String> = reader
            .lines()
            .filter_map(|l| l.ok())
            .filter(|l| l.contains(&pattern))
            .collect();

        let count = matched.len();
        if count > 0 {
            let mut target_file = fs::OpenOptions::new()
                .create(true)
                .append(true)
                .open(&self.target)?;
            for line in &matched {
                writeln!(target_file, "{}", line)?;
            }
        }
        Ok(count)
    }
}

/// 从日志目录中扫描所有 .log 文件,提取错误日志到统一文件
pub fn split_errors_from_dir(log_dir: &str, error_file: &str) -> io::Result<usize> {
    let mut total = 0;
    if let Ok(entries) = fs::read_dir(log_dir) {
        for entry in entries.flatten() {
            let path = entry.path();
            if path.is_file() && path.extension().map_or(false, |e| e == "log") {
                let splitter = LogSplit::new(path.to_str().unwrap_or(""), error_file);
                if let Ok(count) = splitter.split_append() {
                    total += count;
                }
            }
        }
    }
    Ok(total)
}

/// 将日志文件按 INFO/ERROR/DEBUG 三级分流到不同文件
/// 返回 (info_count, error_count, debug_count)
pub fn split_all_levels(source: &str, out_dir: &str) -> io::Result<(usize, usize, usize)> {
    let source_file = File::open(source)?;
    let reader = BufReader::new(source_file);

    let mut info_lines = Vec::new();
    let mut error_lines = Vec::new();
    let mut debug_lines = Vec::new();

    for line in reader.lines().flatten() {
        if line.contains("[ERROR]") {
            error_lines.push(line);
        } else if line.contains("[DEBUG]") {
            debug_lines.push(line);
        } else if line.contains("[INFO]") {
            info_lines.push(line);
        }
    }

    let (ic, ec, dc) = (info_lines.len(), error_lines.len(), debug_lines.len());

    if ic > 0 {
        let mut f = File::create(format!("{}/app-info.log", out_dir))?;
        for l in &info_lines { writeln!(f, "{}", l)?; }
    }
    if ec > 0 {
        let mut f = File::create(format!("{}/app-error.log", out_dir))?;
        for l in &error_lines { writeln!(f, "{}", l)?; }
    }
    if dc > 0 {
        let mut f = File::create(format!("{}/app-db.log", out_dir))?;
        for l in &debug_lines { writeln!(f, "{}", l)?; }
    }

    Ok((ic, ec, dc))
}