Sunday, November 27, 2011

Handling errors in Google App Engine...and failing

After spending a nontrivial amount of my nights and weekends working on an AppEngine app, I wanted a good way to monitor the logs without checking in on them every day. After a particularly frustrating weekend of updates that exposed unnoticed bugs that had yet to be triggered by the app, I set out to find such a way. I set out to find a Pythonic* way.

Since I knew the App Engine Mail API was super easy to configure,  I figured I would just email myself every time there was an exception, before serving my default 500 error page. To do so, I just needed to subclass the default RequestHandler with my own handle_exception method. (OK, prepare yourselves, a bunch of code is about to happen. See the necessary imports at the bottom of the post.)
class ExtendedHandler(RequestHandler):

    def handle_exception(self, exception, debug_mode):
        traceback_info = ''.join(format_exception(*sys.exc_info()))
        email_admins(traceback_info, defer_now=True)

        serve_500(self)
Awesome! By making all my handlers inherit from ExtendedHandler, I can use the native Python modules traceback and sys to get the traceback and my handy dandy
def email_admins(error_msg, defer_now=False):
    if defer_now:
        defer(email_admins, error_msg, defer_now=False)
        return

    sender = 'YOUR APP Errors <errors@your_app_id_here.appspotmail.com>'
    to = 'Robert Admin <bob@example.com>, James Nekbehrd <jim@example.com>'
    subject = 'YOUR APP Error: Admin Notify'
    body = '\n'.join(['Dearest Admin,',
                      '',
                      'An error has occurred in YOUR APP:',
                      error_msg,
                      ''])

    mail.send_mail(sender=sender, to=to,
                   subject=subject, body=body)
to send out the email in the deferred queue** so as not to hold up the handler serving the page. Mission accomplished, right? WRONG!

Unfortunately, handle_exception only handles the "right" kind of exceptions. That is, exceptions which inherit directly from Python's Exception. From the horse's mouth:
Exceptions should typically be derived from the Exception class, either directly or indirectly.
But. But! If the app fails because a request times out, a DeadlineExceededError is thrown and handle_exception falls on its face. Why? Because DeadlineExceededError inherits directly from Exception's parent class: BaseException.  (Gasp)

It's OK little ones, in my next post I explain how I did it while keeping my code Pythonic by using metaclasses.

Imports:
from google.appengine.api import mail
from google.appengine.ext.deferred import defer
from google.appengine.ext.webapp import RequestHandler
import sys
from traceback import format_exception
from SOME_APP_SPECIFIC_LIBRARY import serve_500
*Pythonic:
An idea or piece of code which closely follows the most common idioms of the Python language, rather than implementing code using concepts common to other languages.
**Deferred Queue: Make sure to enable the deferred library in your app.yaml by using deferred: on in your builtins.

No comments: