在pyqt中显示日志的最佳方法?

Car*_*elo 14 python pyqt qt-designer

我目前正在使用qt designer进行GUI.我想知道如何在GUI上打印字符串,就像记录器窗口一样.我正在使用pyqt5.

mfi*_*tzp 13

如果您使用Python logging模块可以轻松创建自定义日志记录处理程序,将日志消息传递给QPlainTextEdit实例(如Christopher所述).

要做到这一点,你首先要子类logging.Handler.在此__init__我们创建QPlainTextEdit将包含日志.这里的关键是句柄将通过该emit()函数接收消息.所以我们重载这个函数并将消息文本传递给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
Run Code Online (Sandbox Code Playgroud)

从此类创建一个对象,将其传递给父类QPlainTextEdit(例如主窗口或布局).然后,您可以为当前记录器添加此处理程序.

# Set up logging to use your widget as a handler
log_handler = QPlainTextEditLogger(<parent widget>)
logging.getLogger().addHandler(log_handler)
Run Code Online (Sandbox Code Playgroud)


小智 12

改编自Todd Vanyo的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_())
Run Code Online (Sandbox Code Playgroud)

  • 在其他情况下如何使用,我在 ui 文件中定义了带有 QMainWindow 的 *.ui,带有选项卡的 QTabWidget(名称 tab_log)。如何将 QTextEditLogger 添加到 QTabWidget 中的选项卡??? (2认同)
  • 这不是线程安全的。`appendPlainText` 应该连接到信号而不是调用它。 (2认同)

tob*_*ker 12

线程安全版本

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)
Run Code Online (Sandbox Code Playgroud)

用法

    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)
Run Code Online (Sandbox Code Playgroud)

  • 仅供未来读者参考。如果您在使用emit 方法时遇到问题,可能是因为logging.Handler 和QObject 之间的命名冲突。请参阅此答案以获取解决方案:[/sf/answers/4666527561/](/sf/answers/4666527561/) (3认同)

Tod*_*nyo 10

这是一个基于mfitzp答案的完整工作示例:

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_()
Run Code Online (Sandbox Code Playgroud)

  • 这可能是一个愚蠢的问题,但是从MyDialog中的QPlainTextEditLogger继承的目的是什么?我正在尝试将此示例转换为PyQt5,并且在不删除第二个继承的情况下无法使其正常工作。没有它似乎工作正常。 (2认同)

Chw*_*ega 5

Alex的答案在单线程场景中应该没问题,但是如果您登录另一个线程(QThread),您可能会收到以下警告:

QObject::connect: Cannot queue arguments of type 'QTextCursor'
(Make sure 'QTextCursor' is registered using qRegisterMetaType().)
Run Code Online (Sandbox Code Playgroud)

self.widget.appendPlainText(msg)这是因为您正在从主线程以外的线程修改 GUI ( ),而没有使用 Qt Signal/Slot 机制。

这是我的解决方案:

QObject::connect: Cannot queue arguments of type 'QTextCursor'
(Make sure 'QTextCursor' is registered using qRegisterMetaType().)
Run Code Online (Sandbox Code Playgroud)
# 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

Run Code Online (Sandbox Code Playgroud)


Chr*_*son 1

听起来您想要使用设置为只读的QPlainTextEdit小部件。

考虑将背景颜色更改为灰色,以提示用户它不可编辑。如果您希望它可滚动或文本可选择,这也取决于您。

这个答案可以让您开始子类化 QPlainTextEdit 以滚动输出、保存到文件等。