From b1be2eee0604f765d4c1b04a1091ce853e8a7684 Mon Sep 17 00:00:00 2001 From: Ebag333 Date: Sat, 18 Mar 2017 12:37:23 -0700 Subject: [PATCH] Add unhandled exception handler. Now catches problems and will try and output to the pyfalog, falling back to outputting to the console. --- gui/errorDialog.py | 4 +- pyfa.py | 334 +++++++++++++++++++++++---------------------- 2 files changed, 173 insertions(+), 165 deletions(-) diff --git a/gui/errorDialog.py b/gui/errorDialog.py index 6574163ca4..98e0fc1bcb 100644 --- a/gui/errorDialog.py +++ b/gui/errorDialog.py @@ -90,7 +90,9 @@ def __init__(self, exception, tb): errorTextCtrl.AppendText('\n') errorTextCtrl.AppendText("fs encoding: " + str(sys.getfilesystemencoding() or "Unknown")) errorTextCtrl.AppendText('\n\n') - errorTextCtrl.AppendText(tb) + if tb: + for line in tb: + errorTextCtrl.AppendText(line) errorTextCtrl.Layout() self.SetSizer(mainSizer) diff --git a/pyfa.py b/pyfa.py index 35ea277641..1c5607528f 100755 --- a/pyfa.py +++ b/pyfa.py @@ -18,22 +18,16 @@ # along with pyfa. If not, see . # ============================================================================== -import sys import os -import os.path import re -import config +import sys import traceback +import warnings +from optparse import AmbiguousOptionError, BadOptionError, OptionParser -from optparse import OptionParser, BadOptionError, AmbiguousOptionError +from logbook import CRITICAL, DEBUG, ERROR, FingersCrossedHandler, INFO, Logger, NestedSetup, NullHandler, StreamHandler, TimedRotatingFileHandler, WARNING -# Import everything -# noinspection PyPackageRequirements -import os -import os.path - -from logbook import TimedRotatingFileHandler, Logger, StreamHandler, NestedSetup, FingersCrossedHandler, NullHandler, \ - CRITICAL, ERROR, WARNING, DEBUG, INFO +import config try: import wxversion @@ -88,6 +82,36 @@ def flush(self): self.level(sys.stderr) +def handleGUIException(exc_type, exc_value, exc_traceback): + tb = traceback.format_tb(exc_traceback) + + try: + # Try and output to our log handler + with logging_setup.threadbound(): + pyfalog.critical("Exception in main thread: {0}", exc_value.message) + # Print the base level traceback + traceback.print_tb(exc_traceback) + + pyfa = wx.App(False) + ErrorFrame(exc_value, tb) + pyfa.MainLoop() + except: + # Most likely logging isn't available. Try and output to the console + print("Exception in main thread: " + str(exc_value.message)) + traceback.print_tb(exc_traceback) + + pyfa = wx.App(False) + ErrorFrame(exc_value, tb) + pyfa.MainLoop() + + finally: + # TODO: Add cleanup when exiting here. + sys.exit() + + +# Replace the uncaught exception handler with our own handler. +sys.excepthook = handleGUIException + # Parse command line options usage = "usage: %prog [--root]" parser = PassThroughOptionParser(usage=usage) @@ -114,186 +138,168 @@ def flush(self): options.logginglevel = ERROR if __name__ == "__main__": - try: - # Configure paths - if options.rootsavedata is True: - config.saveInRoot = True + # Configure paths + if options.rootsavedata is True: + config.saveInRoot = True - # set title if it wasn't supplied by argument - if options.title is None: - options.title = "pyfa %s%s - Python Fitting Assistant" % (config.version, "" if config.tag.lower() != 'git' else " (git)") + # set title if it wasn't supplied by argument + if options.title is None: + options.title = "pyfa %s%s - Python Fitting Assistant" % (config.version, "" if config.tag.lower() != 'git' else " (git)") - config.debug = options.debug + config.debug = options.debug - # convert to unicode if it is set - if options.savepath is not None: - options.savepath = unicode(options.savepath) - config.defPaths(options.savepath) + # convert to unicode if it is set + if options.savepath is not None: + options.savepath = unicode(options.savepath) + config.defPaths(options.savepath) - # Basic logging initialization + # Basic logging initialization - # Logging levels: - ''' - logbook.CRITICAL - logbook.ERROR - logbook.WARNING - logbook.INFO - logbook.DEBUG - logbook.NOTSET - ''' + # Logging levels: + ''' + logbook.CRITICAL + logbook.ERROR + logbook.WARNING + logbook.INFO + logbook.DEBUG + logbook.NOTSET + ''' - if options.debug: - savePath_filename = "Pyfa_debug.log" - else: - savePath_filename = "Pyfa.log" + if options.debug: + savePath_filename = "Pyfa_debug.log" + else: + savePath_filename = "Pyfa.log" - savePath_Destination = os.path.join(config.savePath, savePath_filename) + savePath_Destination = os.path.join(config.savePath, savePath_filename) - try: - if options.debug: - logging_mode = "Debug" - logging_setup = NestedSetup([ - # make sure we never bubble up to the stderr handler - # if we run out of setup handling - NullHandler(), - StreamHandler( - sys.stdout, - bubble=False, - level=options.logginglevel - ), - TimedRotatingFileHandler( - savePath_Destination, - level=0, - backup_count=3, - bubble=True, - date_format='%Y-%m-%d', - ), - ]) - else: - logging_mode = "User" - logging_setup = NestedSetup([ - # make sure we never bubble up to the stderr handler - # if we run out of setup handling - NullHandler(), - FingersCrossedHandler( - TimedRotatingFileHandler( - savePath_Destination, - level=0, - backup_count=3, - bubble=False, - date_format='%Y-%m-%d', - ), - action_level=ERROR, - buffer_size=1000, - # pull_information=True, - # reset=False, - ) - ]) - except: - print("Critical error attempting to setup logging. Falling back to console only.") - logging_mode = "Console Only" + try: + if options.debug: + logging_mode = "Debug" logging_setup = NestedSetup([ # make sure we never bubble up to the stderr handler # if we run out of setup handling NullHandler(), StreamHandler( sys.stdout, - bubble=False + bubble=False, + level=options.logginglevel + ), + TimedRotatingFileHandler( + savePath_Destination, + level=0, + backup_count=3, + bubble=True, + date_format='%Y-%m-%d', + ), + ]) + else: + logging_mode = "User" + logging_setup = NestedSetup([ + # make sure we never bubble up to the stderr handler + # if we run out of setup handling + NullHandler(), + FingersCrossedHandler( + TimedRotatingFileHandler( + savePath_Destination, + level=0, + backup_count=3, + bubble=False, + date_format='%Y-%m-%d', + ), + action_level=ERROR, + buffer_size=1000, + # pull_information=True, + # reset=False, ) ]) + except: + print("Critical error attempting to setup logging. Falling back to console only.") + logging_mode = "Console Only" + logging_setup = NestedSetup([ + # make sure we never bubble up to the stderr handler + # if we run out of setup handling + NullHandler(), + StreamHandler( + sys.stdout, + bubble=False + ) + ]) - except Exception, e: - tb = traceback.format_exc() + with logging_setup.threadbound(): + pyfalog.info("Starting Pyfa") - pyfa = wx.App(False) - ErrorFrame(e, tb) - pyfa.MainLoop() - raise - sys.exit() + pyfalog.info("Running in logging mode: {0}", logging_mode) + pyfalog.info("Writing log file to: {0}", savePath_Destination) - with logging_setup.threadbound(): + # Output all stdout (print) messages as warnings try: - pyfalog.info("Starting Pyfa") - - # Don't redirect if frozen - if not hasattr(sys, 'frozen'): - # Output all stdout (print) messages as warnings - try: - sys.stdout = LoggerWriter(pyfalog.warning) - except ValueError, Exception: - pyfalog.critical("Cannot access log file. Continuing without writing stdout to log.") - - if not options.debug: - # Output all stderr (stacktrace) messages as critical - try: - sys.stderr = LoggerWriter(pyfalog.critical) - except ValueError, Exception: - pyfalog.critical("Cannot access log file. Continuing without writing stderr to log.") - - if sys.version_info < (2, 6) or sys.version_info > (3, 0): - exit_message = "\nPyfa requires python 2.x branch ( >= 2.6 )\nExiting." - pyfalog.critical(exit_message) - raise Exception(exit_message) - - if wx is None or wxversion is None: - exit_message = "\nCannot find wxPython\nYou can download wxPython (2.8+) from http://www.wxpython.org/" - pyfalog.critical(exit_message) - raise Exception(exit_message) - else: - if options.force28 is True and wxversion.checkInstalled('2.8'): - pyfalog.info("wxPython is installed. Version: {0} (forced).", wxversion.getInstalled()) - elif options.force28 is not True and (wxversion.checkInstalled('2.8') or wxversion.checkInstalled('3.0')): - pyfalog.info("wxPython is installed. Version: {0}.", wxversion.getInstalled()) - else: - exit_message = "\nInstalled wxPython version doesn't meet requirements.\nYou can download wxPython 2.8 or 3.0 from http://www.wxpython.org/" - pyfalog.critical(exit_message) - raise Exception(exit_message) - - if sqlalchemy is None: - exit_message = "\nCannot find sqlalchemy.\nYou can download sqlalchemy (0.6+) from http://www.sqlalchemy.org/" - pyfalog.critical(exit_message) - raise Exception(exit_message) - else: - saVersion = sqlalchemy.__version__ - saMatch = re.match("([0-9]+).([0-9]+)([b\.])([0-9]+)", saVersion) - if saMatch: - saMajor = int(saMatch.group(1)) - saMinor = int(saMatch.group(2)) - betaFlag = True if saMatch.group(3) == "b" else False - saBuild = int(saMatch.group(4)) if not betaFlag else 0 - if saMajor == 0 and (saMinor < 5 or (saMinor == 5 and saBuild < 8)): - pyfalog.critical("Pyfa requires sqlalchemy 0.5.8 at least but current sqlalchemy version is {0}", format(sqlalchemy.__version__)) - pyfalog.critical("You can download sqlalchemy (0.5.8+) from http://www.sqlalchemy.org/") - sys.exit(1) - else: - pyfalog.warning("Unknown sqlalchemy version string format, skipping check") + sys.stdout = LoggerWriter(pyfalog.warning) + except ValueError, Exception: + pyfalog.critical("Cannot access log file. Continuing without writing stdout to log.") + + # Output all stderr (stacktrace) messages as critical + try: + sys.stderr = LoggerWriter(pyfalog.critical) + except ValueError, Exception: + pyfalog.critical("Cannot access log file. Continuing without writing stderr to log.") - import eos.db - # noinspection PyUnresolvedReferences - import service.prefetch # noqa: F401 + if sys.version_info < (2, 6) or sys.version_info > (3, 0): + exit_message = "\nPyfa requires python 2.x branch ( >= 2.6 )\nExiting." + pyfalog.critical(exit_message) + raise Exception(exit_message) - # Make sure the saveddata db exists - if not os.path.exists(config.savePath): - os.mkdir(config.savePath) + if hasattr(sys, 'frozen'): + pyfalog.info("Running in a frozen state.") + else: + pyfalog.info("Running in a thawed state.") + + if hasattr(sys, 'frozen') and wx is not None: + pyfalog.info("Running in frozen state with wx installed. Skipping wx validation.") + elif wx is None or wxversion is None: + exit_message = "\nCannot find wxPython\nYou can download wxPython (2.8+) from http://www.wxpython.org/" + pyfalog.critical(exit_message) + raise Exception(exit_message) + else: + if options.force28 is True and wxversion.checkInstalled('2.8'): + pyfalog.info("wxPython is installed. Version: {0} (forced).", wxversion.getInstalled()) + elif options.force28 is not True and (wxversion.checkInstalled('2.8') or wxversion.checkInstalled('3.0')): + pyfalog.info("wxPython is installed. Version: {0}.", wxversion.getInstalled()) + else: + pyfalog.warning("\nInstalled wxPython version doesn't meet requirements.\nYou can download wxPython 2.8 or 3.0 from http://www.wxpython.org/") + pyfalog.critical("Attempting to run with unsupported version of wx. Version: {0}", wxversion.getInstalled()) - eos.db.saveddata_meta.create_all() + if sqlalchemy is None: + exit_message = "\nCannot find sqlalchemy.\nYou can download sqlalchemy (0.6+) from http://www.sqlalchemy.org/" + pyfalog.critical(exit_message) + raise Exception(exit_message) + else: + saVersion = sqlalchemy.__version__ + saMatch = re.match("([0-9]+).([0-9]+)([b\.])([0-9]+)", saVersion) + if saMatch: + saMajor = int(saMatch.group(1)) + saMinor = int(saMatch.group(2)) + betaFlag = True if saMatch.group(3) == "b" else False + saBuild = int(saMatch.group(4)) if not betaFlag else 0 + if saMajor == 0 and (saMinor < 5 or (saMinor == 5 and saBuild < 8)): + pyfalog.critical("Pyfa requires sqlalchemy 0.5.8 at least but current sqlAlchemy version is {0}", format(sqlalchemy.__version__)) + pyfalog.critical("You can download sqlAlchemy (0.5.8+) from http://www.sqlalchemy.org/") + pyfalog.critical("Attempting to run with unsupported version of sqlAlchemy.") + else: + pyfalog.info("Current version of sqlAlchemy is: {0}", sqlalchemy.__version__) + else: + pyfalog.warning("Unknown sqlalchemy version string format, skipping check. Version: {0}", sqlalchemy.__version__) - pyfalog.info("Running in logging mode: {0}", logging_mode) + import eos.db + # noinspection PyUnresolvedReferences + import service.prefetch # noqa: F401 - if hasattr(sys, 'frozen') and options.debug: - pyfalog.critical("Running in frozen mode with debug turned on. Forcing all output to be written to log.") + # Make sure the saveddata db exists + if not os.path.exists(config.savePath): + os.mkdir(config.savePath) - from gui.mainFrame import MainFrame + eos.db.saveddata_meta.create_all() - except Exception as e: - tb = traceback.format_exc() - pyfa = wx.App(False) - ErrorFrame(e, tb) - pyfa.MainLoop() - pyfalog.critical("Exception in main thread.") - pyfalog.critical(tb) - raise - sys.exit() + from gui.mainFrame import MainFrame pyfa = wx.App(False) MainFrame(options.title)