From 0140d0d8a088092b8798b17b38f4e57c08257ea8 Mon Sep 17 00:00:00 2001 From: Paul Kienzle Date: Mon, 1 Jun 2026 22:32:13 -0400 Subject: [PATCH] preserve multiline log messages --- src/sas/qtgui/MainWindow/GuiManager.py | 27 ++++++++++--------- src/sas/qtgui/MainWindow/MainWindow.py | 7 +++++ .../Perspectives/Fitting/FittingWidget.py | 6 ----- .../TabbedEditor/TabbedModelEditor.py | 4 +-- src/sas/qtgui/Utilities/SasviewLogger.py | 9 ++++++- 5 files changed, 31 insertions(+), 22 deletions(-) diff --git a/src/sas/qtgui/MainWindow/GuiManager.py b/src/sas/qtgui/MainWindow/GuiManager.py index e3d787116e..f94d7b6cb5 100644 --- a/src/sas/qtgui/MainWindow/GuiManager.py +++ b/src/sas/qtgui/MainWindow/GuiManager.py @@ -6,7 +6,7 @@ from packaging.version import Version from PySide6.QtCore import QLocale, Qt -from PySide6.QtGui import QStandardItem +from PySide6.QtGui import QStandardItem, QTextCursor from PySide6.QtWidgets import QDockWidget, QLabel, QMessageBox, QProgressBar, QTextBrowser from twisted.internet import reactor @@ -80,9 +80,6 @@ def __init__(self, parent=None): # Decide on a locale QLocale.setDefault(QLocale('en_US')) - # Redefine exception hook to not explicitly crash the app. - sys.excepthook = self.info - # Ensure the user directory has all required layout and files create_user_files_if_needed() @@ -124,9 +121,6 @@ def __init__(self, parent=None): if self.WhatsNew.has_new_messages(): # Not a static method self.WhatsNew.show() - def info(self, type, value, tb): - logger.error("".join(traceback.format_exception(type, value, tb))) - def addWidgets(self): """ Populate the main window with widgets @@ -139,6 +133,8 @@ def addWidgets(self): self.logDockWidget.setVisible(False) self.listWidget = QTextBrowser() + # We could put error log styling here: + # self.listWidget.setStyleSheet("QTextBrowser { line-height: 1.0; }") self.logDockWidget.setWidget(self.listWidget) self._workspace.addDockWidget(Qt.BottomDockWidgetArea, self.logDockWidget) @@ -244,7 +240,6 @@ def addCategories(): model_list = ModelManager().cat_model_list() CategoryInstaller.check_install(model_list=model_list) except Exception: - import traceback logger.error("Category manager: could not load SasView models") logger.error(traceback.format_exc()) @@ -500,7 +495,18 @@ def appendLog(self, signal): """Appends a message to the list widget in the Log Explorer. Use this instead of listWidget.insertPlainText() to facilitate auto-scrolling""" (message, record) = signal - self.listWidget.append(message.strip()) + + # Move cursor to the end of text and append the next log message; + # Don't use append() to add the block since it messes up the formatting + # we do is SasviewLogger. Instead put a
between every entry. + # Scroll the text so that it is visible. + cursor = self.listWidget.textCursor() + cursor.movePosition(QTextCursor.End) + cursor.insertHtml(f"\n
\n{message.strip()}") + self.listWidget.ensureCursorVisible() + + # Show the mess inside the log widget. Qt is doing too much! + # print("\n\n===log contains: ", self.listWidget.toHtml()) # Display log if message is warning (30) or higher # 10: Debug @@ -623,9 +629,6 @@ def actionWhatsNew(self): def showWelcomeMessage(self): """ Show the Welcome panel, when required """ - # Assure the welcome screen is requested - show_welcome_widget = True - if config.SHOW_WELCOME_PANEL: self.actionWelcome() diff --git a/src/sas/qtgui/MainWindow/MainWindow.py b/src/sas/qtgui/MainWindow/MainWindow.py index 7a49aa5ff8..48cda366c3 100644 --- a/src/sas/qtgui/MainWindow/MainWindow.py +++ b/src/sas/qtgui/MainWindow/MainWindow.py @@ -1,5 +1,6 @@ import logging import sys +import traceback from importlib import resources from PySide6.QtCore import Qt, QTimer @@ -16,6 +17,9 @@ from .UI.MainWindowUI import Ui_SasView logger = logging.getLogger(__name__) +def log_uncaught_exception(type, value, tb): + formatted_tb = "".join(traceback.format_exception(type, value, tb)) + logger.error(f"--- Uncaught exception ---\n{formatted_tb.strip()}") class MainSasViewWindow(QMainWindow, Ui_SasView): # Main window of the application @@ -113,6 +117,9 @@ def run_sasview(file_list: list[str] | None = None): # Show the main SV window mainwindow = MainSasViewWindow() + # Redirect uncaught exceptions to logger + sys.excepthook = log_uncaught_exception + # no more splash screen splash.finish(mainwindow) diff --git a/src/sas/qtgui/Perspectives/Fitting/FittingWidget.py b/src/sas/qtgui/Perspectives/Fitting/FittingWidget.py index a09f7c0a96..d91c215b67 100644 --- a/src/sas/qtgui/Perspectives/Fitting/FittingWidget.py +++ b/src/sas/qtgui/Perspectives/Fitting/FittingWidget.py @@ -102,9 +102,6 @@ def __init__(self, parent: QtWidgets.QWidget | None = None, data: Any | None = N # Which tab is this widget displayed in? self.tab_id = tab_id - import sys - sys.excepthook = self.info - # Globals self.initializeGlobals() @@ -189,9 +186,6 @@ def __init__(self, parent: QtWidgets.QWidget | None = None, data: Any | None = N self.label_17.setStyleSheet(new_font) self.label_19.setStyleSheet(new_font) - def info(self, type: Any, value: Any, tb: Any) -> None: - logger.error("".join(traceback.format_exception(type, value, tb))) - @property def logic(self) -> FittingLogic: # make sure the logic contains at least one element diff --git a/src/sas/qtgui/Utilities/ModelEditors/TabbedEditor/TabbedModelEditor.py b/src/sas/qtgui/Utilities/ModelEditors/TabbedEditor/TabbedModelEditor.py index 615c2d3bc2..498ce0cb8f 100644 --- a/src/sas/qtgui/Utilities/ModelEditors/TabbedEditor/TabbedModelEditor.py +++ b/src/sas/qtgui/Utilities/ModelEditors/TabbedEditor/TabbedModelEditor.py @@ -466,10 +466,8 @@ def findFirstError(self, full_path: str | Path) -> int: logger.error(msg) # print four last lines of the stack trace # this will point out the exact line failing - all_lines = traceback.format_exc().split("\n") + all_lines = traceback.format_exc().strip().split("\n") last_lines = all_lines[-4:] - traceback_to_show = "\n".join(last_lines) - logger.error(traceback_to_show) # Set the status bar message # GuiUtils.communicator.statusBarUpdateSignal.emit("Model check failed") diff --git a/src/sas/qtgui/Utilities/SasviewLogger.py b/src/sas/qtgui/Utilities/SasviewLogger.py index 1a9a36a355..a899b561d4 100644 --- a/src/sas/qtgui/Utilities/SasviewLogger.py +++ b/src/sas/qtgui/Utilities/SasviewLogger.py @@ -16,7 +16,14 @@ def format(self, record): if record.levelname in self.LOG_COLORS: level_style = f' style="color: {self.LOG_COLORS[record.levelname]}"' - return logging.Formatter(fmt=self.LOG_FORMAT.format(level_style), datefmt=self.DATE_FORMAT).format(record) + # Format the timestamp, etc. + msg = logging.Formatter(fmt=self.LOG_FORMAT.format(level_style), datefmt=self.DATE_FORMAT).format(record) + + # If it is a multiline error message then put the additional lines in a
 block
+        if "\n" in msg:
+            lead, tail = msg.split("\n", 1)
+            msg = f"{lead}\n
{tail}
" + return msg class QtPostman(QObject): messageWritten = Signal(object)