From e3d70afbe7bfe5e021d7027f13cc32104b191a2f Mon Sep 17 00:00:00 2001 From: Adam Chainz Date: Wed, 24 Aug 2016 11:42:57 +0100 Subject: [PATCH 001/289] Fix warnings from Pytest 3.0+ Fix WC1 warnings from the below: ``` ============================================================== pytest-warning summary ============================================================== WC1 None [pytest] section in setup.cfg files is deprecated, use [tool:pytest] instead. WI1 /Users/adamj/Documents/Projects/raven-python/.tox/py27/lib/python2.7/site-packages/pytest_timeout.py:68 'pytest_runtest_protocol' hook uses deprecated __multicall__ argument WC1 /Users/adamj/Documents/Projects/raven-python/tests/contrib/bottle/tests.py cannot collect test class 'TestApp' because it has a __init__ constructor WC1 /Users/adamj/Documents/Projects/raven-python/tests/contrib/django/tests.py cannot collect test class 'ClientHandler' because it has a __init__ constructor WC1 /Users/adamj/Documents/Projects/raven-python/tests/contrib/django/tests.py cannot collect test class 'Client' because it has a __init__ constructor WC1 /Users/adamj/Documents/Projects/raven-python/tests/contrib/django/tests.py cannot collect test class 'TestModel' because it has a __init__ constructor WC1 /Users/adamj/Documents/Projects/raven-python/tests/contrib/webpy/tests.py cannot collect test class 'TestApp' because it has a __init__ constructor ``` `setup.cfg` fixed as per pytest-dev/pytest#567. `cannot collect test class` fixed by renaming the affected class names so the collector doesn't consider them. The WI1 warning is left as it's in the `pytest_timeout` plugin. --- setup.cfg | 2 +- tests/contrib/bottle/tests.py | 4 ++-- tests/contrib/django/models.py | 2 +- tests/contrib/django/tests.py | 16 ++++++++-------- tests/contrib/webpy/tests.py | 4 ++-- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/setup.cfg b/setup.cfg index 3b8d26278..5aecf8515 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,4 +1,4 @@ -[pytest] +[tool:pytest] python_files=test*.py addopts=--tb=native -p no:doctest norecursedirs=bin dist docs htmlcov hooks node_modules .* {args} diff --git a/tests/contrib/bottle/tests.py b/tests/contrib/bottle/tests.py index 081339847..1c1da6eb5 100644 --- a/tests/contrib/bottle/tests.py +++ b/tests/contrib/bottle/tests.py @@ -1,6 +1,6 @@ from exam import fixture -from webtest import TestApp +from webtest import TestApp as WebtestApp # prevent pytest-warning import bottle @@ -25,7 +25,7 @@ def create_app(raven): app = bottle.app() app.catchall = False app = Sentry(app, client=raven) - tapp = TestApp(app) + tapp = WebtestApp(app) @bottle.route('/error/', ['GET', 'POST']) def an_error(): diff --git a/tests/contrib/django/models.py b/tests/contrib/django/models.py index 06e03f226..d198cfde5 100644 --- a/tests/contrib/django/models.py +++ b/tests/contrib/django/models.py @@ -3,5 +3,5 @@ from django.db import models -class TestModel(models.Model): +class MyTestModel(models.Model): pass diff --git a/tests/contrib/django/tests.py b/tests/contrib/django/tests.py index 3aa6b746d..66797e226 100644 --- a/tests/contrib/django/tests.py +++ b/tests/contrib/django/tests.py @@ -35,8 +35,8 @@ from raven.transport import HTTPTransport from raven.utils.serializer import transform -from django.test.client import Client as TestClient, ClientHandler as TestClientHandler -from .models import TestModel +from django.test.client import Client as DjangoTestClient, ClientHandler as DjangoTestClientHandler +from .models import MyTestModel settings.SENTRY_CLIENT = 'tests.contrib.django.tests.TempStoreClient' @@ -55,7 +55,7 @@ def make_request(): }) -class MockClientHandler(TestClientHandler): +class MockClientHandler(DjangoTestClientHandler): def __call__(self, environ, start_response=[]): # this pretends doesn't require start_response return super(MockClientHandler, self).__call__(environ) @@ -256,7 +256,7 @@ def test_response_middlware_exception(self): def test_broken_500_handler_with_middleware(self): with Settings(BREAK_THAT_500=True, INSTALLED_APPS=['raven.contrib.django']): - client = TestClient(REMOTE_ADDR='127.0.0.1') + client = DjangoTestClient(REMOTE_ADDR='127.0.0.1') client.handler = MockSentryMiddleware(MockClientHandler()) self.assertRaises(Exception, client.get, reverse('sentry-raise-exc')) @@ -733,21 +733,21 @@ def test_real_gettext_lazy(self): class ModelInstanceSerializerTestCase(TestCase): def test_basic(self): - instance = TestModel() + instance = MyTestModel() result = transform(instance) assert isinstance(result, six.string_types) - assert result == '' + assert result == '' class QuerySetSerializerTestCase(TestCase): def test_basic(self): from django.db.models.query import QuerySet - obj = QuerySet(model=TestModel) + obj = QuerySet(model=MyTestModel) result = transform(obj) assert isinstance(result, six.string_types) - assert result == '' + assert result == '' class SentryExceptionHandlerTest(TestCase): diff --git a/tests/contrib/webpy/tests.py b/tests/contrib/webpy/tests.py index d96cb9c20..60824033d 100644 --- a/tests/contrib/webpy/tests.py +++ b/tests/contrib/webpy/tests.py @@ -1,5 +1,5 @@ from exam import fixture -from paste.fixture import TestApp +from paste.fixture import TestApp as PasteTestApp # prevent pytest-warning from raven.base import Client from raven.contrib.webpy import SentryApplication @@ -43,7 +43,7 @@ def app(self): @fixture def client(self): - return TestApp(self.app.wsgifunc()) + return PasteTestApp(self.app.wsgifunc()) def test_get(self): resp = self.client.get('/test', expect_errors=True) From 92363785787d4d4a04ad410dee3b1d0c7a8b388a Mon Sep 17 00:00:00 2001 From: James Cunningham Date: Wed, 24 Aug 2016 10:28:10 -0700 Subject: [PATCH 002/289] Move _emit extraction functionality into a method. --- raven/handlers/logging.py | 42 ++++++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/raven/handlers/logging.py b/raven/handlers/logging.py index cc82550c4..3062daed7 100644 --- a/raven/handlers/logging.py +++ b/raven/handlers/logging.py @@ -26,6 +26,29 @@ )) +def extract_extra(record): + data = {} + + extra = getattr(record, 'data', None) + if not isinstance(extra, dict): + if extra: + extra = {'data': extra} + else: + extra = {} + + for k, v in iteritems(vars(record)): + if k in RESERVED: + continue + if k.startswith('_'): + continue + if '.' not in k and k not in ('culprit', 'server_name', 'fingerprint'): + extra[k] = v + else: + data[k] = v + + return data, extra + + class SentryHandler(logging.Handler, object): def __init__(self, *args, **kwargs): client = kwargs.get('client_cls', Client) @@ -104,24 +127,7 @@ def _get_targetted_stack(self, stack, record): return frames def _emit(self, record, **kwargs): - data = {} - - extra = getattr(record, 'data', None) - if not isinstance(extra, dict): - if extra: - extra = {'data': extra} - else: - extra = {} - - for k, v in iteritems(vars(record)): - if k in RESERVED: - continue - if k.startswith('_'): - continue - if '.' not in k and k not in ('culprit', 'server_name', 'fingerprint'): - extra[k] = v - else: - data[k] = v + data, extra = extract_extra(record) stack = getattr(record, 'stack', None) if stack is True: From 62e6a5dd5bf6ee10b24ef22e0e60cc50869d50ad Mon Sep 17 00:00:00 2001 From: James Cunningham Date: Wed, 24 Aug 2016 10:47:56 -0700 Subject: [PATCH 003/289] Add the ability to pass in a different RESERVED. --- raven/handlers/logging.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/raven/handlers/logging.py b/raven/handlers/logging.py index 3062daed7..489255874 100644 --- a/raven/handlers/logging.py +++ b/raven/handlers/logging.py @@ -26,7 +26,7 @@ )) -def extract_extra(record): +def extract_extra(record, reserved=RESERVED): data = {} extra = getattr(record, 'data', None) @@ -37,7 +37,7 @@ def extract_extra(record): extra = {} for k, v in iteritems(vars(record)): - if k in RESERVED: + if k in reserved: continue if k.startswith('_'): continue From 2e88836fabd936da2a8d6f6dd03e758bc69acb2a Mon Sep 17 00:00:00 2001 From: Giang Manh Date: Fri, 26 Aug 2016 09:19:05 +0900 Subject: [PATCH 004/289] Add tip for dynamically use Sentry --- docs/integrations/tornado.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/integrations/tornado.rst b/docs/integrations/tornado.rst index 4dab1dc1c..b1ad956e3 100644 --- a/docs/integrations/tornado.rst +++ b/docs/integrations/tornado.rst @@ -83,6 +83,14 @@ Asynchronous The value returned by the yield is a ``HTTPResponse`` object. + To dynamically use Python if configuration DSN available, change your base handler on fly and make sure all concrete handlers are imported after this. + + .. code-block:: python + + from raven.contrib.tornado import SentryMixin + app.sentry_client = AsyncSentryClient(dsn) + BaseHandler.__bases__ = (SentryMixin, ) + BaseHandler.__bases__ + Synchronous ~~~~~~~~~~~ From 8d1692067d638a8971a38284dfadfbc4773d71a7 Mon Sep 17 00:00:00 2001 From: Si Feng Date: Sat, 15 Oct 2016 19:59:49 -0700 Subject: [PATCH 005/289] Fix Django 1.10 MIDDLEWARE_CLASSES warning --- raven/contrib/django/models.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/raven/contrib/django/models.py b/raven/contrib/django/models.py index 7aeb563c4..5f1b02560 100644 --- a/raven/contrib/django/models.py +++ b/raven/contrib/django/models.py @@ -230,11 +230,17 @@ def install_middleware(): name = 'raven.contrib.django.middleware.SentryMiddleware' all_names = (name, 'raven.contrib.django.middleware.SentryLogMiddleware') with settings_lock: - middleware_list = set(settings.MIDDLEWARE_CLASSES) - if not any(n in middleware_list for n in all_names): - settings.MIDDLEWARE_CLASSES = ( - name, - ) + tuple(settings.MIDDLEWARE_CLASSES) + # default settings.MIDDLEWARE is None + middleware_attr = 'MIDDLEWARE' if getattr(settings, + 'MIDDLEWARE', + None) is not None \ + else 'MIDDLEWARE_CLASSES' + # make sure to get an empty tuple when attr is None + middleware = getattr(settings, middleware_attr, ()) or () + if set(all_names).isdisjoint(set(middleware)): + setattr(settings, + middleware_attr, + (name,) + tuple(middleware)) if ( From 7a12ac8bcca764a576b4a27725ccb90cd42a505e Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 18 Oct 2016 11:27:40 +0200 Subject: [PATCH 006/289] Improve the close behavior of the wsgi middlware. This fixes #885 --- raven/middleware.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/raven/middleware.py b/raven/middleware.py index e00a5f243..df23b856f 100644 --- a/raven/middleware.py +++ b/raven/middleware.py @@ -43,6 +43,7 @@ class ClosingIterator(Iterator): def __init__(self, sentry, iterable, environ): self.sentry = sentry self.environ = environ + self._close = getattr(iterable, 'close', None) self.iterable = iter(iterable) self.closed = False @@ -65,9 +66,9 @@ def close(self): if self.closed: return try: - if hasattr(self.iterable, 'close'): + if self._close is not None: with common_exception_handling(self.environ, self.sentry): - self.iterable.close() + self._close() finally: self.sentry.client.context.clear() self.sentry.client.transaction.clear() From 3b40d4200171e47617a8ab0240764a1152f6609a Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 18 Oct 2016 11:30:01 +0200 Subject: [PATCH 007/289] Added missing changelog entries --- CHANGES | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGES b/CHANGES index 681954cc1..057285ddd 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,14 @@ +Version 5.28.0 +-------------- + +* Corrected an issue that caused `close()` on WSGI iterables to not be + correctly called. + +Version 5.27.1 +-------------- + +* Bugfix for transaction based culprits. + Version 5.27.0 -------------- From b85ef7d75b5a61323ee39879188c8b64cd089ebb Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 18 Oct 2016 11:35:07 +0200 Subject: [PATCH 008/289] Added changelog entry --- CHANGES | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES b/CHANGES index 057285ddd..5e191e871 100644 --- a/CHANGES +++ b/CHANGES @@ -3,6 +3,7 @@ Version 5.28.0 * Corrected an issue that caused `close()` on WSGI iterables to not be correctly called. +* Fixes the new Django 1.10 `MIDDLEWARE_CLASSES` warning. Version 5.27.1 -------------- From 5274f5438ea9cacc9e15b07a7985698bb92146b1 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 18 Oct 2016 11:42:14 +0200 Subject: [PATCH 009/289] 5.28.0 --- raven/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/__init__.py b/raven/__init__.py index d6e1c1fb8..4d1018823 100644 --- a/raven/__init__.py +++ b/raven/__init__.py @@ -12,7 +12,7 @@ __all__ = ('VERSION', 'Client', 'get_version') -VERSION = '5.28.0.dev0' +VERSION = '5.28.0' def _get_git_revision(path): From 1a4bb009f590a67a5ca701c4060c3560be73f6cf Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 18 Oct 2016 11:46:42 +0200 Subject: [PATCH 010/289] Read for 5.29 --- raven/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/__init__.py b/raven/__init__.py index 4d1018823..65da59e49 100644 --- a/raven/__init__.py +++ b/raven/__init__.py @@ -12,7 +12,7 @@ __all__ = ('VERSION', 'Client', 'get_version') -VERSION = '5.28.0' +VERSION = '5.29.0.dev0' def _get_git_revision(path): From a4ccee6d55d8ffe831cd61d13cc02e9cb1c2b4f9 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 18 Oct 2016 12:57:15 +0200 Subject: [PATCH 011/289] Added register_logging_handler --- raven/breadcrumbs.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/raven/breadcrumbs.py b/raven/breadcrumbs.py index 96d33ef9f..c2e435320 100644 --- a/raven/breadcrumbs.py +++ b/raven/breadcrumbs.py @@ -8,6 +8,7 @@ from raven.utils import once +special_logging_handlers = [] special_logger_handlers = {} @@ -97,6 +98,11 @@ def record(message=None, timestamp=None, level=None, category=None, def _record_log_breadcrumb(logger, level, msg, *args, **kwargs): + for handler in special_logging_handlers: + rv = handler(logger, level, msg, args, kwargs) + if rv: + return + handler = special_logger_handlers.get(logger.name) if handler is not None: rv = handler(logger, level, msg, args, kwargs) @@ -247,6 +253,15 @@ def register_special_log_handler(name_or_logger, callback): special_logger_handlers[name] = callback +def register_logging_handler(callback): + """Registers a callback for log handling. The callback is invoked + with give arguments: `logger`, `level`, `msg`, `args` and `kwargs` + which are the values passed to the logging system. If the callback + returns `True` the default handling is disabled. + """ + special_logging_handlers.append(callback) + + hooked_libraries = {} From 1f2170c3d8f792d33a46e1db50ec9606bf757a07 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 18 Oct 2016 16:06:24 +0200 Subject: [PATCH 012/289] Depend on newer pytest --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 8fa88b7dc..cf3f058e7 100755 --- a/setup.py +++ b/setup.py @@ -75,7 +75,7 @@ 'nose', 'pycodestyle', 'pytz', - 'pytest', + 'pytest>=3.0.0', 'pytest-django==2.9.1', 'pytest-timeout==0.4', 'requests', From 5dbaf82060268e2cee702fc0fc0b9fed8607bdd3 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 18 Oct 2016 16:08:00 +0200 Subject: [PATCH 013/289] Added breadcrumbs tests --- tests/breadcrumbs/tests.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tests/breadcrumbs/tests.py b/tests/breadcrumbs/tests.py index 008447920..1acc4b42d 100644 --- a/tests/breadcrumbs/tests.py +++ b/tests/breadcrumbs/tests.py @@ -119,3 +119,37 @@ def processor(data): assert data['category'] == 'category' assert data['type'] == 'the_type' assert data['data'] == {'foo': 'bar', 'extra': 'something'} + + def test_special_log_handlers(self): + name = __name__ + '.superspecial' + logger = logging.getLogger(name) + + def handler(logger, level, msg, args, kwargs): + assert logger.name == name + assert msg == 'aha!' + return True + + breadcrumbs.register_special_log_handler(name, handler) + + client = Client('http://foo:bar@example.com/0') + with client.context: + logger.debug('aha!') + crumbs = client.context.breadcrumbs.get_buffer() + assert len(crumbs) == 0 + + def test_logging_handlers(self): + name = __name__ + '.superspecial2' + logger = logging.getLogger(name) + + def handler(logger, level, msg, args, kwargs): + if logger.name == name: + assert msg == 'aha!' + return True + + breadcrumbs.register_logging_handler(handler) + + client = Client('http://foo:bar@example.com/0') + with client.context: + logger.debug('aha!') + crumbs = client.context.breadcrumbs.get_buffer() + assert len(crumbs) == 0 From cef503fa2fd69fd0b0f47ba8f2921e8b5ce53fd9 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 18 Oct 2016 16:08:06 +0200 Subject: [PATCH 014/289] Added docs for new breadcrumbs feature --- docs/breadcrumbs.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/breadcrumbs.rst b/docs/breadcrumbs.rst index 71ab5b1f4..ceb40166d 100644 --- a/docs/breadcrumbs.rst +++ b/docs/breadcrumbs.rst @@ -65,6 +65,12 @@ the :py:func:`~raven.breadcrumbs.ignore_logger` and Typically it makes sense to invoke :py:func:`~raven.breadcrumbs.record` from it. +.. py:function:: raven.breadcrumbs.register_logging_handler(callback) + + This is similar to :func:`~raven.breadcrumbs.register_special_log_handler` + but it adds a global callback that is invoked for all log entries. + Otherwise it works the same but multiple handlers can be registered. + Manually Emitting Breadcrumbs ----------------------------- From 06adf0d31aeb5b39c42a751e753f82fed12b7545 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 18 Oct 2016 16:08:09 +0200 Subject: [PATCH 015/289] Added changelog entry --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 5e191e871..76a50c7aa 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 5.29.0 +-------------- + +* Added `register_logging_handler`. + Version 5.28.0 -------------- From 2eb34745b0dbd8e02b76a29ef800f5071a032e07 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 18 Oct 2016 19:55:19 +0200 Subject: [PATCH 016/289] Removed bad middleware mixin from django code --- raven/contrib/django/middleware/wsgi.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/raven/contrib/django/middleware/wsgi.py b/raven/contrib/django/middleware/wsgi.py index e762f0099..86e4e7ece 100644 --- a/raven/contrib/django/middleware/wsgi.py +++ b/raven/contrib/django/middleware/wsgi.py @@ -7,19 +7,11 @@ """ from __future__ import absolute_import -try: - # Django >= 1.10 - from django.utils.deprecation import MiddlewareMixin -except ImportError: - # Not required for Django <= 1.9, see: - # https://docs.djangoproject.com/en/1.10/topics/http/middleware/#upgrading-pre-django-1-10-style-middleware - MiddlewareMixin = object - from raven.middleware import Sentry from raven.utils import memoize -class Sentry(Sentry, MiddlewareMixin): +class Sentry(Sentry): """ Identical to the default WSGI middleware except that the client comes dynamically via ``get_client From 22ccb6401bc40aae9f858d08826ee5104a1b6c51 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 18 Oct 2016 19:59:18 +0200 Subject: [PATCH 017/289] Added changelog entry --- CHANGES | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES b/CHANGES index 76a50c7aa..ca6737798 100644 --- a/CHANGES +++ b/CHANGES @@ -2,6 +2,7 @@ Version 5.29.0 -------------- * Added `register_logging_handler`. +* Removed bad mixin from django's WSGI middleware Version 5.28.0 -------------- From 43b98811f837077fd56149856648d3cabd9ee66c Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 18 Oct 2016 20:02:16 +0200 Subject: [PATCH 018/289] Revert "Merge pull request #882 from do3cc/rest_framework_support" This reverts commit db7d970999e7212bc848349ab5bc1e372980ea9d, reversing changes made to 7ca8746a8375d69f07de13103fcfbab91a868fc6. This broke the world --- CHANGES | 1 - raven/contrib/django/client.py | 10 +--------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/CHANGES b/CHANGES index ca6737798..d568d3b44 100644 --- a/CHANGES +++ b/CHANGES @@ -19,7 +19,6 @@ Version 5.27.1 Version 5.27.0 -------------- -* Added support for extracting data from rest_framework in Django integration * Updated CA bundle. * Added transaction-based culprits for Celery, Django, and Flask. * Fixed an issue where ``ignore_exceptions`` wasn't respected. diff --git a/raven/contrib/django/client.py b/raven/contrib/django/client.py index 38c78df00..d343c62ae 100644 --- a/raven/contrib/django/client.py +++ b/raven/contrib/django/client.py @@ -34,14 +34,6 @@ __all__ = ('DjangoClient',) -VALID_HTTP_REQUESTS = (HttpRequest, ) - -try: - from rest_framework.request import Request - VALID_HTTP_REQUESTS += (Request, ) -except ImportError: - pass - class _FormatConverter(object): @@ -265,7 +257,7 @@ def capture(self, event_type, request=None, **kwargs): if request is None: request = getattr(SentryLogMiddleware.thread, 'request', None) - is_http_request = isinstance(request, VALID_HTTP_REQUESTS) + is_http_request = isinstance(request, HttpRequest) if is_http_request: data.update(self.get_data_from_request(request)) From f3a50ee61944c95a2dd1d4657921eead36c5b5ed Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 18 Oct 2016 20:04:20 +0200 Subject: [PATCH 019/289] Updated changelog --- CHANGES | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES b/CHANGES index d568d3b44..f25a50ef2 100644 --- a/CHANGES +++ b/CHANGES @@ -3,6 +3,8 @@ Version 5.29.0 * Added `register_logging_handler`. * Removed bad mixin from django's WSGI middleware +* Removed "support for extracing data from rest_framework" because + this broke code. Version 5.28.0 -------------- @@ -19,6 +21,7 @@ Version 5.27.1 Version 5.27.0 -------------- +* Added support for extracting data from rest_framework in Django integration * Updated CA bundle. * Added transaction-based culprits for Celery, Django, and Flask. * Fixed an issue where ``ignore_exceptions`` wasn't respected. From a1f59164990b624dd895f0b60d3e342c9ba6060a Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 18 Oct 2016 20:05:24 +0200 Subject: [PATCH 020/289] 5.29.0 --- raven/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/__init__.py b/raven/__init__.py index 65da59e49..cbf97c6df 100644 --- a/raven/__init__.py +++ b/raven/__init__.py @@ -12,7 +12,7 @@ __all__ = ('VERSION', 'Client', 'get_version') -VERSION = '5.29.0.dev0' +VERSION = '5.29.0' def _get_git_revision(path): From 1bccd7d7dda944607f74a974cff4ce7e91092db6 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 18 Oct 2016 20:06:30 +0200 Subject: [PATCH 021/289] Ready for 5.30.0 --- raven/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/__init__.py b/raven/__init__.py index cbf97c6df..d1ed85065 100644 --- a/raven/__init__.py +++ b/raven/__init__.py @@ -12,7 +12,7 @@ __all__ = ('VERSION', 'Client', 'get_version') -VERSION = '5.29.0' +VERSION = '5.30.0.dev0' def _get_git_revision(path): From 653c1d298ad42e6bf898bf05b21ab0e5a46f3447 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 19 Oct 2016 20:11:38 +0200 Subject: [PATCH 022/289] Fixed a missing domain prefix in docs --- docs/breadcrumbs.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/breadcrumbs.rst b/docs/breadcrumbs.rst index ceb40166d..83c6f7a3e 100644 --- a/docs/breadcrumbs.rst +++ b/docs/breadcrumbs.rst @@ -67,7 +67,7 @@ the :py:func:`~raven.breadcrumbs.ignore_logger` and .. py:function:: raven.breadcrumbs.register_logging_handler(callback) - This is similar to :func:`~raven.breadcrumbs.register_special_log_handler` + This is similar to :py:func:`~raven.breadcrumbs.register_special_log_handler` but it adds a global callback that is invoked for all log entries. Otherwise it works the same but multiple handlers can be registered. From 0d8911d3da39f14f555ddda0302a367f151e5d1d Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 19 Oct 2016 20:12:01 +0200 Subject: [PATCH 023/289] Keep the original type when playing with django middlware settings. This fixes #890 --- raven/contrib/django/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/contrib/django/models.py b/raven/contrib/django/models.py index 5f1b02560..ec69b3d73 100644 --- a/raven/contrib/django/models.py +++ b/raven/contrib/django/models.py @@ -240,7 +240,7 @@ def install_middleware(): if set(all_names).isdisjoint(set(middleware)): setattr(settings, middleware_attr, - (name,) + tuple(middleware)) + type(middleware)((name,)) + middleware) if ( From a57f4aeea145211404dc4b2eb17c4e53082a5ab4 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 19 Oct 2016 20:12:27 +0200 Subject: [PATCH 024/289] Updated changelog --- CHANGES | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES b/CHANGES index f25a50ef2..33804bf40 100644 --- a/CHANGES +++ b/CHANGES @@ -5,6 +5,8 @@ Version 5.29.0 * Removed bad mixin from django's WSGI middleware * Removed "support for extracing data from rest_framework" because this broke code. +* Keep the original type for the django middleware settings if we + change them. Version 5.28.0 -------------- From 39a75224e58d3c8694a9346202299a645ea49182 Mon Sep 17 00:00:00 2001 From: MeredithAnya Date: Wed, 19 Oct 2016 11:17:55 -0700 Subject: [PATCH 025/289] Change django release path (#889) * release path change * release path change * use os.pardir --- docs/integrations/django.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/integrations/django.rst b/docs/integrations/django.rst index 333f75aab..43ba2a2a2 100644 --- a/docs/integrations/django.rst +++ b/docs/integrations/django.rst @@ -28,7 +28,7 @@ Additional settings for the client are configured using the 'dsn': '___DSN___', # If you are using git, you can also automatically configure the # release based on the git info. - 'release': raven.fetch_git_sha(os.path.dirname(__file__)), + 'release': raven.fetch_git_sha(os.path.dirname(os.pardir)), } Once you've configured the client, you can test it using the standard Django From 00656eece4d44226788885d78574a272bfa6637a Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 19 Oct 2016 20:20:37 +0200 Subject: [PATCH 026/289] 5.30.0 --- CHANGES | 8 ++++++-- raven/__init__.py | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index 33804bf40..1660115b5 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,9 @@ +Version 5.30.0 +-------------- + +* Keep the original type for the django middleware settings if we + change them. + Version 5.29.0 -------------- @@ -5,8 +11,6 @@ Version 5.29.0 * Removed bad mixin from django's WSGI middleware * Removed "support for extracing data from rest_framework" because this broke code. -* Keep the original type for the django middleware settings if we - change them. Version 5.28.0 -------------- diff --git a/raven/__init__.py b/raven/__init__.py index d1ed85065..00995c02c 100644 --- a/raven/__init__.py +++ b/raven/__init__.py @@ -12,7 +12,7 @@ __all__ = ('VERSION', 'Client', 'get_version') -VERSION = '5.30.0.dev0' +VERSION = '5.30.0' def _get_git_revision(path): From 9cc4c95c242eb7eb0d57ffcdecc8dd3594d3b4b7 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 19 Oct 2016 20:22:01 +0200 Subject: [PATCH 027/289] Ready for next dev --- raven/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/__init__.py b/raven/__init__.py index 00995c02c..7a57ac61a 100644 --- a/raven/__init__.py +++ b/raven/__init__.py @@ -12,7 +12,7 @@ __all__ = ('VERSION', 'Client', 'get_version') -VERSION = '5.30.0' +VERSION = '5.31.0.dev0' def _get_git_revision(path): From e2fc94bcb50988c04821f1091fab09d51352f05a Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 21 Oct 2016 21:21:07 +0200 Subject: [PATCH 028/289] Correctly fixed #886 --- raven/contrib/django/middleware/__init__.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/raven/contrib/django/middleware/__init__.py b/raven/contrib/django/middleware/__init__.py index 1ecec8d1d..43b061f83 100644 --- a/raven/contrib/django/middleware/__init__.py +++ b/raven/contrib/django/middleware/__init__.py @@ -78,7 +78,16 @@ def process_response(self, request, response): return response -class SentryMiddleware(threading.local): +# We need to make a base class for our sentry middleware that is thread +# local but at the same time has the new fnagled middleware mixin applied +# if such a thing exists. +if MiddlewareMixin is object: + _SentryMiddlewareBase = threading.local +else: + _SentryMiddlewareBase = type('_SentryMiddlewareBase', (MiddlewareMixin, threading.local), {}) + + +class SentryMiddleware(_SentryMiddlewareBase): resolver = RouteResolver() # backwards compat From 5d2a680c114e4229355534c6effacdad880da620 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 21 Oct 2016 21:21:51 +0200 Subject: [PATCH 029/289] Added changelog entry --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 1660115b5..29cef02e9 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 5.31.0 +-------------- + +* Improved fix for the Django middleware regression. + Version 5.30.0 -------------- From c03fc27515f3370a185ccef17690a70204ea1d9d Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 21 Oct 2016 21:22:20 +0200 Subject: [PATCH 030/289] 5.31.0 --- raven/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/__init__.py b/raven/__init__.py index 7a57ac61a..74d07cde2 100644 --- a/raven/__init__.py +++ b/raven/__init__.py @@ -12,7 +12,7 @@ __all__ = ('VERSION', 'Client', 'get_version') -VERSION = '5.31.0.dev0' +VERSION = '5.31.0' def _get_git_revision(path): From f9e9bcd28051e7193969e0c495a1cd413df3e29f Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 21 Oct 2016 21:23:48 +0200 Subject: [PATCH 031/289] Ready for 5.32 --- raven/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/__init__.py b/raven/__init__.py index 74d07cde2..725963d5f 100644 --- a/raven/__init__.py +++ b/raven/__init__.py @@ -12,7 +12,7 @@ __all__ = ('VERSION', 'Client', 'get_version') -VERSION = '5.31.0' +VERSION = '5.32.0.dev0' def _get_git_revision(path): From 0501fe213da0b0a3676b77b436cf5c0058e41d89 Mon Sep 17 00:00:00 2001 From: Przemek Kaminski Date: Tue, 25 Oct 2016 06:16:16 +0200 Subject: [PATCH 032/289] Added tests for Django Handler 'tags' argument --- tests/contrib/django/tests.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/contrib/django/tests.py b/tests/contrib/django/tests.py index 3326cca10..45b806b2a 100644 --- a/tests/contrib/django/tests.py +++ b/tests/contrib/django/tests.py @@ -587,6 +587,23 @@ def test_request_kwarg(self): http = event['request'] assert http['method'] == 'POST' + def test_tags(self): + tags = {'tag1': 'test'} + handler = SentryHandler(tags=tags) + + logger = self.logger + logger.handlers = [] + logger.addHandler(handler) + + logger.error('This is a test error') + + assert len(self.raven.events) == 1 + event = self.raven.events.pop(0) + assert 'tags' in event + # event['tags'] also contains some other data, like 'site' + assert 'tag1' in event['tags'] + assert event['tags']['tag1'] == tags['tag1'] + class CeleryIsolatedClientTest(TestCase): def setUp(self): From edf58536f3b14f11aa5bf86b9e1d95a07ec9d6a8 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Tue, 25 Oct 2016 13:31:03 -0700 Subject: [PATCH 033/289] Remove default transaction This causes issues when things are not yet instrumented, and we're left with a useless transaction name which is identical for all operations. --- raven/base.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/raven/base.py b/raven/base.py index 5d5a996ce..6ff424c36 100644 --- a/raven/base.py +++ b/raven/base.py @@ -37,7 +37,7 @@ from raven._compat import text_type, iteritems from raven.utils.encoding import to_unicode from raven.utils.serializer import transform -from raven.utils.stacks import get_stack_info, iter_stack_frames, slim_string +from raven.utils.stacks import get_stack_info, iter_stack_frames from raven.utils.transaction import TransactionStack from raven.transport.registry import TransportRegistry, default_transports @@ -188,9 +188,6 @@ def __init__(self, dsn=None, raise_send_errors=False, transport=None, self.environment = o.get('environment') or None self.release = o.get('release') or os.environ.get('HEROKU_SLUG_COMMIT') self.transaction = TransactionStack() - # find the root transaction as the command which launched this - # process - self.transaction.push(slim_string(' '.join(sys.argv), 128)) self.ignore_exceptions = set(o.get('ignore_exceptions') or ()) From 1fd8bb66ae91c6340c68ca82bbe05f8ee0329273 Mon Sep 17 00:00:00 2001 From: Johan Steffner Date: Thu, 27 Oct 2016 09:40:11 +0200 Subject: [PATCH 034/289] Update event values order for exceptions --- raven/base.py | 6 +++--- raven/events.py | 4 ++-- tests/base/tests.py | 10 +++++----- tests/contrib/bottle/tests.py | 2 +- tests/contrib/django/tests.py | 16 ++++++++-------- tests/contrib/flask/tests.py | 2 +- tests/contrib/test_celery.py | 2 +- tests/contrib/webpy/tests.py | 2 +- tests/events/tests.py | 10 +++++----- tests/handlers/logbook/tests.py | 2 +- tests/handlers/logging/tests.py | 2 +- tests/middleware/tests.py | 6 +++--- tests/processors/tests.py | 4 ++-- 13 files changed, 34 insertions(+), 34 deletions(-) diff --git a/raven/base.py b/raven/base.py index 6ff424c36..780982357 100644 --- a/raven/base.py +++ b/raven/base.py @@ -623,7 +623,7 @@ def _iter_frames(self, data): for frame in data['stacktrace']['frames']: yield frame if 'exception' in data: - for frame in data['exception']['values'][0]['stacktrace']['frames']: + for frame in data['exception']['values'][-1]['stacktrace']['frames']: yield frame def _successful_send(self): @@ -655,9 +655,9 @@ def _log_failed_submission(self, data): """ message = data.pop('message', '') output = [message] - if 'exception' in data and 'stacktrace' in data['exception']['values'][0]: + if 'exception' in data and 'stacktrace' in data['exception']['values'][-1]: # try to reconstruct a reasonable version of the exception - for frame in data['exception']['values'][0]['stacktrace']['frames']: + for frame in data['exception']['values'][-1]['stacktrace']['frames']: output.append(' File "%(fn)s", line %(lineno)s, in %(func)s' % { 'fn': frame.get('filename', 'unknown_filename'), 'lineno': frame.get('lineno', -1), diff --git a/raven/events.py b/raven/events.py index 7434fe9b0..3627823fc 100644 --- a/raven/events.py +++ b/raven/events.py @@ -78,7 +78,7 @@ class Exception(BaseEvent): name = 'exception' def to_string(self, data): - exc = data[self.name]['values'][0] + exc = data[self.name]['values'][-1] if exc['value']: return '%s: %s' % (exc['type'], exc['value']) return exc['type'] @@ -115,7 +115,7 @@ def capture(self, exc_info=None, **kwargs): values = [] for exc_info in _chained_exceptions(exc_info): value = self._get_value(*exc_info) - values.append(value) + values.insert(0, value) return { 'level': kwargs.get('level', logging.ERROR), diff --git a/tests/base/tests.py b/tests/base/tests.py index 83a124259..cb988da2c 100644 --- a/tests/base/tests.py +++ b/tests/base/tests.py @@ -292,7 +292,7 @@ def test_exception_event(self): event = self.client.events.pop(0) self.assertEquals(event['message'], 'ValueError: foo') self.assertTrue('exception' in event) - exc = event['exception']['values'][0] + exc = event['exception']['values'][-1] self.assertEquals(exc['type'], 'ValueError') self.assertEquals(exc['value'], 'foo') self.assertEquals(exc['module'], ValueError.__module__) # this differs in some Python versions @@ -316,7 +316,7 @@ def test_exception_event_true_exc_info(self): event = self.client.events.pop(0) self.assertEquals(event['message'], 'ValueError: foo') self.assertTrue('exception' in event) - exc = event['exception']['values'][0] + exc = event['exception']['values'][-1] stacktrace = exc['stacktrace'] self.assertEquals(len(stacktrace['frames']), 1) frame = stacktrace['frames'][0] @@ -347,7 +347,7 @@ def test2(): self.assertEquals(len(self.client.events), 1) event = self.client.events.pop(0) self.assertEquals(event['message'], 'DecoratorTestException') - exc = event['exception']['values'][0] + exc = event['exception']['values'][-1] self.assertEquals(exc['type'], 'DecoratorTestException') self.assertEquals(exc['module'], self.DecoratorTestException.__module__) stacktrace = exc['stacktrace'] @@ -382,7 +382,7 @@ def test4(): self.assertEquals(len(self.client.events), 1) event = self.client.events.pop(0) self.assertEquals(event['message'], 'DecoratorTestException') - exc = event['exception']['values'][0] + exc = event['exception']['values'][-1] self.assertEquals(exc['type'], 'DecoratorTestException') self.assertEquals(exc['module'], self.DecoratorTestException.__module__) stacktrace = exc['stacktrace'] @@ -563,7 +563,7 @@ def test_marks_in_app_frames_for_exception(self): }) event = client.events.pop(0) - frames = event['exception']['values'][0]['stacktrace']['frames'] + frames = event['exception']['values'][-1]['stacktrace']['frames'] assert frames[0]['in_app'] assert not frames[1]['in_app'] assert not frames[2]['in_app'] diff --git a/tests/contrib/bottle/tests.py b/tests/contrib/bottle/tests.py index 1c1da6eb5..bfa7ada00 100644 --- a/tests/contrib/bottle/tests.py +++ b/tests/contrib/bottle/tests.py @@ -66,7 +66,7 @@ def test_error(self): event = self.raven.events.pop(0) assert 'exception' in event - exc = event['exception']['values'][0] + exc = event['exception']['values'][-1] self.assertEquals(exc['type'], 'ValueError') def test_captureException_captures_http(self): diff --git a/tests/contrib/django/tests.py b/tests/contrib/django/tests.py index 3326cca10..89edd6568 100644 --- a/tests/contrib/django/tests.py +++ b/tests/contrib/django/tests.py @@ -156,7 +156,7 @@ def test_signal_integration(self): assert len(self.raven.events) == 1 event = self.raven.events.pop(0) assert 'exception' in event - exc = event['exception']['values'][0] + exc = event['exception']['values'][-1] assert exc['type'] == 'TypeError' assert exc['value'], "int() argument must be a string or a number == not 'NoneType'" assert event['level'] == logging.ERROR @@ -169,7 +169,7 @@ def test_view_exception(self): assert len(self.raven.events) == 1 event = self.raven.events.pop(0) assert 'exception' in event - exc = event['exception']['values'][0] + exc = event['exception']['values'][-1] assert exc['type'] == 'Exception' assert exc['value'] == 'view exception' assert event['level'] == logging.ERROR @@ -260,7 +260,7 @@ def test_request_middleware_exception(self): event = self.raven.events.pop(0) assert 'exception' in event - exc = event['exception']['values'][0] + exc = event['exception']['values'][-1] assert exc['type'] == 'ImportError' assert exc['value'] == 'request' assert event['level'] == logging.ERROR @@ -276,7 +276,7 @@ def test_response_middlware_exception(self): event = self.raven.events.pop(0) assert 'exception' in event - exc = event['exception']['values'][0] + exc = event['exception']['values'][-1] assert exc['type'] == 'ImportError' assert exc['value'] == 'response' assert event['level'] == logging.ERROR @@ -293,7 +293,7 @@ def test_broken_500_handler_with_middleware(self): event = self.raven.events.pop(0) assert 'exception' in event - exc = event['exception']['values'][0] + exc = event['exception']['values'][-1] assert exc['type'] == 'Exception' assert exc['value'] == 'view exception' assert event['level'] == logging.ERROR @@ -302,7 +302,7 @@ def test_broken_500_handler_with_middleware(self): event = self.raven.events.pop(0) assert 'exception' in event - exc = event['exception']['values'][0] + exc = event['exception']['values'][-1] assert exc['type'] == 'ValueError' assert exc['value'] == 'handler500' assert event['level'] == logging.ERROR @@ -316,7 +316,7 @@ def test_view_middleware_exception(self): event = self.raven.events.pop(0) assert 'exception' in event - exc = event['exception']['values'][0] + exc = event['exception']['values'][-1] assert exc['type'] == 'ImportError' assert exc['value'] == 'view' assert event['level'] == logging.ERROR @@ -498,7 +498,7 @@ def test_marks_django_frames_correctly(self): assert len(self.raven.events) == 1 event = self.raven.events.pop(0) - frames = event['exception']['values'][0]['stacktrace']['frames'] + frames = event['exception']['values'][-1]['stacktrace']['frames'] for frame in frames: if frame['module'].startswith('django.'): assert frame.get('in_app') is False diff --git a/tests/contrib/flask/tests.py b/tests/contrib/flask/tests.py index 73dff6a76..4fffc5301 100644 --- a/tests/contrib/flask/tests.py +++ b/tests/contrib/flask/tests.py @@ -106,7 +106,7 @@ def test_error_handler(self): event = self.raven.events.pop(0) assert 'exception' in event - exc = event['exception']['values'][0] + exc = event['exception']['values'][-1] self.assertEquals(exc['type'], 'ValueError') self.assertEquals(exc['value'], 'hello world') self.assertEquals(event['level'], logging.ERROR) diff --git a/tests/contrib/test_celery.py b/tests/contrib/test_celery.py index bc3de0fef..95a3b745a 100644 --- a/tests/contrib/test_celery.py +++ b/tests/contrib/test_celery.py @@ -26,7 +26,7 @@ def dummy_task(x, y): dummy_task.delay(1, 0) assert len(self.client.events) == 1 event = self.client.events[0] - exception = event['exception']['values'][0] + exception = event['exception']['values'][-1] assert event['culprit'] == 'dummy_task' assert exception['type'] == 'ZeroDivisionError' diff --git a/tests/contrib/webpy/tests.py b/tests/contrib/webpy/tests.py index 82528ddb9..9b13e71e7 100644 --- a/tests/contrib/webpy/tests.py +++ b/tests/contrib/webpy/tests.py @@ -53,7 +53,7 @@ def test_get(self): event = self.store.events.pop() assert 'exception' in event - exc = event['exception']['values'][0] + exc = event['exception']['values'][-1] self.assertEquals(exc['type'], 'ValueError') self.assertEquals(exc['value'], 'That\'s what she said') self.assertEquals(event['message'], 'ValueError: That\'s what she said') diff --git a/tests/events/tests.py b/tests/events/tests.py index e5795b402..484dfe70e 100644 --- a/tests/events/tests.py +++ b/tests/events/tests.py @@ -18,7 +18,7 @@ def transform_expected(self, expected): else: # Otherwise, we only report the first element. def transform_expected(self, expected): - return expected[:1] + return expected[-1:] def check_capture(self, expected): """ @@ -51,7 +51,7 @@ def test_nested(self): try: raise KeyError() except Exception: - self.check_capture(['KeyError', 'ValueError']) + self.check_capture(['ValueError', 'KeyError']) def test_raise_from(self): try: @@ -60,7 +60,7 @@ def test_raise_from(self): try: six.raise_from(KeyError(), exc) except Exception: - self.check_capture(['KeyError', 'ValueError']) + self.check_capture(['ValueError', 'KeyError']) def test_raise_from_different(self): try: @@ -69,7 +69,7 @@ def test_raise_from_different(self): try: six.raise_from(KeyError(), TypeError()) except Exception: - self.check_capture(['KeyError', 'TypeError']) + self.check_capture(['TypeError', 'KeyError']) def test_handles_self_referencing(self): try: @@ -93,7 +93,7 @@ def test_handles_self_referencing(self): try: six.raise_from(exc, exc2) except Exception: - self.check_capture(['ValueError', 'KeyError']) + self.check_capture(['KeyError', 'ValueError']) else: pytest.fail() else: diff --git a/tests/handlers/logbook/tests.py b/tests/handlers/logbook/tests.py index c3810176b..1f00c29cb 100644 --- a/tests/handlers/logbook/tests.py +++ b/tests/handlers/logbook/tests.py @@ -81,7 +81,7 @@ def test_logger(self): self.assertEquals(event['message'], 'This is a test info with an exception') assert 'exception' in event - exc = event['exception']['values'][0] + exc = event['exception']['values'][-1] self.assertEquals(exc['type'], 'ValueError') self.assertEquals(exc['value'], 'This is a test ValueError') self.assertTrue('sentry.interfaces.Message' in event) diff --git a/tests/handlers/logging/tests.py b/tests/handlers/logging/tests.py index bf8063a71..05981cabf 100644 --- a/tests/handlers/logging/tests.py +++ b/tests/handlers/logging/tests.py @@ -134,7 +134,7 @@ def test_logger_exc_info(self): self.assertEqual(event['message'], 'This is a test info with an exception') assert 'exception' in event - exc = event['exception']['values'][0] + exc = event['exception']['values'][-1] self.assertEqual(exc['type'], 'ValueError') self.assertEqual(exc['value'], 'This is a test ValueError') self.assertTrue('sentry.interfaces.Message' in event) diff --git a/tests/middleware/tests.py b/tests/middleware/tests.py index d81a92703..76953ddb6 100644 --- a/tests/middleware/tests.py +++ b/tests/middleware/tests.py @@ -93,7 +93,7 @@ def test_captures_error_in_iteration(self): event = self.client.events.pop(0) assert 'exception' in event - exc = event['exception']['values'][0] + exc = event['exception']['values'][-1] self.assertEquals(exc['type'], 'ValueError') self.assertEquals(exc['value'], 'hello world') self.assertEquals(event['level'], logging.ERROR) @@ -140,7 +140,7 @@ def test_systemexit_is_captured(self): event = self.client.events.pop(0) assert 'exception' in event - exc = event['exception']['values'][0] + exc = event['exception']['values'][-1] self.assertEquals(exc['type'], 'SystemExit') self.assertEquals(exc['value'], '1') self.assertEquals(event['level'], logging.ERROR) @@ -160,7 +160,7 @@ def test_keyboard_interrupt_is_captured(self): event = self.client.events.pop(0) assert 'exception' in event - exc = event['exception']['values'][0] + exc = event['exception']['values'][-1] self.assertEquals(exc['type'], 'KeyboardInterrupt') self.assertEquals(exc['value'], '') self.assertEquals(event['level'], logging.ERROR) diff --git a/tests/processors/tests.py b/tests/processors/tests.py index a38cc56a4..c4c5f52e3 100644 --- a/tests/processors/tests.py +++ b/tests/processors/tests.py @@ -127,12 +127,12 @@ def test_stacktrace(self, *args, **kwargs): proc = SanitizePasswordsProcessor(Mock()) result = proc.process(data) - # data['exception']['values'][0]['stacktrace']['frames'][0]['vars'] + # data['exception']['values'][-1]['stacktrace']['frames'][0]['vars'] self.assertTrue('exception' in result) exception = result['exception'] self.assertTrue('values' in exception) values = exception['values'] - stack = values[0]['stacktrace'] + stack = values[-1]['stacktrace'] self.assertTrue('frames' in stack) self.assertEquals(len(stack['frames']), 2) From c5e44f629b8175167594654b9be7aa0b2aee1609 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 27 Oct 2016 14:09:26 -0700 Subject: [PATCH 035/289] Update interfaces link --- docs/breadcrumbs.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/breadcrumbs.rst b/docs/breadcrumbs.rst index 83c6f7a3e..51348cf27 100644 --- a/docs/breadcrumbs.rst +++ b/docs/breadcrumbs.rst @@ -84,7 +84,7 @@ reference to that. .. py:function:: raven.breadcrumbs.record(**options) This function accepts keyword arguments matching the attributes of a - breadcrumb. For more information see :doc:`/clientdev/interfaces`. + breadcrumb. For more information see :doc:`/clientdev/interfaces/index`. Additionally a `processor` callback can be passed which will be invoked to process the data if the crumb was not rejected. From e665543d712f1531fc94ddac60177b7a78f8281f Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 27 Oct 2016 14:21:49 -0700 Subject: [PATCH 036/289] Fix up configuration examples --- docs/integrations/flask.rst | 59 +++++++++++++++++++++---------------- 1 file changed, 34 insertions(+), 25 deletions(-) diff --git a/docs/integrations/flask.rst b/docs/integrations/flask.rst index 0b31392a7..8491a9d50 100644 --- a/docs/integrations/flask.rst +++ b/docs/integrations/flask.rst @@ -22,14 +22,18 @@ your environment under the ``SENTRY_DSN`` key. Extended Setup -------------- -You can optionally configure logging too:: +You can optionally configure logging too: + +.. sourcecode:: python import logging from raven.contrib.flask import Sentry sentry = Sentry(app, logging=True, level=logging.ERROR, \ logging_exclusions=("logger1", "logger2", ...)) -Building applications on the fly? You can use Raven's ``init_app`` hook:: +Building applications on the fly? You can use Raven's ``init_app`` hook: + +.. sourcecode:: python sentry = Sentry(dsn='http://public_key:secret_key@example.com/1') @@ -38,7 +42,9 @@ Building applications on the fly? You can use Raven's ``init_app`` hook:: sentry.init_app(app) return app -You can pass parameters in the ``init_app`` hook:: +You can pass parameters in the ``init_app`` hook: + +.. sourcecode:: python sentry = Sentry() @@ -53,11 +59,16 @@ Settings -------- Additional settings for the client can be configured using -``SENTRY_`` in your application's configuration:: +``SENTRY_CONFIG`` in your application's configuration: + +.. sourcecode:: python class MyConfig(object): - SENTRY_DSN = '___DSN___' - SENTRY_INCLUDE_PATHS = ['myproject'] + SENTRY_CONFIG = { + 'dsn': '___DSN___', + 'include_paths': ['myproject'], + 'release': raven.fetch_git_sha(os.path.dirname(__file__)), + } If `Flask-Login `_ is used by your application (including `Flask-Security @@ -66,7 +77,9 @@ be captured when an exception or message is captured. By default, only the ``id`` (current_user.get_id()), ``is_authenticated``, and ``is_anonymous`` is captured for the user. If you would like additional attributes on the ``current_user`` to be captured, you can configure them -using ``SENTRY_USER_ATTRS``:: +using ``SENTRY_USER_ATTRS``: + +.. sourcecode:: python class MyConfig(object): SENTRY_USER_ATTRS = ['username', 'first_name', 'last_name', 'email'] @@ -76,16 +89,19 @@ additional attributes will be available under ``sentry.interfaces.User.data`` You can specify the types of exceptions that should not be reported by -Sentry client in your application by setting the -``RAVEN_IGNORE_EXCEPTIONS`` configuration value on your Flask app -configuration:: +Sentry client in your application by setting the ``ignore_exceptions`` +configuration value: + +.. sourcecode:: python class MyExceptionType(Exception): def __init__(self, message): super(MyExceptionType, self).__init__(message) app = Flask(__name__) - app.config["RAVEN_IGNORE_EXCEPTIONS"] = [MyExceptionType] + app.config['SENTRY_CONFIG'] = { + 'ignore_exceptions': [MyExceptionType], + } Usage ----- @@ -95,27 +111,20 @@ capture uncaught exceptions within Flask. If you want to send additional events, a couple of shortcuts are provided on the Sentry Flask middleware object. -Capture an arbitrary exception by calling ``captureException``:: +Capture an arbitrary exception by calling ``captureException``: + +.. sourcecode:: python try: 1 / 0 except ZeroDivisionError: sentry.captureException() -Log a generic message with ``captureMessage``:: - - sentry.captureMessage('hello, world!') - -Configure ---------- - -To configure Sentry to add additional context to errors (such as -release or environment), simply add the keys to ``app.config`` +Log a generic message with ``captureMessage``: - app.config['SENTRY_RELEASE'] = raven.fetch_git_sha(os.path.dirname(__file__)) +.. sourcecode:: python -Sentry automatically looks for ``SENTRY_RELEASE``, ``SENTRY_CONTEXT``, -``SENTRY_ENVIRONMENT``, ``SENTRY_TAGS``, and ``SENTRY_IGNORE_EXCEPTIONS``. + sentry.captureMessage('hello, world!') Getting The Last Event ID ------------------------- @@ -124,7 +133,7 @@ If possible, the last Sentry event ID is stored in the request context ``g.sentry_event_id`` variable. This allow to present the user an error ID if have done a custom error 500 page. -.. code-block:: html+jinja +.. sourcecode:: html+jinja

Error 500

{% if g.sentry_event_id %} From 8a04b60805ebcde45c596c917d50b5fc80e92b36 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 10 Nov 2016 12:50:56 +0900 Subject: [PATCH 037/289] lint --- raven/__init__.py | 1 + raven/contrib/django/__init__.py | 1 + raven/contrib/django/middleware/__init__.py | 1 + raven/contrib/django/serializers.py | 2 ++ raven/utils/__init__.py | 1 + 5 files changed, 6 insertions(+) diff --git a/raven/__init__.py b/raven/__init__.py index 725963d5f..0658062ca 100644 --- a/raven/__init__.py +++ b/raven/__init__.py @@ -45,6 +45,7 @@ def get_version(): base = '%s (%s)' % (base, __build__) return base + __build__ = get_revision() __docformat__ = 'restructuredtext en' diff --git a/raven/contrib/django/__init__.py b/raven/contrib/django/__init__.py index 4ce0b9538..23036a6ea 100644 --- a/raven/contrib/django/__init__.py +++ b/raven/contrib/django/__init__.py @@ -9,4 +9,5 @@ default_app_config = 'raven.contrib.django.apps.RavenConfig' + from .client import DjangoClient # NOQA diff --git a/raven/contrib/django/middleware/__init__.py b/raven/contrib/django/middleware/__init__.py index 43b061f83..06c2b9109 100644 --- a/raven/contrib/django/middleware/__init__.py +++ b/raven/contrib/django/middleware/__init__.py @@ -134,4 +134,5 @@ def request_finished(self, **kwargs): request_finished.disconnect(self.request_finished) + SentryLogMiddleware = SentryMiddleware diff --git a/raven/contrib/django/serializers.py b/raven/contrib/django/serializers.py index 50c02362d..3fc2b8267 100644 --- a/raven/contrib/django/serializers.py +++ b/raven/contrib/django/serializers.py @@ -45,6 +45,7 @@ def serialize(self, value, **kwargs): return self.recurse(text_type(value)) return self.recurse(value, **kwargs) + register(PromiseSerializer) @@ -54,6 +55,7 @@ class HttpRequestSerializer(Serializer): def serialize(self, value, **kwargs): return '<%s at 0x%s>' % (type(value).__name__, id(value)) + register(HttpRequestSerializer) diff --git a/raven/utils/__init__.py b/raven/utils/__init__.py index c22f0bc8e..db23162ce 100644 --- a/raven/utils/__init__.py +++ b/raven/utils/__init__.py @@ -53,6 +53,7 @@ def varmap(func, var, context=None, name=None): del context[objid] return ret + # We store a cache of module_name->version string to avoid # continuous imports and lookups of modules _VERSION_CACHE = {} From f3878ac460114f3111a06416b00dc62baaae410e Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 10 Nov 2016 12:53:57 +0900 Subject: [PATCH 038/289] Improve matrix builds; Drop Python 2.6 - Add Flask-specific builds - Add Celery-specific builds - Restore Django dev build - Reduce Django builds to only focus on subset of versions - Expand norecursedirs - Drop Python 2.6 support (as deps are starting to) - Update pytest-django - Limit django-celery exposure - Correct django-celery 3.1 behavior --- .travis.yml | 88 ++++++++++----------------- ci/setup | 36 ++++++++--- ci/test | 13 ++++ conftest.py | 100 ++++++++++++++++++------------- raven/contrib/celery/__init__.py | 2 +- setup.cfg | 2 +- setup.py | 6 +- tests/versioning/tests.py | 16 ++--- 8 files changed, 142 insertions(+), 121 deletions(-) create mode 100755 ci/test diff --git a/.travis.yml b/.travis.yml index 1f1e6300b..ce726fe4d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,68 +14,42 @@ cache: # secure: NMwOI1H9arp2vbgaidx9OY6y8990hiu0WsHtowEvEdGKXNzAQcy0sW3SoKcB6FN0bk11xhj49+5C++KAwMYwE/SL8Y5OoZ1/iYVI4/XlWNukr+1/pfPKVMgw3v5W+pL5Ba9TBdFfIoFPNYUDPLItSSjg94Bm95034gBkYWC5Hl0= # on: # tags: true -python: - - '2.6' - - '2.7' -# - '3.2' - - '3.3' - - '3.4' - - '3.5' - # pypy fails a lot, and generally should be compatible - # - pypy -env: - matrix: -# - DJANGO=Django==1.4.20 -# - DJANGO=Django==1.5.12 - - DJANGO=Django==1.6.11 -# - DJANGO=Django==1.7.11 - - DJANGO=Django==1.8.7 - - DJANGO=Django==1.9 - - DJANGO=Django==1.10 -# - DJANGO="-e git+git://github.com/django/django.git#egg=Django" install: - time ci/setup - pip install codecov "coverage<4" +before_script: + - pip freeze script: - if [[ ${TRAVIS_PYTHON_VERSION} != 'pypy' ]]; then make lint; fi - - coverage run --source=raven -m py.test tests --timeout 10 + - time ci/test after_success: - codecov -e DJANGO matrix: -# allow_failures: -# - env: DJANGO="-e git+git://github.com/django/django.git#egg=Django" - exclude: -# - python: '3.2' -# env: DJANGO=Django==1.4.20 -# - python: '3.2' -# env: DJANGO=Django==1.9 -# - python: '3.3' -# env: DJANGO=Django==1.4.20 - - python: '3.3' - env: DJANGO=Django==1.9 - - python: '3.3' - env: DJANGO=Django==1.10 -# - python: '3.4' -# env: DJANGO=Django==1.4.20 -# - python: '3.5' -# env: DJANGO=Django==1.4.20 -# - python: '3.5' -# env: DJANGO=Django==1.5.12 - - python: '3.5' - env: DJANGO=Django==1.6.11 -# - python: '3.5' -# env: DJANGO=Django==1.7.11 - - python: '3.5' - env: DJANGO=Django==1.8.7 - - python: '2.6' - env: DJANGO="-e git+git://github.com/django/django.git#egg=Django" - - python: '3.2' - env: DJANGO="-e git+git://github.com/django/django.git#egg=Django" - - python: '2.6' - env: DJANGO=Django==1.10 - - python: '2.6' - env: DJANGO=Django==1.9 - - python: '2.6' - env: DJANGO=Django==1.8.7 - - python: '2.6' - env: DJANGO=Django==1.7.11 + fast_finish: true + allow_failures: + - python: 3.5 + env: DJANGO=dev TEST_SUITE=django + include: + - python: 2.7 + env: FLASK=0.10.1 TEST_SUITE=flask + - python: 2.7 + env: FLASK=0.11.1 TEST_SUITE=flask + - python: 2.7 + env: DJANGO=1.6.11 TEST_SUITE=django + - python: 2.7 + env: DJANGO=1.8.7 TEST_SUITE=django + - python: 2.7 + env: DJANGO=1.9 TEST_SUITE=django + - python: 2.7 + env: DJANGO=1.10 TEST_SUITE=django + - python: 3.5 + env: DJANGO=1.10 TEST_SUITE=django + - python: 3.5 + env: DJANGO=dev TEST_SUITE=django + - python: 2.7 + env: CELERY=3.1 TEST_SUITE=celery + - python: 2.7 + env: CELERY=4.0 TEST_SUITE=celery + - python: 3.3 + - python: 3.4 + - python: 3.5 diff --git a/ci/setup b/ci/setup index 39218490a..20134a341 100755 --- a/ci/setup +++ b/ci/setup @@ -1,13 +1,35 @@ -#!/bin/bash -eu +#!/bin/bash -e -pip install $DJANGO +# we want wheel support when possible +pip install "pip>=8.1" + +if [ -n "$DJANGO" ]; then + if [[ "$DJANGO" == "dev" ]]; then + pip install -e git+git://github.com/django/django.git#egg=Django + else + pip install "Django==$DJANGO" + fi +fi +if [ -n "$FLASK" ]; then + pip install "Flask==$FLASK" +fi +if [ -n "$CELERY" ]; then + pip install "celery==$CELERY" +fi make bootstrap if [[ ${TRAVIS_PYTHON_VERSION::1} == '2' ]]; then pip install gevent fi -if [[ ${TRAVIS_PYTHON_VERSION} == '3.2' ]]; then - pip install -I https://github.com/celery/celery/archive/3.0.zip -fi -if [[ ${TRAVIS_PYTHON_VERSION::1} == '3' ]]; then - pip uninstall django-celery -y +# if [[ ${TRAVIS_PYTHON_VERSION} == '3.2' ]]; then +# pip install -I https://github.com/celery/celery/archive/3.0.zip +# fi + +DJANGO_BITS=(${DJANGO//./ }) +if [[ ${DJANGO_BITS[0]} -eq 1 && ${DJANGO_BITS[1]} -lt 8 ]]; then + # django-celery has fickle dependencies as its deprecated + pip install "django-celery>=3.1" "celery>=3.1,<4" + # newer versions of pytest-django dont support older versions of django + pip install "pytest-django<3.0" +elif [ -n "$DJANGO" ]; then + pip install "pytest-django>=3.0,<3.1" fi diff --git a/ci/test b/ci/test new file mode 100755 index 000000000..2da87e2dd --- /dev/null +++ b/ci/test @@ -0,0 +1,13 @@ +#!/bin/bash -e + +if [[ $TEST_SUITE == 'django' ]]; then + TEST_PATH=tests/contrib/django +elif [[ $TEST_SUITE == 'flask' ]]; then + TEST_PATH=tests/contrib/flask +elif [[ $TEST_SUITE == 'celery' ]]; then + TEST_PATH=tests/contrib/test_celery.py +else + TEST_PATH=tests +fi + +coverage run --source=raven -m py.test $TEST_PATH --timeout 10 diff --git a/conftest.py b/conftest.py index 5c3b03f34..e80bfb93d 100644 --- a/conftest.py +++ b/conftest.py @@ -1,5 +1,7 @@ -from django.conf import settings +from __future__ import absolute_import + import os.path +import pytest import sys @@ -20,6 +22,12 @@ except ImportError: collect_ignore.append("tests/contrib/webpy") +try: + import django # NOQA +except ImportError: + django = None + collect_ignore.append("tests/contrib/django") + INSTALLED_APPS = [ 'django.contrib.auth', @@ -40,46 +48,54 @@ use_djcelery = False -def pytest_configure(config): - where_am_i = os.path.dirname(os.path.abspath(__file__)) - - if not settings.configured: - settings.configure( - DATABASE_ENGINE='sqlite3', - DATABASES={ - 'default': { - 'NAME': ':memory:', - 'ENGINE': 'django.db.backends.sqlite3', - 'TEST_NAME': ':memory:', - }, +def configure_django(project_root): + from django.conf import settings + + settings.configure( + DATABASE_ENGINE='sqlite3', + DATABASES={ + 'default': { + 'NAME': ':memory:', + 'ENGINE': 'django.db.backends.sqlite3', + 'TEST_NAME': ':memory:', }, - DATABASE_NAME=':memory:', - TEST_DATABASE_NAME=':memory:', - INSTALLED_APPS=INSTALLED_APPS, - ROOT_URLCONF='tests.contrib.django.urls', - DEBUG=False, - SITE_ID=1, - BROKER_HOST="localhost", - BROKER_PORT=5672, - BROKER_USER="guest", - BROKER_PASSWORD="guest", - BROKER_VHOST="/", - SENTRY_ALLOW_ORIGIN='*', - CELERY_ALWAYS_EAGER=True, - TEMPLATE_DEBUG=True, - LANGUAGE_CODE='en', - LANGUAGES=(('en', 'English'),), - PROJECT_ROOT=where_am_i, - TEMPLATE_DIRS=[ - os.path.join(where_am_i, 'tests', 'contrib', 'django', 'templates'), + }, + DATABASE_NAME=':memory:', + TEST_DATABASE_NAME=':memory:', + INSTALLED_APPS=INSTALLED_APPS, + ROOT_URLCONF='tests.contrib.django.urls', + DEBUG=False, + SITE_ID=1, + BROKER_HOST="localhost", + BROKER_PORT=5672, + BROKER_USER="guest", + BROKER_PASSWORD="guest", + BROKER_VHOST="/", + SENTRY_ALLOW_ORIGIN='*', + CELERY_ALWAYS_EAGER=True, + TEMPLATE_DEBUG=True, + LANGUAGE_CODE='en', + LANGUAGES=(('en', 'English'),), + TEMPLATE_DIRS=[ + os.path.join(project_root, 'tests', 'contrib', 'django', 'templates'), + ], + TEMPLATES=[{ + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'APP_DIRS': True, + 'DIRS': [ + os.path.join(project_root, 'tests', 'contrib', 'django', 'templates'), ], - TEMPLATES=[{ - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'APP_DIRS': True, - 'DIRS': [ - os.path.join(where_am_i, 'tests', 'contrib', 'django', 'templates'), - ], - }], - ALLOWED_HOSTS=['*'], - DISABLE_SENTRY_INSTRUMENTATION=True, - ) + }], + ALLOWED_HOSTS=['*'], + DISABLE_SENTRY_INSTRUMENTATION=True, + ) + + +def pytest_configure(config): + if django: + configure_django(os.path.dirname(os.path.abspath(__file__))) + + +@pytest.fixture +def project_root(): + return os.path.dirname(os.path.abspath(__file__)) diff --git a/raven/contrib/celery/__init__.py b/raven/contrib/celery/__init__.py index cfc34cbf4..edf2c4283 100644 --- a/raven/contrib/celery/__init__.py +++ b/raven/contrib/celery/__init__.py @@ -68,7 +68,7 @@ def uninstall(self): task_failure.disconnect(self.process_failure_signal) def process_failure_signal(self, sender, task_id, args, kwargs, einfo, **kw): - if self.ignore_expected and isinstance(einfo.exception, sender.throws): + if self.ignore_expected and hasattr(sender, 'throws') and isinstance(einfo.exception, sender.throws): return # This signal is fired inside the stack so let raven do its magic diff --git a/setup.cfg b/setup.cfg index 5aecf8515..1557c8551 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,7 @@ [tool:pytest] python_files=test*.py addopts=--tb=native -p no:doctest -norecursedirs=bin dist docs htmlcov hooks node_modules .* {args} +norecursedirs=raven build bin dist docs htmlcov hooks node_modules .* {args} [flake8] ignore = F999,E501,E128,E124,E402,W503,E731,F841 diff --git a/setup.py b/setup.py index cf3f058e7..41fa8cddb 100755 --- a/setup.py +++ b/setup.py @@ -66,8 +66,6 @@ 'six', 'bottle', 'celery>=2.5', - 'Django>=1.4', - 'django-celery>=2.5', 'exam>=0.5.2', 'flake8>=2.6,<2.7', 'logbook', @@ -75,8 +73,7 @@ 'nose', 'pycodestyle', 'pytz', - 'pytest>=3.0.0', - 'pytest-django==2.9.1', + 'pytest>=3.0.0,<3.1.0', 'pytest-timeout==0.4', 'requests', 'tornado>=4.1', @@ -137,7 +134,6 @@ def run_tests(self): 'Intended Audience :: System Administrators', 'Operating System :: OS Independent', 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.3', diff --git a/tests/versioning/tests.py b/tests/versioning/tests.py index b3b49cb3d..bf3bcddb3 100644 --- a/tests/versioning/tests.py +++ b/tests/versioning/tests.py @@ -5,13 +5,12 @@ import subprocess import six -from django.conf import settings - +from conftest import project_root from raven.versioning import fetch_git_sha, fetch_package_version -def has_git_requirements(): - return os.path.exists(os.path.join(settings.PROJECT_ROOT, '.git', 'refs', 'heads', 'master')) +def has_git_requirements(project_root): + return os.path.exists(os.path.join(project_root, '.git', 'refs', 'heads', 'master')) # Python 2.6 does not contain subprocess.check_output @@ -23,14 +22,15 @@ def check_output(cmd, **kwargs): ).communicate()[0] -@pytest.mark.skipif('not has_git_requirements()') -def test_fetch_git_sha(): - result = fetch_git_sha(settings.PROJECT_ROOT) +@pytest.mark.skipif(not has_git_requirements(project_root()), + reason='unable to detect git repository') +def test_fetch_git_sha(project_root): + result = fetch_git_sha(project_root) assert result is not None assert len(result) == 40 assert isinstance(result, six.string_types) assert result == check_output( - 'git rev-parse --verify HEAD', shell=True, cwd=settings.PROJECT_ROOT + 'git rev-parse --verify HEAD', shell=True, cwd=project_root ).decode('latin1').strip() From 7bcad57773401db279ecfba51bc8c12a336ab97c Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 15 Nov 2016 20:09:54 +0200 Subject: [PATCH 039/289] Make breadcrumb logging patches work when librato also patches --- CHANGES | 6 ++++++ raven/breadcrumbs.py | 7 +++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 29cef02e9..2d4e59996 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,9 @@ +Version 5.32.0 +-------------- + +* Made raven python breadcrumb patches work when librato monkey + patches logging. + Version 5.31.0 -------------- diff --git a/raven/breadcrumbs.py b/raven/breadcrumbs.py index c2e435320..e9f2bf26e 100644 --- a/raven/breadcrumbs.py +++ b/raven/breadcrumbs.py @@ -189,8 +189,11 @@ def %(name)s(self, %(args)s, *args, **kwargs): new_func.__doc__ = func.__doc__ assert code.co_firstlineno == get_code(func).co_firstlineno - assert new_func.__module__ == func.__module__ - assert new_func.__name__ == func.__name__ + + # In theory this should already be set correctly, but in some cases + # it is not. So override it. + new_func.__module__ == func.__module__ + new_func.__name__ == func.__name__ new_func.__patched_for_raven__ = True return new_func From 6ead8beb09d6d40b91be213580dd4d9d5bd72f37 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 15 Nov 2016 20:10:26 +0200 Subject: [PATCH 040/289] 5.32.0 --- raven/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/__init__.py b/raven/__init__.py index 0658062ca..05768bd7f 100644 --- a/raven/__init__.py +++ b/raven/__init__.py @@ -12,7 +12,7 @@ __all__ = ('VERSION', 'Client', 'get_version') -VERSION = '5.32.0.dev0' +VERSION = '5.32.0' def _get_git_revision(path): From 0d9bcff9eda54e7bd5bba67329f8b86d43c65ebb Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 17 Nov 2016 18:09:10 +0100 Subject: [PATCH 041/289] Ready for next dev version --- raven/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/__init__.py b/raven/__init__.py index 05768bd7f..8033537bf 100644 --- a/raven/__init__.py +++ b/raven/__init__.py @@ -12,7 +12,7 @@ __all__ = ('VERSION', 'Client', 'get_version') -VERSION = '5.32.0' +VERSION = '5.33.0.dev0' def _get_git_revision(path): From a73f022a971d8dd8eee0a9d911d7a1d6f1f154a7 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 17 Nov 2016 18:11:47 +0100 Subject: [PATCH 042/289] Automatically strip whitespace in DSNs. This fixes #907 --- CHANGES | 5 +++++ raven/conf/remote.py | 2 +- tests/conf/tests.py | 10 ++++++++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 2d4e59996..7849fc478 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 5.33.0 +-------------- + +* Strip whitespace from DNSs automatically. + Version 5.32.0 -------------- diff --git a/raven/conf/remote.py b/raven/conf/remote.py index 10149c42c..d7f0605fa 100644 --- a/raven/conf/remote.py +++ b/raven/conf/remote.py @@ -84,7 +84,7 @@ def from_string(cls, value, transport=None, transport_registry=None): if PY2: value = to_string(value) - url = urlparse(value) + url = urlparse(value.strip()) if url.scheme not in ('http', 'https'): warnings.warn('Transport selection via DSN is deprecated. You should explicitly pass the transport class to Client() instead.') diff --git a/tests/conf/tests.py b/tests/conf/tests.py index 9caa24210..e02a1fb86 100644 --- a/tests/conf/tests.py +++ b/tests/conf/tests.py @@ -10,6 +10,16 @@ class RemoteConfigTest(TestCase): + def test_path_strip(self): + dsn = 'https://foo:bar@sentry.local/app/1\n' + res = RemoteConfig.from_string(dsn) + assert res.project == '1' + assert res.base_url == 'https://sentry.local/app' + assert res.store_endpoint == 'https://sentry.local/app/api/1/store/' + assert res.public_key == 'foo' + assert res.secret_key == 'bar' + assert res.options == {} + def test_path(self): dsn = 'https://foo:bar@sentry.local/app/1' res = RemoteConfig.from_string(dsn) From 9a5757c070eb4cf3b06168bf1914ffee978c40f9 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 17 Nov 2016 18:18:55 +0100 Subject: [PATCH 043/289] DNS DNS DNS --- CHANGES | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 7849fc478..de66fe09b 100644 --- a/CHANGES +++ b/CHANGES @@ -1,7 +1,7 @@ Version 5.33.0 -------------- -* Strip whitespace from DNSs automatically. +* Strip whitespace from DSNs automatically. Version 5.32.0 -------------- From dc59e2cc7035b63524d2ae2dd4553b0afb33dc7c Mon Sep 17 00:00:00 2001 From: Moritz Schlarb Date: Mon, 21 Nov 2016 11:22:14 +0100 Subject: [PATCH 044/289] Revert "Fix Django LOGGING example root logger" This reverts commit 5d863173772cb53edcf95c7ba83060ebeac90c38. In https://docs.python.org/library/logging.config.html#dictionary-schema-details, there is in fact a special key named ``root`` in the ``LOGGING`` dict that specifies the configuration for the root logger. I think it can also be specified in the ``loggers`` dict, but then its key should just be the empty string rather than ``root``! --- docs/integrations/django.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/integrations/django.rst b/docs/integrations/django.rst index 43ba2a2a2..bf12bfaeb 100644 --- a/docs/integrations/django.rst +++ b/docs/integrations/django.rst @@ -82,6 +82,10 @@ ERROR and above messages to sentry, the following config can be used:: LOGGING = { 'version': 1, 'disable_existing_loggers': True, + 'root': { + 'level': 'WARNING', + 'handlers': ['sentry'], + }, 'formatters': { 'verbose': { 'format': '%(levelname)s %(asctime)s %(module)s ' @@ -101,10 +105,6 @@ ERROR and above messages to sentry, the following config can be used:: } }, 'loggers': { - 'root': { - 'level': 'WARNING', - 'handlers': ['sentry'], - }, 'django.db.backends': { 'level': 'ERROR', 'handlers': ['console'], From 56a453b03200979bbbec1c96e90a34d07f73a6b8 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 21 Nov 2016 12:31:42 -0800 Subject: [PATCH 045/289] Add last_event_id to Client --- CHANGES | 1 + raven/base.py | 13 +++++++++++++ tests/base/tests.py | 6 ++++++ 3 files changed, 20 insertions(+) diff --git a/CHANGES b/CHANGES index de66fe09b..e6461d26e 100644 --- a/CHANGES +++ b/CHANGES @@ -2,6 +2,7 @@ Version 5.33.0 -------------- * Strip whitespace from DSNs automatically. +* Add `last_event_id` accessor to `Client`. Version 5.32.0 -------------- diff --git a/raven/base.py b/raven/base.py index 780982357..e46389cfe 100644 --- a/raven/base.py +++ b/raven/base.py @@ -18,6 +18,7 @@ from datetime import datetime from types import FunctionType +from threading import local if sys.version_info >= (3, 2): import contextlib @@ -150,6 +151,8 @@ def __init__(self, dsn=None, raise_send_errors=False, transport=None, o = options + self._local_state = local() + self.raise_send_errors = raise_send_errors # configure loggers first @@ -609,6 +612,8 @@ def capture(self, event_type, data=None, date=None, time_spent=None, self.send(**data) + self._local_state.last_event_id = data['event_id'] + return data['event_id'] def is_enabled(self): @@ -868,6 +873,14 @@ def captureBreadcrumb(self, *args, **kwargs): capture_breadcrumb = captureBreadcrumb + @property + def last_event_id(self): + return getattr(self._local_state, 'last_event_id', None) + + @last_event_id.setter + def last_event_id(self, value): + self._local_state.last_event_id = value + class DummyClient(Client): "Sends messages into an empty void" diff --git a/tests/base/tests.py b/tests/base/tests.py index cb988da2c..afedfd739 100644 --- a/tests/base/tests.py +++ b/tests/base/tests.py @@ -568,3 +568,9 @@ def test_marks_in_app_frames_for_exception(self): assert not frames[1]['in_app'] assert not frames[2]['in_app'] assert frames[3]['in_app'] + + def test_captures_last_event_id(self): + client = TempStoreClient() + result = client.captureMessage('hello') + + assert result == client.last_event_id From 66da347ded99209da5b5cc6fad944bcb33cbd8aa Mon Sep 17 00:00:00 2001 From: Frankie Dintino Date: Mon, 21 Nov 2016 08:44:58 -0500 Subject: [PATCH 046/289] In Django HTTP request data, multiple values for same key are lists In a Django HttpRequest object, the request data is a QueryDict object, which is a dictionary-like class that supports multiple values for the same key. This is used, for instance, with