Best way to display logs in pyqt?

前端 未结 6 956
迷失自我
迷失自我 2020-12-01 01:42

I am currently working on a GUI using qt designer. I am wondering how I should go about printing strings on the GUI that acts like a logger window. I am using pyqt5.

相关标签:
6条回答
  • 2020-12-01 02:06

    Sounds like you'll want to use a QPlainTextEdit widget set to read-only.

    Consider changing the background color to gray to give the user a hint that it is not editable. It is also up to you if you want it to be scrollable or the text selectable.

    This answer can get you started subclassing QPlainTextEdit to scroll with output, save to a file, whatever.

    0 讨论(0)
  • 2020-12-01 02:07

    Thread-safe version

    class QTextEditLogger(logging.Handler, QtCore.QObject):
        appendPlainText = QtCore.pyqtSignal(str)
    
    def __init__(self, parent):
        super().__init__()
        QtCore.QObject.__init__(self)
        self.widget = QtWidgets.QPlainTextEdit(parent)
        self.widget.setReadOnly(True)
        self.appendPlainText.connect(self.widget.appendPlainText)
    
    def emit(self, record):
        msg = self.format(record)
        self.appendPlainText.emit(msg)
    

    Usage

        logTextBox = QTextEditLogger(self)
    
        # log to text box
        logTextBox.setFormatter(
            logging.Formatter(
                '%(asctime)s %(levelname)s %(module)s %(funcName)s %(message)s'))
        logging.getLogger().addHandler(logTextBox)
        logging.getLogger().setLevel(logging.DEBUG)
    
        # log to file
        fh = logging.FileHandler('my-log.log')
        fh.setLevel(logging.DEBUG)
        fh.setFormatter(
            logging.Formatter(
                '%(asctime)s %(levelname)s %(module)s %(funcName)s %(message)s'))
        logging.getLogger().addHandler(fh)
    
    0 讨论(0)
  • 2020-12-01 02:19

    Here's a complete working example based on mfitzp's answer:

    import sys
    from PyQt4 import QtCore, QtGui
    import logging
    
    # Uncomment below for terminal log messages
    # logging.basicConfig(level=logging.DEBUG, format=' %(asctime)s - %(name)s - %(levelname)s - %(message)s')    
    
    class QPlainTextEditLogger(logging.Handler):
        def __init__(self, parent):
            super().__init__()
            self.widget = QtGui.QPlainTextEdit(parent)
            self.widget.setReadOnly(True)    
    
        def emit(self, record):
            msg = self.format(record)
            self.widget.appendPlainText(msg)    
    
    
    class MyDialog(QtGui.QDialog, QPlainTextEditLogger):
        def __init__(self, parent=None):
            super().__init__(parent)    
    
            logTextBox = QPlainTextEditLogger(self)
            # You can format what is printed to text box
            logTextBox.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))
            logging.getLogger().addHandler(logTextBox)
            # You can control the logging level
            logging.getLogger().setLevel(logging.DEBUG)
    
            self._button = QtGui.QPushButton(self)
            self._button.setText('Test Me')    
    
            layout = QtGui.QVBoxLayout()
            # Add the new logging box widget to the layout
            layout.addWidget(logTextBox.widget)
            layout.addWidget(self._button)
            self.setLayout(layout)    
    
            # Connect signal to slot
            self._button.clicked.connect(self.test)    
    
        def test(self):
            logging.debug('damn, a bug')
            logging.info('something to remember')
            logging.warning('that\'s not right')
            logging.error('foobar')
    
    if (__name__ == '__main__'):
        app = None
        if (not QtGui.QApplication.instance()):
            app = QtGui.QApplication([])
        dlg = MyDialog()
        dlg.show()
        dlg.raise_()
        if (app):
            app.exec_()
    
    0 讨论(0)
  • 2020-12-01 02:21

    Adapted from Todd Vanyo's example for PyQt5:

    import sys
    from PyQt5 import QtWidgets
    import logging
    
    # Uncomment below for terminal log messages
    # logging.basicConfig(level=logging.DEBUG, format=' %(asctime)s - %(name)s - %(levelname)s - %(message)s')
    
    class QTextEditLogger(logging.Handler):
        def __init__(self, parent):
            super().__init__()
            self.widget = QtWidgets.QPlainTextEdit(parent)
            self.widget.setReadOnly(True)
    
        def emit(self, record):
            msg = self.format(record)
            self.widget.appendPlainText(msg)
    
    
    class MyDialog(QtWidgets.QDialog, QtWidgets.QPlainTextEdit):
        def __init__(self, parent=None):
            super().__init__(parent)
    
            logTextBox = QTextEditLogger(self)
            # You can format what is printed to text box
            logTextBox.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))
            logging.getLogger().addHandler(logTextBox)
            # You can control the logging level
            logging.getLogger().setLevel(logging.DEBUG)
    
            self._button = QtWidgets.QPushButton(self)
            self._button.setText('Test Me')
    
            layout = QtWidgets.QVBoxLayout()
            # Add the new logging box widget to the layout
            layout.addWidget(logTextBox.widget)
            layout.addWidget(self._button)
            self.setLayout(layout)
    
            # Connect signal to slot
            self._button.clicked.connect(self.test)
    
        def test(self):
            logging.debug('damn, a bug')
            logging.info('something to remember')
            logging.warning('that\'s not right')
            logging.error('foobar')
    
    app = QtWidgets.QApplication(sys.argv)
    dlg = MyDialog()
    dlg.show()
    dlg.raise_()
    sys.exit(app.exec_())
    
    0 讨论(0)
  • 2020-12-01 02:24

    Alex's answer should be ok in a single thread scenario, but if you are logging in another thread (QThread) you may get the following warning:

    QObject::connect: Cannot queue arguments of type 'QTextCursor'
    (Make sure 'QTextCursor' is registered using qRegisterMetaType().)
    

    This is because you are modifying the GUI (self.widget.appendPlainText(msg)) from a thread other than the main thread without using the Qt Signal/Slot mechanism.

    Here is my solution:

    # my_logger.py
    
    import logging
    from PyQt5.QtCore import pyqtSignal, QObject
    
    class Handler(QObject, logging.Handler):
        new_record = pyqtSignal(object)
    
        def __init__(self, parent):
            super().__init__(parent)
            super(logging.Handler).__init__()
            formatter = Formatter('%(asctime)s|%(levelname)s|%(message)s|', '%d/%m/%Y %H:%M:%S')
            self.setFormatter(formatter)
    
        def emit(self, record):
            msg = self.format(record)
            self.new_record.emit(msg) # <---- emit signal here
    
    class Formatter(logging.Formatter):
        def formatException(self, ei):
            result = super(Formatter, self).formatException(ei)
            return result
    
        def format(self, record):
            s = super(Formatter, self).format(record)
            if record.exc_text:
                s = s.replace('\n', '')
            return s
    
    
       # gui.py
    
       ... # GUI code
       ...
       def setup_logger(self)
            handler = Handler(self)
            log_text_box = QPlainTextEdit(self)
            self.main_layout.addWidget(log_text_box)
            logging.getLogger().addHandler(handler)
            logging.getLogger().setLevel(logging.INFO)
            handler.new_record.connect(log_text_box.appendPlainText) # <---- connect QPlainTextEdit.appendPlainText slot
       ...
    
    0 讨论(0)
  • 2020-12-01 02:32

    If you are using the Python logging module to can easily create a custom logging handler that passes the log messages through to a QPlainTextEdit instance (as described by Christopher).

    To do this you first subclass logging.Handler. In this __init__ we create the QPlainTextEdit that will contain the logs. The key bit here is that the handle will be receiving messages via the emit() function. So we overload this function and pass the message text into the QPlainTextEdit.

    import logging
    
    class QPlainTextEditLogger(logging.Handler):
        def __init__(self, parent):
            super(Logger, self).__init__()
    
            self.widget = QPlainTextEdit(parent)
            self.widget.setReadOnly(True)
    
        def emit(self, record):
            msg = self.format(record)
            self.widget.textCursor().appendPlainText(msg)
    
        def write(self, m):
            pass
    

    Create an object from this class, passing it the parent for the QPlainTextEdit (e.g. the main window, or a layout). You can then add this handler for the current logger.

    # Set up logging to use your widget as a handler
    log_handler = QPlainTextEditLogger(<parent widget>)
    logging.getLogger().addHandler(log_handler)
    
    0 讨论(0)
提交回复
热议问题