From 9b9cac4e5de975fe2417d8231822bc1d2ca2d273 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Tue, 30 Jul 2019 23:58:46 -0700 Subject: [PATCH] Refined Qt GUI example in the logging cookbook. (GH-15045) (GH-15046) (cherry picked from commit 472eced6770c2fabab3031e4e16cd32e32b8a0cc) --- Doc/howto/logging-cookbook.rst | 56 +++++++++++++++++++++++----------- 1 file changed, 39 insertions(+), 17 deletions(-) diff --git a/Doc/howto/logging-cookbook.rst b/Doc/howto/logging-cookbook.rst index ef9ad447f80..9f52780af4f 100644 --- a/Doc/howto/logging-cookbook.rst +++ b/Doc/howto/logging-cookbook.rst @@ -2760,8 +2760,8 @@ The following example shows how to log to a Qt GUI. This introduces a simple ``QtHandler`` class which takes a callable, which should be a slot in the main thread that does GUI updates. A worker thread is also created to show how you can log to the GUI from both the UI itself (via a button for manual logging) -as well as a worker thread doing work in the background (here, just random -short delays). +as well as a worker thread doing work in the background (here, just logging +messages at random levels with random short delays in between). The worker thread is implemented using Qt's ``QThread`` class rather than the :mod:`threading` module, as there are circumstances where one has to use @@ -2769,7 +2769,7 @@ The worker thread is implemented using Qt's ``QThread`` class rather than the The code should work with recent releases of either ``PySide2`` or ``PyQt5``. You should be able to adapt the approach to earlier versions of Qt. Please -refer to the comments in the code for more detailed information. +refer to the comments in the code snippet for more detailed information. .. code-block:: python3 @@ -2789,22 +2789,24 @@ refer to the comments in the code for more detailed information. Signal = QtCore.pyqtSignal Slot = QtCore.pyqtSlot + logger = logging.getLogger(__name__) + # # Signals need to be contained in a QObject or subclass in order to be correctly # initialized. # class Signaller(QtCore.QObject): - signal = Signal(str) + signal = Signal(str, logging.LogRecord) # # Output to a Qt GUI is only supposed to happen on the main thread. So, this # handler is designed to take a slot function which is set up to run in the main - # thread. In this example, the function takes a single argument which is a - # formatted log message. You can attach a formatter instance which formats a - # LogRecord however you like, or change the slot function to take some other - # value derived from the LogRecord. + # thread. In this example, the function takes a string argument which is a + # formatted log message, and the log record which generated it. The formatted + # string is just a convenience - you could format a string for output any way + # you like in the slot function itself. # # You specify the slot function to do whatever GUI updates you want. The handler # doesn't know or care about specific UI elements. @@ -2817,7 +2819,7 @@ refer to the comments in the code for more detailed information. def emit(self, record): s = self.format(record) - self.signaller.signal.emit(s) + self.signaller.signal.emit(s, record) # # 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. def ctname(): return QtCore.QThread.currentThread().objectName() + + # + # Used to generate random levels for logging. + # + LEVELS = (logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR, + logging.CRITICAL) + # # This worker class represents work that is done in a thread separate to the # 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. while not QtCore.QThread.currentThread().isInterruptionRequested(): delay = 0.5 + random.random() * 2 time.sleep(delay) - logger.debug('Message after delay of %3.1f: %d', delay, i, extra=extra) + level = random.choice(LEVELS) + logger.log(level, 'Message after delay of %3.1f: %d', delay, i, extra=extra) i += 1 # @@ -2864,10 +2874,18 @@ refer to the comments in the code for more detailed information. # class Window(QtWidgets.QWidget): + COLORS = { + logging.DEBUG: 'black', + logging.INFO: 'blue', + logging.WARNING: 'orange', + logging.ERROR: 'red', + logging.CRITICAL: 'purple', + } + def __init__(self, app): super(Window, self).__init__() self.app = app - self.textedit = te = QtWidgets.QTextEdit(self) + self.textedit = te = QtWidgets.QPlainTextEdit(self) # Set whatever the default monospace font is for the platform f = QtGui.QFont('nosuchfont') f.setStyleHint(f.Monospace) @@ -2880,7 +2898,7 @@ refer to the comments in the code for more detailed information. self.handler = h = QtHandler(self.update_status) # Remember to use qThreadName rather than threadName in the format string. fs = '%(asctime)s %(qThreadName)-12s %(levelname)-8s %(message)s' - formatter = logging.Formatter(f) + formatter = logging.Formatter(fs) h.setFormatter(formatter) logger.addHandler(h) # Set up to terminate the QThread when we exit @@ -2932,14 +2950,17 @@ refer to the comments in the code for more detailed information. # that's where the slots are set up @Slot(str) - def update_status(self, status): - self.textedit.append(status) + def update_status(self, status, record): + color = self.COLORS.get(record.levelno, 'black') + s = '
%s
' % (color, status) + self.textedit.appendHtml(s) @Slot() def manual_update(self): - levels = (logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR, - logging.CRITICAL) - level = random.choice(levels) + # This function uses the formatted message passed in, but also uses + # information from the record to format the message in an appropriate + # color according to its severity (level). + level = random.choice(LEVELS) extra = {'qThreadName': ctname() } logger.log(level, 'Manually logged!', extra=extra) @@ -2947,6 +2968,7 @@ refer to the comments in the code for more detailed information. def clear_display(self): self.textedit.clear() + def main(): QtCore.QThread.currentThread().setObjectName('MainThread') logging.getLogger().setLevel(logging.DEBUG)