You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何从多进程任务中获取日志并在GUI界面展示?

问题分析与解决方案

核心问题在于多进程的日志无法直接通过父进程的Qt信号传递到GUI:每个子进程拥有独立的Python解释器和logging系统,父进程添加的QtHandler不会被子进程继承。子进程中触发的emit属于子进程内的对象,其信号无法跨进程传递到主进程的GUI线程,因此update_log_gui槽函数无法收到信号。

以下是修正后的完整代码:


1. gui.py 修改

from PyQt5 import QtCore, QtWidgets
import logging
from log_test import main
from multiprocessing import Queue
import threading

Signal = QtCore.pyqtSignal
Slot = QtCore.pyqtSlot


class QtHandler(logging.Handler):
    def __init__(self, slotfunc, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.signal = Signal(str, logging.LogRecord)
        self.signal.connect(slotfunc)

    def emit(self, record):
        s = self.format(record)
        self.signal.emit(s, record)


class Worker(QtCore.QObject):
    finished = Signal()

    def __init__(self, log_queue):
        super().__init__()
        self.log_queue = log_queue

    @Slot()
    def start(self):
        main(self.log_queue)
        self.finished.emit()


class LogQueueListener(QtCore.QObject):
    log_received = Signal(logging.LogRecord)

    def __init__(self, log_queue, handler):
        super().__init__()
        self.log_queue = log_queue
        self.handler = handler
        self.running = True
        self.log_received.connect(self.handler.emit)
        # 启动后台监听线程
        self.listener_thread = threading.Thread(target=self.listen, daemon=True)
        self.listener_thread.start()

    def listen(self):
        while self.running:
            try:
                record = self.log_queue.get(timeout=1)
                if record is None:  # 结束标记
                    break
                self.log_received.emit(record)
            except:
                pass


class Ui_Dialog(QtCore.QObject):
    def __init__(self):
        super().__init__()
        self.log_queue = Queue()

    def setupUi(self, Dialog):
        Dialog.setObjectName("Dialog")
        Dialog.setEnabled(True)
        Dialog.resize(530, 440)
        self.verticalLayout = QtWidgets.QVBoxLayout(Dialog)
        self.verticalLayout.setObjectName("verticalLayout")
        self.button = QtWidgets.QPushButton(Dialog)
        self.button.setText("start working")
        self.verticalLayout.addWidget(self.button)
        self.logWidget = QtWidgets.QPlainTextEdit(Dialog)
        self.logWidget.setReadOnly(True)
        self.verticalLayout.addWidget(self.logWidget)

        # 初始化日志处理器与队列监听
        self.handler = QtHandler(self.update_log_gui)
        root_logger = logging.getLogger('log')
        root_logger.addHandler(self.handler)
        root_logger.setLevel(logging.ERROR)
        # 设置日志格式
        formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
        self.handler.setFormatter(formatter)
        # 启动队列监听
        self.log_listener = LogQueueListener(self.log_queue, self.handler)

        self.button.clicked.connect(self.start_work)

    @Slot(str, logging.LogRecord)
    def update_log_gui(self, status, record):
        self.logWidget.appendPlainText(status)

    def config_thread(self):
        self.worker_thread = QtCore.QThread()
        self.worker_thread.setObjectName('WorkerThread')
        self.worker = Worker(self.log_queue)
        self.worker.moveToThread(self.worker_thread)
        self.worker_thread.started.connect(self.worker.start)
        self.worker.finished.connect(self.worker_thread.quit)
        self.worker.finished.connect(self.worker.deleteLater)
        self.worker_thread.finished.connect(self.worker_thread.deleteLater)
        self.worker_thread.finished.connect(lambda: self.button.setEnabled(True))
        # 线程结束时停止日志监听
        self.worker_thread.finished.connect(self.stop_log_listener)

    def stop_log_listener(self):
        self.log_listener.running = False
        self.log_queue.put(None)  # 发送结束信号

    def start_work(self):
        self.config_thread()
        self.worker_thread.start()
        self.button.setEnabled(False)


if __name__ == "__main__":
    import sys
    QtCore.QThread.currentThread().setObjectName('MainThread')
    app = QtWidgets.QApplication(sys.argv)
    Dialog = QtWidgets.QDialog()
    ui = Ui_Dialog()
    ui.setupUi(Dialog)
    Dialog.show()
    sys.exit(app.exec_())

2. log_test.py 修改

import logging
import time
from multiprocessing import Pool
from logging.handlers import QueueHandler


def configure_logging(log_queue):
    # 为每个子进程初始化日志处理器
    root_logger = logging.getLogger('log')
    root_logger.setLevel(logging.ERROR)
    root_logger.addHandler(QueueHandler(log_queue))


def f(name):
    logger = logging.getLogger('log.' + name)
    logger.error('hello there 1')
    time.sleep(0.5)
    logger.error('hello there 2')
    time.sleep(0.5)
    logger.error('hello there 3')
    time.sleep(0.5)


def main(log_queue):
    # 初始化进程池时配置子进程日志
    with Pool(5, initializer=configure_logging, initargs=(log_queue,)) as p:
        p.map(f, ['aaa', 'bbb', 'ccc'])

关键修改说明

  1. 跨进程日志传输:使用multiprocessing.Queue作为中间载体,子进程通过QueueHandler将日志记录放入队列,父进程通过LogQueueListener线程监听队列。
  2. Qt信号安全调度:LogQueueListener将队列中获取的日志通过Qt信号转发到QtHandler,确保日志更新操作在主GUI线程执行(Qt信号跨线程会自动调度到接收对象的线程)。
  3. 子进程日志初始化:通过Pool的initializer参数,在每个子进程启动时统一配置日志处理器,确保所有子进程的日志都能进入队列。

内容的提问来源于stack exchange,提问作者나상원

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 23:50:35