Marco Alibertis
Marco Alibertis

Reputation: 23

PyQt5 realtime logging in QTextedit from outside main module

took me long to post my first question here, since all the answers for all the questions I had in the past are already here. But I didn't find a solution yet for this problem, so here we go:

DESCRIPTION: In my application, I want to display realtime logging info in a QTextEdit widget. To get this to work, I implemented the solution I found here.

PROBLEM: The code in the link above works like a charm, as long as all the executed functions are part of the main Window class. But for my application I need this to work from outside the main module. I want to be able to get realtime log info (line per line) from external functions (for instance: when the 'someProcess' function is in another module, see code below). When I try to do that, everything works, but it does not display the log info line per line as it did before, but all lines at once after the external function is completed.

I assume that this is logical because in the external function there is no signal emitting from te logger object in the external module. But I can't wrap my head around an easy (or any) way to accomplish this.

main.py

import sys
import time
import logging
import othermodule as om
from PyQt5.QtCore import QObject, pyqtSignal, QThread
from PyQt5.QtWidgets import QWidget, QTextEdit, QPushButton, QVBoxLayout, QApplication

logger = logging.getLogger(__name__)


class ConsoleWindowLogHandler(logging.Handler, QObject):
    sigLog = pyqtSignal(str)
    def __init__(self):
        logging.Handler.__init__(self)
        QObject.__init__(self)

    def emit(self, logRecord):
        message = str(logRecord.getMessage())
        self.sigLog.emit(message)


class Window(QWidget):
    def __init__(self):
        super(Window, self).__init__()

        # Layout
        textBox = QTextEdit()
        textBox.setReadOnly(True)
        self.button = QPushButton('Click')
        vertLayout = QVBoxLayout()
        vertLayout.addWidget(textBox)
        vertLayout.addWidget(self.button)
        self.setLayout(vertLayout)

        # Connect button
        
        self.button.clicked.connect(self.buttonPressed)

        # Thread
        self.bee = Worker(self.test, ())
        self.bee.finished.connect(self.restoreUi)
        self.bee.terminate()

        # Console handler
        consoleHandler = ConsoleWindowLogHandler()
        consoleHandler.sigLog.connect(textBox.append)
        logger.addHandler(consoleHandler)

    def buttonPressed(self):
        self.button.setEnabled(False)
        self.bee.start()

    def restoreUi(self):
        self.button.setEnabled(True)

    def test(self):
        logger.error('Starting')
        om.someProcess()

class Worker(QThread):
    def __init__(self, func, args):
        super(Worker, self).__init__()
        self.func = func
        self.args = args

    def run(self):
        self.func(*self.args)


def main():
    app = QApplication(sys.argv)
    window = Window()
    window.show()
    sys.exit(app.exec_())


if __name__ == "__main__":
    main()

othermodule.py

import time
import logging

logger = logging.getLogger(__name__)

def someProcess(self):
    logger.error("starting")
    for i in range(10):
        logger.error("line%d" % i)  # Every iteration, I want to display this line per line 
                                    # in the textbox on the main window
        time.sleep(2)    # in my own code, here comes a time consuming task 

Upvotes: 2

Views: 1549

Answers (1)

eyllanesc
eyllanesc

Reputation: 244351

The error is caused because ConsoleWindowLogHandler only handles the information of the logger created in main.py but not the one created in othermodule.py, the solution is to assign that ConsoleWindowLogHandler is also the handler of the logger created in othermodule.py:

# Console handler
consoleHandler = ConsoleWindowLogHandler()
consoleHandler.sigLog.connect(textBox.append)
logger.addHandler(consoleHandler)
om.logger.addHandler(consoleHandler)

Note: Although it is trivial you should change def someProcess(self): to def someProcess():

Upvotes: 2

Related Questions