一、 logging 简介
logging是 Python 内置的日志模块,这个模块是 Python 官方提供的进行程序日志记录的核心工具。
1. logging比print先进的地方
- print()是最常用的程序调试语句,它能把信息输出到控制台,但无法持久化(比如保存到文件),也无法区分信息的重要程度;
logging可以灵活控制日志的级别 (DEBUG/INFO/WARNING/ERROR/CRITICAL)、输出位置 (控制台、文件、数据库等)、格式(包含时间、模块、行号等),是生产环境中记录程序运行状态的标准方案。
2. 基础使用示例
-
基础配置方法示例:
import logging
基础配置(可选,不配置则使用默认格式输出到控制台)
logging.basicConfig(
level=logging.INFO, # 只记录 INFO 及以上级别的日志
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', # 日志格式
handlers=[ # 可设多个日志处理器
logging.FileHandler("app_run.log", encoding="utf-8"), # 可选:输出到文件
logging.StreamHandler() # 可选:输出到控制台
]
)输出不同级别的日志
logging.debug('调试信息:比如程序运行的详细过程,开发时用') # 不会输出(因为级别设为INFO)
logging.info('普通信息:比如程序正常启动、完成某个操作') # 以下都会输出, 并在日志文件中记录
logging.warning('警告信息:比如非致命错误,如参数不规范')
logging.error('错误信息:比如功能执行失败,但程序还能运行')
logging.critical('严重错误:比如程序无法继续运行')
自定义logger的方法:
python
import logging, sys
# 1. 创建/取 logger
logger = logging.getLogger('my.module') # 名字随意,推荐用模块名
logger.setLevel(logging.DEBUG) # logger 自身级别
# 2. 创建 handler(可以多个)
h1 = logging.StreamHandler(sys.stdout) # 控制台
h2 = logging.FileHandler('run.log', encoding='utf-8') # 文件
# 3. 创建 formatter
fmt = logging.Formatter(
'%(asctime)s | %(levelname)-8s | %(name)s:%(funcName)s:%(lineno)d | %(message)s'
)
h1.setFormatter(fmt)
h2.setFormatter(fmt)
# 4. 把 handler 挂到 logger
logger.addHandler(h1)
logger.addHandler(h2)
# 5. 使用
logger.debug('debug 信息')
logger.info('普通信息')
logger.warning('警告')
logger.error('错误')
3. 核心特点
- 内置模块:无需额外安装,Python 自带,直接导入即可使用;
- 分级控制:可以通过级别过滤日志,比如生产环境只记录 ERROR 及以上,开发环境记录 DEBUG;
- 灵活输出:支持将日志写入文件、按大小 / 时间分割日志文件、输出到网络等。
总结
logging是Python内置的日志模块,可用于替代print()做专业的日志记录;- 该模块支持日志分级、自定义格式、多输出位置,是生产环境程序必备的调试 / 监控工具;
二、Qt 中的日志管理
1、pyside6的日志消息输出函数
Qt中也内置了几个日志消息输出的函数:
- qDebug():调试信息 开发阶段的诊断信息,Release版可能不显示。
- qWarning(): 警告信息 发生了小错误或异常,但程序能恢复运行。
- **qCritical():**严重错误 发生了错误,程序可能部分功能失效。
- qFatal(): 致命错误 (注意) 会立即终止程序,通常用于检测到无法恢复的逻辑错误。
可以手动调用或由Qt系统在后台自动调用这几个函数,输出相应的Qt日志消息。
Qt日志消息的类型为以下几种:
- **QtMsgType.QtDebugMsg:**调试信息
- **QtMsgType.QtInfoMsg:**状态信息
- **QtMsgType.QtWarningMsg:**警告信息
- **QtMsgType.QtCriticalMsg:**严重错误
- **QtMsgType.QtFatalMsg:**致命错误
每调用一次消息输出函数会生成一条对应类型的日志消息,比如:运行qDebug()会生成一条QtDebugMsg类型的消息。但需注意:pyside6并未提供暴露给开发者的qInfo() 函数来生成QtInfoMsg类型的消息。
2、安装消息管理器来管理日志信息
Qt的日志消息需要使用PySide6.QtCore.qInstallMessageHandler来安装一个管理器来管理。qInstallMessageHandler的基本用法:
python
qInstallMessageHandler(qt_message_handler)
qt_message_handler即为指定的消息处理函数,日志消息输出函数每输出一条消息就调用一次消息处理函数。
- 调用示例:
python
import sys
from PySide6.QtCore import qDebug, qInstallMessageHandler, QMessageLogContext
from PySide6.QtWidgets import QApplication
def qt_message_handler(msg_type, context: QMessageLogContext, msg):
print(f"消息类型:{msg_type}")
print(f"消息上下文的类别:{context.category}")
print(f"消息内容:{msg}")
if __name__ == "__main__":
app = QApplication(sys.argv)
qInstallMessageHandler(qt_message_handler) # Qt日志消息由qt_message_handlerr来处理
qDebug("这是调试信息,仅开发阶段使用") # 调用qDebug会生成一条QtMsgType.QtDebugMsg类型的消息并触发qt_message_handler函数来处理
app.quit()
sys.exit()
- 代码解释:
1、qInstallMessageHandler(qt_message_handler) : Qt日志消息由qt_message_handlerr来函数处理。
2、qDebug("这是调试信息,仅开发阶段使用"):调用qDebug生成一条QtMsgType.QtDebugMsg类型的日志消息,并触发qt_message_handler(日志消息处理函数)来处理调用。
日志消息处理函数接受3个参数:
**msg_type:**消息类型,比如QtMsgType.QtDebugMsg、QtMsgType.QtWarningMsg等。
context: 消息上下文(格式为QMessageLogContext),消息上下文的属性有:
category: 类别
file: 运行的文件
function: 生生消息的函数
line: 生成消息的代码行数
**version :**版本
**msg:**消息内容。
- 一个获取Qt系统运行消息的范例:
python
import sys
import time
from PySide6.QtCore import qInstallMessageHandler, QThread
from PySide6.QtWidgets import QApplication, QPushButton
def qt_message_handler(msg_type, context, msg):
print(f"消息类型:[{msg_type}], 消息内容:[{msg}]")
def on_btn_clicked():
app.quit() # 退出应用
class thread(QThread):
def __init__(self):
super().__init__()
def run(self):
while True:
print("线程正在运行")
time.sleep(1)
if __name__ == "__main__":
app = QApplication(sys.argv)
qInstallMessageHandler(qt_message_handler) # Qt日志消息由qt_message_handlerr来处理
btn = QPushButton("按钮")
btn.clicked.connect(on_btn_clicked)
btn.show()
thread = thread() # 创建一个持续运行的线程对象
thread.start()
sys.exit(app.exec())
运行结果:
python
线程正在运行
线程正在运行
线程正在运行
线程正在运行
消息类型:[QtMsgType.QtFatalMsg], 消息内容:[QThread: Destroyed while thread is still running]
进程已结束,退出代码为 -1073740791 (0xC0000409)
上面的代码中,点击按钮连接了应用的退出函数(app.quit),应用的退出销毁了一个正在运行的线程,触发了一条QtMsgType.QtFatalMsg类型的日志消息。
在使用Pycharm调试程序时,经常会遇到这种"进程已结束,退出代码为 -1073740791 (0xC0000409)",莫名其妙就退出了,也没有提示退出原因,使用上面代码的方式就可以很方便地通过读取Qt日志消息来知道发生了什么。
- 修改后比较完善的代码:
python
import logging
import sys
from PySide6.QtCore import qInstallMessageHandler, QThread, QTimer
from PySide6.QtWidgets import QApplication, QPushButton
def qt_message_handler(msg_type, context, msg):
print(f"消息类型:[{msg_type}], 消息内容:[{msg}]")
def on_btn_clicked():
app.quit() # 退出应用
# 退出应用之前的处理函数
def about_quit():
work_thread.stop_flag = True
work_thread.quit()
work_thread.wait(3000)
work_thread.deleteLater()
class WorkThread(QThread):
def __init__(self):
super().__init__()
self.stop_flag = False
def run(self):
while not self.stop_flag:
logger.debug("线程正在运行")
QTimer.singleShot(0, lambda :None) # 空定时事件,防止线程陷入死循环,保持接受其他Qt事件的能力
QThread.msleep(1000)
logger.debug("线程已停止")
if __name__ == "__main__":
app = QApplication(sys.argv)
# 创建一个日志记录器
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG) # 设置日志级别为 DEBUG
logger.addHandler(logging.StreamHandler(sys.stdout)) # 将日志输出到控制台
# Qt日志消息由qt_message_handler来处理
qInstallMessageHandler(qt_message_handler)
btn = QPushButton("按钮")
btn.clicked.connect(on_btn_clicked)
btn.show()
# 创建一个持续运行的线程对象
work_thread = WorkThread()
work_thread.start()
# 连接应用退出之前的处理函数
app.aboutToQuit.connect(about_quit)
sys.exit(app.exec())
三、结合上面二者,构建日志管理系统
Qt的日志消息默认输出到控制台和命令行,不支持直接将日志写入文件等其他输出途径。logging是Python内置的日志模块,它无需额外安装,支持分级控制,支持将日志写入文件、按大小 / 时间分割日志文件、输出到网络等,但它不直接支持读取Qt格式的日志消息。可以使用qInstallMessageHandler将Qt的日志消息交由logging管理,将二者整合。
python
import sys
import logging
from time import asctime
from PySide6.QtCore import qInstallMessageHandler, QtMsgType, qWarning
from PySide6.QtWidgets import QApplication, QMainWindow, QWidget, QPushButton, QVBoxLayout
# 配置 Python logging 基础设置
logging.basicConfig(
level=logging.DEBUG, # 设置日志级别为 DEBUG
format="%(asctime)s - %(levelname)s - %(message)s", # 设置日志格式
handlers=[ # 设置日志处理器
logging.FileHandler("app_run.log", encoding="utf-8"), # 可选:输出到文件
logging.StreamHandler(sys.stdout) # 可选:输出到控制台
]
)
logger = logging.getLogger(__name__) # 获取当前模块的日志记录器
# 将 Qt 内部消息转发到 Python logging
def qt_to_python_log_handler(msg_type, context, msg):
"""把 Qt 消息转换成 Python logging 日志"""
if msg_type == QtMsgType.QtDebugMsg:
logger.debug(f"Qt: {msg}")
elif msg_type == QtMsgType.QtInfoMsg:
logger.info(f"Qt: {msg}")
elif msg_type == QtMsgType.QtWarningMsg:
logger.warning(f"Qt: {msg}")
elif msg_type == QtMsgType.QtCriticalMsg:
logger.error(f"Qt: {msg}")
elif msg_type == QtMsgType.QtFatalMsg:
logger.critical(f"Qt: {msg}")
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("日志整合示例")
# 业务日志(用 Python logging)
logger.info("程序启动,主窗口初始化")
btn1 = QPushButton("触发 Qt 警告", self)
# 触发 Qt 警告
btn1.clicked.connect(self.qt_warnning_slot)
btn2 = QPushButton("触发 Python 警告", self)
btn2.clicked.connect(self.python_warnning_slot)
layout = QVBoxLayout()
layout.addWidget(btn1)
layout.addWidget(btn2)
self.setLayout(layout)
def qt_warnning_slot(self):
qWarning("触发 Qt 警告")
def python_warnning_slot(self):
logger.warning("触发 Python 警告")
if __name__ == "__main__":
# 安装 Qt 消息处理器,转发到 Python logging
qInstallMessageHandler(qt_to_python_log_handler) # 安装之后,Qt内部的日志由qt_to_python_log_handler来处理
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())