Merge pull request #417 from vkbo/error_handling

Updated Error Handling
This commit is contained in:
Veronica K. Berglyd Olsen
2020-08-17 20:53:05 +02:00
committed by GitHub
2 changed files with 113 additions and 82 deletions
+3 -38
View File
@@ -280,45 +280,10 @@ def main(sysArgs=None):
nwApp.setWindowIcon(QIcon(CONFIG.appIcon)) nwApp.setWindowIcon(QIcon(CONFIG.appIcon))
nwApp.setOrganizationDomain(__domain__) nwApp.setOrganizationDomain(__domain__)
# We try to catch critical errors while setting up the main GUI # Connect the exception handler before making the main GUI
# by wrapping the main GUI in a try/except structure. This will
# not catch all exceptions for other parts of the application.
# For all other unhandled exceptions, we use a custom exception
# handler that pops a dialog box with the error message.
sys.excepthook = exceptionHandler sys.excepthook = exceptionHandler
try: nwGUI = GuiMain()
nwGUI = GuiMain() sys.exit(nwApp.exec_())
sys.exit(nwApp.exec_())
except Exception:
from traceback import print_tb
from nw.error import formatHtmlErrMsg
exType, exValue, exTrace = sys.exc_info()
logger.critical("%s: %s" % (exType.__name__, str(exValue)))
print_tb(exTrace)
try:
del nwApp
errApp = QApplication([])
errMsg = QErrorMessage()
errMsg.setWindowTitle("Critical Error")
errMsg.resize(800, 400)
errMsg.showMessage((
"<h3>A critical error has been encountered</h3>"
"%s"
"<p>Shutting down ...</p>"
) % formatHtmlErrMsg(exType, exValue, exTrace))
errApp.exec_()
except Exception as e:
logger.critical("Could not create error message dialog.")
logger.critical(str(e))
sys.exit(1)
return return
+110 -44
View File
@@ -25,56 +25,113 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
def formatHtmlErrMsg(exType, exValue, exTrace): from PyQt5.QtCore import Qt
"""Generates a HTML version of an exception. from PyQt5.QtWidgets import (
""" qApp, QDialog, QGridLayout, QStyle, QPlainTextEdit, QLabel,
try: QDialogButtonBox
)
class NWErrorMessage(QDialog):
def __init__(self, parent):
QDialog.__init__(self, parent=parent)
# Widgets
self.msgIcon = QLabel()
self.msgIcon.setPixmap(
qApp.style().standardIcon(QStyle.SP_MessageBoxCritical).pixmap(64, 64)
)
self.msgHead = QLabel()
self.msgHead.setOpenExternalLinks(True)
self.msgHead.setWordWrap(True)
self.msgBody = QPlainTextEdit()
self.msgBody.setReadOnly(True)
self.btnBox = QDialogButtonBox(QDialogButtonBox.Close)
self.btnBox.rejected.connect(self._doClose)
# Assemble
self.mainBox = QGridLayout()
self.mainBox.addWidget(self.msgIcon, 0, 0, 2, 1, Qt.AlignTop)
self.mainBox.addWidget(self.msgHead, 0, 1, 1, 1, Qt.AlignTop)
self.mainBox.addWidget(self.msgBody, 1, 1, 1, 1)
self.mainBox.addWidget(self.btnBox, 2, 0, 1, 2)
self.mainBox.setSpacing(16)
self.setLayout(self.mainBox)
self.setMinimumWidth(500)
self.setMinimumHeight(300)
self.setSizeGripEnabled(True)
self.resize(800, 400)
return
def setMessage(self, exType, exValue, exTrace):
"""Generate a message and append session data, error info and
error traceback.
"""
import sys import sys
from traceback import format_tb from traceback import format_tb
from nw import __issuesurl__, __version__ from nw import __issuesurl__, __version__
from PyQt5.Qt import PYQT_VERSION_STR from PyQt5.Qt import PYQT_VERSION_STR
from PyQt5.QtCore import QT_VERSION_STR, QSysInfo from PyQt5.QtCore import QT_VERSION_STR, QSysInfo
fmtTrace = "" self.msgHead.setText((
for trEntry in format_tb(exTrace): "<p>An unhandled error has been encountered.</p>"
for trLine in trEntry.split("\n"): "<p>Please report this error by submitting an issue report on "
stripLine = trLine.lstrip(" ") "GitHub, providing a description and including the error "
nIndent = len(trLine) - len(stripLine) "message and traceback shown below.</p>"
fmtTrace += "&nbsp;"*nIndent + stripLine + "<br>" "<p>URL: <a href='{issueUrl}'>{issueUrl}</a></p>"
).format(
issueUrl = __issuesurl__,
))
try: try:
kernelVersion = QSysInfo.kernelVersion() kernelVersion = QSysInfo.kernelVersion()
except Exception: except Exception:
kernelVersion = "Unknown" kernelVersion = "Unknown"
theMessage = ( try:
"<p>Please report this error by submitting an issue report on " self.msgBody.setPlainText((
"GitHub, providing a description and this error message. " "Environment:\n"
"URL: &lt;{issueUrl}&gt;.</p>" "novelWriter Version: {nwVersion}\n"
"<p><b>Environment</b><br>Version: {nwVersion}, OS: {osType} ({osKernel}), " "Host OS: {osType} ({osKernel})\n"
"Python: {pyVersion} ({pyHexVer:#x}), Qt: {qtVers}, PyQt: {pyqtVers}</p>" "Python: {pyVersion} ({pyHexVer:#x})\n"
"<p><b>Error Type</b><br>{exType}: {exMessage}</p>" "Qt: {qtVers}, PyQt: {pyqtVers}\n"
"<p><b>Traceback</b><br>{exTrace}</p>" "\n"
).format( "{exType}:\n{exMessage}\n"
nwVersion = __version__, "\n"
osType = sys.platform, "Traceback:\n{exTrace}\n"
osKernel = kernelVersion, ).format(
pyVersion = sys.version.split()[0], nwVersion = __version__,
pyHexVer = sys.hexversion, osType = sys.platform,
qtVers = QT_VERSION_STR, osKernel = kernelVersion,
pyqtVers = PYQT_VERSION_STR, pyVersion = sys.version.split()[0],
issueUrl = __issuesurl__, pyHexVer = sys.hexversion,
exType = exType.__name__, qtVers = QT_VERSION_STR,
exMessage = str(exValue), pyqtVers = PYQT_VERSION_STR,
exTrace = fmtTrace exType = exType.__name__,
) exMessage = str(exValue),
exTrace = "\n".join(format_tb(exTrace)),
))
except Exception:
self.msgBody.setPlainText("Failed to generate error report ...")
return theMessage return
except Exception as e: ##
return "Could not generate error message.<br>%s" % str(e) # Slots
##
return "Could not generate error message." def _doClose(self):
"""Close the dialog.
"""
self.close()
return
# END Class NWErrorMessage
def exceptionHandler(exType, exValue, exTrace): def exceptionHandler(exType, exValue, exTrace):
@@ -83,10 +140,10 @@ def exceptionHandler(exType, exValue, exTrace):
import logging import logging
from traceback import print_tb from traceback import print_tb
from nw import CONFIG from nw import CONFIG
from PyQt5.QtWidgets import qApp, QErrorMessage from PyQt5.QtWidgets import qApp
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
logger.error("%s: %s" % (exType.__name__, str(exValue))) logger.critical("%s: %s" % (exType.__name__, str(exValue)))
print_tb(exTrace) print_tb(exTrace)
if not CONFIG.showGUI: if not CONFIG.showGUI:
@@ -103,14 +160,23 @@ def exceptionHandler(exType, exValue, exTrace):
logger.warning("Could not find main GUI window so cannot open error dialog") logger.warning("Could not find main GUI window so cannot open error dialog")
return return
errMsg = QErrorMessage(nwGUI) errMsg = NWErrorMessage(nwGUI)
errMsg.setWindowTitle("Unhandled Error") errMsg.setMessage(exType, exValue, exTrace)
errMsg.resize(800, 400) errMsg.exec_()
errMsg.showMessage((
"<h3>An unhandled error has been encountered</h3>%s" try:
) % formatHtmlErrMsg(exType, exValue, exTrace)) # Try a controlled shudown
nwGUI.closeProject(isYes=True)
nwGUI.closeMain()
logger.info("Emergency shutdown successful")
except Exception as e:
logger.critical("Could not close the project before exiting")
logger.critical(str(e))
qApp.exit(1)
except Exception as e: except Exception as e:
logger.error(str(e)) logger.critical(str(e))
return return