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.setOrganizationDomain(__domain__)
# We try to catch critical errors while setting up 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.
# Connect the exception handler before making the main GUI
sys.excepthook = exceptionHandler
try:
nwGUI = GuiMain()
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)
nwGUI = GuiMain()
sys.exit(nwApp.exec_())
return
+110 -44
View File
@@ -25,56 +25,113 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
def formatHtmlErrMsg(exType, exValue, exTrace):
"""Generates a HTML version of an exception.
"""
try:
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import (
qApp, QDialog, QGridLayout, QStyle, QPlainTextEdit, QLabel,
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
from traceback import format_tb
from nw import __issuesurl__, __version__
from PyQt5.Qt import PYQT_VERSION_STR
from PyQt5.QtCore import QT_VERSION_STR, QSysInfo
fmtTrace = ""
for trEntry in format_tb(exTrace):
for trLine in trEntry.split("\n"):
stripLine = trLine.lstrip(" ")
nIndent = len(trLine) - len(stripLine)
fmtTrace += "&nbsp;"*nIndent + stripLine + "<br>"
self.msgHead.setText((
"<p>An unhandled error has been encountered.</p>"
"<p>Please report this error by submitting an issue report on "
"GitHub, providing a description and including the error "
"message and traceback shown below.</p>"
"<p>URL: <a href='{issueUrl}'>{issueUrl}</a></p>"
).format(
issueUrl = __issuesurl__,
))
try:
kernelVersion = QSysInfo.kernelVersion()
except Exception:
kernelVersion = "Unknown"
theMessage = (
"<p>Please report this error by submitting an issue report on "
"GitHub, providing a description and this error message. "
"URL: &lt;{issueUrl}&gt;.</p>"
"<p><b>Environment</b><br>Version: {nwVersion}, OS: {osType} ({osKernel}), "
"Python: {pyVersion} ({pyHexVer:#x}), Qt: {qtVers}, PyQt: {pyqtVers}</p>"
"<p><b>Error Type</b><br>{exType}: {exMessage}</p>"
"<p><b>Traceback</b><br>{exTrace}</p>"
).format(
nwVersion = __version__,
osType = sys.platform,
osKernel = kernelVersion,
pyVersion = sys.version.split()[0],
pyHexVer = sys.hexversion,
qtVers = QT_VERSION_STR,
pyqtVers = PYQT_VERSION_STR,
issueUrl = __issuesurl__,
exType = exType.__name__,
exMessage = str(exValue),
exTrace = fmtTrace
)
try:
self.msgBody.setPlainText((
"Environment:\n"
"novelWriter Version: {nwVersion}\n"
"Host OS: {osType} ({osKernel})\n"
"Python: {pyVersion} ({pyHexVer:#x})\n"
"Qt: {qtVers}, PyQt: {pyqtVers}\n"
"\n"
"{exType}:\n{exMessage}\n"
"\n"
"Traceback:\n{exTrace}\n"
).format(
nwVersion = __version__,
osType = sys.platform,
osKernel = kernelVersion,
pyVersion = sys.version.split()[0],
pyHexVer = sys.hexversion,
qtVers = QT_VERSION_STR,
pyqtVers = PYQT_VERSION_STR,
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):
@@ -83,10 +140,10 @@ def exceptionHandler(exType, exValue, exTrace):
import logging
from traceback import print_tb
from nw import CONFIG
from PyQt5.QtWidgets import qApp, QErrorMessage
from PyQt5.QtWidgets import qApp
logger = logging.getLogger(__name__)
logger.error("%s: %s" % (exType.__name__, str(exValue)))
logger.critical("%s: %s" % (exType.__name__, str(exValue)))
print_tb(exTrace)
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")
return
errMsg = QErrorMessage(nwGUI)
errMsg.setWindowTitle("Unhandled Error")
errMsg.resize(800, 400)
errMsg.showMessage((
"<h3>An unhandled error has been encountered</h3>%s"
) % formatHtmlErrMsg(exType, exValue, exTrace))
errMsg = NWErrorMessage(nwGUI)
errMsg.setMessage(exType, exValue, exTrace)
errMsg.exec_()
try:
# 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:
logger.error(str(e))
logger.critical(str(e))
return