Description: I have written a custom log handler for capturing log events and writing them to a QTextBrowser object (working sample code shown below).
Issue: Pressing the button invokes someProcess()
. This writes two strings to the logger
object. However, the strings only appear after someProcess()
returns.
Question: How do I get the logged strings to appear in the QTextBrowser object immediately/in real-time? (i.e. as soon as a logger
output method is invoked)
from PyQt4 import QtCore, QtGui
import sys
import time
import logging
logger = logging.getLogger(__name__)
class ConsoleWindowLogHandler(logging.Handler):
def __init__(self, textBox):
super(ConsoleWindowLogHandler, self).__init__()
self.textBox = textBox
def emit(self, logRecord):
self.textBox.append(str(logRecord.getMessage()))
def someProcess():
logger.error("line1")
time.sleep(5)
logger.error("line2")
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
window = QtGui.QWidget()
textBox = QtGui.QTextBrowser()
button = QtGui.QPushButton()
button.clicked.connect(someProcess)
vertLayout = QtGui.QVBoxLayout()
vertLayout.addWidget(textBox)
vertLayout.addWidget(button)
window.setLayout(vertLayout)
window.show()
consoleHandler = ConsoleWindowLogHandler(textBox)
logger.addHandler(consoleHandler)
sys.exit(app.exec_())
EDIT: thanks to the answer by @abarnert, I managed to write this piece of working code using QThread. I subclassed QThread
in order to run some function someProcess
in a background thread. For the signalling, I had to resort to old-style Signal and Slots (I'm not sure how to do it in the new-style). I created a dummy QObject in order to be able to emit signals from the logging handler.
from PyQt4 import QtCore, QtGui
import sys
import time
import logging
logger = logging.getLogger(__name__)
#------------------------------------------------------------------------------
class ConsoleWindowLogHandler(logging.Handler):
def __init__(self, sigEmitter):
super(ConsoleWindowLogHandler, self).__init__()
self.sigEmitter = sigEmitter
def emit(self, logRecord):
message = str(logRecord.getMessage())
self.sigEmitter.emit(QtCore.SIGNAL("logMsg(QString)"), message)
#------------------------------------------------------------------------------
class Window(QtGui.QWidget):
def __init__(self):
super(Window, self).__init__()
# Layout
textBox = QtGui.QTextBrowser()
self.button = QtGui.QPushButton()
vertLayout = QtGui.QVBoxLayout()
vertLayout.addWidget(textBox)
vertLayout.addWidget(self.button)
self.setLayout(vertLayout)
# Connect button
self.button.clicked.connect(self.buttonPressed)
# Thread
self.bee = Worker(self.someProcess, ())
self.bee.finished.connect(self.restoreUi)
self.bee.terminated.connect(self.restoreUi)
# Console handler
dummyEmitter = QtCore.QObject()
self.connect(dummyEmitter, QtCore.SIGNAL("logMsg(QString)"),
textBox.append)
consoleHandler = ConsoleWindowLogHandler(dummyEmitter)
logger.addHandler(consoleHandler)
def buttonPressed(self):
self.button.setEnabled(False)
self.bee.start()
def someProcess(self):
logger.error("starting")
for i in xrange(10):
logger.error("line%d" % i)
time.sleep(2)
def restoreUi(self):
self.button.setEnabled(True)
#------------------------------------------------------------------------------
class Worker(QtCore.QThread):
def __init__(self, func, args):
super(Worker, self).__init__()
self.func = func
self.args = args
def run(self):
self.func(*self.args)
#------------------------------------------------------------------------------
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
Build on top of @Gilead's code and @Cecil's suggestions, I update the code by changing the old-style to new-style signal/slot and changing the QTextBrowser
to QTextEdit
.
import sys
import time
import logging
from qtpy.QtCore import QObject, Signal, QThread
from qtpy.QtWidgets import QWidget, QTextEdit, QPushButton, QVBoxLayout
logger = logging.getLogger(__name__)
class ConsoleWindowLogHandler(logging.Handler, QObject):
sigLog = Signal(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.someProcess) # blocking
self.button.clicked.connect(self.buttonPressed)
# Thread
self.bee = Worker(self.someProcess, ())
self.bee.finished.connect(self.restoreUi)
self.bee.terminated.connect(self.restoreUi)
# Console handler
consoleHandler = ConsoleWindowLogHandler()
consoleHandler.sigLog.connect(textBox.append)
logger.addHandler(consoleHandler)
def buttonPressed(self):
self.button.setEnabled(False)
self.bee.start()
def someProcess(self):
logger.error("starting")
for i in range(10):
logger.error("line%d" % i)
time.sleep(2)
def restoreUi(self):
self.button.setEnabled(True)
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():
from qtpy.QtWidgets import QApplication
app = QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
Translating JoeXinfa's answer to PyQt5:
import sys
import time
import logging
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.someProcess) # blocking
self.button.clicked.connect(self.buttonPressed)
# Thread
self.bee = Worker(self.someProcess, ())
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 someProcess(self):
logger.error("starting")
for i in range(10):
logger.error("line%d" % i)
time.sleep(2)
def restoreUi(self):
self.button.setEnabled(True)
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()
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With