qt在窗口中同步打印日志信息,根据日志级别设置日志颜色

2023-05-16

场景

  一般我们会在程序运行的过程中配置对应的日志信息,帮助我们了解当前程序执行的进度。
  当使用qt增加了操作界面时,同样需要将日志信息在界面中显示出来。

思路

  1. 使用qt的QtWidgets.QTextEdit()控件作为日志显示的区域。

    • 日志展示区域应该能够看到最新的日志,
      • QtWidgets.QTextEdit().moveCursor(QTextCursor.StartOfLine)
      • QtWidgets.QTextEdit().ensureCursorVisible()
  2. 使用qt的信号机制将接受的日志信息写入QtWidgets.QTextEdit()控件

  3. 根据日志信息中的日志级别关键字,控制设置文本的格式

        def update_log(self, text):
        """将日志输出到 text widget"""
        if not self.isVisible():
            return
        if "ERROR" in text: # 错误日志加粗
            text = f"<font color='red'><b>{text}</font><br>"
        elif "INFO" in text:
            text = f"<font color='green'>{text}</font><br>"
        elif "WARNING" in text:
            text = f"<font color='cyan'>{text}</font><br>"
        elif "DEBUG" in text:
            text = f"<font color='blue'>{text}</font><br>"
        else:
            text = f"{text}<br>"
        self.log_browser.setWordWrapMode(QTextOption.WordWrap)
        self.log_browser.moveCursor(QTextCursor.End)
        self.log_browser.insertHtml(text)
        self.log_browser.ensureCursorVisible()
    

实现

效果:
在这里插入图片描述
示例代码:(为保证能够直接运行,已注释掉其他依赖的代码)

# -*- coding:UTF-8 -*-

"""
 @ProjectName  : pythonProject 
 @FileName     : camera_win.py
 @Description: 
 @Time         : 2022/10/27 11:21
 @Author       : Qredsun
 """
import json

from PyQt5 import QtWidgets
from PyQt5.QtCore import QObject
from PyQt5.QtCore import pyqtSignal
from PyQt5.QtGui import QTextCursor
from PyQt5.QtGui import QTextOption
from loguru import logger

from camera_set_conf.camera_conf_set import CameraConf
from ui.Camera_ui import Ui_Form
from ui.browser_win import Dialog as conf_dialog
from ui.common import choose_file
from ui.common import info_box
from util.collect_project_info import read_conf_from_json
from util.collect_project_info import write_json_file

from PyQt5 import QtCore, QtGui, QtWidgets

#单独运行该脚本,隐藏依赖函数
CameraConf = None
conf_dialog = None
# UI
class Ui_Form(object):
    def setupUi(self, Form):
        Form.setObjectName("Form")
        Form.setWindowModality(QtCore.Qt.ApplicationModal)
        Form.resize(356, 285)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(Form.sizePolicy().hasHeightForWidth())
        Form.setSizePolicy(sizePolicy)
        self.gridLayout_2 = QtWidgets.QGridLayout(Form)
        self.gridLayout_2.setObjectName("gridLayout_2")
        self.label = QtWidgets.QLabel(Form)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.label.sizePolicy().hasHeightForWidth())
        self.label.setSizePolicy(sizePolicy)
        self.label.setMinimumSize(QtCore.QSize(0, 35))
        self.label.setMaximumSize(QtCore.QSize(16777215, 55))
        self.label.setObjectName("label")
        self.gridLayout_2.addWidget(self.label, 0, 0, 1, 1)
        self.gridLayout = QtWidgets.QGridLayout()
        self.gridLayout.setObjectName("gridLayout")
        self.common_conf_path_edit = QtWidgets.QLineEdit(Form)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.common_conf_path_edit.sizePolicy().hasHeightForWidth())
        self.common_conf_path_edit.setSizePolicy(sizePolicy)
        self.common_conf_path_edit.setMinimumSize(QtCore.QSize(135, 35))
        self.common_conf_path_edit.setMaximumSize(QtCore.QSize(16777215, 55))
        self.common_conf_path_edit.setObjectName("common_conf_path_edit")
        self.gridLayout.addWidget(self.common_conf_path_edit, 0, 0, 1, 1)
        self.common_conf_path_choose = QtWidgets.QPushButton(Form)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.common_conf_path_choose.sizePolicy().hasHeightForWidth())
        self.common_conf_path_choose.setSizePolicy(sizePolicy)
        self.common_conf_path_choose.setMaximumSize(QtCore.QSize(16777215, 55))
        self.common_conf_path_choose.setObjectName("common_conf_path_choose")
        self.gridLayout.addWidget(self.common_conf_path_choose, 0, 1, 1, 1)
        self.common_conf_edit = QtWidgets.QPushButton(Form)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.common_conf_edit.sizePolicy().hasHeightForWidth())
        self.common_conf_edit.setSizePolicy(sizePolicy)
        self.common_conf_edit.setMaximumSize(QtCore.QSize(16777215, 55))
        self.common_conf_edit.setObjectName("common_conf_edit")
        self.gridLayout.addWidget(self.common_conf_edit, 0, 2, 1, 1)
        self.gridLayout_2.addLayout(self.gridLayout, 1, 0, 1, 1)
        self.horizontalLayout = QtWidgets.QHBoxLayout()
        self.horizontalLayout.setObjectName("horizontalLayout")
        self.label_3 = QtWidgets.QLabel(Form)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.label_3.sizePolicy().hasHeightForWidth())
        self.label_3.setSizePolicy(sizePolicy)
        self.label_3.setMaximumSize(QtCore.QSize(80, 55))
        self.label_3.setObjectName("label_3")
        self.horizontalLayout.addWidget(self.label_3)
        self.comboBox = QtWidgets.QComboBox(Form)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.comboBox.sizePolicy().hasHeightForWidth())
        self.comboBox.setSizePolicy(sizePolicy)
        self.comboBox.setMinimumSize(QtCore.QSize(120, 35))
        self.comboBox.setMaximumSize(QtCore.QSize(16777215, 55))
        self.comboBox.setObjectName("comboBox")
        self.horizontalLayout.addWidget(self.comboBox)
        self.private_conf_edit = QtWidgets.QPushButton(Form)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.private_conf_edit.sizePolicy().hasHeightForWidth())
        self.private_conf_edit.setSizePolicy(sizePolicy)
        self.private_conf_edit.setMaximumSize(QtCore.QSize(16777215, 55))
        self.private_conf_edit.setObjectName("private_conf_edit")
        self.horizontalLayout.addWidget(self.private_conf_edit)
        self.set_conf_btn = QtWidgets.QPushButton(Form)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.set_conf_btn.sizePolicy().hasHeightForWidth())
        self.set_conf_btn.setSizePolicy(sizePolicy)
        self.set_conf_btn.setMaximumSize(QtCore.QSize(16777215, 55))
        self.set_conf_btn.setObjectName("set_conf_btn")
        self.horizontalLayout.addWidget(self.set_conf_btn)
        self.gridLayout_2.addLayout(self.horizontalLayout, 2, 0, 1, 1)
        self.log_browser = QtWidgets.QTextEdit(Form)
        self.log_browser.setMinimumSize(QtCore.QSize(0, 140))
        font = QtGui.QFont()
        font.setPointSize(9)
        self.log_browser.setFont(font)
        self.log_browser.setObjectName("log_browser")
        self.gridLayout_2.addWidget(self.log_browser, 3, 0, 1, 1)

        self.retranslateUi(Form)
        QtCore.QMetaObject.connectSlotsByName(Form)

    def retranslateUi(self, Form):
        _translate = QtCore.QCoreApplication.translate
        Form.setWindowTitle(_translate("Form", "CAMERA设备配置"))
        self.label.setText(_translate("Form", "相机公共配置"))
        self.common_conf_path_edit.setPlaceholderText(_translate("Form", "公共配置文件路径"))
        self.common_conf_path_choose.setText(_translate("Form", "选择文件"))
        self.common_conf_edit.setText(_translate("Form", "配置预览"))
        self.label_3.setText(_translate("Form", "相机编号"))
        self.private_conf_edit.setText(_translate("Form", "配置预览"))
        self.set_conf_btn.setText(_translate("Form", "一键配置"))
        self.log_browser.setHtml(_translate("Form",
                                            "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\">\n"
                                            "<html><head><meta name=\"qrichtext\" content=\"1\" /><style type=\"text/css\">\n"
                                            "p, li { white-space: pre-wrap; }\n"
                                            "</style></head><body style=\" font-family:\'SimSun\'; font-size:14pt; font-weight:400; font-style:normal;\">\n"
                                            "<p style=\"-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:9pt;\"><br /></p></body></html>"))
        self.log_browser.setPlaceholderText(_translate("Form", "程序配置日志"))
        
class Widget(QtWidgets.QWidget, Ui_Form):

    def __init__(self, project_conf):
        super().__init__()
        self.setupUi(self)

        # 日志重定向
        log_out = Stream()
        log_out.newText.connect(self.update_log)
        logger.add(log_out, format="{time} | {level} | {message}")

        # 项目中所有相机对应的配置 
        self.devices_info = project_conf
        # 公共配置路径
        self.common_conf_file_path = None
        # 公共配置
        self.common_conf = None
        # 设备IP
        self.cur_device_ip = None
        # 设备私有配置
        self.private_conf = None
        # 配置对象
        self.conf_setting_obj = None
        # 默认页面配置:
        self.default_win_status()
        # 加载设备列表
        self.load_devices_ip()

        # 选择公共配置文件
        self.common_conf_path_choose.clicked.connect(self.choose_common_conf_file)
        self.common_conf_edit.clicked.connect(self.show_common_conf)
        # 当前 设备 的配置
        self.comboBox.currentTextChanged.connect(self.load_devices_info)
        self.private_conf_edit.clicked.connect(self.show_private_conf)
        # 一键配置
        self.set_conf_btn.clicked.connect(self.set_all_conf)
        
        # 单独运行该脚本,隐藏依赖函数
        self.common_conf_edit.hide()
        self.set_conf_btn.hide()
        self.common_conf_path_choose.hide()
        self.common_conf_edit.hide()


    # 完成所有配置
    def set_all_conf(self):
        self.log_browser.clear()  # 清空打印区日志
        msg = '设备所有配置'
        try:
            if not self.conf_setting_obj.web_set_default_conf():
                return
            if not self.conf_setting_obj.sdk_set_conf():
                return
            logger.info(f'{msg} 成功')
        except Exception as e:
            logger.error(f'{msg} 失败 {e}')


    # 默认页面状态
    def default_win_status(self):
        self.set_conf_btn.setEnabled(False)
        # 配置预览默认不可点击
        self.common_conf_edit.setEnabled(False)

    # 项目配置加载
    def load_devices_ip(self):
        # 设备下拉列表
        items = list(self.devices_info.keys())
        self.comboBox.clear()
        self.comboBox.addItems(items)
        logger.info('加载设备列表')
        self.load_devices_info(update_setting_obj=False)

    def load_devices_info(self, update_setting_obj=True):
        self.log_browser.clear()  # 清空打印区日志
        # 关联设备信息加载
        self.cur_device_ip = self.comboBox.currentText()
        self.private_conf = self.devices_info.get(self.cur_device_ip, {})
        if not len(self.private_conf):
            msg = f'没有 {self.cur_device_ip} 对应的设备信息'
            logger.error(msg)
            info_box(msg, self)
            return
        # 重新初始化配置对象
        if update_setting_obj:
            self._widget_load_common_conf()

    def update_log(self, text):
        """将日志输出到 text widget"""
        if not self.isVisible():
            return
        if "ERROR" in text:
            text = f"<font color='red'><b>{text}</font><br>"
        elif "INFO" in text:
            text = f"<font color='green'>{text}</font><br>"
        elif "WARNING" in text:
            text = f"<font color='cyan'>{text}</font><br>"
        elif "DEBUG" in text:
            text = f"<font color='blue'>{text}</font><br>"
        else:
            text = f"{text}<br>"
        self.log_browser.setWordWrapMode(QTextOption.WordWrap)
        self.log_browser.moveCursor(QTextCursor.End)
        # self.log_browser.append(text)
        self.log_browser.insertHtml(text)
        self.log_browser.ensureCursorVisible()

    # json 配置预览
    def open_conf_browser(self, text, title):
        # conf_browser = conf_dialog(text, title)
        # conf_browser.output_signal.connect(self.update_conf_by_signal)
        # conf_browser.exec()
        msg = 'test msg test msg test msg test msg test msg test msg test msg test msg test msg test msg '
        logger.debug(msg)
        logger.info(msg)
        logger.warning(msg)
        logger.error(msg)
        logger.critical(msg)

    # 获取预览配置中的内容
    def update_conf_by_signal(self, content):
        self.text_browser_conf = content

    def choose_common_conf_file(self):
        self.log_browser.clear()  # 清空打印区日志
        file_path = choose_file(win_name='设备公共配置文件', file_type='*.json', cur_img_save_dir='.', widget=self)
        # if file_path == "":
        #     msg = '请先指定设备公共配置文件'
        #     logger.error(msg)
        #     info_box(msg, self)
        #     self.common_conf_file_path = None
        #     return
        file_path = r'D:\work\boradxt\pythonProject\camera_set_conf\project_conf_jichang.json'  # todo 测试

        self.common_conf_file_path = file_path
        self.common_conf_path_edit.setText(file_path)
        self.common_conf = read_conf_from_json(self.common_conf_file_path)
        logger.info(f'加载公共配置文件:{file_path}')
        self._widget_load_common_conf()

    def _widget_load_common_conf(self):
        try:
            self.common_conf_edit.setEnabled(True)
            self.update_cam_setting_obj()
        except Exception as e:
            logger.error(f'{self.common_conf_file_path} 配置文件格式不符合要求 {e}')

    def update_cam_setting_obj(self):
        msg = f'初始化设备 {self.cur_device_ip} 配置对象'
        try:
            if not self.common_conf_file_path:
                raise ValueError('缺失公共配置')
            self.conf_setting_obj = CameraConf(self.cur_device_ip, self.private_conf, self.common_conf_file_path)
            self.conf_setting_obj.load_conf(self.common_conf)
            logger.debug(f'{msg}成功')
            self.set_conf_btn.setEnabled(True)  # 一键配置按钮可用
        except Exception as e:
            info_box(f'{msg}失败:{e}', self, 2, '错误')
            logger.error(f'{msg}失败:{e}')

    def show_common_conf(self):
        title = f'{self.common_conf["project_name"]}_{self.common_conf["version"]} 公共配置'
        conf_data = json.dumps(self.common_conf, indent=4, ensure_ascii=False)
        self.open_conf_browser(conf_data, title)
        self.common_conf = json.loads(self.text_browser_conf)
        # 保存文件
        write_json_file(self.common_conf, self.common_conf_file_path)
        logger.info(f'{title} 结束预览')

    def show_private_conf(self):
        title = f'{self.cur_device_ip} 私有配置'
        conf_data = json.dumps(self.private_conf, indent=4, ensure_ascii=False)
        self.open_conf_browser(conf_data, title)
        # self.private_conf = json.loads(self.text_browser_conf)
        logger.info(f'{title} 结束预览')


# 日志重定向
class Stream(QObject):
    """Redirects console output to text widget."""
    newText = pyqtSignal(str)

    def write(self, text):
        self.newText.emit(str(text))
        
import sys
if __name__ == '__main__':
    project_conf = {
        "10.10.40.2": {
            "ip": "10.10.40.2",
            "xiecn_ip": "10.10.40.249",
            "netmask": "255.255.255.0",
            "gateway": "10.10.40.254",
            "rod_id": "252L",
            "slot_id": 2,
            "device_option": "M2391-10-TL-M20",
            "point_id": 12000001,
            "osd": "252L-00001-2",
            "is_middle": True
        }}
    app = QtWidgets.QApplication(sys.argv)
    w = Widget(project_conf)
    w.show()
    sys.exit(app.exec_())
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

qt在窗口中同步打印日志信息,根据日志级别设置日志颜色 的相关文章

  • bash PS1配置

    PS1默认配置 span class token punctuation span e span class token punctuation span 0 span class token punctuation span u 64 h
  • 与JWT的不解之缘

    jar xff1a maven lt dependency gt lt groupId gt io jsonwebtoken lt groupId gt lt artifactId gt jjwt lt artifactId gt lt v
  • Linux 给桌面程序设置个性化图标

    由于安装点系统是 Ubuntu 9 04 自带点Firefox 版本是3 0x xff0c 刚好现在需要用一个web developer 插件 能手动更改 xff0c 添加Cookie xff0c 有点想类似Cookie偷窃专用工具 老兵C
  • python初学一(python中 ~ 号的用法)

    一 python中 的用法 1 作用于正负数的时候 表示按位取反 查看 xff1a Python中按位取反运算符 xff08 xff09 在计算机中的计算过程 简单计算 xff1a a 61 a 43 1 2 在numpy中用于 bool
  • Android Animation的一些简单用法

    View Animation startoffset xff1a 动画执行的时间 pivotX xff1a 缩放 xff08 旋转 xff09 的中轴点X坐标 xff0c 距离自身左边缘的位置 pivoty xff1a 缩放 xff08 旋
  • [笔记]有关Static初始化的一点小小记忆

    Q1 xff1a 看下列代码 xff0c 分析输出结果 xff1f span class hljs keyword public span span class hljs keyword class span Test span class
  • Android随手指移动的DragView(一)——获取偏移量

    想要DragView随着手指移动首先得获取偏移量 xff0c 获取偏移量一般有2种思路 xff08 1 xff09 xff0c 通过event getX 64 Override span class hljs keyword public
  • Android随手指移动的DragView(二)——移动DragView

    获取偏移量offsetX和offsetY后 xff0c 可以通过以下几种方式移动DragView xff1a xff08 1 xff09 xff0c 通过layout实现DragView的移动 span class hljs functio
  • android之onNewIntent()用法

    onNewIntent 用法 知识点 xff1a 1 intent的显示和隐式启动 xff1b 2 activity对intent的action的相应 xff1b 3 onNewIntent 和singleTask xff08 栈唯一模式
  • IBM MQ两个队列管理器之间的通信

    本文为转载 原文链接 2个队列管理器之间的通信 前提 1 确保两边的队列管理器的名称不能相同 xff08 如果名称相同将无法通信 xff0c 在连接的时候虽然发送通道和接收通道都是可以运行的 xff0c 但是当放入测试消息的时候会报2087
  • kotlin-android-extensions过时了,迁移到ViewBinding

    前言 回顾历史 xff0c kotlin android extensions插件让我们省去了很多findViewById的代码 xff0c 直接使用控件id操作控件 不过在Android Studio 4 1及以上IDE新建项目的时候 x
  • “下列软件包有未满足的依赖关系“解决方案

    有读者反映 xff0c 安装aptitude后宕机 xff0c 请各位酌情配置 xff0c 这只是个解决方案 根据各位反应的情况 xff0c 会在适配的同时会修改掉一些必要的核心库 xff0c 从而导致严重的系统问题 大家可以先寻找最优方案
  • NanoDet尝试日志(Windows10 + pytorch1.2 + torchvision 0.4.0 + CUDA 10.2)

    作者源码 xff1a https github com RangiLyu nanodet 按照作者的要求来说 xff0c torch的版本需要 1 3 0 xff0c 一方面由于项目需求 xff0c 另一方面由于torch官网未给出1 3
  • ubuntu安装opencv3.4.13的血与泪(一些坑)

    只提tips xff0c 不做教程 xff1a 0 darknet中出现stream stop xff01 一定先安装ffmpeg conda install c conda forge ffmpeg 并在编译时进行 D xff0c 参考d
  • Windows10下编译opencv以及yolov4、yolov4_cpp_dll.dll

    编译的安装顺序是 xff0c CUDA 43 CUDNN xff08 安装包与压缩包不要删除 xff0c 不要删除 xff0c 不要删除 xff0c 防止踩坑的后备 xff09 然后是VisualStdio xff0c 其次是OPENCV
  • darknet添加新层以编译yolo_cpp_dll-------shufflenet模块

    首先感谢AlexeyAB大神提供的YOLOv4源码 xff0c 以及gmayday1997大神提供的split以及shuffle模块源码 xff0c 本文以两位的工作进行yolov4版本中轻量化模块shufflenet模块的添加 首先寻找到
  • TensorBoard出现错误Requirement.parse(‘google-auth<2,>=1.6.3‘))

    在YOLOv5的训练之中 xff0c 可以采用tensorboard工具进行训练过程的查看 xff0c 从而观察模型的收敛情况 在even的上一级目录运行 xff1a tensorboard logdir 61 34 exp4 34 发现出
  • Scene-Graph-Benchmark.pytorch服务器部署

    未采用md编辑 xff0c 望见谅 目录 序言 部署 数据集制作 数据准备 xff1a VG数据集 数据转换 xff08 参考issue xff09 预测 xff08 还未使用模型预测 xff0c 后续添加 xff09 引用 xff08 T
  • [Ubuntu] Argument list too long的问题

    问题溯源 在使用rm rf时 xff0c 文件夹内部大概有4万多的图像文件 xff0c 造成列表长度过长 xff0c 无法删除 此时参考了强哥大佬使用管道 及xarg的方法顺利完成 span class token operator spa
  • ORC识别

    OCR xff08 Optical Character Recognition 光学字符识别 xff09 是指对输入图像进行分析识别处理 xff0c 获取图像中文字信息的过程 xff0c 具有广泛的应用场景 xff0c 例如场景图像文字识别

随机推荐