Created
January 30, 2009 19:01
-
-
Save zvoase/55210 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
There are two main issues with this at the moment: | |
1. The 'Backtrace' section is going to look a little odd, because Ruby formats | |
it with the most recent call *first*, whereas Python uses most recent call | |
*last*. This could be solved, but I haven't done it yet (it's probably | |
quite easy). | |
2. The 'Environment' section's going to look a little weird, because I don't | |
know what should go there. Suggestions are very much welcome. | |
Other than that, it works as it should. If you encounter any problems, contact | |
me at ROT13('[email protected]'). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- coding: utf-8 -*- | |
################################################################################ | |
# getexceptional.py | |
# ----------------- | |
# Python interface and Django middleware for the Exceptional | |
# (http://getexceptional.com) exception-tracking service. | |
################################################################################ | |
# | |
# Copyright (c) 2008 Zachary Voase | |
# | |
# Permission is hereby granted, free of charge, to any person | |
# obtaining a copy of this software and associated documentation | |
# files (the "Software"), to deal in the Software without | |
# restriction, including without limitation the rights to use, | |
# copy, modify, merge, publish, distribute, sublicense, and/or sell | |
# copies of the Software, and to permit persons to whom the | |
# Software is furnished to do so, subject to the following | |
# conditions: | |
# | |
# The above copyright notice and this permission notice shall be | |
# included in all copies or substantial portions of the Software. | |
# | |
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES | |
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT | |
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | |
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR | |
# OTHER DEALINGS IN THE SOFTWARE. | |
# | |
################################################################################ | |
""" | |
Python interface and Django middleware for Exceptional (getexceptional.com) | |
This module exports middleware and helper classes for the Exceptional | |
(http://getexceptional.com) exception-tracking service. To use it, there are | |
only three steps (two if you already have an Exceptional account set up): | |
1. Set up an account and an application at http://getexceptional.com. | |
2. In your Django project's ``settings.py`` file, add a setting called | |
``EXCEPTIONAL_API_KEY`` and set it to a string containing the API key for | |
the application you set up at http://getexceptional.com. | |
3. In your projects ``settings.py`` file, add the string | |
``'exceptional.ExceptionalMiddleware'`` to your ``MIDDLEWARE_CLASSES`` | |
file. The position of the middleware should vary depending on what you | |
have installed. For example, if there is any fallback middleware (such | |
as that used for Django's flatpages app) which relies on certain | |
exceptions being raised, the Exceptional middleware should come *before* | |
it in the list. | |
Now visit a page that raises an exception. In the Exceptional web interface, you | |
should see the exception in a short amount of time, along with all its details. | |
""" | |
import datetime | |
import httplib | |
import os | |
import sys | |
import textwrap | |
import time | |
import traceback | |
import urllib | |
import zlib | |
import django | |
from django.conf import settings | |
from django.core import urlresolvers | |
from django.core.exceptions import MiddlewareNotUsed | |
try: | |
import json | |
except ImportError: | |
try: | |
import simplejson as json | |
except ImportError: | |
from django.utils import simplejson as json | |
try: | |
import pytz | |
except ImportError: | |
pytz = None | |
class Exceptional(object): | |
""" | |
Represents a single exception. | |
This class, instantiated with the Django request and exception info from an | |
exception raised within a Django view, produces a dictionary of data which | |
is in a format compatible with the Exceptional exception-tracking system. | |
This dictionary is stored in a ``data`` attribute. Note that it is not | |
necessarily JSONifiable; when serializing an instance of this class, | |
non-JSONifiable data will be run through ``repr`` so as to give some | |
meaningful output. | |
""" | |
def __init__(self, request, exc_type, exc_instance, tb): | |
self.request = request | |
self.exc_info = (exc_type, exc_instance, tb) | |
self.data = {} | |
self.data['language'] = self.get_language() | |
self.data['framework'] = u'Django %s' % (django.get_version(),) | |
self.data['exception_class'] = self.exc_info[0].__name__ | |
self.data['exception_message'] = str(exc_instance) | |
self.data['exception_backtrace'] = self.get_formatted_traceback() | |
self.data['occurred_at'] = self.get_occurred_at() | |
controller_name, action_name = self.get_controller_and_action() | |
self.data['controller_name'] = controller_name | |
self.data['action_name'] = action_name | |
self.data['application_root'] = self.get_application_root() | |
self.data['url'] = self.request.build_absolute_uri() | |
self.data['parameters'] = self.request.GET.copy() | |
self.data['session'] = {} | |
for key, value in self.request.session.items(): | |
self.data['session'][key] = value | |
print self.data['session'] | |
self.data['environment'] = self.get_environment() | |
def get_formatted_traceback(self): | |
return textwrap.dedent( | |
'\n'.join(traceback.format_tb(self.exc_info[2]))).splitlines() | |
def get_language(self): | |
major, minor, micro, release, serial = sys.version_info | |
version_num = '.'.join(map(str, (major, minor, micro))) | |
# Example: 'Python 2.6.1 (final)' | |
return u'Python %s (%s)' % (version_num, release) | |
def get_occurred_at(self): | |
if pytz: | |
tzinfo = pytz.timezone(settings.TIME_ZONE) | |
timestamp = datetime.datetime.now(tzinfo) | |
return timestamp.strftime('%Y%m%d %H:%M:%S %Z') | |
else: | |
timestamp = datetime.datetime.now() | |
return timestamp.strftime('%Y%m%d %H:%M:%S ') + time.tzname[0] | |
def get_controller_and_action(self): | |
view, pos_args, kwargs = urlresolvers.resolve( | |
self.request.path, getattr(self.request, 'urlconf', None)) | |
return view.__module__, view.__name__ | |
def get_application_root(self): | |
app, view_name = self.get_controller_and_action() | |
app = app.split('.') | |
if 'views' in app: | |
app = app[:app.index('views')] | |
if len(app) == 1: | |
app_obj = __import__(app[0]) | |
else: | |
fromlist = [app_obj[-1]] | |
app_obj = __import__('.'.join(app[:-1]), fromlist=fromlist) | |
return os.path.dirname(app_obj.__file__) | |
def get_environment(self): | |
# There is a problem here. I don't know what the 'environment' part | |
# should contain. At the moment I'm just sticking all sorts of data in | |
# here. | |
environment = {} | |
for key in os.environ: | |
environment['OS::' + key] = os.environ[key] | |
for key in self.request.COOKIES: | |
environment['COOKIES::' + key] = self.request.COOKIES[key] | |
for key in self.request.META: | |
environment['META::' + key] = self.request.META[key] | |
for key in settings._target.__dict__: | |
environment['SETTINGS::' + key] = settings._target.__dict__[key] | |
local_vars = self.exc_info[2].tb_frame.f_locals | |
for key in local_vars: | |
environment['LOCALS::' + key] = local_vars[key] | |
return environment | |
def serialize(self): | |
raw_json_data = json.dumps(self.data, sort_keys=True, default=repr) | |
gz_json_data = zlib.compress(raw_json_data) | |
return urllib.pathname2url(gz_json_data) | |
class ExceptionalAccount(object): | |
""" | |
Represents an API key and connection to the getexceptional.com service. | |
This class is instantiated with an API key and has one useful method, | |
``post``, which takes an instance of the ``Exceptional`` class and sends it | |
to the getexceptional.com service. | |
""" | |
def __init__(self, api_key): | |
self.api_key = api_key | |
def post(self, exc): | |
body = exc.serialize() | |
headers = {'Accept': 'application/x-gzip', | |
'Content-Type': 'application/x-gzip; charset=UTF-8'} | |
connection = httplib.HTTPConnection('getexceptional.com') | |
connection.request('POST', | |
('/errors/?api_key=' + self.api_key + '&protocol_version=3'), | |
body, headers) | |
response = connection.getresponse() | |
connection.close() | |
return True if (response.status == 200) else response | |
class ExceptionalMiddleware(object): | |
""" | |
Middleware to post unhandled exceptions to the getexceptional.com service. | |
This middleware catches exceptions raised within a view and sends them off | |
the the getexceptional.com service. Note that it requires a setting within | |
your project, ``EXCEPTIONAL_API_KEY``. If this setting is not present, then | |
the middleware's ``__init__`` method will raise | |
``django.core.exceptions.MiddlewareNotUsed``. This deactivates the | |
middleware for the life of the server process. | |
""" | |
def __init__(self): | |
api_key = getattr(settings, 'EXCEPTIONAL_API_KEY', None) | |
if not api_key: | |
raise MiddlewareNotUsed | |
self.account = ExceptionalAccount(api_key) | |
def process_exception(self, request, exception_instance): | |
self.account.post(Exceptional(request, *sys.exc_info())) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment