fix(stats): 细化异常处理

- _acquire_lock(): 添加 OSError/IOError 异常处理和日志
- _release_lock(): 添加空值检查和异常捕获
- _load_stats_data():
  - 分离 JSONDecodeError、IOError/OSError 和其他异常
  - 添加详细的日志记录
- _atomic_write():
  - 分离 OSError/IOError 和 JSON 序列化异常
  - 清理临时文件时添加异常捕获
- 移除重复的 LOCK_FILE 定义
This commit is contained in:
2026-03-09 14:52:32 +08:00
parent 63f2df7f18
commit f9cc8bb82a
+56 -21
View File
@@ -37,26 +37,43 @@ def _ensure_stats_dir():
STATS_DIR.mkdir(parents=True, exist_ok=True)
# 锁文件路径
LOCK_FILE = STATS_DIR / "stats_data.lock"
def _acquire_lock(lock_file_path: Path):
"""获取文件锁"""
lock_file_path.parent.mkdir(parents=True, exist_ok=True)
lock_fd = open(lock_file_path, 'w')
fcntl.flock(lock_fd.fileno(), fcntl.LOCK_EX)
return lock_fd
"""获取文件锁
Raises:
OSError: 无法创建或锁定文件时
"""
try:
lock_file_path.parent.mkdir(parents=True, exist_ok=True)
lock_fd = open(lock_file_path, 'w')
fcntl.flock(lock_fd.fileno(), fcntl.LOCK_EX)
return lock_fd
except (OSError, IOError) as e:
logger.error(f"获取文件锁失败: {e}")
raise
def _release_lock(lock_fd):
"""释放文件锁"""
fcntl.flock(lock_fd.fileno(), fcntl.LOCK_UN)
lock_fd.close()
if lock_fd is None:
return
try:
fcntl.flock(lock_fd.fileno(), fcntl.LOCK_UN)
lock_fd.close()
except (OSError, IOError) as e:
logger.warning(f"释放文件锁时发生错误: {e}")
try:
lock_fd.close()
except Exception:
pass
def _load_stats_data() -> Dict[str, Any]:
"""加载统计数据(带锁保护)"""
"""加载统计数据(带锁保护)
Returns:
Dict: 统计数据,加载失败时返回空结构
"""
if not STATS_FILE.exists():
return {"events": [], "metadata": {"created_at": datetime.now().isoformat()}}
@@ -66,22 +83,32 @@ def _load_stats_data() -> Dict[str, Any]:
with open(STATS_FILE, 'r', encoding='utf-8') as f:
return json.load(f)
except json.JSONDecodeError as e:
logger.warning(f"JSON 解析失败,将备份损坏文件: {e}")
# 备份损坏的文件
backup_path = STATS_FILE.with_suffix(f'.corrupted.{datetime.now().strftime("%Y%m%d_%H%M%S")}.bak')
try:
STATS_FILE.rename(backup_path)
except OSError:
pass # 备份失败时忽略
logger.info(f"已备份损坏文件到: {backup_path}")
except OSError as backup_error:
logger.error(f"备份损坏文件失败: {backup_error}")
return {"events": [], "metadata": {"created_at": datetime.now().isoformat()}}
except IOError:
except (OSError, IOError) as e:
logger.error(f"读取统计文件失败: {e}")
return {"events": [], "metadata": {"created_at": datetime.now().isoformat()}}
except Exception as e:
logger.error(f"加载统计数据时发生未知错误: {e}")
return {"events": [], "metadata": {"created_at": datetime.now().isoformat()}}
finally:
if lock_fd:
_release_lock(lock_fd)
_release_lock(lock_fd)
def _atomic_write(data: Dict[str, Any]):
"""原子写入统计数据(带锁保护)"""
"""原子写入统计数据(带锁保护)
Raises:
OSError: 文件操作失败时
TypeError: 数据无法序列化为 JSON 时
"""
_ensure_stats_dir()
lock_fd = None
@@ -103,12 +130,20 @@ def _atomic_write(data: Dict[str, Any]):
# 原子重命名
os.replace(temp_path, STATS_FILE)
temp_path = None # 防止在 finally 中再次删除
except (OSError, IOError) as e:
logger.error(f"写入统计文件失败: {e}")
raise
except (TypeError, ValueError) as e:
logger.error(f"JSON 序列化失败: {e}")
raise
finally:
if lock_fd:
_release_lock(lock_fd)
_release_lock(lock_fd)
# 清理临时文件
if temp_path and os.path.exists(temp_path):
os.unlink(temp_path)
try:
os.unlink(temp_path)
except OSError as e:
logger.warning(f"清理临时文件失败: {e}")
def _filter_sensitive_data(data: Dict[str, Any]) -> Dict[str, Any]: