Skip to content

Commit 472eced

Browse files
authored
Refined Qt GUI example in the logging cookbook. (GH-15045)
1 parent d04f890 commit 472eced

File tree

1 file changed

+39
-17
lines changed

1 file changed

+39
-17
lines changed

Doc/howto/logging-cookbook.rst

Lines changed: 39 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2760,16 +2760,16 @@ The following example shows how to log to a Qt GUI. This introduces a simple
27602760
``QtHandler`` class which takes a callable, which should be a slot in the main
27612761
thread that does GUI updates. A worker thread is also created to show how you
27622762
can log to the GUI from both the UI itself (via a button for manual logging)
2763-
as well as a worker thread doing work in the background (here, just random
2764-
short delays).
2763+
as well as a worker thread doing work in the background (here, just logging
2764+
messages at random levels with random short delays in between).
27652765

27662766
The worker thread is implemented using Qt's ``QThread`` class rather than the
27672767
:mod:`threading` module, as there are circumstances where one has to use
27682768
``QThread``, which offers better integration with other ``Qt`` components.
27692769

27702770
The code should work with recent releases of either ``PySide2`` or ``PyQt5``.
27712771
You should be able to adapt the approach to earlier versions of Qt. Please
2772-
refer to the comments in the code for more detailed information.
2772+
refer to the comments in the code snippet for more detailed information.
27732773

27742774
.. code-block:: python3
27752775
@@ -2789,22 +2789,24 @@ refer to the comments in the code for more detailed information.
27892789
Signal = QtCore.pyqtSignal
27902790
Slot = QtCore.pyqtSlot
27912791
2792+
27922793
logger = logging.getLogger(__name__)
27932794
2795+
27942796
#
27952797
# Signals need to be contained in a QObject or subclass in order to be correctly
27962798
# initialized.
27972799
#
27982800
class Signaller(QtCore.QObject):
2799-
signal = Signal(str)
2801+
signal = Signal(str, logging.LogRecord)
28002802
28012803
#
28022804
# Output to a Qt GUI is only supposed to happen on the main thread. So, this
28032805
# handler is designed to take a slot function which is set up to run in the main
2804-
# thread. In this example, the function takes a single argument which is a
2805-
# formatted log message. You can attach a formatter instance which formats a
2806-
# LogRecord however you like, or change the slot function to take some other
2807-
# value derived from the LogRecord.
2806+
# thread. In this example, the function takes a string argument which is a
2807+
# formatted log message, and the log record which generated it. The formatted
2808+
# string is just a convenience - you could format a string for output any way
2809+
# you like in the slot function itself.
28082810
#
28092811
# You specify the slot function to do whatever GUI updates you want. The handler
28102812
# doesn't know or care about specific UI elements.
@@ -2817,7 +2819,7 @@ refer to the comments in the code for more detailed information.
28172819
28182820
def emit(self, record):
28192821
s = self.format(record)
2820-
self.signaller.signal.emit(s)
2822+
self.signaller.signal.emit(s, record)
28212823
28222824
#
28232825
# This example uses QThreads, which means that the threads at the Python level
@@ -2827,6 +2829,13 @@ refer to the comments in the code for more detailed information.
28272829
def ctname():
28282830
return QtCore.QThread.currentThread().objectName()
28292831
2832+
2833+
#
2834+
# Used to generate random levels for logging.
2835+
#
2836+
LEVELS = (logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR,
2837+
logging.CRITICAL)
2838+
28302839
#
28312840
# This worker class represents work that is done in a thread separate to the
28322841
# main thread. The way the thread is kicked off to do work is via a button press
@@ -2851,7 +2860,8 @@ refer to the comments in the code for more detailed information.
28512860
while not QtCore.QThread.currentThread().isInterruptionRequested():
28522861
delay = 0.5 + random.random() * 2
28532862
time.sleep(delay)
2854-
logger.debug('Message after delay of %3.1f: %d', delay, i, extra=extra)
2863+
level = random.choice(LEVELS)
2864+
logger.log(level, 'Message after delay of %3.1f: %d', delay, i, extra=extra)
28552865
i += 1
28562866
28572867
#
@@ -2864,10 +2874,18 @@ refer to the comments in the code for more detailed information.
28642874
#
28652875
class Window(QtWidgets.QWidget):
28662876
2877+
COLORS = {
2878+
logging.DEBUG: 'black',
2879+
logging.INFO: 'blue',
2880+
logging.WARNING: 'orange',
2881+
logging.ERROR: 'red',
2882+
logging.CRITICAL: 'purple',
2883+
}
2884+
28672885
def __init__(self, app):
28682886
super(Window, self).__init__()
28692887
self.app = app
2870-
self.textedit = te = QtWidgets.QTextEdit(self)
2888+
self.textedit = te = QtWidgets.QPlainTextEdit(self)
28712889
# Set whatever the default monospace font is for the platform
28722890
f = QtGui.QFont('nosuchfont')
28732891
f.setStyleHint(f.Monospace)
@@ -2880,7 +2898,7 @@ refer to the comments in the code for more detailed information.
28802898
self.handler = h = QtHandler(self.update_status)
28812899
# Remember to use qThreadName rather than threadName in the format string.
28822900
fs = '%(asctime)s %(qThreadName)-12s %(levelname)-8s %(message)s'
2883-
formatter = logging.Formatter(f)
2901+
formatter = logging.Formatter(fs)
28842902
h.setFormatter(formatter)
28852903
logger.addHandler(h)
28862904
# Set up to terminate the QThread when we exit
@@ -2932,21 +2950,25 @@ refer to the comments in the code for more detailed information.
29322950
# that's where the slots are set up
29332951
29342952
@Slot(str)
2935-
def update_status(self, status):
2936-
self.textedit.append(status)
2953+
def update_status(self, status, record):
2954+
color = self.COLORS.get(record.levelno, 'black')
2955+
s = '<pre><font color="%s">%s</font></pre>' % (color, status)
2956+
self.textedit.appendHtml(s)
29372957
29382958
@Slot()
29392959
def manual_update(self):
2940-
levels = (logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR,
2941-
logging.CRITICAL)
2942-
level = random.choice(levels)
2960+
# This function uses the formatted message passed in, but also uses
2961+
# information from the record to format the message in an appropriate
2962+
# color according to its severity (level).
2963+
level = random.choice(LEVELS)
29432964
extra = {'qThreadName': ctname() }
29442965
logger.log(level, 'Manually logged!', extra=extra)
29452966
29462967
@Slot()
29472968
def clear_display(self):
29482969
self.textedit.clear()
29492970
2971+
29502972
def main():
29512973
QtCore.QThread.currentThread().setObjectName('MainThread')
29522974
logging.getLogger().setLevel(logging.DEBUG)

0 commit comments

Comments
 (0)