import sys import logging import os import tempfile import traceback from logging.handlers import TimedRotatingFileHandler from datetime import datetime from PySide6.QtWidgets import QApplication, QMessageBox from PySide6.QtCore import QTranslator, QLocale, QLibraryInfo from widgets.login_widget import LoginWidget from utils.menu_translator import MenuTranslator from utils.config_loader import ConfigLoader # 自定义异常处理器 def global_exception_handler(exctype, value, tb): """全局异常处理器,捕获未处理的异常并记录日志""" error_msg = ''.join(traceback.format_exception(exctype, value, tb)) logging.critical(f"未捕获的异常: {error_msg}") # 如果已经有QApplication实例,显示错误对话框 if QApplication.instance(): QMessageBox.critical(None, "系统错误", f"发生了未处理的错误:\n{value}\n\n请联系管理员并提供日志文件。") # 调用原始的异常处理器 sys.__excepthook__(exctype, value, tb) # 安装全局异常处理器 sys.excepthook = global_exception_handler # 设置日志目录 log_dir = 'logs' temp_log_dir = None # 尝试使用应用程序目录 try: if not os.path.exists(log_dir): os.makedirs(log_dir, exist_ok=True) # 测试目录是否可写 test_file = os.path.join(log_dir, 'test_write.tmp') with open(test_file, 'w') as f: f.write('test') os.remove(test_file) logging.basicConfig(level=logging.INFO, format='%(message)s') logging.info(f"使用应用程序日志目录: {os.path.abspath(log_dir)}") except (OSError, PermissionError) as e: # 如果应用程序目录不可用或不可写,使用临时目录 temp_log_dir = os.path.join(tempfile.gettempdir(), 'app_logs') log_dir = temp_log_dir try: if not os.path.exists(log_dir): os.makedirs(log_dir, exist_ok=True) logging.basicConfig(level=logging.INFO, format='%(message)s') logging.info(f"无法使用应用程序日志目录,原因: {e}") logging.info(f"使用临时日志目录: {log_dir}") except Exception as e2: # 如果临时目录也失败,只使用控制台输出 print(f"无法创建日志目录,将只使用控制台输出: {e2}") logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') # 配置全局日志 # 使用日期命名日志文件 today = datetime.now().strftime('%Y-%m-%d') log_file_path = os.path.join(log_dir, f'app_{today}.log') try: # 创建一个TimedRotatingFileHandler,每天轮换一次日志文件 file_handler = TimedRotatingFileHandler( log_file_path, when='midnight', # 每天午夜轮换 interval=1, # 轮换间隔为1天 backupCount=30, # 保留30天的日志 encoding='utf-8' # 使用UTF-8编码 ) # 配置日志格式 log_format = logging.Formatter('%(asctime)s - %(levelname)s - %(name)s - [%(funcName)s:%(lineno)d] - %(message)s') file_handler.setFormatter(log_format) # 控制台输出处理器 console_handler = logging.StreamHandler(sys.stdout) console_handler.setFormatter(log_format) # 配置根日志记录器 root_logger = logging.getLogger() root_logger.setLevel(logging.INFO) # 将默认级别设为INFO,减少DEBUG级别的输出 # 清除已有的handlers for handler in root_logger.handlers[:]: root_logger.removeHandler(handler) root_logger.addHandler(file_handler) root_logger.addHandler(console_handler) logging.info("=================================================================") logging.info("应用程序启动") logging.info(f"Python version: {sys.version}") logging.info(f"Working directory: {os.getcwd()}") logging.info(f"日志文件: {log_file_path}") except Exception as e: # 如果文件处理器创建失败,仅使用控制台输出 logging.error(f"无法设置日志文件,将只使用控制台输出: {e}") # 确保根logger至少有一个控制台处理器 root_logger = logging.getLogger() if not root_logger.handlers: console_handler = logging.StreamHandler(sys.stdout) console_format = logging.Formatter('%(asctime)s - %(levelname)s - %(name)s - [%(funcName)s:%(lineno)d] - %(message)s') console_handler.setFormatter(console_format) root_logger.addHandler(console_handler) logging.info("=================================================================") logging.info("应用程序启动 (仅控制台日志)") logging.info(f"Python version: {sys.version}") logging.info(f"Working directory: {os.getcwd()}") def main(): """主程序入口""" app = QApplication(sys.argv) logging.info("=====================================================") logging.info(" 应用程序启动 ") logging.info("=====================================================") try: # 读取配置 config = ConfigLoader.get_instance() # 打印关键配置信息 enable_serial_ports = config.get_value('serial.printer.enabled', False) # 键盘监听器配置信息 enable_keyboard_listener = config.get_value('serial.keyboard.enabled', False) logging.info(f"配置信息 - 启用串口: {enable_serial_ports}, 启用键盘监听: {enable_keyboard_listener}") # 初始化电力监控器 from utils.electricity_monitor import ElectricityMonitor electricity_monitor = ElectricityMonitor.get_instance() electricity_monitor.start() logging.info("电力监控器已启动") # 设置中文翻译器 translator = QTranslator(app) # 使用兼容新旧版本的方式获取翻译路径 try: # 新版本API translations_path = QLibraryInfo.path(QLibraryInfo.LibraryPath.TranslationsPath) except AttributeError: # 旧版本API translations_path = QLibraryInfo.location(QLibraryInfo.TranslationsPath) # 尝试加载翻译文件 translation_loaded = translator.load("qt_zh_CN", translations_path) # 如果加载失败,尝试在可执行文件目录下查找翻译文件 if not translation_loaded and getattr(sys, 'frozen', False): # 打包后的应用 app_dir = os.path.dirname(sys.executable) possible_paths = [ os.path.join(app_dir, "translations"), os.path.join(app_dir, "PySide6", "translations"), os.path.join(app_dir, "Qt", "translations"), ] for path in possible_paths: if os.path.exists(path): if translator.load("qt_zh_CN", path): translation_loaded = True translations_path = path break app.installTranslator(translator) # 设置应用程序的本地化 locale = QLocale(QLocale.Chinese, QLocale.China) QLocale.setDefault(locale) # 安装菜单翻译器 MenuTranslator.install_menu_translator(app) logging.info(f"已设置中文翻译器,翻译路径: {translations_path},加载状态: {translation_loaded}") # 创建db目录(如果不存在) os.makedirs('db', exist_ok=True) # 从配置获取SQLite数据库路径 config_loader = ConfigLoader.get_instance() sqlite_config = config_loader.get_database_config('sqlite') sqlite_db_path = sqlite_config.get('path', 'db/jtDB.db') # 检查数据库是否存在,如果不存在则初始化 if not os.path.exists(sqlite_db_path): from utils.init_db import init_database init_database() logging.info("初始化数据库完成") login_widget = LoginWidget() login_widget.show() exit_code = app.exec() logging.info(f"应用程序退出,退出码: {exit_code}") # 关闭所有数据库连接 from utils.sql_utils import SQLUtils SQLUtils.close_all_connections() # 停止电力监控器 try: from utils.electricity_monitor import ElectricityMonitor electricity_monitor = ElectricityMonitor.get_instance() electricity_monitor.stop() logging.info("电力监控器已停止") except Exception as e: logging.error(f"停止电力监控器时发生错误: {e}") sys.exit(exit_code) except Exception as e: logging.critical(f"严重错误: {e}", exc_info=True) print(f"严重错误: {e}") # 确保即使日志系统故障也能看到错误 sys.exit(1) if __name__ == "__main__": main()