logging模块_小结

logging模块

距离上次写博客已经快几近一年,短短一年的时间发生的事情改变了博主的一生。命运弄人,博主能做的唯有将失去的东西,用十倍的努力成倍的拿回来
the best revenge is massive success.我又再次回来,再次打开网页,打开markdown继续我的“创作”(搬运)。

logging概念:用于在程序运行时追踪和记录事件,告警和错误之用

logging何时使用:
lwhen to use logging
官网doc的截图,简述:输出正常操作的信息用print(),记录事件流程用logging.info(),解决告警信息用logging.warning(),报告并抛出错误用raise,报告,记录但是不抛出异常用logging.error()

logging分为5个等级 :DEBUG INFO WARNING ERROR CRITICAL
设置logging的等级时使用:

logging.basicConfig(filename='myapp.log', level=logging.INFO)

函数的完整参数列表

filenameSpecifies that a FileHandler be created, using the specifiedfilename, rather than a StreamHandler.
filemodeSpecifies the mode to open the file, if filename is specified(if filemode is unspecified, it defaults to a)
datefmtUse the specified date/time format.
formatUse the specified format string for the handler.
levelSet the root logger level to the specified level.
streamUse the specified stream to initialize the StreamHandler. Note that this argument is incompatible with ‘filename’ - if both are present, ‘stream’ is ignored.

(format参数既可以使用%s %d %f ,又可以使用更高级的template和format函数来定制)


logger

  • Logger:提供基本的方法
  • Handler:提供指向的目标文件
  • Filter:过滤日志文件
  • Formatter:自定义化输出

best practice

logger = logging.getLogger(__name__)

logger的名称随模块名的变更而变更

logger常用函数:
Logger.setLevel()
Logger.addHandler()
Logger.addFilter()

handler常用函数:
setLevel()
setFormatter()
addFilter() and removeFilter()

formatter通过%s,template和format函数来自定义输出

demo:

import logging

# create logger
logger = logging.getLogger('simple_example')
logger.setLevel(logging.DEBUG)

# create console handler and set level to debug
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)

# create formatter
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')

# add formatter to ch
ch.setFormatter(formatter)

# add ch to logger
logger.addHandler(ch)

# 'application' code
logger.debug('debug message')
logger.info('info message')
logger.warn('warn message')
logger.error('error message')
logger.critical('critical message')

其中配置文件可以写为配置文件的形式,如下:

logging.config.fileConfig('logging.conf')
[loggers]
keys=root,simpleExample

[handlers]
keys=consoleHandler

[formatters]
keys=simpleFormatter

[logger_root]
level=DEBUG
handlers=consoleHandler

[logger_simpleExample]
level=DEBUG
handlers=consoleHandler
qualname=simpleExample
propagate=0

[handler_consoleHandler]
class=StreamHandler
level=DEBUG
formatter=simpleFormatter
args=(sys.stdout,)

[formatter_simpleFormatter]
format=%(asctime)s - %(name)s - %(levelname)s - %(message)s
datefmt=
# ui_main_window.py from datetime import datetime import json import os import sys from pathlib import Path from PyQt6.QtWidgets import ( QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton, QTextEdit, QFileDialog, QComboBox, QGroupBox, QApplication, QMessageBox, QDialog, QCheckBox, QRadioButton ) from PyQt6.QtCore import QObject, pyqtSignal import logging print(f" 当前工作目录: {os.getcwd()}") print(f" 文件所在目录: {Path(__file__).resolve()}") from excel_to_clm import ExcelToCLMConverter from channel.range_channel_sync import CLMRangeSynchronizer from power.power_sync import PowerTableSynchronizer from rate_set.rate_sync import RateSetSynchronizer from utils import get_output_dir # ============= 日志重定向器 ============== class LogEmitter(QObject): log_signal = pyqtSignal(str) # ============= 主窗口类 ============== class CLMGeneratorUI(QWidget): # 类顶部定义(靠近 class CLMGeneratorUI(QWidget):) LOCALE_UI_MAPPING = [ ("2_4G", "locale_2g_idx"), ("2_4G_HT", "locale_2g_ht_idx"), ("5G", "locale_5g_idx"), ("5G_HT", "locale_5g_ht_idx"), ("6G", "locale_6g_idx"), ("6G_HT", "locale_6g_ht_idx"), ] def __init__(self, generator=None): super().__init__() self.generator = generator or ExcelToCLMConverter() self.log_emitter = LogEmitter() self.log_emitter.log_signal.connect(self.update_log_area) # 设置日志系统以捕获同步器输出 self._setup_logging_redirect() self.setWindowTitle("Wi-Fi CLM Generator Tool v1.2") self.resize(700, 600) self.init_ui() self.load_default_config_for_power_tables() def _setup_module_logger(self, module_name: str): """ 为指定模块名设置日志转发到 UI :param module_name: 如 "channel", "power", "rate_set" """ logger = logging.getLogger(module_name) logger.setLevel(logging.INFO) # 定义统一的日志处理器类(在方法内定义,但会被外部引用) class QtStreamHandler(logging.Handler): def __init__(self, emitter): super().__init__() self.emitter = emitter self.setFormatter( logging.Formatter('%(levelname)s %(name)s: %(message)s') ) def emit(self, record): try: msg = self.format(record) if msg.strip(): self.emitter.log_signal.emit(msg.strip()) except Exception: self.handleError(record) # 移除旧的同类型 handler for h in logger.handlers[:]: if isinstance(h, QtStreamHandler): logger.removeHandler(h) # 添加新 handler handler = QtStreamHandler(self.log_emitter) logger.addHandler(handler) logger.propagate = False # 防止被父 logger 重复处理 def _setup_logging_redirect(self): """统一注册多个模块日志转发""" modules = ["channel", "power", "rate_set", "excel_to_clm"] for mod in modules: self._setup_module_logger(mod) class LogStream: """伪装成文件对象的日志流,用于 logging.Handler""" def __init__(self, emitter): self.emitter = emitter def write(self, msg): if msg.strip(): self.emitter.log_signal.emit(msg.strip()) def flush(self): pass def init_ui(self): layout = QVBoxLayout() # === 1. 输入区:Excel & Config === input_group = QGroupBox("输入配置") input_layout = QVBoxLayout() # Excel 文件 excel_row = QHBoxLayout() excel_row.addWidget(QLabel("Excel 文件:")) self.excel_path = QLineEdit("input/Archer BE900US 2.xlsx") self.btn_browse_excel = QPushButton("浏览...") self.btn_browse_excel.clicked.connect(self.select_excel) excel_row.addWidget(self.excel_path) excel_row.addWidget(self.btn_browse_excel) input_layout.addLayout(excel_row) # Config 文件 config_row = QHBoxLayout() config_row.addWidget(QLabel("Config 文件:")) self.config_path = QLineEdit("config/config.json") self.btn_browse_config = QPushButton("浏览...") self.btn_browse_config.clicked.connect(self.select_config_file) config_row.addWidget(self.config_path) config_row.addWidget(self.btn_browse_config) input_layout.addLayout(config_row) # --- C 源文件(目标文件)--- cfile_row = QHBoxLayout() cfile_row.addWidget(QLabel("目标 C 文件:")) self.c_file_path = QLineEdit("input/wlc_clm_data_6726b0.c") self.btn_browse_cfile = QPushButton("浏览...") self.btn_browse_cfile.clicked.connect(self.select_c_file) cfile_row.addWidget(self.c_file_path) cfile_row.addWidget(self.btn_browse_cfile) input_layout.addLayout(cfile_row) # 关键:放在这里! # Locale ID(可选,未来可能用于过滤) locale_row = QHBoxLayout() locale_row.addWidget(QLabel("默认国家码:")) self.locale_combo = QComboBox() self.locale_combo.addItems(["US", "FCC", "EU", "JP", "CN"]) locale_row.addWidget(self.locale_combo) input_layout.addLayout(locale_row) input_group.setLayout(input_layout) layout.addWidget(input_group) # === 2. 主流程控制区(分步操作)=== step_group = QGroupBox("主流程控制(请按顺序操作)") step_layout = QVBoxLayout() # === 0. 全局预览模式开关 === self.chk_dry_run = QCheckBox(" 启用预览模式(所有写入操作不实际修改文件)") self.chk_dry_run.setChecked(True) self.chk_dry_run.setToolTip("勾选后所有‘写入’操作仅模拟变更,不会保存到磁盘") layout.addWidget(self.chk_dry_run) # 步骤 1: 解析 Excel self.btn_parse = QPushButton(" 1. 解析 Excel 并生成数据") self.btn_parse.setToolTip("读取Excel并生成内部配置与RANGE宏") self.btn_parse.clicked.connect(self.run_parse) step_layout.addWidget(self.btn_parse) # 步骤 2: 同步选项(互斥选择) sync_choice_layout = QHBoxLayout() self.radio_channel = QRadioButton("插入信道范围") self.radio_power = QRadioButton("注入功率表") self.radio_channel.setChecked(True) # 默认选中 sync_choice_layout.addWidget(self.radio_channel) sync_choice_layout.addWidget(self.radio_power) step_layout.addLayout(sync_choice_layout) # 执行按钮 self.btn_execute_sync = QPushButton(" 2. 执行选定的同步任务") self.btn_execute_sync.clicked.connect(self.execute_selected_sync) step_layout.addWidget(self.btn_execute_sync) step_group.setLayout(step_layout) layout.addWidget(step_group) # === 3. 功率表名称配置(紧凑排布)=== power_table_group = QGroupBox("功率表名称配置") power_table_layout = QVBoxLayout() # 提示语 power_table_layout.addWidget( QLabel("为每个频段指定对应的功率表名称(如 us, eu, cn_5g):") ) # 使用网格布局限制宽度 + 控件大小 grid = QHBoxLayout() # 改为横向两列布局,节省垂直空间 left_col = QVBoxLayout() right_col = QVBoxLayout() table_configs = [ ("2_4G", "2.4G"), ("2_4G_HT", "2.4G HT"), ("5G", "5G"), ("5G_HT", "5G HT"), ("6G", "6G"), ("6G_HT", "6G HT"), ] self.power_table_edits = {} for idx, (key, label) in enumerate(table_configs): row_layout = QHBoxLayout() row_layout.addWidget(QLabel(f"{label}:"), 1) edit = QLineEdit(key.lower().replace("_ht", "").upper()) edit.setMaximumWidth(120) # 限制编辑框宽度 edit.setObjectName(f"edit_{key.replace('.', '_')}") self.power_table_edits[key] = edit row_layout.addWidget(edit, 2) # 分左右列填充 if idx < len(table_configs) // 2: left_col.addLayout(row_layout) else: right_col.addLayout(row_layout) grid.addLayout(left_col) grid.addLayout(right_col) power_table_layout.addLayout(grid) power_table_group.setLayout(power_table_layout) layout.addWidget(power_table_group) # === 5. 独立功能按钮:速率集同步(随时可用)=== utility_group = QGroupBox("独立工具") utility_layout = QVBoxLayout() self.btn_sync_rate_set = QPushButton(" 写入速率集(即时生效)") self.btn_sync_rate_set.clicked.connect(self.run_rate_set_sync) self.btn_sync_rate_set.setToolTip("直接将速率集数据写入C文件,不受主流程限制") utility_layout.addWidget(self.btn_sync_rate_set) utility_group.setLayout(utility_layout) layout.addWidget(utility_group) # === 6. 日志输出 === self.log_area = QTextEdit() self.log_area.setReadOnly(True) self.log_area.setFontFamily("Consolas") # 更适合日志查看的等宽字体 self.log_area.setStyleSheet("QTextEdit { font-size: 9pt; }") self.log_area.append("🔧 CLM Generator 已启动,请开始第一步...") layout.addWidget(self.log_area) self.setLayout(layout) # === 初始化状态控制 === self.update_button_states() # 绑定事件以动态更新按钮状态 self.excel_path.textChanged.connect(self.update_button_states) self.c_file_path.textChanged.connect(self.update_button_states) def update_button_states(self): """根据输入字段和解析状态来启用/禁用相关按钮""" # 检查 Excel 文件路径是否存在 excel_path = self.excel_path.text().strip() has_excel = bool(excel_path) and os.path.exists(excel_path) # 检查 C 文件路径是否填写 c_file_path = self.c_file_path.text().strip() has_c_file = bool(c_file_path) # 检查是否已完成解析(last_config 存在且非 None) has_parsed = ( hasattr(self.generator, 'last_config') and self.generator.last_config is not None ) # 所有判断都返回明确的布尔值 self.btn_parse.setEnabled(has_excel) can_sync = has_parsed and has_c_file self.btn_execute_sync.setEnabled(bool(can_sync)) # 确保为 bool def execute_selected_sync(self): """根据用户选择执行信道同步或功率表同步""" if self.radio_channel.isChecked(): self.run_range_sync() elif self.radio_power.isChecked(): self.run_power_sync() else: self.log("请选择要执行的操作类型") def run_power_sync(self): """执行功率表同步:调用 PowerTableSynchronizer 完成注入""" try: config_path_str = self.config_path.text().strip() or "config/config.json" config_path = Path(config_path_str) if not config_path.exists(): self.log(f"Config 文件不存在: {config_path}") QMessageBox.critical(self, "错误", f"配置文件不存在:\n{config_path}") return dry_run = self.chk_dry_run.isChecked() # ========== 添加日志文件支持 begin ========== file_handler = None log_file_path = None if not dry_run: timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") log_dir = get_output_dir() / "log" log_dir.mkdir(parents=True, exist_ok=True) log_file_path = log_dir / f"power_sync_{timestamp}.log" file_handler = logging.FileHandler(log_file_path, encoding='utf-8') file_handler.setFormatter( logging.Formatter('%(asctime)s [%(levelname)s] %(name)s: %(message)s', datefmt='%H:%M:%S') ) power_logger = logging.getLogger("power") power_logger.addHandler(file_handler) self.log(f" 功率表日志文件已创建: {log_file_path.name}") # ========== end ========== self.log(" 初始化功率表同步器...") synchronizer = PowerTableSynchronizer( c_file_path=None, dry_run=dry_run, config_path=str(config_path), ) self.log(" 开始同步功率表与国家码...") was_modified = synchronizer.run() if was_modified: if dry_run: self.log(" 预览模式:检测到变更,但未写入文件") else: self.log(" 功率表与国家码同步完成!") self.log(" 所有修改已保存至目标 C 文件") else: self.log(" 所有 Locale 已存在,无需更新") QMessageBox.information(self, "成功", "功率表同步完成!详情见日志。") except Exception as e: self.log(f" 功率表同步失败: {str(e)}") import traceback self.log(f"错误详情:\n{traceback.format_exc()}") QMessageBox.critical(self, "错误", f"同步过程出错:\n{str(e)}") finally: # ========== 清理 FileHandler begin ========= if file_handler: try: file_handler.flush() file_handler.close() logging.getLogger("power").removeHandler(file_handler) # ✅ power self.log(" 功率表日志文件已关闭") except Exception as e: print(f"[WARN] 关闭 power 日志文件失败: {e}") if not dry_run and log_file_path: self.log(f" 完整日志已保存至:\n{log_file_path}") # ========== end ========== def run_rate_set_sync(self): """执行速率集同步""" try: config_path_str = self.config_path.text().strip() or "config/config.json" config_path = Path(config_path_str) if not config_path.exists(): self.log(f"Config 文件不存在: {config_path}") QMessageBox.critical(self, "错误", f"配置文件不存在:\n{config_path}") return c_file_path_str = self.c_file_path.text().strip() if not c_file_path_str: self.log(" 未指定目标 C 文件路径") QMessageBox.warning(self, "警告", "请先设置目标 C 文件路径") return c_file_path = Path(c_file_path_str) if not c_file_path.exists(): self.log(f"目标 C 文件不存在: {c_file_path}") QMessageBox.critical(self, "错误", f"C 文件不存在:\n{c_file_path}") return dry_run = self.chk_dry_run.isChecked() # ========== 添加日志文件支持 begin ========== file_handler = None log_file_path = None if not dry_run: timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") log_dir = get_output_dir() / "log" log_dir.mkdir(parents=True, exist_ok=True) log_file_path = log_dir / f"rate_sync_{timestamp}.log" file_handler = logging.FileHandler(log_file_path, encoding='utf-8') file_handler.setFormatter( logging.Formatter('%(asctime)s [%(levelname)s] %(name)s: %(message)s', datefmt='%H:%M:%S') ) rate_logger = logging.getLogger("rate_set") rate_logger.addHandler(file_handler) self.log(f" 速率集日志文件已创建: {log_file_path.name}") # ========== end ========== self.log(" 初始化 [速率集同步器] ...") try: synchronizer = RateSetSynchronizer( c_file_path=str(c_file_path), dry_run=dry_run, config_path=str(config_path) ) except Exception as e: self.log(f" ❌ 初始化失败: {e}") QMessageBox.critical(self, "错误", f"无法创建速率集同步器:\n{str(e)}") return self.log(f" 开始同步速率集数据... (模式: {'预览' if dry_run else '执行'})") result = synchronizer.run() success = result.get("success", False) changed = result.get("changed", False) if success: if dry_run: if changed: self.log(" 📊 预览完成:检测到变更,确认无误后可关闭预览模式执行") else: self.log(" ℹ️ 预览完成:无新数据需要注入") QMessageBox.information(self, "预览完成", "预览模式下未修改文件。") else: if changed: backup = result.get("backup") self.log(f" ✅ 速率集同步成功!") self.log(f" 📁 修改文件: {result['file']}") if backup: self.log(f" 💾 备份文件: {Path(backup).name}") QMessageBox.information(self, "成功", f"速率集已写入!\n备份: {Path(backup).name}") else: self.log(" ℹ️ 所有速率集均已存在,无需更新") QMessageBox.information(self, "无需更新", "所有条目已存在,无需修改。") else: error = result.get("error", "未知错误") self.log(f" ❌ 同步失败: {error}") QMessageBox.critical(self, "失败", f"同步过程中发生错误:\n{error}") except Exception as e: self.log(f" ⚠️ 意外错误: {type(e).__name__}: {e}") import traceback self.log(f"详细堆栈:\n{traceback.format_exc()}") QMessageBox.critical(self, "严重错误", f"程序异常终止:\n{str(e)}") finally: # ========== 清理 FileHandler begin ========== if file_handler: try: file_handler.flush() file_handler.close() logging.getLogger("rate_set").removeHandler(file_handler) self.log(" 功率表日志文件已关闭") except Exception as e: print(f"[WARN] 关闭 power 日志文件失败: {e}") if not dry_run and log_file_path: self.log(f" 完整日志已保存至:\n{log_file_path}") # ========== end ========== def run_parse(self): """解析 Excel 并生成输出(使用真实的 ExcelToCLMConverter.convert 流程)""" file_handler = None log_file_path = None try: excel_path = self.excel_path.text().strip() config_path_str = self.config_path.text().strip() or "config/config.json" dry_run = False # 假设 parse 总是实际执行(非预览) # ========== 添加日志文件支持 begin ========== timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") log_dir = get_output_dir() / "log" log_dir.mkdir(parents=True, exist_ok=True) log_file_path = log_dir / f"parse_{timestamp}.log" file_handler = logging.FileHandler(log_file_path, encoding='utf-8') file_handler.setFormatter( logging.Formatter('%(asctime)s [%(levelname)s] %(name)s: %(message)s', datefmt='%H:%M:%S') ) # 使用独立的 logger 名称 parse_logger = logging.getLogger("excel_to_clm") parse_logger.addHandler(file_handler) parse_logger.setLevel(logging.INFO) self.log(f"📝 已创建解析日志文件: {log_file_path.name}") # ========== end ========== self.log("INFO 开始解析 Excel 文件...") if not excel_path: self.log("ERROR 请先选择 Excel 文件!") return if not os.path.exists(excel_path): self.log(f"ERROR Excel 文件不存在: {excel_path}") return if not os.path.exists(config_path_str): self.log(f"ERROR 配置文件不存在: {config_path_str}") return # === Step A: 收集 locale_map === import re locale_map = {} for display_name, enum_key in self.LOCALE_UI_MAPPING: text = self.power_table_edits[display_name].text().strip() if not text: self.log(f"ERROR [{display_name}] 功率表名称不能为空!") return if not re.match(r'^[a-zA-Z0-9_]+$', text): self.log(f"ERROR [{display_name}] 名称非法:'{text}' → 只能含字母、数字、下划线") return locale_map[enum_key] = text main_locale = locale_map["locale_2g_idx"] # === Step B: 更新 config.json 中的 assigned_locale === saved_names, full_config = self._read_locales_from_config(config_path_str) if full_config is None: self.log("ERROR 加载配置文件失败,终止解析") return updated_count = 0 for tgt in full_config.get("locale_targets", []): enum = tgt.get("enum") if enum in locale_map: new_loc = locale_map[enum] old_loc = tgt.get("assigned_locale") if old_loc != new_loc: tgt["assigned_locale"] = new_loc self.log(f"INFO {enum}: '{old_loc}' → '{new_loc}'") updated_count += 1 if updated_count > 0: try: with open(config_path_str, 'w', encoding='utf-8') as f: json.dump(full_config, f, indent=4, ensure_ascii=False) self.log(f"INFO 已更新 {updated_count} 个 assigned_locale 并保存到 config.json") except Exception as e: self.log(f"ERROR 保存 config 失败: {e}") import traceback self.log(f"DEBUG {traceback.format_exc()}") return # === Step C: 调用 convert() === try: self.generator = ExcelToCLMConverter( config_path=config_path_str, locale_display_name=main_locale ) self.log("INFO 调用 convert() 开始处理...") self.generator.convert(excel_path) self.log("✅ Excel 解析与代码生成完成!") except Exception as e: self.log(f"ERROR 执行 convert() 失败: {type(e).__name__}: {e}") import traceback self.log(f"DEBUG 详细堆栈:\n{traceback.format_exc()}") QMessageBox.critical(self, "解析失败", f"转换过程中发生错误:\n{str(e)}") return self.update_button_states() except Exception as e: self.log(f"❌ 解析流程意外中断: {e}") import traceback self.log(f"TRACEBACK:\n{traceback.format_exc()}") finally: # ========== 清理 FileHandler begin ========== if file_handler: try: file_handler.flush() file_handler.close() logging.getLogger("excel_to_clm").removeHandler(file_handler) self.log("📁 解析日志文件已关闭") except Exception as e: print(f"[WARN] 关闭 parse 日志文件失败: {e}") if log_file_path: self.log(f"📄 完整日志已保存至:\n{log_file_path}") # ========== end ========== def select_excel(self): file, _ = QFileDialog.getOpenFileName( self, "选择 Excel 文件", "", "Excel Files (*.xlsx *.xls)") if file: self.excel_path.setText(file) def select_config_file(self): file, _ = QFileDialog.getOpenFileName( self, "选择配置文件", "config/", "JSON 配置文件 (*.json);;All Files (*)") if file: self.config_path.setText(file) self.load_default_config_for_power_tables() def select_c_file(self): file, _ = QFileDialog.getOpenFileName( self, "选择 C 源文件", "input/", "C Source Files (*.c)") if file: self.c_file_path.setText(file) def log(self, msg): """发送日志消息到信号队列""" self.log_emitter.log_signal.emit(msg) def update_log_area(self, msg): """更新日志文本框""" self.log_area.append(f"> {msg}") print(f"[LOG] {msg}") # ← 关键:让开发者能看到 def clear_log(self): self.log_area.clear() def show_preview(self): try: output_file = get_output_dir() with open(output_file, "r", encoding="utf-8") as f: content = f.read() dialog = QDialog(self) dialog.setWindowTitle(f"预览: {output_file}") dialog.resize(700, 500) layout = QVBoxLayout() text_edit = QTextEdit() text_edit.setReadOnly(True) text_edit.setText(content[:2000] + "\n...(后续内容已截断)") close_btn = QPushButton("关闭") close_btn.clicked.connect(dialog.accept) layout.addWidget(text_edit) layout.addWidget(close_btn) dialog.setLayout(layout) dialog.exec() except FileNotFoundError: self.log(f" 无法预览: 文件未生成 '{output_file}',请先点击【开始生成】") except Exception as e: self.log(f" 预览失败: {str(e)}") def run_range_sync(self,): """运行同步:使用 generator 生成的 manifest 路径自动同步到 C 文件""" from pathlib import Path import logging from datetime import datetime if not hasattr(self.generator, 'last_config') or not self.generator.last_config: self.log(" 尚未生成任何 文件,请先点击【解析 Excel】") return c_file_path = self.c_file_path.text().strip() if not c_file_path: self.log(" 未指定目标 C 文件路径,请在下方输入或选择") return if not Path(c_file_path).exists(): self.log(f" 目标 C 文件不存在: {c_file_path}") return file_handler = None log_file_path = None dry_run = self.chk_dry_run.isChecked() if not dry_run: timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") log_dir = Path(get_output_dir()) / "log" log_dir.mkdir(parents=True, exist_ok=True) log_file_path = log_dir / f"range_sync_{timestamp}.log" file_handler = logging.FileHandler(log_file_path, encoding='utf-8') file_handler.setFormatter( logging.Formatter('%(asctime)s [%(levelname)s] %(message)s', datefmt='%H:%M:%S') ) target_logger = logging.getLogger("channel") target_logger.addHandler(file_handler) self.log(f" 日志文件已创建: {log_file_path}") try: mode = "预览模式" if dry_run else "执行模式" self.log(f" 开始同步... ({mode})") self.log(f" 修改文件: {c_file_path}") sync = CLMRangeSynchronizer( c_file_path=c_file_path, dry_run=dry_run ) was_modified = sync.run() if dry_run: if was_modified: self.log("发现需要更新的内容,确认无误后可点击【执行同步】") else: self.log("所有 RANGE 已存在,无需修改") else: if was_modified: self.log(f"同步成功!已更新 {c_file_path}") self.log(f"备份已保存为 {c_file_path}.bak") else: self.log("文件已是最新,无需修改") except Exception as e: self.log(f"同步失败: {str(e)}") import traceback tb_msg = traceback.format_exc() self.log(f"错误详情:\n{tb_msg}") raise finally: if file_handler: try: file_handler.flush() file_handler.close() logging.getLogger("channel").removeHandler(file_handler) self.log("日志文件已关闭") except Exception as e: print(f"Warning: failed to close file handler: {e}") if not dry_run and log_file_path: self.log(f"完整日志已保存至:\n{log_file_path}") def load_default_config_for_power_tables(self): """从当前 config.json 自动填充空的功率表名称""" config_path_str = self.config_path.text().strip() or "config/config.json" saved_names, _ = self._read_locales_from_config(config_path_str) if not saved_names: return filled_count = 0 for display_name, enum_key in self.LOCALE_UI_MAPPING: edit_widget = self.power_table_edits.get(display_name) if not edit_widget: continue if not edit_widget.text().strip() and enum_key in saved_names: edit_widget.setText(saved_names[enum_key]) filled_count += 1 if filled_count > 0: self.log(f" 已从 config.json 自动填充 {filled_count} 个功率表名称") def _read_locales_from_config(self, config_path_str): """从 config.json 读取 enum -> assigned_locale 映射""" try: if not os.path.exists(config_path_str): raise FileNotFoundError(f"Config 文件不存在: {config_path_str}") with open(config_path_str, 'r', encoding='utf-8') as f: full_config = json.load(f) saved_names = {} for tgt in full_config.get("locale_targets", []): enum = tgt.get("enum") loc = tgt.get("assigned_locale") if enum and loc: saved_names[enum] = loc.strip() return saved_names, full_config except Exception as e: self.log(f"读取配置失败: {e}") import traceback self.log(traceback.format_exc()) return {}, None def run_full_process(self): """一键完成:解析 Excel → 生成 manifest → 同步到 .c 文件""" reply = QMessageBox.question( self, "确认执行", "是否要执行【解析Excel + 更新C文件】全流程?\n\n" "建议先使用【预览同步】检查变更。", QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No ) if reply != QMessageBox.StandardButton.Yes: return self.run_parse() if hasattr(self.generator, 'last_config') and self.generator.last_config: self.run_range_sync() else: self.log(" 解析失败,无法继续同步") if __name__ == '__main__': import sys import traceback from PyQt6.QtWidgets import QMessageBox def handle_exception(exc_type, exc_value, exc_traceback): if issubclass(exc_type, KeyboardInterrupt): sys.__excepthook__(exc_type, exc_value, exc_traceback) return print("".join(traceback.format_exception(exc_type, exc_value, exc_traceback))) QMessageBox.critical(None, "未捕获异常", f"{exc_type.__name__}: {exc_value}") sys.excepthook = handle_exception app = QApplication([]) try: window = CLMGeneratorUI() window.show() sys.exit(app.exec()) except Exception as e: msg = QMessageBox() msg.setWindowTitle("严重错误") msg.setText(f"程序启动失败:\n\n{type(e).__name__}: {e}\n\n详情查看日志或联系开发者。") msg.setIcon(QMessageBox.Icon.Critical) msg.exec() print(f"Critical error: {e}") 打开打包后的文件双击就显示找不到配置文件退出,问题出在哪,我觉得配置文件要手动选择
11-11
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值