Errors: letting users know what went wrong

The game we are porting originally has a nice way of displaying some error messages when the program crashes:
(like this)

So a small pop-up window appears when a crash occurs and you don’t need to have a whole console all the time.

How should (can) I implement this in our Panda3d/Python port? Right now our exe launcher is set to display the console. But when an error occurs, the console closes with the Panda3d window. How to prevent that, or even better, how to implement a pop-up window displaying the error message instead, like in the original game? Are there perhaps some error notifier classes in Python which print an error in the console and if console is disabled print it to a pop-up window instead? Or will we need to use an actual GUI library like Tk and do it ourselves? And if yes, how to create that Tk window when the error kills our program?

The somewhat hacky way I handle this at the moment is to launch the game from a batch file (on Windows of course):

@ECHO OFF
title Console Window Title
"pandapath\python\python" pythonfile.py
IF ERRORLEVEL 1 infloop.bat

The other batch file simply keeps calling itself infinitely:

infloop.bat

If the program exits normally, the window closes. If there was an error, it stays open.

I suppose if you wanted to get fancy you could have a sort of wrapper python script that runs your game and dumps the console output to a text file. Then when the game exits it could look at the output and determine if there was an error and launch a python script that just displays the error message or even gives the option to email the error to you as an error reporting mechanism.

Here ya go.

import tkMessageBox
from sys import exit

def showError(message):
    
    tkMessageBox.showerror("Error!", message)
    
    exit(1)

That’s nice. But I also use Panda’s DirectNotify for error/warning/debug messages now. If I put a notify message after the Tk message, that would kill the program and so also the Tk window, right?
Also I don’t just want to show error messages only when something goes wrong in my code (by explicitly setting it where I believe an error might happen), I would like to redirect ay error message there, is that possible?

PS. This gets rid of the additional window:

base.startTk()
tkroot.withdraw()

Attach a signal handler to the SIGSEGV (segmentation fault), SIGFPE (floating point error, eg divide by zero) and SIGBUS (bus error) signals, and you’ll be able to catch crashes as they happen, showing an appropriate message box. It should be obvious how to catch Python exceptions.

Would need more info. From Python? How? The only C++ code is for the exe launcher.
And also, how to keep the message box if the program crashes (not let it close with everything)?

I think using the signal module (part of the Python standard library) is the best way to catch signals from Python.

I had a look at the module. Still got a very vague idea about signals and if I should really be using them.

Oh, I just found out about this, too:
docs.python.org/dev/library/faulthandler.html
It’s a relatively new feature though, not sure if it’s in Python 2.7.

If not, another option is to make a launcher python file that uses a subprocess to open your game, and checks the signal of the child process, and check if it is SIGSEGV, SIGFPE or any of the others.

I don’t think I want to complicate the code that much.
I guess I’d be better off using Tk error message boxes where I specify it explicitly and use only console in any other case.

I’m still not sure how to keep the console alive for the user to read the error message when an error occurs. The game is ran with an exe launcher, which simply runs “main.py” from one folder with “python.exe” from another. Whenever an error occurs now, the panda window+console close immediately. How to prevent that?

Another thing:
It seems when you enable Tk from Panda, error messages get printed in a window like this:

Would be better if I could disable the console and only keep this, because it only shows up when an error occurs and only has the error text, no other debug messages like in the console. Plus having both the console and this window at the same time is annoying.
This window closes as well now when an error occurs. How to prevent that?

On the other hand, enabling Tk like this really slows things down.

base.startTk()

Maybe I shouldn’t be doing it like this and having Tk in the background all the time?

Found a way to keep the console open.

Any ideas how to enable/disable the console in realtime?
And still would like to know why

base.startTk()

slows everything down so much.

You can’t enable or disable the Windows console from your application.

Calling startTk makes Tk get control over the main loop, this is necessary in order to reliably support Tk on some platforms. It creates a Tk task that steps the Panda task manager 60 times per second. You can change the frame rate using “tk-frame-rate”.

This is a new feature in 1.8.0, and without it, the Tk windows were mostly unresponsive on Mac. If you want to disable it, set “tk-frame-rate” to false.

Perhaps you can just keep Tk disabled all the time, and when an exception happens, you start a Tk window (not via base.startTk but the usual Tkinter way) and show the error message.

Thanks for the ideas.

I think i found a way by using

sys.excepthook

Code:

import sys
import traceback
import tkMessageBox

from panda3d.core import *
import direct.directbase.DirectStart

# a function which creates an error message box window
def errorMessageBox(text):
	tkMessageBox.showerror('Error!', text)
	tkroot.withdraw() # doesn't work?

# redirect errors to a window and keep console alive
def showErrorAndPause(exc_type, exc_value, tb):
	errorMessageBox(exc_type.__name__ + ': ' + str(exc_value)) # GUI
	traceback.print_exception(exc_type, exc_value, tb) # console
	raw_input('Press Enter to exit...') # keep console open
	sys.exit() # close only when enter pressed
	
sys.excepthook = showErrorAndPause

# this will generate error
base.accept('a', lambda: loader.loadModel('nonexistent'))

run()

One problem is sys.exit() raises an exception itself, so you will get the same error and window again when you press OK.
And I can’t disable the default Tk window for some reason.

Why don’t you just wrap a “try: except” block around the run() call?

You can then simply check for a SystemExit exception, and if that happens, use “raise” to restore the firing of the exception.

Not sure how that would work.

You do know how exceptions work in Python, right? They are a basic feature of Python:
docs.python.org/tutorial/errors.html

Your code could look like this:

try:
    base.run()
except Exception, ex:
    # Exception happened, get the exception info
    exc_type, exc_value, exc_traceback = sys.exc_info()
    text = repr(traceback.format_exception(exc_type, exc_value, exc_traceback))

    # Now show a tk dialog showing "text"

Then, “text” will contain the text of the exception that happened (instead of it being printed to the command-line), which you can use to display in a Tk window to show to the user.

You don’t need to explicitly check if SystemExit or KeyboardInterrupt was triggered here, because “Exception” base type does not match these; they are subtypes of “BaseException”. If you did “except:” instead, then it would have also caught SystemExit and KeyboardInterrupt.