diff --git a/app/Language/obtain_language.py b/app/Language/obtain_language.py index 2800ea7e..4d56a2cd 100644 --- a/app/Language/obtain_language.py +++ b/app/Language/obtain_language.py @@ -63,7 +63,7 @@ def run(self): value = self._read_language_value() self.finished.emit(value) except Exception as e: - logger.error(f"读取语言内容失败: {e}") + logger.exception(f"读取语言内容失败: {e}") self.finished.emit(None) def _read_language_value(self): diff --git a/app/common/IPC_URL/csharp_ipc_handler.py b/app/common/IPC_URL/csharp_ipc_handler.py index a9d6ccd1..7916e0fd 100644 --- a/app/common/IPC_URL/csharp_ipc_handler.py +++ b/app/common/IPC_URL/csharp_ipc_handler.py @@ -89,7 +89,7 @@ def start_ipc_client(self) -> bool: return True except Exception as e: self.is_running = False - logger.error(f"启动 C# IPC 客户端失败: {e}") + logger.exception(f"启动 C# IPC 客户端失败: {e}") return False def stop_ipc_client(self): @@ -195,7 +195,7 @@ def get_on_class_left_time(self) -> int: return total_seconds except Exception as e: - logger.error(f"获取距离上课时间失败: {e}") + logger.exception(f"获取距离上课时间失败: {e}") return 0 def get_current_class_info(self) -> dict: @@ -230,7 +230,7 @@ def get_current_class_info(self) -> dict: return {"name": class_name} except Exception as e: - logger.error(f"从 ClassIsland 获取课程信息失败: {e}") + logger.exception(f"从 ClassIsland 获取课程信息失败: {e}") return {} def get_next_class_info(self) -> dict: @@ -265,7 +265,7 @@ def get_next_class_info(self) -> dict: return {"name": class_name} except Exception as e: - logger.error(f"从 ClassIsland 获取下一节课信息失败: {e}") + logger.exception(f"从 ClassIsland 获取下一节课信息失败: {e}") return {} @staticmethod @@ -335,7 +335,7 @@ async def client(): except asyncio.CancelledError: pass except Exception as e: - logger.error(f"C# IPC 客户端循环出错: {e}") + logger.exception(f"C# IPC 客户端循环出错: {e}") finally: self.loop.close() self.loop = None diff --git a/app/common/IPC_URL/protocol_manager.py b/app/common/IPC_URL/protocol_manager.py index 07750151..ca41c1e1 100644 --- a/app/common/IPC_URL/protocol_manager.py +++ b/app/common/IPC_URL/protocol_manager.py @@ -39,7 +39,7 @@ def register_protocol(self) -> bool: elif self.is_linux: return self._register_linux_protocol() else: - logger.error(f"不支持的操作系统: {sys.platform}") + logger.exception(f"不支持的操作系统: {sys.platform}") return False def unregister_protocol(self) -> bool: @@ -54,7 +54,7 @@ def unregister_protocol(self) -> bool: elif self.is_linux: return self._unregister_linux_protocol() else: - logger.error(f"不支持的操作系统: {sys.platform}") + logger.exception(f"不支持的操作系统: {sys.platform}") return False def is_protocol_registered(self) -> bool: @@ -81,7 +81,7 @@ def _register_windows_protocol(self) -> bool: return self._register_windows_protocol_current_user(exe_path) except Exception as e: - logger.error(f"Windows协议注册失败: {e}") + logger.exception(f"Windows协议注册失败: {e}") return False def _register_windows_protocol_current_user(self, exe_path: str) -> bool: @@ -105,7 +105,7 @@ def _register_windows_protocol_current_user(self, exe_path: str) -> bool: return True except Exception as e: - logger.error(f"Windows当前用户协议注册失败: {e}") + logger.exception(f"Windows当前用户协议注册失败: {e}") return False def _unregister_windows_protocol(self) -> bool: @@ -115,7 +115,7 @@ def _unregister_windows_protocol(self) -> bool: return self._unregister_windows_protocol_current_user() except Exception as e: - logger.error(f"Windows协议注销失败: {e}") + logger.exception(f"Windows协议注销失败: {e}") return False def _unregister_windows_protocol_current_user(self) -> bool: @@ -138,7 +138,7 @@ def _unregister_windows_protocol_current_user(self) -> bool: return True except Exception as e: - logger.error(f"Windows当前用户协议注销失败: {e}") + logger.exception(f"Windows当前用户协议注销失败: {e}") return False def _is_windows_protocol_registered(self) -> bool: @@ -177,7 +177,7 @@ def _register_linux_protocol(self) -> bool: return True except Exception as e: - logger.error(f"Linux协议注册失败: {e}") + logger.exception(f"Linux协议注册失败: {e}") return False def _unregister_linux_protocol(self) -> bool: @@ -194,7 +194,7 @@ def _unregister_linux_protocol(self) -> bool: return True except Exception as e: - logger.error(f"Linux协议注销失败: {e}") + logger.exception(f"Linux协议注销失败: {e}") return False def _is_linux_protocol_registered(self) -> bool: diff --git a/app/common/IPC_URL/security_verifier.py b/app/common/IPC_URL/security_verifier.py index 6570c99d..929c6687 100644 --- a/app/common/IPC_URL/security_verifier.py +++ b/app/common/IPC_URL/security_verifier.py @@ -114,7 +114,7 @@ def verify(self, verification_data: Dict[str, Any]) -> bool: return result except Exception as e: - logger.error(f"验证过程出错: {e}") + logger.exception(f"验证过程出错: {e}") return False def _check_attempt_limit(self, command: str) -> bool: @@ -451,7 +451,7 @@ def _perform_verification( result = verifier.verify(verification_data) results.append(result) except Exception as e: - logger.error(f"验证器执行失败: {e}") + logger.exception(f"验证器执行失败: {e}") results.append(False) if self.require_all: diff --git a/app/common/IPC_URL/url_command_handler.py b/app/common/IPC_URL/url_command_handler.py index 5baa8530..f5e4e980 100644 --- a/app/common/IPC_URL/url_command_handler.py +++ b/app/common/IPC_URL/url_command_handler.py @@ -119,7 +119,7 @@ def handle_url_command( return result except Exception as e: - logger.error(f"URL命令处理失败: {e}") + logger.exception(f"URL命令处理失败: {e}") return { "status": "error", "message": f"命令处理失败: {str(e)}", @@ -163,7 +163,7 @@ def handle_ipc_command(self, message: Dict[str, Any]) -> Dict[str, Any]: } except Exception as e: - logger.error(f"IPC命令处理失败: {e}") + logger.exception(f"IPC命令处理失败: {e}") return {"status": "error", "message": f"IPC命令处理失败: {str(e)}"} def _parse_url(self, url: str) -> tuple: @@ -318,7 +318,7 @@ def execute_command(): logger.debug(f"验证后执行命令完成: {command}, 结果: {result}") return result except Exception as e: - logger.error(f"验证后执行命令失败: {command}, 错误: {e}") + logger.exception(f"验证后执行命令失败: {command}, 错误: {e}") # 调用验证窗口 create_verify_password_window(on_verified=execute_command, operation_type=op) @@ -345,7 +345,7 @@ def _execute_command(self, command: str, params: Dict[str, Any]) -> Dict[str, An logger.debug(f"命令执行成功: {command}, 结果: {result}") return result except Exception as e: - logger.error(f"命令执行失败: {command}, 错误: {e}") + logger.exception(f"命令执行失败: {command}, 错误: {e}") return { "status": "error", "message": f"命令执行失败: {str(e)}", @@ -365,7 +365,7 @@ def _execute_command(self, command: str, params: Dict[str, Any]) -> Dict[str, An ) return result except Exception as e: - logger.error( + logger.exception( f"模糊匹配命令执行失败: {matched_command}, 错误: {e}" ) return { @@ -383,7 +383,7 @@ def _execute_command(self, command: str, params: Dict[str, Any]) -> Dict[str, An } except Exception as e: - logger.error(f"命令执行失败: {e}") + logger.exception(f"命令执行失败: {e}") return { "status": "error", "message": f"命令执行失败: {str(e)}", @@ -724,7 +724,7 @@ def verify_and_execute( ) if not self.security_verifier: - logger.error("安全验证器未配置") + logger.exception("安全验证器未配置") return {"success": False, "error": "安全验证器未配置"} # 执行安全验证 diff --git a/app/common/IPC_URL/url_ipc_handler.py b/app/common/IPC_URL/url_ipc_handler.py index f6d1dbba..209d9541 100644 --- a/app/common/IPC_URL/url_ipc_handler.py +++ b/app/common/IPC_URL/url_ipc_handler.py @@ -52,7 +52,7 @@ def register_url_protocol(self) -> bool: try: return self.protocol_manager.register_protocol() except Exception as e: - logger.error(f"注册URL协议失败: {e}") + logger.exception(f"注册URL协议失败: {e}") return False def unregister_url_protocol(self) -> bool: @@ -65,7 +65,7 @@ def unregister_url_protocol(self) -> bool: try: return self.protocol_manager.unregister_protocol() except Exception as e: - logger.error(f"注销URL协议失败: {e}") + logger.exception(f"注销URL协议失败: {e}") return False def is_protocol_registered(self) -> bool: @@ -97,7 +97,7 @@ def start_ipc_server(self, port: int = 0) -> bool: self.is_running = True return True except Exception as e: - logger.error(f"启动IPC服务器失败: {e}") + logger.exception(f"启动IPC服务器失败: {e}") return False def stop_ipc_server(self): @@ -140,11 +140,11 @@ def _run_server(self, port: int): continue except Exception as e: if self.is_running: - logger.error(f"IPC服务器错误: {e}") + logger.exception(f"IPC服务器错误: {e}") break except Exception as e: - logger.error(f"IPC服务器启动错误: {e}") + logger.exception(f"IPC服务器启动错误: {e}") finally: if "server_socket" in locals(): server_socket.close() @@ -158,7 +158,7 @@ def _handle_client(self, client_socket: socket.socket, address: tuple): response = self._process_message(message) client_socket.send(json.dumps(response).encode("utf-8")) except Exception as e: - logger.error(f"处理IPC消息错误: {e}") + logger.exception(f"处理IPC消息错误: {e}") finally: client_socket.close() @@ -188,7 +188,7 @@ def _process_message(self, message: Dict[str, Any]) -> Dict[str, Any]: "type": message_type, "error": str(e), } - logger.error(f"消息处理失败 - 类型: {message_type}, 错误: {e}") + logger.exception(f"消息处理失败 - 类型: {message_type}, 错误: {e}") return error_response else: unknown_response = { @@ -224,7 +224,7 @@ def _handle_url_message(self, payload: Dict[str, Any]) -> Dict[str, Any]: logger.info(f"URL命令执行成功: {url}, 结果: {result}") return {"success": True, "result": result} except Exception as e: - logger.error(f"URL命令执行失败: {url}, 错误: {e}") + logger.exception(f"URL命令执行失败: {url}, 错误: {e}") return {"success": False, "error": str(e)} def register_message_handler(self, message_type: str, handler: Callable): @@ -262,7 +262,7 @@ def send_ipc_message( return json.loads(response_data) except Exception as e: - logger.error(f"发送IPC消息失败: {e}") + logger.exception(f"发送IPC消息失败: {e}") return None def _save_port_config(self, port: int): @@ -286,7 +286,7 @@ def load_port_config(self) -> Optional[int]: config = json.load(f) return config.get("port") except Exception as e: - logger.error(f"加载端口配置失败: {e}") + logger.exception(f"加载端口配置失败: {e}") return None @@ -328,7 +328,7 @@ def handle_url_args(self, url: str) -> Dict[str, Any]: return result except Exception as e: - logger.error(f"URL参数解析失败: {url}, 错误: {e}") + logger.exception(f"URL参数解析失败: {url}, 错误: {e}") return {"success": False, "error": str(e)} def execute_url_command( @@ -361,7 +361,7 @@ def execute_url_command( logger.info(f"URL命令执行成功: {url}, 结果: {result}") return {"success": True, "result": result} except Exception as e: - logger.error(f"URL命令执行失败: {url}, 错误: {e}") + logger.exception(f"URL命令执行失败: {url}, 错误: {e}") return {"success": False, "error": str(e)} def get_available_commands(self) -> Dict[str, Any]: diff --git a/app/common/behind_scenes/behind_scenes_utils.py b/app/common/behind_scenes/behind_scenes_utils.py index 7d4d4f4d..4a488642 100644 --- a/app/common/behind_scenes/behind_scenes_utils.py +++ b/app/common/behind_scenes/behind_scenes_utils.py @@ -39,7 +39,7 @@ def get_behind_scenes_settings(use_cache=True): BehindScenesUtils._cache_timestamp = current_time return settings except Exception as e: - logger.error(f"读取内幕设置失败: {e}") + logger.exception(f"读取内幕设置失败: {e}") return {} @staticmethod @@ -79,7 +79,7 @@ def get_probability_settings(name, mode, pool_name=None): return {"enabled": False, "probability": 1.0} return {"enabled": False, "probability": 1.0} except Exception as e: - logger.error(f"获取概率设置失败: {e}") + logger.exception(f"获取概率设置失败: {e}") return {"enabled": False, "probability": 1.0} @staticmethod @@ -176,7 +176,7 @@ def apply_probability_weights( return filtered_students, weights except Exception as e: - logger.error(f"应用内幕设置失败: {e}") + logger.exception(f"应用内幕设置失败: {e}") return students_dict_list, [1.0] * len(students_dict_list) @staticmethod @@ -239,7 +239,7 @@ def apply_probability_weights_to_items(items, mode, pool_name): return filtered_items, weights except Exception as e: - logger.error(f"应用内幕设置失败: {e}") + logger.exception(f"应用内幕设置失败: {e}") return items, [1.0] * len(items) @staticmethod @@ -271,5 +271,5 @@ def ensure_guaranteed_selection( return None except Exception as e: - logger.error(f"确保必中人员失败: {e}") + logger.exception(f"确保必中人员失败: {e}") return None diff --git a/app/common/data/list.py b/app/common/data/list.py index a1f43b81..7574d9c2 100644 --- a/app/common/data/list.py +++ b/app/common/data/list.py @@ -44,7 +44,7 @@ def get_class_name_list() -> List[str]: return class_files except Exception as e: - logger.error(f"获取班级列表失败: {e}") + logger.exception(f"获取班级列表失败: {e}") return [] @@ -93,7 +93,7 @@ def get_student_list(class_name: str) -> List[Dict[str, Any]]: return student_list except Exception as e: - logger.error(f"获取学生列表失败: {e}") + logger.exception(f"获取学生列表失败: {e}") return [] @@ -204,7 +204,7 @@ def get_pool_name_list() -> List[str]: return pool_files except Exception as e: - logger.error(f"获取奖池列表失败: {e}") + logger.exception(f"获取奖池列表失败: {e}") return [] @@ -252,7 +252,7 @@ def get_pool_data(pool_name: str) -> Dict[str, Any]: return pool_list except Exception as e: - logger.error(f"获取奖池数据失败: {e}") + logger.exception(f"获取奖池数据失败: {e}") return [] @@ -300,7 +300,7 @@ def get_pool_list(pool_name: str) -> List[Dict[str, Any]]: return pool_list except Exception as e: - logger.error(f"获取奖池列表失败: {e}") + logger.exception(f"获取奖池列表失败: {e}") return [] @@ -395,7 +395,7 @@ def filter_students_data( return students_data except Exception as e: - logger.error(f"过滤学生数据失败: {e}") + logger.exception(f"过滤学生数据失败: {e}") return [] @@ -426,7 +426,7 @@ def export_student_data( # 如果文件不存在,返回错误 if not class_file_path.exists(): error_msg = f"班级文件 '{class_name}.json' 不存在" - logger.error(error_msg) + logger.exception(error_msg) return False, error_msg # 读取JSON文件 @@ -447,20 +447,20 @@ def export_student_data( return _export_to_txt(data, file_path) else: error_msg = f"不支持的导出格式: {export_format}" - logger.error(error_msg) + logger.exception(error_msg) return False, error_msg except FileNotFoundError: error_msg = f"班级文件 '{class_name}.json' 不存在" - logger.error(error_msg) + logger.exception(error_msg) return False, error_msg except json.JSONDecodeError: error_msg = f"班级文件 '{class_name}.json' 格式错误" - logger.error(error_msg) + logger.exception(error_msg) return False, error_msg except Exception as e: error_msg = f"导出学生名单时出错: {str(e)}" - logger.error(error_msg) + logger.exception(error_msg) return False, error_msg @@ -491,7 +491,7 @@ def _export_to_excel(data: Dict[str, Any], file_path: str) -> Tuple[bool, str]: try: import pandas as pd except Exception as e: - logger.error(f"导出Excel需要 pandas 库,但导入失败: {e}") + logger.exception(f"导出Excel需要 pandas 库,但导入失败: {e}") return False, "导出失败: pandas 未安装或导入错误" df = pd.DataFrame(export_data) @@ -509,7 +509,7 @@ def _export_to_excel(data: Dict[str, Any], file_path: str) -> Tuple[bool, str]: except Exception as e: error_msg = f"导出Excel文件时出错: {str(e)}" - logger.error(error_msg) + logger.exception(error_msg) return False, error_msg @@ -540,7 +540,7 @@ def _export_to_csv(data: Dict[str, Any], file_path: str) -> Tuple[bool, str]: try: import pandas as pd except Exception as e: - logger.error(f"导出CSV需要 pandas 库,但导入失败: {e}") + logger.exception(f"导出CSV需要 pandas 库,但导入失败: {e}") return False, "导出失败: pandas 未安装或导入错误" df = pd.DataFrame(export_data) @@ -558,7 +558,7 @@ def _export_to_csv(data: Dict[str, Any], file_path: str) -> Tuple[bool, str]: except Exception as e: error_msg = f"导出CSV文件时出错: {str(e)}" - logger.error(error_msg) + logger.exception(error_msg) return False, error_msg @@ -588,7 +588,7 @@ def _export_to_txt(data: Dict[str, Any], file_path: str) -> Tuple[bool, str]: except Exception as e: error_msg = f"导出TXT文件时出错: {str(e)}" - logger.error(error_msg) + logger.exception(error_msg) return False, error_msg @@ -619,7 +619,7 @@ def export_prize_data( # 如果文件不存在,返回错误 if not pool_file_path.exists(): error_msg = f"奖池文件 '{pool_name}.json' 不存在" - logger.error(error_msg) + logger.exception(error_msg) return False, error_msg # 读取JSON文件 @@ -640,20 +640,20 @@ def export_prize_data( return _export_prize_to_txt(data, file_path) else: error_msg = f"不支持的导出格式: {export_format}" - logger.error(error_msg) + logger.exception(error_msg) return False, error_msg except FileNotFoundError: error_msg = f"奖池文件 '{pool_name}.json' 不存在" - logger.error(error_msg) + logger.exception(error_msg) return False, error_msg except json.JSONDecodeError: error_msg = f"奖池文件 '{pool_name}.json' 格式错误" - logger.error(error_msg) + logger.exception(error_msg) return False, error_msg except Exception as e: error_msg = f"导出奖品名单时出错: {str(e)}" - logger.error(error_msg) + logger.exception(error_msg) return False, error_msg @@ -683,7 +683,7 @@ def _export_prize_to_excel(data: Dict[str, Any], file_path: str) -> Tuple[bool, try: import pandas as pd except Exception as e: - logger.error(f"导出Excel需要 pandas 库,但导入失败: {e}") + logger.exception(f"导出Excel需要 pandas 库,但导入失败: {e}") return False, "导出失败: pandas 未安装或导入错误" df = pd.DataFrame(export_data) @@ -701,7 +701,7 @@ def _export_prize_to_excel(data: Dict[str, Any], file_path: str) -> Tuple[bool, except Exception as e: error_msg = f"导出Excel文件时出错: {str(e)}" - logger.error(error_msg) + logger.exception(error_msg) return False, error_msg @@ -731,7 +731,7 @@ def _export_prize_to_csv(data: Dict[str, Any], file_path: str) -> Tuple[bool, st try: import pandas as pd except Exception as e: - logger.error(f"导出CSV需要 pandas 库,但导入失败: {e}") + logger.exception(f"导出CSV需要 pandas 库,但导入失败: {e}") return False, "导出失败: pandas 未安装或导入错误" df = pd.DataFrame(export_data) @@ -749,7 +749,7 @@ def _export_prize_to_csv(data: Dict[str, Any], file_path: str) -> Tuple[bool, st except Exception as e: error_msg = f"导出CSV文件时出错: {str(e)}" - logger.error(error_msg) + logger.exception(error_msg) return False, error_msg @@ -779,5 +779,5 @@ def _export_prize_to_txt(data: Dict[str, Any], file_path: str) -> Tuple[bool, st except Exception as e: error_msg = f"导出TXT文件时出错: {str(e)}" - logger.error(error_msg) + logger.exception(error_msg) return False, error_msg diff --git a/app/common/extraction/cses_parser.py b/app/common/extraction/cses_parser.py index 2c8c1c1e..f42e2a71 100644 --- a/app/common/extraction/cses_parser.py +++ b/app/common/extraction/cses_parser.py @@ -24,7 +24,7 @@ def load_from_file(self, file_path: str) -> bool: self.schedule_data = yaml.safe_load(f) return self._validate_schedule() except Exception as e: - logger.error(f"加载CSES文件失败: {e}") + logger.exception(f"加载CSES文件失败: {e}") return False def load_from_content(self, content: str) -> bool: @@ -40,7 +40,7 @@ def load_from_content(self, content: str) -> bool: self.schedule_data = yaml.safe_load(content) return self._validate_schedule() except Exception as e: - logger.error(f"解析CSES内容失败: {e}") + logger.exception(f"解析CSES内容失败: {e}") return False def get_class_times_by_day(self, day_of_week: int) -> Dict[str, str]: @@ -283,7 +283,7 @@ def _validate_schedule(self) -> bool: bool: 数据有效返回True,否则返回False """ if not self.schedule_data: - logger.error("课程表数据为空") + logger.exception("课程表数据为空") return False schedule = self.schedule_data.get("schedule") @@ -293,7 +293,7 @@ def _validate_schedule(self) -> bool: logger.warning("缺少'timeslots'字段,将使用空课程表数据") return True if not isinstance(timeslots, list): - logger.error("'timeslots'字段必须是列表类型") + logger.exception("'timeslots'字段必须是列表类型") return False for i, timeslot in enumerate(timeslots): if not self._validate_timeslot(timeslot, i): @@ -343,13 +343,13 @@ def _validate_timeslot(self, timeslot: dict, index: int) -> bool: bool: 有效返回True,否则返回False """ if not isinstance(timeslot, dict): - logger.error(f"时间段{index}必须是字典类型") + logger.exception(f"时间段{index}必须是字典类型") return False required_fields = ["name", "start_time", "end_time"] for field in required_fields: if field not in timeslot: - logger.error(f"时间段{index}缺少'{field}'字段") + logger.exception(f"时间段{index}缺少'{field}'字段") return False try: @@ -357,11 +357,11 @@ def _validate_timeslot(self, timeslot: dict, index: int) -> bool: end_time = self._parse_time(timeslot["end_time"]) if start_time >= end_time: - logger.error(f"时间段{index}的开始时间必须早于结束时间") + logger.exception(f"时间段{index}的开始时间必须早于结束时间") return False except ValueError as e: - logger.error(f"时间段{index}时间格式错误: {e}") + logger.exception(f"时间段{index}时间格式错误: {e}") return False return True @@ -387,7 +387,7 @@ def _parse_time(self, time_str: str) -> time: return time(int(parts[0]), int(parts[1]), int(parts[2])) raise ValueError(f"无效的时间格式: {time_str}") except (ValueError, IndexError): - logger.error(f"无法解析时间: {time_str}") + logger.exception(f"无法解析时间: {time_str}") raise ValueError(f"无法解析时间: {time_str}") from None def _parse_time_string_to_seconds(self, time_val: str | int) -> int: @@ -416,7 +416,7 @@ def _parse_time_string_to_seconds(self, time_val: str | int) -> int: return int(parts[0]) * 3600 + int(parts[1]) * 60 + int(parts[2]) return int(time_str) except (ValueError, IndexError): - logger.error(f"无法解析时间字符串: {time_val}") + logger.exception(f"无法解析时间字符串: {time_val}") raise ValueError(f"无法解析时间字符串: {time_val}") from None def _format_time_for_secrandom(self, time_val: str | int) -> str: diff --git a/app/common/extraction/extract.py b/app/common/extraction/extract.py index aac354f8..2264f7fe 100644 --- a/app/common/extraction/extract.py +++ b/app/common/extraction/extract.py @@ -66,7 +66,7 @@ def _get_break_assignment_class_info() -> Dict: return {} except Exception as e: - logger.error(f"获取课间归属课程信息失败: {e}") + logger.exception(f"获取课间归属课程信息失败: {e}") return {} @@ -140,7 +140,7 @@ def _is_non_class_time() -> bool: return not is_in_class_time except Exception as e: - logger.error(f"检测非上课时间失败: {e}") + logger.exception(f"检测非上课时间失败: {e}") return False @@ -219,7 +219,7 @@ def _is_time_in_ranges(current_seconds: int, time_ranges: Dict[str, str]) -> boo return True except Exception as e: - logger.error(f"解析时间段失败: {range_name} = {time_range}, 错误: {e}") + logger.exception(f"解析时间段失败: {range_name} = {time_range}, 错误: {e}") continue return False @@ -245,13 +245,13 @@ def _get_cses_parser() -> CSESParser | None: parser = CSESParser() if not parser.load_from_file(cses_file_path): - logger.error(f"加载CSES文件失败: {cses_file_path}") + logger.exception(f"加载CSES文件失败: {cses_file_path}") return None return parser except Exception as e: - logger.error(f"获取CSES解析器失败: {e}") + logger.exception(f"获取CSES解析器失败: {e}") return None @@ -328,7 +328,7 @@ def _get_current_class_info() -> Dict: return {} except Exception as e: - logger.error(f"获取当前课程信息失败: {e}") + logger.exception(f"获取当前课程信息失败: {e}") return {} @@ -359,7 +359,7 @@ def _get_seconds_to_next_class() -> int: start_total_seconds = _parse_time_string_to_seconds(start_time_str) time_ranges.append((start_total_seconds, range_name)) except Exception as e: - logger.error(f"解析时间段失败: {range_name} = {time_range}, 错误: {e}") + logger.exception(f"解析时间段失败: {range_name} = {time_range}, 错误: {e}") continue # 按开始时间排序 @@ -374,7 +374,7 @@ def _get_seconds_to_next_class() -> int: return 0 except Exception as e: - logger.error(f"计算距离下一节课时间失败: {e}") + logger.exception(f"计算距离下一节课时间失败: {e}") return 0 @@ -420,7 +420,7 @@ def _save_non_class_times_to_settings(non_class_times: Dict[str, str]) -> bool: return True except Exception as e: - logger.error(f"保存非上课时间段失败: {e}") + logger.exception(f"保存非上课时间段失败: {e}") return False @@ -463,7 +463,7 @@ def import_cses_schedule(file_path: str) -> Tuple[bool, str]: return True, import_success_msg except Exception as e: - logger.error(f"导入CSES文件失败: {e}") + logger.exception(f"导入CSES文件失败: {e}") return False, get_content_name_async("course_settings", "import_failed").format( str(e) ) @@ -498,7 +498,7 @@ def import_cses_schedule_from_content(content: str) -> Tuple[bool, str]: ) except Exception as e: - logger.error(f"导入CSES内容失败: {e}") + logger.exception(f"导入CSES内容失败: {e}") return False, get_content_name_async("course_settings", "import_failed").format( str(e) ) diff --git a/app/common/fair_draw/avg_gap_protection.py b/app/common/fair_draw/avg_gap_protection.py index 6e9ecef7..2693038d 100644 --- a/app/common/fair_draw/avg_gap_protection.py +++ b/app/common/fair_draw/avg_gap_protection.py @@ -234,7 +234,7 @@ def apply_avg_gap_protection( pool_initial = _sort_candidates_by_count(pool_initial, student_counts) except Exception as e: - logger.error(f"应用平均值差值保护时发生错误: {e}", exc_info=True) + logger.exception(f"应用平均值差值保护时发生错误: {e}", exc_info=True) # 发生错误时,返回原始候选列表,确保系统可用性 return candidates diff --git a/app/common/history/file_utils.py b/app/common/history/file_utils.py index 7d48e4af..e8bf5c26 100644 --- a/app/common/history/file_utils.py +++ b/app/common/history/file_utils.py @@ -52,7 +52,7 @@ def load_history_data(history_type: str, file_name: str) -> Dict[str, Any]: with open(file_path, "r", encoding="utf-8") as f: return json.load(f) except Exception as e: - logger.error(f"加载历史记录数据失败: {e}") + logger.exception(f"加载历史记录数据失败: {e}") return {} @@ -73,7 +73,7 @@ def save_history_data(history_type: str, file_name: str, data: Dict[str, Any]) - json.dump(data, f, ensure_ascii=False, indent=4) return True except Exception as e: - logger.error(f"保存历史记录数据失败: {e}") + logger.exception(f"保存历史记录数据失败: {e}") return False @@ -95,5 +95,5 @@ def get_all_history_names(history_type: str) -> List[str]: names.sort() return names except Exception as e: - logger.error(f"获取历史记录名称列表失败: {e}") + logger.exception(f"获取历史记录名称列表失败: {e}") return [] diff --git a/app/common/history/history_reader.py b/app/common/history/history_reader.py index 9fea63fa..be714ceb 100644 --- a/app/common/history/history_reader.py +++ b/app/common/history/history_reader.py @@ -42,7 +42,7 @@ def get_roll_call_student_list( ) return cleaned_students except Exception as e: - logger.error(f"获取班级学生列表失败: {e}") + logger.exception(f"获取班级学生列表失败: {e}") return [] @@ -65,7 +65,7 @@ def get_roll_call_history_data( with open_file(history_file, "r", encoding="utf-8") as f: return json.load(f) except Exception as e: - logger.error(f"获取点名历史记录数据失败: {e}") + logger.exception(f"获取点名历史记录数据失败: {e}") return {} @@ -365,7 +365,7 @@ def get_lottery_pool_list( ) return cleaned_lotterys except Exception as e: - logger.error(f"获取奖池奖品列表失败: {e}") + logger.exception(f"获取奖池奖品列表失败: {e}") return [] @@ -388,7 +388,7 @@ def get_lottery_history_data( with open_file(history_file, "r", encoding="utf-8") as f: return json.load(f) except Exception as e: - logger.error(f"获取抽奖历史记录数据失败: {e}") + logger.exception(f"获取抽奖历史记录数据失败: {e}") return {} diff --git a/app/common/history/lottery_history.py b/app/common/history/lottery_history.py index 11997ed7..a9c1bb3c 100644 --- a/app/common/history/lottery_history.py +++ b/app/common/history/lottery_history.py @@ -92,5 +92,5 @@ def save_lottery_history( return save_history_data("lottery", pool_name, history_data) except Exception as e: - logger.error(f"保存抽奖历史失败: {e}") + logger.exception(f"保存抽奖历史失败: {e}") return False diff --git a/app/common/history/roll_call_history.py b/app/common/history/roll_call_history.py index 1333c238..b9f8fad8 100644 --- a/app/common/history/roll_call_history.py +++ b/app/common/history/roll_call_history.py @@ -233,5 +233,5 @@ def save_roll_call_history( return save_history_data("roll_call", class_name, history_data) except Exception as e: - logger.error(f"保存点名历史记录失败: {e}") + logger.exception(f"保存点名历史记录失败: {e}") return False diff --git a/app/common/history/utils.py b/app/common/history/utils.py index c3b39d2c..d948198a 100644 --- a/app/common/history/utils.py +++ b/app/common/history/utils.py @@ -40,7 +40,7 @@ def get_all_names(history_type: str, list_name: str) -> list: except Exception as e: from loguru import logger - logger.error(f"获取历史记录中所有名称失败: {e}") + logger.exception(f"获取历史记录中所有名称失败: {e}") return [] diff --git a/app/common/music/music_player.py b/app/common/music/music_player.py index 86026ae1..1997ff7c 100644 --- a/app/common/music/music_player.py +++ b/app/common/music/music_player.py @@ -96,10 +96,10 @@ def play_music( music_path = get_audio_path(f"music/{music_file}") if not music_path.exists(): - logger.error(f"音乐文件不存在: {music_path}") + logger.exception(f"音乐文件不存在: {music_path}") return False except Exception as e: - logger.error(f"获取音乐文件路径失败: {e}") + logger.exception(f"获取音乐文件路径失败: {e}") return False # 从设置中获取音量和渐入渐出时长 @@ -125,7 +125,7 @@ def play_music( ) self._fade_out_duration = fade_out_ms / 1000.0 except Exception as e: - logger.error(f"获取音乐设置失败: {e}") + logger.exception(f"获取音乐设置失败: {e}") self._volume = 1.0 self._fade_in_duration = 0.0 self._fade_out_duration = 0.0 @@ -208,7 +208,7 @@ def _play_music_worker(self, music_path: str, loop: bool) -> None: data = np.mean(data, axis=1) data = data.astype(np.float32) except Exception as e: - logger.error(f"读取音乐文件失败: {e}") + logger.exception(f"读取音乐文件失败: {e}") return # 初始化音频流(只初始化一次) @@ -221,7 +221,7 @@ def _play_music_worker(self, music_path: str, loop: bool) -> None: ) stream.start() except Exception as e: - logger.error(f"初始化音频流失败: {e}") + logger.exception(f"初始化音频流失败: {e}") return # 计算渐入步数 @@ -254,7 +254,7 @@ def _play_music_worker(self, music_path: str, loop: bool) -> None: try: stream.write(chunk) except Exception as e: - logger.error(f"写入音频流失败: {e}") + logger.exception(f"写入音频流失败: {e}") break # 如果不循环或者收到停止信号,退出循环 @@ -262,7 +262,7 @@ def _play_music_worker(self, music_path: str, loop: bool) -> None: break except Exception as e: - logger.error(f"音乐播放工作线程异常: {e}") + logger.exception(f"音乐播放工作线程异常: {e}") finally: # 确保音频流关闭 if stream: @@ -270,7 +270,7 @@ def _play_music_worker(self, music_path: str, loop: bool) -> None: stream.stop() stream.close() except Exception as e: - logger.error(f"关闭音频流失败: {e}") + logger.exception(f"关闭音频流失败: {e}") self._is_playing = False logger.debug("音乐播放工作线程结束") diff --git a/app/common/safety/secure_store.py b/app/common/safety/secure_store.py index 96ed9259..8c45bfd6 100644 --- a/app/common/safety/secure_store.py +++ b/app/common/safety/secure_store.py @@ -148,7 +148,7 @@ def write_secrets(d: dict) -> None: _set_hidden(str(p)) logger.debug(f"写入安全配置成功:{p}") except PermissionError as e: - logger.error( + logger.exception( f"写入安全配置失败:权限被拒绝,文件可能被占用或无写权限:{p}, 错误:{e}" ) # 尝试使用临时文件写入然后替换 @@ -166,14 +166,14 @@ def write_secrets(d: dict) -> None: _set_hidden(str(p)) logger.debug(f"使用临时文件写入安全配置成功:{p}") except Exception as temp_e: - logger.error(f"使用临时文件写入安全配置也失败:{temp_e}") + logger.exception(f"使用临时文件写入安全配置也失败:{temp_e}") # 降级到明文JSON写入 try: with open(p, "w", encoding="utf-8") as f: json.dump(d, f, ensure_ascii=False, indent=4) logger.warning(f"写入安全配置降级为明文JSON:{p}") except Exception as e2: - logger.error(f"降级写入明文JSON也失败:{e2}") + logger.exception(f"降级写入明文JSON也失败:{e2}") def read_behind_scenes_settings() -> dict: @@ -235,7 +235,7 @@ def write_behind_scenes_settings(d: dict) -> None: _set_hidden(str(p)) logger.debug(f"写入内幕设置成功:{p}") except PermissionError as e: - logger.error( + logger.exception( f"写入内幕设置失败:权限被拒绝,文件可能被占用或无写权限:{p}, 错误:{e}" ) try: @@ -251,18 +251,18 @@ def write_behind_scenes_settings(d: dict) -> None: _set_hidden(str(p)) logger.debug(f"使用临时文件写入内幕设置成功:{p}") except Exception as temp_e: - logger.error(f"使用临时文件写入内幕设置也失败:{temp_e}") + logger.exception(f"使用临时文件写入内幕设置也失败:{temp_e}") try: with open(p, "w", encoding="utf-8") as f: json.dump(d, f, ensure_ascii=False, indent=4) logger.warning(f"写入内幕设置降级为明文JSON:{p}") except Exception as e2: - logger.error(f"降级写入明文JSON也失败:{e2}") + logger.exception(f"降级写入明文JSON也失败:{e2}") except Exception as e: - logger.error(f"写入内幕设置失败:{p}, 错误:{e}") + logger.exception(f"写入内幕设置失败:{p}, 错误:{e}") try: with open(p, "w", encoding="utf-8") as f: json.dump(d, f, ensure_ascii=False, indent=4) logger.warning(f"写入内幕设置降级为明文JSON:{p}") except Exception as e2: - logger.error(f"降级写入明文JSON也失败:{e2}") + logger.exception(f"降级写入明文JSON也失败:{e2}") diff --git a/app/common/shortcut/shortcut_manager.py b/app/common/shortcut/shortcut_manager.py index 39ec9b5d..56f71657 100644 --- a/app/common/shortcut/shortcut_manager.py +++ b/app/common/shortcut/shortcut_manager.py @@ -93,10 +93,10 @@ def on_pressed(): else: logger.debug(f"快捷键热键为空: {config_key} = {shortcut_str}") except Exception as e: - logger.error(f"注册快捷键失败 {config_key}: {e}") + logger.exception(f"注册快捷键失败 {config_key}: {e}") import traceback - logger.error(traceback.format_exc()) + logger.exception(traceback.format_exc()) else: logger.debug(f"快捷键未设置: {config_key}") @@ -129,7 +129,7 @@ def reload_shortcuts(self): try: keyboard.remove_hotkey(hotkey) except Exception as e: - logger.error(f"注销快捷键失败 {config_key}: {e}") + logger.exception(f"注销快捷键失败 {config_key}: {e}") self.shortcuts.clear() @@ -149,7 +149,7 @@ def update_shortcut(self, config_key: str, shortcut_str: str): keyboard.remove_hotkey(old_hotkey) del self.shortcuts[config_key] except Exception as e: - logger.error(f"注销快捷键失败 {config_key}: {e}") + logger.exception(f"注销快捷键失败 {config_key}: {e}") if shortcut_str and self._enabled: try: @@ -168,7 +168,7 @@ def on_pressed(): f"快捷键已更新: {config_key} = {shortcut_str}, 热键: {hotkey}" ) except Exception as e: - logger.error(f"更新快捷键失败 {config_key}: {e}") + logger.exception(f"更新快捷键失败 {config_key}: {e}") def _get_signal_for_key(self, config_key: str) -> Signal: """根据配置键获取对应的信号 @@ -207,7 +207,7 @@ def set_enabled(self, enabled: bool): try: keyboard.remove_hotkey(hotkey) except Exception as e: - logger.error(f"注销快捷键失败 {config_key}: {e}") + logger.exception(f"注销快捷键失败 {config_key}: {e}") self.shortcuts.clear() logger.info("快捷键已禁用") @@ -227,5 +227,5 @@ def cleanup(self): keyboard.unhook_all() logger.debug("已清理所有 keyboard 钩子") except Exception as e: - logger.error(f"清理 keyboard 钩子失败: {e}") + logger.exception(f"清理 keyboard 钩子失败: {e}") self.shortcuts.clear() diff --git a/app/common/voice/edge_tts_worker.py b/app/common/voice/edge_tts_worker.py index a32ef052..82224cfc 100644 --- a/app/common/voice/edge_tts_worker.py +++ b/app/common/voice/edge_tts_worker.py @@ -20,7 +20,7 @@ def run(self): voices = self.get_voices() self.voices_fetched.emit(voices) except Exception as e: - logger.error(f"获取Edge TTS语音列表失败: {e}") + logger.exception(f"获取Edge TTS语音列表失败: {e}") self.error_occurred.emit(str(e)) def get_voices(self): @@ -94,7 +94,7 @@ def get_voices(self): return filtered_voices except Exception as e: - logger.error(f"获取Edge TTS语音列表失败: {e}") + logger.exception(f"获取Edge TTS语音列表失败: {e}") # 返回默认语音列表 return self.get_default_voices() diff --git a/app/common/voice/voice.py b/app/common/voice/voice.py index 90aac39e..a4987044 100644 --- a/app/common/voice/voice.py +++ b/app/common/voice/voice.py @@ -48,7 +48,7 @@ def wrapper(self, *args, **kwargs): # 示例:检查当前用户是否有使用TTS的权限 has_perm = True # 实际实现时替换为真实权限检查 if not has_perm: - logger.error(f"权限不足,无法执行 {func.__name__}") + logger.exception(f"权限不足,无法执行 {func.__name__}") return return func(self, *args, **kwargs) @@ -78,7 +78,7 @@ def set_volume(self, volume: float) -> None: """设置播放音量,范围0.0-1.0""" # 输入验证 if not isinstance(volume, (int, float)): - logger.error(f"无效的音量值: {volume}") + logger.exception(f"无效的音量值: {volume}") return self._volume = max(0.0, min(1.0, float(volume))) @@ -86,7 +86,7 @@ def set_speed(self, speed: int) -> None: """设置播放语速,范围0-200""" # 输入验证 if not isinstance(speed, int): - logger.error(f"无效的语速值: {speed}") + logger.exception(f"无效的语速值: {speed}") return self._speed = max(0, min(200, speed)) @@ -126,14 +126,14 @@ def _playback_worker(self) -> None: logger.debug(f"读取文件成功: 数据长度={len(data)}, 采样率={fs}") self._safe_play(data, fs) except Exception as e: - logger.error(f"读取音频文件失败: {e}", exc_info=True) + logger.exception(f"读取音频文件失败: {e}", exc_info=True) except Empty: # 队列为空时,短暂休息避免CPU占用过高 time.sleep(0.1) continue except Exception as e: - logger.error(f"播放线程异常: {e}", exc_info=True) + logger.exception(f"播放线程异常: {e}", exc_info=True) # 短暂休息后继续,避免异常风暴 time.sleep(0.5) @@ -202,12 +202,12 @@ def _safe_play(self, data: np.ndarray, fs: int) -> None: self._is_playing = False # 播放结束 except sd.PortAudioError as e: - logger.error(f"PortAudio错误:{e}", exc_info=True) + logger.exception(f"PortAudio错误:{e}", exc_info=True) # 处理PortAudio特定错误,避免程序崩溃 with self._is_playing_lock: self._is_playing = False except Exception as e: - logger.error(f"播放音频失败:{e}", exc_info=True) + logger.exception(f"播放音频失败:{e}", exc_info=True) # 确保播放状态正确重置,避免死锁 with self._is_playing_lock: self._is_playing = False @@ -219,7 +219,7 @@ def _safe_play(self, data: np.ndarray, fs: int) -> None: stream.close() logger.debug("音频流已关闭") except Exception as e: - logger.error(f"关闭音频流失败:{e}", exc_info=True) + logger.exception(f"关闭音频流失败:{e}", exc_info=True) # 播放完成后回收内存 try: @@ -232,7 +232,7 @@ def _safe_play(self, data: np.ndarray, fs: int) -> None: gc.collect() logger.debug("播放完成,已回收内存") except Exception as e: - logger.error(f"内存回收失败: {e}") + logger.exception(f"内存回收失败: {e}") def add_task(self, task: Union[Tuple[np.ndarray, int], str]) -> bool: """添加播放任务(线程安全)""" @@ -240,24 +240,24 @@ def add_task(self, task: Union[Tuple[np.ndarray, int], str]) -> bool: # 输入验证 if isinstance(task, tuple): # 内存数据 if len(task) != 2: - logger.error(f"无效的任务格式: {task}") + logger.exception(f"无效的任务格式: {task}") return False data, fs = task if not isinstance(data, np.ndarray) or not isinstance(fs, int): - logger.error("无效的内存数据格式") + logger.exception("无效的内存数据格式") return False else: # 文件路径 if not isinstance(task, str) or not task: - logger.error("无效的文件路径") + logger.exception("无效的文件路径") return False self.play_queue.put_nowait(task) return True except queue.Full: - logger.error("播放队列已满,丢弃新任务") + logger.exception("播放队列已满,丢弃新任务") return False except Exception as e: - logger.error(f"添加播放任务失败: {e}") + logger.exception(f"添加播放任务失败: {e}") return False def stop(self) -> None: @@ -314,10 +314,10 @@ def get_voice(self, text: str, voice: str) -> Tuple[np.ndarray, int]: """获取语音数据(自动缓存)""" # 输入验证 if not isinstance(text, str) or not text: - logger.error(f"无效的文本: {text}") + logger.exception(f"无效的文本: {text}") raise ValueError("文本不能为空") if not isinstance(voice, str) or not voice: - logger.error(f"无效的语音名称: {voice}") + logger.exception(f"无效的语音名称: {voice}") raise ValueError("语音名称不能为空") logger.debug(f"获取语音: text='{text}', voice='{voice}'") @@ -347,7 +347,7 @@ def get_voice(self, text: str, voice: str) -> Tuple[np.ndarray, int]: self._add_to_memory_cache(cache_key, data, fs) return data, fs except Exception as e: - logger.error(f"读取缓存失败: {e}") + logger.exception(f"读取缓存失败: {e}") else: logger.debug(f"未命中缓存,生成新语音: {cache_key}") @@ -396,28 +396,28 @@ async def _generate_voice(self, text: str, voice: str) -> Tuple[np.ndarray, int] return data, fs except NoAudioReceived as e: retry_count += 1 - logger.error( + logger.exception( f"生成语音失败,未接收到音频数据,重试{retry_count}/{max_retries}: {type(e).__name__} {e}" ) if retry_count < max_retries: await asyncio.sleep(1) except WebSocketError as e: retry_count += 1 - logger.error( + logger.exception( f"生成语音失败,WebSocket通信错误,重试{retry_count}/{max_retries}: {type(e).__name__} {e}" ) if retry_count < max_retries: await asyncio.sleep(1) except Exception as e: retry_count += 1 - logger.error( + logger.exception( f"生成语音失败,重试{retry_count}/{max_retries}: {type(e).__name__} {e}" ) if retry_count < max_retries: await asyncio.sleep(1) # 最终失败时的降级处理 - logger.error("生成语音失败,已达到最大重试次数") + logger.exception("生成语音失败,已达到最大重试次数") raise RuntimeError("生成语音失败") def _generate_cache_key(self, text: str, voice: str) -> str: @@ -481,7 +481,7 @@ def _save_to_disk(self, file_path: str, data: np.ndarray, fs: int) -> None: with self._disk_cache_lock: sf.write(file_path, data, fs) except Exception as e: - logger.error(f"保存缓存失败: {e}") + logger.exception(f"保存缓存失败: {e}") def _check_and_cleanup(self) -> None: """检查并执行缓存清理""" @@ -507,7 +507,7 @@ def _cleanup_expired_cache(self) -> None: logger.info("缓存清理:本地音频文件不会过期,跳过文件删除") # 可以在这里添加其他清理逻辑,如日志清理等 except Exception as e: - logger.error(f"缓存清理失败: {e}") + logger.exception(f"缓存清理失败: {e}") class LoadBalancer: @@ -560,11 +560,11 @@ def get_optimal_queue_size(self) -> int: or cpu_percent < 0 or cpu_percent > 100 ): - logger.error("CPU使用率异常,使用基础队列大小") + logger.exception("CPU使用率异常,使用基础队列大小") return self.BASE_QUEUE_SIZE if not isinstance(mem_available, (int, float)) or mem_available < 0: - logger.error("内存信息异常,使用基础队列大小") + logger.exception("内存信息异常,使用基础队列大小") return self.BASE_QUEUE_SIZE # 计算基于CPU的队列大小调整系数 @@ -605,7 +605,7 @@ def get_optimal_queue_size(self) -> int: return queue_size except Exception as e: # 异常处理,确保方法总是返回有效值 - logger.error(f"获取系统负载信息失败: {e},使用基础队列大小") + logger.exception(f"获取系统负载信息失败: {e},使用基础队列大小") return self.BASE_QUEUE_SIZE @@ -658,7 +658,7 @@ def _init_tts_engine(self) -> None: ) logger.info("Windows系统TTS引擎初始化成功") else: - logger.error( + logger.exception( "Windows系统TTS引擎需要Windows 10及以上系统且非x86架构" ) @@ -686,17 +686,17 @@ def _init_tts_engine(self) -> None: ) logger.info("Linux系统TTS引擎初始化成功 (使用espeak)") else: - logger.error( + logger.exception( "Linux系统TTS引擎需要安装espeak: sudo apt-get install espeak" ) except Exception as e: - logger.error(f"Linux系统TTS引擎初始化失败: {e}") + logger.exception(f"Linux系统TTS引擎初始化失败: {e}") else: - logger.error(f"不支持的操作系统: {system},系统TTS功能不可用") + logger.exception(f"不支持的操作系统: {system},系统TTS功能不可用") except Exception as e: - logger.error(f"TTS引擎初始化失败: {e}") + logger.exception(f"TTS引擎初始化失败: {e}") self.voice_engine = None @require_permission("tts.use") @@ -711,16 +711,16 @@ def voice_play( """主入口函数""" # 输入验证 if not isinstance(config, dict): - logger.error(f"无效的配置: {config}") + logger.exception(f"无效的配置: {config}") return if not isinstance(student_names, list): - logger.error(f"无效的学生名单: {student_names}") + logger.exception(f"无效的学生名单: {student_names}") return if engine_type not in [0, 1]: - logger.error(f"无效的引擎类型: {engine_type}") + logger.exception(f"无效的引擎类型: {engine_type}") return if not isinstance(voice_name, str): - logger.error(f"无效的语音名称: {voice_name}") + logger.exception(f"无效的语音名称: {voice_name}") return try: @@ -786,7 +786,7 @@ def voice_play( logger.info("Edge TTS播报") except Exception as e: - logger.error(f"语音播报失败: {e}", exc_info=True) + logger.exception(f"语音播报失败: {e}", exc_info=True) def _handle_system_tts( self, student_names: List[str], config: Dict[str, Any] @@ -806,19 +806,19 @@ def _handle_system_tts( if isinstance(system_volume_size, (int, str)): restore_volume(int(system_volume_size)) except Exception as e: - logger.error(f"系统音量控制处理失败: {e}") + logger.exception(f"系统音量控制处理失败: {e}") # 继续执行,不影响语音播报 with self.system_tts_lock: if self.voice_engine is None: - logger.error("系统TTS引擎未初始化,无法播放语音") + logger.exception("系统TTS引擎未初始化,无法播放语音") return for name in student_names: try: self.voice_engine.say(f"{name}") self.voice_engine.iterate() except Exception as e: - logger.error(f"处理{name}失败: {e}") + logger.exception(f"处理{name}失败: {e}") def _init_system_tts(self, config: Dict[str, Any]) -> Optional[Any]: """初始化系统TTS引擎(跨平台支持)""" @@ -844,7 +844,7 @@ def _init_system_tts(self, config: Dict[str, Any]) -> Optional[Any]: return engine except Exception as e: - logger.error(f"初始化系统TTS引擎失败: {e}") + logger.exception(f"初始化系统TTS引擎失败: {e}") return None def _handle_edge_tts( @@ -874,7 +874,7 @@ def _prepare_and_play( if isinstance(system_volume_size, (int, str)): restore_volume(int(system_volume_size)) except Exception as e: - logger.error(f"系统音量控制处理失败: {e}") + logger.exception(f"系统音量控制处理失败: {e}") # 继续执行,不影响语音播报 # 设置播放音量,转换为0.0-1.0范围 @@ -888,9 +888,9 @@ def _prepare_and_play( data, fs = self.cache_manager.get_voice(name, voice_name) # 提交播放任务 if not self.playback_system.add_task((data, fs)): - logger.error(f"提交播放任务失败: {name}") + logger.exception(f"提交播放任务失败: {name}") except Exception as e: - logger.error(f"处理{name}失败: {e}") + logger.exception(f"处理{name}失败: {e}") logger.debug("所有语音播放任务已提交,将异步播放") @@ -908,4 +908,4 @@ def stop(self) -> None: try: self.voice_engine.stop() except Exception as e: - logger.error(f"停止系统TTS引擎失败: {e}") + logger.exception(f"停止系统TTS引擎失败: {e}") diff --git a/app/core/cs_ipc_handler_setup.py b/app/core/cs_ipc_handler_setup.py index 20fa9e3f..195858f3 100644 --- a/app/core/cs_ipc_handler_setup.py +++ b/app/core/cs_ipc_handler_setup.py @@ -18,5 +18,5 @@ def create_cs_ipc_handler(): logger.debug("C# IPC 处理器初始化完成") return cs_ipc_handler except Exception as e: - logger.error(f"初始化 C# IPC 处理器失败: {e}") + logger.exception(f"初始化 C# IPC 处理器失败: {e}") return None diff --git a/app/core/font_manager.py b/app/core/font_manager.py index 62b093c0..40246bcb 100644 --- a/app/core/font_manager.py +++ b/app/core/font_manager.py @@ -52,7 +52,7 @@ def load_font_by_weight(font_family: str, font_weight: int) -> str: font_id = QFontDatabase.addApplicationFont(str(font_path)) if font_id < 0: - logger.error(f"加载字体文件失败: {font_path}") + logger.exception(f"加载字体文件失败: {font_path}") return font_family font_family = QFontDatabase.applicationFontFamilies(font_id)[0] diff --git a/app/core/single_instance.py b/app/core/single_instance.py index a45868f1..e216b7ac 100644 --- a/app/core/single_instance.py +++ b/app/core/single_instance.py @@ -20,7 +20,7 @@ def check_single_instance() -> Tuple[Optional[QSharedMemory], bool]: _activate_existing_instance() return shared_memory, False else: - logger.error("无法附加到共享内存") + logger.exception("无法附加到共享内存") return shared_memory, False logger.info("单实例检查通过,可以安全启动程序") @@ -74,7 +74,7 @@ def setup_local_server( """ server = QLocalServer() if not server.listen(SHARED_MEMORY_KEY): - logger.error(f"无法启动本地服务器: {server.errorString()}") + logger.exception(f"无法启动本地服务器: {server.errorString()}") return None server.newConnection.connect( diff --git a/app/core/url_handler_setup.py b/app/core/url_handler_setup.py index d1755e18..5cbe9ede 100644 --- a/app/core/url_handler_setup.py +++ b/app/core/url_handler_setup.py @@ -19,5 +19,5 @@ def create_url_handler(): logger.debug("URL处理器初始化完成") return url_handler except Exception as e: - logger.error(f"初始化URL处理器失败: {e}") + logger.exception(f"初始化URL处理器失败: {e}") return None diff --git a/app/core/utils.py b/app/core/utils.py index 5868379c..788a360b 100644 --- a/app/core/utils.py +++ b/app/core/utils.py @@ -24,7 +24,7 @@ def wrapper(*args, **kwargs) -> Optional[T]: return func(*args, **kwargs) except Exception as e: func_name = func.__name__ - logger.error(f"{func_name} 执行失败: {e}", exc_info=True) + logger.exception(f"{func_name} 执行失败: {e}", exc_info=True) return None return wrapper @@ -49,7 +49,7 @@ def activate_window(window: QWidget) -> bool: window.activateWindow() return True except Exception as e: - logger.error(f"激活窗口失败: {e}", exc_info=True) + logger.exception(f"激活窗口失败: {e}", exc_info=True) return False @@ -70,7 +70,7 @@ def safe_close_window(window: Optional[QWidget]) -> bool: window.deleteLater() return True except Exception as e: - logger.error(f"关闭窗口失败: {e}", exc_info=True) + logger.exception(f"关闭窗口失败: {e}", exc_info=True) return False @@ -94,5 +94,5 @@ def safe_execute( try: return True, func(*args, **kwargs) except Exception as e: - logger.error(f"{error_message}: {e}", exc_info=True) + logger.exception(f"{error_message}: {e}", exc_info=True) return False, None diff --git a/app/core/window_manager.py b/app/core/window_manager.py index 3cea1fa2..6adb3bf4 100644 --- a/app/core/window_manager.py +++ b/app/core/window_manager.py @@ -35,7 +35,7 @@ def create_main_window(self) -> None: self._create_main_window_impl, error_message="创建主窗口失败" ) if not success: - logger.error("主窗口创建失败", exc_info=True) + logger.exception("主窗口创建失败", exc_info=True) def _create_main_window_impl(self) -> None: """创建主窗口的实现""" @@ -176,7 +176,7 @@ def create_settings_window(self, is_preview: bool = False) -> None: is_preview, error_message="创建设置窗口失败", ): - logger.error("设置窗口创建失败", exc_info=True) + logger.exception("设置窗口创建失败", exc_info=True) def _create_settings_window_impl(self, is_preview: bool) -> None: """创建设置窗口的实现 @@ -203,7 +203,7 @@ def show_settings_window( is_preview, error_message="显示设置窗口失败", ): - logger.error("设置窗口显示失败", exc_info=True) + logger.exception("设置窗口显示失败", exc_info=True) def _show_settings_window_impl(self, page_name: str, is_preview: bool) -> None: """显示设置窗口的实现 @@ -280,7 +280,7 @@ def create_float_window(self) -> None: if not safe_execute( self._create_float_window_impl, error_message="创建浮窗失败" ): - logger.error("浮窗创建失败", exc_info=True) + logger.exception("浮窗创建失败", exc_info=True) def _create_float_window_impl(self) -> None: """创建浮窗的实现""" @@ -294,7 +294,7 @@ def show_float_window(self) -> None: self._show_float_window_impl, error_message="显示浮窗失败" ) if not success: - logger.error("浮窗显示失败", exc_info=True) + logger.exception("浮窗显示失败", exc_info=True) def _show_float_window_impl(self) -> None: """显示浮窗的实现""" diff --git a/app/page_building/another_window.py b/app/page_building/another_window.py index eef74c5b..d9f67087 100644 --- a/app/page_building/another_window.py +++ b/app/page_building/another_window.py @@ -470,7 +470,7 @@ def check_page(): return window, get_page_callback except Exception as e: # 如果窗口已损坏,从字典中移除并创建新窗口 - logger.error(f"激活剩余名单窗口失败: {e}") + logger.exception(f"激活剩余名单窗口失败: {e}") _window_instances.pop("remaining_list", None) # 创建新窗口 diff --git a/app/page_building/page_template.py b/app/page_building/page_template.py index 92cbf3e5..e90bc3b3 100644 --- a/app/page_building/page_template.py +++ b/app/page_building/page_template.py @@ -498,7 +498,7 @@ def _load_page_content( self.stacked_widget.setCurrentWidget(scroll_area) except (ImportError, AttributeError) as e: - logger.error(f"无法导入页面组件 {page_name}: {e}") + logger.exception(f"无法导入页面组件 {page_name}: {e}") # 清除加载提示(安全地移除所有子项) try: @@ -627,7 +627,7 @@ def _unload_page(self, page_name: str): info["widget"] = None info["loaded"] = False except Exception as e: - logger.error(f"卸载页面 {page_name} 失败: {e}") + logger.exception(f"卸载页面 {page_name} 失败: {e}") def load_all_pages(self, interval_ms: int = 50, max_per_tick: int = 5): """ diff --git a/app/page_building/window_template.py b/app/page_building/window_template.py index 22cb84b8..75a4810e 100644 --- a/app/page_building/window_template.py +++ b/app/page_building/window_template.py @@ -177,7 +177,7 @@ def __connectSignalToSlot(self) -> None: try: qconfig.themeChanged.connect(self._on_theme_changed) except Exception as e: - logger.error(f"连接信号时发生未知错误: {e}") + logger.exception(f"连接信号时发生未知错误: {e}") def _on_theme_changed(self) -> None: """主题变化时自动更新窗口背景""" @@ -186,7 +186,7 @@ def _on_theme_changed(self) -> None: self._apply_current_theme() pass except Exception as e: - logger.error(f"主题变化时更新窗口背景失败: {e}") + logger.exception(f"主题变化时更新窗口背景失败: {e}") def _apply_current_theme(self) -> None: """应用当前主题设置到窗口""" @@ -232,7 +232,7 @@ def _apply_current_theme(self) -> None: logger.debug(f"窗口主题已更新为: {current_theme}") except Exception as e: - logger.error(f"应用主题时出错: {e}") + logger.exception(f"应用主题时出错: {e}") # 设置默认的浅色背景作为备选 self.setStyleSheet("background-color: #ffffff;") self.default_page.setStyleSheet("background-color: transparent;") @@ -287,7 +287,7 @@ def _set_titlebar_colors(self) -> None: f"标题栏颜色已设置: 文字色={title_color}, 背景色={background_color}" ) except Exception as e: - logger.error(f"设置标题栏颜色失败: {e}") + logger.exception(f"设置标题栏颜色失败: {e}") def create_ui_components(self) -> None: """创建UI组件""" @@ -300,7 +300,7 @@ def create_ui_components(self) -> None: # 添加默认页面到堆叠窗口 self.stacked_widget.addWidget(self.default_page) except Exception as e: - logger.error(f"创建UI组件时出错: {e}") + logger.exception(f"创建UI组件时出错: {e}") raise def add_page_from_template( @@ -319,7 +319,7 @@ def add_page_from_template( """ # 输入验证 if not page_name or not isinstance(page_name, str): - logger.error("页面名称必须是非空字符串") + logger.exception("页面名称必须是非空字符串") return None if page_name in self.page_instances: @@ -341,7 +341,7 @@ def add_page_from_template( self.pages[page_name] = page_class return page_instance except Exception as e: - logger.error(f"创建页面 {page_name} 时出错: {e}") + logger.exception(f"创建页面 {page_name} 时出错: {e}") return None def add_page_from_widget(self, page_name: str, widget: QWidget) -> QWidget: @@ -377,7 +377,7 @@ def add_page_from_widget(self, page_name: str, widget: QWidget) -> QWidget: self.page_instances[page_name] = widget return widget except Exception as e: - logger.error(f"添加页面 {page_name} 到堆叠窗口时出错: {e}") + logger.exception(f"添加页面 {page_name} 到堆叠窗口时出错: {e}") raise def get_page(self, page_name: str) -> Optional[QWidget]: @@ -413,7 +413,7 @@ def remove_page(self, page_name: str) -> bool: """ # 输入验证 if not page_name or not isinstance(page_name, str): - logger.error("页面名称必须是非空字符串") + logger.exception("页面名称必须是非空字符串") return False if page_name not in self.page_instances: @@ -441,7 +441,7 @@ def remove_page(self, page_name: str) -> bool: del self.pages[page_name] return True except Exception as e: - logger.error(f"移除页面 {page_name} 时出错: {e}") + logger.exception(f"移除页面 {page_name} 时出错: {e}") return False def switch_to_page(self, page_name: str) -> bool: @@ -456,7 +456,7 @@ def switch_to_page(self, page_name: str) -> bool: """ # 输入验证 if not page_name or not isinstance(page_name, str): - logger.error("页面名称必须是非空字符串") + logger.exception("页面名称必须是非空字符串") return False if page_name not in self.page_instances: @@ -475,7 +475,7 @@ def switch_to_page(self, page_name: str) -> bool: self.stacked_widget.setCurrentWidget(target_page) return True except Exception as e: - logger.error(f"切换到页面 {page_name} 时出错: {e}") + logger.exception(f"切换到页面 {page_name} 时出错: {e}") return False def closeEvent(self, event) -> None: diff --git a/app/tools/config.py b/app/tools/config.py index a034eb8d..6423af45 100644 --- a/app/tools/config.py +++ b/app/tools/config.py @@ -412,7 +412,7 @@ def on_notification_click(): else: logger.warning("通知未配置URL,无法打开链接") except Exception as e: - logger.error(f"打开通知链接失败: {e}") + logger.exception(f"打开通知链接失败: {e}") if sys.platform == "win32": # Windows平台 @@ -499,7 +499,7 @@ def on_notification_click(): logger.warning(f"当前平台不支持系统通知: {sys.platform}") return False except Exception as e: - logger.error(f"发送系统通知时发生意外错误: {e}") + logger.exception(f"发送系统通知时发生意外错误: {e}") return False @@ -565,11 +565,11 @@ def restore_volume(volume_value): f"Windows音量设置为: {volume_value}%,实际设置值: {actual_volume:.1f}%" ) else: - logger.error("音频设备没有Activate方法") + logger.exception("音频设备没有Activate方法") else: - logger.error("无法获取音频设备接口") + logger.exception("无法获取音频设备接口") except Exception as e: - logger.error(f"获取系统主音量控制器失败: {e}") + logger.exception(f"获取系统主音量控制器失败: {e}") # 作为最后尝试,遍历所有会话并设置音量 sessions = AudioUtilities.GetAllSessions() for session in sessions: @@ -583,7 +583,7 @@ def restore_volume(volume_value): # 释放COM库 comtypes.CoUninitialize() except Exception as e: - logger.error(f"Windows音量控制失败: {e}") + logger.exception(f"Windows音量控制失败: {e}") elif sys.platform.startswith("linux"): # Linux音频控制 (使用PulseAudio) try: @@ -615,7 +615,7 @@ def restore_volume(volume_value): pulse.volume_set_all_chans(default_sink, volume_value / 100.0) logger.info(f"Linux音量设置为: {volume_value}%") except Exception as e: - logger.error(f"Linux音量控制失败: {e}") + logger.exception(f"Linux音量控制失败: {e}") else: logger.warning(f"不支持的平台: {sys.platform},音量控制功能不可用") @@ -674,7 +674,7 @@ def export_settings(parent: Optional[QWidget] = None) -> None: dialog.exec() except Exception as e: - logger.error(f"导出设置失败: {e}") + logger.exception(f"导出设置失败: {e}") # 显示错误消息 dialog = MessageBox( get_any_position_value_async( @@ -794,7 +794,7 @@ def import_settings(parent: Optional[QWidget] = None) -> None: success_dialog.exec() except Exception as e: - logger.error(f"导入设置失败: {e}") + logger.exception(f"导入设置失败: {e}") # 显示错误消息 dialog = MessageBox( get_any_position_value_async( @@ -1177,7 +1177,7 @@ def export_diagnostic_data(parent: Optional[QWidget] = None) -> None: json.dumps(system_info, ensure_ascii=False, indent=2), ) except Exception as e: - logger.error(f"写入诊断信息文件失败: {e}") + logger.exception(f"写入诊断信息文件失败: {e}") # 尝试将所有Path对象转换为字符串 class PathEncoder(json.JSONEncoder): @@ -1222,7 +1222,7 @@ def default(self, obj): dialog.exec() except Exception as e: - logger.error(f"导出诊断数据失败: {e}") + logger.exception(f"导出诊断数据失败: {e}") # 显示错误消息 dialog = MessageBox( get_any_position_value_async( @@ -1351,7 +1351,7 @@ def _apply_export_all_warning(): dialog.buttonLayout.insertStretch(1) dialog.exec() except Exception as e: - logger.error(f"导出所有数据失败: {e}") + logger.exception(f"导出所有数据失败: {e}") dialog = MessageBox( get_any_position_value_async( "basic_settings", "data_import_export", "export_failure_title", "name" @@ -1628,7 +1628,7 @@ def _apply_overwrite(): success_dialog.exec() except Exception as e: - logger.error(f"导入所有数据失败: {e}") + logger.exception(f"导入所有数据失败: {e}") # 显示错误消息 dialog = MessageBox( get_any_position_value_async( @@ -1784,7 +1784,7 @@ def _load_drawn_records(file_path: str) -> dict: return drawn_records except (json.JSONDecodeError, IOError) as e: - logger.error(f"读取已抽取记录失败: {e}") + logger.exception(f"读取已抽取记录失败: {e}") return {} @@ -1829,7 +1829,7 @@ def _save_drawn_records(file_path: str, drawn_records: dict) -> None: with open(file_path, "w", encoding="utf-8") as file: json.dump(drawn_records, file, ensure_ascii=False, indent=2) except IOError as e: - logger.error(f"保存已抽取记录失败: {e}") + logger.exception(f"保存已抽取记录失败: {e}") # ======= 读取已抽取记录 ======= @@ -1871,7 +1871,7 @@ def read_drawn_record(class_name: str, gender: str, group: str): logger.debug(f"已读取{class_name}_{gender}_{group}已抽取记录") return drawn_records except (json.JSONDecodeError, IOError) as e: - logger.error(f"读取已抽取记录失败: {e}") + logger.exception(f"读取已抽取记录失败: {e}") return [] else: logger.debug(f"文件 {file_path} 不存在") @@ -1903,7 +1903,7 @@ def remove_record(class_name: str, gender: str, group: str, _prefix: str = "0"): file_name = os.path.basename(os.path.dirname(file_path)) logger.info(f"已删除记录文件夹: {file_name}") except OSError as e: - logger.error(f"删除文件{file_path}失败: {e}") + logger.exception(f"删除文件{file_path}失败: {e}") elif prefix == "until": # 只删除特定前缀的文件 file_path = get_data_path( @@ -1915,7 +1915,7 @@ def remove_record(class_name: str, gender: str, group: str, _prefix: str = "0"): file_name = os.path.basename(os.path.dirname(file_path)) logger.info(f"已删除记录文件夹: {file_name}") except OSError as e: - logger.error(f"删除文件{file_path}失败: {e}") + logger.exception(f"删除文件{file_path}失败: {e}") elif prefix == "restart": # 重启后清除 # 构建搜索模式,匹配所有前缀的文件夹 search_pattern = os.path.join("data", "TEMP", "draw_*.json") @@ -1928,7 +1928,7 @@ def remove_record(class_name: str, gender: str, group: str, _prefix: str = "0"): file_name = os.path.basename(os.path.dirname(file_path)) logger.info(f"已删除记录文件夹: {file_name}") except OSError as e: - logger.error(f"删除文件{file_path}失败: {e}") + logger.exception(f"删除文件{file_path}失败: {e}") def reset_drawn_record(self, class_name: str, gender: str, group: str): @@ -2076,7 +2076,7 @@ def read_drawn_record_simple(pool_name: str): res.append((item["name"], int(item.get("count", 1)))) return res except Exception as e: - logger.error(f"读取奖池已抽取记录失败: {e}") + logger.exception(f"读取奖池已抽取记录失败: {e}") return [] return [] @@ -2088,7 +2088,7 @@ def reset_drawn_prize_record(self, pool_name: str): try: os.remove(fp) except OSError as e: - logger.error(f"删除文件{fp}失败: {e}") + logger.exception(f"删除文件{fp}失败: {e}") show_notification( NotificationType.INFO, NotificationConfig( @@ -2099,7 +2099,7 @@ def reset_drawn_prize_record(self, pool_name: str): parent=self, ) except Exception as e: - logger.error(f"重置奖池抽取记录失败: {e}") + logger.exception(f"重置奖池抽取记录失败: {e}") def set_autostart(enabled: bool) -> bool: @@ -2150,5 +2150,5 @@ def set_autostart(enabled: bool) -> bool: else: return False except Exception as e: - logger.error(f"设置开机自启动失败: {e}") + logger.exception(f"设置开机自启动失败: {e}") return False diff --git a/app/tools/language_manager.py b/app/tools/language_manager.py index 9cadb3d8..6d0c6913 100644 --- a/app/tools/language_manager.py +++ b/app/tools/language_manager.py @@ -70,7 +70,7 @@ def _get_available_languages_from_modules(self) -> set[str]: (module_name, None) for module_name in sorted(discovered) ) except Exception as e: - logger.error(f"枚举语言模块失败: {e}") + logger.exception(f"枚举语言模块失败: {e}") # 扫描所有模块,收集语言代码 for module_name, file_path in module_entries: @@ -202,7 +202,7 @@ def _merge_language_files( else: logger.warning("未能通过 pkgutil.walk_packages 发现语言模块") except Exception as discovery_error: - logger.error(f"枚举语言模块失败: {discovery_error}") + logger.exception(f"枚举语言模块失败: {discovery_error}") if not module_entries: logger.warning("未找到任何语言模块,返回空语言数据") @@ -257,7 +257,7 @@ def _merge_language_files( merged[attr_name] = zh_cn_data except Exception as e: - logger.error(f"导入语言模块 {file_path} 时出错: {e}") + logger.exception(f"导入语言模块 {file_path} 时出错: {e}") continue return merged @@ -288,10 +288,10 @@ def _load_all_languages(self) -> None: language_data = json.load(f) self._loaded_languages[language_code] = language_data except Exception as e: - logger.error(f"加载语言文件 {filename} 时出错: {e}") + logger.exception(f"加载语言文件 {filename} 时出错: {e}") except Exception as e: - logger.error(f"加载语言文件夹时出错: {e}") + logger.exception(f"加载语言文件夹时出错: {e}") def get_current_language(self) -> str: """获取当前语言代码 diff --git a/app/tools/path_utils.py b/app/tools/path_utils.py index ccb74848..fb6aca9b 100644 --- a/app/tools/path_utils.py +++ b/app/tools/path_utils.py @@ -285,7 +285,7 @@ def remove_file(self, path: Union[str, Path]) -> bool: return True return False except Exception as e: - logger.error(f"删除文件失败: {path}, 错误: {e}") + logger.exception(f"删除文件失败: {path}, 错误: {e}") return False diff --git a/app/tools/personalised.py b/app/tools/personalised.py index 1616bddc..f1aaa677 100644 --- a/app/tools/personalised.py +++ b/app/tools/personalised.py @@ -43,7 +43,7 @@ def load_custom_font(): load_custom_font._font_cache = font_family return font_family except Exception as e: - logger.error(f"读取自定义设置失败,使用默认字体: {e}") + logger.exception(f"读取自定义设置失败,使用默认字体: {e}") font_family = _load_default_font() load_custom_font._font_cache = font_family return font_family @@ -67,7 +67,7 @@ def _get_font_family_setting(settings_path): personal_settings = settings.get("personal", {}) return personal_settings.get("font_family", "") except Exception as e: - logger.error(f"读取字体设置失败: {e}") + logger.exception(f"读取字体设置失败: {e}") return "" @@ -97,7 +97,7 @@ def _load_font_by_setting(font_family_setting): font_id = QFontDatabase.addApplicationFont(str(font_path)) if font_id < 0: - logger.error(f"加载自定义字体失败: {font_path}") + logger.exception(f"加载自定义字体失败: {font_path}") return None font_family = QFontDatabase.applicationFontFamilies(font_id)[0] @@ -108,7 +108,7 @@ def _load_font_by_setting(font_family_setting): font_id = QFontDatabase.addApplicationFont(str(font_path)) if font_id < 0: - logger.error(f"加载自定义字体失败: {font_path}") + logger.exception(f"加载自定义字体失败: {font_path}") return None font_family = QFontDatabase.applicationFontFamilies(font_id)[0] @@ -136,7 +136,7 @@ def _load_default_font(): font_id = QFontDatabase.addApplicationFont(str(font_path)) if font_id < 0: - logger.error(f"加载默认字体失败: {font_path}") + logger.exception(f"加载默认字体失败: {font_path}") return None font_family = QFontDatabase.applicationFontFamilies(font_id)[0] @@ -185,7 +185,7 @@ def _get_icon_map(): with open(map_path, "r", encoding="utf-8") as f: _icon_map_cache = json.load(f) except Exception as e: - logger.error(f"加载图标映射表失败: {e}") + logger.exception(f"加载图标映射表失败: {e}") _icon_map_cache = {} return _icon_map_cache @@ -229,7 +229,7 @@ def get_theme_icon(icon_name): _icon_cache[icon_name] = icon return icon except Exception as e: - logger.error(f"加载图标{icon_name}出错: {str(e)}") + logger.exception(f"加载图标{icon_name}出错: {str(e)}") # 返回默认图标 try: # 尝试使用码点创建默认图标 @@ -238,7 +238,7 @@ def get_theme_icon(icon_name): _icon_cache[icon_name] = default_icon return default_icon except Exception as default_error: - logger.error(f"加载默认图标也失败: {str(default_error)}") + logger.exception(f"加载默认图标也失败: {str(default_error)}") # 返回空的QIcon作为最后备选 return QIcon() @@ -309,7 +309,7 @@ def themeColor(): # 获取当前主题色 return themeColor.value except Exception as e: - logger.error(f"获取主题色失败: {e}") + logger.exception(f"获取主题色失败: {e}") # 返回默认主题色 return FALLBACK_THEME_COLOR @@ -337,8 +337,8 @@ def _convert_color_to_hex(color): if qcolor.isValid(): return qcolor.name() else: - logger.error(f"无效的颜色名称: {color}") + logger.exception(f"无效的颜色名称: {color}") return None else: - logger.error(f"不支持的颜色类型: {type(color)}") + logger.exception(f"不支持的颜色类型: {type(color)}") return None diff --git a/app/tools/settings_access.py b/app/tools/settings_access.py index cee81583..cd6fafc5 100644 --- a/app/tools/settings_access.py +++ b/app/tools/settings_access.py @@ -37,7 +37,7 @@ def run(self): # logger.debug(f"读取设置: {self.first_level_key}.{self.second_level_key} = {value}") self.finished.emit(value) except Exception as e: - logger.error(f"读取设置失败: {e}") + logger.exception(f"读取设置失败: {e}") default_value = self._get_default_value() self.finished.emit(default_value) @@ -162,7 +162,7 @@ def readme_settings(first_level_key: str, second_level_key: str): # logger.debug(f"使用默认设置: {first_level_key}.{second_level_key} = {default_value}") return default_value except Exception as e: - logger.error(f"读取设置失败: {e}") + logger.exception(f"读取设置失败: {e}") default_setting = _get_default_setting(first_level_key, second_level_key) if isinstance(default_setting, dict) and "default_value" in default_setting: return default_setting["default_value"] @@ -246,7 +246,7 @@ def update_settings(first_level_key: str, second_level_key: str, value: Any): first_level_key, second_level_key, value ) except Exception as e: - logger.error(f"设置更新失败: {e}") + logger.exception(f"设置更新失败: {e}") def _get_default_setting(first_level_key: str, second_level_key: str): @@ -323,8 +323,8 @@ def get_safe_font_size( return font_size except (ValueError, TypeError) as e: - logger.error(f"获取字体大小设置失败: {e}") + logger.exception(f"获取字体大小设置失败: {e}") return default_size except Exception as e: - logger.error(f"获取字体大小设置时发生未知错误: {e}") + logger.exception(f"获取字体大小设置时发生未知错误: {e}") return default_size diff --git a/app/tools/settings_default.py b/app/tools/settings_default.py index 4f6d9bb6..4053efa9 100644 --- a/app/tools/settings_default.py +++ b/app/tools/settings_default.py @@ -93,7 +93,7 @@ def manage_settings_file(): with open_file(settings_file, "r", encoding="utf-8") as f: current_settings = json.load(f) except Exception as e: - logger.error(f"读取设置文件失败: {e},将重新创建默认设置文件") + logger.exception(f"读取设置文件失败: {e},将重新创建默认设置文件") flat_settings = {} for first_level_key, first_level_value in default_settings.items(): flat_settings[first_level_key] = {} @@ -184,4 +184,4 @@ def manage_settings_file(): pass except Exception as e: - logger.error(f"管理设置文件时发生错误: {e}") + logger.exception(f"管理设置文件时发生错误: {e}") diff --git a/app/tools/update_utils.py b/app/tools/update_utils.py index be0d891f..e10748c8 100644 --- a/app/tools/update_utils.py +++ b/app/tools/update_utils.py @@ -50,7 +50,7 @@ def _run_async_func(async_func: Any, *args: Any, **kwargs: Any) -> Any: try: return asyncio.run(async_func(*args, **kwargs)) except Exception as e: - logger.error(f"运行异步函数失败: {e}") + logger.exception(f"运行异步函数失败: {e}") return None @@ -68,13 +68,13 @@ def check_zip_integrity(zip_path: str) -> bool: # 检查文件是否存在 if not Path(zip_path).exists(): - logger.error(f"ZIP文件不存在: {zip_path}") + logger.exception(f"ZIP文件不存在: {zip_path}") return False # 检查文件大小 file_size = Path(zip_path).stat().st_size if file_size == 0: - logger.error(f"ZIP文件大小为0: {zip_path}") + logger.exception(f"ZIP文件大小为0: {zip_path}") return False # 尝试打开并测试ZIP文件 @@ -82,22 +82,22 @@ def check_zip_integrity(zip_path: str) -> bool: # 测试ZIP文件的完整性 bad_file = zip_ref.testzip() if bad_file is not None: - logger.error(f"ZIP文件损坏,损坏的文件: {bad_file}") + logger.exception(f"ZIP文件损坏,损坏的文件: {bad_file}") return False # 检查ZIP文件是否为空 file_list = zip_ref.namelist() if not file_list: - logger.error(f"ZIP文件为空: {zip_path}") + logger.exception(f"ZIP文件为空: {zip_path}") return False logger.debug(f"ZIP文件完整性检查通过: {zip_path}") return True except zipfile.BadZipFile as e: - logger.error(f"ZIP文件格式错误: {e}") + logger.exception(f"ZIP文件格式错误: {e}") return False except Exception as e: - logger.error(f"检查ZIP文件完整性失败: {e}") + logger.exception(f"检查ZIP文件完整性失败: {e}") return False @@ -115,13 +115,13 @@ def check_deb_integrity(deb_path: str) -> bool: # 检查文件是否存在 if not Path(deb_path).exists(): - logger.error(f"DEB文件不存在: {deb_path}") + logger.exception(f"DEB文件不存在: {deb_path}") return False # 检查文件大小 file_size = Path(deb_path).stat().st_size if file_size == 0: - logger.error(f"DEB文件大小为0: {deb_path}") + logger.exception(f"DEB文件大小为0: {deb_path}") return False # DEB包实际上是ar归档格式 @@ -130,7 +130,7 @@ def check_deb_integrity(deb_path: str) -> bool: # 检查ar文件签名 magic = f.read(8) if not magic.startswith(b"!"): - logger.error(f"DEB文件格式错误,不是有效的ar归档: {deb_path}") + logger.exception(f"DEB文件格式错误,不是有效的ar归档: {deb_path}") return False # 读取ar文件内容 @@ -139,21 +139,21 @@ def check_deb_integrity(deb_path: str) -> bool: # 检查是否包含必要的文件(debian-binary, control.tar.gz, data.tar.gz) if b"debian-binary" not in content: - logger.error(f"DEB文件缺少debian-binary: {deb_path}") + logger.exception(f"DEB文件缺少debian-binary: {deb_path}") return False if b"control.tar" not in content: - logger.error(f"DEB文件缺少control.tar: {deb_path}") + logger.exception(f"DEB文件缺少control.tar: {deb_path}") return False if b"data.tar" not in content: - logger.error(f"DEB文件缺少data.tar: {deb_path}") + logger.exception(f"DEB文件缺少data.tar: {deb_path}") return False logger.debug(f"DEB包完整性检查通过: {deb_path}") return True except Exception as e: - logger.error(f"检查DEB包完整性失败: {e}") + logger.exception(f"检查DEB包完整性失败: {e}") return False @@ -176,7 +176,7 @@ def check_update_file_integrity(file_path: str, file_type: str = None) -> bool: elif file_ext == ".deb": file_type = "deb" else: - logger.error(f"不支持的更新文件类型: {file_ext}") + logger.exception(f"不支持的更新文件类型: {file_ext}") return False # 根据文件类型调用相应的检查函数 @@ -185,10 +185,10 @@ def check_update_file_integrity(file_path: str, file_type: str = None) -> bool: elif file_type == "deb": return check_deb_integrity(file_path) else: - logger.error(f"不支持的文件类型: {file_type}") + logger.exception(f"不支持的文件类型: {file_type}") return False except Exception as e: - logger.error(f"检查更新文件完整性失败: {e}") + logger.exception(f"检查更新文件完整性失败: {e}") return False @@ -238,7 +238,7 @@ def extract_zip(zip_path: str, target_dir: str | Path, overwrite: bool = True) - logger.debug(f"文件解压完成: {zip_path} 到 {target_dir}") return True except Exception as e: - logger.error(f"解压文件失败: {e}") + logger.exception(f"解压文件失败: {e}") return False @@ -338,7 +338,7 @@ async def get_best_source() -> dict: return best_source except Exception as e: - logger.error(f"获取最佳镜像源失败: {e}") + logger.exception(f"获取最佳镜像源失败: {e}") return UPDATE_SOURCES[0] # 返回默认源 @@ -359,7 +359,7 @@ def get_update_source_url() -> str: else: return "https://github.com" except Exception as e: - logger.error(f"获取更新源 URL 失败: {e}") + logger.exception(f"获取更新源 URL 失败: {e}") return "https://github.com" @@ -379,7 +379,7 @@ async def get_update_source_url_async() -> str: else: return "https://github.com" except Exception as e: - logger.error(f"获取更新源 URL 失败: {e}") + logger.exception(f"获取更新源 URL 失败: {e}") return "https://github.com" @@ -508,7 +508,7 @@ async def get_metadata_info_async() -> dict | None: continue # 所有镜像源都失败了 - logger.error("所有镜像源都获取 metadata.yaml 文件失败") + logger.exception("所有镜像源都获取 metadata.yaml 文件失败") return None else: # 使用指定的更新源 @@ -543,12 +543,12 @@ async def get_metadata_info_async() -> dict | None: ) return metadata except Exception as e: - logger.error( + logger.exception( f"使用指定的更新源 {source['name']} 获取 metadata.yaml 失败: {e}" ) return None else: - logger.error(f"无效的更新源索引: {source_index}") + logger.exception(f"无效的更新源索引: {source_index}") return None @@ -601,7 +601,7 @@ async def get_latest_version_async(channel: int | None = None) -> dict | None: ) return {"version": version, "version_no": version_no} except Exception as e: - logger.error(f"获取最新版本信息失败: {e}") + logger.exception(f"获取最新版本信息失败: {e}") return None @@ -633,7 +633,7 @@ def compare_versions(current_version: str, latest_version: str) -> int: try: # 检查版本号是否为空 if not current_version or not latest_version: - logger.error( + logger.exception( f"比较版本号失败: 版本号为空,current={current_version}, latest={latest_version}" ) return -1 @@ -692,7 +692,7 @@ def compare_versions(current_version: str, latest_version: str) -> int: return 0 # 版本号完全相同 except Exception as e: - logger.error(f"比较版本号失败: {e}") + logger.exception(f"比较版本号失败: {e}") return -1 @@ -740,7 +740,7 @@ def get_update_download_url( logger.debug(f"生成更新下载 URL 成功: {download_url}") return download_url except Exception as e: - logger.error(f"生成更新下载 URL 失败: {e}") + logger.exception(f"生成更新下载 URL 失败: {e}") # 返回默认的 GitHub 下载 URL return f"https://github.com/SECTL/SecRandom/releases/download/{version}/SecRandom-{system}-{version}-{arch}-{struct}.zip" @@ -789,7 +789,7 @@ async def get_update_download_url_async( logger.debug(f"生成更新下载 URL 成功: {download_url}") return download_url except Exception as e: - logger.error(f"生成更新下载 URL 失败: {e}") + logger.exception(f"生成更新下载 URL 失败: {e}") # 返回默认的 GitHub 下载 URL return f"https://github.com/SECTL/SecRandom/releases/download/{version}/SecRandom-{system}-{version}-{arch}-{struct}.zip" @@ -906,7 +906,7 @@ async def download_update_async( file_path.unlink() logger.info(f"已删除损坏的文件: {file_path}") except Exception as unlink_error: - logger.error(f"删除损坏文件失败: {unlink_error}") + logger.exception(f"删除损坏文件失败: {unlink_error}") # 继续尝试下一个镜像源 continue @@ -920,11 +920,11 @@ async def download_update_async( file_path.unlink() logger.info(f"已删除部分下载文件: {file_path}") except Exception as unlink_error: - logger.error(f"删除部分下载文件失败: {unlink_error}") + logger.exception(f"删除部分下载文件失败: {unlink_error}") continue # 所有镜像源都失败了 - logger.error("所有镜像源都下载更新文件失败") + logger.exception("所有镜像源都下载更新文件失败") return None @@ -982,18 +982,18 @@ async def install_update_async(file_path: str) -> bool: # 验证更新文件存在 if not Path(file_path).exists(): - logger.error(f"更新文件不存在: {file_path}") + logger.exception(f"更新文件不存在: {file_path}") return False # 检查更新文件完整性 if not check_update_file_integrity(file_path): - logger.error(f"更新文件不完整或已损坏: {file_path}") + logger.exception(f"更新文件不完整或已损坏: {file_path}") # 删除损坏的文件 try: Path(file_path).unlink() logger.info(f"已删除损坏的更新文件: {file_path}") except Exception as e: - logger.error(f"删除损坏的更新文件失败: {e}") + logger.exception(f"删除损坏的更新文件失败: {e}") return False # 判断是否是开发环境 @@ -1026,12 +1026,12 @@ async def install_update_async(file_path: str) -> bool: Path(file_path).unlink() logger.info(f"更新文件已删除: {file_path}") except Exception as e: - logger.error(f"删除更新文件失败: {e}") + logger.exception(f"删除更新文件失败: {e}") logger.info(f"开发环境更新文件安装成功: {file_path}") return True else: - logger.error(f"开发环境更新文件安装失败: {file_path}") + logger.exception(f"开发环境更新文件安装失败: {file_path}") return False else: # 生产环境:新开进程安装,主进程关闭 @@ -1040,7 +1040,7 @@ async def install_update_async(file_path: str) -> bool: # 获取根目录 root_dir = get_app_root() if not Path(root_dir).exists(): - logger.error(f"应用根目录不存在: {root_dir}") + logger.exception(f"应用根目录不存在: {root_dir}") return False # 创建临时安装脚本 @@ -1132,7 +1132,7 @@ def extract_zip(zip_path, target_dir, overwrite=True): logger.info(f"文件解压完成: {zip_path} 到 {target_dir}") return True except Exception as e: - logger.error(f"解压文件失败: {e}") + logger.exception(f"解压文件失败: {e}") return False @@ -1151,7 +1151,7 @@ def restart_application(root_dir): break if not main_program: - logger.error("未找到主程序文件") + logger.exception("未找到主程序文件") return False logger.info(f"找到主程序文件: {main_program}") @@ -1181,7 +1181,7 @@ def restart_application(root_dir): logger.info("应用程序重启成功") return True except Exception as e: - logger.error(f"重启应用程序失败: {e}") + logger.exception(f"重启应用程序失败: {e}") return False @@ -1197,11 +1197,11 @@ def restart_application(root_dir): # 验证参数 if not Path(update_file).exists(): - logger.error(f"更新文件不存在: {update_file}") + logger.exception(f"更新文件不存在: {update_file}") sys.exit(1) if not Path(root_dir).exists(): - logger.error(f"根目录不存在: {root_dir}") + logger.exception(f"根目录不存在: {root_dir}") sys.exit(1) # 等待一段时间,确保主进程已关闭(可配置) @@ -1224,13 +1224,13 @@ def restart_application(root_dir): Path(update_file).unlink() logger.info(f"更新文件已删除: {update_file}") except Exception as e: - logger.error(f"删除更新文件失败: {e}") + logger.exception(f"删除更新文件失败: {e}") else: - logger.error("更新安装失败") + logger.exception("更新安装失败") sys.exit(1) except Exception as e: - logger.error(f"更新安装脚本执行失败: {e}") + logger.exception(f"更新安装脚本执行失败: {e}") sys.exit(1) """ @@ -1243,7 +1243,7 @@ def restart_application(root_dir): temp_script_path = temp_script.name logger.debug(f"临时脚本已创建: {temp_script_path}") except Exception as e: - logger.error(f"创建临时脚本失败: {e}") + logger.exception(f"创建临时脚本失败: {e}") return False try: @@ -1266,12 +1266,12 @@ def restart_application(root_dir): logger.info("生产环境更新进程已启动,主进程将关闭") sys.exit(0) except Exception as e: - logger.error(f"启动更新进程失败: {e}") + logger.exception(f"启动更新进程失败: {e}") return False return True except Exception as e: - logger.error(f"安装更新文件失败: {e}") + logger.exception(f"安装更新文件失败: {e}") return False finally: # 说明: @@ -1618,7 +1618,7 @@ def safe_call_update_interface(method_name, *args): if success: logger.debug("自动安装更新成功") else: - logger.error("自动安装更新失败") + logger.exception("自动安装更新失败") return else: # 文件损坏,需要重新下载 @@ -1630,7 +1630,7 @@ def safe_call_update_interface(method_name, *args): expected_file_path.unlink() logger.debug(f"已删除损坏的文件: {expected_file_path}") except Exception as e: - logger.error(f"删除损坏文件失败: {e}") + logger.exception(f"删除损坏文件失败: {e}") # 继续执行下载流程 if compare_result == 1: @@ -1698,7 +1698,7 @@ def format_size(size_bytes): expected_file_path.unlink() logger.debug(f"已删除损坏的文件: {expected_file_path}") except Exception as e: - logger.error(f"删除损坏文件失败: {e}") + logger.exception(f"删除损坏文件失败: {e}") # 继续执行下载流程 # 通知更新页面开始下载 @@ -1776,7 +1776,7 @@ def format_size(size_bytes): # 更新全局状态 update_status_manager.set_download_cancelled() else: - logger.error("自动下载更新失败") + logger.exception("自动下载更新失败") # 通知更新页面下载失败 safe_call_update_interface("set_download_failed") # 更新全局状态 @@ -1795,7 +1795,7 @@ def format_size(size_bytes): # 更新上次检查时间 safe_call_update_interface("update_last_check_time") except Exception as e: - logger.error(f"启动时检查更新失败: {e}") + logger.exception(f"启动时检查更新失败: {e}") # 通知更新页面检查失败 safe_call_update_interface("set_check_failed") finally: diff --git a/app/view/another_window/contributor.py b/app/view/another_window/contributor.py index b69a56c3..407a651c 100644 --- a/app/view/another_window/contributor.py +++ b/app/view/another_window/contributor.py @@ -283,7 +283,7 @@ def addContributorCard(self, contributor): return card except RuntimeError as e: - logger.error(f"创建贡献者卡片时出错: {e}") + logger.exception(f"创建贡献者卡片时出错: {e}") return None def resizeEvent(self, event): @@ -304,7 +304,7 @@ def _delayed_update_layout(self): if self.isVisible(): self.update_layout() except RuntimeError as e: - logger.error(f"延迟布局更新错误: {e}") + logger.exception(f"延迟布局更新错误: {e}") def closeEvent(self, event): """窗口关闭事件""" diff --git a/app/view/another_window/current_config_viewer.py b/app/view/another_window/current_config_viewer.py index 8bf78461..e50ab6cc 100644 --- a/app/view/another_window/current_config_viewer.py +++ b/app/view/another_window/current_config_viewer.py @@ -139,7 +139,7 @@ def load_current_config(self): ] ) except Exception as e: - logger.error(f"解析文件{file_name}失败: {e}") + logger.exception(f"解析文件{file_name}失败: {e}") data.append( [ "", @@ -188,7 +188,7 @@ def load_current_config(self): self.table_widget.setItem(row, 4, teacher_item) except Exception as e: - logger.error(f"加载当前配置失败: {e}") + logger.exception(f"加载当前配置失败: {e}") self.table_widget.setRowCount(1) self.table_widget.setItem( 0, diff --git a/app/view/another_window/prize/import_prize_name.py b/app/view/another_window/prize/import_prize_name.py index 472ee5f9..f5ef54f2 100644 --- a/app/view/another_window/prize/import_prize_name.py +++ b/app/view/another_window/prize/import_prize_name.py @@ -425,7 +425,7 @@ def __load_file(self, file_path: str): self.fileLoaded.emit(data, columns) except Exception as e: - logger.error(f"加载文件失败: {e}") + logger.exception(f"加载文件失败: {e}") # 通过信号通知UI线程文件加载失败 self.fileLoadError.emit(str(e)) @@ -637,7 +637,7 @@ def __import_data(self): duration=3000, ) show_notification(NotificationType.ERROR, config, parent=self) - logger.error(f"导入数据失败: {e}") + logger.exception(f"导入数据失败: {e}") def __save_prize_data(self, pool_name: str, prize_rows: List[Dict[str, Any]]): """保存奖品数据到班级名单文件""" diff --git a/app/view/another_window/prize/prize_name_setting.py b/app/view/another_window/prize/prize_name_setting.py index 99b4b27a..0a151745 100644 --- a/app/view/another_window/prize/prize_name_setting.py +++ b/app/view/another_window/prize/prize_name_setting.py @@ -128,7 +128,7 @@ def __load_existing_prize_names(self): return names except Exception as e: - logger.error(f"加载奖品名称失败: {str(e)}") + logger.exception(f"加载奖品名称失败: {str(e)}") self.initial_names = [] return [] @@ -303,7 +303,7 @@ def __save_names(self): duration=3000, ) show_notification(NotificationType.ERROR, config, parent=self) - logger.error(f"保存奖品名称失败: {e}") + logger.exception(f"保存奖品名称失败: {e}") def __cancel(self): """取消操作""" diff --git a/app/view/another_window/prize/prize_weight_setting.py b/app/view/another_window/prize/prize_weight_setting.py index 9b35d8de..a6c2b8b1 100644 --- a/app/view/another_window/prize/prize_weight_setting.py +++ b/app/view/another_window/prize/prize_weight_setting.py @@ -129,7 +129,7 @@ def __load_existing_weights(self): return weights except Exception as e: - logger.error(f"加载奖品权重失败: {str(e)}") + logger.exception(f"加载奖品权重失败: {str(e)}") self.initial_weights = [] return [] @@ -315,7 +315,7 @@ def __save_weights(self): duration=3000, ) show_notification(NotificationType.ERROR, config, parent=self) - logger.error(f"保存奖品权重失败: {e}") + logger.exception(f"保存奖品权重失败: {e}") def __cancel(self): """取消操作""" diff --git a/app/view/another_window/prize/set_pool_name.py b/app/view/another_window/prize/set_pool_name.py index fcddcac5..7dd11cdd 100644 --- a/app/view/another_window/prize/set_pool_name.py +++ b/app/view/another_window/prize/set_pool_name.py @@ -92,7 +92,7 @@ def __create_pool_name_input_area(self): self.text_edit.setPlainText("\n".join(pool_names)) self.initial_pool_names = pool_names.copy() # 保存初始奖池列表 except Exception as e: - logger.error(f"加载奖池名称失败: {str(e)}") + logger.exception(f"加载奖池名称失败: {str(e)}") self.initial_pool_names = [] # 出错时设为空列表 input_layout.addWidget(self.text_edit) @@ -178,7 +178,7 @@ def __on_text_changed(self): # 更新初始奖池列表为当前列表,避免重复提示 self.initial_pool_names = current_pool_names.copy() except Exception as e: - logger.error(f"检测奖池变化失败: {e}") + logger.exception(f"检测奖池变化失败: {e}") def __save_pool_names(self): """保存奖池名称""" @@ -342,7 +342,7 @@ def __save_pool_names(self): duration=3000, ) show_notification(NotificationType.ERROR, config, parent=self) - logger.error(f"保存奖池名称失败: {e}") + logger.exception(f"保存奖池名称失败: {e}") def __cancel(self): """取消操作""" diff --git a/app/view/another_window/remaining_list.py b/app/view/another_window/remaining_list.py index c11a387d..3db81062 100644 --- a/app/view/another_window/remaining_list.py +++ b/app/view/another_window/remaining_list.py @@ -648,7 +648,7 @@ def _delayed_update_layout(self) -> None: if self.isVisible() and self.cards: self._update_grid_layout() except RuntimeError as e: - logger.error(f"延迟布局更新错误: {e}") + logger.exception(f"延迟布局更新错误: {e}") # ------------------------------------------------------------------ # 外部接口 diff --git a/app/view/another_window/student/gender_setting.py b/app/view/another_window/student/gender_setting.py index 51fa0516..398f0bb8 100644 --- a/app/view/another_window/student/gender_setting.py +++ b/app/view/another_window/student/gender_setting.py @@ -131,7 +131,7 @@ def __load_existing_genders(self): return genders except Exception as e: - logger.error(f"加载性别失败: {str(e)}") + logger.exception(f"加载性别失败: {str(e)}") self.initial_genders = [] return [] @@ -311,7 +311,7 @@ def __save_genders(self): duration=3000, ) show_notification(NotificationType.ERROR, config, parent=self) - logger.error(f"保存性别失败: {e}") + logger.exception(f"保存性别失败: {e}") def __cancel(self): """取消操作""" diff --git a/app/view/another_window/student/group_setting.py b/app/view/another_window/student/group_setting.py index dd069529..8644eb09 100644 --- a/app/view/another_window/student/group_setting.py +++ b/app/view/another_window/student/group_setting.py @@ -131,7 +131,7 @@ def __load_existing_groups(self): return groups except Exception as e: - logger.error(f"加载小组失败: {str(e)}") + logger.exception(f"加载小组失败: {str(e)}") self.initial_groups = [] return [] @@ -309,7 +309,7 @@ def __save_groups(self): duration=3000, ) show_notification(NotificationType.ERROR, config, parent=self) - logger.error(f"保存小组失败: {e}") + logger.exception(f"保存小组失败: {e}") def __cancel(self): """取消操作""" diff --git a/app/view/another_window/student/import_student_name.py b/app/view/another_window/student/import_student_name.py index 9a1197b2..8284d970 100644 --- a/app/view/another_window/student/import_student_name.py +++ b/app/view/another_window/student/import_student_name.py @@ -429,7 +429,7 @@ def __load_file(self, file_path: str): self.fileLoaded.emit(data, columns) except Exception as e: - logger.error(f"加载文件失败: {e}") + logger.exception(f"加载文件失败: {e}") # 通过信号通知UI线程文件加载失败 self.fileLoadError.emit(str(e)) @@ -671,7 +671,7 @@ def __import_data(self): duration=3000, ) show_notification(NotificationType.ERROR, config, parent=self) - logger.error(f"导入数据失败: {e}") + logger.exception(f"导入数据失败: {e}") def __save_student_data(self, class_name: str, student_data: List[Dict[str, Any]]): """保存学生数据到班级名单文件""" diff --git a/app/view/another_window/student/name_setting.py b/app/view/another_window/student/name_setting.py index 7e9d5ce9..bccfb721 100644 --- a/app/view/another_window/student/name_setting.py +++ b/app/view/another_window/student/name_setting.py @@ -129,7 +129,7 @@ def __load_existing_names(self): return names except Exception as e: - logger.error(f"加载姓名失败: {str(e)}") + logger.exception(f"加载姓名失败: {str(e)}") self.initial_names = [] return [] @@ -298,7 +298,7 @@ def __save_names(self): duration=3000, ) show_notification(NotificationType.ERROR, config, parent=self) - logger.error(f"保存姓名失败: {e}") + logger.exception(f"保存姓名失败: {e}") def __cancel(self): """取消操作""" diff --git a/app/view/another_window/student/set_class_name.py b/app/view/another_window/student/set_class_name.py index 067fce17..fa61549f 100644 --- a/app/view/another_window/student/set_class_name.py +++ b/app/view/another_window/student/set_class_name.py @@ -92,7 +92,7 @@ def __create_class_name_input_area(self): self.text_edit.setPlainText("\n".join(class_names)) self.initial_class_names = class_names.copy() # 保存初始班级列表 except Exception as e: - logger.error(f"加载班级名称失败: {str(e)}") + logger.exception(f"加载班级名称失败: {str(e)}") self.initial_class_names = [] # 出错时设为空列表 input_layout.addWidget(self.text_edit) @@ -178,7 +178,7 @@ def __on_text_changed(self): # 更新初始班级列表为当前列表,避免重复提示 self.initial_class_names = current_class_names.copy() except Exception as e: - logger.error(f"检测班级变化失败: {e}") + logger.exception(f"检测班级变化失败: {e}") def __save_class_names(self): """保存班级名称""" @@ -347,7 +347,7 @@ def __save_class_names(self): duration=3000, ) show_notification(NotificationType.ERROR, config, parent=self) - logger.error(f"保存班级名称失败: {e}") + logger.exception(f"保存班级名称失败: {e}") def __close_window(self): """关闭窗口""" diff --git a/app/view/another_window/usb/bind_usb.py b/app/view/another_window/usb/bind_usb.py index ca928dfe..22867876 100644 --- a/app/view/another_window/usb/bind_usb.py +++ b/app/view/another_window/usb/bind_usb.py @@ -216,7 +216,7 @@ def __bind(self): f"{get_content_name_async('basic_safety_settings', 'usb_bind_success')}: {text}" ) except Exception as e: - logger.error(f"绑定设备失败:{device}, 错误:{e}") + logger.exception(f"绑定设备失败:{device}, 错误:{e}") self._notify_error(str(e)) def __cancel(self): diff --git a/app/view/floating_window/levitation.py b/app/view/floating_window/levitation.py index 0daa54d3..d5486fe5 100644 --- a/app/view/floating_window/levitation.py +++ b/app/view/floating_window/levitation.py @@ -1474,7 +1474,7 @@ def _create_arrow_button(self, direction, x, y): self.arrow_button.setIcon(icon) self.arrow_button.setIconSize(self._storage_icon_size) except Exception as e: - logger.error(f"加载图标失败: {e}") + logger.exception(f"加载图标失败: {e}") # 回退到箭头模式 if direction == "right": self.arrow_button.setText(">") @@ -1757,7 +1757,7 @@ def _keep_window_on_top(self): try: self.raise_() # 将窗口提升到最前面 except Exception as e: - logger.error(f"保持窗口置顶失败: {e}") + logger.exception(f"保持窗口置顶失败: {e}") def closeEvent(self, event): """窗口关闭事件 - 清理所有定时器资源""" diff --git a/app/view/main/lottery.py b/app/view/main/lottery.py index 333aecd7..1cb617bd 100644 --- a/app/view/main/lottery.py +++ b/app/view/main/lottery.py @@ -96,7 +96,7 @@ def closeEvent(self, event): if hasattr(self, "press_timer"): self.press_timer.stop() except Exception as e: - logger.error(f"清理文件监控器失败: {e}") + logger.exception(f"清理文件监控器失败: {e}") super().closeEvent(event) def initUI(self): @@ -378,7 +378,7 @@ def add_control_widget_if_enabled( if is_enabled: layout.addWidget(widget, alignment=Qt.AlignmentFlag.AlignCenter) except Exception as e: - logger.error(f"添加控件 {setting_name} 时出错: {e}") + logger.exception(f"添加控件 {setting_name} 时出错: {e}") # 出错时默认添加控件 layout.addWidget(widget, alignment=Qt.AlignmentFlag.AlignCenter) @@ -446,7 +446,7 @@ def on_pool_changed(self): ): QTimer.singleShot(APP_INIT_DELAY, self._update_remaining_list_delayed) except Exception as e: - logger.error(f"切换奖池时发生错误: {e}") + logger.exception(f"切换奖池时发生错误: {e}") def on_class_changed(self): """当班级选择改变时,更新范围选择、性别选择""" @@ -490,7 +490,7 @@ def on_class_changed(self): self.range_combobox.setEnabled(True) self.gender_combobox.setEnabled(True) except Exception as e: - logger.error(f"切换班级时发生错误: {e}") + logger.exception(f"切换班级时发生错误: {e}") finally: self.range_combobox.blockSignals(False) self.gender_combobox.blockSignals(False) @@ -510,7 +510,7 @@ def on_filter_changed(self): ): QTimer.singleShot(APP_INIT_DELAY, self._update_remaining_list_delayed) except Exception as e: - logger.error(f"切换筛选条件时发生错误: {e}") + logger.exception(f"切换筛选条件时发生错误: {e}") def _update_remaining_list_delayed(self): """延迟更新剩余名单窗口的方法""" @@ -556,7 +556,7 @@ def _update_remaining_list_delayed(self): self.remaining_count ) except Exception as e: - logger.error(f"延迟更新剩余名单时发生错误: {e}") + logger.exception(f"延迟更新剩余名单时发生错误: {e}") def _do_start_draw(self): """实际执行开始抽取的逻辑""" @@ -794,7 +794,7 @@ def play_voice_result(self): class_name=self.pool_list_combobox.currentText(), ) except Exception as e: - logger.error(f"播放语音失败: {e}", exc_info=True) + logger.exception(f"播放语音失败: {e}", exc_info=True) def animate_result(self): """动画过程中更新显示""" @@ -863,7 +863,7 @@ def draw_random(self): paired.append((pid, display_name, pexist)) self.final_selected_students = paired except Exception as e: - logger.error(f"奖池跟随学生拼接失败: {e}") + logger.exception(f"奖池跟随学生拼接失败: {e}") if self.is_animating: self.display_result_animated( @@ -1068,7 +1068,7 @@ def update_remaining_list_window(self): source="lottery", ) except Exception as e: - logger.error(f"更新剩余名单窗口内容失败: {e}") + logger.exception(f"更新剩余名单窗口内容失败: {e}") def show_remaining_list(self): """显示剩余名单窗口""" @@ -1088,7 +1088,7 @@ def show_remaining_list(self): self.update_remaining_list_window() return except Exception as e: - logger.error(f"激活剩余名单窗口失败: {e}") + logger.exception(f"激活剩余名单窗口失败: {e}") # 如果激活失败,继续创建新窗口 # 创建新窗口 @@ -1139,7 +1139,7 @@ def setup_file_watcher(self): self.file_watcher.fileChanged.connect(self.on_file_changed) except Exception as e: - logger.error(f"设置文件监控器失败: {e}") + logger.exception(f"设置文件监控器失败: {e}") def on_directory_changed(self, path): """当文件夹内容发生变化时触发""" @@ -1148,7 +1148,7 @@ def on_directory_changed(self, path): self._sync_watcher_files() QTimer.singleShot(500, self.refresh_pool_list) except Exception as e: - logger.error(f"处理文件夹变化事件失败: {e}") + logger.exception(f"处理文件夹变化事件失败: {e}") def on_file_changed(self, path): """当文件内容发生变化时触发""" @@ -1156,7 +1156,7 @@ def on_file_changed(self, path): # 文件内容变化同样刷新下拉框 QTimer.singleShot(500, self.refresh_pool_list) except Exception as e: - logger.error(f"处理文件变化事件失败: {e}") + logger.exception(f"处理文件变化事件失败: {e}") def _sync_watcher_files(self): """同步奖池目录下的文件到文件监控器""" @@ -1182,7 +1182,7 @@ def _sync_watcher_files(self): except Exception: pass except Exception as e: - logger.error(f"同步奖池文件监控失败: {e}") + logger.exception(f"同步奖池文件监控失败: {e}") def refresh_pool_list(self): """刷新奖池列表下拉框""" @@ -1208,7 +1208,7 @@ def refresh_pool_list(self): self.on_class_changed() except Exception as e: - logger.error(f"刷新奖池列表失败: {e}") + logger.exception(f"刷新奖池列表失败: {e}") def populate_lists(self): """在后台填充奖池/范围/性别下拉框并更新奖数统计""" @@ -1301,7 +1301,7 @@ def populate_lists(self): self._adjustControlWidgetWidths() except Exception as e: - logger.error(f"延迟填充列表失败: {e}") + logger.exception(f"延迟填充列表失败: {e}") def setupSettingsListener(self): """设置设置监听器,监听页面管理设置变化""" diff --git a/app/view/main/quick_draw_animation.py b/app/view/main/quick_draw_animation.py index 7b2f1e86..3be0b157 100644 --- a/app/view/main/quick_draw_animation.py +++ b/app/view/main/quick_draw_animation.py @@ -157,7 +157,7 @@ def draw_random_students(self): """独立的随机学生抽取逻辑,不依赖roll_call_widget的状态""" class_name = readme_settings_async("quick_draw_settings", "default_class") if not class_name: - logger.error("draw_random_students: 未设置默认抽取名单") + logger.exception("draw_random_students: 未设置默认抽取名单") return False group_index = 0 @@ -243,7 +243,7 @@ def execute_quick_draw(self, quick_draw_settings): self.animation_finished.emit() except Exception as e: - logger.error(f"execute_quick_draw: 执行闪抽流程失败: {e}") + logger.exception(f"execute_quick_draw: 执行闪抽流程失败: {e}") self.stop_animation() def display_final_result(self, quick_draw_settings): @@ -288,7 +288,7 @@ def display_final_result(self, quick_draw_settings): ) except Exception as e: - logger.error(f"display_final_result: 显示最终结果失败: {e}") + logger.exception(f"display_final_result: 显示最终结果失败: {e}") def _record_drawn_student(self, quick_draw_settings): """记录已抽取的学生 @@ -309,7 +309,7 @@ def _record_drawn_student(self, quick_draw_settings): ) except Exception as e: - logger.error(f"_record_drawn_student: 记录已抽取学生失败: {e}") + logger.exception(f"_record_drawn_student: 记录已抽取学生失败: {e}") def _update_floating_notification(self): """更新浮窗通知内容 @@ -330,7 +330,7 @@ def _update_floating_notification(self): ) except Exception as e: - logger.error(f"_update_floating_notification: 更新浮窗通知失败: {e}") + logger.exception(f"_update_floating_notification: 更新浮窗通知失败: {e}") def display_result_animated( self, selected_students, class_name, display_settings, draw_count diff --git a/app/view/main/roll_call.py b/app/view/main/roll_call.py index dea3a488..2a2adc9c 100644 --- a/app/view/main/roll_call.py +++ b/app/view/main/roll_call.py @@ -94,7 +94,7 @@ def closeEvent(self, event): if hasattr(self, "press_timer"): self.press_timer.stop() except Exception as e: - logger.error(f"清理文件监控器失败: {e}") + logger.exception(f"清理文件监控器失败: {e}") super().closeEvent(event) def initUI(self): @@ -357,7 +357,7 @@ def add_control_widget_if_enabled( if is_enabled: layout.addWidget(widget, alignment=Qt.AlignmentFlag.AlignCenter) except Exception as e: - logger.error(f"添加控件 {setting_name} 时出错: {e}") + logger.exception(f"添加控件 {setting_name} 时出错: {e}") # 出错时默认添加控件 layout.addWidget(widget, alignment=Qt.AlignmentFlag.AlignCenter) @@ -462,7 +462,7 @@ def on_class_changed(self): ): QTimer.singleShot(APP_INIT_DELAY, self._update_remaining_list_delayed) except Exception as e: - logger.error(f"切换班级时发生错误: {e}") + logger.exception(f"切换班级时发生错误: {e}") finally: self.range_combobox.blockSignals(False) self.gender_combobox.blockSignals(False) @@ -489,7 +489,7 @@ def on_filter_changed(self): ): QTimer.singleShot(APP_INIT_DELAY, self._update_remaining_list_delayed) except Exception as e: - logger.error(f"切换筛选条件时发生错误: {e}") + logger.exception(f"切换筛选条件时发生错误: {e}") def _update_remaining_list_delayed(self): """延迟更新剩余名单窗口的方法""" @@ -535,7 +535,7 @@ def _update_remaining_list_delayed(self): self.remaining_count ) except Exception as e: - logger.error(f"延迟更新剩余名单时发生错误: {e}") + logger.exception(f"延迟更新剩余名单时发生错误: {e}") def _do_start_draw(self): """实际执行开始抽取的逻辑""" @@ -717,7 +717,7 @@ def play_voice_result(self): class_name=self.list_combobox.currentText(), ) except Exception as e: - logger.error(f"播放语音失败: {e}", exc_info=True) + logger.exception(f"播放语音失败: {e}", exc_info=True) def animate_result(self): """动画过程中更新显示""" @@ -968,7 +968,7 @@ def update_remaining_list_window(self): source="roll_call", ) except Exception as e: - logger.error(f"更新剩余名单窗口内容失败: {e}") + logger.exception(f"更新剩余名单窗口内容失败: {e}") def show_remaining_list(self): """显示剩余名单窗口""" @@ -988,7 +988,7 @@ def show_remaining_list(self): self.update_remaining_list_window() return except Exception as e: - logger.error(f"激活剩余名单窗口失败: {e}") + logger.exception(f"激活剩余名单窗口失败: {e}") # 如果激活失败,继续创建新窗口 # 创建新窗口 @@ -1036,21 +1036,21 @@ def setup_file_watcher(self): self.file_watcher.fileChanged.connect(self.on_file_changed) except Exception as e: - logger.error(f"设置文件监控器失败: {e}") + logger.exception(f"设置文件监控器失败: {e}") def on_directory_changed(self, path): """当文件夹内容发生变化时触发""" try: QTimer.singleShot(500, self.refresh_class_list) except Exception as e: - logger.error(f"处理文件夹变化事件失败: {e}") + logger.exception(f"处理文件夹变化事件失败: {e}") def on_file_changed(self, path): """当文件内容发生变化时触发""" try: QTimer.singleShot(500, self.refresh_class_list) except Exception as e: - logger.error(f"处理文件变化事件失败: {e}") + logger.exception(f"处理文件变化事件失败: {e}") def refresh_class_list(self): """刷新班级列表下拉框""" @@ -1076,7 +1076,7 @@ def refresh_class_list(self): self.on_class_changed() except Exception as e: - logger.error(f"刷新班级列表失败: {e}") + logger.exception(f"刷新班级列表失败: {e}") def populate_lists(self): """在后台填充班级/范围/性别下拉框并更新人数统计""" @@ -1088,7 +1088,7 @@ def populate_lists(self): self._adjustControlWidgetWidths() except Exception as e: - logger.error(f"延迟填充列表失败: {e}") + logger.exception(f"延迟填充列表失败: {e}") def _populate_class_list(self): """填充班级列表""" diff --git a/app/view/main/window.py b/app/view/main/window.py index d0be7bed..4ac6e123 100644 --- a/app/view/main/window.py +++ b/app/view/main/window.py @@ -163,13 +163,13 @@ def restart_ipc_server(self, new_port: int): logger.info(f"IPC服务器已在端口 {new_port} 上重新启动") return True else: - logger.error(f"IPC服务器在端口 {new_port} 上启动失败") + logger.exception(f"IPC服务器在端口 {new_port} 上启动失败") return False else: - logger.error("无法访问URLHandler实例") + logger.exception("无法访问URLHandler实例") return False else: - logger.error("无法获取url_handler_instance") + logger.exception("无法获取url_handler_instance") return False def _position_window(self): @@ -210,7 +210,7 @@ def _apply_window_visibility_settings(self): try: self.show() except Exception as e: - logger.error(f"加载窗口显示设置失败: {e}") + logger.exception(f"加载窗口显示设置失败: {e}") def createSubInterface(self): """创建子界面 @@ -388,7 +388,7 @@ def _handle_quick_draw(self): # 确保roll_call_page已经创建 if not hasattr(self, "roll_call_page") or not self.roll_call_page: - logger.error("_handle_quick_draw: roll_call_page未创建") + logger.exception("_handle_quick_draw: roll_call_page未创建") return logger.debug("_handle_quick_draw: roll_call_page已创建") @@ -436,7 +436,7 @@ def _handle_quick_draw(self): "_handle_quick_draw: 创建contentWidget后获取contentWidget成功" ) else: - logger.error("_handle_quick_draw: 无法创建或获取roll_call_widget") + logger.exception("_handle_quick_draw: 无法创建或获取roll_call_widget") return logger.debug("_handle_quick_draw: 成功获取roll_call_widget") @@ -661,7 +661,7 @@ def close_window_secrandom(self): CSharpIPCHandler.instance().stop_ipc_client() logger.debug("C# IPC 停止请求已发出") except Exception as e: - logger.error(f"停止 IPC 客户端失败: {e}") + logger.exception(f"停止 IPC 客户端失败: {e}") # 显式关闭所有顶层窗口(包括悬浮窗、设置窗口等) try: @@ -674,7 +674,7 @@ def close_window_secrandom(self): if hasattr(widget, "hide"): widget.hide() except Exception as e: - logger.error(f"关闭其他窗口时出错: {e}") + logger.exception(f"关闭其他窗口时出错: {e}") # 最后关闭自己 logger.debug("正在关闭主窗口...") @@ -849,7 +849,7 @@ def restart_app(self): ) except Exception as e: - logger.error(f"启动新进程失败: {e}") + logger.exception(f"启动新进程失败: {e}") return # 停止课前重置定时器 @@ -904,7 +904,7 @@ def _check_pre_class_reset(self): self.pre_class_reset_timer.stop() except Exception as e: - logger.error(f"检测课前重置时出错: {e}") + logger.exception(f"检测课前重置时出错: {e}") def _perform_pre_class_reset(self): """执行课前重置操作""" @@ -938,7 +938,7 @@ def handle_remove_readonly(func, path, exc): shutil.rmtree(temp_dir, onerror=handle_remove_readonly) logger.info("已清除 TEMP 文件夹") except Exception as e: - logger.error(f"清除 TEMP 文件夹失败: {e}") + logger.exception(f"清除 TEMP 文件夹失败: {e}") # 刷新点名页面的剩余人数显示 if self.roll_call_page and hasattr( @@ -957,7 +957,7 @@ def handle_remove_readonly(func, path, exc): logger.info("课前重置完成") except Exception as e: - logger.error(f"执行课前重置时出错: {e}") + logger.exception(f"执行课前重置时出错: {e}") def _init_pre_class_reset(self): """初始化课前重置功能""" @@ -986,4 +986,4 @@ def _init_pre_class_reset(self): logger.debug("课前重置定时器已启动(CSES模式)") except Exception as e: - logger.error(f"初始化课前重置功能时出错: {e}") + logger.exception(f"初始化课前重置功能时出错: {e}") diff --git a/app/view/settings/about.py b/app/view/settings/about.py index e7dd4e01..f0f118f7 100644 --- a/app/view/settings/about.py +++ b/app/view/settings/about.py @@ -76,7 +76,7 @@ def _load_click_count(self): data = read_behind_scenes_settings() return data.get("banner_click_count", 0) except Exception as e: - logger.error(f"加载横幅点击次数失败: {e}") + logger.exception(f"加载横幅点击次数失败: {e}") return 0 def _save_click_count(self, count): @@ -86,7 +86,7 @@ def _save_click_count(self, count): data["banner_click_count"] = count write_behind_scenes_settings(data) except Exception as e: - logger.error(f"保存横幅点击次数失败: {e}") + logger.exception(f"保存横幅点击次数失败: {e}") def _on_banner_clicked(self, event): """横幅点击事件""" diff --git a/app/view/settings/basic_settings.py b/app/view/settings/basic_settings.py index eb166d7b..cc81bbc5 100644 --- a/app/view/settings/basic_settings.py +++ b/app/view/settings/basic_settings.py @@ -487,7 +487,7 @@ def __on_url_protocol_changed(self, checked): parent=self.window(), ) except Exception as e: - logger.error(f"URL协议设置错误: {e}") + logger.exception(f"URL协议设置错误: {e}") # 发生错误时恢复原状态 self.url_protocol_switch.setChecked(not checked) @@ -541,7 +541,7 @@ def _restart_ipc_server(self, new_port: int): if success: logger.info(f"IPC服务器已成功重启,使用新端口: {new_port}") else: - logger.error(f"重启IPC服务器失败,端口: {new_port}") + logger.exception(f"重启IPC服务器失败,端口: {new_port}") show_notification( NotificationType.ERROR, NotificationConfig( @@ -557,7 +557,7 @@ def _restart_ipc_server(self, new_port: int): else: logger.warning("无法获取主窗口实例,无法重启IPC服务器") except Exception as e: - logger.error(f"重启IPC服务器时发生错误: {e}") + logger.exception(f"重启IPC服务器时发生错误: {e}") show_notification( NotificationType.ERROR, NotificationConfig( diff --git a/app/view/settings/extraction_settings/lottery_settings.py b/app/view/settings/extraction_settings/lottery_settings.py index 6ee5fa2b..33f41187 100644 --- a/app/view/settings/extraction_settings/lottery_settings.py +++ b/app/view/settings/extraction_settings/lottery_settings.py @@ -146,7 +146,7 @@ def run(self): data = self.fn() self.signals.loaded.emit(data) except Exception as e: - logger.error(f"后台加载 lottery_settings 数据失败: {e}") + logger.exception(f"后台加载 lottery_settings 数据失败: {e}") def _collect(): data = {} @@ -167,7 +167,7 @@ def _collect(): "lottery_settings", "half_repeat" ) except Exception as e: - logger.error(f"收集 lottery_settings 初始数据失败: {e}") + logger.exception(f"收集 lottery_settings 初始数据失败: {e}") return data signals = _Signals() @@ -190,7 +190,7 @@ def _on_background_loaded(self, data: dict): self.on_draw_mode_changed() except Exception as e: - logger.error(f"回填 lottery_settings 数据失败: {e}") + logger.exception(f"回填 lottery_settings 数据失败: {e}") def setup_file_watcher(self): """设置文件系统监视器,监控奖池名单文件夹的变化""" @@ -645,4 +645,4 @@ def open_lottery_image_folder(self): if folder_path: QDesktopServices.openUrl(QUrl.fromLocalFile(str(folder_path))) else: - logger.error("无法获取奖品图片文件夹路径") + logger.exception("无法获取奖品图片文件夹路径") diff --git a/app/view/settings/extraction_settings/quick_draw_settings.py b/app/view/settings/extraction_settings/quick_draw_settings.py index 79c4f45c..591c90b7 100644 --- a/app/view/settings/extraction_settings/quick_draw_settings.py +++ b/app/view/settings/extraction_settings/quick_draw_settings.py @@ -192,7 +192,7 @@ def run(self): data = self.fn() self.signals.loaded.emit(data) except Exception as e: - logger.error(f"后台加载 quick_draw_settings 数据失败: {e}") + logger.exception(f"后台加载 quick_draw_settings 数据失败: {e}") def _collect(): data = {} @@ -224,7 +224,7 @@ def _collect(): "quick_draw_settings", "default_class" ) except Exception as e: - logger.error(f"收集 quick_draw_settings 初始数据失败: {e}") + logger.exception(f"收集 quick_draw_settings 初始数据失败: {e}") return data signals = _Signals() @@ -262,7 +262,7 @@ def _on_background_loaded(self, data: dict): self.on_draw_mode_changed() except Exception as e: - logger.error(f"回填 quick_draw_settings 数据失败: {e}") + logger.exception(f"回填 quick_draw_settings 数据失败: {e}") def on_draw_mode_changed(self): """当抽取模式改变时的处理逻辑""" @@ -706,4 +706,4 @@ def open_student_image_folder(self): if folder_path: QDesktopServices.openUrl(QUrl.fromLocalFile(str(folder_path))) else: - logger.error("无法获取学生图片文件夹路径") + logger.exception("无法获取学生图片文件夹路径") diff --git a/app/view/settings/extraction_settings/roll_call_settings.py b/app/view/settings/extraction_settings/roll_call_settings.py index db215f2a..c57b819c 100644 --- a/app/view/settings/extraction_settings/roll_call_settings.py +++ b/app/view/settings/extraction_settings/roll_call_settings.py @@ -645,4 +645,4 @@ def open_student_image_folder(self): if folder_path: QDesktopServices.openUrl(QUrl.fromLocalFile(str(folder_path))) else: - logger.error("无法获取学生图片文件夹路径") + logger.exception("无法获取学生图片文件夹路径") diff --git a/app/view/settings/history/history_management.py b/app/view/settings/history/history_management.py index 91ca031d..0a2fec1b 100644 --- a/app/view/settings/history/history_management.py +++ b/app/view/settings/history/history_management.py @@ -200,7 +200,7 @@ def clear_roll_call_history(self): position=InfoBarPosition.TOP, ) except Exception as e: - logger.error(f"清除点名历史记录失败: {e}") + logger.exception(f"清除点名历史记录失败: {e}") # 显示错误通知 show_error_notification( title=get_content_name_async( @@ -430,7 +430,7 @@ def clear_lottery_history(self): position=InfoBarPosition.TOP, ) except Exception as e: - logger.error(f"清除抽奖历史记录失败: {e}") + logger.exception(f"清除抽奖历史记录失败: {e}") # 显示错误通知 show_error_notification( title=get_content_name_async( diff --git a/app/view/settings/history/lottery_history_table.py b/app/view/settings/history/lottery_history_table.py index bf88dfd6..ac0e9cb3 100644 --- a/app/view/settings/history/lottery_history_table.py +++ b/app/view/settings/history/lottery_history_table.py @@ -418,7 +418,7 @@ def sort_key(lottery): self.current_row = end_row except Exception as e: - logger.error(f"加载奖品数据失败: {e}") + logger.exception(f"加载奖品数据失败: {e}") Dialog("错误", f"加载奖品数据失败: {e}", self).exec() def _load_more_sessions_data(self): @@ -496,7 +496,7 @@ def sort_key(lottery): self.current_row = end_row except Exception as e: - logger.error(f"加载会话数据失败: {e}") + logger.exception(f"加载会话数据失败: {e}") Dialog("错误", f"加载会话数据失败: {e}", self).exec() def _load_more_stats_data(self, lottery_name): @@ -573,7 +573,7 @@ def sort_key(lottery): self.current_row = end_row except Exception as e: - logger.error(f"加载统计数据失败: {e}") + logger.exception(f"加载统计数据失败: {e}") Dialog("错误", f"加载统计数据失败: {e}", self).exec() def setup_file_watcher(self): @@ -774,7 +774,7 @@ def refresh_data(self): self.table.horizontalHeader().setSortIndicatorShown(True) except Exception as e: - logger.error(f"刷新表格数据失败: {str(e)}") + logger.exception(f"刷新表格数据失败: {str(e)}") finally: self.table.blockSignals(False) @@ -886,5 +886,5 @@ def _update_subject_list(self): self.subject_comboBox.show() except Exception as e: - logger.error(f"更新课程列表失败: {e}") + logger.exception(f"更新课程列表失败: {e}") self.available_subjects = [] diff --git a/app/view/settings/history/roll_call_history_table.py b/app/view/settings/history/roll_call_history_table.py index 173ece3e..d762b87e 100644 --- a/app/view/settings/history/roll_call_history_table.py +++ b/app/view/settings/history/roll_call_history_table.py @@ -485,7 +485,7 @@ def sort_key(student): self.current_row = end_row except Exception as e: - logger.error(f"加载学生数据失败: {e}") + logger.exception(f"加载学生数据失败: {e}") def _load_more_sessions_data(self): """加载更多会话数据""" @@ -587,7 +587,7 @@ def sort_key(student): self.current_row = end_row except Exception as e: - logger.error(f"加载会话数据失败: {e}") + logger.exception(f"加载会话数据失败: {e}") def _load_more_stats_data(self, student_name): """加载更多统计数据""" @@ -702,7 +702,7 @@ def sort_key(student): self.current_row = end_row except Exception as e: - logger.error(f"加载统计数据失败: {e}") + logger.exception(f"加载统计数据失败: {e}") def setup_file_watcher(self): """设置文件系统监视器,监控班级历史记录文件夹的变化""" @@ -866,7 +866,7 @@ def _update_subject_list(self): self.subject_comboBox.show() except Exception as e: - logger.error(f"更新课程列表失败: {e}") + logger.exception(f"更新课程列表失败: {e}") self.available_subjects = [] def refresh_data(self): @@ -987,7 +987,7 @@ def refresh_data(self): self.table.horizontalHeader().setSortIndicatorShown(True) except Exception as e: - logger.error(f"刷新表格数据失败: {str(e)}") + logger.exception(f"刷新表格数据失败: {str(e)}") finally: self.table.blockSignals(False) diff --git a/app/view/settings/list_management/list_management.py b/app/view/settings/list_management/list_management.py index 72c24179..27fe3403 100644 --- a/app/view/settings/list_management/list_management.py +++ b/app/view/settings/list_management/list_management.py @@ -152,9 +152,9 @@ def __init__(self, parent=None): readme_settings_async("roll_call_list", "select_class_name") ) except RuntimeError as e: - logger.error(f"初始化班级列表时发生错误: {e}") + logger.exception(f"初始化班级列表时发生错误: {e}") except Exception as e: - logger.error(f"初始化班级列表时发生未知错误: {e}") + logger.exception(f"初始化班级列表时发生未知错误: {e}") if hasattr(self, "class_name_combo") and self.class_name_combo is not None: try: self.class_name_combo.currentIndexChanged.connect( @@ -165,9 +165,9 @@ def __init__(self, parent=None): ) ) except RuntimeError as e: - logger.error(f"连接班级下拉框信号时发生错误: {e}") + logger.exception(f"连接班级下拉框信号时发生错误: {e}") except Exception as e: - logger.error(f"连接班级下拉框信号时发生未知错误: {e}") + logger.exception(f"连接班级下拉框信号时发生未知错误: {e}") # 设置文件系统监视器 self.setup_file_watcher() @@ -371,7 +371,7 @@ def export_student_list(self): duration=3000, ) show_notification(NotificationType.ERROR, config, parent=self) - logger.error(f"学生名单导出失败: {message}") + logger.exception(f"学生名单导出失败: {message}") def setup_file_watcher(self): """设置文件系统监视器,监控班级名单文件夹的变化 - 使用共享监视器""" @@ -452,9 +452,9 @@ def update_button_states(self): except RuntimeError: pass # 按钮已删除,跳过设置 except RuntimeError as e: - logger.error(f"更新按钮状态时发生错误: {e}") + logger.exception(f"更新按钮状态时发生错误: {e}") except Exception as e: - logger.error(f"更新按钮状态时发生未知错误: {e}") + logger.exception(f"更新按钮状态时发生未知错误: {e}") def cleanup_file_watcher(self): """清理文件系统监视器""" @@ -523,9 +523,9 @@ def refresh_class_list(self): # logger.debug(f"班级列表已刷新,共 {len(class_list)} 个班级") except RuntimeError as e: - logger.error(f"刷新班级列表时发生错误: {e}") + logger.exception(f"刷新班级列表时发生错误: {e}") except Exception as e: - logger.error(f"刷新班级列表时发生未知错误: {e}") + logger.exception(f"刷新班级列表时发生未知错误: {e}") class lottery_list(GroupHeaderCardWidget): @@ -629,9 +629,9 @@ def __init__(self, parent=None): get_content_name_async("lottery_list", "select_pool_name") ) except RuntimeError as e: - logger.error(f"初始化奖池列表时发生错误: {e}") + logger.exception(f"初始化奖池列表时发生错误: {e}") except Exception as e: - logger.error(f"初始化奖池列表时发生未知错误: {e}") + logger.exception(f"初始化奖池列表时发生未知错误: {e}") if hasattr(self, "pool_name_combo") and self.pool_name_combo is not None: try: self.pool_name_combo.currentIndexChanged.connect( @@ -642,9 +642,9 @@ def __init__(self, parent=None): ) ) except RuntimeError as e: - logger.error(f"连接奖池下拉框信号时发生错误: {e}") + logger.exception(f"连接奖池下拉框信号时发生错误: {e}") except Exception as e: - logger.error(f"连接奖池下拉框信号时发生未知错误: {e}") + logger.exception(f"连接奖池下拉框信号时发生未知错误: {e}") # 设置文件系统监视器 self.setup_file_watcher() @@ -833,7 +833,7 @@ def export_prize_name(self): duration=3000, ) show_notification(NotificationType.ERROR, config, parent=self) - logger.error(f"奖品名单导出失败: {message}") + logger.exception(f"奖品名单导出失败: {message}") def setup_file_watcher(self): """设置文件系统监视器,监控奖池名单文件夹的变化 - 使用共享监视器""" @@ -906,9 +906,9 @@ def update_button_states(self): except RuntimeError: pass # 按钮已删除,跳过设置 except RuntimeError as e: - logger.error(f"更新奖池按钮状态时发生错误: {e}") + logger.exception(f"更新奖池按钮状态时发生错误: {e}") except Exception as e: - logger.error(f"更新奖池按钮状态时发生未知错误: {e}") + logger.exception(f"更新奖池按钮状态时发生未知错误: {e}") def cleanup_file_watcher(self): """清理文件系统监视器""" @@ -977,6 +977,6 @@ def refresh_pool_list(self): # logger.debug(f"奖池列表已刷新,共 {len(pool_list)} 个奖池") except RuntimeError as e: - logger.error(f"刷新奖池列表时发生错误: {e}") + logger.exception(f"刷新奖池列表时发生错误: {e}") except Exception as e: - logger.error(f"刷新奖池列表时发生未知错误: {e}") + logger.exception(f"刷新奖池列表时发生未知错误: {e}") diff --git a/app/view/settings/list_management/lottery_table.py b/app/view/settings/list_management/lottery_table.py index af9fdfa8..dad5bbde 100644 --- a/app/view/settings/list_management/lottery_table.py +++ b/app/view/settings/list_management/lottery_table.py @@ -74,9 +74,9 @@ def create_lottery_selection(self): ) self.lottery_comboBox.currentTextChanged.connect(self.refresh_data) except RuntimeError as e: - logger.error(f"连接抽奖名单下拉框信号时发生错误: {e}") + logger.exception(f"连接抽奖名单下拉框信号时发生错误: {e}") except Exception as e: - logger.error(f"连接抽奖名单下拉框信号时发生未知错误: {e}") + logger.exception(f"连接抽奖名单下拉框信号时发生未知错误: {e}") self.addGroup( get_theme_icon("ic_fluent_class_20_filled"), @@ -180,9 +180,9 @@ def refresh_lottery_list(self): if hasattr(self, "table") and self.table is not None: self.refresh_data() except RuntimeError as e: - logger.error(f"刷新抽奖名单列表时发生错误: {e}") + logger.exception(f"刷新抽奖名单列表时发生错误: {e}") except Exception as e: - logger.error(f"刷新抽奖名单列表时发生未知错误: {e}") + logger.exception(f"刷新抽奖名单列表时发生未知错误: {e}") def refresh_data(self): """刷新抽奖名单数据""" @@ -197,7 +197,7 @@ def refresh_data(self): try: pool_name = self.lottery_comboBox.currentText() except RuntimeError: - logger.error("抽奖名单下拉框已被销毁") + logger.exception("抽奖名单下拉框已被销毁") return if not pool_name: @@ -258,7 +258,7 @@ def refresh_data(self): ) except Exception as e: - logger.error(f"刷新抽奖名单表格数据失败: {str(e)}") + logger.exception(f"刷新抽奖名单表格数据失败: {str(e)}") finally: # 恢复信号 self.table.blockSignals(False) @@ -288,7 +288,7 @@ def save_table_data(self, row, col): with open_file(pool_file, "r", encoding="utf-8") as f: pool_data = json.load(f, object_pairs_hook=OrderedDict) except Exception as e: - logger.error(f"加载抽奖池数据失败: {str(e)}") + logger.exception(f"加载抽奖池数据失败: {str(e)}") return # 通过奖品ID找到对应的奖品键 @@ -302,7 +302,7 @@ def save_table_data(self, row, col): break if not matched_key: - logger.error(f"未找到奖品ID为 {item_id} 的奖品,奖品名称: {item_name}") + logger.exception(f"未找到奖品ID为 {item_id} 的奖品,奖品名称: {item_name}") return # 根据列索引更新相应的字段 @@ -341,7 +341,7 @@ def save_table_data(self, row, col): ) self.table.blockSignals(False) except Exception as e: - logger.error(f"保存抽奖池数据失败: {str(e)}") + logger.exception(f"保存抽奖池数据失败: {str(e)}") # 如果保存失败,恢复原来的值 self.table.blockSignals(True) # 阻止信号,避免递归调用 if col == 2: # 奖品名称列 diff --git a/app/view/settings/list_management/roll_call_table.py b/app/view/settings/list_management/roll_call_table.py index 472f72e8..10e95067 100644 --- a/app/view/settings/list_management/roll_call_table.py +++ b/app/view/settings/list_management/roll_call_table.py @@ -175,9 +175,9 @@ def refresh_class_list(self): if hasattr(self, "table") and self.table is not None: self.refresh_data() except RuntimeError as e: - logger.error(f"刷新班级列表时发生错误: {e}") + logger.exception(f"刷新班级列表时发生错误: {e}") except Exception as e: - logger.error(f"刷新班级列表时发生未知错误: {e}") + logger.exception(f"刷新班级列表时发生未知错误: {e}") def refresh_data(self): """刷新表格数据""" @@ -192,7 +192,7 @@ def refresh_data(self): try: class_name = self.class_comboBox.currentText() except RuntimeError: - logger.error("班级下拉框已被销毁") + logger.exception("班级下拉框已被销毁") return if not class_name: @@ -258,7 +258,7 @@ def refresh_data(self): ) except Exception as e: - logger.error(f"刷新表格数据失败: {str(e)}") + logger.exception(f"刷新表格数据失败: {str(e)}") finally: # 恢复信号 self.table.blockSignals(False) @@ -289,7 +289,7 @@ def save_table_data(self, row, col): with open_file(student_file, "r", encoding="utf-8") as f: student_data = json.load(f, object_pairs_hook=OrderedDict) except Exception as e: - logger.error(f"加载学生数据失败: {str(e)}") + logger.exception(f"加载学生数据失败: {str(e)}") return # 通过学号找到对应的学生键 @@ -303,7 +303,7 @@ def save_table_data(self, row, col): break if not matched_key: - logger.error(f"未找到学号为 {student_id} 的学生,学生姓名: {student_name}") + logger.exception(f"未找到学号为 {student_id} 的学生,学生姓名: {student_name}") return # 根据列索引更新相应的字段 @@ -344,7 +344,7 @@ def save_table_data(self, row, col): ) self.table.blockSignals(False) except Exception as e: - logger.error(f"保存学生数据失败: {str(e)}") + logger.exception(f"保存学生数据失败: {str(e)}") # 如果保存失败,恢复原来的值 self.table.blockSignals(True) # 阻止信号,避免递归调用 if col == 2: # 姓名列 diff --git a/app/view/settings/more_settings/behind_scenes_settings.py b/app/view/settings/more_settings/behind_scenes_settings.py index 9cd30166..48dc1fb5 100644 --- a/app/view/settings/more_settings/behind_scenes_settings.py +++ b/app/view/settings/more_settings/behind_scenes_settings.py @@ -63,11 +63,11 @@ def run(self): p.get("name", "") for p in prizes if p.get("exist", True) ] except Exception as e: - logger.error(f"获取奖品列表失败: {e}") + logger.exception(f"获取奖品列表失败: {e}") self.finished.emit(students, prob_data, prize_list) except Exception as e: - logger.error(f"后台加载数据失败: {e}") + logger.exception(f"后台加载数据失败: {e}") self.finished.emit([], {}, []) @@ -373,9 +373,9 @@ def refresh_list(self): if hasattr(self, "table") and self.table is not None: self.refresh_data() except RuntimeError as e: - logger.error(f"刷新名单列表时发生错误: {e}") + logger.exception(f"刷新名单列表时发生错误: {e}") except Exception as e: - logger.error(f"刷新名单列表时发生未知错误: {e}") + logger.exception(f"刷新名单列表时发生未知错误: {e}") def refresh_data(self): """刷新表格数据""" @@ -393,7 +393,7 @@ def refresh_data(self): try: list_name = self.list_comboBox.currentText() except RuntimeError: - logger.error("名单下拉框已被销毁") + logger.exception("名单下拉框已被销毁") return if not list_name: @@ -551,7 +551,7 @@ def on_data_loaded(self, students, prob_data, prize_list): ) except Exception as e: - logger.error(f"刷新表格数据失败: {str(e)}") + logger.exception(f"刷新表格数据失败: {str(e)}") finally: self.table.blockSignals(False) @@ -560,7 +560,7 @@ def save_probability_data(self): try: write_behind_scenes_settings(self.probability_data) except Exception as e: - logger.error(f"保存概率设置数据失败: {e}") + logger.exception(f"保存概率设置数据失败: {e}") def save_table_data(self, row, col): """保存表格编辑的数据""" diff --git a/app/view/settings/more_settings/course_settings.py b/app/view/settings/more_settings/course_settings.py index 6e49fe82..f2308436 100644 --- a/app/view/settings/more_settings/course_settings.py +++ b/app/view/settings/more_settings/course_settings.py @@ -164,7 +164,7 @@ def _update_schedule_info(self): class_info = parser.get_class_info() total_class_periods += len(class_info) except Exception as e: - logger.error(f"解析文件{file_name}失败: {e}") + logger.exception(f"解析文件{file_name}失败: {e}") # 判断是否有课程表数据 if total_class_periods > 0: @@ -182,7 +182,7 @@ def _update_schedule_info(self): ) except Exception as e: - logger.error(f"更新课程表信息失败: {e}") + logger.exception(f"更新课程表信息失败: {e}") self.schedule_info_label.setText("获取课程表信息失败") def on_import_file_clicked(self): @@ -238,7 +238,7 @@ def _import_cses_file(self, file_path: str): ) except Exception as e: - logger.error(f"导入CSES文件失败: {e}") + logger.exception(f"导入CSES文件失败: {e}") import_error_msg = get_content_name_async("course_settings", "import_error") if "{}" in import_error_msg: error_content = import_error_msg.format(str(e)) @@ -274,7 +274,7 @@ def on_view_current_config_clicked(self): create_current_config_viewer_window() except Exception as e: - logger.error(f"显示当前配置失败: {e}") + logger.exception(f"显示当前配置失败: {e}") InfoBar.error( title=get_content_name_async("course_settings", "import_failed"), content=f"无法显示当前配置: {str(e)}", diff --git a/app/view/settings/more_settings/music_settings.py b/app/view/settings/more_settings/music_settings.py index ef74db16..050c7ef7 100644 --- a/app/view/settings/more_settings/music_settings.py +++ b/app/view/settings/more_settings/music_settings.py @@ -125,7 +125,7 @@ def import_music(self): shutil.copy2(src_file, dst_file) except Exception as e: - logger.error(f"导入音乐文件失败: {src_file.name}, 错误: {e}") + logger.exception(f"导入音乐文件失败: {src_file.name}, 错误: {e}") # 刷新音乐文件列表 self.refresh_music_files() @@ -145,7 +145,7 @@ def delete_music(self): try: remove_file(audio_file_path) except Exception as e: - logger.error(f"删除音乐文件失败: {current_text}, 错误: {e}") + logger.exception(f"删除音乐文件失败: {current_text}, 错误: {e}") # 刷新音乐文件列表 self.refresh_music_files() diff --git a/app/view/settings/more_settings/page_management.py b/app/view/settings/more_settings/page_management.py index 5825e2ce..f148fba1 100644 --- a/app/view/settings/more_settings/page_management.py +++ b/app/view/settings/more_settings/page_management.py @@ -63,7 +63,7 @@ def make_placeholder(attr_name: str): # 使用QTimer.singleShot创建定时器 QTimer.singleShot(150 * i, lambda n=name: self._safe_create_deferred(n)) except Exception as e: - logger.error(f"调度延迟创建子组件失败: {e}") + logger.exception(f"调度延迟创建子组件失败: {e}") def _safe_create_deferred(self, name: str): """安全地创建延迟注册的子组件,使用弱引用避免访问已销毁的对象""" @@ -84,10 +84,10 @@ def _safe_create_deferred(self, name: str): logger.debug(f"对象已销毁,取消创建子组件 {name}") return else: - logger.error(f"创建子组件 {name} 时发生运行时错误: {e}") + logger.exception(f"创建子组件 {name} 时发生运行时错误: {e}") return except Exception as e: - logger.error(f"创建子组件 {name} 时发生未知错误: {e}") + logger.exception(f"创建子组件 {name} 时发生未知错误: {e}") return def _create_deferred(self, name: str): @@ -114,7 +114,7 @@ def _create_deferred(self, name: str): real_widget = factory() elapsed = time.perf_counter() - start except Exception as e: - logger.error(f"创建子组件 {name} 失败: {e}") + logger.exception(f"创建子组件 {name} 失败: {e}") return # 再次检查对象是否仍然有效 @@ -139,7 +139,7 @@ def _create_deferred(self, name: str): setattr(self, name, real_widget) logger.debug(f"延迟创建子组件 {name} 耗时: {elapsed:.3f}s") except RuntimeError as e: - logger.error(f"将子组件 {name} 插入主布局失败: {e}") + logger.exception(f"将子组件 {name} 插入主布局失败: {e}") return # 尝试获取占位符的布局 @@ -161,7 +161,7 @@ def _create_deferred(self, name: str): setattr(self, name, real_widget) logger.debug(f"延迟创建子组件 {name} 耗时: {elapsed:.3f}s") except RuntimeError as e: - logger.error(f"绑定子组件 {name} 到占位容器失败: {e}") + logger.exception(f"绑定子组件 {name} 到占位容器失败: {e}") else: # 没有布局,尝试替换占位符 try: @@ -187,7 +187,7 @@ def _create_deferred(self, name: str): setattr(self, name, real_widget) logger.debug(f"延迟创建子组件 {name} 耗时: {elapsed:.3f}s") except RuntimeError as e: - logger.error(f"替换占位 {name} 失败: {e}") + logger.exception(f"替换占位 {name} 失败: {e}") class page_management_roll_call(GroupHeaderCardWidget): diff --git a/app/view/settings/settings.py b/app/view/settings/settings.py index 58e07e14..05e22f63 100644 --- a/app/view/settings/settings.py +++ b/app/view/settings/settings.py @@ -160,7 +160,7 @@ def _apply_window_visibility_settings(self): try: self.show() except Exception as e: - logger.error(f"加载窗口显示设置失败: {e}") + logger.exception(f"加载窗口显示设置失败: {e}") def _handle_main_page_requested(self, page_name: str): """处理主页面请求 @@ -519,9 +519,9 @@ def _on_stacked_widget_changed(self, index: int): f"设置页面已按需创建: {name}, 预览模式: {self.is_preview}" ) except Exception as e: - logger.error(f"延迟创建设置页面 {name} 失败: {e}") + logger.exception(f"延迟创建设置页面 {name} 失败: {e}") except Exception as e: - logger.error(f"处理堆叠窗口改变失败: {e}") + logger.exception(f"处理堆叠窗口改变失败: {e}") def _unload_inactive_pages(self, current_page: str): """卸载不活动的页面以释放内存 @@ -645,7 +645,7 @@ def _unload_settings_page(self, page_name: str): except RuntimeError as e: logger.warning(f"卸载设置页面 {page_name} 时出现警告: {e}") except Exception as e: - logger.error(f"卸载设置页面 {page_name} 失败: {e}") + logger.exception(f"卸载设置页面 {page_name} 失败: {e}") def _background_warmup_pages( self, @@ -679,7 +679,7 @@ def _background_warmup_non_pivot(self, interval_ms: int = 80): # 所有页面都将在用户首次访问时按需创建 pass except Exception as e: - logger.error(f"后台预热非 pivot 页面失败: {e}") + logger.exception(f"后台预热非 pivot 页面失败: {e}") def _create_deferred_page(self, name: str): """根据名字创建对应延迟工厂并把结果加入占位容器""" @@ -724,20 +724,20 @@ def _create_deferred_page(self, name: str): try: real_page = factory(is_preview=self.is_preview) except RuntimeError as e: - logger.error(f"创建延迟页面 {name} 失败(父容器可能已销毁): {e}") + logger.exception(f"创建延迟页面 {name} 失败(父容器可能已销毁): {e}") return except Exception as e: - logger.error(f"创建延迟页面 {name} 失败: {e}") + logger.exception(f"创建延迟页面 {name} 失败: {e}") return try: layout.addWidget(real_page) logger.debug(f"后台预热创建设置页面: {name}") except RuntimeError as e: - logger.error(f"将延迟页面 {name} 插入容器失败(容器可能已销毁): {e}") + logger.exception(f"将延迟页面 {name} 插入容器失败(容器可能已销毁): {e}") return except Exception as e: - logger.error(f"_create_deferred_page 失败: {e}") + logger.exception(f"_create_deferred_page 失败: {e}") def initNavigation(self): """初始化导航系统 @@ -915,7 +915,7 @@ def _load_default_page(self): self.switchTo(self.basicSettingsInterface) logger.debug("已自动导航到基础设置页面") except Exception as e: - logger.error(f"加载默认页面失败: {e}") + logger.exception(f"加载默认页面失败: {e}") def closeEvent(self, event): """窗口关闭事件处理 diff --git a/app/view/settings/update.py b/app/view/settings/update.py index 2af529da..b57b63ba 100644 --- a/app/view/settings/update.py +++ b/app/view/settings/update.py @@ -470,7 +470,7 @@ def format_size(size_bytes): expected_file_path.unlink() logger.debug(f"已删除损坏的文件: {expected_file_path}") except Exception as e: - logger.error(f"删除损坏文件失败: {e}") + logger.exception(f"删除损坏文件失败: {e}") # 如果文件完整且存在,直接使用,不需要下载 if file_exists_and_same_version: @@ -662,7 +662,7 @@ def run(self): ) self.on_complete(file_path) except Exception as e: - logger.error(f"下载任务执行失败: {e}") + logger.exception(f"下载任务执行失败: {e}") # 确保即使发生异常也会调用完成回调 self.on_complete(None) diff --git a/app/view/settings/voice_settings/basic_voice_settings.py b/app/view/settings/voice_settings/basic_voice_settings.py index 0ad0af15..acba4e9b 100644 --- a/app/view/settings/voice_settings/basic_voice_settings.py +++ b/app/view/settings/voice_settings/basic_voice_settings.py @@ -172,11 +172,11 @@ def on_voices_fetched(self, voices): logger.debug(f"Edge TTS语音列表已更新,共{len(voices)}个语音") except Exception as e: - logger.error(f"处理Edge TTS语音列表失败: {e}") + logger.exception(f"处理Edge TTS语音列表失败: {e}") def on_voices_fetch_error(self, error): """语音列表获取失败后的处理""" - logger.error(f"获取Edge TTS语音列表失败: {error}") + logger.exception(f"获取Edge TTS语音列表失败: {error}") def __del__(self): """析构函数,确保worker正确终止""" diff --git a/app/view/settings/voice_settings/specific_announcements.py b/app/view/settings/voice_settings/specific_announcements.py index ff12d5dc..89ca82b2 100644 --- a/app/view/settings/voice_settings/specific_announcements.py +++ b/app/view/settings/voice_settings/specific_announcements.py @@ -294,7 +294,7 @@ def on_item_changed(self, item): item_type = "学生" if self.current_mode == 0 else "奖品" logger.debug(f"已更新{item_type} {name_field} 的语音播报设置") except Exception as e: - logger.error(f"更新语音播报设置失败: {e}") + logger.exception(f"更新语音播报设置失败: {e}") def setup_file_watcher(self): """设置文件系统监视器,监控历史记录文件夹的变化""" @@ -462,7 +462,7 @@ def refresh_data(self): self.original_items.append(row_items) except Exception as e: - logger.error(f"刷新表格数据失败: {str(e)}") + logger.exception(f"刷新表格数据失败: {str(e)}") finally: # 恢复信号 self.table.blockSignals(False) diff --git a/main.py b/main.py index 719fcc4c..e7de2b37 100644 --- a/main.py +++ b/main.py @@ -4,6 +4,7 @@ import gc import sentry_sdk +from sentry_sdk.integrations.loguru import LoguruIntegration, LoggingLevels from PySide6.QtCore import Qt from PySide6.QtWidgets import QApplication from loguru import logger @@ -37,18 +38,25 @@ def main(): logger.remove() configure_logging() + def before_send(event, hint): + # 如果事件中不包含异常信息(即没有堆栈),则不上传 + if "exception" not in event: + return None + return event + sentry_sdk.init( dsn="https://f079219d4004591e72e6e4e4155023fe@o4510689230192640.ingest.us.sentry.io/4510689241071616", + integrations=[ + LoguruIntegration( + level=LoggingLevels.INFO.value, + event_level=LoggingLevels.ERROR.value, + ), + ], + before_send=before_send, send_default_pii=True, + enable_logs=True, ) - def sentry_handler(message): - record = message.record - if record["level"].name == "ERROR": - sentry_sdk.capture_message(message, level=record["level"].name) - - logger.add(sentry_handler, level="ERROR") - wm.app_start_time = time.perf_counter() shared_memory, is_first_instance = check_single_instance() @@ -90,7 +98,7 @@ def sentry_handler(message): ) if not local_server: - logger.error("无法启动本地服务器,程序将退出") + logger.exception("无法启动本地服务器,程序将退出") shared_memory.detach() sys.exit(1) @@ -132,7 +140,7 @@ def sentry_handler(message): sys.stderr.flush() os._exit(0) except Exception as e: - logger.error(f"程序退出过程中发生异常: {e}") + logger.exception(f"程序退出过程中发生异常: {e}") if "shared_memory" in locals(): shared_memory.detach() if "local_server" in locals() and local_server: