PySide6 GUI中用RichHandler+QTextEdit显示HTML日志遇间距对齐问题
问题:PySide6 QTextEdit结合RichHandler显示日志行间距过大、对齐异常
我在PySide6的GUI中用自定义QTextEditLogger类(继承QTextEdit和logging.Handler)展示应用日志,纯文本插入时显示正常,但用rich.RichHandler格式化彩色日志并以HTML插入时,颜色正常但行间距过大、首行缩进异常(已关闭自动换行)。
纯文本正常代码:
class QTextEditLogger(QTextEdit, logging.Handler): def __init__(self, parent, level=NOTSET): QTextEdit.__init__(self, parent) logging.Handler.__init(self, level=level) def emit(self, record): msg = self.format(record) self.append(msg)
最小可复现代码:
import sys import os from time import sleep import logging from logging import Handler, NOTSET from rich.logging import RichHandler from rich.console import Console from PySide6.QtWidgets import QTextEdit, QApplication, QMainWindow from PySide6.QtGui import QTextCursor, QTextOption class QTextEditLogger(QTextEdit, Handler): """A QTextEdit logger that uses RichHandler to format log messages.""" def __init__(self, parent=None, level=NOTSET): QTextEdit.__init__(self, parent) Handler.__init__(self,level=level) self.console = Console(file=open(os.devnull, "wt"), record=True,width=42, height=12, soft_wrap=False) self.rich_handler = RichHandler(show_time=False, show_path=False, show_level=True, markup=True, console=self.console, log_time_format="[%X]", level=self.level) self.rich_handler.setLevel(self.level) QTextEdit.setWordWrapMode(self, QTextOption.WrapMode.NoWrap) self.setAcceptRichText(True) self.setReadOnly(True) def showEvent(self, arg__1): self.console.width = self.width()//self.fontMetrics().averageCharWidth() # Approximate character width self.console.height = self.height()//self.fontMetrics().height() # Approximate character height return super().showEvent(arg__1) def emit(self, record) -> None: """Override the emit method to handle log records.""" self.rich_handler.emit(record) html = self.console.export_html(clear=True, inline_styles=True) self.insertHtml(html) self.verticalScrollBar().setSliderPosition(self.verticalScrollBar().maximum()) c = self.textCursor() c.movePosition(QTextCursor.End) self.setTextCursor(c) class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("QTextEdit Example") # Create a QTextEdit widget self.text_edit = QTextEditLogger(self) self.setCentralWidget(self.text_edit) if __name__ == "__main__": app = QApplication(sys.argv) window = MainWindow() window.show() # Set up logging logger = logging.getLogger() logger.setLevel(logging.DEBUG) logger.addHandler(window.text_edit) logger.info("This is an info message.") sleep(.5) logger.warning("This is a warning message.") sleep(.5) for i in range(10): logger.debug(f"This is a debug message {i}.") logger.error("This is an error message.") sys.exit(app.exec_())
解决方案
问题根源是console.export_html()会输出包含完整HTML文档结构(含<body>、<p>标签)的内容,每次调用insertHtml()插入这些完整结构,会导致QTextEdit解析时产生额外的边距和换行,进而出现行间距过大、首行缩进异常。
修改思路:提取日志内容的核心HTML片段,去掉冗余的外层标签,手动添加换行。
完整修改后的代码
import sys import os from time import sleep import logging from logging import Handler, NOTSET from rich.logging import RichHandler from rich.console import Console from PySide6.QtWidgets import QTextEdit, QApplication, QMainWindow from PySide6.QtGui import QTextCursor, QTextOption class QTextEditLogger(QTextEdit, Handler): """A QTextEdit logger that uses RichHandler to format log messages.""" def __init__(self, parent=None, level=NOTSET): QTextEdit.__init__(self, parent) Handler.__init__(self,level=level) self.console = Console(file=open(os.devnull, "wt"), record=True,width=42, height=12, soft_wrap=False) self.rich_handler = RichHandler(show_time=False, show_path=False, show_level=True, markup=True, console=self.console, log_time_format="[%X]", level=self.level) self.rich_handler.setLevel(self.level) QTextEdit.setWordWrapMode(self, QTextOption.WrapMode.NoWrap) self.setAcceptRichText(True) self.setReadOnly(True) def showEvent(self, arg__1): self.console.width = self.width()//self.fontMetrics().averageCharWidth() # Approximate character width self.console.height = self.height()//self.fontMetrics().height() # Approximate character height return super().showEvent(arg__1) def emit(self, record) -> None: """Override the emit method to handle log records.""" self.rich_handler.emit(record) html = self.console.export_html(clear=True, inline_styles=True) # 提取body内的核心内容,去除外层p标签 start_idx = html.find('<body>') + len('<body>') end_idx = html.find('</body>') core_content = html[start_idx:end_idx].strip() core_content = core_content.replace('<p>', '').replace('</p>', '') # 插入处理后的HTML,手动添加换行 self.insertHtml(core_content + '<br>') # 滚动到底部 self.verticalScrollBar().setSliderPosition(self.verticalScrollBar().maximum()) c = self.textCursor() c.movePosition(QTextCursor.End) self.setTextCursor(c) class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("QTextEdit Example") # Create a QTextEdit widget self.text_edit = QTextEditLogger(self) self.setCentralWidget(self.text_edit) if __name__ == "__main__": app = QApplication(sys.argv) window = MainWindow() window.show() # Set up logging logger = logging.getLogger() logger.setLevel(logging.DEBUG) logger.addHandler(window.text_edit) logger.info("This is an info message.") sleep(.5) logger.warning("This is a warning message.") sleep(.5) for i in range(10): logger.debug(f"This is a debug message {i}.") logger.error("This is an error message.") sys.exit(app.exec_())
说明
- 通过提取
<body>标签内的内容,去掉默认包裹日志的<p>标签,避免了QTextEdit解析时产生的默认边距和缩进。 - 手动添加
<br>标签实现日志行的换行,保证行间距与纯文本模式一致。 - 保留了RichHandler的彩色格式化效果,同时解决了布局异常问题。
内容的提问来源于stack exchange,提问作者Raphael




