PyQt6 优化操作:将运行日志以及程序崩溃时的报错信息显示到界面控件

1. 总体目标

1)将 log 打印和 print 打印的内容输出到界面控件,不同 log 级别对应不同颜色;

如果不需要输出print 内容,注释掉第73、74行(如下示例),以及 onUpdateText 函数即可。

# sys.stdout = Stream()
# sys.stdout.newText.connect(self.onUpdateText)

2)log内容输出到本地文件

3)程序遇到异常时不会立刻退出,弹窗显示报错内容,并提供选项退出程序或继续运行

2. 知识点

1.1 Python logging 模块

Handler处理器:在 Python 的 logging 模块中,Handler 是用来处理日志记录的对象。它负责将日志记录发送到指定的目标,例如文件、终端或者网络。Handler 可以被添加到 Logger 对象中,以便将记录传递给它们。

如需更多信息,请查阅参考链接1,此文对 logging 模块进行了详细的介绍。

1.2 Qt 消息处理

qInstallMessageHandler:qInstallMessageHandler 是 PyQt 中的一个函数,用于管理 Qt 应用程序中的消息。使用时,需要设置Qt消息处理程序。一旦设置,所有 Qt 消息都将由该处理程序处理。通过这个函数,可将 Qt 框架中的所有消息(如警告、错误、调试信息等)重定向到自定义的消息处理函数中,例如将消息输出到控制台、写入日志文件等。

如需更多信息,请查阅参考链接2,此文内容清晰,给博主点赞,PyQt6需稍作改动,参考本文。

1.3 sys.excepthook

当程序中出现未处理的异常时,Python 解释器会调用 sys.excepthook 函数来处理异常。默认情况下,sys.excepthook 函数会将异常信息输出到标准错误流 (sys.stderr) 中,然后退出程序。但是,我们可以通过设置 sys.excepthook 函数来定制自己的异常处理方式,例如记录日志、弹窗提示等。

当Python 程序遇到错误时,它会生成一个 traceback,其中包含了错误发生的位置、错误类型和错误消息等信息。Traceback 通常是在 Python 解释器中显示的,它可以帮助程序员快速定位错误,并且可以提供有用的调试信息。Traceback 可以通过使用 Python 内置的 traceback 模块来捕获和处理。

3. 代码分享

# -*- coding: utf-8 -*-、
import logging
import inspect
import traceback
import os
import sys
from datetime import datetime
from random import randint
from PyQt6.QtCore import pyqtSignal, QObject, QtMsgType, qInstallMessageHandler
from PyQt6.QtWidgets import QDialog, QApplication, QTextEdit, QPushButton, QMessageBox


class Stream(QObject):
    """
    重定向控制台输出到文本框控件
    """
    newText = pyqtSignal(str)

    # 任何定义了类似于文件write方法的对象可以指定给sys.stdout,
    # 所有的标准输出将发送到该方法对象上
    def write(self, text):
        self.newText.emit(str(text))
        QApplication.processEvents()


class QTextEditHandler(logging.Handler):
    def __init__(self, parent):
        super().__init__()
        self.text_edit = parent
        self.formatter = logging.Formatter('%(asctime)s - [%(filename)s][line:%(lineno)d][%(levelname)s]  %(message)s')

    def emit(self, record):
        msg = self.format(record)
        self.text_edit.append(msg)
        self.text_edit.ensureCursorVisible()

    def format(self, record):
        if record.levelno == logging.DEBUG:
            color = 'gray'
        elif record.levelno == logging.INFO:
            color = 'black'
        elif record.levelno == logging.WARNING:
            color = 'orange'
        elif record.levelno == logging.ERROR:
            color = 'darkRed'
        elif record.levelno == logging.CRITICAL or record.levelno == logging.FATAL:
            color = 'red'
        else:
            color = 'blue'
        new_msg = self.formatter.format(record)
        msg = '<span style="color:{}">{}</span>'.format(color, new_msg)
        return msg


class Ui_Form(QDialog):
    """
    主界面
    """

    def __init__(self):
        super().__init__()
        self.resize(1000, 500)
        self.textEdit = QTextEdit(self)
        self.textEdit.resize(900, 200)
        self.QPushButton1 = QPushButton('点我运行正常程序', self)
        self.QPushButton1.clicked.connect(self.normal_func)
        self.QPushButton1.move(10, 250)
        self.QPushButton2 = QPushButton('点我运行异常程序', self)
        self.QPushButton2.clicked.connect(self.error_func)
        self.QPushButton2.move(10, 280)

        # 自定义输出流
        sys.stdout = Stream()
        sys.stdout.newText.connect(self.onUpdateText)

        # Log打印
        self.logger = logging.getLogger(__name__)
        self.logger.setLevel(logging.DEBUG)
        formatter = logging.Formatter('%(asctime)s - [%(filename)s][line:%(lineno)d][%(levelname)s]  %(message)s')
        # QTextEdit-Handler
        handler = QTextEditHandler(self.textEdit)
        handler.setFormatter(formatter)
        self.logger.addHandler(handler)
        # File-Handler
        log_file_name = os.path.join(os.path.dirname(__file__), 'Log_Folder', 'log_{time}_{randomn}'.format(
            time=datetime.now().strftime('%Y_%m%d_%H%M%S'), randomn=randint(0, 9)))
        os.makedirs(os.path.dirname(log_file_name), exist_ok=True)
        file_handler = logging.FileHandler(log_file_name)
        file_handler.setFormatter(formatter)
        self.logger.addHandler(file_handler)

        # QT 消息处理
        qInstallMessageHandler(self.redirection_msg)

    def closeEvent(self, event):
        """
        重写closeEvent, 程序结束时将stdout恢复默认
        """
        reply = QMessageBox.question(
            self, '提示', '确定要退出吗?',
            QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, QMessageBox.StandardButton.No)
        if reply == QMessageBox.StandardButton.Yes:
            sys.stdout = sys.__stdout__
            event.accept()
            res = super().closeEvent(event)
        else:
            event.ignore()
            res = None
        return res

    def onUpdateText(self, text):
        """
        重定向控制台输出到文本框控件
        """
        if text == '\n':
            # print函数默认会在输出内容后自动添加一个换行符\n
            # 后续脚本能够保证,每次显示print内容时,都是从一个新行的行首开始,不再需要这个换行符,跳过即可
            return
        cursor = self.textEdit.textCursor()

        if cursor.position() == cursor.block().position():
            # 如果当前光标位置是行首, 直接打印文字内容
            cursor.insertText(text)
        else:
            # 如果当前光标位置是行中, 换行,从下一行行首开始打印文字内容
            cursor.movePosition(cursor.MoveOperation.End)
            cursor.insertText('\n' + text)
        self.textEdit.setTextCursor(cursor)
        self.textEdit.ensureCursorVisible()
        return

    def error_func(self):
        """
        执行后会报错的程序
        """
        self.logger.info('运行存在异常的程序脚本')
        a = [1, 2, 3, 4]
        self.logger.info(f'程序运行结果: {a[5]}')

    def normal_func(self):
        """
        正常运行的程序
        """
        self.logger.info('INFO级别:用于展示程序的运行状态和正常操作')
        self.logger.debug('DEBUG级别:用于输出详细的运行日志')
        self.logger.warning(
            'WARNING级别:用于记录一些警告信息,这些信息可能表明程序出现了一些异常或潜在的问题,但并不会导致程序的崩溃或功能的异常')
        self.logger.error('ERROR级别:用于记录程序中的错误信息,但仍不影响系统的继续运行')
        self.logger.critical('CRITICAL级别:最高级别的日志,用于记录知名错误,这些错误会导致程序立即退出,并且无法恢复;')
        self.logger.fatal('FATAL级别:与CRITICAL一样表示最高级别的日志,在不同的框架或库中,可能会定义不同的日志级别,'
                          'Python标准日志模块中没有定义FATAL级别,某些第三方日志库可能会使用FATAL级别表示同样的错误')
        a = [1, 2, 3, 4]
        self.logger.info(f'程序运行结果: {a[2]}')
        print('这是第一段print文字,默认从新的一行行首开始显示')
        print('这是第二段print文字,默认从新的一行行首开始显示')
        print('这是第三段print文字')

    def redirection_msg(self, mode, context, message):
        """
        打印错误信息并且弹出警告窗口
        """
        frame = inspect.currentframe().f_back
        filename = frame.f_code.co_filename
        lineno = frame.f_lineno
        funcname = frame.f_code.co_name
        if mode == QtMsgType.QtInfoMsg:
            mode = 20
        elif mode == QtMsgType.QtWarningMsg:
            mode = 30
        elif mode == QtMsgType.QtCriticalMsg:
            mode = 50
        elif mode == QtMsgType.QtFatalMsg:
            mode = 50
        else:
            mode = 10

        record = self.logger.makeRecord(name=self.logger.name, level=mode, fn=filename, lno=lineno, msg=message, args=(), exc_info=None, func=funcname, sinfo=None)
        self.logger.handle(record)


def error_handler(exc_type, exc_value, exc_tb):
    error_message = "".join(traceback.format_exception(exc_type, exc_value, exc_tb))
    reply = QMessageBox.critical(
        None, 'Error Caught!:', error_message,
        QMessageBox.StandardButton.Abort | QMessageBox.StandardButton.Retry,
        QMessageBox.StandardButton.Abort)
    if reply == QMessageBox.StandardButton.Abort:
        sys.exit(1)


if __name__ == '__main__':
    sys.excepthook = error_handler
    app = QApplication(sys.argv)
    ui = Ui_Form()
    ui.show()
    sys.exit(app.exec())

4. 运行效果展示

QTextEdit控件中显示日志信息、print信息,以及运行异常时的报错信息;此外,当程序报错时,会出现弹窗显示具体的报错内容,报错内容将保存到log文件,弹窗关闭后,程序将强制退出。

即便出现异常,程序也不会闪退

报错信息本地文件可见。

附注

还可以把print的部分直接作为info级别的log输出,只需要小小的改动一下onUpdateText函数。

    def onUpdateText(self, text):
        """
        重定向控制台输出到文本框控件
        """
        if text == '\n':
            # print函数默认会在输出内容后自动添加一个换行符\n
            # 后续脚本能够保证,每次显示print内容时,都是从一个新行的行首开始,不再需要这个换行符,跳过即可
            return
        self.logger.info(text)
        # cursor = self.textEdit.textCursor()
        #
        # if cursor.position() == cursor.block().position():
        #     # 如果当前光标位置是行首, 直接打印文字内容
        #     cursor.insertText(text)
        # else:
        #     # 如果当前光标位置是行中, 换行,从下一行行首开始打印文字内容
        #     cursor.movePosition(cursor.MoveOperation.End)
        #     cursor.insertText('\n'+text)
        # self.textEdit.setTextCursor(cursor)
        # self.textEdit.ensureCursorVisible()
        return

效果如下:

参考链接

1. python logging模块详解_logging.getlogger(__name__)-CSDN博客

2. PyQt5 重定向输出以及错误信息_python pyqt5重定向输出流-CSDN博客

  • 19
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 可以使用PyQt5中的QTextEdit控件来展示程序的输出信息。首先需要创建一个QTextEdit对象,然后在程序中将输出信息写入该对象中即可。具体实现可以参考以下代码: ```python import sys from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QTextEdit from PyQt5.QtCore import Qt class MyWidget(QWidget): def __init__(self): super().__init__() self.initUI() def initUI(self): self.text_edit = QTextEdit() self.text_edit.setReadOnly(True) vbox = QVBoxLayout() vbox.addWidget(self.text_edit) self.setLayout(vbox) self.setGeometry(100, 100, 500, 500) self.show() def write(self, text): self.text_edit.moveCursor(Qt.End) self.text_edit.insertPlainText(text) if __name__ == '__main__': app = QApplication(sys.argv) widget = MyWidget() sys.stdout = widget print('Hello PyQt5') sys.exit(app.exec_()) ``` 在上面的代码中,我们创建了一个MyWidget类,该类继承自QWidget类,并且包含一个QTextEdit控件。在MyWidget类中,我们重载了write方法,该方法用于将输出信息写入QTextEdit控件中。在程序中,我们将sys.stdout重定向到MyWidget对象中,这样程序的输出信息就会被写入到QTextEdit控件中。最后,我们调用app.exec_()方法来启动程序的事件循环。 ### 回答2: Python是一种强大的编程语言,同PyQt5是Python的一个GUI框架,它可以开发交互式应用程序和用户界面。使用PyQt5可以将输出信息展示到GUI图形界面上,从而提高交互性和可视化性。下面是通过PyQt5将输出信息展示到GUI图形界面的一些步骤: 1. 安装PyQt5库 PyQt5需要安装,可以通过pip install PyQt5命令来安装。 2. 导入所需的模块 在代码中需要导入PyQt5模块和所需的控件。在这里,我们需要导入QTextEdit控件来实现我们的目的。 ```python from PyQt5.QtWidgets import QApplication, QWidget, QTextEdit, QVBoxLayout ``` 3. 创建GUI窗口 首先要创建GUI窗口,可以通过Qwidget创建一个简单的空白窗口。 ```python app = QApplication([]) window = QWidget() ``` 4. 创建TextEdit控件 接下来,我们创建一个TextEdit控件以用于输出信息。TextEdit控件是一个多行文本框,可以用于显示文本。 ```python text_edit = QTextEdit() ``` 5. 设置TextEdit控件 为了使TextEdit控件看起来更好,可以对其进行一些设置。可以使用setReadOnly()方法来禁止用户编辑文本,使用insertPlainText()方法将文本插入到TextEdite控件中。 ```python text_edit.setReadOnly(True) text_edit.insertPlainText('Hello PyQt5\n') ``` 6. 将TextEdit控件添加到布局中 PyQt5布局管理器可以控制widget在窗口中的大小和位置。在这里,我们使用QVBoxLayout来将TextEdit控件添加到窗口上。 ```python layout = QVBoxLayout() layout.addWidget(text_edit) window.setLayout(layout) ``` 7. 展示GUI窗口 最后,我们将GUI窗口显示在屏幕上。 ```python window.show() app.exec_() ``` 8. 运行程序并输出信息 在上述代码设置完成后,我们就可以运行程序并输出信息到TextEdit控件了。使用insertPlainText()方法一行一行地插入文本。 ```python text_edit.insertPlainText('这是一行输出信息\n') text_edit.insertPlainText('又一行输出信息\n') text_edit.insertPlainText('最后一行输出信息\n') ``` 通过这些步骤,我们就可以使用PyQt5将输出信息展示到GUI图形界面上了。 ### 回答3: Python 是一种高级编程语言,它非常受欢迎,因为它易于学习和理解。PyQt5 是一个 Python 库,它允许我们创建 GUI 应用程序PyQt5 可以帮助我们将 Python 代码集成到 GUI 应用程序中。在本篇文章中,我们将了解如何将 Python 代码的输出信息展示到 GUI 图形界面上。 首先,在 PyQT5 中,我们需要创建一个窗口。使用 QWidget 类来创建窗口对象。QWidget 是所有用户界面对象的基类,包括基本控件,如按钮,标签,文本编辑器等。我们将使用 QTextEdit 控件显示 Python 代码输出信息。QTextEdit 是一个内容可编辑的多行文本编辑器。我们将创建一个 QWidget 窗口,并在窗口中添加 QTextEdit。 ```python import sys from PyQt5.QtWidgets import QApplication, QWidget, QTextEdit, QVBoxLayout class MyApp(QWidget): def __init__(self): super().__init__() self.initUI() def initUI(self): # 创建一个 QTextEdit 控件 self.text_edit = QTextEdit() # 创建一个垂直布局器,将 QTextEdit 控件添加到 QWidget 窗口中 vbox = QVBoxLayout() vbox.addWidget(self.text_edit) self.setLayout(vbox) self.setGeometry(500, 500, 500, 500) self.setWindowTitle('Python Code Output Display') self.show() if __name__ == '__main__': app = QApplication(sys.argv) ex = MyApp() sys.exit(app.exec_()) ``` 现在我们已经创建了一个界面,接下来让我们修改代码,以便输出 Python 代码的输出信息。 ```python import sys from PyQt5.QtWidgets import QApplication, QWidget, QTextEdit, QVBoxLayout from io import StringIO class MyApp(QWidget): def __init__(self): super().__init__() self.initUI() def initUI(self): # 创建一个 QTextEdit 控件 self.text_edit = QTextEdit() # 创建一个垂直布局器,将 QTextEdit 控件添加到 QWidget 窗口中 vbox = QVBoxLayout() vbox.addWidget(self.text_edit) self.setLayout(vbox) # 在 QTextEdit 控件显示 Python 代码输出 sys.stdout = self self.setGeometry(500, 500, 500, 500) self.setWindowTitle('Python Code Output Display') self.show() def write(self, message): self.text_edit.insertPlainText(message) if __name__ == '__main__': app = QApplication(sys.argv) ex = MyApp() sys.exit(app.exec_()) ``` 我们在 MyApp 类中添加了一个 write() 函数,用来将 Python 代码输出传递给 QTextEdit 控件。然后,我们重写了 sys.stdout 对象,使其指向我们的 MyApp 类。这样,我们可以将 Python 代码的输出信息展示在 QTextEdit 控件上。最后,我们运行 QApplication,可以看到 Python 代码输出的信息显示在 QTextEdit 控件中了。 上述是一个简单的在 Python 程序中使用 PyQT5 控件展示输出信息的实例,仅供参考。在实际开发中,可能需要结合 PyQt5 的其他功能和特性来实现更加丰富、复杂的应用场景。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值