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:
@@ -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]:
|
||||
|
||||
Reference in New Issue
Block a user