I have a PySide6 application that uses threading and logging. My logger has a custom handler defined below, which pops up a messagebox that requires user attention for higher level logging events:
import logging from PySide6.QtWidgets import QMessageBox
class QtHandler(logging.Handler):
def emit(self, record):
if record.levelno >= logging.ERROR:
QMessageBox.critical(None, "Error", self.format(record))
elif record.levelno >= logging.WARNING:
QMessageBox.warning(None, "Warning", self.format(record))
This works fine for single-threaded operations, but fails when a thread needs to log something that would trigger the messagebox, since that occurs in the main loop and it is not safe to trigger main loop events from threads.
My first attempt to fix this was to try to make the popup box appear only through emission of a Signal
mediated by Qt.QueuedConnection
, like so:
import logging
from PySide6.QtCore import QObject, Signal, Slot, Qt
from PySide6.QtWidgets import QMessageBox
class QtHandler(logging.Handler, QObject):
queue_messagebox = Signal(object)
def __init__(self, parent=None):
# Initialize both QObject and logging.Handler
QObject.__init__(self, parent)
logging.Handler.__init__(self)
# Connect the signal to the slot using a queued connection to ensure thread safety
self.queue_messagebox.connect(self.show_message_box, Qt.QueuedConnection)
def emit(self, record):
# This is where the logging record is processed
self.queue_messagebox.emit(record) # Emit the signal with the log record
@Slot(object)
def show_message_box(self, record):
# Create the message box based on the log level
if record.levelno >= logging.ERROR:
QMessageBox.critical(None, "Error", self.format(record))
elif record.levelno >= logging.WARNING:
QMessageBox.warning(None, "Warning", self.format(record))
But this fails with the statement that my emit() function is getting too many arguments. My assumption is that this is due to the fact that both logging.Handler and QObject have methods called emit and I am overriding one of them, when I need both of them to work.
How can I address this conflict, or is there another way I could go about making this logging handler threadsafe?
For posterity, this worked in the end, thank you @mahkitah for the suggested framework:
import logging
from PySide6.QtCore import QObject, Signal, Slot, Qt
from PySide6.QtWidgets import QMessageBox
#QObject designed solely to emit signals
class MessageBoxEmitter(QObject):
emit_message = Signal(object)
class QtHandler(logging.Handler):
def __init__(self, parent=None):
super().__init__()
# Create an instance of the internal QObject to handle signal emissions
self.emitter = MessageBoxEmitter()
# Connect the internal signal to the message box displaying slot - queued connection to ensure thread safety
self.emitter.emit_message.connect(self.show_message_box, Qt.QueuedConnection)
def emit(self, record):
# Emit the signal with the log record
self.emitter.emit_message.emit(record)
@Slot(object)
def show_message_box(self, record):
# Create the message box based on the log level
if record.levelno >= logging.ERROR:
QMessageBox.critical(None, "Error", self.format(record))
elif record.levelno >= logging.WARNING:
QMessageBox.warning(None, "Warning", self.format(record))