当前位置: 首页 > news >正文

pyqt logger类与界面分开

文章目录

      • 分离日志类和界面类的完整代码
        • 1. `logger.py` - 日志类实现
        • 2. `main_window.py` - 界面类实现
      • 代码结构说明

分离日志类和界面类的完整代码

以下是将日志类和界面类分开的完整代码结构,包含两个主要文件:logger.pymain_window.py
在这里插入图片描述

1. logger.py - 日志类实现
import os
import datetime
import inspect
from PyQt5.QtCore import Qt, pyqtSignal, QObject, QMutexclass Logger(QObject):"""线程安全的日志记录器,支持日志级别和GUI显示"""log_message = pyqtSignal(str, int)  # 信号:日志消息,日志级别# 日志级别定义DEBUG = 0INFO = 1WARNING = 2ERROR = 3CRITICAL = 4# 日志级别文本和颜色LOG_LEVELS = {DEBUG: ("DEBUG", (128, 128, 128)),  # 灰色INFO: ("INFO", (0, 0, 0)),          # 黑色WARNING: ("WARNING", (255, 128, 0)), # 橙色ERROR: ("ERROR", (255, 0, 0)),      # 红色CRITICAL: ("CRITICAL", (255, 0, 255)) # 紫色}def __init__(self, parent=None):super().__init__(parent)self.log_file = Noneself.log_level = self.INFOself.mutex = QMutex()  # 用于线程安全self.max_log_size = 10 * 1024 * 1024  # 10MBself.log_to_file = Falseself.show_caller_info = True  # 是否显示调用者信息def set_log_file(self, file_path):"""设置日志文件路径"""self.log_file = file_pathself.log_to_file = Trueself._rotate_log_file()def set_log_level(self, level):"""设置日志级别"""self.log_level = leveldef set_show_caller_info(self, show=True):"""设置是否显示调用者信息"""self.show_caller_info = showdef debug(self, message):"""记录调试级别的日志"""self._log(self.DEBUG, message)def info(self, message):"""记录信息级别的日志"""self._log(self.INFO, message)def warning(self, message):"""记录警告级别的日志"""self._log(self.WARNING, message)def error(self, message):"""记录错误级别的日志"""self._log(self.ERROR, message)def critical(self, message):"""记录严重错误级别的日志"""self._log(self.CRITICAL, message)def _get_caller_info(self):"""获取调用者信息(函数名和行号)"""try:# 从栈中获取调用者信息# 0: _get_caller_info, 1: _log, 2: debug/info/warning/error/critical, 3: 实际调用位置frame = inspect.currentframe().f_back.f_back.f_backfunction_name = frame.f_code.co_namefile_name = os.path.basename(frame.f_code.co_filename)line_number = frame.f_linenoreturn f"{file_name}:{function_name}():{line_number}"except Exception as e:return "unknown:unknown():0"def _log(self, level, message):"""内部日志记录方法"""if level < self.log_level:return# 获取当前时间timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]# 获取调用者信息caller_info = self._get_caller_info() if self.show_caller_info else ""# 构建完整的日志消息level_text = self.LOG_LEVELS[level][0]# 如果有调用者信息,添加到日志消息中if caller_info:full_message = f"[{timestamp}] [{level_text}] [{caller_info}] {message}"else:full_message = f"[{timestamp}] [{level_text}] {message}"# 线程安全self.mutex.lock()try:# 发送日志消息到GUIself.log_message.emit(full_message, level)# 写入日志文件if self.log_to_file and self.log_file:self._write_to_file(full_message)finally:self.mutex.unlock()def _write_to_file(self, message):"""写入日志到文件"""try:# 检查文件大小并旋转日志self._rotate_log_file()# 写入日志with open(self.log_file, "a", encoding="utf-8") as f:f.write(f"{message}\n")except Exception as e:# 如果写入日志文件失败,尝试发送错误消息error_msg = f"写入日志文件失败: {str(e)}"if hasattr(self, 'log_message'):self.log_message.emit(f"[ERROR] {error_msg}", self.ERROR)def _rotate_log_file(self):"""旋转日志文件(当日志文件达到最大大小时创建新文件)"""if not self.log_to_file or not self.log_file:return# 检查文件是否存在并且超过最大大小if os.path.exists(self.log_file):file_size = os.path.getsize(self.log_file)if file_size >= self.max_log_size:# 获取文件名和扩展名base_name, ext = os.path.splitext(self.log_file)# 查找可用的备份文件名counter = 1backup_file = f"{base_name}.{counter}{ext}"while os.path.exists(backup_file):counter += 1backup_file = f"{base_name}.{counter}{ext}"# 重命名当前日志文件os.rename(self.log_file, backup_file)
2. main_window.py - 界面类实现
import sys
import datetime
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QTextEdit, QPushButton, QLabel, QComboBox, QFileDialog, QAction, QMenuBar, QStatusBar)
from PyQt5.QtGui import QFont, QColor, QTextCursor, QTextCharFormat
from PyQt5.QtCore import Qt, pyqtSignal, QObject, QThread, QMutex, QMetaObject
from logger import Loggerclass LogViewer(QWidget):"""日志查看器组件,用于显示日志消息"""def __init__(self, parent=None):super().__init__(parent)self.init_ui()def init_ui(self):# 创建主布局layout = QVBoxLayout(self)# 创建日志显示区域self.log_text = QTextEdit()self.log_text.setReadOnly(True)self.log_text.setUndoRedoEnabled(False)  # 禁用撤销/重做,节省内存self.log_text.setLineWrapMode(QTextEdit.NoWrap)# 设置字体,使用等宽字体便于查看font = QFont("Consolas", 10)font.setFixedPitch(True)self.log_text.setFont(font)# 添加到布局layout.addWidget(self.log_text)def append_log(self, message, level):"""添加日志消息到显示区域"""# 创建字符格式char_format = QTextCharFormat()r, g, b = Logger.LOG_LEVELS[level][1]char_format.setForeground(QColor(r, g, b))# 移动到文本末尾cursor = self.log_text.textCursor()cursor.movePosition(QTextCursor.End)# 设置字符格式cursor.setCharFormat(char_format)# 插入新日志cursor.insertText(f"{message}\n")# 滚动到底部self.log_text.setTextCursor(cursor)self.log_text.ensureCursorVisible()# 限制日志行数,防止内存溢出max_lines = 10000if self.log_text.document().blockCount() > max_lines:cursor.setPosition(0)cursor.movePosition(QTextCursor.EndOfLine)cursor.removeSelectedText()cursor.deleteChar()self.log_text.setTextCursor(cursor)class LogLevelFilter(QWidget):"""日志级别过滤器组件"""log_level_changed = pyqtSignal(int)def __init__(self, parent=None):super().__init__(parent)self.init_ui()def init_ui(self):# 创建布局layout = QHBoxLayout(self)layout.setContentsMargins(0, 0, 0, 0)# 添加标签label = QLabel("日志级别:")layout.addWidget(label)# 添加下拉框self.level_combo = QComboBox()self.level_combo.addItems(["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"])self.level_combo.setCurrentIndex(Logger.INFO)  # 默认显示INFO级别self.level_combo.currentIndexChanged.connect(self.on_level_changed)layout.addWidget(self.level_combo)# 添加拉伸layout.addStretch()def on_level_changed(self, index):"""日志级别变更处理"""self.log_level_changed.emit(index)def set_log_level(self, level):"""设置当前日志级别"""self.level_combo.setCurrentIndex(level)class LogSaver(QWidget):"""日志保存组件"""save_log_requested = pyqtSignal()def __init__(self, parent=None):super().__init__(parent)self.init_ui()def init_ui(self):# 创建布局layout = QHBoxLayout(self)layout.setContentsMargins(0, 0, 0, 0)# 添加保存按钮self.save_button = QPushButton("保存日志")self.save_button.clicked.connect(self.on_save_clicked)layout.addWidget(self.save_button)# 添加拉伸layout.addStretch()def on_save_clicked(self):"""保存按钮点击处理"""self.save_log_requested.emit()class LogTester(QWidget):"""日志测试组件,包含五个测试按钮"""def __init__(self, logger, parent=None):super().__init__(parent)self.logger = loggerself.init_ui()def init_ui(self):# 创建布局layout = QHBoxLayout(self)layout.setContentsMargins(0, 0, 0, 0)# 添加五个测试按钮self.debug_button = QPushButton("DEBUG")self.debug_button.setStyleSheet("background-color: #808080; color: white;")self.debug_button.clicked.connect(self.on_debug_clicked)layout.addWidget(self.debug_button)self.info_button = QPushButton("INFO")self.info_button.setStyleSheet("background-color: #000000; color: white;")self.info_button.clicked.connect(self.on_info_clicked)layout.addWidget(self.info_button)self.warning_button = QPushButton("WARNING")self.warning_button.setStyleSheet("background-color: #FF8000; color: white;")self.warning_button.clicked.connect(self.on_warning_clicked)layout.addWidget(self.warning_button)self.error_button = QPushButton("ERROR")self.error_button.setStyleSheet("background-color: #FF0000; color: white;")self.error_button.clicked.connect(self.on_error_clicked)layout.addWidget(self.error_button)self.critical_button = QPushButton("CRITICAL")self.critical_button.setStyleSheet("background-color: #FF00FF; color: white;")self.critical_button.clicked.connect(self.on_critical_clicked)layout.addWidget(self.critical_button)def on_debug_clicked(self):"""DEBUG按钮点击处理"""self.logger.debug("这是一条DEBUG级别的测试日志")def on_info_clicked(self):"""INFO按钮点击处理"""self.logger.info("这是一条INFO级别的测试日志")def on_warning_clicked(self):"""WARNING按钮点击处理"""self.logger.warning("这是一条WARNING级别的测试日志")def on_error_clicked(self):"""ERROR按钮点击处理"""self.logger.error("这是一条ERROR级别的测试日志")def on_critical_clicked(self):"""CRITICAL按钮点击处理"""self.logger.critical("这是一条CRITICAL级别的测试日志")class LoggerDemo(QMainWindow):"""日志类演示主窗口"""def __init__(self):super().__init__()self.logger = Logger()self.init_ui()self.setup_logger()def init_ui(self):# 设置窗口属性self.setWindowTitle("PyQt5 日志类演示")self.setGeometry(100, 100, 800, 600)# 创建中央部件central_widget = QWidget()self.setCentralWidget(central_widget)main_layout = QVBoxLayout(central_widget)# 创建日志测试组件self.log_tester = LogTester(self.logger)main_layout.addWidget(self.log_tester)# 创建日志查看器self.log_viewer = LogViewer()# 创建日志级别过滤器self.log_filter = LogLevelFilter()self.log_filter.log_level_changed.connect(self.on_log_level_changed)# 创建日志保存组件self.log_saver = LogSaver()self.log_saver.save_log_requested.connect(self.on_save_log)# 创建控制区域control_layout = QHBoxLayout()control_layout.addWidget(self.log_filter)control_layout.addWidget(self.log_saver)# 添加到主布局main_layout.addLayout(control_layout)main_layout.addWidget(self.log_viewer)# 创建菜单栏self.create_menu_bar()# 创建状态栏self.statusBar().showMessage("就绪")def create_menu_bar(self):"""创建菜单栏"""menu_bar = self.menuBar()# 文件菜单file_menu = menu_bar.addMenu("文件")# 保存日志动作save_action = QAction("保存日志", self)save_action.setShortcut("Ctrl+S")save_action.triggered.connect(self.on_save_log)file_menu.addAction(save_action)# 设置日志文件动作set_log_file_action = QAction("设置日志文件", self)set_log_file_action.triggered.connect(self.on_set_log_file)file_menu.addAction(set_log_file_action)# 退出动作exit_action = QAction("退出", self)exit_action.setShortcut("Ctrl+Q")exit_action.triggered.connect(self.close)file_menu.addAction(exit_action)# 日志菜单log_menu = menu_bar.addMenu("日志")# 清空日志动作clear_action = QAction("清空日志", self)clear_action.triggered.connect(self.on_clear_log)log_menu.addAction(clear_action)# 模拟日志动作simulate_menu = log_menu.addMenu("模拟日志")# 添加各种级别的模拟日志动作levels = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]for level in levels:action = QAction(f"发送{level}日志", self)action.triggered.connect(lambda checked, l=level: self.on_simulate_log(l))simulate_menu.addAction(action)def setup_logger(self):"""设置日志记录器"""# 连接日志信号到日志查看器self.logger.log_message.connect(self.log_viewer.append_log)# 设置初始日志级别self.logger.set_log_level(Logger.INFO)# 记录启动信息self.logger.info("日志系统已启动")def on_log_level_changed(self, level):"""日志级别变更处理"""self.logger.set_log_level(level)self.statusBar().showMessage(f"日志级别已设置为: {Logger.LOG_LEVELS[level][0]}")def on_save_log(self):"""保存日志文件"""file_path, _ = QFileDialog.getSaveFileName(self, "保存日志", f"log_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}.txt","文本文件 (*.txt);;所有文件 (*)")if file_path:try:with open(file_path, "w", encoding="utf-8") as f:f.write(self.log_viewer.log_text.toPlainText())self.logger.info(f"日志已保存到: {file_path}")except Exception as e:self.logger.error(f"保存日志失败: {str(e)}")def on_set_log_file(self):"""设置日志文件路径"""file_path, _ = QFileDialog.getSaveFileName(self, "设置日志文件", "application.log","日志文件 (*.log);;文本文件 (*.txt);;所有文件 (*)")if file_path:self.logger.set_log_file(file_path)self.logger.info(f"日志文件已设置为: {file_path}")def on_clear_log(self):"""清空日志显示"""self.log_viewer.log_text.clear()self.logger.info("日志已清空")def on_simulate_log(self, level):"""模拟发送不同级别的日志"""level_map = {"DEBUG": self.logger.debug,"INFO": self.logger.info,"WARNING": self.logger.warning,"ERROR": self.logger.error,"CRITICAL": self.logger.critical}if level in level_map:message = f"这是一条{level}级别的测试日志,时间: {datetime.datetime.now().strftime('%H:%M:%S.%f')[:-3]}"level_map[level](message)else:self.logger.warning(f"未知日志级别: {level}")if __name__ == "__main__":app = QApplication(sys.argv)window = LoggerDemo()window.show()# 模拟多线程日志记录class LogThread(QThread):def __init__(self, logger):super().__init__()self.logger = loggerdef run(self):for i in range(10):self.logger.debug(f"线程 {self.currentThreadId()} 发送调试消息 {i}")self.msleep(500)self.logger.info(f"线程 {self.currentThreadId()} 发送信息消息 {i}")self.msleep(500)thread1 = LogThread(window.logger)thread2 = LogThread(window.logger)thread1.start()thread2.start()sys.exit(app.exec_())

代码结构说明

  1. logger.py

    • 包含完整的Logger类实现
    • 负责日志记录的核心功能,包括日志级别控制、日志文件管理、线程安全等
    • 提供日志信号用于与GUI通信
  2. main_window.py

    • 包含所有界面相关的类实现
    • LogViewer: 日志显示组件
    • LogLevelFilter: 日志级别过滤组件
    • LogSaver: 日志保存组件
    • LogTester: 测试按钮组件
    • LoggerDemo: 主窗口类
  3. 依赖关系

    • 界面类通过导入Logger类来使用日志功能
    • 日志类通过信号与界面类通信,实现日志的实时显示

这种分离方式使代码更加模块化,日志类可以独立于界面使用,也便于后续维护和扩展。

相关文章:

  • ISO 绕过 Windows 11 硬件检查:TPM/Secure Boot/CPU/RAM 及 OOBE 网络验证的完整指南
  • 【已解决】win11安装驱动提示 “The file hash value is not in the specified catalog file...”
  • 索引——高效查询的关键
  • 龙芯7A1000桥片数据手册解读(时序)
  • Python 爬虫案例(不定期更新)
  • 缓存与加速技术实践-Kafka消息队列
  • 网络安全基础:从CIA三元组到密钥交换与消息认证
  • 【软考高级系统架构论文】论 SOA 在企业集成架构设计中的应用
  • 从C++编程入手设计模式——观察者模式
  • TensorFlow 安装与 GPU 驱动兼容(h800)
  • 人工智能学习45-Incep网络
  • 经济法-4- 合同法律制度
  • 从0开始学linux韦东山教程Linux驱动入门实验班(1)
  • Web攻防-XSS跨站Cookie盗取数据包提交网络钓鱼BEEF项目XSS平台危害利用
  • 【软考高级系统架构论文】论软件系统架构风格
  • 【simulink】IEEE5节点系统潮流仿真模型(2机5节点全功能基础模型)
  • 【Java】对象
  • 操作系统内核态和用户态--1-基础认识
  • 操作系统内核态和用户态--2-系统调用是什么?
  • 分布式锁 不同的拒绝策略 应用场景 业务上的思考
  • 重庆市政府官方网站/交换友情链接吧
  • 免费网络推广培训课程/武汉网站seo推广公司
  • 网站服务器做缓存吗/福州seo外包公司
  • 2021最火电商平台/关键词优化软件
  • 网站建设维护升级/创建网址链接
  • 延庆住房和城乡建设委员会网站/中国软文网