Search code examples
pythonpython-3.xexceptionloggingpyqt4

Grab any exception in PyQt


I've created an GUI app in PyQt, which I want to share with many people. Sometimes I get unexpected exceptions and I take it for granted - after every exception I improve my code and it gets better and better.

I use a logger for recording these exceptions and special hook for PyQt silenced exceptions. My code looks like this:

Logger

def setLogger(level=logging.DEBUG,
              name="my_logger",
              file=join("src", "log.out")):

    logger = logging.getLogger(name)
    logger.setLevel(level)

    # console logger
    ch = logging.StreamHandler()
    console_lvl = logging.DEBUG
    ch.setLevel(console_lvl)
    formatter = logging.Formatter('%(message)s')
    ch.setFormatter(formatter)

    #file logger    
    fh = logging.FileHandler(file)
    fh.setLevel(level)
    formatter = logging.Formatter(
        '%(asctime)s :: %(name)s - %(message)s',
        datefmt='%m/%d/%Y %I:%M:%S %p')
    fh.setFormatter(formatter)
    logger.addHandler(ch)
    logger.addHandler(fh)
    return logger

2 typical classes in GUI

class MainWindow(QtGui.QMainWindow):

    def __init__(self):
        self.logger = setLogger(name='main_window')
        [...]

class UploadDialog(QtGui.QDialog):

def __init__(self):
    self.logger = setLogger(name='upload_dialog')
    [...]

and so on
PyQt has its silenced exceptions - exception occurs but program keeps running. I use the special hook for this situation (have seen it somewhere, this is not my code)

import sys
sys._excepthook = sys.excepthook

def exception_hook(exctype, value, traceback):
    sys._excepthook(exctype, value, traceback)
    sys.exit(1)

And now I want to grab any exception, so the users of my program in case of any crash could just send me their log.out and I would see the full log.

I've started from something like this.

try:
    app = QtGui.QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())
except Exception:
    logger.exception("MainWindow exception")

I worked well, until an exception was raised inside UploadDialog. Nothing was 'recorded'. So I just did:

#inside MainWindow class
def runDialog(self):
    try:
        dialog = UploadDialog()
        dialog.exec_()
    except Exception:
        self.logger.exception("Dialog exception")
        sys.exit(1)

Again, everything was OK, until an exception was rised inside WorkThread (Inherited from QThread class for background upload). And again.

#inside UploadDialog
def upload(self):
    # initialized as self.task = WorkThread()
    try:
        self.task.start()
    except Exception:
        self.logger.exception("Exception in WorkThread")

And then an exception was raised not after start of WorkThread but when initializing, so

class UploadDialog(QtGui.QDialog):

    def __init__(self):
        try:
            self.task = WorkThread()
        except Exception:
            self.logger.exception("Exception in WorkThread")

And here I just took my head and screamed to myself - "STOP IT. YOU'RE DOING IT WRONG. It's not pythonic, it's dirty, it makes my eyes bleed".

So, I'm asking - is there any elegant and pythonic way to grab ABSOLUTELY any exception, raised in PyQt app with one try...except block?


Solution

  • The point of using an excepthook is so you can monitor all exceptions that are raised by your program, and handle them centrally.

    So you should get rid of most of those try/except blocks, and log the exceptions inside the excepthook function. I say "most", because you may be affected by bug 1230540, where sys.excepthook is not called correctly outside of the main thread. See the tracker thread for some workarounds for that, or this SO answer.

    (PS: the threading issue was fixed in Python-3.8. See this answer).