From 0cc071cb4a5ba4a84ac36ef9492eb49e16304b48 Mon Sep 17 00:00:00 2001 From: Artur Bartkevic Date: Fri, 30 Aug 2013 11:04:55 +0300 Subject: [PATCH 1/3] Add webpy client --- raven/contrib/webpy/__init__.py | 77 ++++++++++++++++++++++++++++ raven/contrib/webpy/utils.py | 26 ++++++++++ setup.py | 2 + tests/contrib/webpy/__init__.py | 0 tests/contrib/webpy/tests.py | 89 +++++++++++++++++++++++++++++++++ 5 files changed, 194 insertions(+) create mode 100644 raven/contrib/webpy/__init__.py create mode 100644 raven/contrib/webpy/utils.py create mode 100644 tests/contrib/webpy/__init__.py create mode 100644 tests/contrib/webpy/tests.py diff --git a/raven/contrib/webpy/__init__.py b/raven/contrib/webpy/__init__.py new file mode 100644 index 000000000..a32338223 --- /dev/null +++ b/raven/contrib/webpy/__init__.py @@ -0,0 +1,77 @@ +""" +raven.contrib.webpy +~~~~~~~~~~~~~~~~~~~ + +:copyright: (c) 2013 by the Sentry Team, see AUTHORS for more details. +:license: BSD, see LICENSE for more details. +""" +from __future__ import absolute_import + +import sys + +import web + +from raven.conf import setup_logging +from raven.handlers.logging import SentryHandler +from raven.contrib.webpy.utils import get_data_from_request + + +class SentryApplication(web.application): + """ + Web.py application for Sentry. + + >>> sentry = Sentry(client, mapping=urls, fvars=globals()) + + Automatically configure logging:: + + >>> sentry = Sentry(client, logging=True, mapping=urls, fvars=globals()) + + Capture an exception:: + + >>> try: + >>> 1 / 0 + >>> except ZeroDivisionError: + >>> sentry.captureException() + + Capture a message:: + + >>> sentry.captureMessage('hello, world!') + """ + def __init__(self, client, logging=False, **kwargs): + self.client = client + self.logging = logging + if self.logging: + setup_logging(SentryHandler(self.client)) + web.application.__init__(self, **kwargs) + + def handle_exception(self, *args, **kwargs): + self.client.captureException( + exc_info=kwargs.get('exc_info'), + data=get_data_from_request(), + extra={ + 'app': self, + }, + ) + + def handle(self): + try: + return web.application.handle(self) + except: + self.handle_exception(exc_info=sys.exc_info()) + raise + + def captureException(self, *args, **kwargs): + assert self.client, 'captureException called before application configured' + data = kwargs.get('data') + if data is None: + kwargs['data'] = get_data_from_request() + + return self.client.captureException(*args, **kwargs) + + def captureMessage(self, *args, **kwargs): + assert self.client, 'captureMessage called before application configured' + data = kwargs.get('data') + if data is None: + kwargs['data'] = get_data_from_request() + + return self.client.captureMessage(*args, **kwargs) diff --git a/raven/contrib/webpy/utils.py b/raven/contrib/webpy/utils.py new file mode 100644 index 000000000..781cb4b29 --- /dev/null +++ b/raven/contrib/webpy/utils.py @@ -0,0 +1,26 @@ +""" +raven.contrib.webpy.utils +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details. +:license: BSD, see LICENSE for more details. +""" +from __future__ import absolute_import + +import web + +from raven.utils.wsgi import get_headers, get_environ + + +def get_data_from_request(): + """Returns request data extracted from web.ctx.""" + return { + 'sentry.interfaces.Http': { + 'url': '%s://%s%s' % (web.ctx['protocol'], web.ctx['host'], web.ctx['path']), + 'query_string': web.ctx.query, + 'method': web.ctx.method, + 'data': web.data(), + 'headers': dict(get_headers(web.ctx.environ)), + 'env': dict(get_environ(web.ctx.environ)), + } + } diff --git a/setup.py b/setup.py index 600756203..a365fd97a 100755 --- a/setup.py +++ b/setup.py @@ -67,6 +67,8 @@ 'webob', 'webtest', 'anyjson', + 'paste', + 'web.py' ] + flask_requires + flask_tests_requires + unittest2_requires diff --git a/tests/contrib/webpy/__init__.py b/tests/contrib/webpy/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/contrib/webpy/tests.py b/tests/contrib/webpy/tests.py new file mode 100644 index 000000000..d3e1cb18e --- /dev/null +++ b/tests/contrib/webpy/tests.py @@ -0,0 +1,89 @@ +import sys + +from exam import fixture +from paste.fixture import TestApp + +from raven.base import Client +from raven.contrib.webpy import SentryApplication +from raven.utils.testutils import TestCase + + +class TempStoreClient(Client): + def __init__(self, servers=None, **kwargs): + self.events = [] + super(TempStoreClient, self).__init__(servers=servers, **kwargs) + + def is_enabled(self): + return True + + def send(self, **kwargs): + self.events.append(kwargs) + + +class TestEndpoint(object): + def GET(self): + raise ValueError('That\'s what she said') + + def POST(self): + raise TypeError('Potato') + + +urls = { + '/test', TestEndpoint +} + + +def create_app(client): + return SentryApplication(client=client, mapping=urls, fvars=globals()) + + +class WebpyTest(TestCase): + @fixture + def app(self): + sys.exc_clear() + self.store = TempStoreClient() + return create_app(self.store) + + @fixture + def client(self): + return TestApp(self.app.wsgifunc()) + + def test_get(self): + resp = self.client.get('/test', expect_errors=True) + + self.assertEquals(resp.status, 500) + self.assertEquals(len(self.store.events), 1) + + event = self.store.events.pop(0) + + self.assertTrue('sentry.interfaces.Exception' in event) + exc = event['sentry.interfaces.Exception'] + self.assertEquals(exc['type'], 'ValueError') + self.assertEquals(exc['value'], 'That\'s what she said') + self.assertEquals(event['message'], 'ValueError: That\'s what she said') + self.assertEquals(event['culprit'], 'tests.contrib.webpy.tests in GET') + + def test_post(self): + response = self.client.post('/test?biz=baz', params={'foo': 'bar'}, expect_errors=True) + self.assertEquals(response.status, 500) + self.assertEquals(len(self.store.events), 1) + + event = self.store.events.pop(0) + + self.assertTrue('sentry.interfaces.Http' in event) + http = event['sentry.interfaces.Http'] + self.assertEquals(http['url'], 'http://localhost/test') + self.assertEquals(http['query_string'], '?biz=baz') + self.assertEquals(http['method'], 'POST') + self.assertEquals(http['data'], 'foo=bar') + self.assertTrue('headers' in http) + headers = http['headers'] + self.assertTrue('Content-Length' in headers, headers.keys()) + self.assertEquals(headers['Content-Length'], '7') + self.assertTrue('Content-Type' in headers, headers.keys()) + self.assertEquals(headers['Content-Type'], 'application/x-www-form-urlencoded') + self.assertTrue('Host' in headers, headers.keys()) + self.assertEquals(headers['Host'], 'localhost') + env = http['env'] + self.assertTrue('SERVER_NAME' in env, env.keys()) + self.assertEquals(env['SERVER_NAME'], 'localhost') From 7a165f4a1a584c0dcc69ce03aa138514051e5ea6 Mon Sep 17 00:00:00 2001 From: Artur Bartkevic Date: Fri, 30 Aug 2013 11:26:46 +0300 Subject: [PATCH 2/3] Fix typo in webpy/tests --- tests/contrib/webpy/tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/contrib/webpy/tests.py b/tests/contrib/webpy/tests.py index d3e1cb18e..ab6455bed 100644 --- a/tests/contrib/webpy/tests.py +++ b/tests/contrib/webpy/tests.py @@ -37,7 +37,7 @@ def create_app(client): return SentryApplication(client=client, mapping=urls, fvars=globals()) -class WebpyTest(TestCase): +class WebPyTest(TestCase): @fixture def app(self): sys.exc_clear() From 4b07bf5f67e04955dd17b446d7c6d8f73ae50c1e Mon Sep 17 00:00:00 2001 From: Artur Bartkevic Date: Mon, 2 Sep 2013 09:43:09 +0300 Subject: [PATCH 3/3] Fix urls --- tests/contrib/webpy/tests.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/tests/contrib/webpy/tests.py b/tests/contrib/webpy/tests.py index ab6455bed..f2603a4c3 100644 --- a/tests/contrib/webpy/tests.py +++ b/tests/contrib/webpy/tests.py @@ -1,5 +1,3 @@ -import sys - from exam import fixture from paste.fixture import TestApp @@ -28,19 +26,18 @@ def POST(self): raise TypeError('Potato') -urls = { +urls = ( '/test', TestEndpoint -} +) def create_app(client): - return SentryApplication(client=client, mapping=urls, fvars=globals()) + return SentryApplication(client=client, mapping=urls) class WebPyTest(TestCase): @fixture def app(self): - sys.exc_clear() self.store = TempStoreClient() return create_app(self.store) @@ -54,8 +51,7 @@ def test_get(self): self.assertEquals(resp.status, 500) self.assertEquals(len(self.store.events), 1) - event = self.store.events.pop(0) - + event = self.store.events.pop() self.assertTrue('sentry.interfaces.Exception' in event) exc = event['sentry.interfaces.Exception'] self.assertEquals(exc['type'], 'ValueError') @@ -68,7 +64,7 @@ def test_post(self): self.assertEquals(response.status, 500) self.assertEquals(len(self.store.events), 1) - event = self.store.events.pop(0) + event = self.store.events.pop() self.assertTrue('sentry.interfaces.Http' in event) http = event['sentry.interfaces.Http']