In order to show logging messages in a PyQt GUI, I'm using a custom logging handler that sends the logRecord as a pyqtSignal
.
This handler inherits from both QObject
and logging.Handler
.
This works as it should but on shutdown there's this error:
File "C:\Program Files\Python313\Lib\logging\__init__.py", line 2242, in shutdown if getattr(h, 'flushOnClose', True): RuntimeError: wrapped C/C++ object of type Log2Qt has been deleted
My interpretation is that logging tries to close the handler but because the handler is also a QObject, Qt has already deleted it.
But when you connect the aboutToQuit
signal to a function that removes the handler from the logger, the error still occurs.
Here's a MRE:
import logging
from PyQt6.QtWidgets import QApplication, QWidget
from PyQt6.QtCore import QObject, pyqtSignal
class Log2Qt(QObject, logging.Handler):
log_forward = pyqtSignal(logging.LogRecord)
def emit(self, record):
self.log_forward.emit(record)
logger = logging.getLogger(__name__)
handler = Log2Qt()
logger.addHandler(handler)
def closing():
# handler.close()
logger.removeHandler(handler)
print(logger.handlers)
app = QApplication([])
app.aboutToQuit.connect(closing)
win = QWidget()
win.show()
app.exec()
The print from closing()
shows that logger has no more handlers, so why does logging still try to close the handler when it's already removed?
And how could you prevent the error from occuring?
This may be caused by the multiple inheritance.
I coincidentally just learned that PyQt6 works differently than PyQt5, affecting the way attributes that exist on the Qt side may be accessed.
Since by default the flushOnClose
attribute does not exist (it's only created for some subclasses, such as MemoryHandler), getattr()
causes the attempt to query the inherited attributes, but that is a problem for a destroyed QObject.
This does not happen with PyQt5, probably due to the different approach written above.
One possibility could be to delete the handler, which will properly destroy the PyQt reference at the correct time, preventing accessing done later in the code:
def closing():
global handler
logger.removeHandler(handler)
del handler
This is not very elegant, though, and you also need to be completely sure that no other reference exists anywhere. Besides, it doesn't really address the issue, it only works around it.
A more appropriate solution, considering the aspect of the attribute, is to explicitly create it:
class Log2Qt(QObject, logging.Handler):
log_forward = pyqtSignal(logging.LogRecord)
flushOnClose = True
def emit(self, record):
self.log_forward.emit(record)
The value is set as True
, which is assumed as default, as shown from the snippet in the traceback.
Another solution is to completely avoid the inheritance by creating a simple QObject subclass with the signal, and create an instance of it in the __init__
of the handler, used later in the emit()
call:
class LogSignaler(QObject):
log_forward = pyqtSignal(logging.LogRecord)
class Log2Qt(logging.Handler):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.signaler = LogSignaler()
def emit(self, record):
self.signaler.log_forward.emit(record)
The benefit of this approach is that it also works fine with PySide, which, as opposed to PyQt, exposes the emit()
function of QObject causing conflicts; see this related post).
The two solutions above make it unnecessary to connect to the aboutToQuit
signal.