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)
|
STATS_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
|
||||||
# 锁文件路径
|
|
||||||
LOCK_FILE = STATS_DIR / "stats_data.lock"
|
|
||||||
|
|
||||||
|
|
||||||
def _acquire_lock(lock_file_path: Path):
|
def _acquire_lock(lock_file_path: Path):
|
||||||
"""获取文件锁"""
|
"""获取文件锁
|
||||||
lock_file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
lock_fd = open(lock_file_path, 'w')
|
Raises:
|
||||||
fcntl.flock(lock_fd.fileno(), fcntl.LOCK_EX)
|
OSError: 无法创建或锁定文件时
|
||||||
return lock_fd
|
"""
|
||||||
|
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):
|
def _release_lock(lock_fd):
|
||||||
"""释放文件锁"""
|
"""释放文件锁"""
|
||||||
fcntl.flock(lock_fd.fileno(), fcntl.LOCK_UN)
|
if lock_fd is None:
|
||||||
lock_fd.close()
|
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]:
|
def _load_stats_data() -> Dict[str, Any]:
|
||||||
"""加载统计数据(带锁保护)"""
|
"""加载统计数据(带锁保护)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict: 统计数据,加载失败时返回空结构
|
||||||
|
"""
|
||||||
if not STATS_FILE.exists():
|
if not STATS_FILE.exists():
|
||||||
return {"events": [], "metadata": {"created_at": datetime.now().isoformat()}}
|
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:
|
with open(STATS_FILE, 'r', encoding='utf-8') as f:
|
||||||
return json.load(f)
|
return json.load(f)
|
||||||
except json.JSONDecodeError as e:
|
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')
|
backup_path = STATS_FILE.with_suffix(f'.corrupted.{datetime.now().strftime("%Y%m%d_%H%M%S")}.bak')
|
||||||
try:
|
try:
|
||||||
STATS_FILE.rename(backup_path)
|
STATS_FILE.rename(backup_path)
|
||||||
except OSError:
|
logger.info(f"已备份损坏文件到: {backup_path}")
|
||||||
pass # 备份失败时忽略
|
except OSError as backup_error:
|
||||||
|
logger.error(f"备份损坏文件失败: {backup_error}")
|
||||||
return {"events": [], "metadata": {"created_at": datetime.now().isoformat()}}
|
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()}}
|
return {"events": [], "metadata": {"created_at": datetime.now().isoformat()}}
|
||||||
finally:
|
finally:
|
||||||
if lock_fd:
|
_release_lock(lock_fd)
|
||||||
_release_lock(lock_fd)
|
|
||||||
|
|
||||||
|
|
||||||
def _atomic_write(data: Dict[str, Any]):
|
def _atomic_write(data: Dict[str, Any]):
|
||||||
"""原子写入统计数据(带锁保护)"""
|
"""原子写入统计数据(带锁保护)
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
OSError: 文件操作失败时
|
||||||
|
TypeError: 数据无法序列化为 JSON 时
|
||||||
|
"""
|
||||||
_ensure_stats_dir()
|
_ensure_stats_dir()
|
||||||
|
|
||||||
lock_fd = None
|
lock_fd = None
|
||||||
@@ -103,12 +130,20 @@ def _atomic_write(data: Dict[str, Any]):
|
|||||||
# 原子重命名
|
# 原子重命名
|
||||||
os.replace(temp_path, STATS_FILE)
|
os.replace(temp_path, STATS_FILE)
|
||||||
temp_path = None # 防止在 finally 中再次删除
|
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:
|
finally:
|
||||||
if lock_fd:
|
_release_lock(lock_fd)
|
||||||
_release_lock(lock_fd)
|
|
||||||
# 清理临时文件
|
# 清理临时文件
|
||||||
if temp_path and os.path.exists(temp_path):
|
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]:
|
def _filter_sensitive_data(data: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
|||||||
Reference in New Issue
Block a user