主界面修改及代理模块初始化
This commit is contained in:
@@ -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(¤t_log) {
|
||||
if meta.len() >= self.max_file_size {
|
||||
self.rotate();
|
||||
}
|
||||
}
|
||||
|
||||
// 追加写入
|
||||
if let Ok(mut file) = OpenOptions::new()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open(¤t_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(¤t, &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(¤t).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(¤t) {
|
||||
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()
|
||||
}
|
||||
Reference in New Issue
Block a user