Skip to content
This repository has been archived by the owner on Jan 31, 2018. It is now read-only.

Commit

Permalink
[bug 1136840] Fix error handling for better debugging
Browse files Browse the repository at this point in the history
* creates DebuggableWSGIHandler which should send more useful error
  emails so I can debug API-related problems, plus in DEBUG=True
  mode, it'll return plain formatted content rather than html formatted
  content; plain text is a lot easier to read in a terminal when
  debugging API-related problems
* created fjord/wsgi.py which gets used by "./manage.py runserver"
  and holds common WSGI setup for fjord
* adjusts wsgi/playdoh.wsgi which gets used by stage/prod environments
  to do stage/prod specific things and then use fjord/wsgi.py for
  the rest
  • Loading branch information
willkg committed Mar 4, 2015
1 parent 1dcaf62 commit ceb53eb
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 8 deletions.
1 change: 1 addition & 0 deletions fjord/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -581,6 +581,7 @@ def JINJA_CONFIG():
]
}

WSGI_APPLICATION = 'fjord.wsgi.application'

# When set to True, this will cause a message to be displayed on all
# pages that this is not production.
Expand Down
30 changes: 30 additions & 0 deletions fjord/wsgi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# This file gets run by ./manage.py (runserver|test) and also gets
# imported for the WSGI application building by wsgi/playdoh.wsgi
# for stage/prod.
#
# It holds the setup that's common to both environments.

import os

import django
from django.core.handlers.wsgi import WSGIHandler

from fjord.wsgi_utils import BetterDebugMixin


os.environ.setdefault('CELERY_LOADER', 'django')


class DebuggableWSGIHandler(BetterDebugMixin, WSGIHandler):
pass


def get_debuggable_wsgi_application():
# This does the same thing as
# django.core.wsgi.get_wsgi_application except it returns a
# different WSGIHandler.
django.setup()
return DebuggableWSGIHandler()


application = get_debuggable_wsgi_application()
87 changes: 87 additions & 0 deletions fjord/wsgi_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
from django.conf import settings


class BetterDebugMixin(object):
"""Provides better debugging data
Developing API endpoints and tired of wading through HTML for HTTP
500 errors?
Working on POST API debugging and not seeing the POST data show up
in the error logs/emails?
Then this mixin is for you!
It:
* spits out text rather than html when DEBUG = True (OMG! THANK
YOU!)
* adds a "HTTP_X_POSTBODY" META variable so you can see the raw post
data in error emails which is gross, but I couldn't figure out
a better way to do it
Usage:
Create a WSGIHandler subclass and bind that to ``application`` in
your wsgi file. For example::
import django
from django.core.handlers.wsgi import WSGIHandler
from fjord.wsgi_utils import BetterDebugMixin
class MyWSGIHandler(BetterDebugMixin, WSGIHandler):
pass
def get_debuggable_wsgi_application():
# This does the same thing as
# django.core.wsgi.get_wsgi_application except
# it returns a different WSGIHandler.
django.setup()
return MyWSGIHandler()
application = get_debuggable_wsgi_application()
"""
def handle_uncaught_exception(self, request, resolver, exc_info):
if settings.DEBUG_PROPAGATE_EXCEPTIONS:
raise

# First, grab the raw POST body and put it somewhere that's
# guaranteed to show up in the error email.

# This should be "bytes" which is str type in Python 2.
#
# FIXME: This is probably broken with Python 3.
postbody = getattr(request, 'body', '')
try:
# For string-ish data, we truncate and decode/re-encode in
# utf-8.
postbody = postbody[:10000].decode('utf-8').encode('utf-8')
except (UnicodeDecodeError, UnicodeEncodeError):
# For binary, we say, 'BINARY CONTENT'
postbody = 'BINARY OR NON-UTF-8 CONTENT'

# The logger.error generates a record which can get handled by
# the AdminEmailHandler. Overriding all that machinery is
# daunting, so we're instead going to shove it in the META
# section which shows up when the machinery does a repr on
# WSGIRequest.
request.META['HTTP_X_POST_BODY'] = postbody

# Second, check the Accept header and if it's not text/html,
# pretend this is an AJAX request so that we get the output in
# text rather than html when DEBUG=True.
if settings.DEBUG:
# request.is_ajax() == True will push this into doing text
# instead of html which is waaaaaayyy more useful from an
# API perspective. So if the Accept header is anything other
# than html, we'll say it's an ajax request to return text.
if 'html' not in request.META.get('HTTP_ACCEPT', 'text/html'):
request.META['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'

return super(BetterDebugMixin, self).handle_uncaught_exception(
request, resolver, exc_info)
25 changes: 17 additions & 8 deletions wsgi/playdoh.wsgi
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
# This gets used by stage/prod to set up the WSGI application for stage/prod
# use. We do some minor environment setup and then have `fjord/wsgi.py` do
# the rest.

import os
import site

# Set up NewRelic stuff.
try:
import newrelic.agent
except ImportError:
Expand All @@ -15,7 +20,6 @@ if newrelic:
newrelic = False


os.environ.setdefault('CELERY_LOADER', 'django')
# NOTE: you can also set DJANGO_SETTINGS_MODULE in your environment to override
# the default value in manage.py

Expand All @@ -24,18 +28,23 @@ wsgidir = os.path.dirname(__file__)
site.addsitedir(os.path.abspath(os.path.join(wsgidir, '../')))

# Explicitly set these so that fjord.manage_utils does the right
# thing.
# thing in production.
os.environ['USING_VENDOR'] = '1'
os.environ['SKIP_CHECK'] = '1'

# manage adds vendor to the Python path and otherwise sets up the
# environment.
# Importing manage has the side-effect of adding vendor/ stuff and
# doing other environment setup.
import manage

from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()

# This is the original Django WSGIHandler preserved here in case
# we ever have to back out the debuggable one.
# from django.core.wsgi import get_wsgi_application
# application = get_wsgi_application()

from fjord.wsgi import get_debuggable_wsgi_application
application = get_debuggable_wsgi_application()


if newrelic:
application = newrelic.agent.wsgi_application()(application)

# vim: ft=python

0 comments on commit ceb53eb

Please sign in to comment.