如何实现PyQt5单窗口聚合多模块日志并后台运行
问题分析与解决方案
核心问题
- 日志体系配置错误:原代码仅给
my_logger模块的logger添加处理器,其他模块的子logger日志无法正确传递到全局处理器;且ConsoleWindowLogHandler初始化逻辑混乱,重复添加FileHandler导致日志重复写入。 - 跨模块UI操作错误:
external_module.py中试图创建新UI实例并手动连接信号,违背日志系统的解耦设计,日志应通过框架自动传递到统一处理器。
修改后的代码实现
1. my_logger.py(全局日志配置)
import logging from PyQt5.QtCore import QObject, pyqtSignal # 使用根日志确保所有模块的日志都能被捕获 root_logger = logging.getLogger() root_logger.setLevel(logging.DEBUG) # 全局FileHandler,仅初始化一次 file_formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(name)s - %(message)s') file_handler = logging.FileHandler('logger.log') file_handler.setFormatter(file_formatter) root_logger.addHandler(file_handler) class ConsoleWindowLogHandler(logging.Handler, QObject): LoggerSignal = pyqtSignal(str) def __init__(self): # 多重继承的正确初始化方式 logging.Handler.__init__(self) QObject.__init__(self) self.setFormatter(file_formatter) def emit(self, logRecord): # 格式化日志消息后发送信号 message = self.format(logRecord) self.LoggerSignal.emit(message)
2. example1.py(日志UI展示窗口)
import sys import time import logging from my_logger import ConsoleWindowLogHandler, root_logger from PyQt5.QtCore import QThread from PyQt5.QtWidgets import QWidget, QTextEdit, QPushButton, QVBoxLayout, QApplication # 获取当前模块的logger,自动继承根日志配置 logger = logging.getLogger(__name__) class Window(QWidget): def __init__(self): super().__init__() self.setWindowTitle("集中日志查看器") self.setGeometry(0, 0, 500, 500) # UI组件布局 self.textEdit = QTextEdit() self.textEdit.setReadOnly(True) self.btn_start = QPushButton('启动测试任务') self.btn_clean = QPushButton('清空日志') vert_layout = QVBoxLayout() vert_layout.addWidget(self.textEdit) vert_layout.addWidget(self.btn_start) vert_layout.addWidget(self.btn_clean) self.setLayout(vert_layout) # 按钮信号连接 self.btn_start.clicked.connect(self.start_test_thread) self.btn_clean.clicked.connect(self.textEdit.clear) # 注册日志处理器到根日志,仅执行一次 self.console_handler = ConsoleWindowLogHandler() self.console_handler.LoggerSignal.connect(self.textEdit.append) root_logger.addHandler(self.console_handler) logger.info("日志查看器已启动") # 初始化测试线程 self.test_thread = Worker(self.test_process) def start_test_thread(self): if not self.test_thread.isRunning(): self.test_thread.start() self.btn_start.setEnabled(False) else: logger.info("测试线程已在运行") def test_process(self): for i in range(3): logger.error(f"测试日志行 {i}") time.sleep(1) self.btn_start.setEnabled(True) class Worker(QThread): def __init__(self, func): super().__init__() self.func = func def run(self): self.func() def main(): app = QApplication(sys.argv) window = Window() window.show() sys.exit(app.exec_()) if __name__ == "__main__": main()
3. external_module.py(外部模块日志输出)
import logging import time from PyQt5.QtCore import QThread from PyQt5.QtWidgets import QWidget, QPushButton, QVBoxLayout, QApplication # 获取当前模块的logger,自动继承根日志配置 logger = logging.getLogger(__name__) class ExternalWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle("外部模块窗口") self.setGeometry(500, 0, 300, 200) btn_send_log = QPushButton('发送测试日志') btn_send_log.clicked.connect(self.send_test_logs) layout = QVBoxLayout() layout.addWidget(btn_send_log) self.setLayout(layout) # 启动后台线程持续输出日志 self.log_thread = Worker(self.continuous_log) self.log_thread.start() def send_test_logs(self): logger.debug("外部模块手动触发的调试日志") logger.info("外部模块手动触发的信息日志") def continuous_log(self): count = 0 while True: logger.warning(f"外部模块后台日志 {count}") count += 1 time.sleep(2) class Worker(QThread): def __init__(self, func): super().__init__() self.func = func def run(self): self.func() def main(): app = QApplication(sys.argv) # 启动日志查看器窗口 from example1 import Window log_window = Window() log_window.show() # 启动外部模块窗口 external_window = ExternalWindow() external_window.show() sys.exit(app.exec_()) if __name__ == "__main__": main()
关键修改说明
- 全局日志统一管理:改用根日志
logging.getLogger(),所有模块的子logger日志会自动向上传递,由根日志的处理器统一处理。 - 处理器单例化:FileHandler仅初始化一次,避免重复写入日志;ConsoleWindowLogHandler在UI窗口中仅注册一次,确保所有日志都发送到同一个UI组件。
- 跨模块解耦:外部模块只需获取自身logger并输出日志,无需操作UI实例,日志通过logging框架自动流转到统一的UI展示窗口。
- 修复多重继承问题:
ConsoleWindowLogHandler中正确调用两个父类的构造函数,解决初始化错误。
内容的提问来源于stack exchange,提问作者devxen21
相关产品推荐
相关产品推荐

