views:

876

answers:

4

I'm writing a little debug app for a bit of kit we're developing and I'd like to roll it out to a few users to see if they can provoke any crashes. Does anyone know a way of effectively wrapping a wxPython app to catch any and all unhandled exceptions that would cause the app to crash?

Ideally I'd want to capture all output (not just errors) and log it to a file. Any unhandled exceptions ought to log to the current file and then allow the exception to pass on as per usual (i.e. the logging process ought to be transparent).

I'm sure someone must have done something along these lines before, but I've not managed to turn up anything that looks useful via google.

+1  A: 

There are various ways. You can put a try..catch block in the wxApplication::OnInit, however, that would not always work with Gtk.

A nice alternative would be to override the Application::HandleEvent in your wxApplication derived class, and write a code like this:

void Application::HandleEvent(wxEvtHandler* handler, wxEventFunction func, wxEvent& event) const
{
    try
    {
        wxAppConsole::HandleEvent(handler, func, event);
    }
    catch (const std::exception& e)
    {
        wxMessageBox(std2wx(e.what()), _("Unhandled Error"),
            wxOK | wxICON_ERROR, wxGetTopLevelParent(wxGetActiveWindow()));
    }
}

It's a C++ example, but you can surely translate to Python easily.

Milan Babuškov
+4  A: 

For logging standard output, you can use a stdout wrapper, such as this one:

from __future__ import with_statement

class OutWrapper(object):
    def __init__(self, realOutput, logFileName):
        self._realOutput = realOutput
        self._logFileName = logFileName

    def _log(self, text):
        with open(self._logFileName, 'a') as logFile:
            logFile.write(text)

    def write(self, text):
        self._log(text)
        self._realOutput.write(text)

You then have to initialize it in your main Python file (the one that runs everything):

import sys    
sys.stdout = OutWrapper(sys.stdout, r'c:\temp\log.txt')

As to logging exceptions, the easiest thing to do is to wrap MainLoop method of wx.App in a try..except, then extract the exception information, save it in some way, and then re-raise the exception through raise, e.g.:

try:
    app.MainLoop()
except:
    exc_info = sys.exc_info()
    saveExcInfo(exc_info) # this method you have to write yourself
    raise
DzinX
Cheers Dzinx - I ended up using a combination of your suggestion and monopocalypse's
Jon Cage
I tried doing this in my application in order to catch exceptions and display friendly error dialogs, but it didn't work. It seems that because wxPython spawns a different thread for App.MainLoop() that the exceptions are out of the scope of the try/except block at that point.
Soviut
+3  A: 

For the exception handling, assuming your log file is opened as log:

import sys
import traceback

def excepthook(type, value, tb):
    message = 'Uncaught exception:\n'
    message += ''.join(traceback.format_exception(type, value, tb))
    log.write(message)

sys.excepthook = excepthook
monopocalypse
Cheers monopocalypse - I ended up using a combination of your suggestion and Dzinx's. I only wish I could accept both your answers!
Jon Cage
A: 

You can use

sys.excepthook

(see Python docs)

and assign some custom object to it, that would catch all exceptions not caught earlier in your code. You can then log any message to any file you wish, together with traceback and do whatever you like with the exception (reraise it, display error message and allow user to continue using your app etc).

As for logging stdout - the best way for me was to write something similar to DzinX's OutWrapper.

If you're at debugging stage, consider flushing your log files after each entry. This harms performance a lot, but if you manage to cause segfault in some underlying C code, your logs won't mislead you.

Abgan