Conflicting names between logging emit function and QT emit signal

Question:

I am trying to send a Qt signal every time the logging handler emit function is called. But I think MyLogHandler.emit and log.emit functions are conflicting.

from PySide2.QtCore import QObject, Signal
import logging

class MyLogHandler(logging.Handler, QObject):
    log = Signal(str)

    def emit(self, record):
        self.log.emit('send')

if __name__ == "__main__":
    logging.getLogger().addHandler(MyLogHandler())
    logging.warning('logging test')

Error:

TypeError: emit() takes 2 positional arguments but 3 were given

UPDATE:

I have tried to use composition (per @eyllanesc), but I still can’t connect the signal to the QML file. I am not sure why I can’t receive the signal in QML. It doesn’t seem like it is emitting anything. What am I doing wrong?

from functools import cached_property
import logging
import sys

from PySide2.QtGui import QGuiApplication
from PySide2.QtQml import QQmlApplicationEngine
from PySide2.QtCore import QCoreApplication, QObject, QTimer, Signal, QUrl

class Main(QObject):
    log = Signal(str)


class Log(object):
    def __init__(self):
        logging.basicConfig(
            level=logging.DEBUG,
            format="%(asctime)s [%(levelname)s] %(message)s",
            handlers=[
                logging.FileHandler("debug.log", mode='w'),
                logging.StreamHandler(),
                MyLogHandler()
            ]
        )

class MyLogHandler(logging.Handler):
    @cached_property
    def main(self):
        return Main()

    def emit(self, record):
        msg = self.format(record)
        self.main.log.emit(msg)


if __name__ == "__main__":
    app = QGuiApplication(sys.argv)
    engine = QQmlApplicationEngine()

    main = Main()

    Log()

    QTimer.singleShot(1000, lambda: logging.warning("logging test"))
    engine.rootContext().setContextProperty("main", main)
    engine.load(QUrl("Main3.qml"))
    app.exec_()

QML: Main3.qml

import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15

Window {
    width: 500
    height: 500
    visible: true
    title: qsTr("MIST")

    Text {
        id: text
        anchors.fill: parent

    }

    Connections {
        target: main

        function onLog(msg) {
            text.text = msg
        }

    }
}
Asked By: Aaron

||

Answers:

The problem is that both base classes have an emit() method that causes that collision. A workaround is not to use inheritance but composition:

from functools import cached_property
import logging

from PySide2.QtCore import QCoreApplication, QObject, QTimer, Signal


class Bridge(QObject):
    log = Signal(str)


class MyLogHandler(logging.Handler):
    @cached_property
    def bridge(self):
        return Bridge()

    def emit(self, record):
        msg = self.format(record)
        self.bridge.log.emit(msg)


if __name__ == "__main__":
    app = QCoreApplication()

    handler = MyLogHandler()
    handler.bridge.log.connect(print)

    logging.getLogger().addHandler(handler)

    QTimer.singleShot(1000, lambda: logging.warning("logging test"))
    QTimer.singleShot(2000, QCoreApplication.quit)

    app.exec_()

Update:

import logging
import sys

from PySide2.QtCore import QObject, QTimer, QUrl, Signal
from PySide2.QtGui import QGuiApplication
from PySide2.QtQml import QQmlApplicationEngine


class Main(QObject):
    log = Signal(str)


class QLogHandler(logging.Handler):
    def __init__(self, emitter):
        super().__init__()
        self._emitter = emitter

    @property
    def emitter(self):
        return self._emitter

    def emit(self, record):
        msg = self.format(record)
        self.emitter.log.emit(msg)


def configure_logging(*, handlers):
    logging.basicConfig(
        level=logging.DEBUG,
        format="%(asctime)s [%(levelname)s] %(message)s",
        handlers=handlers,
    )


def main():
    app = QGuiApplication()

    main = Main()

    configure_logging(
        handlers=[
            logging.FileHandler("debug.log", mode="w"),
            logging.StreamHandler(),
            QLogHandler(main),
        ]
    )

    engine = QQmlApplicationEngine()
    engine.rootContext().setContextProperty("main", main)
    engine.load(QUrl("Main3.qml"))

    QTimer.singleShot(1000, lambda: logging.warning("logging test"))

    ret = app.exec_()
    sys.exit(ret)


if __name__ == "__main__":
    main()
Answered By: eyllanesc

Adding the solution that I ended up using since I found this whole thing difficult to parse and apply.

import logging
from PySide6.QtWidgets import QPlainTextEdit
import PySide6.QtCore as QtCore

# Logging handler which receives any log created using the logging module.
# Contains a QPlainTextEdit which is automatically updated with logs.
class QTextEditLogger(logging.Handler):
    class Emitter(QtCore.QObject):
        log = QtCore.Signal(str)

    def __init__(self, parent):
        super().__init__()

        # create text edit widget
        self.widget = QPlainTextEdit(parent)
        self.widget.setReadOnly(True)

        # Create a QObject which will emit a signal for each log. This implicitly queues each 
        # appendPlainText() call which makes it thread-safe
        self.emitter = QTextEditLogger.Emitter()
        self.emitter.log.connect(self.widget.appendPlainText)

    # override Handler's emit method (this happens to share a name with Qt's emit method.
    # Don't get confused)
    def emit(self, record):
        msg = self.format(record)
        self.emitter.log.emit(msg) # emit a signal containing the log (emit in the Qt sense)
Answered By: PartyBuddha
Categories: questions Tags: , , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.