主界面修改及代理模块初始化

This commit is contained in:
zhongluofeng
2026-07-15 18:22:07 +08:00
parent 29a5f456cb
commit fb361aff9a
39 changed files with 4768 additions and 396 deletions
+305
View File
@@ -0,0 +1,305 @@
use chrono::Local;
use serde::{Deserialize, Serialize};
use std::fs::{self, File, OpenOptions};
use std::io::{BufRead, BufReader, Write};
use std::path::PathBuf;
/// 日志级别
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum LogLevel {
Debug,
Info,
Warn,
Error,
}
/// 单条日志记录
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LogEntry {
pub timestamp: String,
pub level: LogLevel,
pub module: String,
pub message: String,
}
/// 日志文件信息
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LogInfo {
pub log_dir: String,
pub log_files: Vec<String>,
pub total_size_bytes: u64,
pub max_file_size_bytes: u64,
pub max_files: u32,
}
/// 日志管理器 —— 负责文件轮转、写入、查询
pub struct LogManager {
log_dir: PathBuf,
max_file_size: u64,
max_files: u32,
base_name: String,
}
impl LogManager {
pub fn new(log_dir: PathBuf) -> Self {
fs::create_dir_all(&log_dir).ok();
Self {
log_dir,
max_file_size: 5 * 1024 * 1024,
max_files: 5,
base_name: "thing".to_string(),
}
}
/// 写入一条日志
pub fn log(&self, level: LogLevel, module: &str, message: &str) {
let timestamp = Local::now().format("%Y-%m-%d %H:%M:%S").to_string();
let level_str = match level {
LogLevel::Debug => "DEBUG",
LogLevel::Info => "INFO",
LogLevel::Warn => "WARN",
LogLevel::Error => "ERROR",
};
let line = format!(
"[{}] [{}] [{}] {}\n",
timestamp, level_str, module, message
);
let current_log = self.log_dir.join(format!("{}.log", self.base_name));
// 检查是否需要轮转
if let Ok(meta) = fs::metadata(&current_log) {
if meta.len() >= self.max_file_size {
self.rotate();
}
}
// 追加写入
if let Ok(mut file) = OpenOptions::new()
.create(true)
.append(true)
.open(&current_log)
{
let _ = file.write_all(line.as_bytes());
let _ = file.flush();
}
}
/// 日志文件轮转: thing.log → thing.1.log, thing.1.log → thing.2.log, …
fn rotate(&self) {
// 删除最旧的文件
let oldest = self
.log_dir
.join(format!("{}.{}.log", self.base_name, self.max_files));
fs::remove_file(&oldest).ok();
// 依次重命名
for i in (1..self.max_files).rev() {
let src = self
.log_dir
.join(format!("{}.{}.log", self.base_name, i));
let dst = self
.log_dir
.join(format!("{}.{}.log", self.base_name, i + 1));
fs::rename(&src, &dst).ok();
}
// thing.log → thing.1.log
let current = self.log_dir.join(format!("{}.log", self.base_name));
let first_rotated = self
.log_dir
.join(format!("{}.1.log", self.base_name));
fs::rename(&current, &first_rotated).ok();
}
/// 读取日志(支持按模块/级别过滤、条数限制)
pub fn get_logs(
&self,
module: Option<&str>,
level: Option<LogLevel>,
limit: Option<usize>,
) -> Vec<LogEntry> {
let mut entries: Vec<LogEntry> = Vec::new();
// 收集所有日志文件(包括轮转文件)
let mut log_files: Vec<PathBuf> = Vec::new();
let current = self.log_dir.join(format!("{}.log", self.base_name));
if current.exists() {
log_files.push(current);
}
for i in 1..=self.max_files {
let rotated = self
.log_dir
.join(format!("{}.{}.log", self.base_name, i));
if rotated.exists() {
log_files.push(rotated);
}
}
for path in &log_files {
if let Ok(file) = File::open(path) {
for line in BufReader::new(file).lines().flatten() {
if let Some(entry) = Self::parse_line(&line) {
if let Some(ref m) = module {
if entry.module != *m {
continue;
}
}
if let Some(ref l) = level {
if entry.level != *l {
continue;
}
}
entries.push(entry);
}
}
}
}
// 按时间戳降序(最新在前)
entries.sort_by(|a, b| b.timestamp.cmp(&a.timestamp));
if let Some(n) = limit {
entries.truncate(n);
}
entries
}
/// 解析一行日志: `[YYYY-MM-DD HH:MM:SS] [LEVEL] [MODULE] message`
fn parse_line(line: &str) -> Option<LogEntry> {
let line = line.trim();
if line.len() < 22 || !line.starts_with('[') {
return None;
}
let ts_end = line[1..].find(']')? + 1;
let timestamp = line[1..ts_end].to_string();
let rest = line[ts_end + 1..].trim();
if !rest.starts_with('[') {
return None;
}
let lv_end = rest[1..].find(']')? + 1;
let level_str = &rest[1..lv_end];
let level = match level_str {
"DEBUG" => LogLevel::Debug,
"INFO" => LogLevel::Info,
"WARN" => LogLevel::Warn,
"ERROR" => LogLevel::Error,
_ => return None,
};
let rest = rest[lv_end + 1..].trim();
if !rest.starts_with('[') {
return None;
}
let mod_end = rest[1..].find(']')? + 1;
let module = rest[1..mod_end].to_string();
let message = rest[mod_end + 1..].trim().to_string();
Some(LogEntry {
timestamp,
level,
module,
message,
})
}
/// 清空所有日志文件
pub fn clear_logs(&self) -> Result<(), String> {
let current = self.log_dir.join(format!("{}.log", self.base_name));
fs::remove_file(&current).map_err(|e| e.to_string())?;
for i in 1..=self.max_files {
let rotated = self
.log_dir
.join(format!("{}.{}.log", self.base_name, i));
fs::remove_file(&rotated).ok();
}
Ok(())
}
/// 获取日志系统信息
pub fn get_info(&self) -> LogInfo {
let mut log_files: Vec<String> = Vec::new();
let mut total_size: u64 = 0;
let current = self.log_dir.join(format!("{}.log", self.base_name));
if current.exists() {
if let Ok(meta) = fs::metadata(&current) {
total_size += meta.len();
}
log_files.push(format!("{}.log", self.base_name));
}
for i in 1..=self.max_files {
let rotated = self
.log_dir
.join(format!("{}.{}.log", self.base_name, i));
if rotated.exists() {
if let Ok(meta) = fs::metadata(&rotated) {
total_size += meta.len();
}
log_files.push(format!("{}.{}.log", self.base_name, i));
}
}
LogInfo {
log_dir: self.log_dir.to_string_lossy().to_string(),
log_files,
total_size_bytes: total_size,
max_file_size_bytes: self.max_file_size,
max_files: self.max_files,
}
}
}
// ===== Tauri 命令 =====
#[tauri::command]
pub fn log_message(
state: tauri::State<'_, LogManager>,
level: String,
module: String,
message: String,
) -> Result<(), String> {
let level = match level.as_str() {
"debug" => LogLevel::Debug,
"info" => LogLevel::Info,
"warn" => LogLevel::Warn,
"error" => LogLevel::Error,
_ => return Err(format!("无效的日志级别: {}", level)),
};
state.log(level, &module, &message);
Ok(())
}
#[tauri::command]
pub fn get_logs(
state: tauri::State<'_, LogManager>,
module: Option<String>,
level: Option<String>,
limit: Option<usize>,
) -> Vec<LogEntry> {
let level = level.and_then(|l| match l.as_str() {
"debug" => Some(LogLevel::Debug),
"info" => Some(LogLevel::Info),
"warn" => Some(LogLevel::Warn),
"error" => Some(LogLevel::Error),
_ => None,
});
state.get_logs(module.as_deref(), level, limit)
}
#[tauri::command]
pub fn clear_logs(state: tauri::State<'_, LogManager>) -> Result<(), String> {
state.clear_logs()
}
#[tauri::command]
pub fn get_log_info(state: tauri::State<'_, LogManager>) -> LogInfo {
state.get_info()
}