Error handling is a total pain no matter method you choose to use;
in Python we are more or less stuck with exceptions. When you have
exceptions if you want any chance of debugging program failures, you
want to see the stack-trace for any uncaught exceptions. Python
usually obliges by spewing out a stack traces on stderr.
However, it isn't too hard to get in to a situation where you end
up losing those stack traces which ends up leading to a bunch of
head scratcing.
When you have a server, you usually run it daemonized.
When running as a deamon, it is not uncommon for any output to be
redirected to /dev/null. In this case, unless you have
arranged otherwise, your stack traces are going to disappear into
the ether.
When you have a server style program, you definitely want to be using the Python logging system. This lets you output messages to logfiles (or syslogd). So ideally, you want any stack traces to go here as well.
Now, this is fairly straight forward, you can just make
sure your top level function is wrapped in a try/except
block. For example:
try:
main()
except:
logging.exception("Unhandled exception during main")
Another alternative is setting up a custom excepthook
This works great, unless you happen to be using the threading
module. In this case, any exceptions in your run method
(or the function you pass as a target) will actually be
internally caught by the threading module (see the _bootstrap_inner
method).
Unfortunately this code explicitly dumps the strack trace to stderr, which isn’t so useful.
Now, one approach to dealing with this is to have every run method, or target
function expicilty catch any exceptions and output them to the log, however it would be nice
to avoid duplicating this handling everywhere.
The solution I came up with was a simple sublcass the standard Thread class
that catches the exception and places it out on the log.
class LogThread(threading.Thread):
"""LogThread should always e used in preference to threading.Thread.
The interface provided by LogThread is identical to that of threading.Thread,
however, if an exception occurs in the thread the error will be logged
(using logging.exception) rather than printed to stderr.
This is important in daemon style applications where stderr is redirected
to /dev/null.
"""
def __init__(self, **kwargs):
super().__init__(**kwargs)
self._real_run = self.run
self.run = self._wrap_run
def _wrap_run(self):
try:
self._real_run()
except:
logging.exception('Exception during LogThread.run')
Then, use the LogThread class where you would previously use the Thread class.
Another alternative approach to this would be to capture any and all stderr
output and redirect it to the log. An example of this approach can be found
on in electric monk’s blog post "Redirect stdout and stderr to a logger in Python".