refactor(core,shim): 统一基于 thiserror 的错误体系

- 移除 anyhow,使用 thiserror 定义强类型 Error 枚举
- 支持 #[from] 自动转换 mlua::Error,关键路径保留源码与上下文
- LuaRuntimeBuilder 采用惰性错误收集,简化链式初始化调用
- MirrorRegistry 实现规范化 FromLuaConversionError 错误转换
This commit is contained in:
2026-09-10 14:33:54 +08:00
parent 94b4c23299
commit 38dabd2ace
22 changed files with 686 additions and 482 deletions
+65 -63
View File
@@ -1,7 +1,7 @@
use crate::Layout;
use serde::Deserialize;
use std::convert::Infallible;
use std::fs;
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use tracing_appender::non_blocking::{NonBlocking, WorkerGuard};
use tracing_subscriber::layer::SubscriberExt;
@@ -61,41 +61,50 @@ impl FromStr for LogLevel {
}
}
#[derive(Debug, Deserialize, Default)]
pub struct Logger {
#[serde(default)]
/// 日志配置模型(对应 mirror.ini 的纯强类型表示)
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct LogConfig {
pub level: LogLevel,
pub log_dir: Option<PathBuf>,
}
impl Logger {
impl LogConfig {
/// 零依赖解析 ini / key-value 文本内容
pub fn parse_ini(content: &str) -> Self {
let mut logger = Self::default();
/// 计算日志目录的实际物理路径(相对路径基于 base_dir 拼接)
pub fn resolve_log_dir(&self, base_dir: &Path) -> PathBuf {
match &self.log_dir {
Some(p) if p.is_absolute() => p.clone(),
Some(p) => base_dir.join(p),
None => base_dir.join("logs"),
}
}
}
// 协议 1: 反序列化 (INI 文本 -> LogConfig)
impl FromStr for LogConfig {
type Err = Infallible;
fn from_str(content: &str) -> Result<Self, Self::Err> {
let mut config = Self::default();
for line in content.lines() {
let line = line.trim();
// 跳过空行、注释行 (# 或 ;) 和 section 头 ([...])
if line.is_empty()
|| line.starts_with('#')
|| line.starts_with(';')
|| line.starts_with('[')
{
// 跳过空行、注释 (#, ;) 与 section 标头 ([...])
if line.is_empty() || line.starts_with(['#', ';', '[']) {
continue;
}
if let Some((key, val)) = line.split_once('=') {
let key = key.trim();
let val = val.trim().trim_matches('"').trim_matches('\'');
let val = val.trim().trim_matches(['"', '\'']);
match key {
"level" => {
if let Ok(lvl) = LogLevel::from_str(val) {
logger.level = lvl;
config.level = lvl;
}
}
"log_dir" => {
if !val.is_empty() {
logger.log_dir = Some(PathBuf::from(val));
config.log_dir = Some(PathBuf::from(val));
}
}
_ => {}
@@ -103,9 +112,24 @@ impl Logger {
}
}
logger
Ok(config)
}
}
// 协议 2: 序列化 (LogConfig -> INI 文本)
impl std::fmt::Display for LogConfig {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let dir_str = self
.log_dir
.as_deref()
.and_then(|p| p.to_str())
.unwrap_or("logs");
writeln!(f, "level = \"{}\"", self.level.as_str())?;
writeln!(f, "log_dir = \"{}\"", dir_str)
}
}
/// 基于 Layout 自动寻址 mirror.toml 并初始化日志系统。
/// 仅在开启详细日志(如 info/debug/trace)时激活异步文件记录并返回 `WorkerGuard`。
pub fn init_logging_from(layout: &Layout) -> Option<WorkerGuard> {
@@ -113,16 +137,11 @@ pub fn init_logging_from(layout: &Layout) -> Option<WorkerGuard> {
// let config_path = layout.base_dir.join("mirror.toml");
let config_path = layout.base_dir.join("mirror.ini");
// 读取配置文件(如不存在或解析失败,降级回退到默认设置)
let config = if config_path.exists() {
fs::read_to_string(&config_path)
.map(|s| Logger::parse_ini(&s))
// .ok()
// .and_then(|s| toml::from_str::<Logger>(&s).ok())
.unwrap_or_default()
} else {
Logger::default()
};
// 1. 读取并反序列化配置文件(缺失或异常时安全降级为 Default)
let config: LogConfig = fs::read_to_string(&config_path)
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or_default();
// 1. 优先读取环境变量 MIRROR_LOG_LEVEL,解析失败或未设置则退回到 config.level
let effective_level = std::env::var("MIRROR_LOG_LEVEL")
@@ -136,47 +155,30 @@ pub fn init_logging_from(layout: &Layout) -> Option<WorkerGuard> {
// 基础终端输出层
let stderr_layer = fmt::layer().with_writer(std::io::stderr).with_target(false);
// 2. 根据强类型枚举判断是否激活落盘模式
if effective_level.is_verbose() {
let (non_blocking, guard) = create_file_appender(layout, config.log_dir);
let file_layer = fmt::layer()
// 3. 动态配置落盘文件层(仅在 verbose 级别启用)
let (file_layer, guard) = if effective_level.is_verbose() {
let (non_blocking, guard) = create_file_appender(&config.resolve_log_dir(&layout.base_dir));
let layer = fmt::layer()
.with_writer(non_blocking)
.with_ansi(false)
.with_target(false);
let _ = tracing_subscriber::registry()
.with(filter)
.with(stderr_layer)
.with(file_layer)
.try_init();
Some(guard)
(Some(layer), Some(guard))
} else {
let _ = tracing_subscriber::registry()
.with(filter)
.with(stderr_layer)
.try_init();
None
}
}
fn create_file_appender(
layout: &Layout,
log_dir_override: Option<PathBuf>,
) -> (NonBlocking, WorkerGuard) {
let output_dir = match log_dir_override {
Some(user_path) => {
if user_path.is_relative() {
layout.base_dir.join(user_path)
} else {
user_path
}
}
None => layout.base_dir.join("logs"),
(None, None)
};
if let Err(err) = fs::create_dir_all(&output_dir) {
// 4. 统一组装并初始化 Subscriber(Option<Layer> 天然实现了 Layer trait)
let _ = tracing_subscriber::registry()
.with(filter)
.with(stderr_layer)
.with(file_layer)
.try_init();
guard
}
fn create_file_appender(output_dir: &Path) -> (NonBlocking, WorkerGuard) {
if let Err(err) = fs::create_dir_all(output_dir) {
eprintln!("[WARN] 创建日志目录 {} 失败: {}", output_dir.display(), err);
}