通过日志更新QTextEdit时PyQt5程序崩溃

时间:2018-11-13 16:16:27

标签: python python-3.x logging pyqt5

我有一个大型程序,需要很长时间才能记录大量日志。我在前端有一个GUI,其中包含如下定义的自定义日志记录处理程序:

class QHandler(logging.Handler, QTextEdit):
    def __init__(self, parent=None):
        QTextEdit.__init__(self, parent)
        logging.Handler.__init__(self)

        self.setLineWrapMode(QTextEdit.NoWrap)
        self.setReadOnly(True)

        self.emit_lock = Lock()

    def emit(self, record):
        with self.emit_lock:
            self.append(self.format(record))
            self.autoScroll()

    def format(self, record):
        if (record.levelno <= logging.INFO):
            bgcolor = WHITE
            fgcolor = BLACK
        if (record.levelno <= logging.WARNING):
            bgcolor = YELLOW
            fgcolor = BLACK
        if (record.levelno <= logging.ERROR):
            bgcolor = ORANGE
            fgcolor = BLACK
        if (record.levelno <= logging.CRITICAL):
            bgcolor = RED
            fgcolor = BLACK
        else:
            bgcolor = BLACK
            fgcolor = WHITE

        self.setTextBackgroundColor(bgcolor)
        self.setTextColor(fgcolor)
        self.setFont(DEFAULT_FONT)
        record = logging.Handler.format(self, record)
        return record

    def autoScroll(self):
        self.verticalScrollBar().setSliderPosition(self.verticalScrollBar().maximum())

我有一个主要的GUI(QMainWindow),它通过以下方式添加此处理程序:

# inside __init__ of main GUI (QMainWindow):
self.status_handler = QHandler()
# Main gui is divided into tabs and the status handler box is added to the second tab
main_tabs.addTab(self.status_handler, 'Status') 

我有控制器功能,可通过以下方式初始化日志记录处理程序:

# inside controller initializing function
gui = gui_class() # this is the main gui that initializes the handler among other things
logger = logging.getLogger()
gui.status_handler.setFormatter(file_formatter) # defined elsewhere
logger.addHandler(gui.status_handler)

一旦提高了GUI并初始化了日志记录,我将使用以下命令完成python执行:

app = QApplication.instance()
if (app is None):
    app = QApplication([])
    app.setStyle('Fusion')
app.exec_()

GUI的一些插槽连接到按钮信号,这些按钮产生线程以进行实际处理。每个处理线程都有它自己的日志记录调用,该调用似乎可以按预期工作。它们的定义如下:

class Subprocess_Thread(Thread):
    def __init__(self, <args>):
        Thread.__init__(self)
        self.logger = logging.getLogger(self.__class__.__name__)
        self.logger.info('Subprocess Thread Created')

    def run(self):
        # does a bunch of stuff
        self.logger.info('Running stuff')
        # iterates over other objects and calls on them to do stuff
        # where they also have a logger attached and called just like above

当我在没有GUI或什至最小化GUI的情况下运行我的应用程序时,每次运行都很好。我可以在控制台中看到我的日志消息(命令提示符或spyder)。

如果在不最小化GUI的情况下运行相同的应用程序,我将在GUI中看到用于初始化的日志消息以及线程进程的前几部分,但随后它将挂起,看似随机。没有错误消息,正在使用的单个内核的CPU使用率似乎已达到极限。我包括了一个锁,只是为了确保logging不会从不同的线程进入,但这也无济于事。

我尝试去过QPlainTextEditQListWidget,但是每次都遇到相同的问题。

有人知道这个GUI元素为什么在视图中并记录消息时会导致整个Python解释器挂起吗?

1 个答案:

答案 0 :(得分:0)

采样的QHandler不是线程安全的,因此如果您从另一个线程调用它(因为它是GUI),它将产生问题,一种可能的解决方案是从辅助线程发送数据({{1 }})通过def emit(self, record):到GUI线程,您必须使用QMetaObject

pyqtSlot

示例:

class QHandler(logging.Handler, QtWidgets.QTextEdit):
    def __init__(self, parent=None):
        QtWidgets.QTextEdit.__init__(self, parent)
        logging.Handler.__init__(self)

        self.setLineWrapMode(QtWidgets.QTextEdit.NoWrap)
        self.setReadOnly(True)

        self.emit_lock = threading.Lock()

    def emit(self, record):
        with self.emit_lock:
            QtCore.QMetaObject.invokeMethod(self, 
                "append",  
                QtCore.Qt.QueuedConnection,
                QtCore.Q_ARG(str, self.format(record)))
            QtCore.QMetaObject.invokeMethod(self, 
                "autoScroll",
                QtCore.Qt.QueuedConnection)

    def format(self, record):
        if record.levelno == logging.INFO:
            bgcolor = WHITE
            fgcolor = BLACK
        elif record.levelno == logging.WARNING:
            bgcolor = YELLOW
            fgcolor = BLACK
        elif record.levelno == logging.ERROR:
            bgcolor = ORANGE
            fgcolor = BLACK
        elif record.levelno == logging.CRITICAL:
            bgcolor = RED
            fgcolor = BLACK
        else:
            bgcolor = BLACK
            fgcolor = WHITE

        self.setTextBackgroundColor(bgcolor)
        self.setTextColor(fgcolor)
        self.setFont(DEFAULT_FONT)
        record = logging.Handler.format(self, record)
        return record

    @QtCore.pyqtSlot()
    def autoScroll(self):
        self.verticalScrollBar().setSliderPosition(self.verticalScrollBar().maximum())