From 356a7c4d83a5289e7b30a07b0f76829e274b7481 Mon Sep 17 00:00:00 2001 From: Jakub Stasiak Date: Wed, 10 Jun 2015 13:05:13 +0200 Subject: [PATCH 001/692] Fix Eventlet transport on Python 3 eventlet.green.urllib* naming and hierarchy reflects the standard library (2.x: urllib and urllib2, 3.x: just urllib) so there's no eventlet.green.urllib2 on Python 3. --- raven/transport/eventlet.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/raven/transport/eventlet.py b/raven/transport/eventlet.py index 449e9331e..48b783ff9 100644 --- a/raven/transport/eventlet.py +++ b/raven/transport/eventlet.py @@ -13,7 +13,10 @@ try: import eventlet - from eventlet.green import urllib2 as eventlet_urllib2 + try: + from eventlet.green import urllib2 as eventlet_urllib2 + except ImportError: + from eventlet.green.urllib import request as eventlet_urllib2 has_eventlet = True except: has_eventlet = False From fbadd60237d94ec4d9725a4edb14c9e30068e103 Mon Sep 17 00:00:00 2001 From: Josh Kalderimis Date: Tue, 7 Jul 2015 04:12:27 +0200 Subject: [PATCH 002/692] lets try using the container based infrastructure --- .travis.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index ea03e81ea..137322739 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,7 @@ language: python + +sudo: false + python: - "2.6" - "2.7" @@ -6,6 +9,7 @@ python: - "3.3" - "3.4" - "pypy" + env: matrix: - DJANGO=Django==1.4.20 @@ -24,20 +28,23 @@ env: # - S3_BUCKET_NAME= # - S3_CREDENTIALS= - secure: "QEFKt0HhaMlCW0FCLTz3NDY/P4UuVl1Pw8kSUVKKjbaKn2jZdaLKS68yl3Vyrd9AqrBfRNNLBAnvpZjYL6EwqqnZzpRH3KnAf0WRxE6d5ytrUkwZtOnQaN0Tumuqc3xnoXXalPXvs+abhXZpjfOzx0oPBa2WbtMF/RJZ/bwsTKE=" + before_install: # Use closer nameservers - printf "nameserver 199.91.168.70\nnameserver 199.91.168.71\n" | sudo tee /etc/resolv.conf - # These need to be here and not in the env hash because they need to be # evaluated after the virtualenv has been setup - mkdir -p $PIP_DOWNLOAD_CACHE - 'export WAD_ENVIRONMENT_VARIABLES="TRAVIS_PYTHON_VERSION,TRAVIS_NODE_VERSION,WAD_CACHE_PATH"' - 'export WAD_CACHE_PATH="node_modules,$PIP_DOWNLOAD_CACHE,$VIRTUAL_ENV"' + install: - time ci/wad + script: - "if [[ ${TRAVIS_PYTHON_VERSION} != 'pypy' ]]; then make lint; fi" - py.test tests/ --cov raven --cov-report term-missing + matrix: allow_failures: - env: 'DJANGO="-e git+git://github.com/django/django.git#egg=Django"' From c91cad559cd6cd06d31120dd2b6ceb174446e512 Mon Sep 17 00:00:00 2001 From: Josh Kalderimis Date: Tue, 7 Jul 2015 04:27:52 +0200 Subject: [PATCH 003/692] add the libevent-dev package --- .travis.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.travis.yml b/.travis.yml index 137322739..1b6f5c018 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,11 @@ language: python sudo: false +addons: + apt: + packages: + - libevent-dev + python: - "2.6" - "2.7" From 5f1c3fc2a12ce13740aabac38d6d85f8cfd71c37 Mon Sep 17 00:00:00 2001 From: Josh Kalderimis Date: Tue, 7 Jul 2015 04:29:05 +0200 Subject: [PATCH 004/692] remove the use of sudo --- ci/setup | 1 - 1 file changed, 1 deletion(-) diff --git a/ci/setup b/ci/setup index 45b0eda75..6b51a937c 100755 --- a/ci/setup +++ b/ci/setup @@ -1,6 +1,5 @@ #!/bin/bash -eu -sudo apt-get install libevent-dev pip install $DJANGO make bootstrap if [[ ${TRAVIS_PYTHON_VERSION::1} == '2' ]]; then From ceff7c96f29b0f538ad66bfa6e9ddc2f23f35296 Mon Sep 17 00:00:00 2001 From: Matt Robenolt Date: Mon, 6 Jul 2015 23:00:36 -0400 Subject: [PATCH 005/692] Fix `has_git_requirements` check in tests --- tests/versioning/tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/versioning/tests.py b/tests/versioning/tests.py index 164359747..cef371092 100644 --- a/tests/versioning/tests.py +++ b/tests/versioning/tests.py @@ -10,7 +10,7 @@ def has_git_requirements(): - return os.path.join(settings.PROJECT_ROOT, '.git', 'refs', 'heads', 'master') + return os.path.exists(os.path.join(settings.PROJECT_ROOT, '.git', 'refs', 'heads', 'master')) @pytest.mark.skipif('not has_git_requirements()') From 74b5cda2ef76dcf845130cde54314bef40dd998b Mon Sep 17 00:00:00 2001 From: Josh Kalderimis Date: Tue, 7 Jul 2015 05:05:14 +0200 Subject: [PATCH 006/692] Update .travis.yml --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 1b6f5c018..445449d07 100644 --- a/.travis.yml +++ b/.travis.yml @@ -36,7 +36,7 @@ env: before_install: # Use closer nameservers - - printf "nameserver 199.91.168.70\nnameserver 199.91.168.71\n" | sudo tee /etc/resolv.conf + # - printf "nameserver 199.91.168.70\nnameserver 199.91.168.71\n" | sudo tee /etc/resolv.conf # These need to be here and not in the env hash because they need to be # evaluated after the virtualenv has been setup - mkdir -p $PIP_DOWNLOAD_CACHE From 6518720d42fb980592f1e6e00b6738dc69827210 Mon Sep 17 00:00:00 2001 From: Matt Robenolt Date: Mon, 6 Jul 2015 23:36:07 -0400 Subject: [PATCH 007/692] no mirrors in ci/setup --- ci/setup | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/setup b/ci/setup index 45b0eda75..80d7ca5c7 100755 --- a/ci/setup +++ b/ci/setup @@ -4,7 +4,7 @@ sudo apt-get install libevent-dev pip install $DJANGO make bootstrap if [[ ${TRAVIS_PYTHON_VERSION::1} == '2' ]]; then - pip install gevent --use-mirrors + pip install gevent fi if [[ ${TRAVIS_PYTHON_VERSION} == '3.2' ]]; then pip install -I https://github.com/celery/celery/archive/3.0.zip From 8b08e66e7d5139fd5ea191987861dfc116562211 Mon Sep 17 00:00:00 2001 From: Josh Kalderimis Date: Wed, 8 Jul 2015 01:10:06 +0200 Subject: [PATCH 008/692] don't use mirrors --- ci/setup | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/setup b/ci/setup index 6b51a937c..39218490a 100755 --- a/ci/setup +++ b/ci/setup @@ -3,7 +3,7 @@ pip install $DJANGO make bootstrap if [[ ${TRAVIS_PYTHON_VERSION::1} == '2' ]]; then - pip install gevent --use-mirrors + pip install gevent fi if [[ ${TRAVIS_PYTHON_VERSION} == '3.2' ]]; then pip install -I https://github.com/celery/celery/archive/3.0.zip From 6cd5d6c5aa0adc0746c672ee3b856d4b1c9d86ad Mon Sep 17 00:00:00 2001 From: Vincent Driessen Date: Wed, 8 Jul 2015 09:15:00 +0200 Subject: [PATCH 009/692] Fix package names in transport docs --- docs/transports/index.rst | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/transports/index.rst b/docs/transports/index.rst index 98c68d21a..1f0673e5e 100644 --- a/docs/transports/index.rst +++ b/docs/transports/index.rst @@ -33,31 +33,31 @@ For example, to increase the timeout and to disable SSL verification: Builtin Transports ------------------ -.. data:: sentry.transport.thread.ThreadedHTTPTransport +.. data:: raven.transport.thread.ThreadedHTTPTransport The default transport. Manages a threaded worker for processing messages asynchronous. -.. data:: sentry.transport.http.HTTPTransport +.. data:: raven.transport.http.HTTPTransport A synchronous blocking transport. -.. data:: sentry.transport.eventlet.EventletHTTPTransport +.. data:: raven.transport.eventlet.EventletHTTPTransport Should only be used within an Eventlet IO loop. -.. data:: sentry.transport.gevent.GeventedHTTPTransport +.. data:: raven.transport.gevent.GeventedHTTPTransport Should only be used within a Gevent IO loop. -.. data:: sentry.transport.requests.RequestsHTTPTransport +.. data:: raven.transport.requests.RequestsHTTPTransport A synchronous transport which relies on the ``requests`` library. -.. data:: sentry.transport.tornado.TornadoHTTPTransport +.. data:: raven.transport.tornado.TornadoHTTPTransport Should only be used within a Tornado IO loop. -.. data:: sentry.transport.twisted.TwistedHTTPTransport +.. data:: raven.transport.twisted.TwistedHTTPTransport Should only be used within a Twisted event loop. From 39dd912382f9fc403d90a87a639aaae2b5ca5d00 Mon Sep 17 00:00:00 2001 From: Matt Robenolt Date: Wed, 8 Jul 2015 01:41:11 -0600 Subject: [PATCH 010/692] Fix up Makefile to also clean the build directory --- CHANGES | 5 +++++ Makefile | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index eb452b8ff..1e954c1d8 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 5.4.1 +------------- + +* Fixed packaging of 5.4.0 which erronously kept the ``aiohttp.py`` file in the wheel only. + Version 5.4.0 ------------- diff --git a/Makefile b/Makefile index e71bba10f..4de6e2c92 100644 --- a/Makefile +++ b/Makefile @@ -21,7 +21,7 @@ setup-git: cd .git/hooks && ln -sf ../../hooks/* ./ publish: - rm -rf dist + rm -rf dist build python setup.py sdist bdist_wheel twine upload -s dist/* From 58292d33f3ba9f667fd5d40743a93975fd348194 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Fri, 10 Jul 2015 11:53:45 -0600 Subject: [PATCH 011/692] Add test showing failure --- tests/base/tests.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/base/tests.py b/tests/base/tests.py index d2588e0cd..cce73fb90 100644 --- a/tests/base/tests.py +++ b/tests/base/tests.py @@ -9,6 +9,7 @@ from raven.base import Client, ClientState from raven.exceptions import RateLimited from raven.transport import AsyncTransport +from raven.transport.http import HTTPTransport from raven.utils.stacks import iter_stack_frames from raven.utils import six from raven.utils.testutils import TestCase @@ -437,3 +438,11 @@ def test_client_extra_context(self): else: expected = {'logger': "u'test'", 'foo': "u'bar'"} self.assertEquals(event['extra'], expected) + + def test_transport_registration(self): + client = Client('http://public:secret@example.com/1', + transport=HTTPTransport) + assert type(client.remote.get_transport()) is HTTPTransport + + client = Client('sync+http://public:secret@example.com/1') + assert type(client.remote.get_transport()) is HTTPTransport From d3623e897de6be30da7fab2a1fff571be254edec Mon Sep 17 00:00:00 2001 From: David Cramer Date: Fri, 10 Jul 2015 11:59:34 -0600 Subject: [PATCH 012/692] Remove scheme checking behavior --- raven/conf/remote.py | 2 +- raven/transport/base.py | 6 ------ raven/transport/http.py | 2 -- tests/transport/tests.py | 1 - 4 files changed, 1 insertion(+), 10 deletions(-) diff --git a/raven/conf/remote.py b/raven/conf/remote.py index 7ba4cdfcb..e462b0fc8 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 not all([netloc, project, url.username, url.password]): raise InvalidDsn('Invalid Sentry DSN: %r' % url.geturl()) - base_url = '%s://%s%s' % (url.scheme, netloc, path) + base_url = '%s://%s%s' % (url.scheme.rsplit('+', 1)[-1], netloc, path) return cls( base_url=base_url, diff --git a/raven/transport/base.py b/raven/transport/base.py index ed4e15d12..f3adb2877 100644 --- a/raven/transport/base.py +++ b/raven/transport/base.py @@ -7,8 +7,6 @@ """ from __future__ import absolute_import -from raven.transport.exceptions import InvalidScheme - class Transport(object): """ @@ -24,10 +22,6 @@ class Transport(object): async = False scheme = [] - def check_scheme(self, url): - if url.scheme not in self.scheme: - raise InvalidScheme() - def send(self, data, headers): """ You need to override this to do something with the actual diff --git a/raven/transport/http.py b/raven/transport/http.py index 10bb0af1a..a2b968bac 100644 --- a/raven/transport/http.py +++ b/raven/transport/http.py @@ -20,8 +20,6 @@ class HTTPTransport(Transport): def __init__(self, parsed_url, timeout=defaults.TIMEOUT, verify_ssl=True, ca_certs=defaults.CA_BUNDLE): - self.check_scheme(parsed_url) - self._parsed_url = parsed_url self._url = parsed_url.geturl().rsplit('+', 1)[-1] diff --git a/tests/transport/tests.py b/tests/transport/tests.py index 2eba3b852..b0f03e586 100644 --- a/tests/transport/tests.py +++ b/tests/transport/tests.py @@ -23,7 +23,6 @@ class DummyScheme(Transport): scheme = ['mock'] def __init__(self, parsed_url, timeout=5): - self.check_scheme(parsed_url) self._parsed_url = parsed_url self.timeout = timeout From 468bc4e6c0b075737e0b6982904ef62c9bdce352 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Sat, 11 Jul 2015 00:10:45 -0600 Subject: [PATCH 013/692] Support transport option in Flask/Django (fixes GH-623) --- raven/contrib/django/models.py | 41 +++++++++++++++++----------------- raven/contrib/flask.py | 11 ++++++++- raven/utils/imports.py | 12 ++++++++++ tests/contrib/django/tests.py | 10 +++++++++ 4 files changed, 53 insertions(+), 21 deletions(-) create mode 100644 raven/utils/imports.py diff --git a/raven/contrib/django/models.py b/raven/contrib/django/models.py index 7be0fcaa1..e53536cff 100644 --- a/raven/contrib/django/models.py +++ b/raven/contrib/django/models.py @@ -16,10 +16,11 @@ import sys import warnings -from django.conf import settings as django_settings +from django.conf import settings from hashlib import md5 from raven.utils import six +from raven.utils.imports import import_string from raven.contrib.django.management import patch_cli_runner @@ -30,7 +31,7 @@ def get_installed_apps(): """ Modules in settings.INSTALLED_APPS as a set. """ - return set(django_settings.INSTALLED_APPS) + return set(settings.INSTALLED_APPS) _client = (None, None) @@ -107,23 +108,21 @@ class ProxyClient(object): def get_option(x, d=None): - options = getattr(django_settings, 'RAVEN_CONFIG', {}) + options = getattr(settings, 'RAVEN_CONFIG', {}) - return getattr(django_settings, 'SENTRY_%s' % x, options.get(x, d)) + return getattr(settings, 'SENTRY_%s' % x, options.get(x, d)) -def get_client(client=None): +def get_client(client=None, reset=False): global _client tmp_client = client is not None if not tmp_client: - client = getattr(django_settings, 'SENTRY_CLIENT', 'raven.contrib.django.DjangoClient') + client = getattr(settings, 'SENTRY_CLIENT', 'raven.contrib.django.DjangoClient') - if _client[0] != client: - module, class_name = client.rsplit('.', 1) - - ga = lambda x, d=None: getattr(django_settings, 'SENTRY_%s' % x, d) - options = copy.deepcopy(getattr(django_settings, 'RAVEN_CONFIG', {})) + if _client[0] != client or reset: + ga = lambda x, d=None: getattr(settings, 'SENTRY_%s' % x, d) + options = copy.deepcopy(getattr(settings, 'RAVEN_CONFIG', {})) options.setdefault('servers', ga('SERVERS')) options.setdefault('include_paths', ga('INCLUDE_PATHS', [])) options['include_paths'] = set(options['include_paths']) | get_installed_apps() @@ -131,7 +130,6 @@ def get_client(client=None): options.setdefault('timeout', ga('TIMEOUT')) options.setdefault('name', ga('NAME')) options.setdefault('auto_log_stacks', ga('AUTO_LOG_STACKS')) - options.setdefault('key', ga('KEY', md5(django_settings.SECRET_KEY.encode('utf8')).hexdigest())) options.setdefault('string_max_length', ga('MAX_LENGTH_STRING')) options.setdefault('list_max_length', ga('MAX_LENGTH_LIST')) options.setdefault('site', ga('SITE')) @@ -143,10 +141,13 @@ def get_client(client=None): options.setdefault('context', ga('CONTEXT')) options.setdefault('release', ga('RELEASE')) - class_name = str(class_name) + transport = ga('TRANSPORT') or options.get('transport') + if isinstance(transport, basestring): + transport = import_string(transport) + options['transport'] = transport try: - Client = getattr(__import__(module, {}, {}, class_name), class_name) + Client = import_string(client) except ImportError: logger.exception('Failed to import client: %s', client) if not _client[1]: @@ -186,7 +187,7 @@ def register_handlers(): from django.core.signals import got_request_exception # HACK: support Sentry's internal communication - if 'sentry' in django_settings.INSTALLED_APPS: + if 'sentry' in settings.INSTALLED_APPS: from django.db import transaction # Django 1.6 if hasattr(transaction, 'atomic'): @@ -208,7 +209,7 @@ def wrap_sentry(request, **kwargs): got_request_exception.connect(exception_handler, weak=False) # If Celery is installed, register a signal handler - if 'djcelery' in django_settings.INSTALLED_APPS: + if 'djcelery' in settings.INSTALLED_APPS: try: # Celery < 2.5? is not supported from raven.contrib.celery import ( @@ -222,8 +223,8 @@ def wrap_sentry(request, **kwargs): logger.exception('Failed to install Celery error handler') try: - ga = lambda x, d=None: getattr(django_settings, 'SENTRY_%s' % x, d) - options = getattr(django_settings, 'RAVEN_CONFIG', {}) + ga = lambda x, d=None: getattr(settings, 'SENTRY_%s' % x, d) + options = getattr(settings, 'RAVEN_CONFIG', {}) loglevel = options.get('celery_loglevel', ga('CELERY_LOGLEVEL', logging.ERROR)) @@ -237,8 +238,8 @@ def register_serializers(): import raven.contrib.django.serializers # NOQA -if ('raven.contrib.django' in django_settings.INSTALLED_APPS - or 'raven.contrib.django.raven_compat' in django_settings.INSTALLED_APPS): +if ('raven.contrib.django' in settings.INSTALLED_APPS + or 'raven.contrib.django.raven_compat' in settings.INSTALLED_APPS): register_handlers() register_serializers() diff --git a/raven/contrib/flask.py b/raven/contrib/flask.py index a8c7d3906..05f0067c7 100644 --- a/raven/contrib/flask.py +++ b/raven/contrib/flask.py @@ -21,18 +21,27 @@ from flask import request, current_app, g from flask.signals import got_request_exception, request_finished +from werkzeug.exceptions import ClientDisconnected + from raven.conf import setup_logging from raven.base import Client from raven.middleware import Sentry as SentryMiddleware from raven.handlers.logging import SentryHandler from raven.utils.compat import _urlparse +from raven.utils.imports import import_string from raven.utils.wsgi import get_headers, get_environ -from werkzeug.exceptions import ClientDisconnected def make_client(client_cls, app, dsn=None): + # TODO(dcramer): django and Flask share very similar concepts here, and + # should be refactored + transport = app.config.get('SENTRY_TRANSPORT') + if isinstance(transport, basestring): + transport = import_string(transport) + return client_cls( dsn=dsn or app.config.get('SENTRY_DSN') or os.environ.get('SENTRY_DSN'), + transport=transport, include_paths=set(app.config.get('SENTRY_INCLUDE_PATHS', [])) | set([app.import_name]), exclude_paths=app.config.get('SENTRY_EXCLUDE_PATHS'), servers=app.config.get('SENTRY_SERVERS'), diff --git a/raven/utils/imports.py b/raven/utils/imports.py new file mode 100644 index 000000000..e039a287e --- /dev/null +++ b/raven/utils/imports.py @@ -0,0 +1,12 @@ +from __future__ import absolute_import + + +def import_string(key): + key = str(key) + + if '.' not in key: + return __import__(key) + + module_name, class_name = key.rsplit('.', 1) + module = __import__(module_name, {}, {}, [class_name], -1) + return getattr(module, class_name) diff --git a/tests/contrib/django/tests.py b/tests/contrib/django/tests.py index f1bb932fd..6d9b80b04 100644 --- a/tests/contrib/django/tests.py +++ b/tests/contrib/django/tests.py @@ -32,6 +32,7 @@ from raven.contrib.django.middleware.wsgi import Sentry from raven.contrib.django.templatetags.raven import sentry_public_dsn from raven.contrib.django.views import is_valid_origin +from raven.transport import HTTPTransport from raven.utils.serializer import transform from raven.utils import six from raven.utils.six import StringIO @@ -402,6 +403,15 @@ def test_invalid_client(self): with Settings(**extra_settings): assert isinstance(get_client(), DjangoClient) + def test_transport_specification(self): + extra_settings = { + 'SENTRY_TRANSPORT': 'raven.transport.HTTPTransport', + 'SENTRY_DSN': 'http://public:secret@example.com/1', + } + with Settings(**extra_settings): + client = get_client(reset=True) + assert type(client.remote.get_transport()) is HTTPTransport + def test_response_error_id_middleware(self): # TODO: test with 500s with Settings(MIDDLEWARE_CLASSES=[ From 5349d3b5b9c0b47591e8cd1b638920b4a889e1d8 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Sat, 11 Jul 2015 09:30:50 -0600 Subject: [PATCH 014/692] Changes for 5.4.2 --- CHANGES | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES b/CHANGES index 1e954c1d8..fb5a6a998 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,9 @@ +Version 5.4.2 +------------- + +* Remove scheme checking on transports. +* Added ``SENTRY_TRANSPORT`` to Flask and Django configurations. + Version 5.4.1 ------------- From 68e3fce985495b4676dcc01d0dae2b85a0d25753 Mon Sep 17 00:00:00 2001 From: Hanno Schlichting Date: Sat, 11 Jul 2015 18:02:10 +0200 Subject: [PATCH 015/692] Fix ThreadedHTTPTransport import path --- docs/transports/index.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/transports/index.rst b/docs/transports/index.rst index 1f0673e5e..44a9d3e06 100644 --- a/docs/transports/index.rst +++ b/docs/transports/index.rst @@ -33,9 +33,9 @@ For example, to increase the timeout and to disable SSL verification: Builtin Transports ------------------ -.. data:: raven.transport.thread.ThreadedHTTPTransport +.. data:: raven.transport.threaded.ThreadedHTTPTransport - The default transport. Manages a threaded worker for processing messages asynchronous. + The default transport. Manages a threaded worker for processing messages asynchronously. .. data:: raven.transport.http.HTTPTransport From d13b3cf78c93e54bfe13874d2b3d8681cdef50af Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sun, 12 Jul 2015 10:45:49 +0200 Subject: [PATCH 016/692] Started using the sentry doc ext --- .gitmodules | 3 +++ docs/_sentryext | 1 + docs/conf.py | 7 +++++++ 3 files changed, 11 insertions(+) create mode 100644 .gitmodules create mode 160000 docs/_sentryext diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..12c98df1e --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "docs/_sentryext"] + path = docs/_sentryext + url = https://github.com/getsentry/sentry-doc-support.git diff --git a/docs/_sentryext b/docs/_sentryext new file mode 160000 index 000000000..e9b477ffb --- /dev/null +++ b/docs/_sentryext @@ -0,0 +1 @@ +Subproject commit e9b477ffbbb5b50c2a1ccdf4ff845ac1ffbce96b diff --git a/docs/conf.py b/docs/conf.py index 2a4bc6c90..f5e2d2ef1 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -11,6 +11,8 @@ # All configuration values have a default; values that are commented out # serve to show the default. +import os +import sys import datetime # If extensions (or modules to document with autodoc) are in another directory, @@ -222,3 +224,8 @@ ('index', 'raven', u'Raven Documentation', [u'David Cramer'], 1) ] + +if os.environ.get('SENTRY_FEDERATED_DOCS') != '1': + sys.path.insert(0, os.path.abspath('_sentryext')) + import sentryext + sentryext.activate() From 9e385c43b98b9111dd7149f48bdc2fa7e5a594f0 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sun, 12 Jul 2015 11:10:43 +0200 Subject: [PATCH 017/692] Imported new docs --- .gitignore | 3 +- docs/Makefile | 2 +- docs/api.rst | 122 +++++++++ docs/changelog/index.rst | 4 - docs/config.rst | 204 +++++++++++++++ docs/config/index.rst | 247 ------------------ .../index.rst => contributing.rst} | 0 docs/index.rst | 92 ++++--- docs/install/index.rst | 18 -- docs/installation.rst | 25 ++ docs/integrations/bottle.rst | 23 +- docs/integrations/celery.rst | 17 +- docs/integrations/django.rst | 130 +++++---- docs/integrations/flask.rst | 74 ++++-- docs/integrations/index.rst | 16 +- docs/integrations/logbook.rst | 2 +- docs/integrations/logging.rst | 46 ++-- docs/integrations/pylons.rst | 6 +- docs/integrations/rq.rst | 8 +- docs/integrations/tornado.rst | 10 +- docs/integrations/zerorpc.rst | 15 +- docs/integrations/zope.rst | 13 +- docs/platform-support.rst | 9 + docs/transports.rst | 107 ++++++++ docs/transports/index.rst | 68 ----- docs/usage.rst | 91 +++---- docs/wizards.json | 83 ++++++ 27 files changed, 865 insertions(+), 570 deletions(-) create mode 100644 docs/api.rst delete mode 100644 docs/changelog/index.rst create mode 100644 docs/config.rst delete mode 100644 docs/config/index.rst rename docs/{contributing/index.rst => contributing.rst} (100%) delete mode 100644 docs/install/index.rst create mode 100644 docs/installation.rst create mode 100644 docs/platform-support.rst create mode 100644 docs/transports.rst delete mode 100644 docs/transports/index.rst create mode 100644 docs/wizards.json diff --git a/.gitignore b/.gitignore index 01c9c77db..267294f0e 100644 --- a/.gitignore +++ b/.gitignore @@ -13,8 +13,7 @@ pip-log.txt /cover /dist /example_project/local_settings.py -/docs/html -/docs/doctrees +/docs/_build /sentry_index/ /sentry_test_index /example_project/*.db diff --git a/docs/Makefile b/docs/Makefile index ed016ccb1..1db8f3cd8 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -5,7 +5,7 @@ SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = -BUILDDIR = ./ +BUILDDIR = ./_build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 diff --git a/docs/api.rst b/docs/api.rst new file mode 100644 index 000000000..8b3fabb5a --- /dev/null +++ b/docs/api.rst @@ -0,0 +1,122 @@ +API Reference +============= + +.. default-domain:: py + +This gives you an overview of the public API that raven-python exposes. + + +Client +------ + +.. py:class:: raven.Client(dsn=None, **kwargs) + + The client needs to be instanciated once and can then be used for + submitting events to the Sentry server. For information about the + configuration of that client and which parameters are accepted see + :ref:`python-client-config`. + + .. py:method:: capture(event_type, data=None, date=None, \ + time_spent=None, extra=None, stack=False, tags=None, **kwargs) + + This method is the low-level method for reporting events to + Sentry. It captures and processes an event and pipes it via the + configured transport to Sentry. + + Example:: + + capture('raven.events.Message', message='foo', data={ + 'request': { + 'url': '...', + 'data': {}, + 'query_string': '...', + 'method': 'POST', + }, + 'logger': 'logger.name', + }, extra={ + 'key': 'value', + }) + + :param event_type: the module path to the Event class. Builtins can + use shorthand class notation and exclude the + full module path. + :param data: the data base, useful for specifying structured data + interfaces. Any key which contains a '.' will be + assumed to be a data interface. + :param date: the datetime of this event. If not supplied the + current timestamp is used. + :param time_spent: a integer value representing the duration of the + event (in milliseconds) + :param extra: a dictionary of additional standard metadata. + :param stack: If set to `True` a stack frame is recorded together + with the event. + :param tags: list of extra tags + :param kwargs: extra keyword arguments are handled specific to the + reported event type. + :return: a tuple with a 32-length string identifying this event + + .. py:method:: captureMessage(message, **kwargs) + + This is a shorthand to reporting a message via :meth:`capture`. + It passes ``'raven.events.Message'`` as `event_type` and the + message along. All other keyword arguments are regularly + forwarded. + + Example:: + + client.captureMessage('This just happened!') + + .. py:method:: captureException(message, exc_info=None, **kwargs) + + This is a shorthand to reporting an exception via :meth:`capture`. + It passes ``'raven.events.Exception'`` as `event_type` and the + traceback along. All other keyword arguments are regularly + forwarded. + + If exc_info is not provided, or is set to True, then this method + will perform the ``exc_info = sys.exc_info()`` and the requisite + clean-up for you. + + Example:: + + try: + 1 / 0 + except Exception: + client.captureException() + + .. py:method:: send(**data) + + Accepts all data parameters and serializes them, then sends then + onwards via the transport to Sentry. This can be used as to send + low-level protocol data to the server. + + .. py:attribute:: context + + Returns a reference to the thread local context object. See + :py:class:`raven.context.Context` for more information. + +Context +------- + +.. py:class:: raven.context.Context() + + The context object works similar to a dictionary and is used to record + information that should be submitted with events automatically. It is + available through :py:attr:`raven.Client.context` and is thread local. + This means that you can modify this object over time to feed it with + more appropriate information. + + .. py:method:: merge(data) + + Performs a merge of the current data in the context and the new + data provided. + + .. py:method:: clear() + + Clears the context. It's important that you make sure to call + this when you reuse the thread for something else. For instance + for web frameworks it's generally a good idea to call this at the + end of the HTTP request. + + Otherwise you run at risk of seeing incorrect information after + the first use of the thread. diff --git a/docs/changelog/index.rst b/docs/changelog/index.rst deleted file mode 100644 index f8b67d626..000000000 --- a/docs/changelog/index.rst +++ /dev/null @@ -1,4 +0,0 @@ -Changelog -========= - -.. include:: ../../CHANGES diff --git a/docs/config.rst b/docs/config.rst new file mode 100644 index 000000000..27975e182 --- /dev/null +++ b/docs/config.rst @@ -0,0 +1,204 @@ +Configuration +============= + +.. default-domain:: py + +This document describes configuration options available to the Raven +client for the use with Sentry. It also covers some other important parts +about configuring the environment. + + +.. _python-client-config: + +Configuring the Client +---------------------- + +Settings are specified as part of the initialization of the client. The +client is a class that can be instanciated with a specific configuration +and all reporting can then happen from the instance of that object. +Typically an instance is created somewhere globally and then imported as +necessary. + +As of Raven 1.2.0, you can now configure all clients through a standard DSN +string. This can be specified as a default using the ``SENTRY_DSN`` environment +variable, as well as passed to all clients by using the ``dsn`` argument. + +.. code-block:: python + + from raven import Client + + # Read configuration from the environment + client = Client() + + # Manually specify a DSN + client = Client('___DSN___') + + +A reasonably configured client should generally include a few additional +settings: + +.. code-block:: python + + import raven + + client = raven.Client( + dsn='___DSN___' + + # inform the client which parts of code are yours + # include_paths=['my.app'] + include_paths=[__name__.split('.', 1)[0]], + + # pass along the version of your application + # release='1.0.0' + # release=raven.fetch_package_version('my-app') + release=raven.fetch_git_sha(os.path.dirname(__file__)), + ) + +.. versionadded:: 5.2.0 + The *fetch_package_version* and *fetch_git_sha* helpers. + + +The Sentry DSN +-------------- + +.. sentry:edition:: hosted, on-premise + + The most important information is the Sentry DSN. For information + about it see :ref:`configure-the-dsn` in the general Sentry docs. + +The Python client supports one additional modification to the regular DSN +values which is the choice of the transport. To select a specific +transport, the DSN needs to be prepended with the name of the transport. +For instance to select the ``gevent`` transport, the following DSN would +be used:: + + 'gevent+___DSN___' + +For more information see :doc:`transports`. + +Client Arguments +---------------- + +The following are valid arguments which may be passed to the Raven client: + +.. describe:: dsn + + A Sentry compatible DSN as mentioned before:: + + dsn = '___DSN___' + +.. describe:: site + + An optional, arbitrary string to identify this client installation:: + + site = 'my site name' + +.. describe:: name + + This will override the ``server_name`` value for this installation. + Defaults to ``socket.gethostname()``:: + + name = 'sentry_rocks_' + socket.gethostname() + +.. describe:: release + + The version of your application. This will map up into a Release in + Sentry:: + + release = '1.0.3' + +.. describe:: exclude_paths + + Extending this allow you to ignore module prefixes when we attempt to + discover which function an error comes from (typically a view):: + + exclude_paths = [ + 'django', + 'sentry', + 'raven', + 'lxml.objectify', + ] + +.. describe:: include_paths + + For example, in Django this defaults to your list of ``INSTALLED_APPS``, + and is used for drilling down where an exception is located:: + + include_paths = [ + 'django', + 'sentry', + 'raven', + 'lxml.objectify', + ] + +.. describe:: max_list_length + + The maximum number of items a list-like container should store. + + If an iterable is longer than the specified length, the left-most + elements up to length will be kept. + + .. note:: This affects sets as well, which are unordered. + + :: + + list_max_length = 50 + +.. describe:: string_max_length + + The maximum characters of a string that should be stored. + + If a string is longer than the given length, it will be truncated down + to the specified size:: + + string_max_length = 200 + +.. describe:: auto_log_stacks + + Should Raven automatically log frame stacks (including locals) for all + calls as it would for exceptions:: + + auto_log_stacks = True + +.. describe:: processors + + A list of processors to apply to events before sending them to the + Sentry server. Useful for sending additional global state data or + sanitizing data that you want to keep off of the server:: + + processors = ( + 'raven.processors.SanitizePasswordsProcessor', + ) + +Sanitizing Data +--------------- + +Several processors are included with Raven to assist in data +sanitiziation. These are configured with the ``processors`` value. + +.. data:: raven.processors.SanitizePasswordsProcessor + :noindex: + + Removes all keys which resemble ``password``, ``secret``, or + ``api_key`` within stacktrace contexts, HTTP bits (such as cookies, + POST data, the querystring, and environment), and extra data. + +.. data:: raven.processors.RemoveStackLocalsProcessor + :noindex: + + Removes all stacktrace context variables. This will cripple the + functionality of Sentry, as you'll only get raw tracebacks, but it will + ensure no local scoped information is available to the server. + +.. data:: raven.processors.RemovePostDataProcessor + :noindex: + + Removes the ``body`` of all HTTP data. + + +A Note on uWSGI +--------------- + +If you're using uWSGI you will need to add ``enable-threads`` to the +default invocation, or you will need to switch off of the threaded default +transport. diff --git a/docs/config/index.rst b/docs/config/index.rst deleted file mode 100644 index f71538dd1..000000000 --- a/docs/config/index.rst +++ /dev/null @@ -1,247 +0,0 @@ -Configuration -============= - -This document describes configuration options available to Sentry. - - -Configuring the Client ----------------------- - -Settings are specified as part of the initialization of the client. - -As of Raven 1.2.0, you can now configure all clients through a standard DSN -string. This can be specified as a default using the ``SENTRY_DSN`` environment -variable, as well as passed to all clients by using the ``dsn`` argument. - -.. code-block:: python - - from raven import Client - - # Read configuration from the environment - client = Client() - - # Manually specify a DSN - client = Client('http://public:secret@example.com/1') - - -A reasonably configured client should generally include a few additional settings: - -.. code-block:: python - - import raven - - client = raven.Client( - dsn='http://public:secret@example.com/1' - - # inform the client which parts of code are yours - # include_paths=['my.app'] - include_paths=[__name__.split('.', 1)[0]], - - # pass along the version of your application - # release='1.0.0' - # release=raven.fetch_package_version('my-app') - release=raven.fetch_git_sha(os.path.dirname(__file__)), - ) - -.. versionadded:: 5.2.0 - The *fetch_package_version* and *fetch_git_sha* helpers. - - -The Sentry DSN --------------- - -The DSN can be found in Sentry by navigation to Account -> Projects -> [Project Name] -> [Member Name]. Its template resembles the following:: - - '{PROTOCOL}://{PUBLIC_KEY}:{SECRET_KEY}@{HOST}/{PATH}{PROJECT_ID}' - -It is composed of six important pieces: - -* The Protocol used. This can be one of the following: http or https. - -* The public and secret keys to authenticate the client. - -* The hostname of the Sentry server. - -* An optional path if Sentry is not located at the webserver root. This is specific to HTTP requests. - -* The project ID which the authenticated user is bound to. - - -Client Arguments ----------------- - -The following are valid arguments which may be passed to the Raven client: - -dsn -~~~ - -A sentry compatible DSN. - -:: - - dsn = 'http://public:secret@example.com/1' - -project -~~~~~~~ - -Set this to your Sentry project ID. The default value for installations is ``1``. - -:: - - project = 1 - - -public_key -~~~~~~~~~~ - -Set this to the public key of the project member which will authenticate as the -client. You can find this information on the member details page of your project -within Sentry. - -:: - - public_key = 'fb9f9e31ea4f40d48855c603f15a2aa4' - - -secret_key -~~~~~~~~~~ - -Set this to the secret key of the project member which will authenticate as the -client. You can find this information on the member details page of your project -within Sentry. - -:: - - secret_key = '6e968b3d8ba240fcb50072ad9cba0810' - -site -~~~~ - -An optional, arbitrary string to identify this client installation. - -:: - - site = 'my site name' - - -name -~~~~ - -This will override the ``server_name`` value for this installation. Defaults to ``socket.gethostname()``. - -:: - - name = 'sentry_rocks_' + socket.gethostname() - - -release -~~~~~~~~ - -The version of your application. This will map up into a Release in Sentry. - -:: - - release = '1.0.3' - - -exclude_paths -~~~~~~~~~~~~~ - -Extending this allow you to ignore module prefixes when we attempt to discover which function an error comes from (typically a view) - -:: - - exclude_paths = [ - 'django', - 'sentry', - 'raven', - 'lxml.objectify', - ] - -include_paths -~~~~~~~~~~~~~ - -For example, in Django this defaults to your list of ``INSTALLED_APPS``, and is used for drilling down where an exception is located - -:: - - include_paths = [ - 'django', - 'sentry', - 'raven', - 'lxml.objectify', - ] - -list_max_length -~~~~~~~~~~~~~~~ - -The maximum number of items a list-like container should store. - -If an iterable is longer than the specified length, the left-most elements up to length will be kept. - -.. note:: This affects sets as well, which are unordered. - -:: - - list_max_length = 50 - -string_max_length -~~~~~~~~~~~~~~~~~ - -The maximum characters of a string that should be stored. - -If a string is longer than the given length, it will be truncated down to the specified size. - -:: - - string_max_length = 200 - -auto_log_stacks -~~~~~~~~~~~~~~~ - -Should Raven automatically log frame stacks (including locals) for all calls as -it would for exceptions. - -:: - - auto_log_stacks = True - - -processors -~~~~~~~~~~ - -A list of processors to apply to events before sending them to the Sentry server. Useful for sending -additional global state data or sanitizing data that you want to keep off of the server. - -:: - - processors = ( - 'raven.processors.SanitizePasswordsProcessor', - ) - -Sanitizing Data ---------------- - -Several processors are included with Raven to assist in data sanitiziation. These are configured with the -``processors`` value. - -.. data:: raven.processors.SanitizePasswordsProcessor - - Removes all keys which resemble ``password``, ``secret``, or ``api_key`` - within stacktrace contexts, HTTP bits (such as cookies, POST data, - the querystring, and environment), and extra data. - -.. data:: raven.processors.RemoveStackLocalsProcessor - - Removes all stacktrace context variables. This will cripple the functionality of Sentry, as you'll only - get raw tracebacks, but it will ensure no local scoped information is available to the server. - -.. data:: raven.processors.RemovePostDataProcessor - - Removes the ``body`` of all HTTP data. - - -A Note on uWSGI ---------------- - -If you're using uWSGI you will need to add ``enable-threads`` to the default invocation, or you will need to switch off of the threaded transport. diff --git a/docs/contributing/index.rst b/docs/contributing.rst similarity index 100% rename from docs/contributing/index.rst rename to docs/contributing.rst diff --git a/docs/index.rst b/docs/index.rst index 21b7648a9..19bb2c295 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,68 +1,74 @@ -raven-python -============ +.. sentry:edition:: self -Raven is a standalone (and the official) Python client for `Sentry `_. + Raven Python + ============ -This version of Raven requires Sentry 7.0 or newer. +.. sentry:edition:: hosted, on-premise -Users Guide ------------ + .. class:: platform-python -.. toctree:: - :maxdepth: 2 + Python + ====== - install/index - config/index - usage - integrations/index - transports/index +Raven for Python (raven-python) is the official standalone Python client +for Sentry. It can be used with any modern Python interpreter be it +CPython 2.x or 3.x, PyPy or Jython. It's an Open Source project and +available under a very liberal BSD license. + +.. sentry:edition:: self -Developers ----------- + Users Guide + ----------- .. toctree:: :maxdepth: 2 + :titlesonly: - contributing/index + installation + config + usage + integrations/index + transports + platform-support + api -Reference ---------- +.. sentry:edition:: self -.. toctree:: - :maxdepth: 1 + For Developers + -------------- - changelog/index + .. toctree:: + :maxdepth: 2 + :titlesonly: -Supported Platforms -------------------- + contributing -- Python 2.6 -- Python 2.7 -- Python 3.2 -- Python 3.3 -- PyPy -- Google App Engine + Supported Platforms + ------------------- -About Sentry ------------- + - Python 2.6 + - Python 2.7 + - Python 3.2 + - Python 3.3 + - PyPy + - Google App Engine -Sentry provides you with a generic interface to view and interact with your error logs. With this -it allows you to interact and view near real-time information to discover issues and more -easily trace them in your application. + Deprecation Notes + ----------------- + + Milestones releases are 1.3 or 1.4, and our deprecation policy is to a two + version step. For example, a feature will be deprecated in 1.3, and + completely removed in 1.4. + + Resources + --------- -More information about Sentry can be found at http://www.getsentry.com/ +.. sentry:edition:: hosted, on-premise -Resources ---------- + Resources: * `Documentation `_ * `Bug Tracker `_ * `Code `_ * `Mailing List `_ * `IRC `_ (irc.freenode.net, #sentry) - -Deprecation Notes ------------------ - -Milestones releases are 1.3 or 1.4, and our deprecation policy is to a two version step. For example, -a feature will be deprecated in 1.3, and completely removed in 1.4. diff --git a/docs/install/index.rst b/docs/install/index.rst deleted file mode 100644 index b6af50a7c..000000000 --- a/docs/install/index.rst +++ /dev/null @@ -1,18 +0,0 @@ -Install -======= - -If you haven't already, start by downloading Raven. The easiest way is with **pip**:: - - pip install raven --upgrade - -Or with *setuptools*:: - - easy_install -U raven - -Requirements ------------- - -If you installed using pip or setuptools you shouldn't need to worry about requirements. Otherwise -you will need to install the following packages in your environment: - - - ``simplejson`` diff --git a/docs/installation.rst b/docs/installation.rst new file mode 100644 index 000000000..d04497094 --- /dev/null +++ b/docs/installation.rst @@ -0,0 +1,25 @@ +Installation +============ + +If you haven't already, start by downloading Raven. The easiest way is +with *pip*:: + + pip install raven --upgrade + +Or alternatively with *setuptools*:: + + easy_install -U raven + +If you want to use the latest git version you can get it from `the github +repository `_:: + + git clone https://github.com/getsentry/raven-python + pip install raven-python + +Certain additional features can be installed by defining the feature when +``pip`` installing it. For instance to install all dependencies needed to +use the Flask integration, you can depend on ``raven[flask]``:: + + pip install raven[flask] + +For more information refer to the individual integration documentation. diff --git a/docs/integrations/bottle.rst b/docs/integrations/bottle.rst index ae1fd50b4..3b1a48719 100644 --- a/docs/integrations/bottle.rst +++ b/docs/integrations/bottle.rst @@ -1,6 +1,9 @@ Bottle ====== +`Bottle `_ is a microframework for Python. Raven +supports this framework through the WSGI integration. + Setup ----- @@ -11,11 +14,14 @@ The first thing you'll need to do is to disable catchall in your Bottle app:: app = bottle.app() app.catchall = False -.. note:: Bottle will not propagate exceptions to the underlying WSGI middleware by default. Setting catchall to False disables that. +.. note:: Bottle will not propagate exceptions to the underlying WSGI + middleware by default. Setting catchall to False disables that. -Sentry will act as Middleware:: +Sentry will then act as Middleware:: + from raven import Client from raven.contrib.bottle import Sentry + client = Client('___DSN___') app = Sentry(app, client) Usage @@ -25,15 +31,16 @@ Once you've configured the Sentry application you need only call run with it:: run(app=app) -If you want to send additional events, a couple of shortcuts are provided on the Bottle request app object. +If you want to send additional events, a couple of shortcuts are provided +on the Bottle request app object. Capture an arbitrary exception by calling ``captureException``:: - >>> try: - >>> 1 / 0 - >>> except ZeroDivisionError: - >>> request.app.sentry.captureException() + try: + 1 / 0 + except ZeroDivisionError: + request.app.sentry.captureException() Log a generic message with ``captureMessage``:: - >>> request.app.sentry.captureMessage('hello, world!') + request.app.sentry.captureMessage('Hello, world!') diff --git a/docs/integrations/celery.rst b/docs/integrations/celery.rst index c6865e430..052d34a60 100644 --- a/docs/integrations/celery.rst +++ b/docs/integrations/celery.rst @@ -1,14 +1,17 @@ Celery ====== -tl;dr register a couple of signals to hijack Celery error handling +`Celery `_ is a distributed task queue +system for Python built on AMQP principles. For Celery built-in support +by Raven is provided but it requires some manual configuraiton. -.. code-block:: python +To capture errors, you need to register a couple of signals to hijack +Celery error handling:: from raven import Client from raven.contrib.celery import register_signal, register_logger_signal - client = Client() + client = Client('___DSN___') # register a custom filter to filter out duplicate logs register_logger_signal(client) @@ -26,13 +29,13 @@ A more complex version to encapsulate behavior: .. code-block:: python import celery + import raven + from raven.contrib.celery import register_signal, register_logger_signal class Celery(celery.Celery): - def on_configure(self): - import raven - from raven.contrib.celery import register_signal, register_logger_signal - client = raven.Client() + def on_configure(self): + client = raven.Client('___DSN___') # register a custom filter to filter out duplicate logs register_logger_signal(client) diff --git a/docs/integrations/django.rst b/docs/integrations/django.rst index 96ea0e811..55baf35c8 100644 --- a/docs/integrations/django.rst +++ b/docs/integrations/django.rst @@ -1,27 +1,35 @@ Django ====== -Support -------- +.. default-domain:: py -While older versions of Django will likely work, officially only version 1.4 and newer are supported. +`Django `_ is one of (if not the) Python's most +popular web frameworks. Support is built into Raven but needs some +configuration. While older versions of Django will likely work, +officially only version 1.4 and newer are supported. Setup ----- -Using the Django integration is as simple as adding :mod:`raven.contrib.django.raven_compat` to your installed apps:: +Using the Django integration is as simple as adding +:mod:`raven.contrib.django.raven_compat` to your installed apps:: INSTALLED_APPS = ( 'raven.contrib.django.raven_compat', ) -.. note:: This causes Raven to install a hook in Django that will automatically report uncaught exceptions. +.. note:: This causes Raven to install a hook in Django that will + automatically report uncaught exceptions. -Additional settings for the client are configured using the ``RAVEN_CONFIG`` dictionary:: +Additional settings for the client are configured using the +``RAVEN_CONFIG`` dictionary:: import raven + RAVEN_CONFIG = { - 'dsn': 'http://public:secret@example.com/1', + '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__)), } @@ -40,27 +48,38 @@ You'll be referencing the client slightly differently in Django as well:: Using with Raven.js ------------------- -A Django template tag is provided to render a proper public DSN inside your templates, you must first load ``raven``:: +A Django template tag is provided to render a proper public DSN inside +your templates, you must first load ``raven``: + +.. sourcecode:: django {% load raven %} -Inside your template, you can now use:: +Inside your template, you can now use: + +.. sourcecode:: html+django -By default, the DSN is generated in a protocol relative fashion, e.g. ``//public@example.com/1``. If you need a specific protocol, you can override:: +By default, the DSN is generated in a protocol relative fashion, e.g. +``//public@example.com/1``. If you need a specific protocol, you can +override: + +.. sourcecode:: html+django {% sentry_public_dsn 'https' %} -See `Raven.js documentation `_ for more information. +.. sentry:edition:: hosted, on-premise + + See the :doc:`Raven.js documentation <../../../clients/javascript/index>` + for more information. Integration with :mod:`logging` ------------------------------- -To integrate with the standard library's :mod:`logging` module: - -:: +To integrate with the standard library's :mod:`logging` module the +following config can be used:: LOGGING = { 'version': 1, @@ -71,7 +90,8 @@ To integrate with the standard library's :mod:`logging` module: }, 'formatters': { 'verbose': { - 'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s' + 'format': '%(levelname)s %(asctime)s %(module)s ' + '%(process)d %(thread)d %(message)s' }, }, 'handlers': { @@ -136,8 +156,8 @@ Message References Sentry supports sending a message ID to your clients so that they can be tracked easily by your development team. There are two ways to access this -information, the first is via the ``X-Sentry-ID`` HTTP response header. Adding -this is as simple as appending a middleware to your stack:: +information, the first is via the ``X-Sentry-ID`` HTTP response header. +Adding this is as simple as appending a middleware to your stack:: MIDDLEWARE_CLASSES = MIDDLEWARE_CLASSES + ( # We recommend putting this as high in the chain as possible @@ -146,25 +166,24 @@ this is as simple as appending a middleware to your stack:: ) Another alternative method is rendering it within a template. By default, -Sentry will attach :attr:`request.sentry` when it catches a Django exception. -In our example, we will use this information to modify the default -:file:`500.html` which is rendered, and show the user a case reference ID. The -first step in doing this is creating a custom :func:`handler500` in your -:file:`urls.py` file:: +Sentry will attach :attr:`request.sentry` when it catches a Django +exception. In our example, we will use this information to modify the +default :file:`500.html` which is rendered, and show the user a case +reference ID. The first step in doing this is creating a custom +:func:`handler500` in your :file:`urls.py` file:: from django.conf.urls.defaults import * from django.views.defaults import page_not_found, server_error + from django.template import Context, loader + from django.http import HttpResponseServerError def handler500(request): - """ - 500 error handler which includes ``request`` in the context. + """500 error handler which includes ``request`` in the context. Templates: `500.html` Context: None """ - from django.template import Context, loader - from django.http import HttpResponseServerError t = loader.get_template('500.html') # You need to create a 500.html template. return HttpResponseServerError(t.render(Context({ @@ -174,11 +193,12 @@ first step in doing this is creating a custom :func:`handler500` in your Once we've successfully added the :data:`request` context variable, adding the Sentry reference ID to our :file:`500.html` is simple: -.. code-block:: django +.. sourcecode:: html+django

You've encountered an error, oh noes!

{% if request.sentry.id %} -

If you need assistance, you may reference this error as {{ request.sentry.id }}.

+

If you need assistance, you may reference this error as + {{ request.sentry.id }}.

{% endif %} WSGI Middleware @@ -197,30 +217,31 @@ level of your Django application:: Additional Settings ------------------- -SENTRY_CLIENT -~~~~~~~~~~~~~~ +.. describe:: SENTRY_CLIENT -In some situations you may wish for a slightly different behavior to how Sentry -communicates with your server. For this, Raven allows you to specify a custom -client:: + In some situations you may wish for a slightly different behavior to + how Sentry communicates with your server. For this, Raven allows you + to specify a custom client:: - SENTRY_CLIENT = 'raven.contrib.django.raven_compat.DjangoClient' + SENTRY_CLIENT = 'raven.contrib.django.raven_compat.DjangoClient' -SENTRY_CELERY_LOGLEVEL -~~~~~~~~~~~~~~~~~~~~~~ +.. describe:: SENTRY_CELERY_LOGLEVEL -If you are also using Celery, there is a handler being automatically registered -for you that captures the errors from workers. The default logging level for -that handler is ``logging.ERROR`` and can be customized using this setting:: + If you are also using Celery, there is a handler being automatically + registered for you that captures the errors from workers. The default + logging level for that handler is ``logging.ERROR`` and can be + customized using this setting:: - SENTRY_CELERY_LOGLEVEL = logging.INFO - RAVEN_CONFIG = { - 'CELERY_LOGLEVEL': logging.INFO - } + SENTRY_CELERY_LOGLEVEL = logging.INFO + RAVEN_CONFIG = { + 'CELERY_LOGLEVEL': logging.INFO + } Caveats ------- +The following things you should keep in mind when using Raven with Django. + Error Handling Middleware ~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -238,6 +259,7 @@ To work around this, you can either disable your error handling middleware, or add something like the following:: from django.core.signals import got_request_exception + class MyMiddleware(object): def process_exception(self, request, exception): # Make sure the exception signal is fired for Sentry @@ -253,6 +275,7 @@ response codes. Or, alternatively, you can just enable Sentry responses:: from raven.contrib.django.raven_compat.models import sentry_exception_handler + class MyMiddleware(object): def process_exception(self, request, exception): # Make sure the exception signal is fired for Sentry @@ -263,12 +286,14 @@ Or, alternatively, you can just enable Sentry responses:: Gunicorn ~~~~~~~~ -If you are running Django with `gunicorn `_ and using the -``gunicorn`` executable, instead of the ``run_gunicorn`` management command, you -will need to add a hook to gunicorn to activate Raven:: +If you are running Django with `gunicorn `_ and +using the ``gunicorn`` executable, instead of the ``run_gunicorn`` +management command, you will need to add a hook to gunicorn to activate +Raven:: + + from django.core.management import call_command def when_ready(server): - from django.core.management import call_command call_command('validate') Circus @@ -278,20 +303,22 @@ If you are running Django with `circus `_ and `chaussette `_ you will also need to add a hook to circus to activate Raven:: + from django.conf import settings + from django.core.management import call_command + def run_raven(*args, **kwargs): """Set up raven for django by running a django command. It is necessary because chaussette doesn't run a django command. - """ - from django.conf import settings - from django.core.management import call_command if not settings.configured: settings.configure() call_command('validate') return True -And in your circus configuration:: +And in your circus configuration: + +.. sourcecode:: ini [socket:dwebapp] host = 127.0.0.1 @@ -302,4 +329,3 @@ And in your circus configuration:: use_sockets = True numprocesses = 2 hooks.after_start = dproject.hooks.run_raven - diff --git a/docs/integrations/flask.rst b/docs/integrations/flask.rst index 1fab91d7d..b2c95e191 100644 --- a/docs/integrations/flask.rst +++ b/docs/integrations/flask.rst @@ -1,10 +1,14 @@ Flask ===== +`Flask `_ is a popular Python micro webframework. +Support for Flask is provided by Raven directly but for some dependencies +you need to install raven with the flask feature set. + Installation ------------ -If you haven't already, install raven with its explicit Flask dependencies: +If you haven't already, install raven with its explicit Flask dependencies:: pip install raven[flask] @@ -14,10 +18,13 @@ Setup The first thing you'll need to do is to initialize Raven under your application:: from raven.contrib.flask import Sentry - sentry = Sentry(app, dsn='http://public_key:secret_key@example.com/1') + sentry = Sentry(app, dsn='___DSN___') + +If you don't specify the ``dsn`` value, we will attempt to read it from +your environment under the ``SENTRY_DSN`` key. -If you don't specify the ``dsn`` value, we will attempt to read it from your environment under -the ``SENTRY_DSN`` key. +Extended Setup +-------------- You can optionally configure logging too:: @@ -44,25 +51,36 @@ You can pass parameters in the ``init_app`` hook:: logging=True, level=logging.ERROR) return app - Settings -------- -Additional settings for the client can be configured using ``SENTRY_`` in your application's configuration:: +Additional settings for the client can be configured using +``SENTRY_`` in your application's configuration:: class MyConfig(object): - SENTRY_DSN = 'http://public_key:secret_key@example.com/1' + SENTRY_DSN = '___DSN___' SENTRY_INCLUDE_PATHS = ['myproject'] -If `Flask-Login `_ is used by your application (including `Flask-Security `_), user information will 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``:: +If `Flask-Login `_ is used by +your application (including `Flask-Security +`_), user information will +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``:: class MyConfig(object): SENTRY_USER_ATTRS = ['username', 'first_name', 'last_name', 'email'] -``email`` will be captured as ``sentry.interfaces.User.email``, and any additionl attributes will be available under ``sentry.interfaces.User.data`` +``email`` will be captured as ``sentry.interfaces.User.email``, and any +additionl 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:: +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:: class MyExceptionType(Exception): def __init__(self, message): @@ -74,25 +92,28 @@ You can specify the types of exceptions that should not be reported by Sentry cl Usage ----- -Once you've configured the Sentry application it will automatically capture uncaught exceptions within Flask. If you -want to send additional events, a couple of shortcuts are provided on the Sentry Flask middleware object. +Once you've configured the Sentry application it will automatically +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``:: - >>> try: - >>> 1 / 0 - >>> except ZeroDivisionError: - >>> sentry.captureException() + try: + 1 / 0 + except ZeroDivisionError: + sentry.captureException() Log a generic message with ``captureMessage``:: - >>> sentry.captureMessage('hello, world!') + sentry.captureMessage('hello, world!') Getting the last event id ------------------------- -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. +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 @@ -104,10 +125,17 @@ This allow to present the user an error ID if have done a custom error 500 page. Dealing with proxies -------------------- -When your Flask application is behind a proxy such as nginx, Sentry will use the remote address from the proxy, rather than from the actual requesting computer. -By using ``ProxyFix`` from `werkzeug.contrib.fixers `_ the Flask ``.wsgi_app`` can be modified to send the actual ``REMOTE_ADDR`` along to Sentry. :: +When your Flask application is behind a proxy such as nginx, Sentry will +use the remote address from the proxy, rather than from the actual +requesting computer. By using ``ProxyFix`` from `werkzeug.contrib.fixers +`_ +the Flask ``.wsgi_app`` can be modified to send the actual ``REMOTE_ADDR`` +along to Sentry. :: from werkzeug.contrib.fixers import ProxyFix app.wsgi_app = ProxyFix(app.wsgi_app) -This may also require `changes `_ to the proxy configuration to pass the right headers if it isn't doing so already. +This may also require `changes +`_ +to the proxy configuration to pass the right headers if it isn't doing so +already. diff --git a/docs/integrations/index.rst b/docs/integrations/index.rst index bfdefe7a7..b93653856 100644 --- a/docs/integrations/index.rst +++ b/docs/integrations/index.rst @@ -1,13 +1,18 @@ Integrations ============ -.. note:: Some integrations allow specifying these in a standard configuration, otherwise they are generally passed upon - instantiation of the Sentry client. +The Raven Python module also comes with integration for some commonly used +libraries to automatically capture errors from common environments. This +means that once you have such an integration configured you typically do +not need to report errors manually. + +Some integrations allow specifying these in a standard configuration, +otherwise they are generally passed upon instantiation of the Sentry +client. .. toctree:: - :maxdepth: 2 + :maxdepth: 1 - asyncio bottle celery django @@ -16,7 +21,8 @@ Integrations logging pylons pyramid + rq + tornado wsgi zerorpc zope - tornado diff --git a/docs/integrations/logbook.rst b/docs/integrations/logbook.rst index 26f4ef114..d42311b52 100644 --- a/docs/integrations/logbook.rst +++ b/docs/integrations/logbook.rst @@ -15,7 +15,7 @@ First you'll need to configure a handler:: You can also automatically configure the default client with a DSN:: # Configure the default client - handler = SentryHandler('http://public:secret@example.com/1') + handler = SentryHandler('___DSN___') Finally, bind your handler to your context:: diff --git a/docs/integrations/logging.rst b/docs/integrations/logging.rst index 1eef8bc99..602f13c6c 100644 --- a/docs/integrations/logging.rst +++ b/docs/integrations/logging.rst @@ -1,8 +1,10 @@ Logging ======= -Sentry supports the ability to directly tie into the :mod:`logging` module. To -use it simply add :class:`SentryHandler` to your logger. +.. default-domain:: py + +Sentry supports the ability to directly tie into the :mod:`logging` +module. To use it simply add :class:`SentryHandler` to your logger. First you'll need to configure a handler:: @@ -15,7 +17,7 @@ First you'll need to configure a handler:: You can also automatically configure the default client with a DSN:: # Configure the default client - handler = SentryHandler('http://public:secret@example.com/1') + handler = SentryHandler('___DSN___') Finally, call the :func:`setup_logging` helper function:: @@ -31,7 +33,8 @@ Another option is to use :mod:`logging.config.dictConfig`:: 'formatters': { 'console': { - 'format': '[%(asctime)s][%(levelname)s] %(name)s %(filename)s:%(funcName)s:%(lineno)d | %(message)s', + 'format': '[%(asctime)s][%(levelname)s] %(name)s ' + '%(filename)s:%(funcName)s:%(lineno)d | %(message)s', 'datefmt': '%H:%M:%S', }, }, @@ -45,7 +48,7 @@ Another option is to use :mod:`logging.config.dictConfig`:: 'sentry': { 'level': 'ERROR', 'class': 'raven.handlers.logging.SentryHandler', - 'dsn': 'http://public:secret@example.com/1', + 'dsn': '___DSN___', }, }, @@ -79,14 +82,21 @@ Sentry to render it based on that information:: # If you're actually catching an exception, use `exc_info=True` logger.error('There was an error, with a stacktrace!', exc_info=True) - # If you don't have an exception, but still want to capture a stacktrace, use the `stack` arg + # If you don't have an exception, but still want to capture a + # stacktrace, use the `stack` arg logger.error('There was an error, with a stacktrace!', extra={ 'stack': True, }) -.. note:: Depending on the version of Python you're using, ``extra`` might not be an acceptable keyword argument for a logger's ``.exception()`` method (``.debug()``, ``.info()``, ``.warning()``, ``.error()`` and ``.critical()`` should work fine regardless of Python version). This should be fixed as of Python 3.2. Official issue here: http://bugs.python.org/issue15541. +.. note:: Depending on the version of Python you're using, ``extra`` might + not be an acceptable keyword argument for a logger's ``.exception()`` + method (``.debug()``, ``.info()``, ``.warning()``, ``.error()`` and + ``.critical()`` should work fine regardless of Python version). This + should be fixed as of Python 3.2. Official issue here: + http://bugs.python.org/issue15541. -While we don't recommend this, you can also enable implicit stack capturing for all messages:: +While we don't recommend this, you can also enable implicit stack +capturing for all messages:: client = Client(..., auto_log_stacks=True) handler = SentryHandler(client) @@ -108,20 +118,16 @@ within your ``extra`` clause:: } }) -.. note:: The ``url`` and ``view`` keys are used internally by Sentry within the extra data. -.. note:: Any key (in ``data``) prefixed with ``_`` will not automatically output on the Sentry details view. +.. note:: The ``url`` and ``view`` keys are used internally by Sentry + within the extra data. + +.. note:: Any key (in ``data``) prefixed with ``_`` will not automatically + output on the Sentry details view. -Sentry will intelligently group messages if you use proper string formatting. For example, the following messages would -be seen as the same message within Sentry:: +Sentry will intelligently group messages if you use proper string +formatting. For example, the following messages would be seen as the same +message within Sentry:: logger.error('There was some %s error', 'crazy') logger.error('There was some %s error', 'fun') logger.error('There was some %s error', 1) - -.. note:: - - Other languages that provide a logging package that is comparable to the - python :mod:`logging` package may define a Sentry handler. Check the - `Extending Sentry - `_ - documentation. diff --git a/docs/integrations/pylons.rst b/docs/integrations/pylons.rst index f220fd310..f03c022ed 100644 --- a/docs/integrations/pylons.rst +++ b/docs/integrations/pylons.rst @@ -1,6 +1,8 @@ Pylons ====== +Pylons is a framework for Python. + WSGI Middleware --------------- @@ -17,7 +19,7 @@ Configuration is handled via the sentry namespace: .. code-block:: ini [sentry] - dsn=http://public:secret@example.com/1 + dsn=___DSN___ include_paths=my.package,my.other.package, exclude_paths=my.package.crud @@ -65,5 +67,3 @@ Add the following lines to your project's `.ini` file to setup `SentryHandler`: datefmt = %H:%M:%S .. note:: You may want to setup other loggers as well. - - diff --git a/docs/integrations/rq.rst b/docs/integrations/rq.rst index 1a42e5555..d52d758f6 100644 --- a/docs/integrations/rq.rst +++ b/docs/integrations/rq.rst @@ -8,11 +8,13 @@ Usage The simplest way is passing your ``SENTRY_DSN`` through ``rqworker``:: - $ rqworker --sentry-dsn="http://public:secret@example.com/1" + $ rqworker --sentry-dsn="___DSN___" Custom Client ------------- -It's possible to use a custom ``Client`` object and use your own worker process as an alternative to ``rqworker``. +It's possible to use a custom ``Client`` object and use your own worker +process as an alternative to ``rqworker``. -Please see ``rq``'s documentation for more information: http://python-rq.org/patterns/sentry/ +Please see ``rq``'s documentation for more information: +http://python-rq.org/patterns/sentry/ diff --git a/docs/integrations/tornado.rst b/docs/integrations/tornado.rst index 481bb3bb6..4dab1dc1c 100644 --- a/docs/integrations/tornado.rst +++ b/docs/integrations/tornado.rst @@ -1,6 +1,8 @@ Tornado ======= +Tornado is an async web framework for Python. + Setup ----- @@ -8,7 +10,6 @@ The first thing you'll need to do is to initialize sentry client under your application .. code-block:: python - :emphasize-lines: 2,11,12,13 import tornado.web from raven.contrib.tornado import AsyncSentryClient @@ -21,7 +22,7 @@ your application (r"/", MainHandler), ]) application.sentry_client = AsyncSentryClient( - 'http://public_key:secret_key@host:port/project' + '___DSN___' ) @@ -36,7 +37,8 @@ can automatically capture uncaught exceptions by inheriting the `SentryMixin` cl import tornado.web from raven.contrib.tornado import SentryMixin - class UncaughtExceptionExampleHandler(SentryMixin, tornado.web.RequestHandler): + class UncaughtExceptionExampleHandler( + SentryMixin, tornado.web.RequestHandler): def get(self): 1/0 @@ -79,7 +81,7 @@ Asynchronous .. tip:: - The value returned by the yield is a HTTPResponse obejct. + The value returned by the yield is a ``HTTPResponse`` object. Synchronous diff --git a/docs/integrations/zerorpc.rst b/docs/integrations/zerorpc.rst index f4cd412bb..2326821f6 100644 --- a/docs/integrations/zerorpc.rst +++ b/docs/integrations/zerorpc.rst @@ -1,6 +1,9 @@ ZeroRPC ======= +ZeroRPC is a light-weight, reliable and language-agnostic library for +distributed communication between server-side processes. + Setup ----- @@ -12,25 +15,17 @@ registered into ZeroRPC's context manager:: from raven.contrib.zerorpc import SentryMiddleware - sentry = SentryMiddleware(dsn='udp://public_key:secret_key@example.com:4242/1') + sentry = SentryMiddleware(dsn='___DSN___') zerorpc.Context.get_instance().register_middleware(sentry) By default, the middleware will hide internal frames from ZeroRPC when it submits exceptions to Sentry. This behavior can be disabled by passing the ``hide_zerorpc_frames`` parameter to the middleware:: - sentry = SentryMiddleware(hide_zerorpc_frames=False, dsn='udp://public_key:secret_key@example.com:4242/1') + sentry = SentryMiddleware(hide_zerorpc_frames=False, dsn='___DSN___') Compatibility ------------- - ZeroRPC-Python < 0.4.0 is compatible with Raven <= 3.1.0; - ZeroRPC-Python >= 0.4.0 requires Raven > 3.1.0. - -Caveats -------- - -Since sending an exception to Sentry will basically block your RPC call, you are -*strongly* advised to use the UDP server of Sentry. In any cases, a cleaner and -long term solution would be to make Raven requests to the Sentry server -asynchronous. diff --git a/docs/integrations/zope.rst b/docs/integrations/zope.rst index 572ff65b9..c6de39256 100644 --- a/docs/integrations/zope.rst +++ b/docs/integrations/zope.rst @@ -18,17 +18,18 @@ A basic setup for logging looks like that: %import raven.contrib.zope - dsn YOUR_DSN + dsn ___DSN___ level ERROR -This configuration keeps the regular logging to a logfile, but adds logging to sentry for ERRORs. +This configuration keeps the regular logging to a logfile, but adds +logging to sentry for ERRORs. -All options of :py:class:`raven.base.Client` are supported. See :ref:`usage-label` +All options of :py:class:`raven.base.Client` are supported. -Nobody writes zope.conf files these days, instead buildout recipe does that. -To add the equivalent configuration, you would do this: +Nobody writes zope.conf files these days, instead buildout recipe does +that. To add the equivalent configuration, you would do this: .. code-block:: ini @@ -42,6 +43,6 @@ To add the equivalent configuration, you would do this: level INFO - dsn YOUR_DSN + dsn ___DSN___ level ERROR diff --git a/docs/platform-support.rst b/docs/platform-support.rst new file mode 100644 index 000000000..9e3727446 --- /dev/null +++ b/docs/platform-support.rst @@ -0,0 +1,9 @@ +Supported Platforms +=================== + +- Python 2.6 +- Python 2.7 +- Python 3.2 +- Python 3.3 +- PyPy +- Google App Engine diff --git a/docs/transports.rst b/docs/transports.rst new file mode 100644 index 000000000..1db40ade7 --- /dev/null +++ b/docs/transports.rst @@ -0,0 +1,107 @@ +Transports +========== + +A transport is the mechanism in which Raven sends the HTTP request to the +Sentry server. By default, Raven uses a threaded asynchronous transport, +but you can easily adjust this by modifying your ``SENTRY_DSN`` value. + +Transport registration is done via the URL prefix, so for example, a +synchronous transport is as simple as prefixing your ``SENTRY_DSN`` with +the ``sync+`` value. + +Options are passed to transports via the querystring. + +All transports should support at least the following options: + +``timeout = 1`` + The time to wait for a response from the server, in seconds. + +``verify_ssl = 1`` + If the connection is HTTPS, validate the certificate and hostname. + +``ca_certs = [raven]/data/cacert.pem`` + A certificate bundle to use when validating SSL connections. + +For example, to increase the timeout and to disable SSL verification:: + + SENTRY_DSN = '___DSN___?timeout=5&verify_ssl=0' + + +aiohttp +------- + +Should only be used within a :pep:`3156` compatible event loops +(*asyncio* itself and others). + +:: + + SENTRY_DSN = 'aiohttp+___DSN___' + +Eventlet +-------- + +Should only be used within an Eventlet IO loop. + +:: + + SENTRY_DSN = 'eventlet+___DSN___' + + +Gevent +------ + +Should only be used within a Gevent IO loop. + +:: + + SENTRY_DSN = 'gevent+___DSN___' + + +Requests +-------- + +Requires the ``requests`` library. Synchronous. + +:: + + SENTRY_DSN = 'requests+___DSN___' + + +Sync +---- + +A synchronous blocking transport. + +:: + + SENTRY_DSN = 'sync+___DSN___' + + +Threaded (Default) +------------------ + +Spawns an async worker for processing messages. + +:: + + SENTRY_DSN = 'threaded+___DSN___' + + +Tornado +------- + +Should only be used within a Tornado IO loop. + +:: + + SENTRY_DSN = 'tornado+___DSN___' + + +Twisted +------- + +Should only be used within a Twisted event loop. + +:: + + SENTRY_DSN = 'twisted+___DSN___' diff --git a/docs/transports/index.rst b/docs/transports/index.rst deleted file mode 100644 index 44a9d3e06..000000000 --- a/docs/transports/index.rst +++ /dev/null @@ -1,68 +0,0 @@ -Transports -========== - -A transport is the mechanism in which Raven sends the HTTP request to the Sentry server. By default, Raven uses a threaded asynchronous transport, but you can easily adjust this by modifying your ``SENTRY_DSN`` value. - -Transport registration is done as part of the Client configuration: - -.. code-block:: python - - # Use the synchronous HTTP transport - client = Client('http://public:secret@example.com/1', transport=HTTPTransport) - -Options are passed to transports via the querystring. - -All transports should support at least the following options: - -``timeout = 1`` - The time to wait for a response from the server, in seconds. - -``verify_ssl = 1`` - If the connection is HTTPS, validate the certificate and hostname. - -``ca_certs = [raven]/data/cacert.pem`` - A certificate bundle to use when validating SSL connections. - -For example, to increase the timeout and to disable SSL verification: - -:: - - SENTRY_DSN = 'http://public:secret@example.com/1?timeout=5&verify_ssl=0' - - -Builtin Transports ------------------- - -.. data:: raven.transport.threaded.ThreadedHTTPTransport - - The default transport. Manages a threaded worker for processing messages asynchronously. - -.. data:: raven.transport.http.HTTPTransport - - A synchronous blocking transport. - -.. data:: raven.transport.eventlet.EventletHTTPTransport - - Should only be used within an Eventlet IO loop. - -.. data:: raven.transport.gevent.GeventedHTTPTransport - - Should only be used within a Gevent IO loop. - -.. data:: raven.transport.requests.RequestsHTTPTransport - - A synchronous transport which relies on the ``requests`` library. - -.. data:: raven.transport.tornado.TornadoHTTPTransport - - Should only be used within a Tornado IO loop. - -.. data:: raven.transport.twisted.TwistedHTTPTransport - - Should only be used within a Twisted event loop. - - -Other Transports ----------------- - -- `aiohttp `_ diff --git a/docs/usage.rst b/docs/usage.rst index e5384c63b..287bbf90c 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -1,78 +1,79 @@ Usage ===== +This gives a basic overview of how to use the raven client with Python +directly. + Capture an Error ---------------- -:: +The most basic use for raven is to record one specific error that occurs:: from raven import Client - client = Client('http://dd2c825ff9b1417d88a99573903ebf80:91631495b10b45f8a1cdbc492088da6a@localhost:9000/1') + client = Client('___DSN___') try: 1 / 0 except ZeroDivisionError: client.captureException() +Reporting an Event +------------------ -Adding Context --------------- +To report an arbitrary event you can use the +:py:meth:`~raven.Client.capture` method. This is the most low-level +method available. In most cases you would want to use the +:py:meth:`~raven.Client.captureMessage` method instead however which +directly reports a message:: -A few helpers exist for adding context to a request. These are most useful within a middleware, or some kind of context wrapper. + client.captureMessage('Something went fundamentally wrong') -:: - # If you're using the Django client, we already deal with this for you. - class DjangoUserContext(object): - def process_request(self, request): - client.user_context({ - 'email': request.user.email, - }) +Adding Context +-------------- - def process_response(self, request): +The raven client internally keeps a thread local mapping that can carry +additional information. Whenever a message is submitted to Sentry that +additional data will be passed along. + +For instance if you use a web framework, you can use this to inject +additional information into the context. The basic primitive for this is +the :py:attr:`~raven.Client.context` attribute. It provides a `merge()` +and `clear()` function that can be used:: + + def handle_request(request): + client.context.merge({'user': { + 'email': request.user.email + }}) + try: + ... + finally: client.context.clear() - -See also: - -- Client.extra_context -- Client.http_context -- Client.tags_context - - Testing the Client ------------------ -Once you've got your server configured, you can test the Raven client by using its CLI:: +Once you've got your server configured, you can test the Raven client by +using its CLI:: - raven test + raven test ___DSN___ -If you've configured your environment to have SENTRY_DSN available, you can simply drop -the optional DSN argument:: +If you've configured your environment to have ``SENTRY_DSN`` available, you +can simply drop the optional DSN argument:: - raven test + raven test You should get something like the following, assuming you're configured everything correctly:: - $ raven test http://dd2c825ff9b1417d88a99573903ebf80:91631495b10b45f8a1cdbc492088da6a@localhost:9000/1 - Using DSN configuration: - http://dd2c825ff9b1417d88a99573903ebf80:91631495b10b45f8a1cdbc492088da6a@localhost:9000/1 - - Client configuration: - servers : ['http://localhost:9000/api/store/'] - project : 1 - public_key : dd2c825ff9b1417d88a99573903ebf80 - secret_key : 91631495b10b45f8a1cdbc492088da6a - - Sending a test message... success! - - The test message can be viewed at the following URL: - http://localhost:9000/1/search/?q=c988bf5cb7db4653825c92f6864e7206$b8a6fbd29cc9113a149ad62cf7e0ddd5 - + $ raven test sync+___DSN___ + Using DSN configuration: + sync+___DSN___ -Client API ----------- + Client configuration: + servers : ['___API_URL___/api/store/'] + project : ___PROJECT_ID___ + public_key : ___PUBLIC_KEY___ + secret_key : ___SECRET_KEY___ -.. autoclass:: raven.base.Client - :members: + Sending a test message... success! diff --git a/docs/wizards.json b/docs/wizards.json new file mode 100644 index 000000000..e673af1a6 --- /dev/null +++ b/docs/wizards.json @@ -0,0 +1,83 @@ +{ + "configurations": { + "python": { + "name": "Python", + "client_lib": "raven-python", + "is_framework": false, + "doc_link": "installation", + "snippets": [ + "installation#installation", + "usage#capture-an-error", + "usage#reporting-an-event" + ] + }, + "python-flask": { + "name": "Flask", + "client_lib": "raven-python", + "is_framework": true, + "doc_link": "integrations/flask", + "snippets": [ + "integrations/flask#installation", + "integrations/flask#setup" + ] + }, + "python-bottle": { + "name": "Bottle", + "client_lib": "raven-python", + "is_framework": true, + "doc_link": "integrations/bottle", + "snippets": [ + "integrations/bottle#setup", + "integrations/bottle#usage" + ] + }, + "python-celery": { + "name": "Celery", + "client_lib": "raven-python", + "is_framework": true, + "doc_link": "integrations/celery", + "snippets": [ + "integrations/celery" + ] + }, + "python-django": { + "name": "Django", + "client_lib": "raven-python", + "is_framework": true, + "doc_link": "integrations/django", + "snippets": [ + "integrations/django#setup" + ] + }, + "python-pylons": { + "name": "Pylons", + "client_lib": "raven-python", + "is_framework": true, + "doc_link": "integrations/pylons", + "snippets": [ + "integrations/pylons#wsgi-middleware", + "integrations/pylons#logger-setup" + ] + }, + "python-pyramid": { + "name": "Pyramid", + "client_lib": "raven-python", + "is_framework": true, + "doc_link": "integrations/pyramid", + "snippets": [ + "integrations/pyramid#pastedeploy-filter", + "integrations/pyramid#logger-setup" + ] + }, + "python-tornado": { + "name": "Tornado", + "client_lib": "raven-python", + "is_framework": true, + "doc_link": "integrations/tornado", + "snippets": [ + "integrations/tornado#setup", + "integrations/tornado#usage" + ] + } + } +} From f6a451a861c8075e4029c467a03ba80ed1870532 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sun, 12 Jul 2015 11:11:18 +0200 Subject: [PATCH 018/692] Updated to latest sentry ext --- docs/_sentryext | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_sentryext b/docs/_sentryext index e9b477ffb..ce0441713 160000 --- a/docs/_sentryext +++ b/docs/_sentryext @@ -1 +1 @@ -Subproject commit e9b477ffbbb5b50c2a1ccdf4ff845ac1ffbce96b +Subproject commit ce0441713dd7588fb320bf363f1d2cbbab052675 From cd8c6dee64d237461c81623bb2e366fc32aac81d Mon Sep 17 00:00:00 2001 From: David Cramer Date: Sun, 12 Jul 2015 08:55:41 -0600 Subject: [PATCH 019/692] Fix various py3 compatibility (refs GH-625) --- raven/contrib/django/models.py | 2 +- raven/contrib/flask.py | 3 ++- raven/utils/imports.py | 2 -- tests/contrib/django/tests.py | 3 +-- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/raven/contrib/django/models.py b/raven/contrib/django/models.py index e53536cff..2927bb924 100644 --- a/raven/contrib/django/models.py +++ b/raven/contrib/django/models.py @@ -142,7 +142,7 @@ def get_client(client=None, reset=False): options.setdefault('release', ga('RELEASE')) transport = ga('TRANSPORT') or options.get('transport') - if isinstance(transport, basestring): + if isinstance(transport, six.string_types): transport = import_string(transport) options['transport'] = transport diff --git a/raven/contrib/flask.py b/raven/contrib/flask.py index 05f0067c7..ed09af23f 100644 --- a/raven/contrib/flask.py +++ b/raven/contrib/flask.py @@ -27,6 +27,7 @@ from raven.base import Client from raven.middleware import Sentry as SentryMiddleware from raven.handlers.logging import SentryHandler +from raven.utils import six from raven.utils.compat import _urlparse from raven.utils.imports import import_string from raven.utils.wsgi import get_headers, get_environ @@ -36,7 +37,7 @@ def make_client(client_cls, app, dsn=None): # TODO(dcramer): django and Flask share very similar concepts here, and # should be refactored transport = app.config.get('SENTRY_TRANSPORT') - if isinstance(transport, basestring): + if isinstance(transport, six.string_types): transport = import_string(transport) return client_cls( diff --git a/raven/utils/imports.py b/raven/utils/imports.py index e039a287e..2884f7564 100644 --- a/raven/utils/imports.py +++ b/raven/utils/imports.py @@ -2,8 +2,6 @@ def import_string(key): - key = str(key) - if '.' not in key: return __import__(key) diff --git a/tests/contrib/django/tests.py b/tests/contrib/django/tests.py index 6d9b80b04..0d3703c57 100644 --- a/tests/contrib/django/tests.py +++ b/tests/contrib/django/tests.py @@ -2,7 +2,6 @@ from __future__ import absolute_import from __future__ import with_statement -from __future__ import unicode_literals import datetime import django @@ -716,7 +715,7 @@ def fake_gettext(to_translate): assert result == expected def test_real_gettext_lazy(self): - d = {'lazy_translation': gettext_lazy('testing')} + d = {u'lazy_translation': gettext_lazy(u'testing')} key = "'lazy_translation'" if six.PY3 else "u'lazy_translation'" value = "'testing'" if six.PY3 else "u'testing'" assert transform(d) == {key: value} From 62dc112cee09a9efa21b2f53ff1cc281c71256ab Mon Sep 17 00:00:00 2001 From: David Cramer Date: Sun, 12 Jul 2015 11:24:36 -0600 Subject: [PATCH 020/692] Fix various py3 compatibility (refs GH-625) --- tests/contrib/django/tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/contrib/django/tests.py b/tests/contrib/django/tests.py index 0d3703c57..98a93cd8a 100644 --- a/tests/contrib/django/tests.py +++ b/tests/contrib/django/tests.py @@ -715,7 +715,7 @@ def fake_gettext(to_translate): assert result == expected def test_real_gettext_lazy(self): - d = {u'lazy_translation': gettext_lazy(u'testing')} + d = {six.text_type('lazy_translation'): gettext_lazy(six.text_type('testing'))} key = "'lazy_translation'" if six.PY3 else "u'lazy_translation'" value = "'testing'" if six.PY3 else "u'testing'" assert transform(d) == {key: value} From 3761bf3ab35bdab37cb7e8537d7d025cdf63cd0e Mon Sep 17 00:00:00 2001 From: David Cramer Date: Sun, 12 Jul 2015 11:55:20 -0600 Subject: [PATCH 021/692] Dont specify level for import (refs GH-625) --- raven/utils/imports.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/utils/imports.py b/raven/utils/imports.py index 2884f7564..0b1a009b1 100644 --- a/raven/utils/imports.py +++ b/raven/utils/imports.py @@ -6,5 +6,5 @@ def import_string(key): return __import__(key) module_name, class_name = key.rsplit('.', 1) - module = __import__(module_name, {}, {}, [class_name], -1) + module = __import__(module_name, {}, {}, [class_name]) return getattr(module, class_name) From f4b2c251e2a1a0e0494972186e7dbc9f02b3c84d Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sun, 12 Jul 2015 20:05:29 +0200 Subject: [PATCH 022/692] Split up hooks --- docs/_sentryext | 2 +- hooks/pre-commit | 51 +++++++++--------------------------- hooks/pre-commit.flake8 | 39 +++++++++++++++++++++++++++ hooks/pre-commit.verify-docs | 1 + 4 files changed, 53 insertions(+), 40 deletions(-) create mode 100755 hooks/pre-commit.flake8 create mode 120000 hooks/pre-commit.verify-docs diff --git a/docs/_sentryext b/docs/_sentryext index ce0441713..2210ebed8 160000 --- a/docs/_sentryext +++ b/docs/_sentryext @@ -1 +1 @@ -Subproject commit ce0441713dd7588fb320bf363f1d2cbbab052675 +Subproject commit 2210ebed81f91aabd730bf5f7224a0fdfee58d2d diff --git a/hooks/pre-commit b/hooks/pre-commit index d58db4d62..245c4b70c 100755 --- a/hooks/pre-commit +++ b/hooks/pre-commit @@ -1,39 +1,12 @@ -#!/usr/bin/env python - -import glob -import os -import sys - -os.environ['PYFLAKES_NODOCTEST'] = '1' - -# pep8.py uses sys.argv to find setup.cfg -sys.argv = [os.path.join(os.path.dirname(__file__), os.pardir, os.pardir)] - -# git usurbs your bin path for hooks and will always run system python -if 'VIRTUAL_ENV' in os.environ: - site_packages = glob.glob( - '%s/lib/*/site-packages' % os.environ['VIRTUAL_ENV'])[0] - sys.path.insert(0, site_packages) - - -def main(): - from flake8.main import DEFAULT_CONFIG - from flake8.engine import get_style_guide - from flake8.hooks import run - - gitcmd = "git diff-index --cached --name-only HEAD" - - _, files_modified, _ = run(gitcmd) - - # remove non-py files and files which no longer exist - files_modified = filter( - lambda x: x.endswith('.py') and os.path.exists(x), - files_modified) - - flake8_style = get_style_guide(parse_argv=True, config_file=DEFAULT_CONFIG) - report = flake8_style.check_files(files_modified) - - return report.total_errors - -if __name__ == '__main__': - sys.exit(main()) +#!/bin/bash + +failed=0 +for filename in hooks/pre-commit.*; do + if [ -f "hooks/$filename" ]; then + if $("./hooks/$filename") != "0"; then + failed=1 + fi + fi +done + +exit failed diff --git a/hooks/pre-commit.flake8 b/hooks/pre-commit.flake8 new file mode 100755 index 000000000..d58db4d62 --- /dev/null +++ b/hooks/pre-commit.flake8 @@ -0,0 +1,39 @@ +#!/usr/bin/env python + +import glob +import os +import sys + +os.environ['PYFLAKES_NODOCTEST'] = '1' + +# pep8.py uses sys.argv to find setup.cfg +sys.argv = [os.path.join(os.path.dirname(__file__), os.pardir, os.pardir)] + +# git usurbs your bin path for hooks and will always run system python +if 'VIRTUAL_ENV' in os.environ: + site_packages = glob.glob( + '%s/lib/*/site-packages' % os.environ['VIRTUAL_ENV'])[0] + sys.path.insert(0, site_packages) + + +def main(): + from flake8.main import DEFAULT_CONFIG + from flake8.engine import get_style_guide + from flake8.hooks import run + + gitcmd = "git diff-index --cached --name-only HEAD" + + _, files_modified, _ = run(gitcmd) + + # remove non-py files and files which no longer exist + files_modified = filter( + lambda x: x.endswith('.py') and os.path.exists(x), + files_modified) + + flake8_style = get_style_guide(parse_argv=True, config_file=DEFAULT_CONFIG) + report = flake8_style.check_files(files_modified) + + return report.total_errors + +if __name__ == '__main__': + sys.exit(main()) diff --git a/hooks/pre-commit.verify-docs b/hooks/pre-commit.verify-docs new file mode 120000 index 000000000..43fcf0893 --- /dev/null +++ b/hooks/pre-commit.verify-docs @@ -0,0 +1 @@ +../docs/_sentryext/verify-docs.py \ No newline at end of file From 01b724edffe2f65099912d80278e89516b2cb8e1 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sun, 12 Jul 2015 20:23:08 +0200 Subject: [PATCH 023/692] Fixed a bug in the pre-commit hook --- hooks/pre-commit | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hooks/pre-commit b/hooks/pre-commit index 245c4b70c..661c78e8b 100755 --- a/hooks/pre-commit +++ b/hooks/pre-commit @@ -2,11 +2,11 @@ failed=0 for filename in hooks/pre-commit.*; do - if [ -f "hooks/$filename" ]; then - if $("./hooks/$filename") != "0"; then + if [ -f "$filename" ]; then + if ! $filename; then failed=1 fi fi done -exit failed +exit $failed From a2ce5c2c305587021152855ae136cf5851401052 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Sun, 12 Jul 2015 13:46:30 -0600 Subject: [PATCH 024/692] Enable caching / remove wad bits --- .travis.yml | 17 +-- ci/wad | 334 ---------------------------------------------------- 2 files changed, 6 insertions(+), 345 deletions(-) delete mode 100755 ci/wad diff --git a/.travis.yml b/.travis.yml index 445449d07..319b79943 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,6 +7,11 @@ addons: packages: - libevent-dev +cache: + directories: + - node_modules + - .pip_download_cache + python: - "2.6" - "2.7" @@ -25,14 +30,6 @@ env: - 'DJANGO="-e git+git://github.com/django/django.git#egg=Django"' global: - 'PIP_DOWNLOAD_CACHE=".pip_download_cache"' - - 'WAD_FILES="package.json,Makefile,setup.py"' - - 'WAD_INSTALL_COMMAND=ci/setup' - - # This should be specified with: - # travis encrypt S3_BUCKET_NAME=secretvalue S3_CREDENTIALS=accesskey:secretkey - # - S3_BUCKET_NAME= - # - S3_CREDENTIALS= - - secure: "QEFKt0HhaMlCW0FCLTz3NDY/P4UuVl1Pw8kSUVKKjbaKn2jZdaLKS68yl3Vyrd9AqrBfRNNLBAnvpZjYL6EwqqnZzpRH3KnAf0WRxE6d5ytrUkwZtOnQaN0Tumuqc3xnoXXalPXvs+abhXZpjfOzx0oPBa2WbtMF/RJZ/bwsTKE=" before_install: # Use closer nameservers @@ -40,11 +37,9 @@ before_install: # These need to be here and not in the env hash because they need to be # evaluated after the virtualenv has been setup - mkdir -p $PIP_DOWNLOAD_CACHE - - 'export WAD_ENVIRONMENT_VARIABLES="TRAVIS_PYTHON_VERSION,TRAVIS_NODE_VERSION,WAD_CACHE_PATH"' - - 'export WAD_CACHE_PATH="node_modules,$PIP_DOWNLOAD_CACHE,$VIRTUAL_ENV"' install: - - time ci/wad + - time ci/setup script: - "if [[ ${TRAVIS_PYTHON_VERSION} != 'pypy' ]]; then make lint; fi" diff --git a/ci/wad b/ci/wad deleted file mode 100755 index 52730008c..000000000 --- a/ci/wad +++ /dev/null @@ -1,334 +0,0 @@ -#!/usr/bin/env ruby - -# Generated on: 20-09-2013 at 12:38 - -require 'time' -require 'net/http' -require 'net/https' -require 'digest/md5' -require 'digest/sha1' -require 'fileutils' -require 'openssl' -require 'base64' -require 'cgi' -class Presss - # Computes the Authorization header for a AWS request based on a message, - # the access key ID and secret access key. - class Authorization - attr_accessor :access_key_id, :secret_access_key - - def initialize(access_key_id, secret_access_key) - @access_key_id, @secret_access_key = access_key_id, secret_access_key - end - - # Returns the value for the Authorization header for a message contents. - def header(string) - 'AWS ' + access_key_id + ':' + sign(string) - end - - # Returns a signature for a AWS request message. - def sign(string) - Base64.encode64(hmac_sha1(string)).strip - end - - def hmac_sha1(string) - OpenSSL::HMAC.digest('sha1', secret_access_key, string) - end - end - - class HTTP - attr_accessor :config - - def initialize(config) - @config = config - end - - # Returns the configured bucket name. - def bucket_name - config[:bucket_name] - end - - def region - config[:region] || 'us-east-1' - end - - def domain - case region - when 'us-east-1' - 's3.amazonaws.com' - else - 's3-%s.amazonaws.com' % region - end - end - - def bucket_in_hostname? - config[:bucket_in_hostname] - end - - def url_prefix - if bucket_in_hostname? - "https://#{bucket_name}.#{domain}" - else - "https://#{domain}/#{bucket_name}" - end - end - - # Returns the absolute path based on the key for the object. - def absolute_path(path) - path.start_with?('/') ? path : '/' + path - end - - # Returns the canonicalized resource used in the authorization - # signature for an absolute path to an object. - def canonicalized_resource(path) - if bucket_name.nil? - raise ArgumentError, "Please configure a bucket_name: Presss.config = { bucket_name: 'my-bucket-name }" - else - '/' + bucket_name + absolute_path(path) - end - end - - # Returns a Presss::Authorization instance for the configured - # AWS credentials. - def authorization - @authorization ||= Presss::Authorization.new( - config[:access_key_id], - config[:secret_access_key] - ) - end - - def signed_url(verb, expires, headers, path) - path = absolute_path(path) - canonical_path = canonicalized_resource(path) - signature = [ verb.to_s.upcase, nil, nil, expires, [ headers, canonical_path ].flatten.compact ].flatten.join("\n") - signed = authorization.sign(signature) - "#{url_prefix}#{path}?Signature=#{CGI.escape(signed)}&Expires=#{expires}&AWSAccessKeyId=#{CGI.escape(authorization.access_key_id)}" - end - - def download(path, destination) - url = signed_url(:get, Time.now.to_i + 600, nil, path) - Presss.log "signed_url=#{url}" - system 'curl', '-f', '-S', '-o', destination, url - $?.success? - end - - # Puts an object with a key using a file or string. Optionally pass in - # the content-type if you want to set a specific one. - def put(path, file) - header = 'x-amz-storage-class:REDUCED_REDUNDANCY' - url = signed_url(:put, Time.now.to_i + 600, header, path) - Presss.log "signed_url=#{url}" - system 'curl', '-f', '-S', '-H', header, '-T', file, url - $?.success? - end - end - - class << self - attr_accessor :config - attr_accessor :logger - end - self.config = {} - - # Get a object with a certain key. - def self.download(path, destination) - t0 = Time.now - request = Presss::HTTP.new(config) - log("Trying to GET #{path}") - if request.download(path, destination) - puts("[wad] Downloaded in #{(Time.now - t0).to_i} seconds") - true - else - nil - end - end - - # Puts an object with a key using a file or string. Optionally pass in - # the content-type if you want to set a specific one. - def self.put(path, filename, content_type='application/x-download') - request = Presss::HTTP.new(config) - log("Trying to PUT #{path}") - request.put(path, filename) - end - - # Logs to the configured logger if a logger was configured. - def self.log(message) - if logger - logger.info('[Presss] ' + message) - end - end -end - -# Utility class to push and fetch Bundler directories to speed up -# test runs on Travis-CI -class Wad - class Key - def default_environment_variables - [] - end - - def default_files - [ "#{ENV['BUNDLE_GEMFILE']}.lock" ] - end - - def environment_variables - if ENV['WAD_ENVIRONMENT_VARIABLES'] - ENV['WAD_ENVIRONMENT_VARIABLES'].split(',') - else - default_environment_variables - end - end - - def files - ENV['WAD_FILES'] ? ENV['WAD_FILES'].split(',') : default_files - end - - def environment_variable_contents - environment_variables.map { |v| ENV[v] } - end - - def file_contents - files.map { |f| File.read(f) rescue nil } - end - - def contents - segments = [ RUBY_VERSION, RUBY_PLATFORM ] + environment_variable_contents + file_contents - Digest::SHA1.hexdigest(segments.join("\n")) - end - end - - def initialize - s3_configure - end - - def project_root - Dir.pwd - end - - def artifact_name - @artifact_name ||= Key.new.contents - end - - def bzip_filename - File.join(project_root, "tmp/#{artifact_name}.tar.bz2") - end - - def cache_path - ENV['WAD_CACHE_PATH'] ? ENV['WAD_CACHE_PATH'].split(",") : [ '.bundle' ] - end - - def s3_bucket_name - if bucket = ENV['WAD_S3_BUCKET_NAME'] || ENV['S3_BUCKET_NAME'] - bucket - end - end - - def s3_credentials - if creds = ENV['WAD_S3_CREDENTIALS'] || ENV['S3_CREDENTIALS'] - creds.split(':') - end - end - - def s3_access_key_id - s3_credentials && s3_credentials[0] - end - - def s3_secret_access_key - s3_credentials && s3_credentials[1] - end - - def s3_path - "#{artifact_name}.tar.bz2" - end - - def s3_configure - Presss.config = { - :bucket_name => s3_bucket_name, - :access_key_id => s3_access_key_id, - :secret_access_key => s3_secret_access_key, - :region => ENV['WAD_AWS_REGION'], - :bucket_in_hostname => (ENV['WAD_BUCKET_IN_HOSTNAME'] == 'true') - } - end - - def s3_write - log "Trying to write Wad to S3" - if Presss.put(s3_path, bzip_filename) - log "Wrote Wad to S3" - else - log "Failed to write to S3, debug with `wad -v'" - end - end - - def s3_read - if File.exist?(bzip_filename) - log "Removing bundle from filesystem" - FileUtils.rm_f(bzip_filename) - end - - log "Trying to fetch Wad from S3" - FileUtils.mkdir_p(File.dirname(bzip_filename)) - Presss.download(s3_path, bzip_filename) - end - - def zip - log "Creating artifact with tar (#{File.basename(bzip_filename)})" - system("cd #{project_root} && tar -cPjf #{bzip_filename} #{cache_path.join(' ')}") - $?.success? - end - - - def unzip - log "Unpacking artifact with tar (#{File.basename(bzip_filename)})" - system("cd #{project_root} && tar -xPjf #{bzip_filename}") - $?.success? - end - - def put - zip - s3_write - end - - def get - if s3_read - unzip - end - end - - def default_command - bundle_without = ENV['WAD_BUNDLE_WITHOUT'] || "development production" - "bundle install --path .bundle --without='#{bundle_without}'" - end - - def install - log "Installing..." - command = ENV['WAD_INSTALL_COMMAND'] || default_command - puts command - system(command) - $?.success? - end - - def setup - if !s3_credentials || !s3_bucket_name - log "No S3 credentials defined. Set WAD_S3_CREDENTIALS= and WAD_S3_BUCKET_NAME= for caching." - install - elsif get - install - elsif install - put - else - abort "Failed properly fetch or install. Please review the logs." - end - end - - def log(message) - puts "[wad] #{message}" - end -end - -if ARGV.index('-v') - require 'logger' - Presss.logger = Logger.new($stdout) -end - -Wad.new.setup -__END__ From d8487802f6912d299db879a3f59b24cbb079c6fa Mon Sep 17 00:00:00 2001 From: David Cramer Date: Sun, 12 Jul 2015 16:58:45 -0700 Subject: [PATCH 025/692] Changes for 5.4.3 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index fb5a6a998..af0e37f6d 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 5.4.3 +------------- + +* Python 3 compatibility fixes. + Version 5.4.2 ------------- From 88f1dfd3eaf4f532d6614bad4bd2bcaa5014b3c1 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Sun, 12 Jul 2015 17:01:09 -0700 Subject: [PATCH 026/692] 5.5.0.dev0 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index b6aaaaa97..8f63db976 100755 --- a/setup.py +++ b/setup.py @@ -95,7 +95,7 @@ def run_tests(self): setup( name='raven', - version='5.4.0.dev0', + version='5.5.0.dev0', author='David Cramer', author_email='dcramer@gmail.com', url='https://github.com/getsentry/raven-python', From 87b203419793698bdf7ac0fec974a742bee3feab Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 13 Jul 2015 02:38:18 -0700 Subject: [PATCH 027/692] Handle unicode strings with import_string (refs GH-627) --- raven/utils/imports.py | 8 +++++++- tests/utils/test_imports.py | 22 ++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 tests/utils/test_imports.py diff --git a/raven/utils/imports.py b/raven/utils/imports.py index 0b1a009b1..56ef18a7c 100644 --- a/raven/utils/imports.py +++ b/raven/utils/imports.py @@ -1,10 +1,16 @@ from __future__ import absolute_import +from . import six + def import_string(key): + # HACK(dcramer): Ensure a unicode key is still importable + if not six.PY3: + key = str(key) + if '.' not in key: return __import__(key) module_name, class_name = key.rsplit('.', 1) - module = __import__(module_name, {}, {}, [class_name]) + module = __import__(module_name, {}, {}, [class_name], 0) return getattr(module, class_name) diff --git a/tests/utils/test_imports.py b/tests/utils/test_imports.py new file mode 100644 index 000000000..e131abc61 --- /dev/null +++ b/tests/utils/test_imports.py @@ -0,0 +1,22 @@ +from __future__ import absolute_import + +import raven + +from raven.utils import six +from raven.utils.imports import import_string + + +def test_import_string(): + new_raven = import_string('raven') + assert new_raven is raven + + # this will test unicode on python2 + new_raven = import_string(six.text_type('raven')) + assert new_raven is raven + + new_client = import_string('raven.Client') + assert new_client is raven.Client + + # this will test unicode on python2 + new_client = import_string(six.text_type('raven.Client')) + assert new_client is raven.Client From 60944c0b4888529064a1ee99b4e1462327d988a5 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 13 Jul 2015 14:58:06 -0700 Subject: [PATCH 028/692] Changes for 5.4.4 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index af0e37f6d..f818858d6 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 5.4.4 +------------- + +* Enforce string-type imports. + Version 5.4.3 ------------- From 83e544b6fe4389456eb61d43d06c0e6d8746ab78 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 14 Jul 2015 17:00:37 +0200 Subject: [PATCH 029/692] Added two misisng python versions. --- docs/index.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/index.rst b/docs/index.rst index 19bb2c295..9a707f7c4 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -50,6 +50,8 @@ available under a very liberal BSD license. - Python 2.7 - Python 3.2 - Python 3.3 + - Python 3.4 + - Python 3.5 - PyPy - Google App Engine From ddb6d54a8457b7eb5e56b1a203546e7b9f71337d Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 14 Jul 2015 17:00:43 +0200 Subject: [PATCH 030/692] Updated sentryext --- docs/_sentryext | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_sentryext b/docs/_sentryext index 2210ebed8..20496d487 160000 --- a/docs/_sentryext +++ b/docs/_sentryext @@ -1 +1 @@ -Subproject commit 2210ebed81f91aabd730bf5f7224a0fdfee58d2d +Subproject commit 20496d4877a7b74e3b06c55f9f385de2898df49e From f7b9a2dbf5177a79578f19f0a059073604866084 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 15 Jul 2015 23:37:43 +0200 Subject: [PATCH 031/692] Set an apostrophe where it belongs. --- docs/index.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index 9a707f7c4..cce33ea63 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -17,8 +17,8 @@ available under a very liberal BSD license. .. sentry:edition:: self - Users Guide - ----------- + User's Guide + ------------ .. toctree:: :maxdepth: 2 From 0ab984b8da5076712d6dce7cfe788b54b039cbf8 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Wed, 15 Jul 2015 22:42:34 -0700 Subject: [PATCH 032/692] Capture errors via sys.excepthook --- raven/base.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/raven/base.py b/raven/base.py index 9324da5a2..5d133d94f 100644 --- a/raven/base.py +++ b/raven/base.py @@ -117,7 +117,8 @@ class Client(object): _registry = TransportRegistry(transports=default_transports) - def __init__(self, dsn=None, raise_send_errors=False, transport=None, **options): + def __init__(self, dsn=None, raise_send_errors=False, transport=None, + install_sys_hook=True, **options): global Raven o = options @@ -174,6 +175,9 @@ def __init__(self, dsn=None, raise_send_errors=False, transport=None, **options) self._context = Context() + if install_sys_hook: + self.install_sys_hook() + def set_dsn(self, dsn=None, transport=None): if dsn is None and os.environ.get('SENTRY_DSN'): msg = "Configuring Raven from environment variable 'SENTRY_DSN'" @@ -196,6 +200,12 @@ def set_dsn(self, dsn=None, transport=None): self.logger.debug("Configuring Raven for host: {0}".format(self.remote)) + def install_sys_hook(self): + def handle_exception(*exc_info): + sys.__excepthook__(*exc_info) + self.captureException(exc_info=exc_info) + sys.excepthook = handle_exception + @classmethod def register_scheme(cls, scheme, transport_class): cls._registry.register_scheme(scheme, transport_class) From 058def47a40bd2ba92716f648942cd9a06111747 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Wed, 15 Jul 2015 22:51:00 -0700 Subject: [PATCH 033/692] Support for custom exception hook fallbacks --- raven/base.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/raven/base.py b/raven/base.py index 5d133d94f..638e15c1e 100644 --- a/raven/base.py +++ b/raven/base.py @@ -35,6 +35,8 @@ __all__ = ('Client',) +__excepthook__ = None + PLATFORM_NAME = 'python' # singleton for the client @@ -201,8 +203,13 @@ def set_dsn(self, dsn=None, transport=None): self.logger.debug("Configuring Raven for host: {0}".format(self.remote)) def install_sys_hook(self): + global __excepthook__ + + if __excepthook__ is None: + __excepthook__ = sys.excepthook + def handle_exception(*exc_info): - sys.__excepthook__(*exc_info) + __excepthook__(*exc_info) self.captureException(exc_info=exc_info) sys.excepthook = handle_exception From 3b7403d7cad2b56a55574a7dc8c0d9f399fb0c7d Mon Sep 17 00:00:00 2001 From: David Cramer Date: Wed, 15 Jul 2015 23:00:32 -0700 Subject: [PATCH 034/692] Remove old-style configuration values --- docs/config/index.rst | 32 -------------------------------- 1 file changed, 32 deletions(-) diff --git a/docs/config/index.rst b/docs/config/index.rst index f71538dd1..e33e80e5a 100644 --- a/docs/config/index.rst +++ b/docs/config/index.rst @@ -81,38 +81,6 @@ A sentry compatible DSN. dsn = 'http://public:secret@example.com/1' -project -~~~~~~~ - -Set this to your Sentry project ID. The default value for installations is ``1``. - -:: - - project = 1 - - -public_key -~~~~~~~~~~ - -Set this to the public key of the project member which will authenticate as the -client. You can find this information on the member details page of your project -within Sentry. - -:: - - public_key = 'fb9f9e31ea4f40d48855c603f15a2aa4' - - -secret_key -~~~~~~~~~~ - -Set this to the secret key of the project member which will authenticate as the -client. You can find this information on the member details page of your project -within Sentry. - -:: - - secret_key = '6e968b3d8ba240fcb50072ad9cba0810' site ~~~~ From 728622d6c0c47bbfbfd0d57865ff6be9198ab3fa Mon Sep 17 00:00:00 2001 From: David Cramer Date: Wed, 15 Jul 2015 23:00:41 -0700 Subject: [PATCH 035/692] Remove deprecated 'site' config value from docs --- docs/config/index.rst | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/docs/config/index.rst b/docs/config/index.rst index e33e80e5a..0b2662156 100644 --- a/docs/config/index.rst +++ b/docs/config/index.rst @@ -82,16 +82,6 @@ A sentry compatible DSN. dsn = 'http://public:secret@example.com/1' -site -~~~~ - -An optional, arbitrary string to identify this client installation. - -:: - - site = 'my site name' - - name ~~~~ From 18fc6e4d3f6ec29cc44676082adae06df5a8de8b Mon Sep 17 00:00:00 2001 From: David Cramer Date: Wed, 15 Jul 2015 23:02:13 -0700 Subject: [PATCH 036/692] Expand config docs --- docs/config/index.rst | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/docs/config/index.rst b/docs/config/index.rst index 0b2662156..bbd5054e8 100644 --- a/docs/config/index.rst +++ b/docs/config/index.rst @@ -82,6 +82,16 @@ A sentry compatible DSN. dsn = 'http://public:secret@example.com/1' +transport +~~~~~~~~~ + +The transport implementation to use. + +:: + + transport = ThreadedHTTPTransport + + name ~~~~ @@ -116,6 +126,7 @@ Extending this allow you to ignore module prefixes when we attempt to discover w 'lxml.objectify', ] + include_paths ~~~~~~~~~~~~~ @@ -130,6 +141,7 @@ For example, in Django this defaults to your list of ``INSTALLED_APPS``, and is 'lxml.objectify', ] + list_max_length ~~~~~~~~~~~~~~~ @@ -143,6 +155,7 @@ If an iterable is longer than the specified length, the left-most elements up to list_max_length = 50 + string_max_length ~~~~~~~~~~~~~~~~~ @@ -154,6 +167,7 @@ If a string is longer than the given length, it will be truncated down to the sp string_max_length = 200 + auto_log_stacks ~~~~~~~~~~~~~~~ @@ -177,6 +191,19 @@ additional global state data or sanitizing data that you want to keep off of the 'raven.processors.SanitizePasswordsProcessor', ) + +install_sys_hook +~~~~~~~~~~~~~~~~ + +Install a global exception hook (via ``sys.excepthook``). + +Defaults to ``True``. + +:: + + install_sys_hook = False + + Sanitizing Data --------------- From 8d41f49362e6cdfcdbd6e9f30e53afc9926c7067 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Wed, 15 Jul 2015 23:03:25 -0700 Subject: [PATCH 037/692] prioritze our exception hook --- raven/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/base.py b/raven/base.py index 638e15c1e..d4ee69579 100644 --- a/raven/base.py +++ b/raven/base.py @@ -209,8 +209,8 @@ def install_sys_hook(self): __excepthook__ = sys.excepthook def handle_exception(*exc_info): - __excepthook__(*exc_info) self.captureException(exc_info=exc_info) + __excepthook__(*exc_info) sys.excepthook = handle_exception @classmethod From 9e4046f04de05e55c91adc5ea4d1fd1084eb560c Mon Sep 17 00:00:00 2001 From: David Cramer Date: Wed, 15 Jul 2015 23:09:15 -0700 Subject: [PATCH 038/692] Kill various legacy settings --- raven/base.py | 5 ++--- raven/conf/defaults.py | 14 -------------- raven/contrib/django/models.py | 4 ---- 3 files changed, 2 insertions(+), 21 deletions(-) diff --git a/raven/base.py b/raven/base.py index d4ee69579..d26133222 100644 --- a/raven/base.py +++ b/raven/base.py @@ -142,7 +142,7 @@ def __init__(self, dsn=None, raise_send_errors=False, transport=None, self.include_paths = set(o.get('include_paths') or []) self.exclude_paths = set(o.get('exclude_paths') or []) - self.name = six.text_type(o.get('name') or defaults.NAME) + self.name = six.text_type(o.get('name') or o.get('machine') or defaults.NAME) self.auto_log_stacks = bool( o.get('auto_log_stacks') or defaults.AUTO_LOG_STACKS) self.capture_locals = bool( @@ -151,7 +151,7 @@ def __init__(self, dsn=None, raise_send_errors=False, transport=None, o.get('string_max_length') or defaults.MAX_LENGTH_STRING) self.list_max_length = int( o.get('list_max_length') or defaults.MAX_LENGTH_LIST) - self.site = o.get('site', defaults.SITE) + self.site = o.get('site') self.include_versions = o.get('include_versions', True) self.processors = o.get('processors') if self.processors is None: @@ -166,7 +166,6 @@ def __init__(self, dsn=None, raise_send_errors=False, transport=None, self.module_cache = ModuleProxyCache() - # servers may be set to a NoneType (for Django) if not self.is_enabled(): self.logger.info( 'Raven is not configured (logging is disabled). Please see the' diff --git a/raven/conf/defaults.py b/raven/conf/defaults.py index 11e3d39da..c1fafb9e7 100644 --- a/raven/conf/defaults.py +++ b/raven/conf/defaults.py @@ -28,20 +28,6 @@ # will set it to None and require it passed in to ``Client`` on initializtion. NAME = socket.gethostname() if hasattr(socket, 'gethostname') else None -# Superuser key -- will be used if set, otherwise defers to -# SECRET_KEY and PUBLIC_KEY -KEY = None - -# Credentials to authenticate with the Sentry server -SECRET_KEY = None -PUBLIC_KEY = None - -# We allow setting the site name either by explicitly setting it with the -# SENTRY_SITE setting, or using the django.contrib.sites framework for -# fetching the current site. Since we can't reliably query the database -# from this module, the specific logic is within the SiteFilter -SITE = None - # The maximum number of elements to store for a list-like structure. MAX_LENGTH_LIST = 50 diff --git a/raven/contrib/django/models.py b/raven/contrib/django/models.py index 2927bb924..3db5ce6d1 100644 --- a/raven/contrib/django/models.py +++ b/raven/contrib/django/models.py @@ -123,7 +123,6 @@ def get_client(client=None, reset=False): if _client[0] != client or reset: ga = lambda x, d=None: getattr(settings, 'SENTRY_%s' % x, d) options = copy.deepcopy(getattr(settings, 'RAVEN_CONFIG', {})) - options.setdefault('servers', ga('SERVERS')) options.setdefault('include_paths', ga('INCLUDE_PATHS', [])) options['include_paths'] = set(options['include_paths']) | get_installed_apps() options.setdefault('exclude_paths', ga('EXCLUDE_PATHS')) @@ -133,9 +132,6 @@ def get_client(client=None, reset=False): options.setdefault('string_max_length', ga('MAX_LENGTH_STRING')) options.setdefault('list_max_length', ga('MAX_LENGTH_LIST')) options.setdefault('site', ga('SITE')) - options.setdefault('public_key', ga('PUBLIC_KEY')) - options.setdefault('secret_key', ga('SECRET_KEY')) - options.setdefault('project', ga('PROJECT')) options.setdefault('processors', ga('PROCESSORS')) options.setdefault('dsn', ga('DSN')) options.setdefault('context', ga('CONTEXT')) From 37eb1e8ef6a472ab8539b78761ff679f8570c304 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Wed, 15 Jul 2015 23:14:36 -0700 Subject: [PATCH 039/692] Remove various deprecated behaviors --- docs/usage.rst | 2 +- raven/contrib/flask.py | 4 ---- raven/contrib/zope/component.xml | 4 ---- raven/handlers/logging.py | 3 --- tests/base/tests.py | 19 ++++++++----------- tests/contrib/bottle/tests.py | 4 ++-- tests/contrib/flask/tests.py | 4 ++-- tests/contrib/pylons/tests.py | 4 +--- tests/contrib/webpy/tests.py | 4 ++-- tests/contrib/zerorpc/tests.py | 4 ++-- tests/handlers/logbook/tests.py | 4 ++-- tests/handlers/logging/tests.py | 8 ++------ tests/middleware/tests.py | 4 ++-- 13 files changed, 24 insertions(+), 44 deletions(-) diff --git a/docs/usage.rst b/docs/usage.rst index e5384c63b..2ae8345e3 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -60,7 +60,7 @@ You should get something like the following, assuming you're configured everythi http://dd2c825ff9b1417d88a99573903ebf80:91631495b10b45f8a1cdbc492088da6a@localhost:9000/1 Client configuration: - servers : ['http://localhost:9000/api/store/'] + base_url : http://localhost:9000 project : 1 public_key : dd2c825ff9b1417d88a99573903ebf80 secret_key : 91631495b10b45f8a1cdbc492088da6a diff --git a/raven/contrib/flask.py b/raven/contrib/flask.py index ed09af23f..67ef1cafc 100644 --- a/raven/contrib/flask.py +++ b/raven/contrib/flask.py @@ -45,11 +45,7 @@ def make_client(client_cls, app, dsn=None): transport=transport, include_paths=set(app.config.get('SENTRY_INCLUDE_PATHS', [])) | set([app.import_name]), exclude_paths=app.config.get('SENTRY_EXCLUDE_PATHS'), - servers=app.config.get('SENTRY_SERVERS'), name=app.config.get('SENTRY_NAME'), - public_key=app.config.get('SENTRY_PUBLIC_KEY'), - secret_key=app.config.get('SENTRY_SECRET_KEY'), - project=app.config.get('SENTRY_PROJECT'), site=app.config.get('SENTRY_SITE_NAME'), processors=app.config.get('SENTRY_PROCESSORS'), string_max_length=app.config.get('SENTRY_MAX_LENGTH_STRING'), diff --git a/raven/contrib/zope/component.xml b/raven/contrib/zope/component.xml index c3d92f2ec..b4d36477d 100644 --- a/raven/contrib/zope/component.xml +++ b/raven/contrib/zope/component.xml @@ -8,18 +8,14 @@ datatype="raven.contrib.zope.ZopeSentryHandlerFactory" implements="ZConfig.logger.handler" extends="ZConfig.logger.base-log-handler"> - - - - diff --git a/raven/handlers/logging.py b/raven/handlers/logging.py index e9083998d..fa7599652 100644 --- a/raven/handlers/logging.py +++ b/raven/handlers/logging.py @@ -42,9 +42,6 @@ def __init__(self, *args, **kwargs): )) elif 'client' in kwargs: self.client = kwargs['client'] - elif len(args) == 2 and not kwargs: - servers, key = args - self.client = client(servers=servers, key=key) else: self.client = client(*args, **kwargs) diff --git a/tests/base/tests.py b/tests/base/tests.py index cce73fb90..ccbd547ca 100644 --- a/tests/base/tests.py +++ b/tests/base/tests.py @@ -16,9 +16,9 @@ class TempStoreClient(Client): - def __init__(self, servers=None, **kwargs): + def __init__(self, **kwargs): self.events = [] - super(TempStoreClient, self).__init__(servers=servers, **kwargs) + super(TempStoreClient, self).__init__(**kwargs) def is_enabled(self): return True @@ -124,13 +124,13 @@ def test_send_remote_failover_with_retry_after(self, should_try, send): # test error send.side_effect = RateLimited('foo', 5) - client.send_remote('sync+http://example.com/api/store', client.encode({})) + client.send_remote('sync+http://example.com/api/1/store/', client.encode({})) self.assertEquals(client.state.status, client.state.ERROR) self.assertEqual(client.state.retry_after, 5) # test recovery send.side_effect = None - client.send_remote('sync+http://example.com/api/store', client.encode({})) + client.send_remote('sync+http://example.com/api/1/store/', client.encode({})) self.assertEquals(client.state.status, client.state.ONLINE) self.assertEqual(client.state.retry_after, 0) @@ -143,25 +143,22 @@ def test_async_send_remote_failover(self, should_try, get_transport): get_transport.return_value = async_transport client = Client( - servers=['http://example.com'], - public_key='public', - secret_key='secret', - project=1, + dsn='http://public:secret@example.com/1', ) # test immediate raise of error async_send.side_effect = Exception() - client.send_remote('http://example.com/api/store', client.encode({})) + client.send_remote('http://example.com/api/1/store/', client.encode({})) self.assertEquals(client.state.status, client.state.ERROR) # test recovery - client.send_remote('http://example.com/api/store', client.encode({})) + client.send_remote('http://example.com/api/1/store/', client.encode({})) success_cb = async_send.call_args[0][2] success_cb() self.assertEquals(client.state.status, client.state.ONLINE) # test delayed raise of error - client.send_remote('http://example.com/api/store', client.encode({})) + client.send_remote('http://example.com/api/1/store/', client.encode({})) failure_cb = async_send.call_args[0][3] failure_cb(Exception()) self.assertEquals(client.state.status, client.state.ERROR) diff --git a/tests/contrib/bottle/tests.py b/tests/contrib/bottle/tests.py index 18439ec04..081339847 100644 --- a/tests/contrib/bottle/tests.py +++ b/tests/contrib/bottle/tests.py @@ -10,9 +10,9 @@ class TempStoreClient(Client): - def __init__(self, servers=None, **kwargs): + def __init__(self, **kwargs): self.events = [] - super(TempStoreClient, self).__init__(servers=servers, **kwargs) + super(TempStoreClient, self).__init__(**kwargs) def is_enabled(self): return True diff --git a/tests/contrib/flask/tests.py b/tests/contrib/flask/tests.py index d1df979f7..f66e34a23 100644 --- a/tests/contrib/flask/tests.py +++ b/tests/contrib/flask/tests.py @@ -12,9 +12,9 @@ class TempStoreClient(Client): - def __init__(self, servers=None, **kwargs): + def __init__(self, **kwargs): self.events = [] - super(TempStoreClient, self).__init__(servers=servers, **kwargs) + super(TempStoreClient, self).__init__(**kwargs) def is_enabled(self): return True diff --git a/tests/contrib/pylons/tests.py b/tests/contrib/pylons/tests.py index eb59869a7..29b659c47 100644 --- a/tests/contrib/pylons/tests.py +++ b/tests/contrib/pylons/tests.py @@ -12,8 +12,6 @@ def setUp(self): def test_init(self): config = { - 'sentry.servers': 'http://localhost/api/store', - 'sentry.public_key': 'p' * 32, - 'sentry.secret_key': 's' * 32, + 'sentry.dsn': 'http://public:secret@example.com/1', } middleware = Sentry(self.app, config) diff --git a/tests/contrib/webpy/tests.py b/tests/contrib/webpy/tests.py index 29c8ae30e..d96cb9c20 100644 --- a/tests/contrib/webpy/tests.py +++ b/tests/contrib/webpy/tests.py @@ -7,9 +7,9 @@ class TempStoreClient(Client): - def __init__(self, servers=None, **kwargs): + def __init__(self, **kwargs): self.events = [] - super(TempStoreClient, self).__init__(servers=servers, **kwargs) + super(TempStoreClient, self).__init__(**kwargs) def is_enabled(self): return True diff --git a/tests/contrib/zerorpc/tests.py b/tests/contrib/zerorpc/tests.py index 20b592d9f..1228c9f61 100644 --- a/tests/contrib/zerorpc/tests.py +++ b/tests/contrib/zerorpc/tests.py @@ -13,9 +13,9 @@ class TempStoreClient(Client): - def __init__(self, servers=None, **kwargs): + def __init__(self, **kwargs): self.events = [] - super(TempStoreClient, self).__init__(servers=servers, **kwargs) + super(TempStoreClient, self).__init__(**kwargs) def is_enabled(self): return True diff --git a/tests/handlers/logbook/tests.py b/tests/handlers/logbook/tests.py index 12dbd7c27..aad74f56f 100644 --- a/tests/handlers/logbook/tests.py +++ b/tests/handlers/logbook/tests.py @@ -9,9 +9,9 @@ class TempStoreClient(Client): - def __init__(self, servers=None, **kwargs): + def __init__(self, **kwargs): self.events = [] - super(TempStoreClient, self).__init__(servers=servers, **kwargs) + super(TempStoreClient, self).__init__(**kwargs) def is_enabled(self): return True diff --git a/tests/handlers/logging/tests.py b/tests/handlers/logging/tests.py index 9bdbf6219..a2dac3088 100644 --- a/tests/handlers/logging/tests.py +++ b/tests/handlers/logging/tests.py @@ -12,9 +12,9 @@ class TempStoreClient(Client): - def __init__(self, servers=None, **kwargs): + def __init__(self, **kwargs): self.events = [] - super(TempStoreClient, self).__init__(servers=servers, **kwargs) + super(TempStoreClient, self).__init__(**kwargs) def is_enabled(self): return True @@ -227,10 +227,6 @@ def test_client_kwarg(self): handler = SentryHandler(client=client) self.assertEqual(handler.client, client) - def test_args_as_servers_and_key(self): - handler = SentryHandler(['http://sentry.local/api/store/'], 'KEY') - self.assertTrue(isinstance(handler.client, Client)) - def test_first_arg_as_dsn(self): handler = SentryHandler('http://public:secret@example.com/1') self.assertTrue(isinstance(handler.client, Client)) diff --git a/tests/middleware/tests.py b/tests/middleware/tests.py index c9a672431..23e94caa4 100644 --- a/tests/middleware/tests.py +++ b/tests/middleware/tests.py @@ -10,9 +10,9 @@ class TempStoreClient(Client): - def __init__(self, servers=None, **kwargs): + def __init__(self, **kwargs): self.events = [] - super(TempStoreClient, self).__init__(servers=servers, **kwargs) + super(TempStoreClient, self).__init__(**kwargs) def is_enabled(self): return True From ead83c4f027de6ed8ada9459f729a866c3541a0d Mon Sep 17 00:00:00 2001 From: David Cramer Date: Wed, 15 Jul 2015 23:15:11 -0700 Subject: [PATCH 040/692] Remove unused defaults --- raven/conf/defaults.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/raven/conf/defaults.py b/raven/conf/defaults.py index c1fafb9e7..073e01a85 100644 --- a/raven/conf/defaults.py +++ b/raven/conf/defaults.py @@ -15,9 +15,6 @@ ROOT = os.path.normpath(os.path.join(os.path.dirname(__file__), os.pardir)) -# This should be the full URL to sentries store view -SERVERS = None - TIMEOUT = 1 # TODO: this is specific to Django @@ -45,9 +42,6 @@ 'raven.processors.SanitizePasswordsProcessor', ) -# Default Project ID -PROJECT = 1 - try: # Try for certifi first since they likely keep their bundle more up to date import certifi From 7218bd6047d09b86d4cfb7404cb290965fd33883 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 16 Jul 2015 12:22:25 +0200 Subject: [PATCH 041/692] Updated sentry ext and renamed wizards.json. --- docs/_sentryext | 2 +- docs/{wizards.json => sentry-doc-config.json} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename docs/{wizards.json => sentry-doc-config.json} (100%) diff --git a/docs/_sentryext b/docs/_sentryext index 20496d487..4ae64ecc1 160000 --- a/docs/_sentryext +++ b/docs/_sentryext @@ -1 +1 @@ -Subproject commit 20496d4877a7b74e3b06c55f9f385de2898df49e +Subproject commit 4ae64ecc1f0febd54e01ef36867ab84a70462801 diff --git a/docs/wizards.json b/docs/sentry-doc-config.json similarity index 100% rename from docs/wizards.json rename to docs/sentry-doc-config.json From eee65dc84c5866501c8e4581c9c13722e14d56ee Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 16 Jul 2015 19:05:44 +0200 Subject: [PATCH 042/692] Bumped _sentryext --- docs/_sentryext | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_sentryext b/docs/_sentryext index 4ae64ecc1..c523fdd0c 160000 --- a/docs/_sentryext +++ b/docs/_sentryext @@ -1 +1 @@ -Subproject commit 4ae64ecc1f0febd54e01ef36867ab84a70462801 +Subproject commit c523fdd0cb366fed58df9be098cfc6eca572f7b2 From 9f77c4ec0ef9fcdeab4fce53972bfe76ea117f99 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 16 Jul 2015 19:08:04 +0200 Subject: [PATCH 043/692] configurations -> wizards --- docs/sentry-doc-config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sentry-doc-config.json b/docs/sentry-doc-config.json index e673af1a6..6b184127d 100644 --- a/docs/sentry-doc-config.json +++ b/docs/sentry-doc-config.json @@ -1,5 +1,5 @@ { - "configurations": { + "wizards": { "python": { "name": "Python", "client_lib": "raven-python", From 72bc7c98d9b2adb84985c20f3a8918add2a803c3 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 20 Jul 2015 17:14:37 +0200 Subject: [PATCH 044/692] Language --- docs/config.rst | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/docs/config.rst b/docs/config.rst index 27975e182..00c1ea1e6 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -14,20 +14,16 @@ Configuring the Client ---------------------- Settings are specified as part of the initialization of the client. The -client is a class that can be instanciated with a specific configuration +client is a class that can be instantiated with a specific configuration and all reporting can then happen from the instance of that object. Typically an instance is created somewhere globally and then imported as necessary. -As of Raven 1.2.0, you can now configure all clients through a standard DSN -string. This can be specified as a default using the ``SENTRY_DSN`` environment -variable, as well as passed to all clients by using the ``dsn`` argument. - .. code-block:: python from raven import Client - # Read configuration from the environment + # Read configuration from the ``SENTRY_DSN`` environment variable client = Client() # Manually specify a DSN From 08b6d82cdbf851194cf07c0872c07da0184ef2e2 Mon Sep 17 00:00:00 2001 From: Jacob Haslehurst Date: Tue, 21 Jul 2015 16:31:14 +1000 Subject: [PATCH 045/692] Fix grammar issue --- docs/integrations/django.rst | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/integrations/django.rst b/docs/integrations/django.rst index 55baf35c8..0990cc1a3 100644 --- a/docs/integrations/django.rst +++ b/docs/integrations/django.rst @@ -2,9 +2,7 @@ Django ====== .. default-domain:: py - -`Django `_ is one of (if not the) Python's most -popular web frameworks. Support is built into Raven but needs some +`Django `_ is arguably Python's most popular web framework. Support is built into Raven but needs some configuration. While older versions of Django will likely work, officially only version 1.4 and newer are supported. From 0b1e8eeccfa5993384599055eea77eaea534c9dd Mon Sep 17 00:00:00 2001 From: Jacob Haslehurst Date: Tue, 21 Jul 2015 16:35:23 +1000 Subject: [PATCH 046/692] stick to 80 char max line length --- docs/integrations/django.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/integrations/django.rst b/docs/integrations/django.rst index 0990cc1a3..2dfbcc0d7 100644 --- a/docs/integrations/django.rst +++ b/docs/integrations/django.rst @@ -2,9 +2,10 @@ Django ====== .. default-domain:: py -`Django `_ is arguably Python's most popular web framework. Support is built into Raven but needs some -configuration. While older versions of Django will likely work, -officially only version 1.4 and newer are supported. +`Django `_ is arguably Python's most popular web +framework. Support is built into Raven but needs some configuration. While +older versions of Django will likely work, officially only version 1.4 and +newer are supported. Setup ----- From e3e5be7a7483b91f3d10af444cba7aece64c9d4b Mon Sep 17 00:00:00 2001 From: Kelsey Francis Date: Tue, 21 Jul 2015 12:52:00 -0700 Subject: [PATCH 047/692] Respect wrap_wsgi arg passed to flask.Sentry constructor Prior to this commit any wrap_wsgi arg passed to raven.contrib.flask.Sentry() got overwritten within init_app. --- raven/contrib/flask.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/contrib/flask.py b/raven/contrib/flask.py index 67ef1cafc..706201a71 100644 --- a/raven/contrib/flask.py +++ b/raven/contrib/flask.py @@ -237,7 +237,7 @@ def init_app(self, app, dsn=None, logging=None, level=None, wrap_wsgi=None, if wrap_wsgi is not None: self.wrap_wsgi = wrap_wsgi - else: + elif self.wrap_wsgi is None: # Fix https://github.com/getsentry/raven-python/issues/412 # the gist is that we get errors twice in debug mode if we don't do this if app and app.debug: From dd4543dde55199ca568fd942df3827bf3187314c Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 22 Jul 2015 10:34:22 +0200 Subject: [PATCH 048/692] Experiment with moving more of the docs onto the index page. --- docs/{config.rst => advanced.rst} | 24 +++++++--- docs/index.rst | 77 +++++++++++++++++++++++++++---- docs/installation.rst | 25 ---------- 3 files changed, 86 insertions(+), 40 deletions(-) rename docs/{config.rst => advanced.rst} (88%) delete mode 100644 docs/installation.rst diff --git a/docs/config.rst b/docs/advanced.rst similarity index 88% rename from docs/config.rst rename to docs/advanced.rst index 00c1ea1e6..82849439e 100644 --- a/docs/config.rst +++ b/docs/advanced.rst @@ -1,12 +1,24 @@ -Configuration -============= +Advanced Usage +============== -.. default-domain:: py +This covers some advanced usage scenarios for raven Python. -This document describes configuration options available to the Raven -client for the use with Sentry. It also covers some other important parts -about configuring the environment. +Alternative Installations +------------------------- +If you want to use the latest git version you can get it from `the github +repository `_:: + + git clone https://github.com/getsentry/raven-python + pip install raven-python + +Certain additional features can be installed by defining the feature when +``pip`` installing it. For instance to install all dependencies needed to +use the Flask integration, you can depend on ``raven[flask]``:: + + pip install raven[flask] + +For more information refer to the individual integration documentation. .. _python-client-config: diff --git a/docs/index.rst b/docs/index.rst index cce33ea63..47d0c43e3 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -10,23 +10,82 @@ Python ====== -Raven for Python (raven-python) is the official standalone Python client -for Sentry. It can be used with any modern Python interpreter be it -CPython 2.x or 3.x, PyPy or Jython. It's an Open Source project and -available under a very liberal BSD license. +For pairing Sentry up with Python you can use the Raven for Python +(raven-python) library. It is the official standalone Python client for +Sentry. It can be used with any modern Python interpreter be it CPython +2.x or 3.x, PyPy or Jython. It's an Open Source project and available +under a very liberal BSD license. -.. sentry:edition:: self +Installation +------------ + +If you haven't already, start by downloading Raven. The easiest way is +with *pip*:: + + pip install raven --upgrade + +Configuring the Client +---------------------- + +Settings are specified as part of the initialization of the client. The +client is a class that can be instanciated with a specific configuration +and all reporting can then happen from the instance of that object. +Typically an instance is created somewhere globally and then imported as +necessary. For getting started all you need is your DSN: + +.. sourcecode:: python + + from raven import Client + client = Client('___DSN___') + +Capture an Error +---------------- + +The most basic use for raven is to record one specific error that occurs:: + + from raven import Client + + client = Client('___DSN___') + + try: + 1 / 0 + except ZeroDivisionError: + client.captureException() + +Adding Context +-------------- + +The raven client internally keeps a thread local mapping that can carry +additional information. Whenever a message is submitted to Sentry that +additional data will be passed along. This context is available as +`client.context` and can be modified or cleared. + +Example usage: + +.. sourcecode:: python + + def handle_request(request): + client.context.merge({'user': { + 'email': request.user.email + }}) + try: + ... + finally: + client.context.clear() + +Deep Dive +--------- - User's Guide - ------------ +Raven Python is more than that however. To dive deeper into what it does, +how it works and how it integrates into other systems there is more to +discover: .. toctree:: :maxdepth: 2 :titlesonly: - installation - config usage + advanced integrations/index transports platform-support diff --git a/docs/installation.rst b/docs/installation.rst deleted file mode 100644 index d04497094..000000000 --- a/docs/installation.rst +++ /dev/null @@ -1,25 +0,0 @@ -Installation -============ - -If you haven't already, start by downloading Raven. The easiest way is -with *pip*:: - - pip install raven --upgrade - -Or alternatively with *setuptools*:: - - easy_install -U raven - -If you want to use the latest git version you can get it from `the github -repository `_:: - - git clone https://github.com/getsentry/raven-python - pip install raven-python - -Certain additional features can be installed by defining the feature when -``pip`` installing it. For instance to install all dependencies needed to -use the Flask integration, you can depend on ``raven[flask]``:: - - pip install raven[flask] - -For more information refer to the individual integration documentation. From ff1606440e27c28c89178a4559460c79b425c1ce Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 22 Jul 2015 10:50:44 +0200 Subject: [PATCH 049/692] Renamed a headline to make more sense in the context --- docs/usage.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/usage.rst b/docs/usage.rst index 7f801809a..1d8f083f9 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -1,5 +1,5 @@ -Usage -===== +Basic Usage +=========== This gives a basic overview of how to use the raven client with Python directly. From 872b821b71be43703906e586536732094d1f41df Mon Sep 17 00:00:00 2001 From: David Cramer Date: Wed, 22 Jul 2015 16:59:33 +0200 Subject: [PATCH 050/692] Changes for 5.5.0 --- CHANGES | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGES b/CHANGES index f818858d6..26adfd411 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,10 @@ +Version 5.5.0 +------------- + +* Added ``sys.excepthook`` handler (installed by default). +* Fixed an issue where ``wrap_wsgi`` wasn't being respected. +* Various deprecated code removed. + Version 5.4.4 ------------- From 09d32dbe9bf5ab6816c0fe89200eb88ad79d4a34 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Wed, 22 Jul 2015 16:59:55 +0200 Subject: [PATCH 051/692] 5.6.0.dev0 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 8f63db976..9a7d2377d 100755 --- a/setup.py +++ b/setup.py @@ -95,7 +95,7 @@ def run_tests(self): setup( name='raven', - version='5.5.0.dev0', + version='5.6.0.dev0', author='David Cramer', author_email='dcramer@gmail.com', url='https://github.com/getsentry/raven-python', From 41dcfe2129a6a0805a8df590020057e69d6bebc8 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 22 Jul 2015 17:46:48 +0200 Subject: [PATCH 052/692] data -> describe in docs --- docs/advanced.rst | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/docs/advanced.rst b/docs/advanced.rst index 82849439e..050b6d83d 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -184,22 +184,19 @@ Sanitizing Data Several processors are included with Raven to assist in data sanitiziation. These are configured with the ``processors`` value. -.. data:: raven.processors.SanitizePasswordsProcessor - :noindex: +.. describe:: raven.processors.SanitizePasswordsProcessor Removes all keys which resemble ``password``, ``secret``, or ``api_key`` within stacktrace contexts, HTTP bits (such as cookies, POST data, the querystring, and environment), and extra data. -.. data:: raven.processors.RemoveStackLocalsProcessor - :noindex: +.. describe:: raven.processors.RemoveStackLocalsProcessor Removes all stacktrace context variables. This will cripple the functionality of Sentry, as you'll only get raw tracebacks, but it will ensure no local scoped information is available to the server. -.. data:: raven.processors.RemovePostDataProcessor - :noindex: +.. describe:: raven.processors.RemovePostDataProcessor Removes the ``body`` of all HTTP data. From 4371e3de97a53b7ca270f352e61ea72ebebb730d Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 24 Jul 2015 13:42:16 +0200 Subject: [PATCH 053/692] Updates to the API docs --- docs/api.rst | 32 ++++++++++++++++++++++++++++++++ docs/index.rst | 20 ++++++++------------ 2 files changed, 40 insertions(+), 12 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index 8b3fabb5a..62d78004a 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -95,6 +95,38 @@ Client Returns a reference to the thread local context object. See :py:class:`raven.context.Context` for more information. + .. py:method:: user_context(data) + + Updates the user context for future events. + + Equivalent to this:: + + client.context.merge({'user': data}) + + .. py:method:: http_context(data) + + Updates the HTTP context for future events. + + Equivalent to this:: + + client.context.merge({'request': data}) + + .. py:method:: extra_context(data) + + Update the extra context for future events. + + Equivalent to this:: + + client.context.merge({'extra': data}) + + .. py:method:: tags_context(data) + + Update the tags context for future events. + + Equivalent to this:: + + client.context.merge({'tags': data}) + Context ------- diff --git a/docs/index.rst b/docs/index.rst index 47d0c43e3..649cba1b5 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -55,23 +55,19 @@ The most basic use for raven is to record one specific error that occurs:: Adding Context -------------- -The raven client internally keeps a thread local mapping that can carry -additional information. Whenever a message is submitted to Sentry that -additional data will be passed along. This context is available as -`client.context` and can be modified or cleared. - -Example usage: +Much of the usefulness of Sentry comes from additional context data with +the events. The Python client makes this very convenient by providing +methods to set thread local context data that is then submitted +automatically with all events. For instance you can use +:py:meth:`~raven.Client.user_context` to set the information about the +current user: .. sourcecode:: python def handle_request(request): - client.context.merge({'user': { + client.user_context({ 'email': request.user.email - }}) - try: - ... - finally: - client.context.clear() + }) Deep Dive --------- From d1a47c1d6ea422f95aa4ee2fb564d50c75edc0eb Mon Sep 17 00:00:00 2001 From: David Cramer Date: Tue, 28 Jul 2015 14:12:55 -0700 Subject: [PATCH 054/692] Expand docs for to match current behavior - Add transport attribute - Remove scheme-based DSN - Add fingerprint - Remove checksum --- docs/advanced.rst | 46 ++++++++++++++++++++++++++++------------------ 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/docs/advanced.rst b/docs/advanced.rst index 050b6d83d..904e65385 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -66,24 +66,6 @@ settings: The *fetch_package_version* and *fetch_git_sha* helpers. -The Sentry DSN --------------- - -.. sentry:edition:: hosted, on-premise - - The most important information is the Sentry DSN. For information - about it see :ref:`configure-the-dsn` in the general Sentry docs. - -The Python client supports one additional modification to the regular DSN -values which is the choice of the transport. To select a specific -transport, the DSN needs to be prepended with the name of the transport. -For instance to select the ``gevent`` transport, the following DSN would -be used:: - - 'gevent+___DSN___' - -For more information see :doc:`transports`. - Client Arguments ---------------- @@ -95,6 +77,13 @@ The following are valid arguments which may be passed to the Raven client: dsn = '___DSN___' +.. describe:: transport + + The HTTP transport class to use. By default this is an asynchronous worker + thread that runs in-process. + + For more information see :doc:`transports`. + .. describe:: site An optional, arbitrary string to identify this client installation:: @@ -201,6 +190,27 @@ sanitiziation. These are configured with the ``processors`` value. Removes the ``body`` of all HTTP data. +Changing Grouping Behavior +-------------------------- + +In some cases you may see issues where Sentry groups multiple events together +when they should be separate entities. In other cases, Sentry simply doesn't +group events together because they're so sporadic that they never look the same. + +Both of these problems can be addressed by specifying the ``fingerprint`` +attribute. + +For example, if you have HTTP 404 (page not found) errors, and you'd prefer they +deduplicate by taking into account the URL: + +.. code-style:: python + + client.captureException(fingerprint=['{{ default }}', 'http://my-url/']) + +.. sentry:edition:: hosted, on-premise + + For more information, see :ref:`custom-grouping`. + A Note on uWSGI --------------- From 296555fc123d27e040281217fcf4921dfe421dc7 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Tue, 28 Jul 2015 14:24:06 -0700 Subject: [PATCH 055/692] code-style => code-block --- docs/advanced.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/advanced.rst b/docs/advanced.rst index 904e65385..f7c20116d 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -203,7 +203,7 @@ attribute. For example, if you have HTTP 404 (page not found) errors, and you'd prefer they deduplicate by taking into account the URL: -.. code-style:: python +.. code-block:: python client.captureException(fingerprint=['{{ default }}', 'http://my-url/']) From 0df38c7c2380e2e43ee87f6fb85b882ad59c4656 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Tue, 28 Jul 2015 14:37:11 -0700 Subject: [PATCH 056/692] Language --- docs/advanced.rst | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/advanced.rst b/docs/advanced.rst index f7c20116d..2aec2b139 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -189,9 +189,8 @@ sanitiziation. These are configured with the ``processors`` value. Removes the ``body`` of all HTTP data. - -Changing Grouping Behavior --------------------------- +Custom Grouping Behavior +------------------------ In some cases you may see issues where Sentry groups multiple events together when they should be separate entities. In other cases, Sentry simply doesn't From faff47c9f01ef0f08773fbc7f132f9a9562154e3 Mon Sep 17 00:00:00 2001 From: Matt Robenolt Date: Sat, 1 Aug 2015 12:24:50 -0700 Subject: [PATCH 057/692] Update django middleware docs The old method has been removed since Django 1.4 --- docs/integrations/django.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/integrations/django.rst b/docs/integrations/django.rst index 2dfbcc0d7..297bf6599 100644 --- a/docs/integrations/django.rst +++ b/docs/integrations/django.rst @@ -208,9 +208,9 @@ middleware which will ensure that you catch errors even at the fundamental level of your Django application:: from raven.contrib.django.raven_compat.middleware.wsgi import Sentry - from django.core.handlers.wsgi import WSGIHandler + from django.core.wsgi import get_wsgi_application - application = Sentry(WSGIHandler()) + application = Sentry(get_wsgi_application()) Additional Settings From 8cb97dd28945f84ff8eaf4642c4abc84769003c0 Mon Sep 17 00:00:00 2001 From: Matt Robenolt Date: Sun, 2 Aug 2015 16:12:45 -0700 Subject: [PATCH 058/692] Use correct pip cached directory --- .travis.yml | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/.travis.yml b/.travis.yml index 319b79943..7597da639 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,8 +9,7 @@ addons: cache: directories: - - node_modules - - .pip_download_cache + - $HOME/.cache/pip python: - "2.6" @@ -28,15 +27,6 @@ env: - DJANGO=Django==1.7.7 - DJANGO=Django==1.8 - 'DJANGO="-e git+git://github.com/django/django.git#egg=Django"' - global: - - 'PIP_DOWNLOAD_CACHE=".pip_download_cache"' - -before_install: - # Use closer nameservers - # - printf "nameserver 199.91.168.70\nnameserver 199.91.168.71\n" | sudo tee /etc/resolv.conf - # These need to be here and not in the env hash because they need to be - # evaluated after the virtualenv has been setup - - mkdir -p $PIP_DOWNLOAD_CACHE install: - time ci/setup From a87eecc3e1c5c941ec29bcc4701afb2bc3acc885 Mon Sep 17 00:00:00 2001 From: Matt Robenolt Date: Sun, 2 Aug 2015 17:29:20 -0700 Subject: [PATCH 059/692] Refactor get_version_from_app to better handle unexepcted cases For example, graphite's `version` returns a `module`. So this should cover all of the bases to assure that we don't return back a `str` of a `module`. --- raven/utils/__init__.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/raven/utils/__init__.py b/raven/utils/__init__.py index 6db2ee43f..42e60f367 100644 --- a/raven/utils/__init__.py +++ b/raven/utils/__init__.py @@ -56,26 +56,30 @@ def varmap(func, var, context=None, name=None): def get_version_from_app(module_name, app): + version = None if hasattr(app, 'get_version'): - get_version = app.get_version - if callable(get_version): - version = get_version() - else: - version = get_version + version = app.get_version elif hasattr(app, '__version__'): version = app.__version__ elif hasattr(app, 'VERSION'): version = app.VERSION elif hasattr(app, 'version'): version = app.version - elif pkg_resources: + + if callable(version): + version = version() + + if not isinstance(version, (six.string_types, list, tuple)): + version = None + + if version is None: + if pkg_resources is None: + return None # pull version from pkg_resources if distro exists try: version = pkg_resources.get_distribution(module_name).version except pkg_resources.DistributionNotFound: return None - else: - return None if isinstance(version, (list, tuple)): version = '.'.join(map(str, version)) From 94fe2b3d9c40e80be79342fdb25237b1b97cac13 Mon Sep 17 00:00:00 2001 From: Matt Robenolt Date: Sun, 2 Aug 2015 17:40:11 -0700 Subject: [PATCH 060/692] Add pytest-timeout to try and get travis to error faster on hanging builds --- .travis.yml | 2 +- setup.py | 1 + test-requirements.txt | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 7597da639..f0054318b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -33,7 +33,7 @@ install: script: - "if [[ ${TRAVIS_PYTHON_VERSION} != 'pypy' ]]; then make lint; fi" - - py.test tests/ --cov raven --cov-report term-missing + - py.test tests/ --cov raven --cov-report term-missing --timeout 10 matrix: allow_failures: diff --git a/setup.py b/setup.py index 9a7d2377d..d2f51b2e7 100755 --- a/setup.py +++ b/setup.py @@ -66,6 +66,7 @@ 'pytest>=2.7.0,<2.8.0', 'pytest-cov>=1.4', 'pytest-django>=2.8.0,<2.7.0', + 'pytest-timeout==0.4', 'requests', 'tornado', 'webob', diff --git a/test-requirements.txt b/test-requirements.txt index 4925081fd..1484a8141 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,2 +1,3 @@ pytest-django +pytest-timeout -e .[dev,tests] From 2d334d971a3dd30b4cf3a82aa1e485b5b65fb18b Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 3 Aug 2015 22:47:59 -0700 Subject: [PATCH 061/692] Clean up invalid Celery logic --- docs/integrations/celery.rst | 3 ++- raven/contrib/celery/__init__.py | 25 +++++++++++++++++-------- raven/contrib/django/models.py | 10 +++------- 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/docs/integrations/celery.rst b/docs/integrations/celery.rst index 052d34a60..531f2bf64 100644 --- a/docs/integrations/celery.rst +++ b/docs/integrations/celery.rst @@ -13,6 +13,7 @@ Celery error handling:: client = Client('___DSN___') + # a shortcut for register_logger() and register_logging_filter() # register a custom filter to filter out duplicate logs register_logger_signal(client) @@ -37,7 +38,7 @@ A more complex version to encapsulate behavior: def on_configure(self): client = raven.Client('___DSN___') - # register a custom filter to filter out duplicate logs + # a shortcut for register_logger() and register_logging_filter() register_logger_signal(client) # hook into the Celery error handler diff --git a/raven/contrib/celery/__init__.py b/raven/contrib/celery/__init__.py index 56b724745..53923b1ad 100644 --- a/raven/contrib/celery/__init__.py +++ b/raven/contrib/celery/__init__.py @@ -37,24 +37,33 @@ def process_failure_signal(sender, task_id, args, kwargs, **kw): def register_logger_signal(client, logger=None, loglevel=logging.ERROR): - filter_ = CeleryFilter() + # logger is unused + register_logging_filter(client) + register_logger(client, loglevel) - if logger is None: - logger = logging.getLogger() - handler = SentryHandler(client) - handler.setLevel(loglevel) - handler.addFilter(filter_) - def process_logger_event(sender, logger, loglevel, logfile, format, +def register_logging_filter(client): + def sentry_logging_filter(sender, logger, loglevel, logfile, format, colorize, **kw): # Attempt to find an existing SentryHandler, and if it exists ensure # that the CeleryFilter is installed. # If one is found, we do not attempt to install another one. for h in logger.handlers: if type(h) == SentryHandler: + filter_ = CeleryFilter() h.addFilter(filter_) return False + after_setup_logger.connect(sentry_logging_filter, weak=False) + + +def register_logger(client, loglevel=logging.ERROR): + def sentry_logger(sender, logger, loglevel, logfile, format, + colorize, **kw): + filter_ = CeleryFilter() + handler = SentryHandler(client) + handler.setLevel(loglevel) + handler.addFilter(filter_) logger.addHandler(handler) - after_setup_logger.connect(process_logger_event, weak=False) + after_setup_logger.connect(sentry_logger, weak=False) diff --git a/raven/contrib/django/models.py b/raven/contrib/django/models.py index 3db5ce6d1..1e550bd79 100644 --- a/raven/contrib/django/models.py +++ b/raven/contrib/django/models.py @@ -209,7 +209,8 @@ def wrap_sentry(request, **kwargs): try: # Celery < 2.5? is not supported from raven.contrib.celery import ( - register_signal, register_logger_signal) + register_signal, register_logging_filter + ) except ImportError: logger.exception('Failed to install Celery error handler') else: @@ -219,12 +220,7 @@ def wrap_sentry(request, **kwargs): logger.exception('Failed to install Celery error handler') try: - ga = lambda x, d=None: getattr(settings, 'SENTRY_%s' % x, d) - options = getattr(settings, 'RAVEN_CONFIG', {}) - loglevel = options.get('celery_loglevel', - ga('CELERY_LOGLEVEL', logging.ERROR)) - - register_logger_signal(client, loglevel=loglevel) + register_logging_filter(client) except Exception: logger.exception('Failed to install Celery error handler') From ed845e9cef5c53bf3952cebb943db1210b2386ea Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 3 Aug 2015 23:09:15 -0700 Subject: [PATCH 062/692] Filter out celery.redirect --- raven/contrib/celery/__init__.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/raven/contrib/celery/__init__.py b/raven/contrib/celery/__init__.py index 53923b1ad..68a8fed05 100644 --- a/raven/contrib/celery/__init__.py +++ b/raven/contrib/celery/__init__.py @@ -14,6 +14,12 @@ class CeleryFilter(logging.Filter): def filter(self, record): + # celery.redirect is used for stdout -- kill it + # TODO(dcramer): this should be more configurable, but our default + # injections with Celery aren't super friendly towards configurability + if record.name == 'celery.redirect': + return False + # Context is fixed in Celery 3.x so use internal flag instead extra_data = getattr(record, 'data', {}) if not isinstance(extra_data, dict): From d65cc551ca25327c365d4dbd02aaccba7c0077d6 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Tue, 4 Aug 2015 08:39:45 -0700 Subject: [PATCH 063/692] redirect => redirected --- raven/contrib/celery/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/raven/contrib/celery/__init__.py b/raven/contrib/celery/__init__.py index 68a8fed05..a245bbba7 100644 --- a/raven/contrib/celery/__init__.py +++ b/raven/contrib/celery/__init__.py @@ -14,10 +14,10 @@ class CeleryFilter(logging.Filter): def filter(self, record): - # celery.redirect is used for stdout -- kill it + # celery.redirected is used for stdout -- kill it # TODO(dcramer): this should be more configurable, but our default # injections with Celery aren't super friendly towards configurability - if record.name == 'celery.redirect': + if record.name == 'celery.redirected': return False # Context is fixed in Celery 3.x so use internal flag instead From ae52042cf2d18bb3ae28c809fb3202c88c7741af Mon Sep 17 00:00:00 2001 From: Hsiaoming Yang Date: Wed, 5 Aug 2015 14:05:07 +0800 Subject: [PATCH 064/692] Don't send X-Sentry-ID when last_event_id is None --- raven/contrib/flask.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/raven/contrib/flask.py b/raven/contrib/flask.py index 706201a71..237ca84bd 100644 --- a/raven/contrib/flask.py +++ b/raven/contrib/flask.py @@ -223,7 +223,8 @@ def before_request(self, *args, **kwargs): self.client.user_context(self.get_user_info(request)) def after_request(self, sender, response, *args, **kwargs): - response.headers['X-Sentry-ID'] = self.last_event_id + if self.last_event_id: + response.headers['X-Sentry-ID'] = self.last_event_id self.client.context.clear() return response From d819a14b64cb0d224f80c1659e6fe49b447ee913 Mon Sep 17 00:00:00 2001 From: Patrick Cloke Date: Tue, 11 Aug 2015 11:27:59 -0400 Subject: [PATCH 065/692] Add documentation for the `IGNORABLE_404_URLS` setting. --- docs/integrations/django.rst | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/docs/integrations/django.rst b/docs/integrations/django.rst index 297bf6599..d6bdd4125 100644 --- a/docs/integrations/django.rst +++ b/docs/integrations/django.rst @@ -139,17 +139,33 @@ addition of an optional ``request`` key in the extra data:: ----------- In certain conditions you may wish to log 404 events to the Sentry server. To -do this, you simply need to enable a Django middleware:: +do this, you simply need to enable a Django middleware: + +.. sourcecode:: python MIDDLEWARE_CLASSES = ( - 'raven.contrib.django.raven_compat.middleware.Sentry404CatchMiddleware', - ..., + 'raven.contrib.django.raven_compat.middleware.Sentry404CatchMiddleware', + ..., ) + MIDDLEWARE_CLASSES - + It is recommended to put the middleware at the top, so that any only 404s that bubbled all the way up get logged. Certain middlewares (e.g. flatpages) capture 404s and replace the response. +It is also possible to configure this middleware to ignore 404s on particular +pages by defining the ``IGNORABLE_404_URLS`` setting as an iterable of regular +expression patterns. If any of these patterns produces as match against the full +requested URL (as defined by the regular expression's ``search`` method), then +the 404 will not be reported to Sentry. + +.. sourcecode:: python + + import re + + IGNORABLE_404_URLS = ( + re.compile('/foo'), + ) + Message References ------------------ From c2d1bb516ce427b4ce74770443d514acf2b95228 Mon Sep 17 00:00:00 2001 From: Patrick Cloke Date: Wed, 12 Aug 2015 07:38:56 -0400 Subject: [PATCH 066/692] Clarify and fix typo from #639. --- docs/integrations/django.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/integrations/django.rst b/docs/integrations/django.rst index d6bdd4125..b5dfaf95f 100644 --- a/docs/integrations/django.rst +++ b/docs/integrations/django.rst @@ -154,9 +154,9 @@ capture 404s and replace the response. It is also possible to configure this middleware to ignore 404s on particular pages by defining the ``IGNORABLE_404_URLS`` setting as an iterable of regular -expression patterns. If any of these patterns produces as match against the full -requested URL (as defined by the regular expression's ``search`` method), then -the 404 will not be reported to Sentry. +expression patterns. If any pattern produces a match against the full requested +URL (as defined by the regular expression's ``search`` method), then the 404 +will not be reported to Sentry. .. sourcecode:: python From 0cfd4e35f9fb0531bd7b8d213f471fccf7d92d7c Mon Sep 17 00:00:00 2001 From: Pierre Dulac Date: Sat, 15 Aug 2015 16:39:17 +0200 Subject: [PATCH 067/692] The LoaderOrigin has been merged with StringOrigin described in the Django 1.9 release notes https://docs.djangoproject.com/en/dev/releases/1.9/#template-loaderorigin-and-stringorigin-are-removed --- raven/contrib/django/client.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/raven/contrib/django/client.py b/raven/contrib/django/client.py index 04cc5ab5f..8ee88df11 100644 --- a/raven/contrib/django/client.py +++ b/raven/contrib/django/client.py @@ -14,7 +14,13 @@ from django.core.exceptions import SuspiciousOperation from django.http import HttpRequest from django.template import TemplateSyntaxError -from django.template.loader import LoaderOrigin + +try: + # support Django 1.9 + from django.template.base import Origin +except ImportError: + # backward compatibility + from django.template.loader import LoaderOrigin as Origin from raven.base import Client from raven.contrib.django.utils import get_data_from_template, get_host @@ -148,7 +154,7 @@ def capture(self, event_type, request=None, **kwargs): # As of r16833 (Django) all exceptions may contain a ``django_template_source`` attribute (rather than the # legacy ``TemplateSyntaxError.source`` check) which describes template information. if hasattr(exc_value, 'django_template_source') or ((isinstance(exc_value, TemplateSyntaxError) and - isinstance(getattr(exc_value, 'source', None), (tuple, list)) and isinstance(exc_value.source[0], LoaderOrigin))): + isinstance(getattr(exc_value, 'source', None), (tuple, list)) and isinstance(exc_value.source[0], Origin))): source = getattr(exc_value, 'django_template_source', getattr(exc_value, 'source', None)) if source is None: self.logger.info('Unable to get template source from exception') From cca9b2bb6088a226a3503de5f13c787a34177471 Mon Sep 17 00:00:00 2001 From: Li Yazhou Date: Tue, 18 Aug 2015 16:30:08 +0800 Subject: [PATCH 068/692] add try..except for error on self.get_current_user() --- raven/contrib/tornado/__init__.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/raven/contrib/tornado/__init__.py b/raven/contrib/tornado/__init__.py index 4b1645ab9..a8245ac8a 100644 --- a/raven/contrib/tornado/__init__.py +++ b/raven/contrib/tornado/__init__.py @@ -169,9 +169,13 @@ def get_sentry_user_info(self): `tornado.web.RequestHandler.get_current_user` tests postitively for on Truth calue testing """ + try: + user = self.get_current_user() + except Exception: + return {} return { 'user': { - 'is_authenticated': True if self.get_current_user() else False + 'is_authenticated': True if user else False } } From 13fd2410b6efbc1491eebc19361cbfa6b06e86af Mon Sep 17 00:00:00 2001 From: David Cramer Date: Wed, 26 Aug 2015 11:45:03 -0700 Subject: [PATCH 069/692] Cleanup docstring --- raven/contrib/tornado/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/contrib/tornado/__init__.py b/raven/contrib/tornado/__init__.py index a8245ac8a..42d6eb4da 100644 --- a/raven/contrib/tornado/__init__.py +++ b/raven/contrib/tornado/__init__.py @@ -27,7 +27,7 @@ def capture(self, *args, **kwargs): and extracts the keyword argument callback which will be called on asynchronous sending of the request - :return: a 32-length string identifying this event and checksum + :return: a 32-length string identifying this event """ if not self.is_enabled(): return From a76101f1fe38162ffa36c8d5370cbb8b4ab176d4 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Wed, 26 Aug 2015 11:45:10 -0700 Subject: [PATCH 070/692] Dont base64-encode bodies - Send deflate content-encoding --- raven/base.py | 9 ++++++--- tests/base/tests.py | 8 +++++--- tests/transport/tests.py | 5 ++--- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/raven/base.py b/raven/base.py index d26133222..d3ca872ec 100644 --- a/raven/base.py +++ b/raven/base.py @@ -8,7 +8,6 @@ from __future__ import absolute_import -import base64 import zlib import logging import os @@ -626,6 +625,7 @@ def send_encoded(self, message, auth_header=None, **kwargs): headers = { 'User-Agent': client_string, 'X-Sentry-Auth': auth_header, + 'Content-Encoding': self.get_content_encoding(), 'Content-Type': 'application/octet-stream', } @@ -636,17 +636,20 @@ def send_encoded(self, message, auth_header=None, **kwargs): **kwargs ) + def get_content_encoding(self): + return 'deflate' + def encode(self, data): """ Serializes ``data`` into a raw string. """ - return base64.b64encode(zlib.compress(json.dumps(data).encode('utf8'))) + return zlib.compress(json.dumps(data).encode('utf8')) def decode(self, data): """ Unserializes a string, ``data``. """ - return json.loads(zlib.decompress(base64.b64decode(data)).decode('utf8')) + return json.loads(zlib.decompress(data).decode('utf8')) def captureMessage(self, message, **kwargs): """ diff --git a/tests/base/tests.py b/tests/base/tests.py index ccbd547ca..ae69ae904 100644 --- a/tests/base/tests.py +++ b/tests/base/tests.py @@ -175,10 +175,11 @@ def test_send(self, time, send_remote): }) send_remote.assert_called_once_with( url='http://example.com/api/1/store/', - data=six.b('eJyrVkrLz1eyUlBKSixSqgUAIJgEVA=='), + data=client.encode({'foo': 'bar'}), headers={ 'User-Agent': 'raven-python/%s' % (raven.VERSION,), 'Content-Type': 'application/octet-stream', + 'Content-Encoding': client.get_content_encoding(), 'X-Sentry-Auth': ( 'Sentry sentry_timestamp=1328055286.51, ' 'sentry_client=raven-python/%s, sentry_version=6, ' @@ -199,11 +200,12 @@ def test_send_with_auth_header(self, time, send_remote): }) send_remote.assert_called_once_with( url='http://example.com/api/1/store/', - data=six.b('eJyrVkrLz1eyUlBKSixSqgUAIJgEVA=='), + data=client.encode({'foo': 'bar'}), headers={ 'User-Agent': 'raven-python/%s' % (raven.VERSION,), 'Content-Type': 'application/octet-stream', - 'X-Sentry-Auth': 'foo' + 'Content-Encoding': client.get_content_encoding(), + 'X-Sentry-Auth': 'foo', }, ) diff --git a/tests/transport/tests.py b/tests/transport/tests.py index b0f03e586..3c5d57a0b 100644 --- a/tests/transport/tests.py +++ b/tests/transport/tests.py @@ -14,7 +14,6 @@ import datetime import calendar import pytz -import base64 import zlib @@ -58,8 +57,8 @@ def test_custom_transport(self): mock_cls = c._transport_cache['mock://some_username:some_password@localhost:8143/1'].get_transport() - expected_message = zlib.decompress(base64.b64decode(c.encode(data))) - actual_message = zlib.decompress(base64.b64decode(mock_cls._data)) + expected_message = zlib.decompress(c.encode(data)) + actual_message = zlib.decompress(mock_cls._data) # These loads()/dumps() pairs order the dict keys before comparing the string. # See GH504 From 56d8cd4f7e0d51eec65535adb04bf940c8127905 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Wed, 26 Aug 2015 11:48:36 -0700 Subject: [PATCH 071/692] Begin changes for 5.6.0 --- CHANGES | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES b/CHANGES index 26adfd411..90981cbc8 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,9 @@ +Version 5.6.0 +------------- + +* Content is no longer base64-encoded. + + Version 5.5.0 ------------- From 6d0d0d5de0b0d033f9cef8d2a5c850de722b4f59 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Wed, 26 Aug 2015 11:51:06 -0700 Subject: [PATCH 072/692] Correct fingerprint behavior --- CHANGES | 1 + raven/base.py | 5 ++++- tests/base/tests.py | 10 ++++++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 90981cbc8..a5ae987de 100644 --- a/CHANGES +++ b/CHANGES @@ -2,6 +2,7 @@ Version 5.6.0 ------------- * Content is no longer base64-encoded. +* ``fingerprint`` is now correctly supported. Version 5.5.0 diff --git a/raven/base.py b/raven/base.py index d3ca872ec..7b4a0d9c6 100644 --- a/raven/base.py +++ b/raven/base.py @@ -273,7 +273,7 @@ def get_public_dsn(self, scheme=None): def build_msg(self, event_type, data=None, date=None, time_spent=None, extra=None, stack=None, public_key=None, - tags=None, **kwargs): + tags=None, fingerprint=None, **kwargs): """ Captures, processes and serializes an event into a dict object @@ -375,6 +375,9 @@ def build_msg(self, event_type, data=None, date=None, if culprit: data['culprit'] = culprit + if fingerprint: + data['fingerprint'] = fingerprint + # Run the data through processors for processor in self.get_processors(): data.update(processor.process(data)) diff --git a/tests/base/tests.py b/tests/base/tests.py index ae69ae904..e360b2fe3 100644 --- a/tests/base/tests.py +++ b/tests/base/tests.py @@ -350,6 +350,16 @@ def test_message_event(self): assert 'stacktrace' not in event self.assertTrue('timestamp' in event) + def test_fingerprint(self): + self.client.captureMessage( + message='test', + fingerprint=['{{ default }}', 'foobar'], + ) + + assert len(self.client.events) == 1 + event = self.client.events.pop(0) + assert event['fingerprint'] == ['{{ default }}', 'foobar'] + def test_context(self): self.client.context.merge({ 'tags': {'foo': 'bar'}, From 94a6d6a87383b37abfd9fb459e19d2f5eec65c3e Mon Sep 17 00:00:00 2001 From: David Cramer Date: Wed, 26 Aug 2015 11:52:45 -0700 Subject: [PATCH 073/692] Note additional changes --- CHANGES | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index a5ae987de..dd27a10c7 100644 --- a/CHANGES +++ b/CHANGES @@ -3,7 +3,8 @@ Version 5.6.0 * Content is no longer base64-encoded. * ``fingerprint`` is now correctly supported. - +* Django: 1.9 compatibility. +* Celery: Filter ``celery.redirect`` logger. Version 5.5.0 ------------- From 8b0aad48f138c9d25c58ee139161fb74192ab80e Mon Sep 17 00:00:00 2001 From: David Cramer Date: Wed, 26 Aug 2015 13:57:18 -0700 Subject: [PATCH 074/692] Ensure repr is is limited to string_max_length --- raven/utils/serializer/base.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/raven/utils/serializer/base.py b/raven/utils/serializer/base.py index a569dfd96..d9997e622 100644 --- a/raven/utils/serializer/base.py +++ b/raven/utils/serializer/base.py @@ -50,10 +50,12 @@ def recurse(self, value, max_depth=6, _depth=0, **kwargs): Given ``value``, recurse (using the parent serializer) to handle coercing of newly defined values. """ + string_max_length = kwargs.get('string_max_length', None) + _depth += 1 if _depth >= max_depth: try: - value = six.text_type(repr(value)) + value = six.text_type(repr(value))[:string_max_length] except Exception as e: import traceback traceback.print_exc() From 59e3730574d57f15f320de891058008808aaa6a0 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Wed, 26 Aug 2015 14:03:39 -0700 Subject: [PATCH 075/692] 5.7.0.dev0 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index d2f51b2e7..013a97e32 100755 --- a/setup.py +++ b/setup.py @@ -96,7 +96,7 @@ def run_tests(self): setup( name='raven', - version='5.6.0.dev0', + version='5.7.0.dev0', author='David Cramer', author_email='dcramer@gmail.com', url='https://github.com/getsentry/raven-python', From e97071ae841f3a7762d7ae19ef0aabd47d9313f6 Mon Sep 17 00:00:00 2001 From: Joakim Recht Date: Fri, 28 Aug 2015 21:40:36 +0200 Subject: [PATCH 076/692] Fix Tornado error handling. The error handling was quite broken - the exception handlers were never invoked, and they used methods which have been removed. This fixes #601 --- raven/contrib/tornado/__init__.py | 36 +++++++++++++++---------------- raven/transport/tornado.py | 25 +++++++++++++++++---- tests/contrib/tornado/tests.py | 25 +++++++++++++++++++++ 3 files changed, 63 insertions(+), 23 deletions(-) diff --git a/raven/contrib/tornado/__init__.py b/raven/contrib/tornado/__init__.py index 42d6eb4da..2966d18a8 100644 --- a/raven/contrib/tornado/__init__.py +++ b/raven/contrib/tornado/__init__.py @@ -7,6 +7,9 @@ """ from __future__ import absolute_import +from functools import partial + +from tornado import ioloop from tornado.httpclient import AsyncHTTPClient, HTTPError from raven.base import Client @@ -51,30 +54,25 @@ def send_remote(self, url, data, headers=None, callback=None): headers = {} if not self.state.should_try(): - message = self._get_log_message(data) - self.error_logger.error(message) + data = self.decode(data) + self._log_failed_submission(data) return + future = self._send_remote( + url=url, data=data, headers=headers, callback=callback + ) + ioloop.IOLoop.current().add_future(future, partial(self._handle_result, url, data)) + return future + + def _handle_result(self, url, data, future): try: - self._send_remote( - url=url, data=data, headers=headers, callback=callback - ) + future.result() except HTTPError as e: - body = e.response.body - self.error_logger.error( - 'Unable to reach Sentry log server: %s ' - '(url: %%s, body: %%s)' % (e,), - url, body, exc_info=True, - extra={'data': {'body': body, 'remote_url': url}} - ) + data = self.decode(data) + self._failed_send(e, url, data) except Exception as e: - self.error_logger.error( - 'Unable to reach Sentry log server: %s (url: %%s)' % (e,), - url, exc_info=True, extra={'data': {'remote_url': url}} - ) - message = self._get_log_message(data) - self.error_logger.error('Failed to submit message: %r', message) - self.state.set_fail() + data = self.decode(data) + self._failed_send(e, url, data) else: self.state.set_success() diff --git a/raven/transport/tornado.py b/raven/transport/tornado.py index 0dcc52c09..aa5306fca 100644 --- a/raven/transport/tornado.py +++ b/raven/transport/tornado.py @@ -7,6 +7,9 @@ """ from __future__ import absolute_import +from functools import partial + +from raven.transport.base import AsyncTransport from raven.transport.http import HTTPTransport try: @@ -17,7 +20,7 @@ has_tornado = False -class TornadoHTTPTransport(HTTPTransport): +class TornadoHTTPTransport(AsyncTransport, HTTPTransport): scheme = ['tornado+http', 'tornado+https'] @@ -27,7 +30,7 @@ def __init__(self, parsed_url, *args, **kwargs): super(TornadoHTTPTransport, self).__init__(parsed_url, *args, **kwargs) - def send(self, data, headers): + def async_send(self, data, headers, success_cb, failure_cb): kwargs = dict(method='POST', headers=headers, body=data) kwargs["validate_cert"] = self.verify_ssl kwargs["connect_timeout"] = self.timeout @@ -37,7 +40,21 @@ def send(self, data, headers): if ioloop.IOLoop.initialized(): client = AsyncHTTPClient() kwargs['callback'] = None + + future = client.fetch(self._url, **kwargs) + ioloop.IOLoop.current().add_future(future, partial(self.handler, success_cb, failure_cb)) else: client = HTTPClient() - - client.fetch(self._url, **kwargs) + try: + client.fetch(self._url, **kwargs) + success_cb() + except Exception as e: + failure_cb(e) + + @staticmethod + def handler(future, success, error): + try: + future.result() + success() + except Exception as e: + error(e) diff --git a/tests/contrib/tornado/tests.py b/tests/contrib/tornado/tests.py index 0f665362b..42f374467 100644 --- a/tests/contrib/tornado/tests.py +++ b/tests/contrib/tornado/tests.py @@ -3,6 +3,8 @@ from mock import patch from tornado import web, gen, testing +from tornado.concurrent import Future +from tornado.httpclient import HTTPError from raven.contrib.tornado import SentryMixin, AsyncSentryClient from raven.utils import six @@ -212,3 +214,26 @@ def test_send_error_handler_async(self, send): user_data = kwargs['user'] self.assertEqual(user_data['is_authenticated'], False) + + @testing.gen_test + def test_sending_to_unresponsive_sentry_server_logs_error(self): + client = self.get_app().sentry_client + with patch.object(client, '_failed_send') as mock_failed: + client.send() + + yield gen.sleep(0.01) # we need to run after the async send + assert mock_failed.called + + @testing.gen_test + def test_non_successful_responses_marks_client_as_failed(self): + client = self.get_app().sentry_client + with patch.object(client, '_failed_send') as mock_failed: + with patch.object(client, '_send_remote') as mock_send: + + f = Future() + f.set_exception(HTTPError(499, 'error')) + mock_send.return_value = f + client.send() + + yield gen.sleep(0.01) # we need to run after the async send + assert mock_failed.called From 10216c0f34fcd0e97e82645f4562d3d11eac609d Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 31 Aug 2015 14:30:53 -0700 Subject: [PATCH 077/692] Remove twine as no one uses it --- Makefile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 4de6e2c92..1f773a52d 100644 --- a/Makefile +++ b/Makefile @@ -22,7 +22,6 @@ setup-git: publish: rm -rf dist build - python setup.py sdist bdist_wheel - twine upload -s dist/* + python setup.py sdist bdist_wheel upload .PHONY: bootstrap test lint coverage setup-git publish From cd9a8b256f9640bd50162256550d032e8b86932e Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 31 Aug 2015 14:45:01 -0700 Subject: [PATCH 078/692] Improve stacktrace truncation code - Update stacktrace truncation code to match server behavior (and defaults) - Ensure maximum length of each set of variables --- raven/utils/stacks.py | 86 +++++++++++++++++++++++-------------- tests/utils/stacks/tests.py | 17 ++++---- 2 files changed, 63 insertions(+), 40 deletions(-) diff --git a/raven/utils/stacks.py b/raven/utils/stacks.py index b070b30cb..75a88d392 100644 --- a/raven/utils/stacks.py +++ b/raven/utils/stacks.py @@ -175,8 +175,54 @@ def iter_stack_frames(frames=None): yield frame, lineno +def get_frame_locals(frame, transformer=transform, max_var_size=4096): + f_locals = getattr(frame, 'f_locals', None) + if not f_locals: + return None + + if not isinstance(f_locals, dict): + # XXX: Genshi (and maybe others) have broken implementations of + # f_locals that are not actually dictionaries + try: + f_locals = to_dict(f_locals) + except Exception: + return None + + f_vars = {} + f_size = 0 + for k, v in six.iteritems(f_locals): + v = transformer(v) + v_size = len(repr(v)) + if v_size + f_size < 4096: + f_vars[k] = v + f_size += v_size + return f_vars + + +def slim_frame_data(frames, frame_allowance=25): + """ + Removes various excess metadata from middle frames which go beyond + ``frame_allowance``. + + Returns ``frames``. + """ + frames_len = len(frames) + + if frames_len <= frame_allowance: + return frames + + half_max = frame_allowance / 2 + + for n in xrange(half_max, frames_len - half_max): + # remove heavy components + frames[n].pop('vars', None) + frames[n].pop('pre_context', None) + frames[n].pop('post_context', None) + return frames + + def get_stack_info(frames, transformer=transform, capture_locals=True, - max_frames=50): + frame_allowance=25): """ Given a list of frames, returns a list of stack information dictionary objects that are JSON-ready. @@ -187,14 +233,8 @@ def get_stack_info(frames, transformer=transform, capture_locals=True, """ __traceback_hide__ = True # NOQA - half_max = max_frames / 2 - - top_results = [] - bottom_results = [] - - total_frames = 0 - - for frame_no, frame_info in enumerate(frames): + result = [] + for frame_info in frames: # Old, terrible API if isinstance(frame_info, (list, tuple)): frame, lineno = frame_info @@ -240,14 +280,6 @@ def get_stack_info(frames, transformer=transform, capture_locals=True, if not filename: filename = abs_path - if capture_locals and not isinstance(f_locals, dict): - # XXX: Genshi (and maybe others) have broken implementations of - # f_locals that are not actually dictionaries - try: - f_locals = to_dict(f_locals) - except Exception: - capture_locals = False - frame_result = { 'abs_path': abs_path, 'filename': filename, @@ -256,10 +288,9 @@ def get_stack_info(frames, transformer=transform, capture_locals=True, 'lineno': lineno + 1, } if capture_locals: - frame_result['vars'] = dict( - (k, transformer(v)) - for k, v in six.iteritems(f_locals) - ) + f_vars = get_frame_locals(frame, transformer=transformer) + if f_vars: + frame_result['vars'] = f_vars if context_line is not None: frame_result.update({ @@ -267,19 +298,10 @@ def get_stack_info(frames, transformer=transform, capture_locals=True, 'context_line': context_line, 'post_context': post_context, }) - - if frame_no >= half_max: - while len(bottom_results) > half_max - 1: - bottom_results.pop(0) - bottom_results.append(frame_result) - else: - top_results.append(frame_result) - total_frames += 1 + result.append(frame_result) stackinfo = { - 'frames': top_results + bottom_results, + 'frames': slim_frame_data(result, frame_allowance=frame_allowance), } - if total_frames > max_frames: - stackinfo['frames_omitted'] = (half_max + 1, total_frames - half_max + 1) return stackinfo diff --git a/tests/utils/stacks/tests.py b/tests/utils/stacks/tests.py index c962c49bd..8ae22c475 100644 --- a/tests/utils/stacks/tests.py +++ b/tests/utils/stacks/tests.py @@ -73,28 +73,29 @@ def test_bad_locals_in_frame(self): } assert result['vars'] == expected - def test_max_frames(self): + def test_frame_allowance(self): frames = [] for x in range(10): frame = Mock() - frame.f_locals = {} + frame.f_locals = {'k': 'v'} frame.f_lineno = None frame.f_globals = {} frame.f_code.co_filename = str(x) frame.f_code.co_name = __name__ frames.append((frame, 1)) - results = get_stack_info(frames, max_frames=4) - assert results['frames_omitted'] == (3, 9) - assert len(results['frames']) == 4 + results = get_stack_info(frames, frame_allowance=4) + assert len(results['frames']) == 10 assert results['frames'][0]['filename'] == '0' assert results['frames'][1]['filename'] == '1' - assert results['frames'][2]['filename'] == '8' - assert results['frames'][3]['filename'] == '9' + for idx, frame in enumerate(results['frames'][2:8]): + assert frame['filename'] == str(idx + 2) + assert 'vars' not in frame + assert results['frames'][8]['filename'] == '8' + assert results['frames'][9]['filename'] == '9' class GetLineFromFileTest(TestCase): - def test_non_ascii_file(self): import os.path filename = os.path.join(os.path.dirname(__file__), 'utf8_file.txt') From 5d4326095a1178882f25def1a07f11ba6f90ee8d Mon Sep 17 00:00:00 2001 From: Joakim Recht Date: Fri, 4 Sep 2015 09:50:47 +0200 Subject: [PATCH 079/692] Fix the Tornado transport, arguments were in wrong order. Also, add tests to ensure that it actually works. --- raven/transport/tornado.py | 2 +- tests/transport/tornado/tests.py | 31 +++++++++++++++++++++++++++++-- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/raven/transport/tornado.py b/raven/transport/tornado.py index aa5306fca..c58967e7f 100644 --- a/raven/transport/tornado.py +++ b/raven/transport/tornado.py @@ -52,7 +52,7 @@ def async_send(self, data, headers, success_cb, failure_cb): failure_cb(e) @staticmethod - def handler(future, success, error): + def handler(success, error, future): try: future.result() success() diff --git a/tests/transport/tornado/tests.py b/tests/transport/tornado/tests.py index 8ab9b0902..351b4d4a4 100644 --- a/tests/transport/tornado/tests.py +++ b/tests/transport/tornado/tests.py @@ -3,10 +3,16 @@ import mock from raven.base import Client -from raven.utils.testutils import TestCase +from tornado import gen, testing, httpclient -class TornadoTransportTests(TestCase): +class TornadoTransportTests(testing.AsyncTestCase): + + def get_new_ioloop(self): + io_loop = super(TornadoTransportTests, self).get_new_ioloop() + io_loop.make_current() + return io_loop + @mock.patch("raven.transport.tornado.HTTPClient") def test_send(self, fake_client): url = "https://user:pass@host:1234/1" @@ -34,3 +40,24 @@ def test_send(self, fake_client): self.assertEqual(kwargs["connect_timeout"], timeout) self.assertEqual(kwargs["validate_cert"], bool(verify_ssl)) self.assertEqual(kwargs["ca_certs"], ca_certs) + + @testing.gen_test + def test__sending_with_error_calls_error_callback(self): + c = Client(dsn='tornado+http://uver:pass@localhost:46754/1') + + with mock.patch.object(Client, '_failed_send') as mock_failed: + c.captureMessage(message='test') + yield gen.sleep(0.01) # we need to run after the async send + + assert mock_failed.called + + @testing.gen_test + def test__sending_successfully_calls_success_callback(self): + c = Client(dsn='tornado+http://uver:pass@localhost:46754/1') + with mock.patch.object(Client, '_successful_send') as mock_successful: + with mock.patch.object(httpclient.AsyncHTTPClient, 'fetch') as mock_fetch: + mock_fetch.return_value = gen.maybe_future(True) + c.captureMessage(message='test') + yield gen.sleep(0.01) # we need to run after the async send + + assert mock_successful.called From 5b4e86cb0130167db4999b6441ddcb62905ad73f Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 10 Sep 2015 11:15:10 -0500 Subject: [PATCH 080/692] Revert "redirect => redirected" This reverts commit d65cc551ca25327c365d4dbd02aaccba7c0077d6. --- raven/contrib/celery/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/raven/contrib/celery/__init__.py b/raven/contrib/celery/__init__.py index a245bbba7..68a8fed05 100644 --- a/raven/contrib/celery/__init__.py +++ b/raven/contrib/celery/__init__.py @@ -14,10 +14,10 @@ class CeleryFilter(logging.Filter): def filter(self, record): - # celery.redirected is used for stdout -- kill it + # celery.redirect is used for stdout -- kill it # TODO(dcramer): this should be more configurable, but our default # injections with Celery aren't super friendly towards configurability - if record.name == 'celery.redirected': + if record.name == 'celery.redirect': return False # Context is fixed in Celery 3.x so use internal flag instead From 32366939eb2a0b3ffa5857caf79d6e2559869916 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 10 Sep 2015 11:15:13 -0500 Subject: [PATCH 081/692] Revert "Clean up invalid Celery logic" This reverts commit 2d334d971a3dd30b4cf3a82aa1e485b5b65fb18b. --- docs/integrations/celery.rst | 3 +-- raven/contrib/celery/__init__.py | 25 ++++++++----------------- raven/contrib/django/models.py | 10 +++++++--- 3 files changed, 16 insertions(+), 22 deletions(-) diff --git a/docs/integrations/celery.rst b/docs/integrations/celery.rst index 531f2bf64..052d34a60 100644 --- a/docs/integrations/celery.rst +++ b/docs/integrations/celery.rst @@ -13,7 +13,6 @@ Celery error handling:: client = Client('___DSN___') - # a shortcut for register_logger() and register_logging_filter() # register a custom filter to filter out duplicate logs register_logger_signal(client) @@ -38,7 +37,7 @@ A more complex version to encapsulate behavior: def on_configure(self): client = raven.Client('___DSN___') - # a shortcut for register_logger() and register_logging_filter() + # register a custom filter to filter out duplicate logs register_logger_signal(client) # hook into the Celery error handler diff --git a/raven/contrib/celery/__init__.py b/raven/contrib/celery/__init__.py index 68a8fed05..f477c4089 100644 --- a/raven/contrib/celery/__init__.py +++ b/raven/contrib/celery/__init__.py @@ -43,33 +43,24 @@ def process_failure_signal(sender, task_id, args, kwargs, **kw): def register_logger_signal(client, logger=None, loglevel=logging.ERROR): - # logger is unused - register_logging_filter(client) - register_logger(client, loglevel) + filter_ = CeleryFilter() + if logger is None: + logger = logging.getLogger() + handler = SentryHandler(client) + handler.setLevel(loglevel) + handler.addFilter(filter_) -def register_logging_filter(client): - def sentry_logging_filter(sender, logger, loglevel, logfile, format, + def process_logger_event(sender, logger, loglevel, logfile, format, colorize, **kw): # Attempt to find an existing SentryHandler, and if it exists ensure # that the CeleryFilter is installed. # If one is found, we do not attempt to install another one. for h in logger.handlers: if type(h) == SentryHandler: - filter_ = CeleryFilter() h.addFilter(filter_) return False - after_setup_logger.connect(sentry_logging_filter, weak=False) - - -def register_logger(client, loglevel=logging.ERROR): - def sentry_logger(sender, logger, loglevel, logfile, format, - colorize, **kw): - filter_ = CeleryFilter() - handler = SentryHandler(client) - handler.setLevel(loglevel) - handler.addFilter(filter_) logger.addHandler(handler) - after_setup_logger.connect(sentry_logger, weak=False) + after_setup_logger.connect(process_logger_event, weak=False) diff --git a/raven/contrib/django/models.py b/raven/contrib/django/models.py index 1e550bd79..3db5ce6d1 100644 --- a/raven/contrib/django/models.py +++ b/raven/contrib/django/models.py @@ -209,8 +209,7 @@ def wrap_sentry(request, **kwargs): try: # Celery < 2.5? is not supported from raven.contrib.celery import ( - register_signal, register_logging_filter - ) + register_signal, register_logger_signal) except ImportError: logger.exception('Failed to install Celery error handler') else: @@ -220,7 +219,12 @@ def wrap_sentry(request, **kwargs): logger.exception('Failed to install Celery error handler') try: - register_logging_filter(client) + ga = lambda x, d=None: getattr(settings, 'SENTRY_%s' % x, d) + options = getattr(settings, 'RAVEN_CONFIG', {}) + loglevel = options.get('celery_loglevel', + ga('CELERY_LOGLEVEL', logging.ERROR)) + + register_logger_signal(client, loglevel=loglevel) except Exception: logger.exception('Failed to install Celery error handler') From 4076f5089621c5ee528be2e361ab4a1f6918a7c4 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 10 Sep 2015 11:15:18 -0500 Subject: [PATCH 082/692] Revert "Filter out celery.redirect" This reverts commit ed845e9cef5c53bf3952cebb943db1210b2386ea. --- raven/contrib/celery/__init__.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/raven/contrib/celery/__init__.py b/raven/contrib/celery/__init__.py index f477c4089..56b724745 100644 --- a/raven/contrib/celery/__init__.py +++ b/raven/contrib/celery/__init__.py @@ -14,12 +14,6 @@ class CeleryFilter(logging.Filter): def filter(self, record): - # celery.redirect is used for stdout -- kill it - # TODO(dcramer): this should be more configurable, but our default - # injections with Celery aren't super friendly towards configurability - if record.name == 'celery.redirect': - return False - # Context is fixed in Celery 3.x so use internal flag instead extra_data = getattr(record, 'data', {}) if not isinstance(extra_data, dict): From e335057772331ecc2387afcdbf648bfc00d8677c Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 10 Sep 2015 16:02:13 -0500 Subject: [PATCH 083/692] Clean up is_authenticated attributes for Flask (refs GH-655) --- raven/contrib/flask.py | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/raven/contrib/flask.py b/raven/contrib/flask.py index 237ca84bd..f44a8395f 100644 --- a/raven/contrib/flask.py +++ b/raven/contrib/flask.py @@ -149,31 +149,27 @@ def get_user_info(self, request): return try: - is_authenticated = current_user.is_authenticated() + is_authenticated = current_user.is_authenticated except AttributeError: # HACK: catch the attribute error thrown by flask-login is not attached # > current_user = LocalProxy(lambda: _request_ctx_stack.top.user) # E AttributeError: 'RequestContext' object has no attribute 'user' return {} - if is_authenticated: - user_info = { - 'is_authenticated': True, - 'is_anonymous': current_user.is_anonymous(), - 'id': current_user.get_id(), - } - - if 'SENTRY_USER_ATTRS' in current_app.config: - for attr in current_app.config['SENTRY_USER_ATTRS']: - if hasattr(current_user, attr): - user_info[attr] = getattr(current_user, attr) - else: - user_info = { - 'is_authenticated': False, - 'is_anonymous': current_user.is_anonymous(), - } + if callable(is_authenticated): + is_authenticated = is_authenticated() + + if not is_authenticated: + return {} + + user_info = { + 'id': current_user.get_id(), + } - return user_info + if 'SENTRY_USER_ATTRS' in current_app.config: + for attr in current_app.config['SENTRY_USER_ATTRS']: + if hasattr(current_user, attr): + user_info[attr] = getattr(current_user, attr) def get_http_info(self, request): """ From 24521704c6fb4509b6e12aeb31a1d74f90cce44a Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 10 Sep 2015 16:03:32 -0500 Subject: [PATCH 084/692] Handle failures with context helpers --- raven/contrib/flask.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/raven/contrib/flask.py b/raven/contrib/flask.py index f44a8395f..a418da3a1 100644 --- a/raven/contrib/flask.py +++ b/raven/contrib/flask.py @@ -215,8 +215,14 @@ def get_http_info_with_retriever(self, request, retriever=None): def before_request(self, *args, **kwargs): self.last_event_id = None - self.client.http_context(self.get_http_info(request)) - self.client.user_context(self.get_user_info(request)) + try: + self.client.http_context(self.get_http_info(request)) + except Exception as e: + self.client.logger.exception(unicode(e)) + try: + self.client.user_context(self.get_user_info(request)) + except Exception as e: + self.client.logger.exception(unicode(e)) def after_request(self, sender, response, *args, **kwargs): if self.last_event_id: From 6a84d4856426c4537ece5474d48a846736c4e06a Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 10 Sep 2015 16:07:29 -0500 Subject: [PATCH 085/692] Remove is_authenticated property (excl. Tornado) --- raven/contrib/django/client.py | 3 +-- raven/contrib/zope/__init__.py | 9 +++++---- tests/contrib/django/tests.py | 2 -- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/raven/contrib/django/client.py b/raven/contrib/django/client.py index 8ee88df11..9dbb82ddf 100644 --- a/raven/contrib/django/client.py +++ b/raven/contrib/django/client.py @@ -35,11 +35,10 @@ class DjangoClient(Client): def get_user_info(self, user): if not user.is_authenticated(): - return {'is_authenticated': False} + return {} user_info = { 'id': user.pk, - 'is_authenticated': True, } if hasattr(user, 'email'): diff --git a/raven/contrib/zope/__init__.py b/raven/contrib/zope/__init__.py index 02af422e9..4550ece61 100644 --- a/raven/contrib/zope/__init__.py +++ b/raven/contrib/zope/__init__.py @@ -96,11 +96,12 @@ def emit(self, record): setattr(record, 'request', http) user = request.get('AUTHENTICATED_USER', None) if user is not None and user != nobody: - user_dict = dict(id=user.getId(), - is_authenticated=True, - email=user.getProperty('email') or '') + user_dict = { + 'id': user.getId(), + 'email': user.getProperty('email') or '', + } else: - user_dict = {'is_authenticated': False} + user_dict = {} setattr(record, 'user', user_dict) except (AttributeError, KeyError): logger.warning('Could not extract data from request', exc_info=True) diff --git a/tests/contrib/django/tests.py b/tests/contrib/django/tests.py index 98a93cd8a..de4b86697 100644 --- a/tests/contrib/django/tests.py +++ b/tests/contrib/django/tests.py @@ -193,7 +193,6 @@ def test_user_info(self): assert 'user' in event user_info = event['user'] assert user_info == { - 'is_authenticated': True, 'username': user.username, 'id': user.id, 'email': user.email, @@ -217,7 +216,6 @@ class MyUser(AbstractBaseUser): ) user_info = self.raven.get_user_info(user) assert user_info == { - 'is_authenticated': True, 'username': user.username, 'id': user.id, 'email': user.email, From 7393d398326388bb9fddb6e9f5a3210106001ccd Mon Sep 17 00:00:00 2001 From: Antonis Kalipetis Date: Mon, 14 Sep 2015 17:37:05 +0300 Subject: [PATCH 086/692] Fixed dependencies for tests --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 013a97e32..86fcb4052 100755 --- a/setup.py +++ b/setup.py @@ -65,7 +65,7 @@ 'pytz', 'pytest>=2.7.0,<2.8.0', 'pytest-cov>=1.4', - 'pytest-django>=2.8.0,<2.7.0', + 'pytest-django>=2.7.0,<2.8.0', 'pytest-timeout==0.4', 'requests', 'tornado', From f19287da47d09e75a945c44f79b1e08e3cb0a8a5 Mon Sep 17 00:00:00 2001 From: Antonis Kalipetis Date: Mon, 14 Sep 2015 17:38:18 +0300 Subject: [PATCH 087/692] Added test for correct git SHA The default behavior of `fetch_git_sha` should be to fetch the SHA of the active branch - if any - and not the master branch. --- tests/versioning/tests.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/versioning/tests.py b/tests/versioning/tests.py index cef371092..96eb06032 100644 --- a/tests/versioning/tests.py +++ b/tests/versioning/tests.py @@ -2,6 +2,7 @@ import os.path import pytest +import subprocess from django.conf import settings @@ -19,6 +20,9 @@ def test_fetch_git_sha(): assert result is not None assert len(result) == 40 assert isinstance(result, six.string_types) + assert result == subprocess.check_output( + 'git rev-parse --verify HEAD', shell=True, cwd=settings.PROJECT_ROOT + ).strip() def test_fetch_package_version(): From dfb7e71c40cf7ab81346e5be69935f8d4314312a Mon Sep 17 00:00:00 2001 From: Antonis Kalipetis Date: Mon, 14 Sep 2015 17:47:06 +0300 Subject: [PATCH 088/692] Improved default head guessing for `fetch_git_sha` --- raven/versioning.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/raven/versioning.py b/raven/versioning.py index 616efd81a..6d2aa9c63 100644 --- a/raven/versioning.py +++ b/raven/versioning.py @@ -12,11 +12,20 @@ __all__ = ('fetch_git_sha', 'fetch_package_version') -def fetch_git_sha(path, head='master'): +def fetch_git_sha(path, head=None): """ >>> fetch_git_sha(os.path.dirname(__file__)) """ - revision_file = os.path.join(path, '.git', 'refs', 'heads', head) + if not head: + try: + head = open(os.path.join(path, '.git', 'HEAD'), 'r') + revision_file = os.path.join( + path, '.git', *head.read().strip().split(' ')[1].split('/') + ) + finally: + head.close() + else: + revision_file = os.path.join(path, '.git', 'refs', 'heads', head) if not os.path.exists(revision_file): if not os.path.exists(os.path.join(path, '.git')): raise InvalidGitRepository('%s does not seem to be the root of a git repository' % (path,)) From 2ce05c72b4892b6a06a3e0970149b2ca0f5f26b0 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 14 Sep 2015 11:08:02 -0700 Subject: [PATCH 089/692] Improve error handling for fetch_git_sha --- raven/versioning.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/raven/versioning.py b/raven/versioning.py index 6d2aa9c63..df72fa6de 100644 --- a/raven/versioning.py +++ b/raven/versioning.py @@ -17,15 +17,22 @@ def fetch_git_sha(path, head=None): >>> fetch_git_sha(os.path.dirname(__file__)) """ if not head: + head_path = os.path.join(path, '.git', 'HEAD') + if not os.path.exists(head_path): + raise InvalidGitRepository('Cannot identify HEAD for git repository at %s' % (path,)) + + with open(head_path, 'r') as fp: + head = fp.read().strip() + try: - head = open(os.path.join(path, '.git', 'HEAD'), 'r') revision_file = os.path.join( - path, '.git', *head.read().strip().split(' ')[1].split('/') + path, '.git', *head.split(' ')[1].split('/') ) - finally: - head.close() + except IndexError: + raise InvalidGitRepository('Unparseable HEAD (%r) for git repository %s' % (head, path,)) else: revision_file = os.path.join(path, '.git', 'refs', 'heads', head) + if not os.path.exists(revision_file): if not os.path.exists(os.path.join(path, '.git')): raise InvalidGitRepository('%s does not seem to be the root of a git repository' % (path,)) From b581d07db36617345dc36ed3368189431510ca79 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Wed, 16 Sep 2015 10:45:41 -0700 Subject: [PATCH 090/692] Changes for 5.7.0 --- CHANGES | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGES b/CHANGES index dd27a10c7..1c08e813a 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,15 @@ +Version 5.7.0 +------------- + +* Reverted changes to Celery which incorrectly caused some configurations + to log unwanted messages. +* Improved behavior in ``fetch_git_sha``. +* Removed ``is_authenticated`` property from most integrations. +* Better error handling for errors within Flask context. +* Support for new versions of Flask-Login. +* Update Tornado support for modern versions. +* Update stacktrace truncation code to match current versions of Sentry server. + Version 5.6.0 ------------- From 97ad85d594986546bee2c8f97b1188b9abe8a227 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Wed, 16 Sep 2015 10:48:06 -0700 Subject: [PATCH 091/692] 5.8.0.dev0 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 86fcb4052..de8aedffb 100755 --- a/setup.py +++ b/setup.py @@ -96,7 +96,7 @@ def run_tests(self): setup( name='raven', - version='5.7.0.dev0', + version='5.8.0.dev0', author='David Cramer', author_email='dcramer@gmail.com', url='https://github.com/getsentry/raven-python', From 3e5ae0c9bf3bba007966fddb641cd9c0ed70fe09 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Wed, 16 Sep 2015 14:55:53 -0700 Subject: [PATCH 092/692] Python 3 compatibility (refs GH-659) --- raven/contrib/flask.py | 5 +++-- raven/utils/stacks.py | 2 +- tests/contrib/zerorpc/tests.py | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/raven/contrib/flask.py b/raven/contrib/flask.py index a418da3a1..444e6849e 100644 --- a/raven/contrib/flask.py +++ b/raven/contrib/flask.py @@ -29,6 +29,7 @@ from raven.handlers.logging import SentryHandler from raven.utils import six from raven.utils.compat import _urlparse +from raven.utils.encoding import to_unicode from raven.utils.imports import import_string from raven.utils.wsgi import get_headers, get_environ @@ -218,11 +219,11 @@ def before_request(self, *args, **kwargs): try: self.client.http_context(self.get_http_info(request)) except Exception as e: - self.client.logger.exception(unicode(e)) + self.client.logger.exception(to_unicode(e)) try: self.client.user_context(self.get_user_info(request)) except Exception as e: - self.client.logger.exception(unicode(e)) + self.client.logger.exception(to_unicode(e)) def after_request(self, sender, response, *args, **kwargs): if self.last_event_id: diff --git a/raven/utils/stacks.py b/raven/utils/stacks.py index 75a88d392..f458273f8 100644 --- a/raven/utils/stacks.py +++ b/raven/utils/stacks.py @@ -213,7 +213,7 @@ def slim_frame_data(frames, frame_allowance=25): half_max = frame_allowance / 2 - for n in xrange(half_max, frames_len - half_max): + for n in range(half_max, frames_len - half_max): # remove heavy components frames[n].pop('vars', None) frames[n].pop('pre_context', None) diff --git a/tests/contrib/zerorpc/tests.py b/tests/contrib/zerorpc/tests.py index 1228c9f61..1df7c285f 100644 --- a/tests/contrib/zerorpc/tests.py +++ b/tests/contrib/zerorpc/tests.py @@ -65,7 +65,7 @@ def test_zerorpc_middleware_with_pushpull(self): self._client.choice([]) - for attempt in xrange(0, 10): + for attempt in range(0, 10): gevent.sleep(0.1) if len(self._sentry.events): exc = self._sentry.events[0]['exception'] From d0f5f5ce69b29e45c92c606e04c92cc766c9ca5c Mon Sep 17 00:00:00 2001 From: David Cramer Date: Wed, 16 Sep 2015 14:59:28 -0700 Subject: [PATCH 093/692] Handle SHA as HEAD --- raven/versioning.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/raven/versioning.py b/raven/versioning.py index df72fa6de..6cd24d26c 100644 --- a/raven/versioning.py +++ b/raven/versioning.py @@ -24,12 +24,11 @@ def fetch_git_sha(path, head=None): with open(head_path, 'r') as fp: head = fp.read().strip() - try: - revision_file = os.path.join( - path, '.git', *head.split(' ')[1].split('/') - ) - except IndexError: - raise InvalidGitRepository('Unparseable HEAD (%r) for git repository %s' % (head, path,)) + if '/' in head: + head = head.split(' ')[1].split('/') + revision_file = os.path.join( + path, '.git', head + ) else: revision_file = os.path.join(path, '.git', 'refs', 'heads', head) From acb27abb83e9ec4b88572d411ac5ff1e7eee0d84 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Wed, 16 Sep 2015 15:08:35 -0700 Subject: [PATCH 094/692] Ensure int is passed for range --- hooks/pre-commit.flake8 | 3 +++ raven/utils/stacks.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/hooks/pre-commit.flake8 b/hooks/pre-commit.flake8 index d58db4d62..c49623f0c 100755 --- a/hooks/pre-commit.flake8 +++ b/hooks/pre-commit.flake8 @@ -2,6 +2,7 @@ import glob import os +import six import sys os.environ['PYFLAKES_NODOCTEST'] = '1' @@ -25,6 +26,8 @@ def main(): _, files_modified, _ = run(gitcmd) + files_modified = [six.text_type(x) for x in files_modified] + # remove non-py files and files which no longer exist files_modified = filter( lambda x: x.endswith('.py') and os.path.exists(x), diff --git a/raven/utils/stacks.py b/raven/utils/stacks.py index f458273f8..8dc24a1a3 100644 --- a/raven/utils/stacks.py +++ b/raven/utils/stacks.py @@ -211,7 +211,7 @@ def slim_frame_data(frames, frame_allowance=25): if frames_len <= frame_allowance: return frames - half_max = frame_allowance / 2 + half_max = int(frame_allowance / 2) for n in range(half_max, frames_len - half_max): # remove heavy components From e29c6e9b8b60fd8bfe4179626925cd00d1c6e3c2 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Wed, 16 Sep 2015 15:17:30 -0700 Subject: [PATCH 095/692] Various fixes for Python3 / versioning --- hooks/pre-commit.flake8 | 2 +- raven/versioning.py | 20 ++++++++++++-------- tests/versioning/tests.py | 2 +- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/hooks/pre-commit.flake8 b/hooks/pre-commit.flake8 index c49623f0c..769326803 100755 --- a/hooks/pre-commit.flake8 +++ b/hooks/pre-commit.flake8 @@ -2,7 +2,6 @@ import glob import os -import six import sys os.environ['PYFLAKES_NODOCTEST'] = '1' @@ -21,6 +20,7 @@ def main(): from flake8.main import DEFAULT_CONFIG from flake8.engine import get_style_guide from flake8.hooks import run + from raven.utils import six gitcmd = "git diff-index --cached --name-only HEAD" diff --git a/raven/versioning.py b/raven/versioning.py index 6cd24d26c..c8bf03625 100644 --- a/raven/versioning.py +++ b/raven/versioning.py @@ -1,12 +1,15 @@ from __future__ import absolute_import import os.path + try: import pkg_resources except ImportError: # pkg_resource is not available on Google App Engine pkg_resources = None +from raven.utils import six + from .exceptions import InvalidGitRepository __all__ = ('fetch_git_sha', 'fetch_package_version') @@ -22,13 +25,14 @@ def fetch_git_sha(path, head=None): raise InvalidGitRepository('Cannot identify HEAD for git repository at %s' % (path,)) with open(head_path, 'r') as fp: - head = fp.read().strip() - - if '/' in head: - head = head.split(' ')[1].split('/') - revision_file = os.path.join( - path, '.git', head - ) + head = six.text_type(fp.read()).strip() + + if head.startswith('ref: '): + revision_file = os.path.join( + path, '.git', *head.rsplit(' ', 1)[-1].split('/') + ) + else: + revision_file = os.path.join(path, '.git', head) else: revision_file = os.path.join(path, '.git', 'refs', 'heads', head) @@ -39,7 +43,7 @@ def fetch_git_sha(path, head=None): fh = open(revision_file, 'r') try: - return fh.read().strip() + return six.text_type(fh.read()).strip() finally: fh.close() diff --git a/tests/versioning/tests.py b/tests/versioning/tests.py index 96eb06032..594969129 100644 --- a/tests/versioning/tests.py +++ b/tests/versioning/tests.py @@ -22,7 +22,7 @@ def test_fetch_git_sha(): assert isinstance(result, six.string_types) assert result == subprocess.check_output( 'git rev-parse --verify HEAD', shell=True, cwd=settings.PROJECT_ROOT - ).strip() + ).decode('latin1').strip() def test_fetch_package_version(): From af2e4626218a500257fc20147c81a21e79ff8572 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Wed, 16 Sep 2015 15:02:52 -0700 Subject: [PATCH 096/692] Changes for 5.7.1 --- CHANGES | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES b/CHANGES index 1c08e813a..181038f63 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,9 @@ +Version 5.7.1 +------------- + +* Correctly handle SHAs in .git/HEAD. +* Fixed several cases of invalid Python3 syntax. + Version 5.7.0 ------------- From 31f22e8b412ae9d5f5cb096fb36f3398a2f35bbb Mon Sep 17 00:00:00 2001 From: Petri Lehtinen Date: Fri, 18 Sep 2015 09:05:01 +0300 Subject: [PATCH 097/692] Client.capture(): tags should be a dict --- docs/api.rst | 2 +- raven/base.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index 62d78004a..94a7633f2 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -50,7 +50,7 @@ Client :param extra: a dictionary of additional standard metadata. :param stack: If set to `True` a stack frame is recorded together with the event. - :param tags: list of extra tags + :param tags: dict of extra tags :param kwargs: extra keyword arguments are handled specific to the reported event type. :return: a tuple with a 32-length string identifying this event diff --git a/raven/base.py b/raven/base.py index 7b4a0d9c6..b6a0f9215 100644 --- a/raven/base.py +++ b/raven/base.py @@ -509,7 +509,7 @@ def capture(self, event_type, data=None, date=None, time_spent=None, event (in milliseconds) :param extra: a dictionary of additional standard metadata :param stack: a stacktrace for the event - :param tags: list of extra tags + :param tags: dict of extra tags :return: a tuple with a 32-length string identifying this event """ From d3b2fb63a44fa46aa420610bae358b06412ca27f Mon Sep 17 00:00:00 2001 From: Matt Robenolt Date: Fri, 18 Sep 2015 15:55:02 -0700 Subject: [PATCH 098/692] Support `fingerprint` through logging handler Fixes GH-661 --- raven/handlers/logging.py | 2 +- tests/handlers/logging/tests.py | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/raven/handlers/logging.py b/raven/handlers/logging.py index fa7599652..4af0fba4c 100644 --- a/raven/handlers/logging.py +++ b/raven/handlers/logging.py @@ -116,7 +116,7 @@ def _emit(self, record, **kwargs): continue if k.startswith('_'): continue - if '.' not in k and k not in ('culprit', 'server_name'): + if '.' not in k and k not in ('culprit', 'server_name', 'fingerprint'): extra[k] = v else: data[k] = v diff --git a/tests/handlers/logging/tests.py b/tests/handlers/logging/tests.py index a2dac3088..239224e37 100644 --- a/tests/handlers/logging/tests.py +++ b/tests/handlers/logging/tests.py @@ -215,6 +215,30 @@ def test_tags_on_error(self): event = self.client.events.pop(0) assert event['tags'] == {'foo': 'bar'} + def test_fingerprint_on_event(self): + record = self.make_record('Message', extra={'fingerprint': ['foo']}) + self.handler.emit(record) + + self.assertEqual(len(self.client.events), 1) + event = self.client.events.pop(0) + assert event['fingerprint'] == ['foo'] + + def test_culprit_on_event(self): + record = self.make_record('Message', extra={'culprit': 'foo'}) + self.handler.emit(record) + + self.assertEqual(len(self.client.events), 1) + event = self.client.events.pop(0) + assert event['culprit'] == 'foo' + + def test_server_name_on_event(self): + record = self.make_record('Message', extra={'server_name': 'foo'}) + self.handler.emit(record) + + self.assertEqual(len(self.client.events), 1) + event = self.client.events.pop(0) + assert event['server_name'] == 'foo' + class LoggingHandlerTest(TestCase): def test_client_arg(self): From 6a60ed8f8434f42b8205d6fe215b06565f8dad99 Mon Sep 17 00:00:00 2001 From: Matt Robenolt Date: Fri, 18 Sep 2015 15:59:20 -0700 Subject: [PATCH 099/692] Add docs for logging fingerprint parameter --- docs/integrations/logging.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/integrations/logging.rst b/docs/integrations/logging.rst index 602f13c6c..da703aa6e 100644 --- a/docs/integrations/logging.rst +++ b/docs/integrations/logging.rst @@ -111,6 +111,7 @@ within your ``extra`` clause:: logger.error('There was some crazy error', exc_info=True, extra={ # Optionally you can pass additional arguments to specify request info 'culprit': 'my.view.name', + 'fingerprint': [...], 'data': { # You may specify any values here and Sentry will log and output them From dd30f185bb3c8664d7fff3633be8f77a7c58710c Mon Sep 17 00:00:00 2001 From: Matt Robenolt Date: Fri, 18 Sep 2015 16:00:57 -0700 Subject: [PATCH 100/692] Changes for 5.7.2 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 181038f63..ef071672e 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 5.7.2 +------------- + +* Handle passing ``fingerprint`` through logging handler. + Version 5.7.1 ------------- From fb671d5d8ef444b12378bb2ecd1db78730539bbc Mon Sep 17 00:00:00 2001 From: David Cramer Date: Tue, 22 Sep 2015 11:59:18 -0700 Subject: [PATCH 101/692] Expand / fix wizard configurations --- docs/sentry-doc-config.json | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/sentry-doc-config.json b/docs/sentry-doc-config.json index 6b184127d..30f63d3d9 100644 --- a/docs/sentry-doc-config.json +++ b/docs/sentry-doc-config.json @@ -6,7 +6,7 @@ "is_framework": false, "doc_link": "installation", "snippets": [ - "installation#installation", + "index#installation", "usage#capture-an-error", "usage#reporting-an-event" ] @@ -27,6 +27,7 @@ "is_framework": true, "doc_link": "integrations/bottle", "snippets": [ + "index#installation", "integrations/bottle#setup", "integrations/bottle#usage" ] @@ -37,6 +38,7 @@ "is_framework": true, "doc_link": "integrations/celery", "snippets": [ + "index#installation", "integrations/celery" ] }, @@ -46,6 +48,7 @@ "is_framework": true, "doc_link": "integrations/django", "snippets": [ + "index#installation", "integrations/django#setup" ] }, @@ -55,6 +58,7 @@ "is_framework": true, "doc_link": "integrations/pylons", "snippets": [ + "index#installation", "integrations/pylons#wsgi-middleware", "integrations/pylons#logger-setup" ] @@ -65,6 +69,7 @@ "is_framework": true, "doc_link": "integrations/pyramid", "snippets": [ + "index#installation", "integrations/pyramid#pastedeploy-filter", "integrations/pyramid#logger-setup" ] @@ -75,6 +80,7 @@ "is_framework": true, "doc_link": "integrations/tornado", "snippets": [ + "index#installation", "integrations/tornado#setup", "integrations/tornado#usage" ] From 5563919261986b7eb2415510dbca4a2d00a4ba74 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Tue, 22 Sep 2015 18:07:14 -0700 Subject: [PATCH 102/692] Swap in variable placeholders --- docs/integrations/pyramid.rst | 4 ++-- docs/integrations/wsgi.rst | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/integrations/pyramid.rst b/docs/integrations/pyramid.rst index b879a47f7..b54dc4a62 100644 --- a/docs/integrations/pyramid.rst +++ b/docs/integrations/pyramid.rst @@ -16,7 +16,7 @@ A filter factory for `PasteDeploy `_ exists to a [filter:raven] use = egg:raven#raven - dsn = http://public:secret@example.com/1 + dsn = ___DSN___ include_paths = my.package, my.other.package exclude_paths = my.package.crud @@ -58,7 +58,7 @@ Add the following lines to your project's `.ini` file to setup `SentryHandler`: [handler_sentry] class = raven.handlers.logging.SentryHandler - args = ('http://public:secret@example.com/1',) + args = ('___DSN___',) level = WARNING formatter = generic diff --git a/docs/integrations/wsgi.rst b/docs/integrations/wsgi.rst index 25fa38458..c72c29338 100644 --- a/docs/integrations/wsgi.rst +++ b/docs/integrations/wsgi.rst @@ -10,7 +10,7 @@ Raven includes a simple to use WSGI middleware. application = Sentry( application, - Client('http://public:secret@example.com/1') + Client('___DSN___') ) .. note:: Many frameworks will not propagate exceptions to the underlying WSGI middleware by default. From 9bd631909c2fa30eaec39098f7be77747989591f Mon Sep 17 00:00:00 2001 From: Dan Bravender Date: Wed, 23 Sep 2015 11:53:58 -0400 Subject: [PATCH 103/692] Pass the required parsed_url parameter back to HTTPTransport --- raven/transport/twisted.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/transport/twisted.py b/raven/transport/twisted.py index fb04272d2..c087718a0 100644 --- a/raven/transport/twisted.py +++ b/raven/transport/twisted.py @@ -30,7 +30,7 @@ def __init__(self, parsed_url, *args, **kwargs): if not has_twisted: raise ImportError('TwistedHTTPTransport requires twisted.web.') - super(TwistedHTTPTransport, self).__init__(*args, **kwargs) + super(TwistedHTTPTransport, self).__init__(parsed_url, *args, **kwargs) # Import reactor as late as possible. from twisted.internet import reactor From 8c601978c21312d416e7e0157dfb2a75be609c4b Mon Sep 17 00:00:00 2001 From: Matt Robenolt Date: Wed, 23 Sep 2015 12:44:51 -0700 Subject: [PATCH 104/692] doc_links should have trailing slashes --- docs/sentry-doc-config.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/sentry-doc-config.json b/docs/sentry-doc-config.json index 30f63d3d9..acf1167ad 100644 --- a/docs/sentry-doc-config.json +++ b/docs/sentry-doc-config.json @@ -4,7 +4,7 @@ "name": "Python", "client_lib": "raven-python", "is_framework": false, - "doc_link": "installation", + "doc_link": "installation/", "snippets": [ "index#installation", "usage#capture-an-error", @@ -15,7 +15,7 @@ "name": "Flask", "client_lib": "raven-python", "is_framework": true, - "doc_link": "integrations/flask", + "doc_link": "integrations/flask/", "snippets": [ "integrations/flask#installation", "integrations/flask#setup" @@ -25,7 +25,7 @@ "name": "Bottle", "client_lib": "raven-python", "is_framework": true, - "doc_link": "integrations/bottle", + "doc_link": "integrations/bottle/", "snippets": [ "index#installation", "integrations/bottle#setup", @@ -36,7 +36,7 @@ "name": "Celery", "client_lib": "raven-python", "is_framework": true, - "doc_link": "integrations/celery", + "doc_link": "integrations/celery/", "snippets": [ "index#installation", "integrations/celery" @@ -46,7 +46,7 @@ "name": "Django", "client_lib": "raven-python", "is_framework": true, - "doc_link": "integrations/django", + "doc_link": "integrations/django/", "snippets": [ "index#installation", "integrations/django#setup" @@ -56,7 +56,7 @@ "name": "Pylons", "client_lib": "raven-python", "is_framework": true, - "doc_link": "integrations/pylons", + "doc_link": "integrations/pylons/", "snippets": [ "index#installation", "integrations/pylons#wsgi-middleware", @@ -67,7 +67,7 @@ "name": "Pyramid", "client_lib": "raven-python", "is_framework": true, - "doc_link": "integrations/pyramid", + "doc_link": "integrations/pyramid/", "snippets": [ "index#installation", "integrations/pyramid#pastedeploy-filter", @@ -78,7 +78,7 @@ "name": "Tornado", "client_lib": "raven-python", "is_framework": true, - "doc_link": "integrations/tornado", + "doc_link": "integrations/tornado/", "snippets": [ "index#installation", "integrations/tornado#setup", From c1f6ce5d5f0de8ec11e0739b4e040d550afeb0af Mon Sep 17 00:00:00 2001 From: Matt Robenolt Date: Wed, 23 Sep 2015 12:46:02 -0700 Subject: [PATCH 105/692] Flask needs installation --- docs/sentry-doc-config.json | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/sentry-doc-config.json b/docs/sentry-doc-config.json index acf1167ad..c7cae3b04 100644 --- a/docs/sentry-doc-config.json +++ b/docs/sentry-doc-config.json @@ -17,6 +17,7 @@ "is_framework": true, "doc_link": "integrations/flask/", "snippets": [ + "index#installation", "integrations/flask#installation", "integrations/flask#setup" ] From d07ce65b1b1bcd9cce9e77dbe525f90be96164d5 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 24 Sep 2015 12:26:21 -0700 Subject: [PATCH 106/692] Remove UUID serializer (same as default behavior) Refs GH-664 --- raven/utils/serializer/base.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/raven/utils/serializer/base.py b/raven/utils/serializer/base.py index d9997e622..ab569dcea 100644 --- a/raven/utils/serializer/base.py +++ b/raven/utils/serializer/base.py @@ -9,7 +9,6 @@ from __future__ import absolute_import import itertools -import uuid import types from raven.utils import six @@ -76,13 +75,6 @@ def serialize(self, value, **kwargs): ) -class UUIDSerializer(Serializer): - types = (uuid.UUID,) - - def serialize(self, value, **kwargs): - return repr(value) - - class DictSerializer(Serializer): types = (dict,) @@ -178,7 +170,6 @@ def serialize(self, value, **kwargs): # register all serializers, order matters serialization_manager.register(IterableSerializer) -serialization_manager.register(UUIDSerializer) serialization_manager.register(DictSerializer) serialization_manager.register(UnicodeSerializer) serialization_manager.register(StringSerializer) From d9a0695d13a947b7e144a82deee1d7bb09851299 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 29 Sep 2015 23:49:23 +0200 Subject: [PATCH 107/692] Changed the docs to mention platforms. --- docs/sentry-doc-config.json | 56 ++++++++++++++++--------------------- 1 file changed, 24 insertions(+), 32 deletions(-) diff --git a/docs/sentry-doc-config.json b/docs/sentry-doc-config.json index c7cae3b04..6c407cf29 100644 --- a/docs/sentry-doc-config.json +++ b/docs/sentry-doc-config.json @@ -1,86 +1,78 @@ { - "wizards": { + "platforms": { "python": { "name": "Python", - "client_lib": "raven-python", - "is_framework": false, + "type": "language", "doc_link": "installation/", - "snippets": [ + "wizard": [ "index#installation", "usage#capture-an-error", "usage#reporting-an-event" ] }, - "python-flask": { + "python.flask": { "name": "Flask", - "client_lib": "raven-python", - "is_framework": true, + "type": "framework", "doc_link": "integrations/flask/", - "snippets": [ + "wizard": [ "index#installation", "integrations/flask#installation", "integrations/flask#setup" ] }, - "python-bottle": { + "python.bottle": { "name": "Bottle", - "client_lib": "raven-python", - "is_framework": true, + "type": "framework", "doc_link": "integrations/bottle/", - "snippets": [ + "wizard": [ "index#installation", "integrations/bottle#setup", "integrations/bottle#usage" ] }, - "python-celery": { + "python.celery": { "name": "Celery", - "client_lib": "raven-python", - "is_framework": true, + "type": "library", "doc_link": "integrations/celery/", - "snippets": [ + "wizard": [ "index#installation", "integrations/celery" ] }, - "python-django": { + "python.django": { "name": "Django", - "client_lib": "raven-python", - "is_framework": true, + "type": "framework", "doc_link": "integrations/django/", - "snippets": [ + "wizard": [ "index#installation", "integrations/django#setup" ] }, - "python-pylons": { + "python.pylons": { "name": "Pylons", - "client_lib": "raven-python", - "is_framework": true, + "type": "framework", "doc_link": "integrations/pylons/", - "snippets": [ + "wizard": [ "index#installation", "integrations/pylons#wsgi-middleware", "integrations/pylons#logger-setup" ] }, - "python-pyramid": { + "python.pyramid": { "name": "Pyramid", - "client_lib": "raven-python", - "is_framework": true, + "type": "framework", "doc_link": "integrations/pyramid/", - "snippets": [ + "wizard": [ "index#installation", "integrations/pyramid#pastedeploy-filter", "integrations/pyramid#logger-setup" ] }, - "python-tornado": { + "python.tornado": { "name": "Tornado", - "client_lib": "raven-python", - "is_framework": true, + "type": "framework", "doc_link": "integrations/tornado/", - "snippets": [ + "wizard": [ "index#installation", "integrations/tornado#setup", "integrations/tornado#usage" From 060286c8bcb2a22a4832a5f87e9ef9c7d7bb1473 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 29 Sep 2015 23:50:40 +0200 Subject: [PATCH 108/692] Updated sentryext --- docs/_sentryext | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_sentryext b/docs/_sentryext index c523fdd0c..ace38d375 160000 --- a/docs/_sentryext +++ b/docs/_sentryext @@ -1 +1 @@ -Subproject commit c523fdd0cb366fed58df9be098cfc6eca572f7b2 +Subproject commit ace38d37571b53dcbf6c00128b216fd9b53ec2bf From 2fbd4a710ec1965bc9bc5863e3c01bc27716015e Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 30 Sep 2015 18:07:54 +0200 Subject: [PATCH 109/692] Fixed bad syntax on the django page --- docs/integrations/django.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/integrations/django.rst b/docs/integrations/django.rst index b5dfaf95f..5998b75da 100644 --- a/docs/integrations/django.rst +++ b/docs/integrations/django.rst @@ -2,6 +2,7 @@ Django ====== .. default-domain:: py + `Django `_ is arguably Python's most popular web framework. Support is built into Raven but needs some configuration. While older versions of Django will likely work, officially only version 1.4 and From d9d17697a983025407f838c8d95896a3222dce37 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Wed, 30 Sep 2015 11:07:29 -0700 Subject: [PATCH 110/692] Remove legacy gunicorn recommendation (fixes GH-666) --- docs/integrations/django.rst | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/docs/integrations/django.rst b/docs/integrations/django.rst index 5998b75da..df87b3de4 100644 --- a/docs/integrations/django.rst +++ b/docs/integrations/django.rst @@ -149,7 +149,7 @@ do this, you simply need to enable a Django middleware: ..., ) + MIDDLEWARE_CLASSES -It is recommended to put the middleware at the top, so that any only 404s +It is recommended to put the middleware at the top, so that any only 404s that bubbled all the way up get logged. Certain middlewares (e.g. flatpages) capture 404s and replace the response. @@ -298,20 +298,6 @@ Or, alternatively, you can just enable Sentry responses:: sentry_exception_handler(request=request) return HttpResponse('foo') - -Gunicorn -~~~~~~~~ - -If you are running Django with `gunicorn `_ and -using the ``gunicorn`` executable, instead of the ``run_gunicorn`` -management command, you will need to add a hook to gunicorn to activate -Raven:: - - from django.core.management import call_command - - def when_ready(server): - call_command('validate') - Circus ~~~~~~ From 0e5b659bc964dee6edd132bc8fab1695ef71cd05 Mon Sep 17 00:00:00 2001 From: Steve Peak Date: Sun, 4 Oct 2015 10:48:10 +0200 Subject: [PATCH 111/692] Submit coverage reports to Codecov --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index f0054318b..319eb7dec 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,11 +30,15 @@ env: install: - time ci/setup + - pip install codecov script: - "if [[ ${TRAVIS_PYTHON_VERSION} != 'pypy' ]]; then make lint; fi" - py.test tests/ --cov raven --cov-report term-missing --timeout 10 +after_success: + - codecov -e DJANGO + matrix: allow_failures: - env: 'DJANGO="-e git+git://github.com/django/django.git#egg=Django"' From 0687e6a9e694e2d2ccce95c647d9b7604e05a1f4 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Tue, 6 Oct 2015 17:33:22 -0700 Subject: [PATCH 112/692] Switch to coverage command --- .travis.yml | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 319eb7dec..9e859c541 100644 --- a/.travis.yml +++ b/.travis.yml @@ -34,7 +34,7 @@ install: script: - "if [[ ${TRAVIS_PYTHON_VERSION} != 'pypy' ]]; then make lint; fi" - - py.test tests/ --cov raven --cov-report term-missing --timeout 10 + - coverage run --source=raven -m py.test tests --timeout 10 after_success: - codecov -e DJANGO diff --git a/setup.py b/setup.py index de8aedffb..6b6bf2462 100755 --- a/setup.py +++ b/setup.py @@ -55,6 +55,7 @@ tests_require = [ 'bottle', 'celery>=2.5', + 'coverage', 'Django>=1.4', 'django-celery>=2.5', 'exam>=0.5.2', @@ -64,7 +65,6 @@ 'pep8', 'pytz', 'pytest>=2.7.0,<2.8.0', - 'pytest-cov>=1.4', 'pytest-django>=2.7.0,<2.8.0', 'pytest-timeout==0.4', 'requests', From 82939e9ad23268f5d7d01c0939369e7134c7d5c0 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Tue, 6 Oct 2015 17:35:45 -0700 Subject: [PATCH 113/692] Coverage is provided by codecov --- setup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.py b/setup.py index 6b6bf2462..8a21bd043 100755 --- a/setup.py +++ b/setup.py @@ -55,7 +55,6 @@ tests_require = [ 'bottle', 'celery>=2.5', - 'coverage', 'Django>=1.4', 'django-celery>=2.5', 'exam>=0.5.2', From 531afa59fed952605d7f2ef27121f04d2a021ae2 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 8 Oct 2015 11:18:09 -0700 Subject: [PATCH 114/692] Respect include_paths when configured --- raven/contrib/django/models.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/raven/contrib/django/models.py b/raven/contrib/django/models.py index 3db5ce6d1..5172fcf1b 100644 --- a/raven/contrib/django/models.py +++ b/raven/contrib/django/models.py @@ -124,7 +124,8 @@ def get_client(client=None, reset=False): ga = lambda x, d=None: getattr(settings, 'SENTRY_%s' % x, d) options = copy.deepcopy(getattr(settings, 'RAVEN_CONFIG', {})) options.setdefault('include_paths', ga('INCLUDE_PATHS', [])) - options['include_paths'] = set(options['include_paths']) | get_installed_apps() + if not options['include_paths']: + options['include_paths'] = get_installed_apps() options.setdefault('exclude_paths', ga('EXCLUDE_PATHS')) options.setdefault('timeout', ga('TIMEOUT')) options.setdefault('name', ga('NAME')) From 0bb58608ab6ee6eebdcee2b8b39a62a1c9fc577e Mon Sep 17 00:00:00 2001 From: Ritesh Kadmawala Date: Thu, 15 Oct 2015 17:58:55 +0530 Subject: [PATCH 115/692] - Ensuring user info is returned properly if SENTRY_USER_ATTRS are provided --- raven/contrib/flask.py | 2 ++ tests/contrib/flask/tests.py | 25 +++++++++++++++++++++---- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/raven/contrib/flask.py b/raven/contrib/flask.py index 444e6849e..6cc721c00 100644 --- a/raven/contrib/flask.py +++ b/raven/contrib/flask.py @@ -172,6 +172,8 @@ def get_user_info(self, request): if hasattr(current_user, attr): user_info[attr] = getattr(current_user, attr) + return user_info + def get_http_info(self, request): """ Determine how to retrieve actual data by using request.mimetype. diff --git a/tests/contrib/flask/tests.py b/tests/contrib/flask/tests.py index f66e34a23..072cec805 100644 --- a/tests/contrib/flask/tests.py +++ b/tests/contrib/flask/tests.py @@ -27,13 +27,23 @@ class User(AnonymousUserMixin): is_active = lambda x: True is_authenticated = lambda x: True get_id = lambda x: 1 + name = 'TestUser' + def to_dict(self): + return { + 'id': self.get_id(), + 'name': self.name + } -def create_app(ignore_exceptions=None, debug=False): + +def create_app(ignore_exceptions=None, debug=False, **config): import os app = Flask(__name__) app.config['SECRET_KEY'] = os.urandom(40) + for key, value in config.items(): + app.config[key] = value + app.debug = debug if ignore_exceptions: @@ -56,10 +66,10 @@ def capture_message(): current_app.extensions['sentry'].captureMessage('Interesting') return 'World' - @app.route('/an-error-logged-in/', methods=['GET', 'POST']) + @app.route('/login/', methods=['GET', 'POST']) def login(): login_user(User()) - raise ValueError('hello world') + return "hello world" return app @@ -255,13 +265,20 @@ def test_captureMessage_sets_last_event_id(self): class FlaskLoginTest(BaseTest): + + @fixture + def app(self): + return create_app(SENTRY_USER_ATTRS=['name']) + @before def setup_login(self): self.login_manager = init_login(self.app) def test_user(self): - self.client.get('/an-error-logged-in/') + self.client.get('/login/') + self.client.get('/an-error/') event = self.raven.events.pop(0) assert event['message'] == 'ValueError: hello world' assert 'request' in event assert 'user' in event + self.assertDictEqual(event['user'], User().to_dict()) From ab9f645186d7309ba9c9e594a4e89e82e3a93919 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 15 Oct 2015 10:55:36 -0700 Subject: [PATCH 116/692] Improve unsupported DSN scheme error --- raven/conf/remote.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/raven/conf/remote.py b/raven/conf/remote.py index e462b0fc8..bcba49117 100644 --- a/raven/conf/remote.py +++ b/raven/conf/remote.py @@ -7,7 +7,7 @@ from raven.utils import six from raven.utils.urlparse import parse_qsl, urlparse -ERR_UNKNOWN_SCHEME = 'Unsupported Sentry DSN scheme: {0}' +ERR_UNKNOWN_SCHEME = 'Unsupported Sentry DSN scheme: {0} ({1})' DEFAULT_TRANSPORT = ThreadedHTTPTransport @@ -66,7 +66,7 @@ def from_string(cls, value, transport=None, transport_registry=None): transport_registry = TransportRegistry(default_transports) if not transport_registry.supported_scheme(url.scheme): - raise InvalidDsn(ERR_UNKNOWN_SCHEME.format(url.scheme)) + raise InvalidDsn(ERR_UNKNOWN_SCHEME.format(url.scheme, value)) transport = transport_registry.get_transport_cls(url.scheme) From 2e43aa70918827e37ec4a0d33bf4460825bcbd56 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Fri, 16 Oct 2015 11:49:29 -0700 Subject: [PATCH 117/692] Support HEROKU_SLUG_COMMIT for default release --- raven/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/base.py b/raven/base.py index b6a0f9215..8d037eaa6 100644 --- a/raven/base.py +++ b/raven/base.py @@ -161,7 +161,7 @@ def __init__(self, dsn=None, raise_send_errors=False, transport=None, context = {'sys.argv': sys.argv[:]} self.extra = context self.tags = o.get('tags') or {} - self.release = o.get('release') + self.release = o.get('release') or os.environ.get('HEROKU_SLUG_COMMIT') self.module_cache = ModuleProxyCache() From 9614a709d2a3c3df5ad3708e649df1fc51da6234 Mon Sep 17 00:00:00 2001 From: Axel Haustant Date: Mon, 19 Oct 2015 12:00:50 +0200 Subject: [PATCH 118/692] Try to detect version with pkg_resources first --- raven/utils/__init__.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/raven/utils/__init__.py b/raven/utils/__init__.py index 42e60f367..1714d526d 100644 --- a/raven/utils/__init__.py +++ b/raven/utils/__init__.py @@ -57,6 +57,16 @@ def varmap(func, var, context=None, name=None): def get_version_from_app(module_name, app): version = None + + # Try to pull version from pkg_resource first + # as it is able to detect version tagged with egg_info -b + if pkg_resources is not None: + # pull version from pkg_resources if distro exists + try: + return pkg_resources.get_distribution(module_name).version + except pkg_resources.DistributionNotFound: + pass + if hasattr(app, 'get_version'): version = app.get_version elif hasattr(app, '__version__'): @@ -73,13 +83,7 @@ def get_version_from_app(module_name, app): version = None if version is None: - if pkg_resources is None: - return None - # pull version from pkg_resources if distro exists - try: - version = pkg_resources.get_distribution(module_name).version - except pkg_resources.DistributionNotFound: - return None + return None if isinstance(version, (list, tuple)): version = '.'.join(map(str, version)) From dd5da1fb77f8369c232408d0ff3e3cbd762eb0e8 Mon Sep 17 00:00:00 2001 From: Matthias Feldotto Date: Mon, 19 Oct 2015 23:15:59 +0200 Subject: [PATCH 119/692] DSN configuration with environment variable --- docs/integrations/pyramid.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/integrations/pyramid.rst b/docs/integrations/pyramid.rst index b54dc4a62..1077a4c55 100644 --- a/docs/integrations/pyramid.rst +++ b/docs/integrations/pyramid.rst @@ -68,4 +68,4 @@ Add the following lines to your project's `.ini` file to setup `SentryHandler`: .. note:: You may want to setup other loggers as well. See the `Pyramid Logging Documentation `_ for more information. - +Instead of defining the DSN in the `.ini` file you can also use the environment variable ``SENTRY_DSN`` which overwrites the setting in this file. Because of a syntax check you cannot remove the ``args`` setting completely, as workaround you can define an empty list of arguments ``args = ()``. From 8038eff3232ffd1df8f14ba7aa9bbc90bce26696 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 19 Oct 2015 15:04:49 -0700 Subject: [PATCH 120/692] Add support for in_app on exception frames --- raven/base.py | 12 ++++++-- raven/contrib/django/client.py | 21 +++++--------- tests/base/tests.py | 50 ++++++++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+), 16 deletions(-) diff --git a/raven/base.py b/raven/base.py index 8d037eaa6..f258aa2be 100644 --- a/raven/base.py +++ b/raven/base.py @@ -327,8 +327,8 @@ def build_msg(self, event_type, data=None, date=None, 'stacktrace': stack_info, }) - if 'stacktrace' in data and self.include_paths: - for frame in data['stacktrace']['frames']: + if self.include_paths: + for frame in self._iter_frames(data): if frame.get('in_app') is not None: continue @@ -531,6 +531,14 @@ def is_enabled(self): """ return self.remote.is_active() + def _iter_frames(self, data): + if 'stacktrace' in data: + for frame in data['stacktrace']['frames']: + yield frame + if 'exception' in data: + for frame in data['exception']['values'][0]['stacktrace']['frames']: + yield frame + def _successful_send(self): self.state.set_success() diff --git a/raven/contrib/django/client.py b/raven/contrib/django/client.py index 9dbb82ddf..804941411 100644 --- a/raven/contrib/django/client.py +++ b/raven/contrib/django/client.py @@ -108,20 +108,13 @@ def get_data_from_request(self, request): def build_msg(self, *args, **kwargs): data = super(DjangoClient, self).build_msg(*args, **kwargs) - stacks = [ - data.get('stacktrace'), - ] - if 'exception' in data: - stacks.append(data['exception']['values'][0]['stacktrace']) - - for stacktrace in filter(bool, stacks): - for frame in stacktrace['frames']: - module = frame.get('module') - if not module: - continue - - if module.startswith('django.'): - frame['in_app'] = False + for frame in self._iter_frames(data): + module = frame.get('module') + if not module: + continue + + if module.startswith('django.'): + frame['in_app'] = False if not self.site and 'django.contrib.sites' in settings.INSTALLED_APPS: try: diff --git a/tests/base/tests.py b/tests/base/tests.py index e360b2fe3..1b1851ac5 100644 --- a/tests/base/tests.py +++ b/tests/base/tests.py @@ -455,3 +455,53 @@ def test_transport_registration(self): client = Client('sync+http://public:secret@example.com/1') assert type(client.remote.get_transport()) is HTTPTransport + + def test_marks_in_app_frames_for_stacktrace(self): + client = TempStoreClient( + include_paths=['foo'], + exclude_paths=['foo.bar'], + ) + client.captureMessage('hello', data={ + 'stacktrace': { + 'frames': [ + {'module': 'foo'}, + {'module': 'bar'}, + {'module': 'foo.bar'}, + {'module': 'foo.baz'}, + ] + } + }) + + event = client.events.pop(0) + frames = event['stacktrace']['frames'] + assert frames[0]['in_app'] + assert not frames[1]['in_app'] + assert not frames[2]['in_app'] + assert frames[3]['in_app'] + + def test_marks_in_app_frames_for_exception(self): + client = TempStoreClient( + include_paths=['foo'], + exclude_paths=['foo.bar'], + ) + client.captureMessage('hello', data={ + 'exception': { + 'values': [{ + 'stacktrace': { + 'frames': [ + {'module': 'foo'}, + {'module': 'bar'}, + {'module': 'foo.bar'}, + {'module': 'foo.baz'}, + ] + } + }] + } + }) + + event = client.events.pop(0) + frames = event['exception']['values'][0]['stacktrace']['frames'] + assert frames[0]['in_app'] + assert not frames[1]['in_app'] + assert not frames[2]['in_app'] + assert frames[3]['in_app'] From 9ef72b2cfb76b5082416742e9438e072d948b245 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 19 Oct 2015 15:06:32 -0700 Subject: [PATCH 121/692] Changes for 5.8.0 --- CHANGES | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGES b/CHANGES index ef071672e..a00128d96 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,11 @@ +Version 5.8.0 +------------- + +* Added support for detecting `release` on Heroku. +* pkg_resources is now prioritized for default version detection. +* Updated `in_app` support to include exception frames. +* Fixed support for `SENTRY_USER_ATTRS` in Flask. + Version 5.7.2 ------------- From 5a16f7f012eed90c6cbd463daf288d8645bab51e Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 19 Oct 2015 15:37:38 -0700 Subject: [PATCH 122/692] Convert input DSN values to strings (fixes GH-653) --- CHANGES | 1 + raven/conf/remote.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/CHANGES b/CHANGES index a00128d96..eeb97baf4 100644 --- a/CHANGES +++ b/CHANGES @@ -5,6 +5,7 @@ Version 5.8.0 * pkg_resources is now prioritized for default version detection. * Updated `in_app` support to include exception frames. * Fixed support for `SENTRY_USER_ATTRS` in Flask. +* Handle DSNs which are sent as unicode values in Python 2. Version 5.7.2 ------------- diff --git a/raven/conf/remote.py b/raven/conf/remote.py index bcba49117..a109e2184 100644 --- a/raven/conf/remote.py +++ b/raven/conf/remote.py @@ -5,6 +5,7 @@ from raven.exceptions import InvalidDsn from raven.transport.threaded import ThreadedHTTPTransport from raven.utils import six +from raven.utils.encoding import to_string from raven.utils.urlparse import parse_qsl, urlparse ERR_UNKNOWN_SCHEME = 'Unsupported Sentry DSN scheme: {0} ({1})' @@ -55,6 +56,7 @@ def get_public_dsn(self): @classmethod def from_string(cls, value, transport=None, transport_registry=None): + value = to_string(value) url = urlparse(value) if url.scheme not in ('http', 'https'): From 483af2d6d1f4de587fb228c573f4a0117babd7f0 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 19 Oct 2015 15:38:35 -0700 Subject: [PATCH 123/692] 5.9.0.dev0 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 8a21bd043..1e8fd6123 100755 --- a/setup.py +++ b/setup.py @@ -95,7 +95,7 @@ def run_tests(self): setup( name='raven', - version='5.8.0.dev0', + version='5.9.0.dev0', author='David Cramer', author_email='dcramer@gmail.com', url='https://github.com/getsentry/raven-python', From c0726143cbb7b10fa26dc4b4d3a959df06fb2f67 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 19 Oct 2015 21:21:26 -0700 Subject: [PATCH 124/692] Only convert DSN to string in Python 2.x (fixes GH-674) --- raven/conf/remote.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/raven/conf/remote.py b/raven/conf/remote.py index a109e2184..e720bfd9c 100644 --- a/raven/conf/remote.py +++ b/raven/conf/remote.py @@ -56,7 +56,11 @@ def get_public_dsn(self): @classmethod def from_string(cls, value, transport=None, transport_registry=None): - value = to_string(value) + # in Python 2.x sending the DSN as a unicode value will eventually + # cause issues in httplib + if not six.PY3: + value = to_string(value) + url = urlparse(value) if url.scheme not in ('http', 'https'): From 4d801cce7083d95f05a665f54df7e0274d057329 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 19 Oct 2015 21:32:41 -0700 Subject: [PATCH 125/692] Lock in lower version of coverage --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 9e859c541..9b181ff4a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,7 +30,7 @@ env: install: - time ci/setup - - pip install codecov + - pip install codecov "coverage<4" script: - "if [[ ${TRAVIS_PYTHON_VERSION} != 'pypy' ]]; then make lint; fi" From 4d34f712f2216a75e783f9f76a9dd3c08fb4839e Mon Sep 17 00:00:00 2001 From: Matt Robenolt Date: Tue, 20 Oct 2015 16:42:53 -0700 Subject: [PATCH 126/692] Fix link to complete docs --- docs/sentry-doc-config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sentry-doc-config.json b/docs/sentry-doc-config.json index 6c407cf29..9acb6d30b 100644 --- a/docs/sentry-doc-config.json +++ b/docs/sentry-doc-config.json @@ -3,7 +3,7 @@ "python": { "name": "Python", "type": "language", - "doc_link": "installation/", + "doc_link": "", "wizard": [ "index#installation", "usage#capture-an-error", From a93f8201fe08af37d204153bad3dbb20c11ca973 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Fri, 7 Aug 2015 18:08:27 -0700 Subject: [PATCH 127/692] Switch source reading to linecache --- raven/utils/stacks.py | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/raven/utils/stacks.py b/raven/utils/stacks.py index 8dc24a1a3..62791ff66 100644 --- a/raven/utils/stacks.py +++ b/raven/utils/stacks.py @@ -8,6 +8,7 @@ from __future__ import absolute_import import inspect +import linecache import re import sys import warnings @@ -46,28 +47,15 @@ def get_lines_from_file(filename, lineno, context_lines, loader=None, module_nam source = None if source is not None: source = source.splitlines() + if source is None: try: - f = open(filename, 'rb') - try: - source = f.readlines() - finally: - f.close() + source = linecache.getlines(filename) except (OSError, IOError): - pass - - if source is None: return None, None, None - encoding = 'utf8' - for line in source[:2]: - # File coding may be specified. Match pattern from PEP-263 - # (http://www.python.org/dev/peps/pep-0263/) - match = _coding_re.search(line.decode('utf8')) # let's assume utf8 - if match: - encoding = match.group(1) - break - source = [six.text_type(sline, encoding, 'replace') for sline in source] + if not source: + return None, None, None lower_bound = max(0, lineno - context_lines) upper_bound = min(lineno + 1 + context_lines, len(source)) From 8490a3e8b9c79b6e27a78585a3f23952d76e8d26 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Wed, 28 Oct 2015 09:55:28 -0700 Subject: [PATCH 128/692] Correct transport docs --- docs/transports.rst | 73 ++++++++++++++++++++++++++++----------------- 1 file changed, 45 insertions(+), 28 deletions(-) diff --git a/docs/transports.rst b/docs/transports.rst index 1db40ade7..31f030c47 100644 --- a/docs/transports.rst +++ b/docs/transports.rst @@ -3,11 +3,16 @@ Transports A transport is the mechanism in which Raven sends the HTTP request to the Sentry server. By default, Raven uses a threaded asynchronous transport, -but you can easily adjust this by modifying your ``SENTRY_DSN`` value. +but you can easily adjust this by passing your own transport class. -Transport registration is done via the URL prefix, so for example, a -synchronous transport is as simple as prefixing your ``SENTRY_DSN`` with -the ``sync+`` value. + +The transport class is passed via the ``transport`` parameter on ``Client``: + +.. code-block:: python + + from raven import Client + + Client('...', transport=TransportClass) Options are passed to transports via the querystring. @@ -27,24 +32,16 @@ For example, to increase the timeout and to disable SSL verification:: SENTRY_DSN = '___DSN___?timeout=5&verify_ssl=0' -aiohttp -------- - -Should only be used within a :pep:`3156` compatible event loops -(*asyncio* itself and others). - -:: - - SENTRY_DSN = 'aiohttp+___DSN___' - Eventlet -------- Should only be used within an Eventlet IO loop. -:: +.. code-block:: python + + from raven.transport.eventlet import EventletHTTPTransport - SENTRY_DSN = 'eventlet+___DSN___' + Client('...', transport=EventletHTTPTransport) Gevent @@ -52,9 +49,11 @@ Gevent Should only be used within a Gevent IO loop. -:: +.. code-block:: python - SENTRY_DSN = 'gevent+___DSN___' + from raven.transport.gevent import GeventHTTPTransport + + Client('...', transport=GeventHTTPTransport) Requests @@ -62,9 +61,19 @@ Requests Requires the ``requests`` library. Synchronous. -:: +.. code-block:: python + + from raven.transport.requests import RequestsHTTPTransport + + Client('...', transport=RequestsHTTPTransport) + +Alternatively, a threaded client also exists for Requests: + +.. code-block:: python - SENTRY_DSN = 'requests+___DSN___' + from raven.transport.threaded_requests import ThreadedRequestsHTTPTransport + + Client('...', transport=ThreadedRequestsHTTPTransport) Sync @@ -72,9 +81,11 @@ Sync A synchronous blocking transport. -:: +.. code-block:: python + + from raven.transport.http import HTTPTransport - SENTRY_DSN = 'sync+___DSN___' + Client('...', transport=HTTPTransport) Threaded (Default) @@ -82,9 +93,11 @@ Threaded (Default) Spawns an async worker for processing messages. -:: +.. code-block:: python + + from raven.transport.threaded import ThreadedHTTPTransport - SENTRY_DSN = 'threaded+___DSN___' + Client('...', transport=ThreadedHTTPTransport) Tornado @@ -92,9 +105,11 @@ Tornado Should only be used within a Tornado IO loop. -:: +.. code-block:: python - SENTRY_DSN = 'tornado+___DSN___' + from raven.transport.tornado import TornadoHTTPTransport + + Client('...', transport=TornadoHTTPTransport) Twisted @@ -102,6 +117,8 @@ Twisted Should only be used within a Twisted event loop. -:: +.. code-block:: twisted + + from raven.transport.twisted import TwistedHTTPTransport - SENTRY_DSN = 'twisted+___DSN___' + Client('...', transport=TwistedHTTPTransport) From 12c148e1746ff1fb6e7d3ad53f3fd57734437346 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Wed, 28 Oct 2015 13:49:32 -0700 Subject: [PATCH 129/692] Bad code block --- docs/transports.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/transports.rst b/docs/transports.rst index 31f030c47..7c0a189ac 100644 --- a/docs/transports.rst +++ b/docs/transports.rst @@ -117,7 +117,7 @@ Twisted Should only be used within a Twisted event loop. -.. code-block:: twisted +.. code-block:: python from raven.transport.twisted import TwistedHTTPTransport From de38ac6718814188ee369ce08c1614bddbd81a5a Mon Sep 17 00:00:00 2001 From: Matthias Erll Date: Fri, 30 Oct 2015 10:04:51 +0100 Subject: [PATCH 130/692] Handle non-text dictionary keys when sanitizing data. --- raven/processors.py | 2 +- tests/processors/tests.py | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/raven/processors.py b/raven/processors.py index 608b9d73b..bf4e69d61 100644 --- a/raven/processors.py +++ b/raven/processors.py @@ -94,7 +94,7 @@ def sanitize(self, key, value): if not key: # key can be a NoneType return value - key = key.lower() + key = six.text_type(key).lower() for field in self.FIELDS: if field in key: # store mask as a fixed length for security diff --git a/tests/processors/tests.py b/tests/processors/tests.py index c17ea09f7..47608887e 100644 --- a/tests/processors/tests.py +++ b/tests/processors/tests.py @@ -10,6 +10,11 @@ VARS = { 'foo': 'bar', + 'vars_dict': { + 42: 'bar', + ('foo', 'bar'): 'hello', + 'password': 'hello', + }, 'password': 'hello', 'the_secret': 'hello', 'a_password_here': 'hello', @@ -21,6 +26,7 @@ def get_stack_trace_data_real(exception_class=TypeError, **kwargs): def _will_throw_type_error(foo, **kwargs): + vars_dict = VARS['vars_dict'] password = "you should not see this" # NOQA F841 the_secret = "nor this" # NOQA F841 a_password_here = "Don't look at me!" # NOQA F841 @@ -81,6 +87,24 @@ def _check_vars_sanitized(self, vars, proc): self.assertIn(vars['foo'], ( VARS['foo'], "'%s'" % VARS['foo'], '"%s"' % VARS['foo']) ) + self.assertTrue('vars_dict' in vars) + vars_dict = vars['vars_dict'] + ref_dict = VARS['vars_dict'].copy() + ref_dict['password'] = proc.MASK + self.assertTrue(42 in vars_dict or '42' in vars_dict) + if 42 in vars_dict: + # Extra data - dictionary keys are not changed. + self.assertDictEqual(vars_dict, ref_dict) + else: + # Stack trace - dictionary keys are converted to strings. + self.assertTrue('42' in vars_dict) + self.assertIn(vars_dict['42'], "'%s'" % ref_dict[42], '"%s"' % ref_dict[42]) + self.assertTrue('("\'foo\'", "\'bar\'")' in vars_dict or "('\"foo\"', '\"bar\"')" in vars_dict) + self.assertTrue('"password"' in vars_dict or "'password'" in vars_dict) + if "'password'" in vars_dict: + self.assertEqual(vars_dict["'password'"], proc.MASK) + else: + self.assertEqual(vars_dict['"password"'], proc.MASK) self.assertTrue('password' in vars) self.assertEquals(vars['password'], proc.MASK) self.assertTrue('the_secret' in vars) From b994915a08efdbdec23b6796cadde45037c3680d Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 2 Nov 2015 12:00:59 -0800 Subject: [PATCH 131/692] Expand RQ documentation --- docs/integrations/rq.rst | 24 +++++++++++++++++------- docs/sentry-doc-config.json | 10 ++++++++++ 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/docs/integrations/rq.rst b/docs/integrations/rq.rst index d52d758f6..5adba3665 100644 --- a/docs/integrations/rq.rst +++ b/docs/integrations/rq.rst @@ -1,20 +1,30 @@ -Configuring RQ -============== +RQ +== Starting with RQ version 0.3.1, support for Sentry has been built in. Usage ----- -The simplest way is passing your ``SENTRY_DSN`` through ``rqworker``:: +RQ natively supports binding with Sentry by passing your ``SENTRY_DSN`` through ``rqworker``:: $ rqworker --sentry-dsn="___DSN___" -Custom Client -------------- -It's possible to use a custom ``Client`` object and use your own worker -process as an alternative to ``rqworker``. +Extended Setup +-------------- + +If you want to pass additional information, such as ``release``, you'll need to bind your +own instance of the Sentry ``Client``: + +.. code-block:: python + + from raven import Client + from raven.transport.http import HTTPTransport + from rq.contrib.sentry import register_sentry + + client = Client('___DSN___', transport=HTTPTransport) + register_sentry(client, worker) Please see ``rq``'s documentation for more information: http://python-rq.org/patterns/sentry/ diff --git a/docs/sentry-doc-config.json b/docs/sentry-doc-config.json index 9acb6d30b..0a606a587 100644 --- a/docs/sentry-doc-config.json +++ b/docs/sentry-doc-config.json @@ -68,6 +68,16 @@ "integrations/pyramid#logger-setup" ] }, + "python.rq": { + "name": "RQ", + "type": "framework", + "doc_link": "integrations/rq/", + "wizard": [ + "index#installation", + "integrations/rq#usage", + "integrations/rq#extended-setup" + ] + }, "python.tornado": { "name": "Tornado", "type": "framework", From 1a7fda84915f14f12c7d9c0dde6d86850645dfe2 Mon Sep 17 00:00:00 2001 From: Matt Robenolt Date: Wed, 4 Nov 2015 15:47:40 -0800 Subject: [PATCH 132/692] Ignore bodies from GET and HEAD --- raven/contrib/django/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/contrib/django/client.py b/raven/contrib/django/client.py index 804941411..38370251e 100644 --- a/raven/contrib/django/client.py +++ b/raven/contrib/django/client.py @@ -74,7 +74,7 @@ def get_data_from_request(self, request): host = get_host(request) uri = '%s://%s%s' % (scheme, host, request.path) - if request.method != 'GET': + if request.method not in ('GET', 'HEAD'): try: data = request.body except Exception: From 4fe75cb001e5337c383900f3039bd751ad2b2804 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 5 Nov 2015 03:11:18 +0100 Subject: [PATCH 133/692] Added support level --- docs/sentry-doc-config.json | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/sentry-doc-config.json b/docs/sentry-doc-config.json index 0a606a587..11525fdbd 100644 --- a/docs/sentry-doc-config.json +++ b/docs/sentry-doc-config.json @@ -1,4 +1,5 @@ { + "support_level": "production", "platforms": { "python": { "name": "Python", From cd2850e8f1f7dd815be098e25cf0d14778b2da4f Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 9 Nov 2015 00:30:16 -0800 Subject: [PATCH 134/692] Update link to docs --- README.rst | 2 +- docs/index.rst | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.rst b/README.rst index c9c6d64cb..576780118 100644 --- a/README.rst +++ b/README.rst @@ -15,7 +15,7 @@ any Python application. Resources --------- -* `Documentation `_ +* `Documentation `_ * `Bug Tracker `_ * `Code `_ * `Mailing List `_ diff --git a/docs/index.rst b/docs/index.rst index 649cba1b5..b708d193a 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -112,11 +112,11 @@ discover: Deprecation Notes ----------------- - + Milestones releases are 1.3 or 1.4, and our deprecation policy is to a two version step. For example, a feature will be deprecated in 1.3, and completely removed in 1.4. - + Resources --------- @@ -124,7 +124,7 @@ discover: Resources: -* `Documentation `_ +* `Documentation `_ * `Bug Tracker `_ * `Code `_ * `Mailing List `_ From ff47a56f79931253008299b333ab1ea0d4db1638 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 9 Nov 2015 20:53:50 +0100 Subject: [PATCH 135/692] Small fixes for the flask integration docs --- docs/integrations/flask.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/integrations/flask.rst b/docs/integrations/flask.rst index b2c95e191..438a47958 100644 --- a/docs/integrations/flask.rst +++ b/docs/integrations/flask.rst @@ -47,8 +47,8 @@ You can pass parameters in the ``init_app`` hook:: def create_app(): app = Flask(__name__) - sentry.init_app(app, dsn='http://public_key:secret_key@example.com/1', - logging=True, level=logging.ERROR) + sentry.init_app(app, dsn='___DSN___', logging=True, + level=logging.ERROR) return app Settings @@ -108,7 +108,7 @@ Log a generic message with ``captureMessage``:: sentry.captureMessage('hello, world!') -Getting the last event id +Getting The Last Event ID ------------------------- If possible, the last Sentry event ID is stored in the request context @@ -122,7 +122,7 @@ ID if have done a custom error 500 page.

The error identifier is {{ g.sentry_event_id }}

{% endif %} -Dealing with proxies +Dealing With Proxies -------------------- When your Flask application is behind a proxy such as nginx, Sentry will From d98f2c6ff7eb52a834f95be2b02d9b2fb4c2d097 Mon Sep 17 00:00:00 2001 From: Quentin Pradet Date: Tue, 10 Nov 2015 09:27:48 +0400 Subject: [PATCH 136/692] Ensure Django LOGGING example passes PEP8 --- 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 df87b3de4..f1568fbfd 100644 --- a/docs/integrations/django.rst +++ b/docs/integrations/django.rst @@ -97,7 +97,7 @@ following config can be used:: 'handlers': { 'sentry': { 'level': 'ERROR', - 'class': 'raven.contrib.django.raven_compat.handlers.SentryHandler', + 'class': 'raven.contrib.django.raven_compat.handlers.SentryHandler' }, 'console': { 'level': 'DEBUG', From 276266822a8dd39a5a0746ec8ee69cdfe42bdb6c Mon Sep 17 00:00:00 2001 From: Matt Robenolt Date: Tue, 10 Nov 2015 15:40:27 -0800 Subject: [PATCH 137/692] No www --- README.rst | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 576780118..8d5d2bfd3 100644 --- a/README.rst +++ b/README.rst @@ -4,7 +4,7 @@ Raven .. image:: https://travis-ci.org/getsentry/raven-python.svg?branch=master :target: https://travis-ci.org/getsentry/raven-python -Raven is a Python client for `Sentry `_. It provides +Raven is a Python client for `Sentry `_. It provides full out-of-the-box support for many of the popular frameworks, including Django, and Flask. Raven also includes drop-in support for any WSGI-compatible web application. diff --git a/setup.py b/setup.py index 1e8fd6123..782529229 100755 --- a/setup.py +++ b/setup.py @@ -99,7 +99,7 @@ def run_tests(self): author='David Cramer', author_email='dcramer@gmail.com', url='https://github.com/getsentry/raven-python', - description='Raven is a client for Sentry (https://www.getsentry.com)', + description='Raven is a client for Sentry (https://getsentry.com)', long_description=__doc__, packages=find_packages(exclude=("tests", "tests.*",)), zip_safe=False, From 3eddaa4dffaad3861282568f02bc8ad966be2fd8 Mon Sep 17 00:00:00 2001 From: Matt Robenolt Date: Thu, 12 Nov 2015 17:20:16 -0800 Subject: [PATCH 138/692] Log SystemExit in wsgi middleware when code != 0 SystemExit does not subclass Exception, so we don't catch this. SystemExit manifests itself (at least) from gunicorn when gunicorn attempts to gracefully shut down a worker process. See GH-675 for more background. Fixes: GH-675 --- raven/middleware.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/raven/middleware.py b/raven/middleware.py index 588bd6ff9..ca0dc2175 100644 --- a/raven/middleware.py +++ b/raven/middleware.py @@ -36,6 +36,10 @@ def __call__(self, environ, start_response): except Exception: self.handle_exception(environ) raise + except SystemExit as e: + if e.code != 0: + self.handle_exception(environ) + raise try: for event in iterable: @@ -43,6 +47,10 @@ def __call__(self, environ, start_response): except Exception: self.handle_exception(environ) raise + except SystemExit as e: + if e.code != 0: + self.handle_exception(environ) + raise finally: # wsgi spec requires iterable to call close if it exists # see http://blog.dscpl.com.au/2012/10/obligations-for-calling-close-on.html @@ -51,6 +59,10 @@ def __call__(self, environ, start_response): iterable.close() except Exception: self.handle_exception(environ) + except SystemExit as e: + if e.code != 0: + self.handle_exception(environ) + raise self.client.context.clear() def get_http_context(self, environ): From 5be739c3317bcdf6d445b622a2a688a29d67ee16 Mon Sep 17 00:00:00 2001 From: Matt Robenolt Date: Thu, 12 Nov 2015 17:41:12 -0800 Subject: [PATCH 139/692] Add tests --- tests/middleware/tests.py | 46 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/tests/middleware/tests.py b/tests/middleware/tests.py index 23e94caa4..8af14c6aa 100644 --- a/tests/middleware/tests.py +++ b/tests/middleware/tests.py @@ -32,6 +32,14 @@ def close(self): self.closed = True +class ExitingIterable(ErroringIterable): + def __init__(self, code=0): + self._code = code + + def __iter__(self): + raise SystemExit(self._code) + + class ExampleApp(object): def __init__(self, iterable): self.iterable = iterable @@ -86,3 +94,41 @@ def test_captures_error_in_iteration(self): self.assertEquals(env['SERVER_NAME'], 'localhost') self.assertTrue('SERVER_PORT' in env, env.keys()) self.assertEquals(env['SERVER_PORT'], '80') + + def test_systemexit_0_is_ignored(self): + iterable = ExitingIterable(code=0) + app = ExampleApp(iterable) + middleware = Sentry(app, client=self.client) + + response = middleware(self.request.environ, lambda *args: None) + + with self.assertRaises(SystemExit): + response = list(response) + + # TODO: this should be a separate test + self.assertTrue(iterable.closed, True) + + self.assertEquals(len(self.client.events), 0) + + def test_systemexit_is_captured(self): + iterable = ExitingIterable(code=1) + app = ExampleApp(iterable) + middleware = Sentry(app, client=self.client) + + response = middleware(self.request.environ, lambda *args: None) + + with self.assertRaises(SystemExit): + response = list(response) + + # TODO: this should be a separate test + self.assertTrue(iterable.closed, True) + + self.assertEquals(len(self.client.events), 1) + event = self.client.events.pop(0) + + assert 'exception' in event + exc = event['exception']['values'][0] + self.assertEquals(exc['type'], 'SystemExit') + self.assertEquals(exc['value'], '1') + self.assertEquals(event['level'], logging.ERROR) + self.assertEquals(event['message'], 'SystemExit: 1') From 80b24acc33a7246658ad6142e0bb31f4f8334d6e Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 13 Nov 2015 08:32:20 +0100 Subject: [PATCH 140/692] Extension to #686 to also log keyboard interrupts. --- raven/middleware.py | 9 +++++++++ tests/middleware/tests.py | 32 +++++++++++++++++++++++++++----- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/raven/middleware.py b/raven/middleware.py index ca0dc2175..4607489c8 100644 --- a/raven/middleware.py +++ b/raven/middleware.py @@ -36,6 +36,9 @@ def __call__(self, environ, start_response): except Exception: self.handle_exception(environ) raise + except KeyboardInterrupt: + self.handle_exception(environ) + raise except SystemExit as e: if e.code != 0: self.handle_exception(environ) @@ -47,6 +50,9 @@ def __call__(self, environ, start_response): except Exception: self.handle_exception(environ) raise + except KeyboardInterrupt: + self.handle_exception(environ) + raise except SystemExit as e: if e.code != 0: self.handle_exception(environ) @@ -59,6 +65,9 @@ def __call__(self, environ, start_response): iterable.close() except Exception: self.handle_exception(environ) + except KeyboardInterrupt: + self.handle_exception(environ) + raise except SystemExit as e: if e.code != 0: self.handle_exception(environ) diff --git a/tests/middleware/tests.py b/tests/middleware/tests.py index 8af14c6aa..e5b4da278 100644 --- a/tests/middleware/tests.py +++ b/tests/middleware/tests.py @@ -33,11 +33,11 @@ def close(self): class ExitingIterable(ErroringIterable): - def __init__(self, code=0): - self._code = code + def __init__(self, exc_func): + self._exc_func = exc_func def __iter__(self): - raise SystemExit(self._code) + raise self._exc_func() class ExampleApp(object): @@ -96,7 +96,7 @@ def test_captures_error_in_iteration(self): self.assertEquals(env['SERVER_PORT'], '80') def test_systemexit_0_is_ignored(self): - iterable = ExitingIterable(code=0) + iterable = ExitingIterable(lambda: SystemExit(0)) app = ExampleApp(iterable) middleware = Sentry(app, client=self.client) @@ -111,7 +111,7 @@ def test_systemexit_0_is_ignored(self): self.assertEquals(len(self.client.events), 0) def test_systemexit_is_captured(self): - iterable = ExitingIterable(code=1) + iterable = ExitingIterable(lambda: SystemExit(1)) app = ExampleApp(iterable) middleware = Sentry(app, client=self.client) @@ -132,3 +132,25 @@ def test_systemexit_is_captured(self): self.assertEquals(exc['value'], '1') self.assertEquals(event['level'], logging.ERROR) self.assertEquals(event['message'], 'SystemExit: 1') + + def test_keyboard_interrupt_is_captured(self): + iterable = ExitingIterable(lambda: KeyboardInterrupt()) + app = ExampleApp(iterable) + middleware = Sentry(app, client=self.client) + + response = middleware(self.request.environ, lambda *args: None) + + with self.assertRaises(KeyboardInterrupt): + response = list(response) + + # TODO: this should be a separate test + self.assertTrue(iterable.closed, True) + + self.assertEquals(len(self.client.events), 1) + event = self.client.events.pop(0) + + assert 'exception' in event + exc = event['exception']['values'][0] + self.assertEquals(exc['type'], 'KeyboardInterrupt') + self.assertEquals(exc['value'], '') + self.assertEquals(event['level'], logging.ERROR) From bbc7e1523c3723c5fe479494e03abb0e0400ecf1 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 13 Nov 2015 17:38:43 +0100 Subject: [PATCH 141/692] Clarified setting key. This fixes #865 --- docs/integrations/django.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/integrations/django.rst b/docs/integrations/django.rst index f1568fbfd..995eb927a 100644 --- a/docs/integrations/django.rst +++ b/docs/integrations/django.rst @@ -249,6 +249,9 @@ Additional Settings customized using this setting:: SENTRY_CELERY_LOGLEVEL = logging.INFO + + Alternatively you can use a similarly named key in ``RAVEN_CONFIG``:: + RAVEN_CONFIG = { 'CELERY_LOGLEVEL': logging.INFO } From 9db4e7aba9ab046a792e2c58a7e88b90cc3163ec Mon Sep 17 00:00:00 2001 From: Anton Blintsov Date: Sat, 21 Nov 2015 17:47:20 +0300 Subject: [PATCH 142/692] Modify client.capture_exceptions to return both decorator and context manager --- raven/base.py | 52 ++++++++++++++++++++++++++++++--------------- setup.py | 9 ++++++++ tests/base/tests.py | 41 ++++++++++++++++++++++++++++++++--- 3 files changed, 82 insertions(+), 20 deletions(-) diff --git a/raven/base.py b/raven/base.py index f258aa2be..74daf94a2 100644 --- a/raven/base.py +++ b/raven/base.py @@ -17,10 +17,14 @@ import warnings from datetime import datetime -from functools import wraps from pprint import pformat from types import FunctionType +if sys.version_info >= (3, 2): + import contextlib +else: + import contextlib2 as contextlib + import raven from raven.conf import defaults from raven.conf.remote import RemoteConfig @@ -689,10 +693,11 @@ def captureException(self, exc_info=None, **kwargs): return self.capture( 'raven.events.Exception', exc_info=exc_info, **kwargs) - def capture_exceptions(self, function_or_exceptions, **kwargs): + def capture_exceptions(self, function_or_exceptions=None, **kwargs): """ - Wrap a function in try/except and automatically call ``.captureException`` - if it raises an exception, then the exception is reraised. + Wrap a function or code block in try/except and automatically call + ``.captureException`` if it raises an exception, then the exception + is reraised. By default, it will capture ``Exception`` @@ -700,28 +705,41 @@ def capture_exceptions(self, function_or_exceptions, **kwargs): >>> def foo(): >>> raise Exception() + >>> with client.capture_exceptions(): + >>> raise Exception() + You can also specify exceptions to be caught specifically >>> @client.capture_exceptions((IOError, LookupError)) >>> def bar(): >>> ... + >>> with client.capture_exceptions((IOError, LookupError)): + >>> ... + ``kwargs`` are passed through to ``.captureException``. """ - def make_decorator(exceptions): - def decorator(func): - @wraps(func) - def wrapper(*funcargs, **funckwargs): - try: - return func(*funcargs, **funckwargs) - except exceptions: - self.captureException(**kwargs) - raise - return wrapper - return decorator + function = None + exceptions = (Exception,) if isinstance(function_or_exceptions, FunctionType): - return make_decorator((Exception,))(function_or_exceptions) - return make_decorator(function_or_exceptions) + function = function_or_exceptions + elif function_or_exceptions is not None: + exceptions = function_or_exceptions + + # In python3.2 contextmanager acts both as contextmanager and decorator + @contextlib.contextmanager + def make_decorator(exceptions): + try: + yield + except exceptions: + self.captureException(**kwargs) + raise + + decorator = make_decorator(exceptions) + + if function: + return decorator(function) + return decorator def captureQuery(self, query, params=(), engine=None, **kwargs): """ diff --git a/setup.py b/setup.py index 782529229..258910c6e 100755 --- a/setup.py +++ b/setup.py @@ -24,6 +24,10 @@ from setuptools.command.test import test as TestCommand import sys +install_requires = [ + 'contextlib2', +] + setup_requires = [ 'pytest', ] @@ -52,6 +56,10 @@ unittest2_requires = [] webpy_tests_requires = [] + # If it's python3.2 or greater, don't use contextlib backport + if sys.version_info[1] >= 2: + install_requires.remove('contextlib2') + tests_require = [ 'bottle', 'celery>=2.5', @@ -110,6 +118,7 @@ def run_tests(self): }, license='BSD', tests_require=tests_require, + install_requires=install_requires, cmdclass={'test': PyTest}, include_package_data=True, entry_points={ diff --git a/tests/base/tests.py b/tests/base/tests.py index 1b1851ac5..3cf290b39 100644 --- a/tests/base/tests.py +++ b/tests/base/tests.py @@ -323,9 +323,9 @@ def test2(): self.assertEquals(exc['type'], 'DecoratorTestException') self.assertEquals(exc['module'], self.DecoratorTestException.__module__) stacktrace = exc['stacktrace'] - # this is a wrapped function so two frames are expected - self.assertEquals(len(stacktrace['frames']), 2) - frame = stacktrace['frames'][1] + # this is a wrapped class object with __call__ so three frames are expected + self.assertEquals(len(stacktrace['frames']), 3) + frame = stacktrace['frames'][-1] self.assertEquals(frame['module'], __name__) self.assertEquals(frame['function'], 'test2') @@ -341,6 +341,41 @@ def test3(): self.assertEquals(len(self.client.events), 0) + def test_context_manager_functionality(self): + def test4(): + raise self.DecoratorTestException() + + try: + with self.client.capture_exceptions(): + test4() + except self.DecoratorTestException: + pass + + self.assertEquals(len(self.client.events), 1) + event = self.client.events.pop(0) + self.assertEquals(event['message'], 'DecoratorTestException') + exc = event['exception']['values'][0] + self.assertEquals(exc['type'], 'DecoratorTestException') + self.assertEquals(exc['module'], self.DecoratorTestException.__module__) + stacktrace = exc['stacktrace'] + # three frames are expected: test4, `with` block and context manager internals + self.assertEquals(len(stacktrace['frames']), 3) + frame = stacktrace['frames'][-1] + self.assertEquals(frame['module'], __name__) + self.assertEquals(frame['function'], 'test4') + + def test_content_manager_filtering(self): + def test5(): + raise Exception() + + try: + with self.client.capture_exceptions(self.DecoratorTestException): + test5() + except Exception: + pass + + self.assertEquals(len(self.client.events), 0) + def test_message_event(self): self.client.captureMessage(message='test') From 1454412df95a98940a78b74e3f6336c41c5d6df0 Mon Sep 17 00:00:00 2001 From: Olle Vidner Date: Sat, 21 Nov 2015 22:17:18 +0100 Subject: [PATCH 143/692] #676: Support detached HEAD --- raven/versioning.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/versioning.py b/raven/versioning.py index c8bf03625..5efa7e884 100644 --- a/raven/versioning.py +++ b/raven/versioning.py @@ -32,7 +32,7 @@ def fetch_git_sha(path, head=None): path, '.git', *head.rsplit(' ', 1)[-1].split('/') ) else: - revision_file = os.path.join(path, '.git', head) + return head else: revision_file = os.path.join(path, '.git', 'refs', 'heads', head) From 747ad0dda193f34a4e6c20231f278b9e4c91c402 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 30 Nov 2015 11:03:08 -0800 Subject: [PATCH 144/692] Support SENTRY_RELEASE in Flask (fixes GH-687) --- raven/contrib/flask.py | 1 + 1 file changed, 1 insertion(+) diff --git a/raven/contrib/flask.py b/raven/contrib/flask.py index 6cc721c00..0ab489899 100644 --- a/raven/contrib/flask.py +++ b/raven/contrib/flask.py @@ -53,6 +53,7 @@ def make_client(client_cls, app, dsn=None): list_max_length=app.config.get('SENTRY_MAX_LENGTH_LIST'), auto_log_stacks=app.config.get('SENTRY_AUTO_LOG_STACKS'), tags=app.config.get('SENTRY_TAGS'), + release=app.config.get('SENTRY_RELEASE'), extra={ 'app': app, }, From b2d7abbecaaf1d959b2a44d6777e7647e6ed63da Mon Sep 17 00:00:00 2001 From: David Cramer Date: Tue, 1 Dec 2015 17:24:19 -0800 Subject: [PATCH 145/692] setup_requires is unused --- setup.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/setup.py b/setup.py index 258910c6e..0f8ca7e5f 100755 --- a/setup.py +++ b/setup.py @@ -28,10 +28,6 @@ 'contextlib2', ] -setup_requires = [ - 'pytest', -] - dev_requires = [ 'flake8>=2.0,<2.1', ] From a2adcbe23f00012ed1519e849c358bfc6396c877 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Tue, 1 Dec 2015 17:31:52 -0800 Subject: [PATCH 146/692] Remove invalid requirements --- Makefile | 2 +- setup.py | 8 ++------ test-requirements.txt | 3 --- 3 files changed, 3 insertions(+), 10 deletions(-) delete mode 100644 test-requirements.txt diff --git a/Makefile b/Makefile index 1f773a52d..d7bf90fed 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ bootstrap: - pip install -r test-requirements.txt + pip install -e . [tests] make setup-git test: bootstrap lint diff --git a/setup.py b/setup.py index 0f8ca7e5f..af132e8ff 100755 --- a/setup.py +++ b/setup.py @@ -28,10 +28,6 @@ 'contextlib2', ] -dev_requires = [ - 'flake8>=2.0,<2.1', -] - unittest2_requires = ['unittest2'] flask_requires = [ 'Flask>=0.8', @@ -62,12 +58,13 @@ 'Django>=1.4', 'django-celery>=2.5', 'exam>=0.5.2', + 'flake8>=2.0,<2.1', 'logbook', 'mock', 'nose', 'pep8', 'pytz', - 'pytest>=2.7.0,<2.8.0', + 'pytest', 'pytest-django>=2.7.0,<2.8.0', 'pytest-timeout==0.4', 'requests', @@ -110,7 +107,6 @@ def run_tests(self): extras_require={ 'flask': flask_requires, 'tests': tests_require, - 'dev': dev_requires, }, license='BSD', tests_require=tests_require, diff --git a/test-requirements.txt b/test-requirements.txt deleted file mode 100644 index 1484a8141..000000000 --- a/test-requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -pytest-django -pytest-timeout --e .[dev,tests] From 435823d7e86512d1b9ba33a011bc6aa1ca2af62c Mon Sep 17 00:00:00 2001 From: David Cramer Date: Tue, 1 Dec 2015 17:33:53 -0800 Subject: [PATCH 147/692] Correct install --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index d7bf90fed..6639f668b 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ bootstrap: - pip install -e . [tests] + pip install -e "file://`pwd`#egg=raven[tests]" make setup-git test: bootstrap lint From b0e8a20a10082917d60ba9d258fea2950c9409db Mon Sep 17 00:00:00 2001 From: Matt Robenolt Date: Thu, 3 Dec 2015 13:23:02 -0800 Subject: [PATCH 148/692] Update cacertificate bundle with latest from Mozilla --- raven/data/cacert.pem | 1854 +++++++++++++++++++++-------------------- 1 file changed, 936 insertions(+), 918 deletions(-) diff --git a/raven/data/cacert.pem b/raven/data/cacert.pem index 3346ab5ca..672fb1feb 100644 --- a/raven/data/cacert.pem +++ b/raven/data/cacert.pem @@ -1,159 +1,3 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -# Issuer: CN=GTE CyberTrust Global Root O=GTE Corporation OU=GTE CyberTrust Solutions, Inc. -# Subject: CN=GTE CyberTrust Global Root O=GTE Corporation OU=GTE CyberTrust Solutions, Inc. -# Label: "GTE CyberTrust Global Root" -# Serial: 421 -# MD5 Fingerprint: ca:3d:d3:68:f1:03:5c:d0:32:fa:b8:2b:59:e8:5a:db -# SHA1 Fingerprint: 97:81:79:50:d8:1c:96:70:cc:34:d8:09:cf:79:44:31:36:7e:f4:74 -# SHA256 Fingerprint: a5:31:25:18:8d:21:10:aa:96:4b:02:c7:b7:c6:da:32:03:17:08:94:e5:fb:71:ff:fb:66:67:d5:e6:81:0a:36 ------BEGIN CERTIFICATE----- -MIICWjCCAcMCAgGlMA0GCSqGSIb3DQEBBAUAMHUxCzAJBgNVBAYTAlVTMRgwFgYD -VQQKEw9HVEUgQ29ycG9yYXRpb24xJzAlBgNVBAsTHkdURSBDeWJlclRydXN0IFNv -bHV0aW9ucywgSW5jLjEjMCEGA1UEAxMaR1RFIEN5YmVyVHJ1c3QgR2xvYmFsIFJv -b3QwHhcNOTgwODEzMDAyOTAwWhcNMTgwODEzMjM1OTAwWjB1MQswCQYDVQQGEwJV -UzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQLEx5HVEUgQ3liZXJU -cnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0IEds -b2JhbCBSb290MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCVD6C28FCc6HrH -iM3dFw4usJTQGz0O9pTAipTHBsiQl8i4ZBp6fmw8U+E3KHNgf7KXUwefU/ltWJTS -r41tiGeA5u2ylc9yMcqlHHK6XALnZELn+aks1joNrI1CqiQBOeacPwGFVw1Yh0X4 -04Wqk2kmhXBIgD8SFcd5tB8FLztimQIDAQABMA0GCSqGSIb3DQEBBAUAA4GBAG3r -GwnpXtlR22ciYaQqPEh346B8pt5zohQDhT37qw4wxYMWM4ETCJ57NE7fQMh017l9 -3PR2VX2bY1QY6fDq81yx2YtCHrnAlU66+tXifPVoYb+O7AWXX1uw16OFNMQkpw0P -lZPvy5TYnh+dXIVtx6quTx8itc2VrbqnzPmrC3p/ ------END CERTIFICATE----- - -# Issuer: CN=Thawte Server CA O=Thawte Consulting cc OU=Certification Services Division -# Subject: CN=Thawte Server CA O=Thawte Consulting cc OU=Certification Services Division -# Label: "Thawte Server CA" -# Serial: 1 -# MD5 Fingerprint: c5:70:c4:a2:ed:53:78:0c:c8:10:53:81:64:cb:d0:1d -# SHA1 Fingerprint: 23:e5:94:94:51:95:f2:41:48:03:b4:d5:64:d2:a3:a3:f5:d8:8b:8c -# SHA256 Fingerprint: b4:41:0b:73:e2:e6:ea:ca:47:fb:c4:2f:8f:a4:01:8a:f4:38:1d:c5:4c:fa:a8:44:50:46:1e:ed:09:45:4d:e9 ------BEGIN CERTIFICATE----- -MIIDEzCCAnygAwIBAgIBATANBgkqhkiG9w0BAQQFADCBxDELMAkGA1UEBhMCWkEx -FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYD -VQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlv -biBTZXJ2aWNlcyBEaXZpc2lvbjEZMBcGA1UEAxMQVGhhd3RlIFNlcnZlciBDQTEm -MCQGCSqGSIb3DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5jb20wHhcNOTYwODAx -MDAwMDAwWhcNMjAxMjMxMjM1OTU5WjCBxDELMAkGA1UEBhMCWkExFTATBgNVBAgT -DFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3 -dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNl -cyBEaXZpc2lvbjEZMBcGA1UEAxMQVGhhd3RlIFNlcnZlciBDQTEmMCQGCSqGSIb3 -DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQAD -gY0AMIGJAoGBANOkUG7I/1Zr5s9dtuoMaHVHoqrC2oQl/Kj0R1HahbUgdJSGHg91 -yekIYfUGbTBuFRkC6VLAYttNmZ7iagxEOM3+vuNkCXDF/rFrKbYvScg71CcEJRCX -L+eQbcAoQpnXTEPew/UhbVSfXcNY4cDk2VuwuNy0e982OsK1ZiIS1ocNAgMBAAGj -EzARMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAB/pMaVz7lcxG -7oWDTSEwjsrZqG9JGubaUeNgcGyEYRGhGshIPllDfU+VPaGLtwtimHp1it2ITk6e -QNuozDJ0uW8NxuOzRAvZim+aKZuZGCg70eNAKJpaPNW15yAbi8qkq43pUdniTCxZ -qdq5snUb9kLy78fyGPmJvKP/iiMucEc= ------END CERTIFICATE----- - -# Issuer: CN=Thawte Premium Server CA O=Thawte Consulting cc OU=Certification Services Division -# Subject: CN=Thawte Premium Server CA O=Thawte Consulting cc OU=Certification Services Division -# Label: "Thawte Premium Server CA" -# Serial: 1 -# MD5 Fingerprint: 06:9f:69:79:16:66:90:02:1b:8c:8c:a2:c3:07:6f:3a -# SHA1 Fingerprint: 62:7f:8d:78:27:65:63:99:d2:7d:7f:90:44:c9:fe:b3:f3:3e:fa:9a -# SHA256 Fingerprint: ab:70:36:36:5c:71:54:aa:29:c2:c2:9f:5d:41:91:16:3b:16:2a:22:25:01:13:57:d5:6d:07:ff:a7:bc:1f:72 ------BEGIN CERTIFICATE----- -MIIDJzCCApCgAwIBAgIBATANBgkqhkiG9w0BAQQFADCBzjELMAkGA1UEBhMCWkEx -FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYD -VQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlv -biBTZXJ2aWNlcyBEaXZpc2lvbjEhMB8GA1UEAxMYVGhhd3RlIFByZW1pdW0gU2Vy -dmVyIENBMSgwJgYJKoZIhvcNAQkBFhlwcmVtaXVtLXNlcnZlckB0aGF3dGUuY29t -MB4XDTk2MDgwMTAwMDAwMFoXDTIwMTIzMTIzNTk1OVowgc4xCzAJBgNVBAYTAlpB -MRUwEwYDVQQIEwxXZXN0ZXJuIENhcGUxEjAQBgNVBAcTCUNhcGUgVG93bjEdMBsG -A1UEChMUVGhhd3RlIENvbnN1bHRpbmcgY2MxKDAmBgNVBAsTH0NlcnRpZmljYXRp -b24gU2VydmljZXMgRGl2aXNpb24xITAfBgNVBAMTGFRoYXd0ZSBQcmVtaXVtIFNl -cnZlciBDQTEoMCYGCSqGSIb3DQEJARYZcHJlbWl1bS1zZXJ2ZXJAdGhhd3RlLmNv -bTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA0jY2aovXwlue2oFBYo847kkE -VdbQ7xwblRZH7xhINTpS9CtqBo87L+pW46+GjZ4X9560ZXUCTe/LCaIhUdib0GfQ -ug2SBhRz1JPLlyoAnFxODLz6FVL88kRu2hFKbgifLy3j+ao6hnO2RlNYyIkFvYMR -uHM/qgeN9EJN50CdHDcCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG -9w0BAQQFAAOBgQAmSCwWwlj66BZ0DKqqX1Q/8tfJeGBeXm43YyJ3Nn6yF8Q0ufUI -hfzJATj/Tb7yFkJD57taRvvBxhEf8UqwKEbJw8RCfbz6q1lu1bdRiBHjpIUZa4JM -pAwSremkrj/xw0llmozFyD4lt5SZu5IycQfwhl7tUCemDaYj+bvLpgcUQg== ------END CERTIFICATE----- - -# Issuer: O=Equifax OU=Equifax Secure Certificate Authority -# Subject: O=Equifax OU=Equifax Secure Certificate Authority -# Label: "Equifax Secure CA" -# Serial: 903804111 -# MD5 Fingerprint: 67:cb:9d:c0:13:24:8a:82:9b:b2:17:1e:d1:1b:ec:d4 -# SHA1 Fingerprint: d2:32:09:ad:23:d3:14:23:21:74:e4:0d:7f:9d:62:13:97:86:63:3a -# SHA256 Fingerprint: 08:29:7a:40:47:db:a2:36:80:c7:31:db:6e:31:76:53:ca:78:48:e1:be:bd:3a:0b:01:79:a7:07:f9:2c:f1:78 ------BEGIN CERTIFICATE----- -MIIDIDCCAomgAwIBAgIENd70zzANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJV -UzEQMA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2Vy -dGlmaWNhdGUgQXV0aG9yaXR5MB4XDTk4MDgyMjE2NDE1MVoXDTE4MDgyMjE2NDE1 -MVowTjELMAkGA1UEBhMCVVMxEDAOBgNVBAoTB0VxdWlmYXgxLTArBgNVBAsTJEVx -dWlmYXggU2VjdXJlIENlcnRpZmljYXRlIEF1dGhvcml0eTCBnzANBgkqhkiG9w0B -AQEFAAOBjQAwgYkCgYEAwV2xWGcIYu6gmi0fCG2RFGiYCh7+2gRvE4RiIcPRfM6f -BeC4AfBONOziipUEZKzxa1NfBbPLZ4C/QgKO/t0BCezhABRP/PvwDN1Dulsr4R+A -cJkVV5MW8Q+XarfCaCMczE1ZMKxRHjuvK9buY0V7xdlfUNLjUA86iOe/FP3gx7kC -AwEAAaOCAQkwggEFMHAGA1UdHwRpMGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEQ -MA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2VydGlm -aWNhdGUgQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMBoGA1UdEAQTMBGBDzIwMTgw -ODIyMTY0MTUxWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUSOZo+SvSspXXR9gj -IBBPM5iQn9QwHQYDVR0OBBYEFEjmaPkr0rKV10fYIyAQTzOYkJ/UMAwGA1UdEwQF -MAMBAf8wGgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUA -A4GBAFjOKer89961zgK5F7WF0bnj4JXMJTENAKaSbn+2kmOeUJXRmm/kEd5jhW6Y -7qj/WsjTVbJmcVfewCHrPSqnI0kBBIZCe/zuf6IWUrVnZ9NA2zsmWLIodz2uFHdh -1voqZiegDfqnc1zqcPGUIWVEX/r87yloqaKHee9570+sB3c4 ------END CERTIFICATE----- - -# Issuer: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority -# Subject: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority -# Label: "Verisign Class 3 Public Primary Certification Authority" -# Serial: 149843929435818692848040365716851702463 -# MD5 Fingerprint: 10:fc:63:5d:f6:26:3e:0d:f3:25:be:5f:79:cd:67:67 -# SHA1 Fingerprint: 74:2c:31:92:e6:07:e4:24:eb:45:49:54:2b:e1:bb:c5:3e:61:74:e2 -# SHA256 Fingerprint: e7:68:56:34:ef:ac:f6:9a:ce:93:9a:6b:25:5b:7b:4f:ab:ef:42:93:5b:50:a2:65:ac:b5:cb:60:27:e4:4e:70 ------BEGIN CERTIFICATE----- -MIICPDCCAaUCEHC65B0Q2Sk0tjjKewPMur8wDQYJKoZIhvcNAQECBQAwXzELMAkG -A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz -cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2 -MDEyOTAwMDAwMFoXDTI4MDgwMTIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV -BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt -YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN -ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE -BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is -I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G -CSqGSIb3DQEBAgUAA4GBALtMEivPLCYATxQT3ab7/AoRhIzzKBxnki98tsX63/Do -lbwdj2wsqFHMc9ikwFPwTtYmwHYBV4GSXiHx0bH/59AhWM1pF+NEHJwZRDmJXNyc -AA9WjQKZ7aKQRUzkuxCkPfAyAw7xzvjoyVGM5mKf5p/AfbdynMk2OmufTqj/ZA1k ------END CERTIFICATE----- - -# Issuer: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority - G2/(c) 1998 VeriSign, Inc. - For authorized use only/VeriSign Trust Network -# Subject: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority - G2/(c) 1998 VeriSign, Inc. - For authorized use only/VeriSign Trust Network -# Label: "Verisign Class 3 Public Primary Certification Authority - G2" -# Serial: 167285380242319648451154478808036881606 -# MD5 Fingerprint: a2:33:9b:4c:74:78:73:d4:6c:e7:c1:f3:8d:cb:5c:e9 -# SHA1 Fingerprint: 85:37:1c:a6:e5:50:14:3d:ce:28:03:47:1b:de:3a:09:e8:f8:77:0f -# SHA256 Fingerprint: 83:ce:3c:12:29:68:8a:59:3d:48:5f:81:97:3c:0f:91:95:43:1e:da:37:cc:5e:36:43:0e:79:c7:a8:88:63:8b ------BEGIN CERTIFICATE----- -MIIDAjCCAmsCEH3Z/gfPqB63EHln+6eJNMYwDQYJKoZIhvcNAQEFBQAwgcExCzAJ -BgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xh -c3MgMyBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcy -MTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3Jp -emVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMB4X -DTk4MDUxODAwMDAwMFoXDTI4MDgwMTIzNTk1OVowgcExCzAJBgNVBAYTAlVTMRcw -FQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMyBQdWJsaWMg -UHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEo -YykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5 -MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMIGfMA0GCSqGSIb3DQEB -AQUAA4GNADCBiQKBgQDMXtERXVxp0KvTuWpMmR9ZmDCOFoUgRm1HP9SFIIThbbP4 -pO0M8RcPO/mn+SXXwc+EY/J8Y8+iR/LGWzOOZEAEaMGAuWQcRXfH2G71lSk8UOg0 -13gfqLptQ5GVj0VXXn7F+8qkBOvqlzdUMG+7AUcyM83cV5tkaWH4mx0ciU9cZwID -AQABMA0GCSqGSIb3DQEBBQUAA4GBAFFNzb5cy5gZnBWyATl4Lk0PZ3BwmcYQWpSk -U01UbSuvDV1Ai2TT1+7eVmGSX6bEHRBhNtMsJzzoKQm5EWR0zLVznxxIqbxhAe7i -F6YM40AIOw7n60RzKprxaZLvcRTDOaxxp5EJb+RxBrO6WVcmeQD2+A2iMzAo1KpY -oJ2daZH9 ------END CERTIFICATE----- # Issuer: CN=GlobalSign Root CA O=GlobalSign nv-sa OU=Root CA # Subject: CN=GlobalSign Root CA O=GlobalSign nv-sa OU=Root CA @@ -214,84 +58,6 @@ AfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7 TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg== -----END CERTIFICATE----- -# Issuer: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 1 Policy Validation Authority -# Subject: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 1 Policy Validation Authority -# Label: "ValiCert Class 1 VA" -# Serial: 1 -# MD5 Fingerprint: 65:58:ab:15:ad:57:6c:1e:a8:a7:b5:69:ac:bf:ff:eb -# SHA1 Fingerprint: e5:df:74:3c:b6:01:c4:9b:98:43:dc:ab:8c:e8:6a:81:10:9f:e4:8e -# SHA256 Fingerprint: f4:c1:49:55:1a:30:13:a3:5b:c7:bf:fe:17:a7:f3:44:9b:c1:ab:5b:5a:0a:e7:4b:06:c2:3b:90:00:4c:01:04 ------BEGIN CERTIFICATE----- -MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0 -IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz -BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDEgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y -aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG -9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNTIyMjM0OFoXDTE5MDYy -NTIyMjM0OFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y -azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs -YXNzIDEgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw -Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl -cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDYWYJ6ibiWuqYvaG9Y -LqdUHAZu9OqNSLwxlBfw8068srg1knaw0KWlAdcAAxIiGQj4/xEjm84H9b9pGib+ -TunRf50sQB1ZaG6m+FiwnRqP0z/x3BkGgagO4DrdyFNFCQbmD3DD+kCmDuJWBQ8Y -TfwggtFzVXSNdnKgHZ0dwN0/cQIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFBoPUn0 -LBwGlN+VYH+Wexf+T3GtZMjdd9LvWVXoP+iOBSoh8gfStadS/pyxtuJbdxdA6nLW -I8sogTLDAHkY7FkXicnGah5xyf23dKUlRWnFSKsZ4UWKJWsZ7uW7EvV/96aNUcPw -nXS3qT6gpf+2SQMT2iLM7XGCK5nPOrf1LXLI ------END CERTIFICATE----- - -# Issuer: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 2 Policy Validation Authority -# Subject: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 2 Policy Validation Authority -# Label: "ValiCert Class 2 VA" -# Serial: 1 -# MD5 Fingerprint: a9:23:75:9b:ba:49:36:6e:31:c2:db:f2:e7:66:ba:87 -# SHA1 Fingerprint: 31:7a:2a:d0:7f:2b:33:5e:f5:a1:c3:4e:4b:57:e8:b7:d8:f1:fc:a6 -# SHA256 Fingerprint: 58:d0:17:27:9c:d4:dc:63:ab:dd:b1:96:a6:c9:90:6c:30:c4:e0:87:83:ea:e8:c1:60:99:54:d6:93:55:59:6b ------BEGIN CERTIFICATE----- -MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0 -IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz -BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y -aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG -9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAwMTk1NFoXDTE5MDYy -NjAwMTk1NFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y -azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs -YXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw -Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl -cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDOOnHK5avIWZJV16vY -dA757tn2VUdZZUcOBVXc65g2PFxTXdMwzzjsvUGJ7SVCCSRrCl6zfN1SLUzm1NZ9 -WlmpZdRJEy0kTRxQb7XBhVQ7/nHk01xC+YDgkRoKWzk2Z/M/VXwbP7RfZHM047QS -v4dk+NoS/zcnwbNDu+97bi5p9wIDAQABMA0GCSqGSIb3DQEBBQUAA4GBADt/UG9v -UJSZSWI4OB9L+KXIPqeCgfYrx+jFzug6EILLGACOTb2oWH+heQC1u+mNr0HZDzTu -IYEZoDJJKPTEjlbVUjP9UNV+mWwD5MlM/Mtsq2azSiGM5bUMMj4QssxsodyamEwC -W/POuZ6lcg5Ktz885hZo+L7tdEy8W9ViH0Pd ------END CERTIFICATE----- - -# Issuer: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 3 Policy Validation Authority -# Subject: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 3 Policy Validation Authority -# Label: "RSA Root Certificate 1" -# Serial: 1 -# MD5 Fingerprint: a2:6f:53:b7:ee:40:db:4a:68:e7:fa:18:d9:10:4b:72 -# SHA1 Fingerprint: 69:bd:8c:f4:9c:d3:00:fb:59:2e:17:93:ca:55:6a:f3:ec:aa:35:fb -# SHA256 Fingerprint: bc:23:f9:8a:31:3c:b9:2d:e3:bb:fc:3a:5a:9f:44:61:ac:39:49:4c:4a:e1:5a:9e:9d:f1:31:e9:9b:73:01:9a ------BEGIN CERTIFICATE----- -MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0 -IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz -BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDMgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y -aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG -9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAwMjIzM1oXDTE5MDYy -NjAwMjIzM1owgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y -azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs -YXNzIDMgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw -Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl -cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDjmFGWHOjVsQaBalfD -cnWTq8+epvzzFlLWLU2fNUSoLgRNB0mKOCn1dzfnt6td3zZxFJmP3MKS8edgkpfs -2Ejcv8ECIMYkpChMMFp2bbFc893enhBxoYjHW5tBbcqwuI4V7q0zK89HBFx1cQqY -JJgpp0lZpd34t0NiYfPT4tBVPwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFa7AliE -Zwgs3x/be0kz9dNnnfS0ChCzycUs4pJqcXgn8nCDQtM+z6lU9PHYkhaM0QTLS6vJ -n0WuPIqpsHEzXcjFV9+vqDWzf4mH6eglkrh/hXqu1rweN1gqZ8mRzyqBPu3GOd/A -PhmcGcwTTYJBtYze4D1gCCAPRX5ron+jjBXu ------END CERTIFICATE----- - # Issuer: CN=VeriSign Class 3 Public Primary Certification Authority - G3 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 1999 VeriSign, Inc. - For authorized use only # Subject: CN=VeriSign Class 3 Public Primary Certification Authority - G3 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 1999 VeriSign, Inc. - For authorized use only # Label: "Verisign Class 3 Public Primary Certification Authority - G3" @@ -324,74 +90,6 @@ F4ErWjfJXir0xuKhXFSbplQAz/DxwceYMBo7Nhbbo27q/a2ywtrvAkcTisDxszGt TxzhT5yvDwyd93gN2PQ1VoDat20Xj50egWTh/sVFuq1ruQp6Tk9LhO5L8X3dEQ== -----END CERTIFICATE----- -# Issuer: CN=VeriSign Class 4 Public Primary Certification Authority - G3 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 1999 VeriSign, Inc. - For authorized use only -# Subject: CN=VeriSign Class 4 Public Primary Certification Authority - G3 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 1999 VeriSign, Inc. - For authorized use only -# Label: "Verisign Class 4 Public Primary Certification Authority - G3" -# Serial: 314531972711909413743075096039378935511 -# MD5 Fingerprint: db:c8:f2:27:2e:b1:ea:6a:29:23:5d:fe:56:3e:33:df -# SHA1 Fingerprint: c8:ec:8c:87:92:69:cb:4b:ab:39:e9:8d:7e:57:67:f3:14:95:73:9d -# SHA256 Fingerprint: e3:89:36:0d:0f:db:ae:b3:d2:50:58:4b:47:30:31:4e:22:2f:39:c1:56:a0:20:14:4e:8d:96:05:61:79:15:06 ------BEGIN CERTIFICATE----- -MIIEGjCCAwICEQDsoKeLbnVqAc/EfMwvlF7XMA0GCSqGSIb3DQEBBQUAMIHKMQsw -CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl -cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu -LCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlT -aWduIENsYXNzIDQgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3Jp -dHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQswCQYD -VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlT -aWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJ -bmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWdu -IENsYXNzIDQgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg -LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK3LpRFpxlmr8Y+1 -GQ9Wzsy1HyDkniYlS+BzZYlZ3tCD5PUPtbut8XzoIfzk6AzufEUiGXaStBO3IFsJ -+mGuqPKljYXCKtbeZjbSmwL0qJJgfJxptI8kHtCGUvYynEFYHiK9zUVilQhu0Gbd -U6LM8BDcVHOLBKFGMzNcF0C5nk3T875Vg+ixiY5afJqWIpA7iCXy0lOIAgwLePLm -NxdLMEYH5IBtptiWLugs+BGzOA1mppvqySNb247i8xOOGlktqgLw7KSHZtzBP/XY -ufTsgsbSPZUd5cBPhMnZo0QoBmrXRazwa2rvTl/4EYIeOGM0ZlDUPpNz+jDDZq3/ -ky2X7wMCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAj/ola09b5KROJ1WrIhVZPMq1 -CtRK26vdoV9TxaBXOcLORyu+OshWv8LZJxA6sQU8wHcxuzrTBXttmhwwjIDLk5Mq -g6sFUYICABFna/OIYUdfA5PVWw3g8dShMjWFsjrbsIKr0csKvE+MW8VLADsfKoKm -fjaF3H48ZwC15DtS4KjrXRX5xm3wrR0OhbepmnMUWluPQSjA1egtTaRezarZ7c7c -2NU8Qh0XwRJdRTjDOPP8hS6DRkiy1yBfkjaP53kPmF6Z6PDQpLv1U70qzlmwr25/ -bLvSHgCwIe34QWKCudiyxLtGUPMxxY8BqHTr9Xgn2uf3ZkPznoM+IKrDNWCRzg== ------END CERTIFICATE----- - -# Issuer: CN=Entrust.net Secure Server Certification Authority O=Entrust.net OU=www.entrust.net/CPS incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited -# Subject: CN=Entrust.net Secure Server Certification Authority O=Entrust.net OU=www.entrust.net/CPS incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited -# Label: "Entrust.net Secure Server CA" -# Serial: 927650371 -# MD5 Fingerprint: df:f2:80:73:cc:f1:e6:61:73:fc:f5:42:e9:c5:7c:ee -# SHA1 Fingerprint: 99:a6:9b:e6:1a:fe:88:6b:4d:2b:82:00:7c:b8:54:fc:31:7e:15:39 -# SHA256 Fingerprint: 62:f2:40:27:8c:56:4c:4d:d8:bf:7d:9d:4f:6f:36:6e:a8:94:d2:2f:5f:34:d9:89:a9:83:ac:ec:2f:ff:ed:50 ------BEGIN CERTIFICATE----- -MIIE2DCCBEGgAwIBAgIEN0rSQzANBgkqhkiG9w0BAQUFADCBwzELMAkGA1UEBhMC -VVMxFDASBgNVBAoTC0VudHJ1c3QubmV0MTswOQYDVQQLEzJ3d3cuZW50cnVzdC5u -ZXQvQ1BTIGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMc -KGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDE6MDgGA1UEAxMxRW50cnVzdC5u -ZXQgU2VjdXJlIFNlcnZlciBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw05OTA1 -MjUxNjA5NDBaFw0xOTA1MjUxNjM5NDBaMIHDMQswCQYDVQQGEwJVUzEUMBIGA1UE -ChMLRW50cnVzdC5uZXQxOzA5BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5j -b3JwLiBieSByZWYuIChsaW1pdHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBF -bnRydXN0Lm5ldCBMaW1pdGVkMTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUg -U2VydmVyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGdMA0GCSqGSIb3DQEBAQUA -A4GLADCBhwKBgQDNKIM0VBuJ8w+vN5Ex/68xYMmo6LIQaO2f55M28Qpku0f1BBc/ -I0dNxScZgSYMVHINiC3ZH5oSn7yzcdOAGT9HZnuMNSjSuQrfJNqc1lB5gXpa0zf3 -wkrYKZImZNHkmGw6AIr1NJtl+O3jEP/9uElY3KDegjlrgbEWGWG5VLbmQwIBA6OC -AdcwggHTMBEGCWCGSAGG+EIBAQQEAwIABzCCARkGA1UdHwSCARAwggEMMIHeoIHb -oIHYpIHVMIHSMQswCQYDVQQGEwJVUzEUMBIGA1UEChMLRW50cnVzdC5uZXQxOzA5 -BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5jb3JwLiBieSByZWYuIChsaW1p -dHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBFbnRydXN0Lm5ldCBMaW1pdGVk -MTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUgU2VydmVyIENlcnRpZmljYXRp -b24gQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMCmgJ6AlhiNodHRwOi8vd3d3LmVu -dHJ1c3QubmV0L0NSTC9uZXQxLmNybDArBgNVHRAEJDAigA8xOTk5MDUyNTE2MDk0 -MFqBDzIwMTkwNTI1MTYwOTQwWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAU8Bdi -E1U9s/8KAGv7UISX8+1i0BowHQYDVR0OBBYEFPAXYhNVPbP/CgBr+1CEl/PtYtAa -MAwGA1UdEwQFMAMBAf8wGQYJKoZIhvZ9B0EABAwwChsEVjQuMAMCBJAwDQYJKoZI -hvcNAQEFBQADgYEAkNwwAvpkdMKnCqV8IY00F6j7Rw7/JXyNEwr75Ji174z4xRAN -95K+8cPV1ZVqBLssziY2ZcgxxufuP+NXdYR6Ee9GTxj005i7qIcyunL2POI9n9cd -2cNgQ4xYDiKWL2KjLB+6rQXvqzJ4h6BUcxm1XAX5Uj5tLUUL9wqT6u0G+bI= ------END CERTIFICATE----- - # Issuer: CN=Entrust.net Certification Authority (2048) O=Entrust.net OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited # Subject: CN=Entrust.net Certification Authority (2048) O=Entrust.net OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited # Label: "Entrust.net Premium 2048 Secure Server CA" @@ -454,54 +152,6 @@ ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp -----END CERTIFICATE----- -# Issuer: CN=Equifax Secure Global eBusiness CA-1 O=Equifax Secure Inc. -# Subject: CN=Equifax Secure Global eBusiness CA-1 O=Equifax Secure Inc. -# Label: "Equifax Secure Global eBusiness CA" -# Serial: 1 -# MD5 Fingerprint: 8f:5d:77:06:27:c4:98:3c:5b:93:78:e7:d7:7d:9b:cc -# SHA1 Fingerprint: 7e:78:4a:10:1c:82:65:cc:2d:e1:f1:6d:47:b4:40:ca:d9:0a:19:45 -# SHA256 Fingerprint: 5f:0b:62:ea:b5:e3:53:ea:65:21:65:16:58:fb:b6:53:59:f4:43:28:0a:4a:fb:d1:04:d7:7d:10:f9:f0:4c:07 ------BEGIN CERTIFICATE----- -MIICkDCCAfmgAwIBAgIBATANBgkqhkiG9w0BAQQFADBaMQswCQYDVQQGEwJVUzEc -MBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5jLjEtMCsGA1UEAxMkRXF1aWZheCBT -ZWN1cmUgR2xvYmFsIGVCdXNpbmVzcyBDQS0xMB4XDTk5MDYyMTA0MDAwMFoXDTIw -MDYyMTA0MDAwMFowWjELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE0VxdWlmYXggU2Vj -dXJlIEluYy4xLTArBgNVBAMTJEVxdWlmYXggU2VjdXJlIEdsb2JhbCBlQnVzaW5l -c3MgQ0EtMTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAuucXkAJlsTRVPEnC -UdXfp9E3j9HngXNBUmCbnaEXJnitx7HoJpQytd4zjTov2/KaelpzmKNc6fuKcxtc -58O/gGzNqfTWK8D3+ZmqY6KxRwIP1ORROhI8bIpaVIRw28HFkM9yRcuoWcDNM50/ -o5brhTMhHD4ePmBudpxnhcXIw2ECAwEAAaNmMGQwEQYJYIZIAYb4QgEBBAQDAgAH -MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUvqigdHJQa0S3ySPY+6j/s1dr -aGwwHQYDVR0OBBYEFL6ooHRyUGtEt8kj2Puo/7NXa2hsMA0GCSqGSIb3DQEBBAUA -A4GBADDiAVGqx+pf2rnQZQ8w1j7aDRRJbpGTJxQx78T3LUX47Me/okENI7SS+RkA -Z70Br83gcfxaz2TE4JaY0KNA4gGK7ycH8WUBikQtBmV1UsCGECAhX2xrD2yuCRyv -8qIYNMR1pHMc8Y3c7635s3a0kr/clRAevsvIO1qEYBlWlKlV ------END CERTIFICATE----- - -# Issuer: CN=Equifax Secure eBusiness CA-1 O=Equifax Secure Inc. -# Subject: CN=Equifax Secure eBusiness CA-1 O=Equifax Secure Inc. -# Label: "Equifax Secure eBusiness CA 1" -# Serial: 4 -# MD5 Fingerprint: 64:9c:ef:2e:44:fc:c6:8f:52:07:d0:51:73:8f:cb:3d -# SHA1 Fingerprint: da:40:18:8b:91:89:a3:ed:ee:ae:da:97:fe:2f:9d:f5:b7:d1:8a:41 -# SHA256 Fingerprint: cf:56:ff:46:a4:a1:86:10:9d:d9:65:84:b5:ee:b5:8a:51:0c:42:75:b0:e5:f9:4f:40:bb:ae:86:5e:19:f6:73 ------BEGIN CERTIFICATE----- -MIICgjCCAeugAwIBAgIBBDANBgkqhkiG9w0BAQQFADBTMQswCQYDVQQGEwJVUzEc -MBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5jLjEmMCQGA1UEAxMdRXF1aWZheCBT -ZWN1cmUgZUJ1c2luZXNzIENBLTEwHhcNOTkwNjIxMDQwMDAwWhcNMjAwNjIxMDQw -MDAwWjBTMQswCQYDVQQGEwJVUzEcMBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5j -LjEmMCQGA1UEAxMdRXF1aWZheCBTZWN1cmUgZUJ1c2luZXNzIENBLTEwgZ8wDQYJ -KoZIhvcNAQEBBQADgY0AMIGJAoGBAM4vGbwXt3fek6lfWg0XTzQaDJj0ItlZ1MRo -RvC0NcWFAyDGr0WlIVFFQesWWDYyb+JQYmT5/VGcqiTZ9J2DKocKIdMSODRsjQBu -WqDZQu4aIZX5UkxVWsUPOE9G+m34LjXWHXzr4vCwdYDIqROsvojvOm6rXyo4YgKw -Env+j6YDAgMBAAGjZjBkMBEGCWCGSAGG+EIBAQQEAwIABzAPBgNVHRMBAf8EBTAD -AQH/MB8GA1UdIwQYMBaAFEp4MlIR21kWNl7fwRQ2QGpHfEyhMB0GA1UdDgQWBBRK -eDJSEdtZFjZe38EUNkBqR3xMoTANBgkqhkiG9w0BAQQFAAOBgQB1W6ibAxHm6VZM -zfmpTMANmvPMZWnmJXbMWbfWVMMdzZmsGd20hdXgPfxiIKeES1hl8eL5lSE/9dR+ -WB5Hh1Q+WKG1tfgq73HnvMP2sUlG4tega+VWeponmHxGYhTnyfxuAxJ5gDgdSIKN -/Bf+KpYrtWKmpj29f5JZzVoqgrI3eQ== ------END CERTIFICATE----- - # Issuer: CN=AddTrust Class 1 CA Root O=AddTrust AB OU=AddTrust TTP Network # Subject: CN=AddTrust Class 1 CA Root O=AddTrust AB OU=AddTrust TTP Network # Label: "AddTrust Low-Value Services Root" @@ -831,77 +481,6 @@ OCiNUW7dFGdTbHFcJoRNdVq2fmBWqU2t+5sel/MN2dKXVHfaPRK34B7vCAas+YWH QMAJKOSLakhT2+zNVVXxxvjpoixMptEmX36vWkzaH6byHCx+rgIW0lbQL1dTR+iS -----END CERTIFICATE----- -# Issuer: CN=America Online Root Certification Authority 1 O=America Online Inc. -# Subject: CN=America Online Root Certification Authority 1 O=America Online Inc. -# Label: "America Online Root Certification Authority 1" -# Serial: 1 -# MD5 Fingerprint: 14:f1:08:ad:9d:fa:64:e2:89:e7:1c:cf:a8:ad:7d:5e -# SHA1 Fingerprint: 39:21:c1:15:c1:5d:0e:ca:5c:cb:5b:c4:f0:7d:21:d8:05:0b:56:6a -# SHA256 Fingerprint: 77:40:73:12:c6:3a:15:3d:5b:c0:0b:4e:51:75:9c:df:da:c2:37:dc:2a:33:b6:79:46:e9:8e:9b:fa:68:0a:e3 ------BEGIN CERTIFICATE----- -MIIDpDCCAoygAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEc -MBoGA1UEChMTQW1lcmljYSBPbmxpbmUgSW5jLjE2MDQGA1UEAxMtQW1lcmljYSBP -bmxpbmUgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAxMB4XDTAyMDUyODA2 -MDAwMFoXDTM3MTExOTIwNDMwMFowYzELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE0Ft -ZXJpY2EgT25saW5lIEluYy4xNjA0BgNVBAMTLUFtZXJpY2EgT25saW5lIFJvb3Qg -Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgMTCCASIwDQYJKoZIhvcNAQEBBQADggEP -ADCCAQoCggEBAKgv6KRpBgNHw+kqmP8ZonCaxlCyfqXfaE0bfA+2l2h9LaaLl+lk -hsmj76CGv2BlnEtUiMJIxUo5vxTjWVXlGbR0yLQFOVwWpeKVBeASrlmLojNoWBym -1BW32J/X3HGrfpq/m44zDyL9Hy7nBzbvYjnF3cu6JRQj3gzGPTzOggjmZj7aUTsW -OqMFf6Dch9Wc/HKpoH145LcxVR5lu9RhsCFg7RAycsWSJR74kEoYeEfffjA3PlAb -2xzTa5qGUwew76wGePiEmf4hjUyAtgyC9mZweRrTT6PP8c9GsEsPPt2IYriMqQko -O3rHl+Ee5fSfwMCuJKDIodkP1nsmgmkyPacCAwEAAaNjMGEwDwYDVR0TAQH/BAUw -AwEB/zAdBgNVHQ4EFgQUAK3Zo/Z59m50qX8zPYEX10zPM94wHwYDVR0jBBgwFoAU -AK3Zo/Z59m50qX8zPYEX10zPM94wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB -BQUAA4IBAQB8itEfGDeC4Liwo+1WlchiYZwFos3CYiZhzRAW18y0ZTTQEYqtqKkF -Zu90821fnZmv9ov761KyBZiibyrFVL0lvV+uyIbqRizBs73B6UlwGBaXCBOMIOAb -LjpHyx7kADCVW/RFo8AasAFOq73AI25jP4BKxQft3OJvx8Fi8eNy1gTIdGcL+oir -oQHIb/AUr9KZzVGTfu0uOMe9zkZQPXLjeSWdm4grECDdpbgyn43gKd8hdIaC2y+C -MMbHNYaz+ZZfRtsMRf3zUMNvxsNIrUam4SdHCh0Om7bCd39j8uB9Gr784N/Xx6ds -sPmuujz9dLQR6FgNgLzTqIA6me11zEZ7 ------END CERTIFICATE----- - -# Issuer: CN=America Online Root Certification Authority 2 O=America Online Inc. -# Subject: CN=America Online Root Certification Authority 2 O=America Online Inc. -# Label: "America Online Root Certification Authority 2" -# Serial: 1 -# MD5 Fingerprint: d6:ed:3c:ca:e2:66:0f:af:10:43:0d:77:9b:04:09:bf -# SHA1 Fingerprint: 85:b5:ff:67:9b:0c:79:96:1f:c8:6e:44:22:00:46:13:db:17:92:84 -# SHA256 Fingerprint: 7d:3b:46:5a:60:14:e5:26:c0:af:fc:ee:21:27:d2:31:17:27:ad:81:1c:26:84:2d:00:6a:f3:73:06:cc:80:bd ------BEGIN CERTIFICATE----- -MIIFpDCCA4ygAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEc -MBoGA1UEChMTQW1lcmljYSBPbmxpbmUgSW5jLjE2MDQGA1UEAxMtQW1lcmljYSBP -bmxpbmUgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAyMB4XDTAyMDUyODA2 -MDAwMFoXDTM3MDkyOTE0MDgwMFowYzELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE0Ft -ZXJpY2EgT25saW5lIEluYy4xNjA0BgNVBAMTLUFtZXJpY2EgT25saW5lIFJvb3Qg -Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIP -ADCCAgoCggIBAMxBRR3pPU0Q9oyxQcngXssNt79Hc9PwVU3dxgz6sWYFas14tNwC -206B89enfHG8dWOgXeMHDEjsJcQDIPT/DjsS/5uN4cbVG7RtIuOx238hZK+GvFci -KtZHgVdEglZTvYYUAQv8f3SkWq7xuhG1m1hagLQ3eAkzfDJHA1zEpYNI9FdWboE2 -JxhP7JsowtS013wMPgwr38oE18aO6lhOqKSlGBxsRZijQdEt0sdtjRnxrXm3gT+9 -BoInLRBYBbV4Bbkv2wxrkJB+FFk4u5QkE+XRnRTf04JNRvCAOVIyD+OEsnpD8l7e -Xz8d3eOyG6ChKiMDbi4BFYdcpnV1x5dhvt6G3NRI270qv0pV2uh9UPu0gBe4lL8B -PeraunzgWGcXuVjgiIZGZ2ydEEdYMtA1fHkqkKJaEBEjNa0vzORKW6fIJ/KD3l67 -Xnfn6KVuY8INXWHQjNJsWiEOyiijzirplcdIz5ZvHZIlyMbGwcEMBawmxNJ10uEq -Z8A9W6Wa6897GqidFEXlD6CaZd4vKL3Ob5Rmg0gp2OpljK+T2WSfVVcmv2/LNzGZ -o2C7HK2JNDJiuEMhBnIMoVxtRsX6Kc8w3onccVvdtjc+31D1uAclJuW8tf48ArO3 -+L5DwYcRlJ4jbBeKuIonDFRH8KmzwICMoCfrHRnjB453cMor9H124HhnAgMBAAGj -YzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFE1FwWg4u3OpaaEg5+31IqEj -FNeeMB8GA1UdIwQYMBaAFE1FwWg4u3OpaaEg5+31IqEjFNeeMA4GA1UdDwEB/wQE -AwIBhjANBgkqhkiG9w0BAQUFAAOCAgEAZ2sGuV9FOypLM7PmG2tZTiLMubekJcmn -xPBUlgtk87FYT15R/LKXeydlwuXK5w0MJXti4/qftIe3RUavg6WXSIylvfEWK5t2 -LHo1YGwRgJfMqZJS5ivmae2p+DYtLHe/YUjRYwu5W1LtGLBDQiKmsXeu3mnFzccc -obGlHBD7GL4acN3Bkku+KVqdPzW+5X1R+FXgJXUjhx5c3LqdsKyzadsXg8n33gy8 -CNyRnqjQ1xU3c6U1uPx+xURABsPr+CKAXEfOAuMRn0T//ZoyzH1kUQ7rVyZ2OuMe -IjzCpjbdGe+n/BLzJsBZMYVMnNjP36TMzCmT/5RtdlwTCJfy7aULTd3oyWgOZtMA -DjMSW7yV5TKQqLPGbIOtd+6Lfn6xqavT4fG2wLHqiMDn05DpKJKUe2h7lyoKZy2F -AjgQ5ANh1NolNscIWC2hp1GvMApJ9aZphwctREZ2jirlmjvXGKL8nDgQzMY70rUX -Om/9riW99XJZZLF0KjhfGEzfz3EEWjbUvy+ZnOjZurGV5gJLIaFb1cFPj65pbVPb -AZO1XB4Y3WRayhgoPmMEEf0cjQAPuDffZ4qdZqkCapH/E8ovXYO8h5Ns3CRRFgQl -Zvqz2cK6Kb6aSDiCmfS/O0oxGfm/jiEzFMpPVF/7zvuPcX/9XhmgD0uRuMRUvAaw -RY8mkaKO/qk= ------END CERTIFICATE----- - # Issuer: CN=Visa eCommerce Root O=VISA OU=Visa International Service Association # Subject: CN=Visa eCommerce Root O=VISA OU=Visa International Service Association # Label: "Visa eCommerce Root" @@ -1272,73 +851,6 @@ u3pCMuiufi3IvKwUv9kP2Vv8wfl6leF9fpb8cbDCTMjfRTTJzg3ynGQI0DvDKcWy iJf2fL1LuCAWZwWN4jvBcj+UlTfHXbme2JOhF4//DGYVwSR8MnwDHTuhWEUykw== -----END CERTIFICATE----- -# Issuer: O=TDC Internet OU=TDC Internet Root CA -# Subject: O=TDC Internet OU=TDC Internet Root CA -# Label: "TDC Internet Root CA" -# Serial: 986490188 -# MD5 Fingerprint: 91:f4:03:55:20:a1:f8:63:2c:62:de:ac:fb:61:1c:8e -# SHA1 Fingerprint: 21:fc:bd:8e:7f:6c:af:05:1b:d1:b3:43:ec:a8:e7:61:47:f2:0f:8a -# SHA256 Fingerprint: 48:98:c6:88:8c:0c:ff:b0:d3:e3:1a:ca:8a:37:d4:e3:51:5f:f7:46:d0:26:35:d8:66:46:cf:a0:a3:18:5a:e7 ------BEGIN CERTIFICATE----- -MIIEKzCCAxOgAwIBAgIEOsylTDANBgkqhkiG9w0BAQUFADBDMQswCQYDVQQGEwJE -SzEVMBMGA1UEChMMVERDIEludGVybmV0MR0wGwYDVQQLExRUREMgSW50ZXJuZXQg -Um9vdCBDQTAeFw0wMTA0MDUxNjMzMTdaFw0yMTA0MDUxNzAzMTdaMEMxCzAJBgNV -BAYTAkRLMRUwEwYDVQQKEwxUREMgSW50ZXJuZXQxHTAbBgNVBAsTFFREQyBJbnRl -cm5ldCBSb290IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxLhA -vJHVYx/XmaCLDEAedLdInUaMArLgJF/wGROnN4NrXceO+YQwzho7+vvOi20jxsNu -Zp+Jpd/gQlBn+h9sHvTQBda/ytZO5GhgbEaqHF1j4QeGDmUApy6mcca8uYGoOn0a -0vnRrEvLznWv3Hv6gXPU/Lq9QYjUdLP5Xjg6PEOo0pVOd20TDJ2PeAG3WiAfAzc1 -4izbSysseLlJ28TQx5yc5IogCSEWVmb/Bexb4/DPqyQkXsN/cHoSxNK1EKC2IeGN -eGlVRGn1ypYcNIUXJXfi9i8nmHj9eQY6otZaQ8H/7AQ77hPv01ha/5Lr7K7a8jcD -R0G2l8ktCkEiu7vmpwIDAQABo4IBJTCCASEwEQYJYIZIAYb4QgEBBAQDAgAHMGUG -A1UdHwReMFwwWqBYoFakVDBSMQswCQYDVQQGEwJESzEVMBMGA1UEChMMVERDIElu -dGVybmV0MR0wGwYDVQQLExRUREMgSW50ZXJuZXQgUm9vdCBDQTENMAsGA1UEAxME -Q1JMMTArBgNVHRAEJDAigA8yMDAxMDQwNTE2MzMxN1qBDzIwMjEwNDA1MTcwMzE3 -WjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUbGQBx/2FbazI2p5QCIUItTxWqFAw -HQYDVR0OBBYEFGxkAcf9hW2syNqeUAiFCLU8VqhQMAwGA1UdEwQFMAMBAf8wHQYJ -KoZIhvZ9B0EABBAwDhsIVjUuMDo0LjADAgSQMA0GCSqGSIb3DQEBBQUAA4IBAQBO -Q8zR3R0QGwZ/t6T609lN+yOfI1Rb5osvBCiLtSdtiaHsmGnc540mgwV5dOy0uaOX -wTUA/RXaOYE6lTGQ3pfphqiZdwzlWqCE/xIWrG64jcN7ksKsLtB9KOy282A4aW8+ -2ARVPp7MVdK6/rtHBNcK2RYKNCn1WBPVT8+PVkuzHu7TmHnaCB4Mb7j4Fifvwm89 -9qNLPg7kbWzbO0ESm70NRyN/PErQr8Cv9u8btRXE64PECV90i9kR+8JWsTz4cMo0 -jUNAE4z9mQNUecYu6oah9jrUCbz0vGbMPVjQV0kK7iXiQe4T+Zs4NNEA9X7nlB38 -aQNiuJkFBT1reBK9sG9l ------END CERTIFICATE----- - -# Issuer: CN=UTN - DATACorp SGC O=The USERTRUST Network OU=http://www.usertrust.com -# Subject: CN=UTN - DATACorp SGC O=The USERTRUST Network OU=http://www.usertrust.com -# Label: "UTN DATACorp SGC Root CA" -# Serial: 91374294542884689855167577680241077609 -# MD5 Fingerprint: b3:a5:3e:77:21:6d:ac:4a:c0:c9:fb:d5:41:3d:ca:06 -# SHA1 Fingerprint: 58:11:9f:0e:12:82:87:ea:50:fd:d9:87:45:6f:4f:78:dc:fa:d6:d4 -# SHA256 Fingerprint: 85:fb:2f:91:dd:12:27:5a:01:45:b6:36:53:4f:84:02:4a:d6:8b:69:b8:ee:88:68:4f:f7:11:37:58:05:b3:48 ------BEGIN CERTIFICATE----- -MIIEXjCCA0agAwIBAgIQRL4Mi1AAIbQR0ypoBqmtaTANBgkqhkiG9w0BAQUFADCB -kzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug -Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho -dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xGzAZBgNVBAMTElVUTiAtIERBVEFDb3Jw -IFNHQzAeFw05OTA2MjQxODU3MjFaFw0xOTA2MjQxOTA2MzBaMIGTMQswCQYDVQQG -EwJVUzELMAkGA1UECBMCVVQxFzAVBgNVBAcTDlNhbHQgTGFrZSBDaXR5MR4wHAYD -VQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxITAfBgNVBAsTGGh0dHA6Ly93d3cu -dXNlcnRydXN0LmNvbTEbMBkGA1UEAxMSVVROIC0gREFUQUNvcnAgU0dDMIIBIjAN -BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3+5YEKIrblXEjr8uRgnn4AgPLit6 -E5Qbvfa2gI5lBZMAHryv4g+OGQ0SR+ysraP6LnD43m77VkIVni5c7yPeIbkFdicZ -D0/Ww5y0vpQZY/KmEQrrU0icvvIpOxboGqBMpsn0GFlowHDyUwDAXlCCpVZvNvlK -4ESGoE1O1kduSUrLZ9emxAW5jh70/P/N5zbgnAVssjMiFdC04MwXwLLA9P4yPykq -lXvY8qdOD1R8oQ2AswkDwf9c3V6aPryuvEeKaq5xyh+xKrhfQgUL7EYw0XILyulW -bfXv33i+Ybqypa4ETLyorGkVl73v67SMvzX41MPRKA5cOp9wGDMgd8SirwIDAQAB -o4GrMIGoMAsGA1UdDwQEAwIBxjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRT -MtGzz3/64PGgXYVOktKeRR20TzA9BgNVHR8ENjA0MDKgMKAuhixodHRwOi8vY3Js -LnVzZXJ0cnVzdC5jb20vVVROLURBVEFDb3JwU0dDLmNybDAqBgNVHSUEIzAhBggr -BgEFBQcDAQYKKwYBBAGCNwoDAwYJYIZIAYb4QgQBMA0GCSqGSIb3DQEBBQUAA4IB -AQAnNZcAiosovcYzMB4p/OL31ZjUQLtgyr+rFywJNn9Q+kHcrpY6CiM+iVnJowft -Gzet/Hy+UUla3joKVAgWRcKZsYfNjGjgaQPpxE6YsjuMFrMOoAyYUJuTqXAJyCyj -j98C5OBxOvG0I3KgqgHf35g+FFCgMSa9KOlaMCZ1+XtgHI3zzVAmbQQnmt/VDUVH -KWss5nbZqSl9Mt3JNjy9rjXxEZ4du5A/EkdOjtd+D2JzHVImOBwYSf0wdJrE5SIv -2MCN7ZF6TACPcn9d2t0bi0Vr591pl6jFVkwPDPafepE39peC4N1xaf92P2BNPM/3 -mfnGV/TJVTl4uix5yaaIK/QI ------END CERTIFICATE----- - # Issuer: CN=UTN-USERFirst-Hardware O=The USERTRUST Network OU=http://www.usertrust.com # Subject: CN=UTN-USERFirst-Hardware O=The USERTRUST Network OU=http://www.usertrust.com # Label: "UTN USERFirst Hardware Root CA" @@ -1490,84 +1002,6 @@ f1qbFFgBJ34TUMdrKuZoPL9coAob4Q566eKAw+np9v1sEZ7Q5SgnK1QyQhSCdeZK 8CtmdWOMovsEPoMOmzbwGOQmIMOM8CgHrTwXZoi1/baI -----END CERTIFICATE----- -# Issuer: CN=NetLock Uzleti (Class B) Tanusitvanykiado O=NetLock Halozatbiztonsagi Kft. OU=Tanusitvanykiadok -# Subject: CN=NetLock Uzleti (Class B) Tanusitvanykiado O=NetLock Halozatbiztonsagi Kft. OU=Tanusitvanykiadok -# Label: "NetLock Business (Class B) Root" -# Serial: 105 -# MD5 Fingerprint: 39:16:aa:b9:6a:41:e1:14:69:df:9e:6c:3b:72:dc:b6 -# SHA1 Fingerprint: 87:9f:4b:ee:05:df:98:58:3b:e3:60:d6:33:e7:0d:3f:fe:98:71:af -# SHA256 Fingerprint: 39:df:7b:68:2b:7b:93:8f:84:71:54:81:cc:de:8d:60:d8:f2:2e:c5:98:87:7d:0a:aa:c1:2b:59:18:2b:03:12 ------BEGIN CERTIFICATE----- -MIIFSzCCBLSgAwIBAgIBaTANBgkqhkiG9w0BAQQFADCBmTELMAkGA1UEBhMCSFUx -ETAPBgNVBAcTCEJ1ZGFwZXN0MScwJQYDVQQKEx5OZXRMb2NrIEhhbG96YXRiaXp0 -b25zYWdpIEtmdC4xGjAYBgNVBAsTEVRhbnVzaXR2YW55a2lhZG9rMTIwMAYDVQQD -EylOZXRMb2NrIFV6bGV0aSAoQ2xhc3MgQikgVGFudXNpdHZhbnlraWFkbzAeFw05 -OTAyMjUxNDEwMjJaFw0xOTAyMjAxNDEwMjJaMIGZMQswCQYDVQQGEwJIVTERMA8G -A1UEBxMIQnVkYXBlc3QxJzAlBgNVBAoTHk5ldExvY2sgSGFsb3phdGJpenRvbnNh -Z2kgS2Z0LjEaMBgGA1UECxMRVGFudXNpdHZhbnlraWFkb2sxMjAwBgNVBAMTKU5l -dExvY2sgVXpsZXRpIChDbGFzcyBCKSBUYW51c2l0dmFueWtpYWRvMIGfMA0GCSqG -SIb3DQEBAQUAA4GNADCBiQKBgQCx6gTsIKAjwo84YM/HRrPVG/77uZmeBNwcf4xK -gZjupNTKihe5In+DCnVMm8Bp2GQ5o+2So/1bXHQawEfKOml2mrriRBf8TKPV/riX -iK+IA4kfpPIEPsgHC+b5sy96YhQJRhTKZPWLgLViqNhr1nGTLbO/CVRY7QbrqHvc -Q7GhaQIDAQABo4ICnzCCApswEgYDVR0TAQH/BAgwBgEB/wIBBDAOBgNVHQ8BAf8E -BAMCAAYwEQYJYIZIAYb4QgEBBAQDAgAHMIICYAYJYIZIAYb4QgENBIICURaCAk1G -SUdZRUxFTSEgRXplbiB0YW51c2l0dmFueSBhIE5ldExvY2sgS2Z0LiBBbHRhbGFu -b3MgU3pvbGdhbHRhdGFzaSBGZWx0ZXRlbGVpYmVuIGxlaXJ0IGVsamFyYXNvayBh -bGFwamFuIGtlc3p1bHQuIEEgaGl0ZWxlc2l0ZXMgZm9seWFtYXRhdCBhIE5ldExv -Y2sgS2Z0LiB0ZXJtZWtmZWxlbG9zc2VnLWJpenRvc2l0YXNhIHZlZGkuIEEgZGln -aXRhbGlzIGFsYWlyYXMgZWxmb2dhZGFzYW5hayBmZWx0ZXRlbGUgYXogZWxvaXJ0 -IGVsbGVub3J6ZXNpIGVsamFyYXMgbWVndGV0ZWxlLiBBeiBlbGphcmFzIGxlaXJh -c2EgbWVndGFsYWxoYXRvIGEgTmV0TG9jayBLZnQuIEludGVybmV0IGhvbmxhcGph -biBhIGh0dHBzOi8vd3d3Lm5ldGxvY2submV0L2RvY3MgY2ltZW4gdmFneSBrZXJo -ZXRvIGF6IGVsbGVub3J6ZXNAbmV0bG9jay5uZXQgZS1tYWlsIGNpbWVuLiBJTVBP -UlRBTlQhIFRoZSBpc3N1YW5jZSBhbmQgdGhlIHVzZSBvZiB0aGlzIGNlcnRpZmlj -YXRlIGlzIHN1YmplY3QgdG8gdGhlIE5ldExvY2sgQ1BTIGF2YWlsYWJsZSBhdCBo -dHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIG9yIGJ5IGUtbWFpbCBhdCBjcHNA -bmV0bG9jay5uZXQuMA0GCSqGSIb3DQEBBAUAA4GBAATbrowXr/gOkDFOzT4JwG06 -sPgzTEdM43WIEJessDgVkcYplswhwG08pXTP2IKlOcNl40JwuyKQ433bNXbhoLXa -n3BukxowOR0w2y7jfLKRstE3Kfq51hdcR0/jHTjrn9V7lagonhVK0dHQKwCXoOKS -NitjrFgBazMpUIaD8QFI ------END CERTIFICATE----- - -# Issuer: CN=NetLock Expressz (Class C) Tanusitvanykiado O=NetLock Halozatbiztonsagi Kft. OU=Tanusitvanykiadok -# Subject: CN=NetLock Expressz (Class C) Tanusitvanykiado O=NetLock Halozatbiztonsagi Kft. OU=Tanusitvanykiadok -# Label: "NetLock Express (Class C) Root" -# Serial: 104 -# MD5 Fingerprint: 4f:eb:f1:f0:70:c2:80:63:5d:58:9f:da:12:3c:a9:c4 -# SHA1 Fingerprint: e3:92:51:2f:0a:cf:f5:05:df:f6:de:06:7f:75:37:e1:65:ea:57:4b -# SHA256 Fingerprint: 0b:5e:ed:4e:84:64:03:cf:55:e0:65:84:84:40:ed:2a:82:75:8b:f5:b9:aa:1f:25:3d:46:13:cf:a0:80:ff:3f ------BEGIN CERTIFICATE----- -MIIFTzCCBLigAwIBAgIBaDANBgkqhkiG9w0BAQQFADCBmzELMAkGA1UEBhMCSFUx -ETAPBgNVBAcTCEJ1ZGFwZXN0MScwJQYDVQQKEx5OZXRMb2NrIEhhbG96YXRiaXp0 -b25zYWdpIEtmdC4xGjAYBgNVBAsTEVRhbnVzaXR2YW55a2lhZG9rMTQwMgYDVQQD -EytOZXRMb2NrIEV4cHJlc3N6IChDbGFzcyBDKSBUYW51c2l0dmFueWtpYWRvMB4X -DTk5MDIyNTE0MDgxMVoXDTE5MDIyMDE0MDgxMVowgZsxCzAJBgNVBAYTAkhVMREw -DwYDVQQHEwhCdWRhcGVzdDEnMCUGA1UEChMeTmV0TG9jayBIYWxvemF0Yml6dG9u -c2FnaSBLZnQuMRowGAYDVQQLExFUYW51c2l0dmFueWtpYWRvazE0MDIGA1UEAxMr -TmV0TG9jayBFeHByZXNzeiAoQ2xhc3MgQykgVGFudXNpdHZhbnlraWFkbzCBnzAN -BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA6+ywbGGKIyWvYCDj2Z/8kwvbXY2wobNA -OoLO/XXgeDIDhlqGlZHtU/qdQPzm6N3ZW3oDvV3zOwzDUXmbrVWg6dADEK8KuhRC -2VImESLH0iDMgqSaqf64gXadarfSNnU+sYYJ9m5tfk63euyucYT2BDMIJTLrdKwW -RMbkQJMdf60CAwEAAaOCAp8wggKbMBIGA1UdEwEB/wQIMAYBAf8CAQQwDgYDVR0P -AQH/BAQDAgAGMBEGCWCGSAGG+EIBAQQEAwIABzCCAmAGCWCGSAGG+EIBDQSCAlEW -ggJNRklHWUVMRU0hIEV6ZW4gdGFudXNpdHZhbnkgYSBOZXRMb2NrIEtmdC4gQWx0 -YWxhbm9zIFN6b2xnYWx0YXRhc2kgRmVsdGV0ZWxlaWJlbiBsZWlydCBlbGphcmFz -b2sgYWxhcGphbiBrZXN6dWx0LiBBIGhpdGVsZXNpdGVzIGZvbHlhbWF0YXQgYSBO -ZXRMb2NrIEtmdC4gdGVybWVrZmVsZWxvc3NlZy1iaXp0b3NpdGFzYSB2ZWRpLiBB -IGRpZ2l0YWxpcyBhbGFpcmFzIGVsZm9nYWRhc2FuYWsgZmVsdGV0ZWxlIGF6IGVs -b2lydCBlbGxlbm9yemVzaSBlbGphcmFzIG1lZ3RldGVsZS4gQXogZWxqYXJhcyBs -ZWlyYXNhIG1lZ3RhbGFsaGF0byBhIE5ldExvY2sgS2Z0LiBJbnRlcm5ldCBob25s -YXBqYW4gYSBodHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIGNpbWVuIHZhZ3kg -a2VyaGV0byBheiBlbGxlbm9yemVzQG5ldGxvY2submV0IGUtbWFpbCBjaW1lbi4g -SU1QT1JUQU5UISBUaGUgaXNzdWFuY2UgYW5kIHRoZSB1c2Ugb2YgdGhpcyBjZXJ0 -aWZpY2F0ZSBpcyBzdWJqZWN0IHRvIHRoZSBOZXRMb2NrIENQUyBhdmFpbGFibGUg -YXQgaHR0cHM6Ly93d3cubmV0bG9jay5uZXQvZG9jcyBvciBieSBlLW1haWwgYXQg -Y3BzQG5ldGxvY2submV0LjANBgkqhkiG9w0BAQQFAAOBgQAQrX/XDDKACtiG8XmY -ta3UzbM2xJZIwVzNmtkFLp++UOv0JhQQLdRmF/iewSf98e3ke0ugbLWrmldwpu2g -pO0u9f38vf5NNwgMvOOWgyL1SRt/Syu0VMGAfJlOHdCM7tCs5ZL6dVb+ZKATj7i4 -Fp1hBWeAyNDYpQcCNJgEjTME1A== ------END CERTIFICATE----- - # Issuer: CN=XRamp Global Certification Authority O=XRamp Security Services Inc OU=www.xrampsecurity.com # Subject: CN=XRamp Global Certification Authority O=XRamp Security Services Inc OU=www.xrampsecurity.com # Label: "XRamp Global CA Root" @@ -1980,71 +1414,6 @@ rscL9yuwNwXsvFcj4jjSm2jzVhKIT0J8uDHEtdvkyCE06UgRNe76x5JXxZ805Mf2 9w4LTJxoeHtxMcfrHuBnQfO3oKfN5XozNmr6mis= -----END CERTIFICATE----- -# Issuer: CN=TÜRKTRUST Elektronik Sertifika Hizmet Sağlayıcısı O=(c) 2005 TÜRKTRUST Bilgi İletişim ve Bilişim Güvenliği Hizmetleri A.Ş. -# Subject: CN=TÜRKTRUST Elektronik Sertifika Hizmet Sağlayıcısı O=(c) 2005 TÜRKTRUST Bilgi İletişim ve Bilişim Güvenliği Hizmetleri A.Ş. -# Label: "TURKTRUST Certificate Services Provider Root 1" -# Serial: 1 -# MD5 Fingerprint: f1:6a:22:18:c9:cd:df:ce:82:1d:1d:b7:78:5c:a9:a5 -# SHA1 Fingerprint: 79:98:a3:08:e1:4d:65:85:e6:c2:1e:15:3a:71:9f:ba:5a:d3:4a:d9 -# SHA256 Fingerprint: 44:04:e3:3b:5e:14:0d:cf:99:80:51:fd:fc:80:28:c7:c8:16:15:c5:ee:73:7b:11:1b:58:82:33:a9:b5:35:a0 ------BEGIN CERTIFICATE----- -MIID+zCCAuOgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBtzE/MD0GA1UEAww2VMOc -UktUUlVTVCBFbGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sx -c8SxMQswCQYDVQQGDAJUUjEPMA0GA1UEBwwGQU5LQVJBMVYwVAYDVQQKDE0oYykg -MjAwNSBUw5xSS1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUgQmlsacWfaW0gR8O8 -dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLjAeFw0wNTA1MTMxMDI3MTdaFw0xNTAz -MjIxMDI3MTdaMIG3MT8wPQYDVQQDDDZUw5xSS1RSVVNUIEVsZWt0cm9uaWsgU2Vy -dGlmaWthIEhpem1ldCBTYcSfbGF5xLFjxLFzxLExCzAJBgNVBAYMAlRSMQ8wDQYD -VQQHDAZBTktBUkExVjBUBgNVBAoMTShjKSAyMDA1IFTDnFJLVFJVU1QgQmlsZ2kg -xLBsZXRpxZ9pbSB2ZSBCaWxpxZ9pbSBHw7x2ZW5sacSfaSBIaXptZXRsZXJpIEEu -xZ4uMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAylIF1mMD2Bxf3dJ7 -XfIMYGFbazt0K3gNfUW9InTojAPBxhEqPZW8qZSwu5GXyGl8hMW0kWxsE2qkVa2k -heiVfrMArwDCBRj1cJ02i67L5BuBf5OI+2pVu32Fks66WJ/bMsW9Xe8iSi9BB35J -YbOG7E6mQW6EvAPs9TscyB/C7qju6hJKjRTP8wrgUDn5CDX4EVmt5yLqS8oUBt5C -urKZ8y1UiBAG6uEaPj1nH/vO+3yC6BFdSsG5FOpU2WabfIl9BJpiyelSPJ6c79L1 -JuTm5Rh8i27fbMx4W09ysstcP4wFjdFMjK2Sx+F4f2VsSQZQLJ4ywtdKxnWKWU51 -b0dewQIDAQABoxAwDjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQAV -9VX/N5aAWSGk/KEVTCD21F/aAyT8z5Aa9CEKmu46sWrv7/hg0Uw2ZkUd82YCdAR7 -kjCo3gp2D++Vbr3JN+YaDayJSFvMgzbC9UZcWYJWtNX+I7TYVBxEq8Sn5RTOPEFh -fEPmzcSBCYsk+1Ql1haolgxnB2+zUEfjHCQo3SqYpGH+2+oSN7wBGjSFvW5P55Fy -B0SFHljKVETd96y5y4khctuPwGkplyqjrhgjlxxBKot8KsF8kOipKMDTkcatKIdA -aLX/7KfS0zgYnNN9aV3wxqUeJBujR/xpB2jn5Jq07Q+hh4cCzofSSE7hvP/L8XKS -RGQDJereW26fyfJOrN3H ------END CERTIFICATE----- - -# Issuer: CN=TÜRKTRUST Elektronik Sertifika Hizmet Sağlayıcısı O=TÜRKTRUST Bilgi İletişim ve Bilişim Güvenliği Hizmetleri A.Ş. (c) Kasım 2005 -# Subject: CN=TÜRKTRUST Elektronik Sertifika Hizmet Sağlayıcısı O=TÜRKTRUST Bilgi İletişim ve Bilişim Güvenliği Hizmetleri A.Ş. (c) Kasım 2005 -# Label: "TURKTRUST Certificate Services Provider Root 2" -# Serial: 1 -# MD5 Fingerprint: 37:a5:6e:d4:b1:25:84:97:b7:fd:56:15:7a:f9:a2:00 -# SHA1 Fingerprint: b4:35:d4:e1:11:9d:1c:66:90:a7:49:eb:b3:94:bd:63:7b:a7:82:b7 -# SHA256 Fingerprint: c4:70:cf:54:7e:23:02:b9:77:fb:29:dd:71:a8:9a:7b:6c:1f:60:77:7b:03:29:f5:60:17:f3:28:bf:4f:6b:e6 ------BEGIN CERTIFICATE----- -MIIEPDCCAySgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBvjE/MD0GA1UEAww2VMOc -UktUUlVTVCBFbGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sx -c8SxMQswCQYDVQQGEwJUUjEPMA0GA1UEBwwGQW5rYXJhMV0wWwYDVQQKDFRUw5xS -S1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUgQmlsacWfaW0gR8O8dmVubGnEn2kg -SGl6bWV0bGVyaSBBLsWeLiAoYykgS2FzxLFtIDIwMDUwHhcNMDUxMTA3MTAwNzU3 -WhcNMTUwOTE2MTAwNzU3WjCBvjE/MD0GA1UEAww2VMOcUktUUlVTVCBFbGVrdHJv -bmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMQswCQYDVQQGEwJU -UjEPMA0GA1UEBwwGQW5rYXJhMV0wWwYDVQQKDFRUw5xSS1RSVVNUIEJpbGdpIMSw -bGV0acWfaW0gdmUgQmlsacWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWe -LiAoYykgS2FzxLFtIDIwMDUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB -AQCpNn7DkUNMwxmYCMjHWHtPFoylzkkBH3MOrHUTpvqeLCDe2JAOCtFp0if7qnef -J1Il4std2NiDUBd9irWCPwSOtNXwSadktx4uXyCcUHVPr+G1QRT0mJKIx+XlZEdh -R3n9wFHxwZnn3M5q+6+1ATDcRhzviuyV79z/rxAc653YsKpqhRgNF8k+v/Gb0AmJ -Qv2gQrSdiVFVKc8bcLyEVK3BEx+Y9C52YItdP5qtygy/p1Zbj3e41Z55SZI/4PGX -JHpsmxcPbe9TmJEr5A++WXkHeLuXlfSfadRYhwqp48y2WBmfJiGxxFmNskF1wK1p -zpwACPI2/z7woQ8arBT9pmAPAgMBAAGjQzBBMB0GA1UdDgQWBBTZN7NOBf3Zz58S -Fq62iS/rJTqIHDAPBgNVHQ8BAf8EBQMDBwYAMA8GA1UdEwEB/wQFMAMBAf8wDQYJ -KoZIhvcNAQEFBQADggEBAHJglrfJ3NgpXiOFX7KzLXb7iNcX/nttRbj2hWyfIvwq -ECLsqrkw9qtY1jkQMZkpAL2JZkH7dN6RwRgLn7Vhy506vvWolKMiVW4XSf/SKfE4 -Jl3vpao6+XF75tpYHdN0wgH6PmlYX63LaL4ULptswLbcoCb6dxriJNoaN+BnrdFz -gw2lGh1uEpJ+hGIAF728JRhX8tepb1mIvDS3LoV4nZbcFMMsilKbloxSZj2GFotH -uFEJjOp9zYhys2AzsfAKRO8P9Qk3iCQOLGsgOqL6EfJANZxEaGM7rDNvY7wsu/LS -y3Z9fYjYHcgFHW68lKlmjHdxx/qR+i9Rnuk5UrbnBEI= ------END CERTIFICATE----- - # Issuer: CN=SwissSign Gold CA - G2 O=SwissSign AG # Subject: CN=SwissSign Gold CA - G2 O=SwissSign AG # Label: "SwissSign Gold CA - G2" @@ -2583,152 +1952,6 @@ t0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg== -----END CERTIFICATE----- -# Issuer: CN=AC Raíz Certicámara S.A. O=Sociedad Cameral de Certificación Digital - Certicámara S.A. -# Subject: CN=AC Raíz Certicámara S.A. O=Sociedad Cameral de Certificación Digital - Certicámara S.A. -# Label: "AC Ra\xC3\xADz Certic\xC3\xA1mara S.A." -# Serial: 38908203973182606954752843738508300 -# MD5 Fingerprint: 93:2a:3e:f6:fd:23:69:0d:71:20:d4:2b:47:99:2b:a6 -# SHA1 Fingerprint: cb:a1:c5:f8:b0:e3:5e:b8:b9:45:12:d3:f9:34:a2:e9:06:10:d3:36 -# SHA256 Fingerprint: a6:c5:1e:0d:a5:ca:0a:93:09:d2:e4:c0:e4:0c:2a:f9:10:7a:ae:82:03:85:7f:e1:98:e3:e7:69:e3:43:08:5c ------BEGIN CERTIFICATE----- -MIIGZjCCBE6gAwIBAgIPB35Sk3vgFeNX8GmMy+wMMA0GCSqGSIb3DQEBBQUAMHsx -CzAJBgNVBAYTAkNPMUcwRQYDVQQKDD5Tb2NpZWRhZCBDYW1lcmFsIGRlIENlcnRp -ZmljYWNpw7NuIERpZ2l0YWwgLSBDZXJ0aWPDoW1hcmEgUy5BLjEjMCEGA1UEAwwa -QUMgUmHDrXogQ2VydGljw6FtYXJhIFMuQS4wHhcNMDYxMTI3MjA0NjI5WhcNMzAw -NDAyMjE0MjAyWjB7MQswCQYDVQQGEwJDTzFHMEUGA1UECgw+U29jaWVkYWQgQ2Ft -ZXJhbCBkZSBDZXJ0aWZpY2FjacOzbiBEaWdpdGFsIC0gQ2VydGljw6FtYXJhIFMu -QS4xIzAhBgNVBAMMGkFDIFJhw616IENlcnRpY8OhbWFyYSBTLkEuMIICIjANBgkq -hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAq2uJo1PMSCMI+8PPUZYILrgIem08kBeG -qentLhM0R7LQcNzJPNCNyu5LF6vQhbCnIwTLqKL85XXbQMpiiY9QngE9JlsYhBzL -fDe3fezTf3MZsGqy2IiKLUV0qPezuMDU2s0iiXRNWhU5cxh0T7XrmafBHoi0wpOQ -Y5fzp6cSsgkiBzPZkc0OnB8OIMfuuzONj8LSWKdf/WU34ojC2I+GdV75LaeHM/J4 -Ny+LvB2GNzmxlPLYvEqcgxhaBvzz1NS6jBUJJfD5to0EfhcSM2tXSExP2yYe68yQ -54v5aHxwD6Mq0Do43zeX4lvegGHTgNiRg0JaTASJaBE8rF9ogEHMYELODVoqDA+b -MMCm8Ibbq0nXl21Ii/kDwFJnmxL3wvIumGVC2daa49AZMQyth9VXAnow6IYm+48j -ilSH5L887uvDdUhfHjlvgWJsxS3EF1QZtzeNnDeRyPYL1epjb4OsOMLzP96a++Ej -YfDIJss2yKHzMI+ko6Kh3VOz3vCaMh+DkXkwwakfU5tTohVTP92dsxA7SH2JD/zt -A/X7JWR1DhcZDY8AFmd5ekD8LVkH2ZD6mq093ICK5lw1omdMEWux+IBkAC1vImHF -rEsm5VoQgpukg3s0956JkSCXjrdCx2bD0Omk1vUgjcTDlaxECp1bczwmPS9KvqfJ -pxAe+59QafMCAwEAAaOB5jCB4zAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQE -AwIBBjAdBgNVHQ4EFgQU0QnQ6dfOeXRU+Tows/RtLAMDG2gwgaAGA1UdIASBmDCB -lTCBkgYEVR0gADCBiTArBggrBgEFBQcCARYfaHR0cDovL3d3dy5jZXJ0aWNhbWFy -YS5jb20vZHBjLzBaBggrBgEFBQcCAjBOGkxMaW1pdGFjaW9uZXMgZGUgZ2FyYW50 -7WFzIGRlIGVzdGUgY2VydGlmaWNhZG8gc2UgcHVlZGVuIGVuY29udHJhciBlbiBs -YSBEUEMuMA0GCSqGSIb3DQEBBQUAA4ICAQBclLW4RZFNjmEfAygPU3zmpFmps4p6 -xbD/CHwso3EcIRNnoZUSQDWDg4902zNc8El2CoFS3UnUmjIz75uny3XlesuXEpBc -unvFm9+7OSPI/5jOCk0iAUgHforA1SBClETvv3eiiWdIG0ADBaGJ7M9i4z0ldma/ -Jre7Ir5v/zlXdLp6yQGVwZVR6Kss+LGGIOk/yzVb0hfpKv6DExdA7ohiZVvVO2Dp -ezy4ydV/NgIlqmjCMRW3MGXrfx1IebHPOeJCgBbT9ZMj/EyXyVo3bHwi2ErN0o42 -gzmRkBDI8ck1fj+404HGIGQatlDCIaR43NAvO2STdPCWkPHv+wlaNECW8DYSwaN0 -jJN+Qd53i+yG2dIPPy3RzECiiWZIHiCznCNZc6lEc7wkeZBWN7PGKX6jD/EpOe9+ -XCgycDWs2rjIdWb8m0w5R44bb5tNAlQiM+9hup4phO9OSzNHdpdqy35f/RWmnkJD -W2ZaiogN9xa5P1FlK2Zqi9E4UqLWRhH6/JocdJ6PlwsCT2TG9WjTSy3/pDceiz+/ -RL5hRqGEPQgnTIEgd4kI6mdAXmwIUV80WoyWaM3X94nCHNMyAK9Sy9NgWyo6R35r -MDOhYil/SrnhLecUIw4OGEfhefwVVdCx/CVxY3UzHCMrr1zZ7Ud3YA47Dx7SwNxk -BYn8eNZcLCZDqQ== ------END CERTIFICATE----- - -# Issuer: CN=TC TrustCenter Class 2 CA II O=TC TrustCenter GmbH OU=TC TrustCenter Class 2 CA -# Subject: CN=TC TrustCenter Class 2 CA II O=TC TrustCenter GmbH OU=TC TrustCenter Class 2 CA -# Label: "TC TrustCenter Class 2 CA II" -# Serial: 941389028203453866782103406992443 -# MD5 Fingerprint: ce:78:33:5c:59:78:01:6e:18:ea:b9:36:a0:b9:2e:23 -# SHA1 Fingerprint: ae:50:83:ed:7c:f4:5c:bc:8f:61:c6:21:fe:68:5d:79:42:21:15:6e -# SHA256 Fingerprint: e6:b8:f8:76:64:85:f8:07:ae:7f:8d:ac:16:70:46:1f:07:c0:a1:3e:ef:3a:1f:f7:17:53:8d:7a:ba:d3:91:b4 ------BEGIN CERTIFICATE----- -MIIEqjCCA5KgAwIBAgIOLmoAAQACH9dSISwRXDswDQYJKoZIhvcNAQEFBQAwdjEL -MAkGA1UEBhMCREUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxIjAgBgNV -BAsTGVRDIFRydXN0Q2VudGVyIENsYXNzIDIgQ0ExJTAjBgNVBAMTHFRDIFRydXN0 -Q2VudGVyIENsYXNzIDIgQ0EgSUkwHhcNMDYwMTEyMTQzODQzWhcNMjUxMjMxMjI1 -OTU5WjB2MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMgVHJ1c3RDZW50ZXIgR21i -SDEiMCAGA1UECxMZVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMiBDQTElMCMGA1UEAxMc -VEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMiBDQSBJSTCCASIwDQYJKoZIhvcNAQEBBQAD -ggEPADCCAQoCggEBAKuAh5uO8MN8h9foJIIRszzdQ2Lu+MNF2ujhoF/RKrLqk2jf -tMjWQ+nEdVl//OEd+DFwIxuInie5e/060smp6RQvkL4DUsFJzfb95AhmC1eKokKg -uNV/aVyQMrKXDcpK3EY+AlWJU+MaWss2xgdW94zPEfRMuzBwBJWl9jmM/XOBCH2J -XjIeIqkiRUuwZi4wzJ9l/fzLganx4Duvo4bRierERXlQXa7pIXSSTYtZgo+U4+lK -8edJsBTj9WLL1XK9H7nSn6DNqPoByNkN39r8R52zyFTfSUrxIan+GE7uSNQZu+99 -5OKdy1u2bv/jzVrndIIFuoAlOMvkaZ6vQaoahPUCAwEAAaOCATQwggEwMA8GA1Ud -EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTjq1RMgKHbVkO3 -kUrL84J6E1wIqzCB7QYDVR0fBIHlMIHiMIHfoIHcoIHZhjVodHRwOi8vd3d3LnRy -dXN0Y2VudGVyLmRlL2NybC92Mi90Y19jbGFzc18yX2NhX0lJLmNybIaBn2xkYXA6 -Ly93d3cudHJ1c3RjZW50ZXIuZGUvQ049VEMlMjBUcnVzdENlbnRlciUyMENsYXNz -JTIwMiUyMENBJTIwSUksTz1UQyUyMFRydXN0Q2VudGVyJTIwR21iSCxPVT1yb290 -Y2VydHMsREM9dHJ1c3RjZW50ZXIsREM9ZGU/Y2VydGlmaWNhdGVSZXZvY2F0aW9u -TGlzdD9iYXNlPzANBgkqhkiG9w0BAQUFAAOCAQEAjNfffu4bgBCzg/XbEeprS6iS -GNn3Bzn1LL4GdXpoUxUc6krtXvwjshOg0wn/9vYua0Fxec3ibf2uWWuFHbhOIprt -ZjluS5TmVfwLG4t3wVMTZonZKNaL80VKY7f9ewthXbhtvsPcW3nS7Yblok2+XnR8 -au0WOB9/WIFaGusyiC2y8zl3gK9etmF1KdsjTYjKUCjLhdLTEKJZbtOTVAB6okaV -hgWcqRmY5TFyDADiZ9lA4CQze28suVyrZZ0srHbqNZn1l7kPJOzHdiEoZa5X6AeI -dUpWoNIFOqTmjZKILPPy4cHGYdtBxceb9w4aUUXCYWvcZCcXjFq32nQozZfkvQ== ------END CERTIFICATE----- - -# Issuer: CN=TC TrustCenter Class 3 CA II O=TC TrustCenter GmbH OU=TC TrustCenter Class 3 CA -# Subject: CN=TC TrustCenter Class 3 CA II O=TC TrustCenter GmbH OU=TC TrustCenter Class 3 CA -# Label: "TC TrustCenter Class 3 CA II" -# Serial: 1506523511417715638772220530020799 -# MD5 Fingerprint: 56:5f:aa:80:61:12:17:f6:67:21:e6:2b:6d:61:56:8e -# SHA1 Fingerprint: 80:25:ef:f4:6e:70:c8:d4:72:24:65:84:fe:40:3b:8a:8d:6a:db:f5 -# SHA256 Fingerprint: 8d:a0:84:fc:f9:9c:e0:77:22:f8:9b:32:05:93:98:06:fa:5c:b8:11:e1:c8:13:f6:a1:08:c7:d3:36:b3:40:8e ------BEGIN CERTIFICATE----- -MIIEqjCCA5KgAwIBAgIOSkcAAQAC5aBd1j8AUb8wDQYJKoZIhvcNAQEFBQAwdjEL -MAkGA1UEBhMCREUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxIjAgBgNV -BAsTGVRDIFRydXN0Q2VudGVyIENsYXNzIDMgQ0ExJTAjBgNVBAMTHFRDIFRydXN0 -Q2VudGVyIENsYXNzIDMgQ0EgSUkwHhcNMDYwMTEyMTQ0MTU3WhcNMjUxMjMxMjI1 -OTU5WjB2MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMgVHJ1c3RDZW50ZXIgR21i -SDEiMCAGA1UECxMZVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMyBDQTElMCMGA1UEAxMc -VEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMyBDQSBJSTCCASIwDQYJKoZIhvcNAQEBBQAD -ggEPADCCAQoCggEBALTgu1G7OVyLBMVMeRwjhjEQY0NVJz/GRcekPewJDRoeIMJW -Ht4bNwcwIi9v8Qbxq63WyKthoy9DxLCyLfzDlml7forkzMA5EpBCYMnMNWju2l+Q -Vl/NHE1bWEnrDgFPZPosPIlY2C8u4rBo6SI7dYnWRBpl8huXJh0obazovVkdKyT2 -1oQDZogkAHhg8fir/gKya/si+zXmFtGt9i4S5Po1auUZuV3bOx4a+9P/FRQI2Alq -ukWdFHlgfa9Aigdzs5OW03Q0jTo3Kd5c7PXuLjHCINy+8U9/I1LZW+Jk2ZyqBwi1 -Rb3R0DHBq1SfqdLDYmAD8bs5SpJKPQq5ncWg/jcCAwEAAaOCATQwggEwMA8GA1Ud -EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTUovyfs8PYA9NX -XAek0CSnwPIA1DCB7QYDVR0fBIHlMIHiMIHfoIHcoIHZhjVodHRwOi8vd3d3LnRy -dXN0Y2VudGVyLmRlL2NybC92Mi90Y19jbGFzc18zX2NhX0lJLmNybIaBn2xkYXA6 -Ly93d3cudHJ1c3RjZW50ZXIuZGUvQ049VEMlMjBUcnVzdENlbnRlciUyMENsYXNz -JTIwMyUyMENBJTIwSUksTz1UQyUyMFRydXN0Q2VudGVyJTIwR21iSCxPVT1yb290 -Y2VydHMsREM9dHJ1c3RjZW50ZXIsREM9ZGU/Y2VydGlmaWNhdGVSZXZvY2F0aW9u -TGlzdD9iYXNlPzANBgkqhkiG9w0BAQUFAAOCAQEANmDkcPcGIEPZIxpC8vijsrlN -irTzwppVMXzEO2eatN9NDoqTSheLG43KieHPOh6sHfGcMrSOWXaiQYUlN6AT0PV8 -TtXqluJucsG7Kv5sbviRmEb8yRtXW+rIGjs/sFGYPAfaLFkB2otE6OF0/ado3VS6 -g0bsyEa1+K+XwDsJHI/OcpY9M1ZwvJbL2NV9IJqDnxrcOfHFcqMRA/07QlIp2+gB -95tejNaNhk4Z+rwcvsUhpYeeeC422wlxo3I0+GzjBgnyXlal092Y+tTmBvTwtiBj -S+opvaqCZh77gaqnN60TGOaSw4HBM7uIHqHn4rS9MWwOUT1v+5ZWgOI2F9Hc5A== ------END CERTIFICATE----- - -# Issuer: CN=TC TrustCenter Universal CA I O=TC TrustCenter GmbH OU=TC TrustCenter Universal CA -# Subject: CN=TC TrustCenter Universal CA I O=TC TrustCenter GmbH OU=TC TrustCenter Universal CA -# Label: "TC TrustCenter Universal CA I" -# Serial: 601024842042189035295619584734726 -# MD5 Fingerprint: 45:e1:a5:72:c5:a9:36:64:40:9e:f5:e4:58:84:67:8c -# SHA1 Fingerprint: 6b:2f:34:ad:89:58:be:62:fd:b0:6b:5c:ce:bb:9d:d9:4f:4e:39:f3 -# SHA256 Fingerprint: eb:f3:c0:2a:87:89:b1:fb:7d:51:19:95:d6:63:b7:29:06:d9:13:ce:0d:5e:10:56:8a:8a:77:e2:58:61:67:e7 ------BEGIN CERTIFICATE----- -MIID3TCCAsWgAwIBAgIOHaIAAQAC7LdggHiNtgYwDQYJKoZIhvcNAQEFBQAweTEL -MAkGA1UEBhMCREUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxJDAiBgNV -BAsTG1RDIFRydXN0Q2VudGVyIFVuaXZlcnNhbCBDQTEmMCQGA1UEAxMdVEMgVHJ1 -c3RDZW50ZXIgVW5pdmVyc2FsIENBIEkwHhcNMDYwMzIyMTU1NDI4WhcNMjUxMjMx -MjI1OTU5WjB5MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMgVHJ1c3RDZW50ZXIg -R21iSDEkMCIGA1UECxMbVEMgVHJ1c3RDZW50ZXIgVW5pdmVyc2FsIENBMSYwJAYD -VQQDEx1UQyBUcnVzdENlbnRlciBVbml2ZXJzYWwgQ0EgSTCCASIwDQYJKoZIhvcN -AQEBBQADggEPADCCAQoCggEBAKR3I5ZEr5D0MacQ9CaHnPM42Q9e3s9B6DGtxnSR -JJZ4Hgmgm5qVSkr1YnwCqMqs+1oEdjneX/H5s7/zA1hV0qq34wQi0fiU2iIIAI3T -fCZdzHd55yx4Oagmcw6iXSVphU9VDprvxrlE4Vc93x9UIuVvZaozhDrzznq+VZeu -jRIPFDPiUHDDSYcTvFHe15gSWu86gzOSBnWLknwSaHtwag+1m7Z3W0hZneTvWq3z -wZ7U10VOylY0Ibw+F1tvdwxIAUMpsN0/lm7mlaoMwCC2/T42J5zjXM9OgdwZu5GQ -fezmlwQek8wiSdeXhrYTCjxDI3d+8NzmzSQfO4ObNDqDNOMCAwEAAaNjMGEwHwYD -VR0jBBgwFoAUkqR1LKSevoFE63n8isWVpesQdXMwDwYDVR0TAQH/BAUwAwEB/zAO -BgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFJKkdSyknr6BROt5/IrFlaXrEHVzMA0G -CSqGSIb3DQEBBQUAA4IBAQAo0uCG1eb4e/CX3CJrO5UUVg8RMKWaTzqwOuAGy2X1 -7caXJ/4l8lfmXpWMPmRgFVp/Lw0BxbFg/UU1z/CyvwbZ71q+s2IhtNerNXxTPqYn -8aEt2hojnczd7Dwtnic0XQ/CNnm8yUpiLe1r2X1BQ3y2qsrtYbE3ghUJGooWMNjs -ydZHcnhLEEYUjl8Or+zHL6sQ17bxbuyGssLoDZJz3KL0Dzq/YSMQiZxIQG5wALPT -ujdEWBF6AmqI8Dc08BnprNRlc/ZpjGSUOnmFKbAWKwyCPwacx/0QK54PLLae4xW/ -2TYcuiUaUj0a7CIMHOCkoj3w6DnPgcB77V0fb8XQC9eY ------END CERTIFICATE----- - # Issuer: CN=Deutsche Telekom Root CA 2 O=Deutsche Telekom AG OU=T-TeleSec Trust Center # Subject: CN=Deutsche Telekom Root CA 2 O=Deutsche Telekom AG OU=T-TeleSec Trust Center # Label: "Deutsche Telekom Root CA 2" @@ -2759,36 +1982,6 @@ xbrYNuSD7Odlt79jWvNGr4GUN9RBjNYj1h7P9WgbRGOiWrqnNVmh5XAFmw4jV5mU Cm26OWMohpLzGITY+9HPBVZkVw== -----END CERTIFICATE----- -# Issuer: CN=ComSign Secured CA O=ComSign -# Subject: CN=ComSign Secured CA O=ComSign -# Label: "ComSign Secured CA" -# Serial: 264725503855295744117309814499492384489 -# MD5 Fingerprint: 40:01:25:06:8d:21:43:6a:0e:43:00:9c:e7:43:f3:d5 -# SHA1 Fingerprint: f9:cd:0e:2c:da:76:24:c1:8f:bd:f0:f0:ab:b6:45:b8:f7:fe:d5:7a -# SHA256 Fingerprint: 50:79:41:c7:44:60:a0:b4:70:86:22:0d:4e:99:32:57:2a:b5:d1:b5:bb:cb:89:80:ab:1c:b1:76:51:a8:44:d2 ------BEGIN CERTIFICATE----- -MIIDqzCCApOgAwIBAgIRAMcoRwmzuGxFjB36JPU2TukwDQYJKoZIhvcNAQEFBQAw -PDEbMBkGA1UEAxMSQ29tU2lnbiBTZWN1cmVkIENBMRAwDgYDVQQKEwdDb21TaWdu -MQswCQYDVQQGEwJJTDAeFw0wNDAzMjQxMTM3MjBaFw0yOTAzMTYxNTA0NTZaMDwx -GzAZBgNVBAMTEkNvbVNpZ24gU2VjdXJlZCBDQTEQMA4GA1UEChMHQ29tU2lnbjEL -MAkGA1UEBhMCSUwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDGtWhf -HZQVw6QIVS3joFd67+l0Kru5fFdJGhFeTymHDEjWaueP1H5XJLkGieQcPOqs49oh -gHMhCu95mGwfCP+hUH3ymBvJVG8+pSjsIQQPRbsHPaHA+iqYHU4Gk/v1iDurX8sW -v+bznkqH7Rnqwp9D5PGBpX8QTz7RSmKtUxvLg/8HZaWSLWapW7ha9B20IZFKF3ue -Mv5WJDmyVIRD9YTC2LxBkMyd1mja6YJQqTtoz7VdApRgFrFD2UNd3V2Hbuq7s8lr -9gOUCXDeFhF6K+h2j0kQmHe5Y1yLM5d19guMsqtb3nQgJT/j8xH5h2iGNXHDHYwt -6+UarA9z1YJZQIDTAgMBAAGjgacwgaQwDAYDVR0TBAUwAwEB/zBEBgNVHR8EPTA7 -MDmgN6A1hjNodHRwOi8vZmVkaXIuY29tc2lnbi5jby5pbC9jcmwvQ29tU2lnblNl -Y3VyZWRDQS5jcmwwDgYDVR0PAQH/BAQDAgGGMB8GA1UdIwQYMBaAFMFL7XC29z58 -ADsAj8c+DkWfHl3sMB0GA1UdDgQWBBTBS+1wtvc+fAA7AI/HPg5Fnx5d7DANBgkq -hkiG9w0BAQUFAAOCAQEAFs/ukhNQq3sUnjO2QiBq1BW9Cav8cujvR3qQrFHBZE7p -iL1DRYHjZiM/EoZNGeQFsOY3wo3aBijJD4mkU6l1P7CW+6tMM1X5eCZGbxs2mPtC -dsGCuY7e+0X5YxtiOzkGynd6qDwJz2w2PQ8KRUtpFhpFfTMDZflScZAmlaxMDPWL -kz/MdXSFmLr/YnpNH4n+rr2UAJm/EaXc4HnFFgt9AmEd6oX5AhVP51qJThRv4zdL -hfXBPGHg/QVBspJ/wx2g0K5SZGBrGMYmnNj1ZOQ2GmKfig8+/21OGVZOIJFsnzQz -OjRXUDpvgV4GxvU+fE6OK85lBi5d0ipTdF7Tbieejw== ------END CERTIFICATE----- - # Issuer: CN=Cybertrust Global Root O=Cybertrust, Inc # Subject: CN=Cybertrust Global Root O=Cybertrust, Inc # Label: "Cybertrust Global Root" @@ -2926,34 +2119,6 @@ h7U/2k3ZIQAw3pDaDtMaSKk+hQsUi4y8QZ5q9w5wwDX3OaJdZtB7WZ+oRxKaJyOk LY4ng5IgodcVf/EuGO70SH8vf/GhGLWhC5SgYiAynB321O+/TIho -----END CERTIFICATE----- -# Issuer: CN=Buypass Class 3 CA 1 O=Buypass AS-983163327 -# Subject: CN=Buypass Class 3 CA 1 O=Buypass AS-983163327 -# Label: "Buypass Class 3 CA 1" -# Serial: 2 -# MD5 Fingerprint: df:3c:73:59:81:e7:39:50:81:04:4c:34:a2:cb:b3:7b -# SHA1 Fingerprint: 61:57:3a:11:df:0e:d8:7e:d5:92:65:22:ea:d0:56:d7:44:b3:23:71 -# SHA256 Fingerprint: b7:b1:2b:17:1f:82:1d:aa:99:0c:d0:fe:50:87:b1:28:44:8b:a8:e5:18:4f:84:c5:1e:02:b5:c8:fb:96:2b:24 ------BEGIN CERTIFICATE----- -MIIDUzCCAjugAwIBAgIBAjANBgkqhkiG9w0BAQUFADBLMQswCQYDVQQGEwJOTzEd -MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxHTAbBgNVBAMMFEJ1eXBhc3Mg -Q2xhc3MgMyBDQSAxMB4XDTA1MDUwOTE0MTMwM1oXDTE1MDUwOTE0MTMwM1owSzEL -MAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MR0wGwYD -VQQDDBRCdXlwYXNzIENsYXNzIDMgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEP -ADCCAQoCggEBAKSO13TZKWTeXx+HgJHqTjnmGcZEC4DVC69TB4sSveZn8AKxifZg -isRbsELRwCGoy+Gb72RRtqfPFfV0gGgEkKBYouZ0plNTVUhjP5JW3SROjvi6K//z -NIqeKNc0n6wv1g/xpC+9UrJJhW05NfBEMJNGJPO251P7vGGvqaMU+8IXF4Rs4HyI -+MkcVyzwPX6UvCWThOiaAJpFBUJXgPROztmuOfbIUxAMZTpHe2DC1vqRycZxbL2R -hzyRhkmr8w+gbCZ2Xhysm3HljbybIR6c1jh+JIAVMYKWsUnTYjdbiAwKYjT+p0h+ -mbEwi5A3lRyoH6UsjfRVyNvdWQrCrXig9IsCAwEAAaNCMEAwDwYDVR0TAQH/BAUw -AwEB/zAdBgNVHQ4EFgQUOBTmyPCppAP0Tj4io1vy1uCtQHQwDgYDVR0PAQH/BAQD -AgEGMA0GCSqGSIb3DQEBBQUAA4IBAQABZ6OMySU9E2NdFm/soT4JXJEVKirZgCFP -Bdy7pYmrEzMqnji3jG8CcmPHc3ceCQa6Oyh7pEfJYWsICCD8igWKH7y6xsL+z27s -EzNxZy5p+qksP2bAEllNC1QCkoS72xLvg3BweMhT+t/Gxv/ciC8HwEmdMldg0/L2 -mSlf56oBzKwzqBwKu5HEA6BvtjT5htOzdlSY9EqBs1OdTUDs5XcTRa9bqh/YL0yC -e/4qxFi7T/ye/QNlGioOw6UgFpRreaaiErS7GqQjel/wroQk5PMr+4okoyeYZdow -dXb8GZHo2+ubPzK/QJcHJrrM85SFSnonk8+QQtS4Wxam58tAA915 ------END CERTIFICATE----- - # Issuer: CN=EBG Elektronik Sertifika Hizmet Sağlayıcısı O=EBG Bilişim Teknolojileri ve Hizmetleri A.Ş. # Subject: CN=EBG Elektronik Sertifika Hizmet Sağlayıcısı O=EBG Bilişim Teknolojileri ve Hizmetleri A.Ş. # Label: "EBG Elektronik Sertifika Hizmet Sa\xC4\x9Flay\xc4\xb1\x63\xc4\xb1s\xc4\xb1" @@ -3501,28 +2666,6 @@ r0CodaxWkHS4oJyleW/c6RrIaQXpuvoDs3zk4E7Czp3otkYNbn5XOmeUwssfnHdK Z05phkOTOPu220+DkdRgfks+KzgHVZhepA== -----END CERTIFICATE----- -# Issuer: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority -# Subject: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority -# Label: "Verisign Class 3 Public Primary Certification Authority" -# Serial: 80507572722862485515306429940691309246 -# MD5 Fingerprint: ef:5a:f1:33:ef:f1:cd:bb:51:02:ee:12:14:4b:96:c4 -# SHA1 Fingerprint: a1:db:63:93:91:6f:17:e4:18:55:09:40:04:15:c7:02:40:b0:ae:6b -# SHA256 Fingerprint: a4:b6:b3:99:6f:c2:f3:06:b3:fd:86:81:bd:63:41:3d:8c:50:09:cc:4f:a3:29:c2:cc:f0:e2:fa:1b:14:03:05 ------BEGIN CERTIFICATE----- -MIICPDCCAaUCEDyRMcsf9tAbDpq40ES/Er4wDQYJKoZIhvcNAQEFBQAwXzELMAkG -A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz -cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2 -MDEyOTAwMDAwMFoXDTI4MDgwMjIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV -BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt -YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN -ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE -BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is -I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G -CSqGSIb3DQEBBQUAA4GBABByUqkFFBkyCEHwxWsKzH4PIRnN5GfcX6kb5sroc50i -2JhucwNhkcV8sEVAbkSdjbCxlnRhLQ2pRdKkkirWmnWXbj9T/UWZYB2oK0z5XqcJ -2HUw19JlYD1n1khVdWk/kfVIC0dpImmClr7JyDiGSnoscxlIaU5rfGW/D/xwzoiQ ------END CERTIFICATE----- - # Issuer: CN=Microsec e-Szigno Root CA 2009 O=Microsec Ltd. # Subject: CN=Microsec e-Szigno Root CA 2009 O=Microsec Ltd. # Label: "Microsec e-Szigno Root CA 2009" @@ -3555,36 +2698,6 @@ tyERzAMBVnCnEJIeGzSBHq2cGsMEPO0CYdYeBvNfOofyK/FFh+U9rNHHV4S9a67c HMN1Rq41Bab2XD0h7lbwyYIiLXpUq3DDfSJlgnCW -----END CERTIFICATE----- -# Issuer: CN=e-Guven Kok Elektronik Sertifika Hizmet Saglayicisi O=Elektronik Bilgi Guvenligi A.S. -# Subject: CN=e-Guven Kok Elektronik Sertifika Hizmet Saglayicisi O=Elektronik Bilgi Guvenligi A.S. -# Label: "E-Guven Kok Elektronik Sertifika Hizmet Saglayicisi" -# Serial: 91184789765598910059173000485363494069 -# MD5 Fingerprint: 3d:41:29:cb:1e:aa:11:74:cd:5d:b0:62:af:b0:43:5b -# SHA1 Fingerprint: dd:e1:d2:a9:01:80:2e:1d:87:5e:84:b3:80:7e:4b:b1:fd:99:41:34 -# SHA256 Fingerprint: e6:09:07:84:65:a4:19:78:0c:b6:ac:4c:1c:0b:fb:46:53:d9:d9:cc:6e:b3:94:6e:b7:f3:d6:99:97:ba:d5:98 ------BEGIN CERTIFICATE----- -MIIDtjCCAp6gAwIBAgIQRJmNPMADJ72cdpW56tustTANBgkqhkiG9w0BAQUFADB1 -MQswCQYDVQQGEwJUUjEoMCYGA1UEChMfRWxla3Ryb25payBCaWxnaSBHdXZlbmxp -Z2kgQS5TLjE8MDoGA1UEAxMzZS1HdXZlbiBLb2sgRWxla3Ryb25payBTZXJ0aWZp -a2EgSGl6bWV0IFNhZ2xheWljaXNpMB4XDTA3MDEwNDExMzI0OFoXDTE3MDEwNDEx -MzI0OFowdTELMAkGA1UEBhMCVFIxKDAmBgNVBAoTH0VsZWt0cm9uaWsgQmlsZ2kg -R3V2ZW5saWdpIEEuUy4xPDA6BgNVBAMTM2UtR3V2ZW4gS29rIEVsZWt0cm9uaWsg -U2VydGlmaWthIEhpem1ldCBTYWdsYXlpY2lzaTCCASIwDQYJKoZIhvcNAQEBBQAD -ggEPADCCAQoCggEBAMMSIJ6wXgBljU5Gu4Bc6SwGl9XzcslwuedLZYDBS75+PNdU -MZTe1RK6UxYC6lhj71vY8+0qGqpxSKPcEC1fX+tcS5yWCEIlKBHMilpiAVDV6wlT -L/jDj/6z/P2douNffb7tC+Bg62nsM+3YjfsSSYMAyYuXjDtzKjKzEve5TfL0TW3H -5tYmNwjy2f1rXKPlSFxYvEK+A1qBuhw1DADT9SN+cTAIJjjcJRFHLfO6IxClv7wC -90Nex/6wN1CZew+TzuZDLMN+DfIcQ2Zgy2ExR4ejT669VmxMvLz4Bcpk9Ok0oSy1 -c+HCPujIyTQlCFzz7abHlJ+tiEMl1+E5YP6sOVkCAwEAAaNCMEAwDgYDVR0PAQH/ -BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFJ/uRLOU1fqRTy7ZVZoE -VtstxNulMA0GCSqGSIb3DQEBBQUAA4IBAQB/X7lTW2M9dTLn+sR0GstG30ZpHFLP -qk/CaOv/gKlR6D1id4k9CnU58W5dF4dvaAXBlGzZXd/aslnLpRCKysw5zZ/rTt5S -/wzw9JKp8mxTq5vSR6AfdPebmvEvFZ96ZDAYBzwqD2fK/A+JYZ1lpTzlvBNbCNvj -/+27BrtqBrF6T2XGgv0enIu1De5Iu7i9qgi0+6N8y5/NkHZchpZ4Vwpm+Vganf2X -KWDeEaaQHBkc7gGWIjQ0LpH5t8Qn0Xvmv/uARFoW5evg1Ao4vOSR49XrXMGs3xtq -fJ7lddK2l4fbzIcrQzqECK+rPNv3PGYxhrCdU3nt+CPeQuMtgvEP5fqX ------END CERTIFICATE----- - # Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R3 # Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R3 # Label: "GlobalSign Root CA - R3" @@ -4123,37 +3236,6 @@ ducTZnV+ZfsBn5OHiJ35Rld8TWCvmHMTI6QgkYH60GFmuH3Rr9ZvHmw96RH9qfmC IoaZM3Fa6hlXPZHNqcCjbgcTpsnt+GijnsNacgmHKNHEc8RzGF9QdRYxn7fofMM= -----END CERTIFICATE----- -# Issuer: CN=A-Trust-nQual-03 O=A-Trust Ges. f. Sicherheitssysteme im elektr. Datenverkehr GmbH OU=A-Trust-nQual-03 -# Subject: CN=A-Trust-nQual-03 O=A-Trust Ges. f. Sicherheitssysteme im elektr. Datenverkehr GmbH OU=A-Trust-nQual-03 -# Label: "A-Trust-nQual-03" -# Serial: 93214 -# MD5 Fingerprint: 49:63:ae:27:f4:d5:95:3d:d8:db:24:86:b8:9c:07:53 -# SHA1 Fingerprint: d3:c0:63:f2:19:ed:07:3e:34:ad:5d:75:0b:32:76:29:ff:d5:9a:f2 -# SHA256 Fingerprint: 79:3c:bf:45:59:b9:fd:e3:8a:b2:2d:f1:68:69:f6:98:81:ae:14:c4:b0:13:9a:c7:88:a7:8a:1a:fc:ca:02:fb ------BEGIN CERTIFICATE----- -MIIDzzCCAregAwIBAgIDAWweMA0GCSqGSIb3DQEBBQUAMIGNMQswCQYDVQQGEwJB -VDFIMEYGA1UECgw/QS1UcnVzdCBHZXMuIGYuIFNpY2hlcmhlaXRzc3lzdGVtZSBp -bSBlbGVrdHIuIERhdGVudmVya2VociBHbWJIMRkwFwYDVQQLDBBBLVRydXN0LW5R -dWFsLTAzMRkwFwYDVQQDDBBBLVRydXN0LW5RdWFsLTAzMB4XDTA1MDgxNzIyMDAw -MFoXDTE1MDgxNzIyMDAwMFowgY0xCzAJBgNVBAYTAkFUMUgwRgYDVQQKDD9BLVRy -dXN0IEdlcy4gZi4gU2ljaGVyaGVpdHNzeXN0ZW1lIGltIGVsZWt0ci4gRGF0ZW52 -ZXJrZWhyIEdtYkgxGTAXBgNVBAsMEEEtVHJ1c3QtblF1YWwtMDMxGTAXBgNVBAMM -EEEtVHJ1c3QtblF1YWwtMDMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB -AQCtPWFuA/OQO8BBC4SAzewqo51ru27CQoT3URThoKgtUaNR8t4j8DRE/5TrzAUj -lUC5B3ilJfYKvUWG6Nm9wASOhURh73+nyfrBJcyFLGM/BWBzSQXgYHiVEEvc+RFZ -znF/QJuKqiTfC0Li21a8StKlDJu3Qz7dg9MmEALP6iPESU7l0+m0iKsMrmKS1GWH -2WrX9IWf5DMiJaXlyDO6w8dB3F/GaswADm0yqLaHNgBid5seHzTLkDx4iHQF63n1 -k3Flyp3HaxgtPVxO59X4PzF9j4fsCiIvI+n+u33J4PTs63zEsMMtYrWacdaxaujs -2e3Vcuy+VwHOBVWf3tFgiBCzAgMBAAGjNjA0MA8GA1UdEwEB/wQFMAMBAf8wEQYD -VR0OBAoECERqlWdVeRFPMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOC -AQEAVdRU0VlIXLOThaq/Yy/kgM40ozRiPvbY7meIMQQDbwvUB/tOdQ/TLtPAF8fG -KOwGDREkDg6lXb+MshOWcdzUzg4NCmgybLlBMRmrsQd7TZjTXLDR8KdCoLXEjq/+ -8T/0709GAHbrAvv5ndJAlseIOrifEXnzgGWovR/TeIGgUUw3tKZdJXDRZslo+S4R -FGjxVJgIrCaSD96JntT6s3kr0qN51OyLrIdTaEJMUVF0HhsnLuP1Hyl0Te2v9+GS -mYHovjrHF1D2t8b8m7CKa9aIA5GPBnc6hQLdmNVDeD/GMBWsm2vLV7eJUYs66MmE -DNuxUCAKGkq6ahq97BvIxYSazQ== ------END CERTIFICATE----- - # Issuer: CN=TWCA Root Certification Authority O=TAIWAN-CA OU=Root CA # Subject: CN=TWCA Root Certification Authority O=TAIWAN-CA OU=Root CA # Label: "TWCA Root Certification Authority" @@ -5132,3 +4214,939 @@ maHFCN795trV1lpFDMS3wrUU77QR/w4VtfX128a961qn8FYiqTxlVMYVqL2Gns2D lmh6cYGJ4Qvh6hEbaAjMaZ7snkGeRDImeuKHCnE96+RapNLbxc3G3mB/ufNPRJLv KrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed -----END CERTIFICATE----- + +# Issuer: CN=QuoVadis Root CA 1 G3 O=QuoVadis Limited +# Subject: CN=QuoVadis Root CA 1 G3 O=QuoVadis Limited +# Label: "QuoVadis Root CA 1 G3" +# Serial: 687049649626669250736271037606554624078720034195 +# MD5 Fingerprint: a4:bc:5b:3f:fe:37:9a:fa:64:f0:e2:fa:05:3d:0b:ab +# SHA1 Fingerprint: 1b:8e:ea:57:96:29:1a:c9:39:ea:b8:0a:81:1a:73:73:c0:93:79:67 +# SHA256 Fingerprint: 8a:86:6f:d1:b2:76:b5:7e:57:8e:92:1c:65:82:8a:2b:ed:58:e9:f2:f2:88:05:41:34:b7:f1:f4:bf:c9:cc:74 +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIUeFhfLq0sGUvjNwc1NBMotZbUZZMwDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMSBHMzAeFw0xMjAxMTIxNzI3NDRaFw00 +MjAxMTIxNzI3NDRaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDEgRzMwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCgvlAQjunybEC0BJyFuTHK3C3kEakEPBtV +wedYMB0ktMPvhd6MLOHBPd+C5k+tR4ds7FtJwUrVu4/sh6x/gpqG7D0DmVIB0jWe +rNrwU8lmPNSsAgHaJNM7qAJGr6Qc4/hzWHa39g6QDbXwz8z6+cZM5cOGMAqNF341 +68Xfuw6cwI2H44g4hWf6Pser4BOcBRiYz5P1sZK0/CPTz9XEJ0ngnjybCKOLXSoh +4Pw5qlPafX7PGglTvF0FBM+hSo+LdoINofjSxxR3W5A2B4GbPgb6Ul5jxaYA/qXp +UhtStZI5cgMJYr2wYBZupt0lwgNm3fME0UDiTouG9G/lg6AnhF4EwfWQvTA9xO+o +abw4m6SkltFi2mnAAZauy8RRNOoMqv8hjlmPSlzkYZqn0ukqeI1RPToV7qJZjqlc +3sX5kCLliEVx3ZGZbHqfPT2YfF72vhZooF6uCyP8Wg+qInYtyaEQHeTTRCOQiJ/G +KubX9ZqzWB4vMIkIG1SitZgj7Ah3HJVdYdHLiZxfokqRmu8hqkkWCKi9YSgxyXSt +hfbZxbGL0eUQMk1fiyA6PEkfM4VZDdvLCXVDaXP7a3F98N/ETH3Goy7IlXnLc6KO +Tk0k+17kBL5yG6YnLUlamXrXXAkgt3+UuU/xDRxeiEIbEbfnkduebPRq34wGmAOt +zCjvpUfzUwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQUo5fW816iEOGrRZ88F2Q87gFwnMwwDQYJKoZIhvcNAQELBQAD +ggIBABj6W3X8PnrHX3fHyt/PX8MSxEBd1DKquGrX1RUVRpgjpeaQWxiZTOOtQqOC +MTaIzen7xASWSIsBx40Bz1szBpZGZnQdT+3Btrm0DWHMY37XLneMlhwqI2hrhVd2 +cDMT/uFPpiN3GPoajOi9ZcnPP/TJF9zrx7zABC4tRi9pZsMbj/7sPtPKlL92CiUN +qXsCHKnQO18LwIE6PWThv6ctTr1NxNgpxiIY0MWscgKCP6o6ojoilzHdCGPDdRS5 +YCgtW2jgFqlmgiNR9etT2DGbe+m3nUvriBbP+V04ikkwj+3x6xn0dxoxGE1nVGwv +b2X52z3sIexe9PSLymBlVNFxZPT5pqOBMzYzcfCkeF9OrYMh3jRJjehZrJ3ydlo2 +8hP0r+AJx2EqbPfgna67hkooby7utHnNkDPDs3b69fBsnQGQ+p6Q9pxyz0fawx/k +NSBT8lTR32GDpgLiJTjehTItXnOQUl1CxM49S+H5GYQd1aJQzEH7QRTDvdbJWqNj +ZgKAvQU6O0ec7AAmTPWIUb+oI38YB7AL7YsmoWTTYUrrXJ/es69nA7Mf3W1daWhp +q1467HxpvMc7hU6eFbm0FU/DlXpY18ls6Wy58yljXrQs8C097Vpl4KlbQMJImYFt +nh8GKjwStIsPm6Ik8KaN1nrgS7ZklmOVhMJKzRwuJIczYOXD +-----END CERTIFICATE----- + +# Issuer: CN=QuoVadis Root CA 2 G3 O=QuoVadis Limited +# Subject: CN=QuoVadis Root CA 2 G3 O=QuoVadis Limited +# Label: "QuoVadis Root CA 2 G3" +# Serial: 390156079458959257446133169266079962026824725800 +# MD5 Fingerprint: af:0c:86:6e:bf:40:2d:7f:0b:3e:12:50:ba:12:3d:06 +# SHA1 Fingerprint: 09:3c:61:f3:8b:8b:dc:7d:55:df:75:38:02:05:00:e1:25:f5:c8:36 +# SHA256 Fingerprint: 8f:e4:fb:0a:f9:3a:4d:0d:67:db:0b:eb:b2:3e:37:c7:1b:f3:25:dc:bc:dd:24:0e:a0:4d:af:58:b4:7e:18:40 +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00 +MjAxMTIxODU5MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIgRzMwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQChriWyARjcV4g/Ruv5r+LrI3HimtFhZiFf +qq8nUeVuGxbULX1QsFN3vXg6YOJkApt8hpvWGo6t/x8Vf9WVHhLL5hSEBMHfNrMW +n4rjyduYNM7YMxcoRvynyfDStNVNCXJJ+fKH46nafaF9a7I6JaltUkSs+L5u+9ym +c5GQYaYDFCDy54ejiK2toIz/pgslUiXnFgHVy7g1gQyjO/Dh4fxaXc6AcW34Sas+ +O7q414AB+6XrW7PFXmAqMaCvN+ggOp+oMiwMzAkd056OXbxMmO7FGmh77FOm6RQ1 +o9/NgJ8MSPsc9PG/Srj61YxxSscfrf5BmrODXfKEVu+lV0POKa2Mq1W/xPtbAd0j +IaFYAI7D0GoT7RPjEiuA3GfmlbLNHiJuKvhB1PLKFAeNilUSxmn1uIZoL1NesNKq +IcGY5jDjZ1XHm26sGahVpkUG0CM62+tlXSoREfA7T8pt9DTEceT/AFr2XK4jYIVz +8eQQsSWu1ZK7E8EM4DnatDlXtas1qnIhO4M15zHfeiFuuDIIfR0ykRVKYnLP43eh +vNURG3YBZwjgQQvD6xVu+KQZ2aKrr+InUlYrAoosFCT5v0ICvybIxo/gbjh9Uy3l +7ZizlWNof/k19N+IxWA1ksB8aRxhlRbQ694Lrz4EEEVlWFA4r0jyWbYW8jwNkALG +cC4BrTwV1wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQU7edvdlq/YOxJW8ald7tyFnGbxD0wDQYJKoZIhvcNAQELBQAD +ggIBAJHfgD9DCX5xwvfrs4iP4VGyvD11+ShdyLyZm3tdquXK4Qr36LLTn91nMX66 +AarHakE7kNQIXLJgapDwyM4DYvmL7ftuKtwGTTwpD4kWilhMSA/ohGHqPHKmd+RC +roijQ1h5fq7KpVMNqT1wvSAZYaRsOPxDMuHBR//47PERIjKWnML2W2mWeyAMQ0Ga +W/ZZGYjeVYg3UQt4XAoeo0L9x52ID8DyeAIkVJOviYeIyUqAHerQbj5hLja7NQ4n +lv1mNDthcnPxFlxHBlRJAHpYErAK74X9sbgzdWqTHBLmYF5vHX/JHyPLhGGfHoJE ++V+tYlUkmlKY7VHnoX6XOuYvHxHaU4AshZ6rNRDbIl9qxV6XU/IyAgkwo1jwDQHV +csaxfGl7w/U2Rcxhbl5MlMVerugOXou/983g7aEOGzPuVBj+D77vfoRrQ+NwmNtd +dbINWQeFFSM51vHfqSYP1kjHs6Yi9TM3WpVHn3u6GBVv/9YUZINJ0gpnIdsPNWNg +KCLjsZWDzYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeM +HVOyToV7BjjHLPj4sHKNJeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4 +WSr2Rz0ZiC3oheGe7IUIarFsNMkd7EgrO3jtZsSOeWmD3n+M +-----END CERTIFICATE----- + +# Issuer: CN=QuoVadis Root CA 3 G3 O=QuoVadis Limited +# Subject: CN=QuoVadis Root CA 3 G3 O=QuoVadis Limited +# Label: "QuoVadis Root CA 3 G3" +# Serial: 268090761170461462463995952157327242137089239581 +# MD5 Fingerprint: df:7d:b9:ad:54:6f:68:a1:df:89:57:03:97:43:b0:d7 +# SHA1 Fingerprint: 48:12:bd:92:3c:a8:c4:39:06:e7:30:6d:27:96:e6:a4:cf:22:2e:7d +# SHA256 Fingerprint: 88:ef:81:de:20:2e:b0:18:45:2e:43:f8:64:72:5c:ea:5f:bd:1f:c2:d9:d2:05:73:07:09:c5:d8:b8:69:0f:46 +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIULvWbAiin23r/1aOp7r0DoM8Sah0wDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMyBHMzAeFw0xMjAxMTIyMDI2MzJaFw00 +MjAxMTIyMDI2MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDMgRzMwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCzyw4QZ47qFJenMioKVjZ/aEzHs286IxSR +/xl/pcqs7rN2nXrpixurazHb+gtTTK/FpRp5PIpM/6zfJd5O2YIyC0TeytuMrKNu +FoM7pmRLMon7FhY4futD4tN0SsJiCnMK3UmzV9KwCoWdcTzeo8vAMvMBOSBDGzXR +U7Ox7sWTaYI+FrUoRqHe6okJ7UO4BUaKhvVZR74bbwEhELn9qdIoyhA5CcoTNs+c +ra1AdHkrAj80//ogaX3T7mH1urPnMNA3I4ZyYUUpSFlob3emLoG+B01vr87ERROR +FHAGjx+f+IdpsQ7vw4kZ6+ocYfx6bIrc1gMLnia6Et3UVDmrJqMz6nWB2i3ND0/k +A9HvFZcba5DFApCTZgIhsUfei5pKgLlVj7WiL8DWM2fafsSntARE60f75li59wzw +eyuxwHApw0BiLTtIadwjPEjrewl5qW3aqDCYz4ByA4imW0aucnl8CAMhZa634Ryl +sSqiMd5mBPfAdOhx3v89WcyWJhKLhZVXGqtrdQtEPREoPHtht+KPZ0/l7DxMYIBp +VzgeAVuNVejH38DMdyM0SXV89pgR6y3e7UEuFAUCf+D+IOs15xGsIs5XPd7JMG0Q +A4XN8f+MFrXBsj6IbGB/kE+V9/YtrQE5BwT6dYB9v0lQ7e/JxHwc64B+27bQ3RP+ +ydOc17KXqQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQUxhfQvKjqAkPyGwaZXSuQILnXnOQwDQYJKoZIhvcNAQELBQAD +ggIBADRh2Va1EodVTd2jNTFGu6QHcrxfYWLopfsLN7E8trP6KZ1/AvWkyaiTt3px +KGmPc+FSkNrVvjrlt3ZqVoAh313m6Tqe5T72omnHKgqwGEfcIHB9UqM+WXzBusnI +FUBhynLWcKzSt/Ac5IYp8M7vaGPQtSCKFWGafoaYtMnCdvvMujAWzKNhxnQT5Wvv +oxXqA/4Ti2Tk08HS6IT7SdEQTXlm66r99I0xHnAUrdzeZxNMgRVhvLfZkXdxGYFg +u/BYpbWcC/ePIlUnwEsBbTuZDdQdm2NnL9DuDcpmvJRPpq3t/O5jrFc/ZSXPsoaP +0Aj/uHYUbt7lJ+yreLVTubY/6CD50qi+YUbKh4yE8/nxoGibIh6BJpsQBJFxwAYf +3KDTuVan45gtf4Od34wrnDKOMpTwATwiKp9Dwi7DmDkHOHv8XgBCH/MyJnmDhPbl +8MFREsALHgQjDFSlTC9JxUrRtm5gDWv8a4uFJGS3iQ6rJUdbPM9+Sb3H6QrG2vd+ +DhcI00iX0HGS8A85PjRqHH3Y8iKuu2n0M7SmSFXRDw4m6Oy2Cy2nhTXN/VnIn9HN +PlopNLk9hM6xZdRZkZFWdSHBd575euFgndOtBBj0fOtek49TSiIp+EgrPk2GrFt/ +ywaZWWDYWGWVjUTR939+J399roD1B0y2PpxxVJkES/1Y+Zj0 +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Assured ID Root G2 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Assured ID Root G2 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Assured ID Root G2" +# Serial: 15385348160840213938643033620894905419 +# MD5 Fingerprint: 92:38:b9:f8:63:24:82:65:2c:57:33:e6:fe:81:8f:9d +# SHA1 Fingerprint: a1:4b:48:d9:43:ee:0a:0e:40:90:4f:3c:e0:a4:c0:91:93:51:5d:3f +# SHA256 Fingerprint: 7d:05:eb:b6:82:33:9f:8c:94:51:ee:09:4e:eb:fe:fa:79:53:a1:14:ed:b2:f4:49:49:45:2f:ab:7d:2f:c1:85 +-----BEGIN CERTIFICATE----- +MIIDljCCAn6gAwIBAgIQC5McOtY5Z+pnI7/Dr5r0SzANBgkqhkiG9w0BAQsFADBl +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv +b3QgRzIwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl +cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ5ygvUj82ckmIkzTz+GoeMVSA +n61UQbVH35ao1K+ALbkKz3X9iaV9JPrjIgwrvJUXCzO/GU1BBpAAvQxNEP4Htecc +biJVMWWXvdMX0h5i89vqbFCMP4QMls+3ywPgym2hFEwbid3tALBSfK+RbLE4E9Hp +EgjAALAcKxHad3A2m67OeYfcgnDmCXRwVWmvo2ifv922ebPynXApVfSr/5Vh88lA +bx3RvpO704gqu52/clpWcTs/1PPRCv4o76Pu2ZmvA9OPYLfykqGxvYmJHzDNw6Yu +YjOuFgJ3RFrngQo8p0Quebg/BLxcoIfhG69Rjs3sLPr4/m3wOnyqi+RnlTGNAgMB +AAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQW +BBTOw0q5mVXyuNtgv6l+vVa1lzan1jANBgkqhkiG9w0BAQsFAAOCAQEAyqVVjOPI +QW5pJ6d1Ee88hjZv0p3GeDgdaZaikmkuOGybfQTUiaWxMTeKySHMq2zNixya1r9I +0jJmwYrA8y8678Dj1JGG0VDjA9tzd29KOVPt3ibHtX2vK0LRdWLjSisCx1BL4Gni +lmwORGYQRI+tBev4eaymG+g3NJ1TyWGqolKvSnAWhsI6yLETcDbYz+70CjTVW0z9 +B5yiutkBclzzTcHdDrEcDcRjvq30FPuJ7KJBDkzMyFdA0G4Dqs0MjomZmWzwPDCv +ON9vvKO+KSAnq3T/EyJ43pdSVR6DtVQgA+6uwE9W3jfMw3+qBCe703e4YtsXfJwo +IhNzbM8m9Yop5w== +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Assured ID Root G3 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Assured ID Root G3 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Assured ID Root G3" +# Serial: 15459312981008553731928384953135426796 +# MD5 Fingerprint: 7c:7f:65:31:0c:81:df:8d:ba:3e:99:e2:5c:ad:6e:fb +# SHA1 Fingerprint: f5:17:a2:4f:9a:48:c6:c9:f8:a2:00:26:9f:dc:0f:48:2c:ab:30:89 +# SHA256 Fingerprint: 7e:37:cb:8b:4c:47:09:0c:ab:36:55:1b:a6:f4:5d:b8:40:68:0f:ba:16:6a:95:2d:b1:00:71:7f:43:05:3f:c2 +-----BEGIN CERTIFICATE----- +MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQsw +CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu +ZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3Qg +RzMwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu +Y29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwdjAQBgcq +hkjOPQIBBgUrgQQAIgNiAAQZ57ysRGXtzbg/WPuNsVepRC0FFfLvC/8QdJ+1YlJf +Zn4f5dwbRXkLzMZTCp2NXQLZqVneAlr2lSoOjThKiknGvMYDOAdfVdp+CW7if17Q +RSAPWXYQ1qAk8C3eNvJsKTmjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/ +BAQDAgGGMB0GA1UdDgQWBBTL0L2p4ZgFUaFNN6KDec6NHSrkhDAKBggqhkjOPQQD +AwNnADBkAjAlpIFFAmsSS3V0T8gj43DydXLefInwz5FyYZ5eEJJZVrmDxxDnOOlY +JjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy1vUhZscv +6pZjamVFkpUBtA== +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Global Root G2 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Global Root G2 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Global Root G2" +# Serial: 4293743540046975378534879503202253541 +# MD5 Fingerprint: e4:a6:8a:c8:54:ac:52:42:46:0a:fd:72:48:1b:2a:44 +# SHA1 Fingerprint: df:3c:24:f9:bf:d6:66:76:1b:26:80:73:fe:06:d1:cc:8d:4f:82:a4 +# SHA256 Fingerprint: cb:3c:cb:b7:60:31:e5:e0:13:8f:8d:d3:9a:23:f9:de:47:ff:c3:5e:43:c1:14:4c:ea:27:d4:6a:5a:b1:cb:5f +-----BEGIN CERTIFICATE----- +MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH +MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT +MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j +b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI +2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx +1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ +q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz +tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ +vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP +BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV +5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY +1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4 +NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG +Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91 +8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe +pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl +MrY= +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Global Root G3 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Global Root G3 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Global Root G3" +# Serial: 7089244469030293291760083333884364146 +# MD5 Fingerprint: f5:5d:a4:50:a5:fb:28:7e:1e:0f:0d:cc:96:57:56:ca +# SHA1 Fingerprint: 7e:04:de:89:6a:3e:66:6d:00:e6:87:d3:3f:fa:d9:3b:e8:3d:34:9e +# SHA256 Fingerprint: 31:ad:66:48:f8:10:41:38:c7:38:f3:9e:a4:32:01:33:39:3e:3a:18:cc:02:29:6e:f9:7c:2a:c9:ef:67:31:d0 +-----BEGIN CERTIFICATE----- +MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQsw +CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu +ZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAe +Fw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVTMRUw +EwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20x +IDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0CAQYF +K4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FG +fp4tn+6OYwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPO +Z9wj/wMco+I+o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAd +BgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNpYim8S8YwCgYIKoZIzj0EAwMDaAAwZQIx +AK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y3maTD/HMsQmP3Wyr+mt/ +oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34VOKa5Vt8 +sycX +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Trusted Root G4 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Trusted Root G4 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Trusted Root G4" +# Serial: 7451500558977370777930084869016614236 +# MD5 Fingerprint: 78:f2:fc:aa:60:1f:2f:b4:eb:c9:37:ba:53:2e:75:49 +# SHA1 Fingerprint: dd:fb:16:cd:49:31:c9:73:a2:03:7d:3f:c8:3a:4d:7d:77:5d:05:e4 +# SHA256 Fingerprint: 55:2f:7b:dc:f1:a7:af:9e:6c:e6:72:01:7f:4f:12:ab:f7:72:40:c7:8e:76:1a:c2:03:d1:d9:d2:0a:c8:99:88 +-----BEGIN CERTIFICATE----- +MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBi +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3Qg +RzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBiMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu +Y29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3y +ithZwuEppz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1If +xp4VpX6+n6lXFllVcq9ok3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDV +ySAdYyktzuxeTsiT+CFhmzTrBcZe7FsavOvJz82sNEBfsXpm7nfISKhmV1efVFiO +DCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGYQJB5w3jHtrHEtWoYOAMQ +jdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6MUSaM0C/ +CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCi +EhtmmnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADM +fRyVw4/3IbKyEbe7f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QY +uKZ3AeEPlAwhHbJUKSWJbOUOUlFHdL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXK +chYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8oR7FwI+isX4KJpn15GkvmB0t +9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +hjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD +ggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2 +SV1EY+CtnJYYZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd ++SeuMIW59mdNOj6PWTkiU0TryF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWc +fFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy7zBZLq7gcfJW5GqXb5JQbZaNaHqa +sjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iahixTXTBmyUEFxPT9N +cCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN5r5N +0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie +4u1Ki7wb/UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mI +r/OSmbaz5mEP0oUA51Aa5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1 +/YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tKG48BtieVU+i2iW1bvGjUI+iLUaJW+fCm +gKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP82Z+ +-----END CERTIFICATE----- + +# Issuer: CN=Certification Authority of WoSign O=WoSign CA Limited +# Subject: CN=Certification Authority of WoSign O=WoSign CA Limited +# Label: "WoSign" +# Serial: 125491772294754854453622855443212256657 +# MD5 Fingerprint: a1:f2:f9:b5:d2:c8:7a:74:b8:f3:05:f1:d7:e1:84:8d +# SHA1 Fingerprint: b9:42:94:bf:91:ea:8f:b6:4b:e6:10:97:c7:fb:00:13:59:b6:76:cb +# SHA256 Fingerprint: 4b:22:d5:a6:ae:c9:9f:3c:db:79:aa:5e:c0:68:38:47:9c:d5:ec:ba:71:64:f7:f2:2d:c1:d6:5f:63:d8:57:08 +-----BEGIN CERTIFICATE----- +MIIFdjCCA16gAwIBAgIQXmjWEXGUY1BWAGjzPsnFkTANBgkqhkiG9w0BAQUFADBV +MQswCQYDVQQGEwJDTjEaMBgGA1UEChMRV29TaWduIENBIExpbWl0ZWQxKjAoBgNV +BAMTIUNlcnRpZmljYXRpb24gQXV0aG9yaXR5IG9mIFdvU2lnbjAeFw0wOTA4MDgw +MTAwMDFaFw0zOTA4MDgwMTAwMDFaMFUxCzAJBgNVBAYTAkNOMRowGAYDVQQKExFX +b1NpZ24gQ0EgTGltaXRlZDEqMCgGA1UEAxMhQ2VydGlmaWNhdGlvbiBBdXRob3Jp +dHkgb2YgV29TaWduMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvcqN +rLiRFVaXe2tcesLea9mhsMMQI/qnobLMMfo+2aYpbxY94Gv4uEBf2zmoAHqLoE1U +fcIiePyOCbiohdfMlZdLdNiefvAA5A6JrkkoRBoQmTIPJYhTpA2zDxIIFgsDcScc +f+Hb0v1naMQFXQoOXXDX2JegvFNBmpGN9J42Znp+VsGQX+axaCA2pIwkLCxHC1l2 +ZjC1vt7tj/id07sBMOby8w7gLJKA84X5KIq0VC6a7fd2/BVoFutKbOsuEo/Uz/4M +x1wdC34FMr5esAkqQtXJTpCzWQ27en7N1QhatH/YHGkR+ScPewavVIMYe+HdVHpR +aG53/Ma/UkpmRqGyZxq7o093oL5d//xWC0Nyd5DKnvnyOfUNqfTq1+ezEC8wQjch +zDBwyYaYD8xYTYO7feUapTeNtqwylwA6Y3EkHp43xP901DfA4v6IRmAR3Qg/UDar +uHqklWJqbrDKaiFaafPz+x1wOZXzp26mgYmhiMU7ccqjUu6Du/2gd/Tkb+dC221K +mYo0SLwX3OSACCK28jHAPwQ+658geda4BmRkAjHXqc1S+4RFaQkAKtxVi8QGRkvA +Sh0JWzko/amrzgD5LkhLJuYwTKVYyrREgk/nkR4zw7CT/xH8gdLKH3Ep3XZPkiWv +HYG3Dy+MwwbMLyejSuQOmbp8HkUff6oZRZb9/D0CAwEAAaNCMEAwDgYDVR0PAQH/ +BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFOFmzw7R8bNLtwYgFP6H +EtX2/vs+MA0GCSqGSIb3DQEBBQUAA4ICAQCoy3JAsnbBfnv8rWTjMnvMPLZdRtP1 +LOJwXcgu2AZ9mNELIaCJWSQBnfmvCX0KI4I01fx8cpm5o9dU9OpScA7F9dY74ToJ +MuYhOZO9sxXqT2r09Ys/L3yNWC7F4TmgPsc9SnOeQHrAK2GpZ8nzJLmzbVUsWh2e +JXLOC62qx1ViC777Y7NhRCOjy+EaDveaBk3e1CNOIZZbOVtXHS9dCF4Jef98l7VN +g64N1uajeeAz0JmWAjCnPv/So0M/BVoG6kQC2nz4SNAzqfkHx5Xh9T71XXG68pWp +dIhhWeO/yloTunK0jF02h+mmxTwTv97QRCbut+wucPrXnbes5cVAWubXbHssw1ab +R80LzvobtCHXt2a49CUwi1wNuepnsvRtrtWhnk/Yn+knArAdBtaP4/tIEp9/EaEQ +PkxROpaw0RPxx9gmrjrKkcRpnd8BKWRRb2jaFOwIQZeQjdCygPLPwj2/kWjFgGce +xGATVdVhmVd8upUPYUk6ynW8yQqTP2cOEvIo4jEbwFcW3wh8GcF+Dx+FHgo2fFt+ +J7x6v+Db9NpSvd4MVHAxkUOVyLzwPt0JfjBkUO1/AaQzZ01oT74V77D2AhGiGxMl +OtzCWfHjXEa7ZywCRuoeSKbmW9m1vFGikpbbqsY3Iqb+zCB0oy2pLmvLwIIRIbWT +ee5Ehr7XHuQe+w== +-----END CERTIFICATE----- + +# Issuer: CN=CA 沃通根证书 O=WoSign CA Limited +# Subject: CN=CA 沃通根证书 O=WoSign CA Limited +# Label: "WoSign China" +# Serial: 106921963437422998931660691310149453965 +# MD5 Fingerprint: 78:83:5b:52:16:76:c4:24:3b:83:78:e8:ac:da:9a:93 +# SHA1 Fingerprint: 16:32:47:8d:89:f9:21:3a:92:00:85:63:f5:a4:a7:d3:12:40:8a:d6 +# SHA256 Fingerprint: d6:f0:34:bd:94:aa:23:3f:02:97:ec:a4:24:5b:28:39:73:e4:47:aa:59:0f:31:0c:77:f4:8f:df:83:11:22:54 +-----BEGIN CERTIFICATE----- +MIIFWDCCA0CgAwIBAgIQUHBrzdgT/BtOOzNy0hFIjTANBgkqhkiG9w0BAQsFADBG +MQswCQYDVQQGEwJDTjEaMBgGA1UEChMRV29TaWduIENBIExpbWl0ZWQxGzAZBgNV +BAMMEkNBIOayg+mAmuagueivgeS5pjAeFw0wOTA4MDgwMTAwMDFaFw0zOTA4MDgw +MTAwMDFaMEYxCzAJBgNVBAYTAkNOMRowGAYDVQQKExFXb1NpZ24gQ0EgTGltaXRl +ZDEbMBkGA1UEAwwSQ0Eg5rKD6YCa5qC56K+B5LmmMIICIjANBgkqhkiG9w0BAQEF +AAOCAg8AMIICCgKCAgEA0EkhHiX8h8EqwqzbdoYGTufQdDTc7WU1/FDWiD+k8H/r +D195L4mx/bxjWDeTmzj4t1up+thxx7S8gJeNbEvxUNUqKaqoGXqW5pWOdO2XCld1 +9AXbbQs5uQF/qvbW2mzmBeCkTVL829B0txGMe41P/4eDrv8FAxNXUDf+jJZSEExf +v5RxadmWPgxDT74wwJ85dE8GRV2j1lY5aAfMh09Qd5Nx2UQIsYo06Yms25tO4dnk +UkWMLhQfkWsZHWgpLFbE4h4TV2TwYeO5Ed+w4VegG63XX9Gv2ystP9Bojg/qnw+L +NVgbExz03jWhCl3W6t8Sb8D7aQdGctyB9gQjF+BNdeFyb7Ao65vh4YOhn0pdr8yb ++gIgthhid5E7o9Vlrdx8kHccREGkSovrlXLp9glk3Kgtn3R46MGiCWOc76DbT52V +qyBPt7D3h1ymoOQ3OMdc4zUPLK2jgKLsLl3Az+2LBcLmc272idX10kaO6m1jGx6K +yX2m+Jzr5dVjhU1zZmkR/sgO9MHHZklTfuQZa/HpelmjbX7FF+Ynxu8b22/8DU0G +AbQOXDBGVWCvOGU6yke6rCzMRh+yRpY/8+0mBe53oWprfi1tWFxK1I5nuPHa1UaK +J/kR8slC/k7e3x9cxKSGhxYzoacXGKUN5AXlK8IrC6KVkLn9YDxOiT7nnO4fuwEC +AwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O +BBYEFOBNv9ybQV0T6GTwp+kVpOGBwboxMA0GCSqGSIb3DQEBCwUAA4ICAQBqinA4 +WbbaixjIvirTthnVZil6Xc1bL3McJk6jfW+rtylNpumlEYOnOXOvEESS5iVdT2H6 +yAa+Tkvv/vMx/sZ8cApBWNromUuWyXi8mHwCKe0JgOYKOoICKuLJL8hWGSbueBwj +/feTZU7n85iYr83d2Z5AiDEoOqsuC7CsDCT6eiaY8xJhEPRdF/d+4niXVOKM6Cm6 +jBAyvd0zaziGfjk9DgNyp115j0WKWa5bIW4xRtVZjc8VX90xJc/bYNaBRHIpAlf2 +ltTW/+op2znFuCyKGo3Oy+dCMYYFaA6eFN0AkLppRQjbbpCBhqcqBT/mhDn4t/lX +X0ykeVoQDF7Va/81XwVRHmyjdanPUIPTfPRm94KNPQx96N97qA4bLJyuQHCH2u2n +FoJavjVsIE4iYdm8UXrNemHcSxH5/mc0zy4EZmFcV5cjjPOGG0jfKq+nwf/Yjj4D +u9gqsPoUJbJRa4ZDhS4HIxaAjUz7tGM7zMN07RujHv41D198HRaG9Q7DlfEvr10l +O1Hm13ZBONFLAzkopR6RctR9q5czxNM+4Gm2KHmgCY0c0f9BckgG/Jou5yD5m6Le +ie2uPAmvylezkolwQOQvT8Jwg0DXJCxr5wkf09XHwQj02w47HAcLQxGEIYbpgNR1 +2KvxAmLBsX5VYc8T1yaw15zLKYs4SgsOkI26oQ== +-----END CERTIFICATE----- + +# Issuer: CN=COMODO RSA Certification Authority O=COMODO CA Limited +# Subject: CN=COMODO RSA Certification Authority O=COMODO CA Limited +# Label: "COMODO RSA Certification Authority" +# Serial: 101909084537582093308941363524873193117 +# MD5 Fingerprint: 1b:31:b0:71:40:36:cc:14:36:91:ad:c4:3e:fd:ec:18 +# SHA1 Fingerprint: af:e5:d2:44:a8:d1:19:42:30:ff:47:9f:e2:f8:97:bb:cd:7a:8c:b4 +# SHA256 Fingerprint: 52:f0:e1:c4:e5:8e:c6:29:29:1b:60:31:7f:07:46:71:b8:5d:7e:a8:0d:5b:07:27:34:63:53:4b:32:b4:02:34 +-----BEGIN CERTIFICATE----- +MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCB +hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G +A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV +BAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMTE5 +MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgT +EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR +Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCR +6FSS0gpWsawNJN3Fz0RndJkrN6N9I3AAcbxT38T6KhKPS38QVr2fcHK3YX/JSw8X +pz3jsARh7v8Rl8f0hj4K+j5c+ZPmNHrZFGvnnLOFoIJ6dq9xkNfs/Q36nGz637CC +9BR++b7Epi9Pf5l/tfxnQ3K9DADWietrLNPtj5gcFKt+5eNu/Nio5JIk2kNrYrhV +/erBvGy2i/MOjZrkm2xpmfh4SDBF1a3hDTxFYPwyllEnvGfDyi62a+pGx8cgoLEf +Zd5ICLqkTqnyg0Y3hOvozIFIQ2dOciqbXL1MGyiKXCJ7tKuY2e7gUYPDCUZObT6Z ++pUX2nwzV0E8jVHtC7ZcryxjGt9XyD+86V3Em69FmeKjWiS0uqlWPc9vqv9JWL7w +qP/0uK3pN/u6uPQLOvnoQ0IeidiEyxPx2bvhiWC4jChWrBQdnArncevPDt09qZah +SL0896+1DSJMwBGB7FY79tOi4lu3sgQiUpWAk2nojkxl8ZEDLXB0AuqLZxUpaVIC +u9ffUGpVRr+goyhhf3DQw6KqLCGqR84onAZFdr+CGCe01a60y1Dma/RMhnEw6abf +Fobg2P9A3fvQQoh/ozM6LlweQRGBY84YcWsr7KaKtzFcOmpH4MN5WdYgGq/yapiq +crxXStJLnbsQ/LBMQeXtHT1eKJ2czL+zUdqnR+WEUwIDAQABo0IwQDAdBgNVHQ4E +FgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB +/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAArx1UaEt65Ru2yyTUEUAJNMnMvl +wFTPoCWOAvn9sKIN9SCYPBMtrFaisNZ+EZLpLrqeLppysb0ZRGxhNaKatBYSaVqM +4dc+pBroLwP0rmEdEBsqpIt6xf4FpuHA1sj+nq6PK7o9mfjYcwlYRm6mnPTXJ9OV +2jeDchzTc+CiR5kDOF3VSXkAKRzH7JsgHAckaVd4sjn8OoSgtZx8jb8uk2Intzna +FxiuvTwJaP+EmzzV1gsD41eeFPfR60/IvYcjt7ZJQ3mFXLrrkguhxuhoqEwWsRqZ +CuhTLJK7oQkYdQxlqHvLI7cawiiFwxv/0Cti76R7CZGYZ4wUAc1oBmpjIXUDgIiK +boHGhfKppC3n9KUkEEeDys30jXlYsQab5xoq2Z0B15R97QNKyvDb6KkBPvVWmcke +jkk9u+UJueBPSZI9FoJAzMxZxuY67RIuaTxslbH9qh17f4a+Hg4yRvv7E491f0yL +S0Zj/gA0QHDBw7mh3aZw4gSzQbzpgJHqZJx64SIDqZxubw5lT2yHh17zbqD5daWb +QOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk527RH89elWsn2/x20Kk4yl +0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7ILaZRfyHB +NVOFBkpdn627G190 +-----END CERTIFICATE----- + +# Issuer: CN=USERTrust RSA Certification Authority O=The USERTRUST Network +# Subject: CN=USERTrust RSA Certification Authority O=The USERTRUST Network +# Label: "USERTrust RSA Certification Authority" +# Serial: 2645093764781058787591871645665788717 +# MD5 Fingerprint: 1b:fe:69:d1:91:b7:19:33:a3:72:a8:0f:e1:55:e5:b5 +# SHA1 Fingerprint: 2b:8f:1b:57:33:0d:bb:a2:d0:7a:6c:51:f7:0e:e9:0d:da:b9:ad:8e +# SHA256 Fingerprint: e7:93:c9:b0:2f:d8:aa:13:e2:1c:31:22:8a:cc:b0:81:19:64:3b:74:9c:89:89:64:b1:74:6d:46:c3:d4:cb:d2 +-----BEGIN CERTIFICATE----- +MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB +iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl +cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV +BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAw +MjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNV +BAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU +aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2Vy +dGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK +AoICAQCAEmUXNg7D2wiz0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B +3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2jY0K2dvKpOyuR+OJv0OwWIJAJPuLodMkY +tJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFnRghRy4YUVD+8M/5+bJz/ +Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O+T23LLb2 +VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT +79uq/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6 +c0Plfg6lZrEpfDKEY1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmT +Yo61Zs8liM2EuLE/pDkP2QKe6xJMlXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97l +c6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8yexDJtC/QV9AqURE9JnnV4ee +UB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+eLf8ZxXhyVeE +Hg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd +BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8G +A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPF +Up/L+M+ZBn8b2kMVn54CVVeWFPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KO +VWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ7l8wXEskEVX/JJpuXior7gtNn3/3 +ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQEg9zKC7F4iRO/Fjs +8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM8WcR +iQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYze +Sf7dNXGiFSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZ +XHlKYC6SQK5MNyosycdiyA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/ +qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9cJ2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRB +VXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aB +L6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfG +jjxDah2nGN59PRbxYvnKkKj9 +-----END CERTIFICATE----- + +# Issuer: CN=USERTrust ECC Certification Authority O=The USERTRUST Network +# Subject: CN=USERTrust ECC Certification Authority O=The USERTRUST Network +# Label: "USERTrust ECC Certification Authority" +# Serial: 123013823720199481456569720443997572134 +# MD5 Fingerprint: fa:68:bc:d9:b5:7f:ad:fd:c9:1d:06:83:28:cc:24:c1 +# SHA1 Fingerprint: d1:cb:ca:5d:b2:d5:2a:7f:69:3b:67:4d:e5:f0:5a:1d:0c:95:7d:f0 +# SHA256 Fingerprint: 4f:f4:60:d5:4b:9c:86:da:bf:bc:fc:57:12:e0:40:0d:2b:ed:3f:bc:4d:4f:bd:aa:86:e0:6a:dc:d2:a9:ad:7a +-----BEGIN CERTIFICATE----- +MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDEL +MAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNl +eSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMT +JVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMjAx +MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT +Ck5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVUaGUg +VVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlm +aWNhdGlvbiBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqflo +I+d61SRvU8Za2EurxtW20eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinng +o4N+LZfQYcTxmdwlkWOrfzCjtHDix6EznPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0G +A1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBBHU6+4WMB +zzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbW +RNZu9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg= +-----END CERTIFICATE----- + +# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R4 +# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R4 +# Label: "GlobalSign ECC Root CA - R4" +# Serial: 14367148294922964480859022125800977897474 +# MD5 Fingerprint: 20:f0:27:68:d1:7e:a0:9d:0e:e6:2a:ca:df:5c:89:8e +# SHA1 Fingerprint: 69:69:56:2e:40:80:f4:24:a1:e7:19:9f:14:ba:f3:ee:58:ab:6a:bb +# SHA256 Fingerprint: be:c9:49:11:c2:95:56:76:db:6c:0a:55:09:86:d7:6e:3b:a0:05:66:7c:44:2c:97:62:b4:fb:b7:73:de:22:8c +-----BEGIN CERTIFICATE----- +MIIB4TCCAYegAwIBAgIRKjikHJYKBN5CsiilC+g0mAIwCgYIKoZIzj0EAwIwUDEk +MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI0MRMwEQYDVQQKEwpH +bG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoX +DTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBD +QSAtIFI0MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuMZ5049sJQ6fLjkZHAOkrprlOQcJ +FspjsbmG+IpXwVfOQvpzofdlQv8ewQCybnMO/8ch5RikqtlxP6jUuc6MHaNCMEAw +DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFFSwe61F +uOJAf/sKbvu+M8k8o4TVMAoGCCqGSM49BAMCA0gAMEUCIQDckqGgE6bPA7DmxCGX +kPoUVy0D7O48027KqGx2vKLeuwIgJ6iFJzWbVsaj8kfSt24bAgAXqmemFZHe+pTs +ewv4n4Q= +-----END CERTIFICATE----- + +# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R5 +# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R5 +# Label: "GlobalSign ECC Root CA - R5" +# Serial: 32785792099990507226680698011560947931244 +# MD5 Fingerprint: 9f:ad:3b:1c:02:1e:8a:ba:17:74:38:81:0c:a2:bc:08 +# SHA1 Fingerprint: 1f:24:c6:30:cd:a4:18:ef:20:69:ff:ad:4f:dd:5f:46:3a:1b:69:aa +# SHA256 Fingerprint: 17:9f:bc:14:8a:3d:d0:0f:d2:4e:a1:34:58:cc:43:bf:a7:f5:9c:81:82:d7:83:a5:13:f6:eb:ec:10:0c:89:24 +-----BEGIN CERTIFICATE----- +MIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEk +MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpH +bG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoX +DTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBD +QSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAER0UOlvt9Xb/pOdEh+J8LttV7HpI6SFkc +8GIxLcB6KP4ap1yztsyX50XUWPrRd21DosCHZTQKH3rd6zwzocWdTaRvQZU4f8ke +hOvRnkmSh5SHDDqFSmafnVmTTZdhBoZKo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUPeYpSJvqB8ohREom3m7e0oPQn1kwCgYI +KoZIzj0EAwMDaAAwZQIxAOVpEslu28YxuglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg +515dTguDnFt2KaAJJiFqYgIwcdK1j1zqO+F4CYWodZI7yFz9SO8NdCKoCOJuxUnO +xwy8p2Fp8fc74SrL+SvzZpA3 +-----END CERTIFICATE----- + +# Issuer: CN=Staat der Nederlanden Root CA - G3 O=Staat der Nederlanden +# Subject: CN=Staat der Nederlanden Root CA - G3 O=Staat der Nederlanden +# Label: "Staat der Nederlanden Root CA - G3" +# Serial: 10003001 +# MD5 Fingerprint: 0b:46:67:07:db:10:2f:19:8c:35:50:60:d1:0b:f4:37 +# SHA1 Fingerprint: d8:eb:6b:41:51:92:59:e0:f3:e7:85:00:c0:3d:b6:88:97:c9:ee:fc +# SHA256 Fingerprint: 3c:4f:b0:b9:5a:b8:b3:00:32:f4:32:b8:6f:53:5f:e1:72:c1:85:d0:fd:39:86:58:37:cf:36:18:7f:a6:f4:28 +-----BEGIN CERTIFICATE----- +MIIFdDCCA1ygAwIBAgIEAJiiOTANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJO +TDEeMBwGA1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFh +dCBkZXIgTmVkZXJsYW5kZW4gUm9vdCBDQSAtIEczMB4XDTEzMTExNDExMjg0MloX +DTI4MTExMzIzMDAwMFowWjELMAkGA1UEBhMCTkwxHjAcBgNVBAoMFVN0YWF0IGRl +ciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5lZGVybGFuZGVuIFJv +b3QgQ0EgLSBHMzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL4yolQP +cPssXFnrbMSkUeiFKrPMSjTysF/zDsccPVMeiAho2G89rcKezIJnByeHaHE6n3WW +IkYFsO2tx1ueKt6c/DrGlaf1F2cY5y9JCAxcz+bMNO14+1Cx3Gsy8KL+tjzk7FqX +xz8ecAgwoNzFs21v0IJyEavSgWhZghe3eJJg+szeP4TrjTgzkApyI/o1zCZxMdFy +KJLZWyNtZrVtB0LrpjPOktvA9mxjeM3KTj215VKb8b475lRgsGYeCasH/lSJEULR +9yS6YHgamPfJEf0WwTUaVHXvQ9Plrk7O53vDxk5hUUurmkVLoR9BvUhTFXFkC4az +5S6+zqQbwSmEorXLCCN2QyIkHxcE1G6cxvx/K2Ya7Irl1s9N9WMJtxU51nus6+N8 +6U78dULI7ViVDAZCopz35HCz33JvWjdAidiFpNfxC95DGdRKWCyMijmev4SH8RY7 +Ngzp07TKbBlBUgmhHbBqv4LvcFEhMtwFdozL92TkA1CvjJFnq8Xy7ljY3r735zHP +bMk7ccHViLVlvMDoFxcHErVc0qsgk7TmgoNwNsXNo42ti+yjwUOH5kPiNL6VizXt +BznaqB16nzaeErAMZRKQFWDZJkBE41ZgpRDUajz9QdwOWke275dhdU/Z/seyHdTt +XUmzqWrLZoQT1Vyg3N9udwbRcXXIV2+vD3dbAgMBAAGjQjBAMA8GA1UdEwEB/wQF +MAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRUrfrHkleuyjWcLhL75Lpd +INyUVzANBgkqhkiG9w0BAQsFAAOCAgEAMJmdBTLIXg47mAE6iqTnB/d6+Oea31BD +U5cqPco8R5gu4RV78ZLzYdqQJRZlwJ9UXQ4DO1t3ApyEtg2YXzTdO2PCwyiBwpwp +LiniyMMB8jPqKqrMCQj3ZWfGzd/TtiunvczRDnBfuCPRy5FOCvTIeuXZYzbB1N/8 +Ipf3YF3qKS9Ysr1YvY2WTxB1v0h7PVGHoTx0IsL8B3+A3MSs/mrBcDCw6Y5p4ixp +gZQJut3+TcCDjJRYwEYgr5wfAvg1VUkvRtTA8KCWAg8zxXHzniN9lLf9OtMJgwYh +/WA9rjLA0u6NpvDntIJ8CsxwyXmA+P5M9zWEGYox+wrZ13+b8KKaa8MFSu1BYBQw +0aoRQm7TIwIEC8Zl3d1Sd9qBa7Ko+gE4uZbqKmxnl4mUnrzhVNXkanjvSr0rmj1A +fsbAddJu+2gw7OyLnflJNZoaLNmzlTnVHpL3prllL+U9bTpITAjc5CgSKL59NVzq +4BZ+Extq1z7XnvwtdbLBFNUjA9tbbws+eC8N3jONFrdI54OagQ97wUNNVQQXOEpR +1VmiiXTTn74eS9fGbbeIJG9gkaSChVtWQbzQRKtqE77RLFi3EjNYsjdj3BP1lB0/ +QFH1T/U67cjF68IeHRaVesd+QnGTbksVtzDfqu1XhUisHWrdOWnk4Xl4vs4Fv6EM +94B7IWcnMFk= +-----END CERTIFICATE----- + +# Issuer: CN=Staat der Nederlanden EV Root CA O=Staat der Nederlanden +# Subject: CN=Staat der Nederlanden EV Root CA O=Staat der Nederlanden +# Label: "Staat der Nederlanden EV Root CA" +# Serial: 10000013 +# MD5 Fingerprint: fc:06:af:7b:e8:1a:f1:9a:b4:e8:d2:70:1f:c0:f5:ba +# SHA1 Fingerprint: 76:e2:7e:c1:4f:db:82:c1:c0:a6:75:b5:05:be:3d:29:b4:ed:db:bb +# SHA256 Fingerprint: 4d:24:91:41:4c:fe:95:67:46:ec:4c:ef:a6:cf:6f:72:e2:8a:13:29:43:2f:9d:8a:90:7a:c4:cb:5d:ad:c1:5a +-----BEGIN CERTIFICATE----- +MIIFcDCCA1igAwIBAgIEAJiWjTANBgkqhkiG9w0BAQsFADBYMQswCQYDVQQGEwJO +TDEeMBwGA1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSkwJwYDVQQDDCBTdGFh +dCBkZXIgTmVkZXJsYW5kZW4gRVYgUm9vdCBDQTAeFw0xMDEyMDgxMTE5MjlaFw0y +MjEyMDgxMTEwMjhaMFgxCzAJBgNVBAYTAk5MMR4wHAYDVQQKDBVTdGFhdCBkZXIg +TmVkZXJsYW5kZW4xKTAnBgNVBAMMIFN0YWF0IGRlciBOZWRlcmxhbmRlbiBFViBS +b290IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA48d+ifkkSzrS +M4M1LGns3Amk41GoJSt5uAg94JG6hIXGhaTK5skuU6TJJB79VWZxXSzFYGgEt9nC +UiY4iKTWO0Cmws0/zZiTs1QUWJZV1VD+hq2kY39ch/aO5ieSZxeSAgMs3NZmdO3d +Z//BYY1jTw+bbRcwJu+r0h8QoPnFfxZpgQNH7R5ojXKhTbImxrpsX23Wr9GxE46p +rfNeaXUmGD5BKyF/7otdBwadQ8QpCiv8Kj6GyzyDOvnJDdrFmeK8eEEzduG/L13l +pJhQDBXd4Pqcfzho0LKmeqfRMb1+ilgnQ7O6M5HTp5gVXJrm0w912fxBmJc+qiXb +j5IusHsMX/FjqTf5m3VpTCgmJdrV8hJwRVXj33NeN/UhbJCONVrJ0yPr08C+eKxC +KFhmpUZtcALXEPlLVPxdhkqHz3/KRawRWrUgUY0viEeXOcDPusBCAUCZSCELa6fS +/ZbV0b5GnUngC6agIk440ME8MLxwjyx1zNDFjFE7PZQIZCZhfbnDZY8UnCHQqv0X +cgOPvZuM5l5Tnrmd74K74bzickFbIZTTRTeU0d8JOV3nI6qaHcptqAqGhYqCvkIH +1vI4gnPah1vlPNOePqc7nvQDs/nxfRN0Av+7oeX6AHkcpmZBiFxgV6YuCcS6/ZrP +px9Aw7vMWgpVSzs4dlG4Y4uElBbmVvMCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB +/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFP6rAJCYniT8qcwaivsnuL8wbqg7 +MA0GCSqGSIb3DQEBCwUAA4ICAQDPdyxuVr5Os7aEAJSrR8kN0nbHhp8dB9O2tLsI +eK9p0gtJ3jPFrK3CiAJ9Brc1AsFgyb/E6JTe1NOpEyVa/m6irn0F3H3zbPB+po3u +2dfOWBfoqSmuc0iH55vKbimhZF8ZE/euBhD/UcabTVUlT5OZEAFTdfETzsemQUHS +v4ilf0X8rLiltTMMgsT7B/Zq5SWEXwbKwYY5EdtYzXc7LMJMD16a4/CrPmEbUCTC +wPTxGfARKbalGAKb12NMcIxHowNDXLldRqANb/9Zjr7dn3LDWyvfjFvO5QxGbJKy +CqNMVEIYFRIYvdr8unRu/8G2oGTYqV9Vrp9canaW2HNnh/tNf1zuacpzEPuKqf2e +vTY4SUmH9A4U8OmHuD+nT3pajnnUk+S7aFKErGzp85hwVXIy+TSrK0m1zSBi5Dp6 +Z2Orltxtrpfs/J92VoguZs9btsmksNcFuuEnL5O7Jiqik7Ab846+HUCjuTaPPoIa +Gl6I6lD4WeKDRikL40Rc4ZW2aZCaFG+XroHPaO+Zmr615+F/+PoTRxZMzG0IQOeL +eG9QgkRQP2YGiqtDhFZKDyAthg710tvSeopLzaXoTvFeJiUBWSOgftL2fiFX1ye8 +FVdMpEbB4IMeDExNH08GGeL5qPQ6gqGyeUN51q1veieQA6TqJIc/2b3Z6fJfUEkc +7uzXLg== +-----END CERTIFICATE----- + +# Issuer: CN=IdenTrust Commercial Root CA 1 O=IdenTrust +# Subject: CN=IdenTrust Commercial Root CA 1 O=IdenTrust +# Label: "IdenTrust Commercial Root CA 1" +# Serial: 13298821034946342390520003877796839426 +# MD5 Fingerprint: b3:3e:77:73:75:ee:a0:d3:e3:7e:49:63:49:59:bb:c7 +# SHA1 Fingerprint: df:71:7e:aa:4a:d9:4e:c9:55:84:99:60:2d:48:de:5f:bc:f0:3a:25 +# SHA256 Fingerprint: 5d:56:49:9b:e4:d2:e0:8b:cf:ca:d0:8a:3e:38:72:3d:50:50:3b:de:70:69:48:e4:2f:55:60:30:19:e5:28:ae +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIQCgFCgAAAAUUjyES1AAAAAjANBgkqhkiG9w0BAQsFADBK +MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVu +VHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwHhcNMTQwMTE2MTgxMjIzWhcNMzQw +MTE2MTgxMjIzWjBKMQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScw +JQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCnUBneP5k91DNG8W9RYYKyqU+PZ4ldhNlT +3Qwo2dfw/66VQ3KZ+bVdfIrBQuExUHTRgQ18zZshq0PirK1ehm7zCYofWjK9ouuU ++ehcCuz/mNKvcbO0U59Oh++SvL3sTzIwiEsXXlfEU8L2ApeN2WIrvyQfYo3fw7gp +S0l4PJNgiCL8mdo2yMKi1CxUAGc1bnO/AljwpN3lsKImesrgNqUZFvX9t++uP0D1 +bVoE/c40yiTcdCMbXTMTEl3EASX2MN0CXZ/g1Ue9tOsbobtJSdifWwLziuQkkORi +T0/Br4sOdBeo0XKIanoBScy0RnnGF7HamB4HWfp1IYVl3ZBWzvurpWCdxJ35UrCL +vYf5jysjCiN2O/cz4ckA82n5S6LgTrx+kzmEB/dEcH7+B1rlsazRGMzyNeVJSQjK +Vsk9+w8YfYs7wRPCTY/JTw436R+hDmrfYi7LNQZReSzIJTj0+kuniVyc0uMNOYZK +dHzVWYfCP04MXFL0PfdSgvHqo6z9STQaKPNBiDoT7uje/5kdX7rL6B7yuVBgwDHT +c+XvvqDtMwt0viAgxGds8AgDelWAf0ZOlqf0Hj7h9tgJ4TNkK2PXMl6f+cB7D3hv +l7yTmvmcEpB4eoCHFddydJxVdHixuuFucAS6T6C6aMN7/zHwcz09lCqxC0EOoP5N +iGVreTO01wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB +/zAdBgNVHQ4EFgQU7UQZwNPwBovupHu+QucmVMiONnYwDQYJKoZIhvcNAQELBQAD +ggIBAA2ukDL2pkt8RHYZYR4nKM1eVO8lvOMIkPkp165oCOGUAFjvLi5+U1KMtlwH +6oi6mYtQlNeCgN9hCQCTrQ0U5s7B8jeUeLBfnLOic7iPBZM4zY0+sLj7wM+x8uwt +LRvM7Kqas6pgghstO8OEPVeKlh6cdbjTMM1gCIOQ045U8U1mwF10A0Cj7oV+wh93 +nAbowacYXVKV7cndJZ5t+qntozo00Fl72u1Q8zW/7esUTTHHYPTa8Yec4kjixsU3 ++wYQ+nVZZjFHKdp2mhzpgq7vmrlR94gjmmmVYjzlVYA211QC//G5Xc7UI2/YRYRK +W2XviQzdFKcgyxilJbQN+QHwotL0AMh0jqEqSI5l2xPE4iUXfeu+h1sXIFRRk0pT +AwvsXcoz7WL9RccvW9xYoIA55vrX/hMUpu09lEpCdNTDd1lzzY9GvlU47/rokTLq +l1gEIt44w8y8bckzOmoKaT+gyOpyj4xjhiO9bTyWnpXgSUyqorkqG5w2gXjtw+hG +4iZZRHUe2XWJUc0QhJ1hYMtd+ZciTY6Y5uN/9lu7rs3KSoFrXgvzUeF0K+l+J6fZ +mUlO+KWA2yUPHGNiiskzZ2s8EIPGrd6ozRaOjfAHN3Gf8qv8QfXBi+wAN10J5U6A +7/qxXDgGpRtK4dw4LTzcqx+QGtVKnO7RcGzM7vRX+Bi6hG6H +-----END CERTIFICATE----- + +# Issuer: CN=IdenTrust Public Sector Root CA 1 O=IdenTrust +# Subject: CN=IdenTrust Public Sector Root CA 1 O=IdenTrust +# Label: "IdenTrust Public Sector Root CA 1" +# Serial: 13298821034946342390521976156843933698 +# MD5 Fingerprint: 37:06:a5:b0:fc:89:9d:ba:f4:6b:8c:1a:64:cd:d5:ba +# SHA1 Fingerprint: ba:29:41:60:77:98:3f:f4:f3:ef:f2:31:05:3b:2e:ea:6d:4d:45:fd +# SHA256 Fingerprint: 30:d0:89:5a:9a:44:8a:26:20:91:63:55:22:d1:f5:20:10:b5:86:7a:ca:e1:2c:78:ef:95:8f:d4:f4:38:9f:2f +-----BEGIN CERTIFICATE----- +MIIFZjCCA06gAwIBAgIQCgFCgAAAAUUjz0Z8AAAAAjANBgkqhkiG9w0BAQsFADBN +MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVu +VHJ1c3QgUHVibGljIFNlY3RvciBSb290IENBIDEwHhcNMTQwMTE2MTc1MzMyWhcN +MzQwMTE2MTc1MzMyWjBNMQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0 +MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3RvciBSb290IENBIDEwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2IpT8pEiv6EdrCvsnduTyP4o7 +ekosMSqMjbCpwzFrqHd2hCa2rIFCDQjrVVi7evi8ZX3yoG2LqEfpYnYeEe4IFNGy +RBb06tD6Hi9e28tzQa68ALBKK0CyrOE7S8ItneShm+waOh7wCLPQ5CQ1B5+ctMlS +bdsHyo+1W/CD80/HLaXIrcuVIKQxKFdYWuSNG5qrng0M8gozOSI5Cpcu81N3uURF +/YTLNiCBWS2ab21ISGHKTN9T0a9SvESfqy9rg3LvdYDaBjMbXcjaY8ZNzaxmMc3R +3j6HEDbhuaR672BQssvKplbgN6+rNBM5Jeg5ZuSYeqoSmJxZZoY+rfGwyj4GD3vw +EUs3oERte8uojHH01bWRNszwFcYr3lEXsZdMUD2xlVl8BX0tIdUAvwFnol57plzy +9yLxkA2T26pEUWbMfXYD62qoKjgZl3YNa4ph+bz27nb9cCvdKTz4Ch5bQhyLVi9V +GxyhLrXHFub4qjySjmm2AcG1hp2JDws4lFTo6tyePSW8Uybt1as5qsVATFSrsrTZ +2fjXctscvG29ZV/viDUqZi/u9rNl8DONfJhBaUYPQxxp+pu10GFqzcpL2UyQRqsV +WaFHVCkugyhfHMKiq3IXAAaOReyL4jM9f9oZRORicsPfIsbyVtTdX5Vy7W1f90gD +W/3FKqD2cyOEEBsB5wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQU43HgntinQtnbcZFrlJPrw6PRFKMwDQYJKoZIhvcN +AQELBQADggIBAEf63QqwEZE4rU1d9+UOl1QZgkiHVIyqZJnYWv6IAcVYpZmxI1Qj +t2odIFflAWJBF9MJ23XLblSQdf4an4EKwt3X9wnQW3IV5B4Jaj0z8yGa5hV+rVHV +DRDtfULAj+7AmgjVQdZcDiFpboBhDhXAuM/FSRJSzL46zNQuOAXeNf0fb7iAaJg9 +TaDKQGXSc3z1i9kKlT/YPyNtGtEqJBnZhbMX73huqVjRI9PHE+1yJX9dsXNw0H8G +lwmEKYBhHfpe/3OsoOOJuBxxFcbeMX8S3OFtm6/n6J91eEyrRjuazr8FGF1NFTwW +mhlQBJqymm9li1JfPFgEKCXAZmExfrngdbkaqIHWchezxQMxNRF4eKLg6TCMf4Df +WN88uieW4oA0beOY02QnrEh+KHdcxiVhJfiFDGX6xDIvpZgF5PgLZxYWxoK4Mhn5 ++bl53B/N66+rDt0b20XkeucC4pVd/GnwU2lhlXV5C15V5jgclKlZM57IcXR5f1GJ +tshquDDIajjDbp7hNxbqBWJMWxJH7ae0s1hWx0nzfxJoCTFx8G34Tkf71oXuxVhA +GaQdp/lLQzfcaFpPz+vCZHTetBXZ9FRUGi8c15dxVJCO2SCdUyt/q4/i6jC8UDfv +8Ue1fXwsBOxonbRJRBD0ckscZOf85muQ3Wl9af0AVqW3rLatt8o+Ae+c +-----END CERTIFICATE----- + +# Issuer: CN=Entrust Root Certification Authority - G2 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2009 Entrust, Inc. - for authorized use only +# Subject: CN=Entrust Root Certification Authority - G2 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2009 Entrust, Inc. - for authorized use only +# Label: "Entrust Root Certification Authority - G2" +# Serial: 1246989352 +# MD5 Fingerprint: 4b:e2:c9:91:96:65:0c:f4:0e:5a:93:92:a0:0a:fe:b2 +# SHA1 Fingerprint: 8c:f4:27:fd:79:0c:3a:d1:66:06:8d:e8:1e:57:ef:bb:93:22:72:d4 +# SHA256 Fingerprint: 43:df:57:74:b0:3e:7f:ef:5f:e4:0d:93:1a:7b:ed:f1:bb:2e:6b:42:73:8c:4e:6d:38:41:10:3d:3a:a7:f3:39 +-----BEGIN CERTIFICATE----- +MIIEPjCCAyagAwIBAgIESlOMKDANBgkqhkiG9w0BAQsFADCBvjELMAkGA1UEBhMC +VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50 +cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3Qs +IEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVz +dCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIwHhcNMDkwNzA3MTcy +NTU0WhcNMzAxMjA3MTc1NTU0WjCBvjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUVu +dHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwt +dGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0 +aG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmlj +YXRpb24gQXV0aG9yaXR5IC0gRzIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQC6hLZy254Ma+KZ6TABp3bqMriVQRrJ2mFOWHLP/vaCeb9zYQYKpSfYs1/T +RU4cctZOMvJyig/3gxnQaoCAAEUesMfnmr8SVycco2gvCoe9amsOXmXzHHfV1IWN +cCG0szLni6LVhjkCsbjSR87kyUnEO6fe+1R9V77w6G7CebI6C1XiUJgWMhNcL3hW +wcKUs/Ja5CeanyTXxuzQmyWC48zCxEXFjJd6BmsqEZ+pCm5IO2/b1BEZQvePB7/1 +U1+cPvQXLOZprE4yTGJ36rfo5bs0vBmLrpxR57d+tVOxMyLlbc9wPBr64ptntoP0 +jaWvYkxN4FisZDQSA/i2jZRjJKRxAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAP +BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqciZ60B7vfec7aVHUbI2fkBJmqzAN +BgkqhkiG9w0BAQsFAAOCAQEAeZ8dlsa2eT8ijYfThwMEYGprmi5ZiXMRrEPR9RP/ +jTkrwPK9T3CMqS/qF8QLVJ7UG5aYMzyorWKiAHarWWluBh1+xLlEjZivEtRh2woZ +Rkfz6/djwUAFQKXSt/S1mja/qYh2iARVBCuch38aNzx+LaUa2NSJXsq9rD1s2G2v +1fN2D807iDginWyTmsQ9v4IbZT+mD12q/OWyFcq1rca8PdCE6OoGcrBNOTJ4vz4R +nAuknZoh8/CbCzB428Hch0P+vGOaysXCHMnHjf87ElgI5rY97HosTvuDls4MPGmH +VHOkc8KT/1EQrBVUAdj8BbGJoX90g5pJ19xOe4pIb4tF9g== +-----END CERTIFICATE----- + +# Issuer: CN=Entrust Root Certification Authority - EC1 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2012 Entrust, Inc. - for authorized use only +# Subject: CN=Entrust Root Certification Authority - EC1 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2012 Entrust, Inc. - for authorized use only +# Label: "Entrust Root Certification Authority - EC1" +# Serial: 51543124481930649114116133369 +# MD5 Fingerprint: b6:7e:1d:f0:58:c5:49:6c:24:3b:3d:ed:98:18:ed:bc +# SHA1 Fingerprint: 20:d8:06:40:df:9b:25:f5:12:25:3a:11:ea:f7:59:8a:eb:14:b5:47 +# SHA256 Fingerprint: 02:ed:0e:b2:8c:14:da:45:16:5c:56:67:91:70:0d:64:51:d7:fb:56:f0:b2:ab:1d:3b:8e:b0:70:e5:6e:df:f5 +-----BEGIN CERTIFICATE----- +MIIC+TCCAoCgAwIBAgINAKaLeSkAAAAAUNCR+TAKBggqhkjOPQQDAzCBvzELMAkG +A1UEBhMCVVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3 +d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDEyIEVu +dHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25seTEzMDEGA1UEAxMq +RW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRUMxMB4XDTEy +MTIxODE1MjUzNloXDTM3MTIxODE1NTUzNlowgb8xCzAJBgNVBAYTAlVTMRYwFAYD +VQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0 +L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxMiBFbnRydXN0LCBJbmMuIC0g +Zm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxMzAxBgNVBAMTKkVudHJ1c3QgUm9vdCBD +ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEVDMTB2MBAGByqGSM49AgEGBSuBBAAi +A2IABIQTydC6bUF74mzQ61VfZgIaJPRbiWlH47jCffHyAsWfoPZb1YsGGYZPUxBt +ByQnoaD41UcZYUx9ypMn6nQM72+WCf5j7HBdNq1nd67JnXxVRDqiY1Ef9eNi1KlH +Bz7MIKNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O +BBYEFLdj5xrdjekIplWDpOBqUEFlEUJJMAoGCCqGSM49BAMDA2cAMGQCMGF52OVC +R98crlOZF7ZvHH3hvxGU0QOIdeSNiaSKd0bebWHvAvX7td/M/k7//qnmpwIwW5nX +hTcGtXsI/esni0qU+eH6p44mCOh8kmhtc9hvJqwhAriZtyZBWyVgrtBIGu4G +-----END CERTIFICATE----- + +# Issuer: CN=CFCA EV ROOT O=China Financial Certification Authority +# Subject: CN=CFCA EV ROOT O=China Financial Certification Authority +# Label: "CFCA EV ROOT" +# Serial: 407555286 +# MD5 Fingerprint: 74:e1:b6:ed:26:7a:7a:44:30:33:94:ab:7b:27:81:30 +# SHA1 Fingerprint: e2:b8:29:4b:55:84:ab:6b:58:c2:90:46:6c:ac:3f:b8:39:8f:84:83 +# SHA256 Fingerprint: 5c:c3:d7:8e:4e:1d:5e:45:54:7a:04:e6:87:3e:64:f9:0c:f9:53:6d:1c:cc:2e:f8:00:f3:55:c4:c5:fd:70:fd +-----BEGIN CERTIFICATE----- +MIIFjTCCA3WgAwIBAgIEGErM1jANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJD +TjEwMC4GA1UECgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9y +aXR5MRUwEwYDVQQDDAxDRkNBIEVWIFJPT1QwHhcNMTIwODA4MDMwNzAxWhcNMjkx +MjMxMDMwNzAxWjBWMQswCQYDVQQGEwJDTjEwMC4GA1UECgwnQ2hpbmEgRmluYW5j +aWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQDDAxDRkNBIEVWIFJP +T1QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDXXWvNED8fBVnVBU03 +sQ7smCuOFR36k0sXgiFxEFLXUWRwFsJVaU2OFW2fvwwbwuCjZ9YMrM8irq93VCpL +TIpTUnrD7i7es3ElweldPe6hL6P3KjzJIx1qqx2hp/Hz7KDVRM8Vz3IvHWOX6Jn5 +/ZOkVIBMUtRSqy5J35DNuF++P96hyk0g1CXohClTt7GIH//62pCfCqktQT+x8Rgp +7hZZLDRJGqgG16iI0gNyejLi6mhNbiyWZXvKWfry4t3uMCz7zEasxGPrb382KzRz +EpR/38wmnvFyXVBlWY9ps4deMm/DGIq1lY+wejfeWkU7xzbh72fROdOXW3NiGUgt +hxwG+3SYIElz8AXSG7Ggo7cbcNOIabla1jj0Ytwli3i/+Oh+uFzJlU9fpy25IGvP +a931DfSCt/SyZi4QKPaXWnuWFo8BGS1sbn85WAZkgwGDg8NNkt0yxoekN+kWzqot +aK8KgWU6cMGbrU1tVMoqLUuFG7OA5nBFDWteNfB/O7ic5ARwiRIlk9oKmSJgamNg +TnYGmE69g60dWIolhdLHZR4tjsbftsbhf4oEIRUpdPA+nJCdDC7xij5aqgwJHsfV +PKPtl8MeNPo4+QgO48BdK4PRVmrJtqhUUy54Mmc9gn900PvhtgVguXDbjgv5E1hv +cWAQUhC5wUEJ73IfZzF4/5YFjQIDAQABo2MwYTAfBgNVHSMEGDAWgBTj/i39KNAL +tbq2osS/BqoFjJP7LzAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAd +BgNVHQ4EFgQU4/4t/SjQC7W6tqLEvwaqBYyT+y8wDQYJKoZIhvcNAQELBQADggIB +ACXGumvrh8vegjmWPfBEp2uEcwPenStPuiB/vHiyz5ewG5zz13ku9Ui20vsXiObT +ej/tUxPQ4i9qecsAIyjmHjdXNYmEwnZPNDatZ8POQQaIxffu2Bq41gt/UP+TqhdL +jOztUmCypAbqTuv0axn96/Ua4CUqmtzHQTb3yHQFhDmVOdYLO6Qn+gjYXB74BGBS +ESgoA//vU2YApUo0FmZ8/Qmkrp5nGm9BC2sGE5uPhnEFtC+NiWYzKXZUmhH4J/qy +P5Hgzg0b8zAarb8iXRvTvyUFTeGSGn+ZnzxEk8rUQElsgIfXBDrDMlI1Dlb4pd19 +xIsNER9Tyx6yF7Zod1rg1MvIB671Oi6ON7fQAUtDKXeMOZePglr4UeWJoBjnaH9d +Ci77o0cOPaYjesYBx4/IXr9tgFa+iiS6M+qf4TIRnvHST4D2G0CvOJ4RUHlzEhLN +5mydLIhyPDCBBpEi6lmt2hkuIsKNuYyH4Ga8cyNfIWRjgEj1oDwYPZTISEEdQLpe +/v5WOaHIz16eGWRGENoXkbcFgKyLmZJ956LYBws2J+dIeWCKw9cTXPhyQN9Ky8+Z +AAoACxGV2lZFA4gKn2fQ1XmxqI1AbQ3CekD6819kR5LLU7m7Wc5P/dAVUwHY3+vZ +5nbv0CO7O6l5s9UCKc2Jo5YPSjXnTkLAdc0Hz+Ys63su +-----END CERTIFICATE----- + +# Issuer: CN=TÜRKTRUST Elektronik Sertifika Hizmet Sağlayıcısı H5 O=TÜRKTRUST Bilgi İletişim ve Bilişim Güvenliği Hizmetleri A.Ş. +# Subject: CN=TÜRKTRUST Elektronik Sertifika Hizmet Sağlayıcısı H5 O=TÜRKTRUST Bilgi İletişim ve Bilişim Güvenliği Hizmetleri A.Ş. +# Label: "TÜRKTRUST Elektronik Sertifika Hizmet Sağlayıcısı H5" +# Serial: 156233699172481 +# MD5 Fingerprint: da:70:8e:f0:22:df:93:26:f6:5f:9f:d3:15:06:52:4e +# SHA1 Fingerprint: c4:18:f6:4d:46:d1:df:00:3d:27:30:13:72:43:a9:12:11:c6:75:fb +# SHA256 Fingerprint: 49:35:1b:90:34:44:c1:85:cc:dc:5c:69:3d:24:d8:55:5c:b2:08:d6:a8:14:13:07:69:9f:4a:f0:63:19:9d:78 +-----BEGIN CERTIFICATE----- +MIIEJzCCAw+gAwIBAgIHAI4X/iQggTANBgkqhkiG9w0BAQsFADCBsTELMAkGA1UE +BhMCVFIxDzANBgNVBAcMBkFua2FyYTFNMEsGA1UECgxEVMOcUktUUlVTVCBCaWxn +aSDEsGxldGnFn2ltIHZlIEJpbGnFn2ltIEfDvHZlbmxpxJ9pIEhpem1ldGxlcmkg +QS7Fni4xQjBABgNVBAMMOVTDnFJLVFJVU1QgRWxla3Ryb25payBTZXJ0aWZpa2Eg +SGl6bWV0IFNhxJ9sYXnEsWPEsXPEsSBINTAeFw0xMzA0MzAwODA3MDFaFw0yMzA0 +MjgwODA3MDFaMIGxMQswCQYDVQQGEwJUUjEPMA0GA1UEBwwGQW5rYXJhMU0wSwYD +VQQKDERUw5xSS1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUgQmlsacWfaW0gR8O8 +dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLjFCMEAGA1UEAww5VMOcUktUUlVTVCBF +bGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxIEg1MIIB +IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApCUZ4WWe60ghUEoI5RHwWrom +/4NZzkQqL/7hzmAD/I0Dpe3/a6i6zDQGn1k19uwsu537jVJp45wnEFPzpALFp/kR +Gml1bsMdi9GYjZOHp3GXDSHHmflS0yxjXVW86B8BSLlg/kJK9siArs1mep5Fimh3 +4khon6La8eHBEJ/rPCmBp+EyCNSgBbGM+42WAA4+Jd9ThiI7/PS98wl+d+yG6w8z +5UNP9FR1bSmZLmZaQ9/LXMrI5Tjxfjs1nQ/0xVqhzPMggCTTV+wVunUlm+hkS7M0 +hO8EuPbJbKoCPrZV4jI3X/xml1/N1p7HIL9Nxqw/dV8c7TKcfGkAaZHjIxhT6QID +AQABo0IwQDAdBgNVHQ4EFgQUVpkHHtOsDGlktAxQR95DLL4gwPswDgYDVR0PAQH/ +BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAJ5FdnsX +SDLyOIspve6WSk6BGLFRRyDN0GSxDsnZAdkJzsiZ3GglE9Rc8qPoBP5yCccLqh0l +VX6Wmle3usURehnmp349hQ71+S4pL+f5bFgWV1Al9j4uPqrtd3GqqpmWRgqujuwq +URawXs3qZwQcWDD1YIq9pr1N5Za0/EKJAWv2cMhQOQwt1WbZyNKzMrcbGW3LM/nf +peYVhDfwwvJllpKQd/Ct9JDpEXjXk4nAPQu6KfTomZ1yju2dL+6SfaHx/126M2CF +Yv4HAqGEVka+lgqaE9chTLd8B59OTj+RdPsnnRHM3eaxynFNExc5JsUpISuTKWqW ++qtB4Uu2NQvAmxU= +-----END CERTIFICATE----- + +# Issuer: CN=TÜRKTRUST Elektronik Sertifika Hizmet Sağlayıcısı H6 O=TÜRKTRUST Bilgi İletişim ve Bilişim Güvenliği Hizmetleri A.Ş. +# Subject: CN=TÜRKTRUST Elektronik Sertifika Hizmet Sağlayıcısı H6 O=TÜRKTRUST Bilgi İletişim ve Bilişim Güvenliği Hizmetleri A.Ş. +# Label: "TÜRKTRUST Elektronik Sertifika Hizmet Sağlayıcısı H6" +# Serial: 138134509972618 +# MD5 Fingerprint: f8:c5:ee:2a:6b:be:95:8d:08:f7:25:4a:ea:71:3e:46 +# SHA1 Fingerprint: 8a:5c:8c:ee:a5:03:e6:05:56:ba:d8:1b:d4:f6:c9:b0:ed:e5:2f:e0 +# SHA256 Fingerprint: 8d:e7:86:55:e1:be:7f:78:47:80:0b:93:f6:94:d2:1d:36:8c:c0:6e:03:3e:7f:ab:04:bb:5e:b9:9d:a6:b7:00 +-----BEGIN CERTIFICATE----- +MIIEJjCCAw6gAwIBAgIGfaHyZeyKMA0GCSqGSIb3DQEBCwUAMIGxMQswCQYDVQQG +EwJUUjEPMA0GA1UEBwwGQW5rYXJhMU0wSwYDVQQKDERUw5xSS1RSVVNUIEJpbGdp +IMSwbGV0acWfaW0gdmUgQmlsacWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBB +LsWeLjFCMEAGA1UEAww5VMOcUktUUlVTVCBFbGVrdHJvbmlrIFNlcnRpZmlrYSBI +aXptZXQgU2HEn2xhecSxY8Sxc8SxIEg2MB4XDTEzMTIxODA5MDQxMFoXDTIzMTIx +NjA5MDQxMFowgbExCzAJBgNVBAYTAlRSMQ8wDQYDVQQHDAZBbmthcmExTTBLBgNV +BAoMRFTDnFJLVFJVU1QgQmlsZ2kgxLBsZXRpxZ9pbSB2ZSBCaWxpxZ9pbSBHw7x2 +ZW5sacSfaSBIaXptZXRsZXJpIEEuxZ4uMUIwQAYDVQQDDDlUw5xSS1RSVVNUIEVs +ZWt0cm9uaWsgU2VydGlmaWthIEhpem1ldCBTYcSfbGF5xLFjxLFzxLEgSDYwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCdsGjW6L0UlqMACprx9MfMkU1x +eHe59yEmFXNRFpQJRwXiM/VomjX/3EsvMsew7eKC5W/a2uqsxgbPJQ1BgfbBOCK9 ++bGlprMBvD9QFyv26WZV1DOzXPhDIHiTVRZwGTLmiddk671IUP320EEDwnS3/faA +z1vFq6TWlRKb55cTMgPp1KtDWxbtMyJkKbbSk60vbNg9tvYdDjTu0n2pVQ8g9P0p +u5FbHH3GQjhtQiht1AH7zYiXSX6484P4tZgvsycLSF5W506jM7NE1qXyGJTtHB6p +lVxiSvgNZ1GpryHV+DKdeboaX+UEVU0TRv/yz3THGmNtwx8XEsMeED5gCLMxAgMB +AAGjQjBAMB0GA1UdDgQWBBTdVRcT9qzoSCHK77Wv0QAy7Z6MtTAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAb1gNl0Oq +FlQ+v6nfkkU/hQu7VtMMUszIv3ZnXuaqs6fvuay0EBQNdH49ba3RfdCaqaXKGDsC +QC4qnFAUi/5XfldcEQlLNkVS9z2sFP1E34uXI9TDwe7UU5X+LEr+DXCqu4svLcsy +o4LyVN/Y8t3XSHLuSqMplsNEzm61kod2pLv0kmzOLBQJZo6NrRa1xxsJYTvjIKID +gI6tflEATseWhvtDmHd9KMeP2Cpu54Rvl0EpABZeTeIT6lnAY2c6RPuY/ATTMHKm +9ocJV612ph1jmv3XZch4gyt1O6VbuA1df74jrlZVlFjvH4GMKrLN5ptjnhi85WsG +tAuYSyher4hYyw== +-----END CERTIFICATE----- + +# Issuer: CN=Certinomis - Root CA O=Certinomis OU=0002 433998903 +# Subject: CN=Certinomis - Root CA O=Certinomis OU=0002 433998903 +# Label: "Certinomis - Root CA" +# Serial: 1 +# MD5 Fingerprint: 14:0a:fd:8d:a8:28:b5:38:69:db:56:7e:61:22:03:3f +# SHA1 Fingerprint: 9d:70:bb:01:a5:a4:a0:18:11:2e:f7:1c:01:b9:32:c5:34:e7:88:a8 +# SHA256 Fingerprint: 2a:99:f5:bc:11:74:b7:3c:bb:1d:62:08:84:e0:1c:34:e5:1c:cb:39:78:da:12:5f:0e:33:26:88:83:bf:41:58 +-----BEGIN CERTIFICATE----- +MIIFkjCCA3qgAwIBAgIBATANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJGUjET +MBEGA1UEChMKQ2VydGlub21pczEXMBUGA1UECxMOMDAwMiA0MzM5OTg5MDMxHTAb +BgNVBAMTFENlcnRpbm9taXMgLSBSb290IENBMB4XDTEzMTAyMTA5MTcxOFoXDTMz +MTAyMTA5MTcxOFowWjELMAkGA1UEBhMCRlIxEzARBgNVBAoTCkNlcnRpbm9taXMx +FzAVBgNVBAsTDjAwMDIgNDMzOTk4OTAzMR0wGwYDVQQDExRDZXJ0aW5vbWlzIC0g +Um9vdCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANTMCQosP5L2 +fxSeC5yaah1AMGT9qt8OHgZbn1CF6s2Nq0Nn3rD6foCWnoR4kkjW4znuzuRZWJfl +LieY6pOod5tK8O90gC3rMB+12ceAnGInkYjwSond3IjmFPnVAy//ldu9n+ws+hQV +WZUKxkd8aRi5pwP5ynapz8dvtF4F/u7BUrJ1Mofs7SlmO/NKFoL21prbcpjp3vDF +TKWrteoB4owuZH9kb/2jJZOLyKIOSY008B/sWEUuNKqEUL3nskoTuLAPrjhdsKkb +5nPJWqHZZkCqqU2mNAKthH6yI8H7KsZn9DS2sJVqM09xRLWtwHkziOC/7aOgFLSc +CbAK42C++PhmiM1b8XcF4LVzbsF9Ri6OSyemzTUK/eVNfaoqoynHWmgE6OXWk6Ri +wsXm9E/G+Z8ajYJJGYrKWUM66A0ywfRMEwNvbqY/kXPLynNvEiCL7sCCeN5LLsJJ +wx3tFvYk9CcbXFcx3FXuqB5vbKziRcxXV4p1VxngtViZSTYxPDMBbRZKzbgqg4SG +m/lg0h9tkQPTYKbVPZrdd5A9NaSfD171UkRpucC63M9933zZxKyGIjK8e2uR73r4 +F2iw4lNVYC2vPsKD2NkJK/DAZNuHi5HMkesE/Xa0lZrmFAYb1TQdvtj/dBxThZng +WVJKYe2InmtJiUZ+IFrZ50rlau7SZRFDAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIB +BjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTvkUz1pcMw6C8I6tNxIqSSaHh0 +2TAfBgNVHSMEGDAWgBTvkUz1pcMw6C8I6tNxIqSSaHh02TANBgkqhkiG9w0BAQsF +AAOCAgEAfj1U2iJdGlg+O1QnurrMyOMaauo++RLrVl89UM7g6kgmJs95Vn6RHJk/ +0KGRHCwPT5iVWVO90CLYiF2cN/z7ZMF4jIuaYAnq1fohX9B0ZedQxb8uuQsLrbWw +F6YSjNRieOpWauwK0kDDPAUwPk2Ut59KA9N9J0u2/kTO+hkzGm2kQtHdzMjI1xZS +g081lLMSVX3l4kLr5JyTCcBMWwerx20RoFAXlCOotQqSD7J6wWAsOMwaplv/8gzj +qh8c3LigkyfeY+N/IZ865Z764BNqdeuWXGKRlI5nU7aJ+BIJy29SWwNyhlCVCNSN +h4YVH5Uk2KRvms6knZtt0rJ2BobGVgjF6wnaNsIbW0G+YSrjcOa4pvi2WsS9Iff/ +ql+hbHY5ZtbqTFXhADObE5hjyW/QASAJN1LnDE8+zbz1X5YnpyACleAu6AdBBR8V +btaw5BngDwKTACdyxYvRVB9dSsNAl35VpnzBMwQUAR1JIGkLGZOdblgi90AMRgwj +Y/M50n92Uaf0yKHxDHYiI0ZSKS3io0EHVmmY0gUJvGnHWmHNj4FgFU2A3ZDifcRQ +8ow7bkrHxuaAKzyBvBGAFhAn1/DNP3nMcyrDflOR1m749fPH0FFNjkulW+YZFzvW +gQncItzujrnEj1PhZ7szuIgVRs/taTX/dQ1G885x4cVrhkIGuUE= +-----END CERTIFICATE----- + +# Issuer: CN=OISTE WISeKey Global Root GB CA O=WISeKey OU=OISTE Foundation Endorsed +# Subject: CN=OISTE WISeKey Global Root GB CA O=WISeKey OU=OISTE Foundation Endorsed +# Label: "OISTE WISeKey Global Root GB CA" +# Serial: 157768595616588414422159278966750757568 +# MD5 Fingerprint: a4:eb:b9:61:28:2e:b7:2f:98:b0:35:26:90:99:51:1d +# SHA1 Fingerprint: 0f:f9:40:76:18:d3:d7:6a:4b:98:f0:a8:35:9e:0c:fd:27:ac:cc:ed +# SHA256 Fingerprint: 6b:9c:08:e8:6e:b0:f7:67:cf:ad:65:cd:98:b6:21:49:e5:49:4a:67:f5:84:5e:7b:d1:ed:01:9f:27:b8:6b:d6 +-----BEGIN CERTIFICATE----- +MIIDtTCCAp2gAwIBAgIQdrEgUnTwhYdGs/gjGvbCwDANBgkqhkiG9w0BAQsFADBt +MQswCQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUg +Rm91bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9i +YWwgUm9vdCBHQiBDQTAeFw0xNDEyMDExNTAwMzJaFw0zOTEyMDExNTEwMzFaMG0x +CzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQLExlPSVNURSBG +b3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2Jh +bCBSb290IEdCIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Be3 +HEokKtaXscriHvt9OO+Y9bI5mE4nuBFde9IllIiCFSZqGzG7qFshISvYD06fWvGx +WuR51jIjK+FTzJlFXHtPrby/h0oLS5daqPZI7H17Dc0hBt+eFf1Biki3IPShehtX +1F1Q/7pn2COZH8g/497/b1t3sWtuuMlk9+HKQUYOKXHQuSP8yYFfTvdv37+ErXNk +u7dCjmn21HYdfp2nuFeKUWdy19SouJVUQHMD9ur06/4oQnc/nSMbsrY9gBQHTC5P +99UKFg29ZkM3fiNDecNAhvVMKdqOmq0NpQSHiB6F4+lT1ZvIiwNjeOvgGUpuuy9r +M2RYk61pv48b74JIxwIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUw +AwEB/zAdBgNVHQ4EFgQUNQ/INmNe4qPs+TtmFc5RUuORmj0wEAYJKwYBBAGCNxUB +BAMCAQAwDQYJKoZIhvcNAQELBQADggEBAEBM+4eymYGQfp3FsLAmzYh7KzKNbrgh +cViXfa43FK8+5/ea4n32cZiZBKpDdHij40lhPnOMTZTg+XHEthYOU3gf1qKHLwI5 +gSk8rxWYITD+KJAAjNHhy/peyP34EEY7onhCkRd0VQreUGdNZtGn//3ZwLWoo4rO +ZvUPQ82nK1d7Y0Zqqi5S2PTt4W2tKZB4SLrhI6qjiey1q5bAtEuiHZeeevJuQHHf +aPFlTc58Bd9TZaml8LGXBHAVRgOY1NK/VLSgWH1Sb9pWJmLU2NuJMW8c8CLC02Ic +Nc1MaRVUGpCY3useX8p3x8uOPUNpnJpY0CQ73xtAln41rYHHTnG6iBM= +-----END CERTIFICATE----- + +# Issuer: CN=Certification Authority of WoSign G2 O=WoSign CA Limited +# Subject: CN=Certification Authority of WoSign G2 O=WoSign CA Limited +# Label: "Certification Authority of WoSign G2" +# Serial: 142423943073812161787490648904721057092 +# MD5 Fingerprint: c8:1c:7d:19:aa:cb:71:93:f2:50:f8:52:a8:1e:ba:60 +# SHA1 Fingerprint: fb:ed:dc:90:65:b7:27:20:37:bc:55:0c:9c:56:de:bb:f2:78:94:e1 +# SHA256 Fingerprint: d4:87:a5:6f:83:b0:74:82:e8:5e:96:33:94:c1:ec:c2:c9:e5:1d:09:03:ee:94:6b:02:c3:01:58:1e:d9:9e:16 +-----BEGIN CERTIFICATE----- +MIIDfDCCAmSgAwIBAgIQayXaioidfLwPBbOxemFFRDANBgkqhkiG9w0BAQsFADBY +MQswCQYDVQQGEwJDTjEaMBgGA1UEChMRV29TaWduIENBIExpbWl0ZWQxLTArBgNV +BAMTJENlcnRpZmljYXRpb24gQXV0aG9yaXR5IG9mIFdvU2lnbiBHMjAeFw0xNDEx +MDgwMDU4NThaFw00NDExMDgwMDU4NThaMFgxCzAJBgNVBAYTAkNOMRowGAYDVQQK +ExFXb1NpZ24gQ0EgTGltaXRlZDEtMCsGA1UEAxMkQ2VydGlmaWNhdGlvbiBBdXRo +b3JpdHkgb2YgV29TaWduIEcyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEAvsXEoCKASU+/2YcRxlPhuw+9YH+v9oIOH9ywjj2X4FA8jzrvZjtFB5sg+OPX +JYY1kBaiXW8wGQiHC38Gsp1ij96vkqVg1CuAmlI/9ZqD6TRay9nVYlzmDuDfBpgO +gHzKtB0TiGsOqCR3A9DuW/PKaZE1OVbFbeP3PU9ekzgkyhjpJMuSA93MHD0JcOQg +5PGurLtzaaNjOg9FD6FKmsLRY6zLEPg95k4ot+vElbGs/V6r+kHLXZ1L3PR8du9n +fwB6jdKgGlxNIuG12t12s9R23164i5jIFFTMaxeSt+BKv0mUYQs4kI9dJGwlezt5 +2eJ+na2fmKEG/HgUYFf47oB3sQIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU+mCp62XF3RYUCE4MD42b4Pdkr2cwDQYJ +KoZIhvcNAQELBQADggEBAFfDejaCnI2Y4qtAqkePx6db7XznPWZaOzG73/MWM5H8 +fHulwqZm46qwtyeYP0nXYGdnPzZPSsvxFPpahygc7Y9BMsaV+X3avXtbwrAh449G +3CE4Q3RM+zD4F3LBMvzIkRfEzFg3TgvMWvchNSiDbGAtROtSjFA9tWwS1/oJu2yy +SrHFieT801LYYRf+epSEj3m2M1m6D8QL4nCgS3gu+sif/a+RZQp4OBXllxcU3fng +LDT4ONCEIgDAFFEYKwLcMFrw6AF8NTojrwjkr6qOKEJJLvD1mTS+7Q9LGOHSJDy7 +XUe3IfKN0QqZjuNuPq1w4I+5ysxugTH2e5x6eeRncRg= +-----END CERTIFICATE----- + +# Issuer: CN=CA WoSign ECC Root O=WoSign CA Limited +# Subject: CN=CA WoSign ECC Root O=WoSign CA Limited +# Label: "CA WoSign ECC Root" +# Serial: 138625735294506723296996289575837012112 +# MD5 Fingerprint: 80:c6:53:ee:61:82:28:72:f0:ff:21:b9:17:ca:b2:20 +# SHA1 Fingerprint: d2:7a:d2:be:ed:94:c0:a1:3c:c7:25:21:ea:5d:71:be:81:19:f3:2b +# SHA256 Fingerprint: 8b:45:da:1c:06:f7:91:eb:0c:ab:f2:6b:e5:88:f5:fb:23:16:5c:2e:61:4b:f8:85:56:2d:0d:ce:50:b2:9b:02 +-----BEGIN CERTIFICATE----- +MIICCTCCAY+gAwIBAgIQaEpYcIBr8I8C+vbe6LCQkDAKBggqhkjOPQQDAzBGMQsw +CQYDVQQGEwJDTjEaMBgGA1UEChMRV29TaWduIENBIExpbWl0ZWQxGzAZBgNVBAMT +EkNBIFdvU2lnbiBFQ0MgUm9vdDAeFw0xNDExMDgwMDU4NThaFw00NDExMDgwMDU4 +NThaMEYxCzAJBgNVBAYTAkNOMRowGAYDVQQKExFXb1NpZ24gQ0EgTGltaXRlZDEb +MBkGA1UEAxMSQ0EgV29TaWduIEVDQyBSb290MHYwEAYHKoZIzj0CAQYFK4EEACID +YgAE4f2OuEMkq5Z7hcK6C62N4DrjJLnSsb6IOsq/Srj57ywvr1FQPEd1bPiUt5v8 +KB7FVMxjnRZLU8HnIKvNrCXSf4/CwVqCXjCLelTOA7WRf6qU0NGKSMyCBSah1VES +1ns2o0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E +FgQUqv3VWqP2h4syhf3RMluARZPzA7gwCgYIKoZIzj0EAwMDaAAwZQIxAOSkhLCB +1T2wdKyUpOgOPQB0TKGXa/kNUTyh2Tv0Daupn75OcsqF1NnstTJFGG+rrQIwfcf3 +aWMvoeGY7xMQ0Xk/0f7qO3/eVvSQsRUR2LIiFdAvwyYua/GRspBl9JrmkO5K +-----END CERTIFICATE----- From 8f791cf0b09b02d9c3cc94505823c2ff3203fe8d Mon Sep 17 00:00:00 2001 From: Nik Nyby Date: Thu, 3 Dec 2015 21:21:06 -0500 Subject: [PATCH 149/692] enable automated testing on django 1.9 and python 3.5 --- .travis.yml | 26 ++++++++++++++++++++++---- tox.ini | 2 +- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9b181ff4a..f4d84e6a8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,6 +17,7 @@ python: - "3.2" - "3.3" - "3.4" + - "3.5" - "pypy" env: @@ -24,8 +25,9 @@ env: - DJANGO=Django==1.4.20 - DJANGO=Django==1.5.12 - DJANGO=Django==1.6.11 - - DJANGO=Django==1.7.7 - - DJANGO=Django==1.8 + - DJANGO=Django==1.7.11 + - DJANGO=Django==1.8.7 + - DJANGO=Django==1.9 - 'DJANGO="-e git+git://github.com/django/django.git#egg=Django"' install: @@ -45,15 +47,31 @@ matrix: 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.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.8 + env: DJANGO=Django==1.9 + - python: "2.6" + env: DJANGO=Django==1.8.7 - python: "2.6" - env: DJANGO=Django==1.7.7 + env: DJANGO=Django==1.7.11 diff --git a/tox.ini b/tox.ini index 41d42e08a..3d3cbd735 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ # and then run "tox" from this directory. [tox] -envlist = py26, py27, py30, py31, py32, py33, py34, pypy +envlist = py26, py27, py30, py31, py32, py33, py34, py35, pypy [testenv] commands = From 546aa3afc76919360b161f2650c0e015f59d1e7d Mon Sep 17 00:00:00 2001 From: Przemek Kaminski Date: Fri, 4 Dec 2015 21:40:51 +0100 Subject: [PATCH 150/692] Django handler: add support for tags in logging setup --- docs/integrations/django.rst | 1 + raven/contrib/django/handlers.py | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/integrations/django.rst b/docs/integrations/django.rst index 995eb927a..3f39c39af 100644 --- a/docs/integrations/django.rst +++ b/docs/integrations/django.rst @@ -98,6 +98,7 @@ following config can be used:: 'sentry': { 'level': 'ERROR', 'class': 'raven.contrib.django.raven_compat.handlers.SentryHandler' + 'tags': {'custom-tag': 'x'}, }, 'console': { 'level': 'DEBUG', diff --git a/raven/contrib/django/handlers.py b/raven/contrib/django/handlers.py index 504fbc121..4db138196 100644 --- a/raven/contrib/django/handlers.py +++ b/raven/contrib/django/handlers.py @@ -13,8 +13,9 @@ class SentryHandler(BaseSentryHandler): - def __init__(self, level=logging.NOTSET): + def __init__(self, level=logging.NOTSET, tags=None): logging.Handler.__init__(self, level=level) + self.tags = tags def _get_client(self): from raven.contrib.django.models import client @@ -25,5 +26,7 @@ def _get_client(self): def _emit(self, record): request = getattr(record, 'request', None) + if self.tags is not None and not hasattr(record, 'tags'): + record.tags = self.tags return super(SentryHandler, self)._emit(record, request=request) From 6f0ad8232ad3bddb95c6d5b7ce5890ce409e8db6 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 10 Dec 2015 20:09:56 +0100 Subject: [PATCH 151/692] Spawn new thread for different pid This spawns a thread again first time on queue if the process was forked by the time the enqueueing happened. --- raven/transport/threaded.py | 17 ++++++++++++++--- tests/transport/threaded/tests.py | 23 +++++++++++++++++++++++ 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/raven/transport/threaded.py b/raven/transport/threaded.py index d56c59a55..b46e78673 100644 --- a/raven/transport/threaded.py +++ b/raven/transport/threaded.py @@ -30,18 +30,26 @@ def __init__(self, shutdown_timeout=DEFAULT_TIMEOUT): self._queue = Queue(-1) self._lock = threading.Lock() self._thread = None + self._thread_for_pid = None self.options = { 'shutdown_timeout': shutdown_timeout, } self.start() def is_alive(self): - return self._thread.is_alive() + if self._thread_for_pid != os.getpid(): + return False + return self._thread and self._thread.is_alive() + + def _ensure_thread(self): + if self.is_alive(): + return + self.start() def main_thread_terminated(self): self._lock.acquire() try: - if not self._thread: + if not self.is_alive(): # thread not started or already stopped - nothing to do return @@ -107,10 +115,11 @@ def start(self): """ self._lock.acquire() try: - if not self._thread: + if not self.is_alive(): self._thread = threading.Thread(target=self._target) self._thread.setDaemon(True) self._thread.start() + self._thread_for_pid = os.getpid() finally: self._lock.release() atexit.register(self.main_thread_terminated) @@ -125,10 +134,12 @@ def stop(self, timeout=None): self._queue.put_nowait(self._terminator) self._thread.join(timeout=timeout) self._thread = None + self._thread_for_pid = None finally: self._lock.release() def queue(self, callback, *args, **kwargs): + self._ensure_thread() self._queue.put_nowait((callback, args, kwargs)) def _target(self): diff --git a/tests/transport/threaded/tests.py b/tests/transport/threaded/tests.py index cbd74f03c..f63dc71ef 100644 --- a/tests/transport/threaded/tests.py +++ b/tests/transport/threaded/tests.py @@ -64,6 +64,29 @@ def test_shutdown_waits_for_send(self): self.assertEqual(len(transport.events), 1) + def test_fork_spawns_anew(self): + url = urlparse(self.url) + transport = DummyThreadedScheme(url) + transport.send_delay = 0.5 + + data = self.client.build_msg('raven.events.Message', message='foo') + + pid = os.fork() + if pid == 0: + time.sleep(0.1) + + transport.async_send(data, None, None, None) + + # this should wait for the message to get sent + transport.get_worker().main_thread_terminated() + + self.assertEqual(len(transport.events), 1) + # Use os._exit here so that py.test gets not confused about + # what the hell we're doing here. + os._exit(0) + else: + os.waitpid(pid, 0) + def test_fork_with_active_worker(self): # Test threaded transport when forking with an active worker. # Forking a process doesn't clone the worker thread - make sure From 6063125f9ca2a56c5a7f2819732b0e9bafe81c4b Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 10 Dec 2015 11:15:02 -0800 Subject: [PATCH 152/692] Cache wsgi property (fixes GH-677) --- raven/contrib/django/middleware/wsgi.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/raven/contrib/django/middleware/wsgi.py b/raven/contrib/django/middleware/wsgi.py index 643fb328c..86e4e7ece 100644 --- a/raven/contrib/django/middleware/wsgi.py +++ b/raven/contrib/django/middleware/wsgi.py @@ -8,6 +8,7 @@ from __future__ import absolute_import from raven.middleware import Sentry +from raven.utils import memoize class Sentry(Sentry): @@ -21,7 +22,7 @@ class Sentry(Sentry): def __init__(self, application): self.application = application - @property + @memoize def client(self): from raven.contrib.django.models import client return client From 95220c99ae6062fed7d4211c67b8dffc031f4c7c Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 10 Dec 2015 11:24:52 -0800 Subject: [PATCH 153/692] Remove use of check_output (not in Py2.6) --- tests/versioning/tests.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tests/versioning/tests.py b/tests/versioning/tests.py index 594969129..a39173c44 100644 --- a/tests/versioning/tests.py +++ b/tests/versioning/tests.py @@ -14,13 +14,22 @@ def has_git_requirements(): return os.path.exists(os.path.join(settings.PROJECT_ROOT, '.git', 'refs', 'heads', 'master')) +# Python 2.6 does not contain subprocess.check_output +def check_output(cmd, **kwargs): + return subprocess.Popen( + cmd, + stdout=subprocess.PIPE, + **kwargs + ).communicate()[0] + + @pytest.mark.skipif('not has_git_requirements()') def test_fetch_git_sha(): result = fetch_git_sha(settings.PROJECT_ROOT) assert result is not None assert len(result) == 40 assert isinstance(result, six.string_types) - assert result == subprocess.check_output( + assert result == check_output( 'git rev-parse --verify HEAD', shell=True, cwd=settings.PROJECT_ROOT ).decode('latin1').strip() From 02862d64c70e02f2d8f45aaeec0fa5e90e5f0144 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 10 Dec 2015 11:57:47 -0800 Subject: [PATCH 154/692] Support tags on root logging handler --- raven/contrib/django/handlers.py | 17 +++-------------- raven/handlers/logging.py | 4 ++++ 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/raven/contrib/django/handlers.py b/raven/contrib/django/handlers.py index 4db138196..14ef2d387 100644 --- a/raven/contrib/django/handlers.py +++ b/raven/contrib/django/handlers.py @@ -8,25 +8,14 @@ from __future__ import absolute_import -import logging +from raven.contrib.django.models import client from raven.handlers.logging import SentryHandler as BaseSentryHandler class SentryHandler(BaseSentryHandler): - def __init__(self, level=logging.NOTSET, tags=None): - logging.Handler.__init__(self, level=level) - self.tags = tags - - def _get_client(self): - from raven.contrib.django.models import client - - return client - - client = property(_get_client) + def __init__(self, *args, **kwargs): + super(SentryHandler, self).__init__(client=client, *args, **kwargs) def _emit(self, record): request = getattr(record, 'request', None) - if self.tags is not None and not hasattr(record, 'tags'): - record.tags = self.tags - return super(SentryHandler, self)._emit(record, request=request) diff --git a/raven/handlers/logging.py b/raven/handlers/logging.py index 4af0fba4c..ad2e912b4 100644 --- a/raven/handlers/logging.py +++ b/raven/handlers/logging.py @@ -45,6 +45,8 @@ def __init__(self, *args, **kwargs): else: self.client = client(*args, **kwargs) + self.tags = kwargs.pop('tags', None) + logging.Handler.__init__(self, level=kwargs.get('level', logging.NOTSET)) def can_record(self, record): @@ -168,6 +170,8 @@ def _emit(self, record, **kwargs): if hasattr(record, 'tags'): kwargs['tags'] = record.tags + elif self.tags: + kwargs['tags'] = self.tags kwargs.update(handler_kwargs) From c4b96d74c3763afcb2b7df165986e740c0451ca9 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 10 Dec 2015 12:06:54 -0800 Subject: [PATCH 155/692] Add environment support --- raven/base.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/raven/base.py b/raven/base.py index 74daf94a2..5065bca1e 100644 --- a/raven/base.py +++ b/raven/base.py @@ -165,6 +165,7 @@ def __init__(self, dsn=None, raise_send_errors=False, transport=None, context = {'sys.argv': sys.argv[:]} self.extra = context self.tags = o.get('tags') or {} + self.environment = o.get('environment') or None self.release = o.get('release') or os.environ.get('HEROKU_SLUG_COMMIT') self.module_cache = ModuleProxyCache() @@ -368,6 +369,9 @@ def build_msg(self, event_type, data=None, date=None, if self.release is not None: data['release'] = self.release + if self.environment is not None: + data['environment'] = self.environment + data['tags'] = merge_dicts(self.tags, data['tags'], tags) data['extra'] = merge_dicts(self.extra, data['extra'], extra) From f9060ccf67d8b19a7d06946895b1039f65e67b53 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 10 Dec 2015 12:07:02 -0800 Subject: [PATCH 156/692] initial changes in 5.9.0 --- CHANGES | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGES b/CHANGES index eeb97baf4..fdb702da4 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,10 @@ +Version 5.9.0 +------------- + +* The threaded worker will now correctly handle forking. +* The 'environment' parameter is now supported (requires a Sentry 8.0 server ). +* 'tags' can now be specified as part of a LoggingHandler's constructor. + Version 5.8.0 ------------- From f39b68f3f754acfc06ac8ebf41cb23eb26b4b550 Mon Sep 17 00:00:00 2001 From: Kieran Brownlees Date: Thu, 17 Dec 2015 10:54:57 +1300 Subject: [PATCH 157/692] Catch the RuntimeError raised if contentypes is not available In Django 1.9 it became an error to import a model of an app before it is loaded (https://docs.djangoproject.com/en/1.9/releases/1.9/#features-removed-in-1-9) This means the import of the user model may fail if the contenttype or user contrib applications are not in INSTALLED_APPS. Fixes #705 --- raven/contrib/django/client.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/raven/contrib/django/client.py b/raven/contrib/django/client.py index 38370251e..3e9025843 100644 --- a/raven/contrib/django/client.py +++ b/raven/contrib/django/client.py @@ -52,15 +52,19 @@ def get_user_info(self, user): return user_info def get_data_from_request(self, request): + result = {} + try: from django.contrib.auth.models import AbstractBaseUser as BaseUser except ImportError: from django.contrib.auth.models import User as BaseUser # NOQA - - result = {} - - if hasattr(request, 'user') and isinstance(request.user, BaseUser): - result['user'] = self.get_user_info(request.user) + except RuntimeError: + # If the contenttype / user applications are not installed trying to + # import the user models will fail for django >= 1.9. + pass + else: + if hasattr(request, 'user') and isinstance(request.user, BaseUser): + result['user'] = self.get_user_info(request.user) try: uri = request.build_absolute_uri() From 47e8e791f16735674415330a5d01ff84888b7d6f Mon Sep 17 00:00:00 2001 From: David Cramer Date: Wed, 16 Dec 2015 16:53:05 -0800 Subject: [PATCH 158/692] 5.10.0.dev0 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index af132e8ff..2c692c53f 100755 --- a/setup.py +++ b/setup.py @@ -96,7 +96,7 @@ def run_tests(self): setup( name='raven', - version='5.9.0.dev0', + version='5.10.0.dev0', author='David Cramer', author_email='dcramer@gmail.com', url='https://github.com/getsentry/raven-python', From bd2d1ec1384265b99fafdff5ed6d8d72c8feb5f7 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Wed, 16 Dec 2015 16:54:33 -0800 Subject: [PATCH 159/692] Changes for 5.9.1 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index fdb702da4..2bf727280 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 5.9.1 +------------- + +* Support for isolated apps in Django 1.9. + Version 5.9.0 ------------- From 18579dd596fdff42ce385ccfbb7eb4832fc9f63c Mon Sep 17 00:00:00 2001 From: Kieran Brownlees Date: Fri, 18 Dec 2015 09:48:54 +1300 Subject: [PATCH 160/692] Store user information if AbstractBaseUser doesn't exist The previous fix had the side effect of stopping user information being stored when AbstractBaseUser doesn't exist since the else clause would never be executed. Actually fixes #705 --- raven/contrib/django/client.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/raven/contrib/django/client.py b/raven/contrib/django/client.py index 3e9025843..ffb03e571 100644 --- a/raven/contrib/django/client.py +++ b/raven/contrib/django/client.py @@ -52,8 +52,6 @@ def get_user_info(self, user): return user_info def get_data_from_request(self, request): - result = {} - try: from django.contrib.auth.models import AbstractBaseUser as BaseUser except ImportError: @@ -61,10 +59,12 @@ def get_data_from_request(self, request): except RuntimeError: # If the contenttype / user applications are not installed trying to # import the user models will fail for django >= 1.9. - pass - else: - if hasattr(request, 'user') and isinstance(request.user, BaseUser): - result['user'] = self.get_user_info(request.user) + BaseUser = None + + result = {} + + if BaseUser and hasattr(request, 'user') and isinstance(request.user, BaseUser): + result['user'] = self.get_user_info(request.user) try: uri = request.build_absolute_uri() From ae5315b25797b7d306f6536962aa45c2bff60d37 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 17 Dec 2015 12:58:22 -0800 Subject: [PATCH 161/692] Changes for 5.9.2 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 2bf727280..e5e4d6c36 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 5.9.2 +------------- + +* Correct behavior introduced for Django 1.9. + Version 5.9.1 ------------- From e5c4ff5251e8a2892a54b2b5d185f03372f2f0e6 Mon Sep 17 00:00:00 2001 From: Stefano Brentegani Date: Sun, 27 Dec 2015 07:27:46 +0100 Subject: [PATCH 162/692] Fix example in django.rst Missing comma in LOGGING example --- 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 3f39c39af..201a827f7 100644 --- a/docs/integrations/django.rst +++ b/docs/integrations/django.rst @@ -97,7 +97,7 @@ following config can be used:: 'handlers': { 'sentry': { 'level': 'ERROR', - 'class': 'raven.contrib.django.raven_compat.handlers.SentryHandler' + 'class': 'raven.contrib.django.raven_compat.handlers.SentryHandler', 'tags': {'custom-tag': 'x'}, }, 'console': { From 2f6fc1410ee5532698c49c37fe9a721339b097fd Mon Sep 17 00:00:00 2001 From: Matt Robenolt Date: Thu, 31 Dec 2015 15:28:13 -0500 Subject: [PATCH 163/692] django: ignore a missing settings.SITE_ID SITE_ID isn't required to exist or to be set, so just ignore it. Fixes GH-712 --- raven/contrib/django/client.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/raven/contrib/django/client.py b/raven/contrib/django/client.py index ffb03e571..51cbda262 100644 --- a/raven/contrib/django/client.py +++ b/raven/contrib/django/client.py @@ -128,7 +128,11 @@ def build_msg(self, *args, **kwargs): data['tags'].setdefault('site', site_name) except Exception: # Database error? Fallback to the id - data['tags'].setdefault('site', settings.SITE_ID) + try: + data['tags'].setdefault('site', settings.SITE_ID) + except AttributeError: + # SITE_ID wasn't set, so just ignore + pass return data From 775e7e23403a18b6a741a82379e7c436a44d8bec Mon Sep 17 00:00:00 2001 From: David Cramer Date: Fri, 8 Jan 2016 14:19:39 -0800 Subject: [PATCH 164/692] Correct memoize behavior --- raven/utils/__init__.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/raven/utils/__init__.py b/raven/utils/__init__.py index 1714d526d..4477ba36d 100644 --- a/raven/utils/__init__.py +++ b/raven/utils/__init__.py @@ -162,6 +162,5 @@ def __get__(self, obj, type=None): return self d, n = vars(obj), self.__name__ if n not in d: - value = self.func(obj) - d[n] = value - return value + d[n] = self.func(obj) + return d[n] From db96b5b312cbc9820d1bb8c5111c4833fe2f4dc4 Mon Sep 17 00:00:00 2001 From: Jonas Obrist Date: Tue, 12 Jan 2016 15:27:10 +0900 Subject: [PATCH 165/692] Do not capture tornado.web.HTTPErrors if their status code is not 5xx --- raven/contrib/tornado/__init__.py | 4 ++++ tests/contrib/tornado/tests.py | 32 +++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/raven/contrib/tornado/__init__.py b/raven/contrib/tornado/__init__.py index 2966d18a8..a617b012f 100644 --- a/raven/contrib/tornado/__init__.py +++ b/raven/contrib/tornado/__init__.py @@ -11,6 +11,7 @@ from tornado import ioloop from tornado.httpclient import AsyncHTTPClient, HTTPError +from tornado import web from raven.base import Client @@ -227,6 +228,9 @@ def log_exception(self, typ, value, tb): log_exception() is added in Tornado v3.1. """ rv = super(SentryMixin, self).log_exception(typ, value, tb) + # Do not capture web.HTTPErrors outside the 500 range. + if isinstance(value, web.HTTPError) and (value.status_code < 500 or value.status_code > 599): + return rv self.captureException(exc_info=(typ, value, tb)) return rv diff --git a/tests/contrib/tornado/tests.py b/tests/contrib/tornado/tests.py index 42f374467..a2bd1b713 100644 --- a/tests/contrib/tornado/tests.py +++ b/tests/contrib/tornado/tests.py @@ -62,6 +62,11 @@ def get_current_user(self): } +class HTTPErrorHandler(SentryMixin, web.RequestHandler): + def get(self): + raise web.HTTPError(int(self.get_query_argument('code', 500)), "Oops") + + class TornadoAsyncClientTestCase(testing.AsyncHTTPTestCase): def get_app(self): app = web.Application([ @@ -71,6 +76,7 @@ def get_app(self): web.url(r'/send-error-async', SendErrorAsyncHandler), web.url(r'/an-error-with-custom-non-dict-data', AnErrorWithCustomNonDictData), web.url(r'/an-error-with-custom-dict-data', AnErrorWithCustomDictData), + web.url(r'/http-error', HTTPErrorHandler) ]) app.sentry_client = AsyncSentryClient( 'http://public_key:secret_key@host:9000/project' @@ -237,3 +243,29 @@ def test_non_successful_responses_marks_client_as_failed(self): yield gen.sleep(0.01) # we need to run after the async send assert mock_failed.called + + @patch('raven.contrib.tornado.AsyncSentryClient.send') + def test_http_error_500(self, send): + response = self.fetch('/http-error?code=500') + self.assertEqual(response.code, 500) + self.assertEqual(send.call_count, 1) + args, kwargs = send.call_args + + assert 'user' in kwargs + assert 'request' in kwargs + assert 'exception' in kwargs + + http_data = kwargs['request'] + self.assertEqual(http_data['cookies'], None) + self.assertEqual(http_data['url'], response.effective_url) + self.assertEqual(http_data['query_string'], 'code=500') + self.assertEqual(http_data['method'], 'GET') + + user_data = kwargs['user'] + self.assertEqual(user_data['is_authenticated'], False) + + @patch('raven.contrib.tornado.AsyncSentryClient.send') + def test_http_error_400(self, send): + response = self.fetch('/http-error?code=400') + self.assertEqual(response.code, 400) + self.assertEqual(send.call_count, 0) From f2eedb742cad803cdf0f2f2ed19bbb6fb4a1fd25 Mon Sep 17 00:00:00 2001 From: Matt Robenolt Date: Mon, 11 Jan 2016 22:39:52 -0800 Subject: [PATCH 166/692] Alias import to prevent attribute lookup --- raven/contrib/tornado/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/raven/contrib/tornado/__init__.py b/raven/contrib/tornado/__init__.py index a617b012f..cb285c381 100644 --- a/raven/contrib/tornado/__init__.py +++ b/raven/contrib/tornado/__init__.py @@ -11,7 +11,7 @@ from tornado import ioloop from tornado.httpclient import AsyncHTTPClient, HTTPError -from tornado import web +from tornado.web import HTTPError as WebHTTPError from raven.base import Client @@ -228,8 +228,8 @@ def log_exception(self, typ, value, tb): log_exception() is added in Tornado v3.1. """ rv = super(SentryMixin, self).log_exception(typ, value, tb) - # Do not capture web.HTTPErrors outside the 500 range. - if isinstance(value, web.HTTPError) and (value.status_code < 500 or value.status_code > 599): + # Do not capture tornado.web.HTTPErrors outside the 500 range. + if isinstance(value, WebHTTPError) and (value.status_code < 500 or value.status_code > 599): return rv self.captureException(exc_info=(typ, value, tb)) return rv From 97e93ea7baf4aadecbe3f14ba1b87f3b0917c2af Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 12 Jan 2016 21:40:51 +0100 Subject: [PATCH 167/692] Strip six module and rename to raven._compat We're seeing some odd issues with our six module interacting badly with the six module from other libraries for some users. The suspicion is that our six module in some cases gets unvendored. We thus rename it to `raven._compat` (which is also what some other Python modules do). We still use six normally for the tests. --- hooks/pre-commit.flake8 | 8 +- raven/_compat.py | 177 ++++++++++++ raven/base.py | 23 +- raven/conf/remote.py | 6 +- raven/context.py | 6 +- raven/contrib/django/models.py | 12 +- raven/contrib/django/serializers.py | 11 +- raven/contrib/django/views.py | 4 +- raven/contrib/flask.py | 16 +- raven/exceptions.py | 4 +- raven/handlers/logbook.py | 4 +- raven/handlers/logging.py | 28 +- raven/processors.py | 8 +- raven/transport/http.py | 6 +- raven/utils/__init__.py | 15 +- raven/utils/encoding.py | 31 ++- raven/utils/imports.py | 4 +- raven/utils/serializer/base.py | 41 +-- raven/utils/serializer/manager.py | 12 +- raven/utils/six.py | 406 ---------------------------- raven/utils/stacks.py | 16 +- raven/utils/wsgi.py | 4 +- raven/versioning.py | 19 +- setup.py | 1 + tests/base/tests.py | 2 +- tests/contrib/django/tests.py | 4 +- tests/contrib/tornado/tests.py | 2 +- tests/handlers/logbook/tests.py | 2 +- tests/handlers/logging/tests.py | 2 +- tests/utils/encoding/tests.py | 3 +- tests/utils/stacks/tests.py | 3 +- tests/utils/test_imports.py | 3 +- tests/versioning/tests.py | 2 +- 33 files changed, 350 insertions(+), 535 deletions(-) create mode 100644 raven/_compat.py delete mode 100644 raven/utils/six.py diff --git a/hooks/pre-commit.flake8 b/hooks/pre-commit.flake8 index 769326803..1850e9083 100755 --- a/hooks/pre-commit.flake8 +++ b/hooks/pre-commit.flake8 @@ -20,13 +20,17 @@ def main(): from flake8.main import DEFAULT_CONFIG from flake8.engine import get_style_guide from flake8.hooks import run - from raven.utils import six gitcmd = "git diff-index --cached --name-only HEAD" _, files_modified, _ = run(gitcmd) - files_modified = [six.text_type(x) for x in files_modified] + try: + text_type = unicode + except NameError: + text_type = str + + files_modified = [text_type(x) for x in files_modified] # remove non-py files and files which no longer exist files_modified = filter( diff --git a/raven/_compat.py b/raven/_compat.py new file mode 100644 index 000000000..bd5c5e79d --- /dev/null +++ b/raven/_compat.py @@ -0,0 +1,177 @@ +"""Utilities for writing code that runs on Python 2 and 3""" +# flake8: noqa + +# Copyright (c) 2010-2013 Benjamin Peterson +# +# 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. +from __future__ import absolute_import + +import operator +import sys +import types + +__author__ = "Benjamin Peterson " +__version__ = "1.3.0" + + +PY2 = sys.version_info[0] == 2 + +if not PY2: + string_types = str, + integer_types = int, + class_types = type, + text_type = str + binary_type = bytes + + MAXSIZE = sys.maxsize +else: + string_types = basestring, + integer_types = (int, long) + class_types = (type, types.ClassType) + text_type = unicode + binary_type = str + + if sys.platform.startswith("java"): + # Jython always uses 32 bits. + MAXSIZE = int((1 << 31) - 1) + else: + # It's possible to have sizeof(long) != sizeof(Py_ssize_t). + class X(object): + def __len__(self): + return 1 << 31 + try: + len(X()) + except OverflowError: + # 32-bit + MAXSIZE = int((1 << 31) - 1) + else: + # 64-bit + MAXSIZE = int((1 << 63) - 1) + del X + + +def _import_module(name): + """Import module, returning the module after the last dot.""" + __import__(name) + return sys.modules[name] + + +if not PY2: + _iterkeys = "keys" + _itervalues = "values" + _iteritems = "items" + _iterlists = "lists" +else: + _iterkeys = "iterkeys" + _itervalues = "itervalues" + _iteritems = "iteritems" + _iterlists = "iterlists" + + +try: + advance_iterator = next +except NameError: + def advance_iterator(it): + return it.next() +next = advance_iterator + + +try: + callable = callable +except NameError: + def callable(obj): + return any("__call__" in klass.__dict__ for klass in type(obj).__mro__) + + +def iterkeys(d, **kw): + """Return an iterator over the keys of a dictionary.""" + return iter(getattr(d, _iterkeys)(**kw)) + + +def itervalues(d, **kw): + """Return an iterator over the values of a dictionary.""" + return iter(getattr(d, _itervalues)(**kw)) + + +def iteritems(d, **kw): + """Return an iterator over the (key, value) pairs of a dictionary.""" + return iter(getattr(d, _iteritems)(**kw)) + + +def iterlists(d, **kw): + """Return an iterator over the (key, [values]) pairs of a dictionary.""" + return iter(getattr(d, _iterlists)(**kw)) + + +if not PY2: + def b(s): + return s.encode("latin-1") + + def u(s): + return s + if sys.version_info[1] <= 1: + def int2byte(i): + return bytes((i,)) + else: + # This is about 2x faster than the implementation above on 3.2+ + int2byte = operator.methodcaller("to_bytes", 1, "big") + import io + StringIO = io.StringIO + BytesIO = io.BytesIO +else: + def b(s): # NOQA + return s + + def u(s): # NOQA + return unicode(s, "unicode_escape") + int2byte = chr + import StringIO + StringIO = BytesIO = StringIO.StringIO + + +if not PY2: + import builtins + exec_ = getattr(builtins, "exec") + + def reraise(tp, value, tb=None): + if value.__traceback__ is not tb: + raise value.with_traceback(tb) + raise value + + del builtins + +else: + def exec_(_code_, _globs_=None, _locs_=None): + """Execute code in a namespace.""" + if _globs_ is None: + frame = sys._getframe(1) + _globs_ = frame.f_globals + if _locs_ is None: + _locs_ = frame.f_locals + del frame + elif _locs_ is None: + _locs_ = _globs_ + exec("""exec _code_ in _globs_, _locs_""") + + exec_("""def reraise(tp, value, tb=None): + raise tp, value, tb +""") + +def with_metaclass(meta, base=object): + """Create a base class with a metaclass.""" + return meta("NewBase", (base,), {}) diff --git a/raven/base.py b/raven/base.py index 5065bca1e..21d7a178f 100644 --- a/raven/base.py +++ b/raven/base.py @@ -30,7 +30,8 @@ from raven.conf.remote import RemoteConfig from raven.context import Context from raven.exceptions import APIError, RateLimited -from raven.utils import six, json, get_versions, get_auth_header, merge_dicts +from raven.utils import json, get_versions, get_auth_header, merge_dicts +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, get_culprit @@ -145,7 +146,7 @@ def __init__(self, dsn=None, raise_send_errors=False, transport=None, self.include_paths = set(o.get('include_paths') or []) self.exclude_paths = set(o.get('exclude_paths') or []) - self.name = six.text_type(o.get('name') or o.get('machine') or defaults.NAME) + self.name = text_type(o.get('name') or o.get('machine') or defaults.NAME) self.auto_log_stacks = bool( o.get('auto_log_stacks') or defaults.AUTO_LOG_STACKS) self.capture_locals = bool( @@ -252,7 +253,8 @@ def get_ident(self, result): >>> result = client.capture(**kwargs) >>> ident = client.get_ident(result) """ - warnings.warn('Client.get_ident is deprecated. The event ID is now returned as the result of capture.', + warnings.warn('Client.get_ident is deprecated. The event ID is now ' + 'returned as the result of capture.', DeprecationWarning) return result @@ -306,7 +308,7 @@ def build_msg(self, event_type, data=None, date=None, if data.get('culprit'): culprit = data['culprit'] - for k, v in six.iteritems(result): + for k, v in iteritems(result): if k not in data: data[k] = v @@ -394,11 +396,11 @@ def build_msg(self, event_type, data=None, date=None, data['message'] = kwargs.get('message', handler.to_string(data)) # tags should only be key=>u'value' - for key, value in six.iteritems(data['tags']): + for key, value in iteritems(data['tags']): data['tags'][key] = to_unicode(value) # extra data can be any arbitrary value - for k, v in six.iteritems(data['extra']): + for k, v in iteritems(data['extra']): data['extra'][k] = self.transform(v) # It's important date is added **after** we serialize @@ -556,7 +558,8 @@ def _failed_send(self, exc, url, data): if isinstance(exc, RateLimited): retry_after = exc.retry_after self.error_logger.error( - 'Sentry responded with an API error: %s(%s)', type(exc).__name__, exc.message) + 'Sentry responded with an API error: %s(%s)', + type(exc).__name__, exc.message) else: self.error_logger.error( 'Sentry responded with an error: %s (url: %s)\n%s', @@ -577,10 +580,10 @@ def _log_failed_submission(self, data): if 'exception' in data and 'stacktrace' in data['exception']['values'][0]: # try to reconstruct a reasonable version of the exception for frame in data['exception']['values'][0]['stacktrace']['frames']: - output.append(' File "%(filename)s", line %(lineno)s, in %(function)s' % { - 'filename': frame['filename'], + output.append(' File "%(fn)s", line %(lineno)s, in %(func)s' % { + 'fn': frame['filename'], 'lineno': frame['lineno'], - 'function': frame['function'], + 'func': frame['function'], }) self.uncaught_logger.error(output) diff --git a/raven/conf/remote.py b/raven/conf/remote.py index e720bfd9c..f0918315e 100644 --- a/raven/conf/remote.py +++ b/raven/conf/remote.py @@ -2,9 +2,9 @@ import warnings +from raven._compat import PY2, text_type from raven.exceptions import InvalidDsn from raven.transport.threaded import ThreadedHTTPTransport -from raven.utils import six from raven.utils.encoding import to_string from raven.utils.urlparse import parse_qsl, urlparse @@ -32,7 +32,7 @@ def __init__(self, base_url=None, project=None, public_key=None, self._transport_cls = transport or DEFAULT_TRANSPORT def __unicode__(self): - return six.text_type(self.base_url) + return text_type(self.base_url) def is_active(self): return all([self.base_url, self.project, self.public_key, self.secret_key]) @@ -58,7 +58,7 @@ def get_public_dsn(self): def from_string(cls, value, transport=None, transport_registry=None): # in Python 2.x sending the DSN as a unicode value will eventually # cause issues in httplib - if not six.PY3: + if PY2: value = to_string(value) url = urlparse(value) diff --git a/raven/context.py b/raven/context.py index 5d939392d..e941360cc 100644 --- a/raven/context.py +++ b/raven/context.py @@ -10,7 +10,7 @@ from collections import Mapping, Iterable from threading import local -from raven.utils import six +from raven._compat import iteritems class Context(local, Mapping, Iterable): @@ -42,10 +42,10 @@ def __repr__(self): def merge(self, data): d = self.data - for key, value in six.iteritems(data): + for key, value in iteritems(data): if key in ('tags', 'extra'): d.setdefault(key, {}) - for t_key, t_value in six.iteritems(value): + for t_key, t_value in iteritems(value): d[key][t_key] = t_value else: d[key] = value diff --git a/raven/contrib/django/models.py b/raven/contrib/django/models.py index 5172fcf1b..62f2de8f3 100644 --- a/raven/contrib/django/models.py +++ b/raven/contrib/django/models.py @@ -19,7 +19,7 @@ from django.conf import settings from hashlib import md5 -from raven.utils import six +from raven._compat import PY2, binary_type, text_type from raven.utils.imports import import_string from raven.contrib.django.management import patch_cli_runner @@ -61,7 +61,7 @@ class ProxyClient(object): __ne__ = lambda x, o: get_client() != o __gt__ = lambda x, o: get_client() > o __ge__ = lambda x, o: get_client() >= o - if not six.PY3: + if PY2: __cmp__ = lambda x, o: cmp(get_client(), o) # NOQA __hash__ = lambda x: hash(get_client()) # attributes are currently not callable @@ -92,11 +92,11 @@ class ProxyClient(object): __invert__ = lambda x: ~(get_client()) __complex__ = lambda x: complex(get_client()) __int__ = lambda x: int(get_client()) - if not six.PY3: + if PY2: __long__ = lambda x: long(get_client()) # NOQA __float__ = lambda x: float(get_client()) - __str__ = lambda x: six.binary_type(get_client()) - __unicode__ = lambda x: six.text_type(get_client()) + __str__ = lambda x: binary_type(get_client()) + __unicode__ = lambda x: text_type(get_client()) __oct__ = lambda x: oct(get_client()) __hex__ = lambda x: hex(get_client()) __index__ = lambda x: get_client().__index__() @@ -139,7 +139,7 @@ def get_client(client=None, reset=False): options.setdefault('release', ga('RELEASE')) transport = ga('TRANSPORT') or options.get('transport') - if isinstance(transport, six.string_types): + if isinstance(transport, string_types): transport = import_string(transport) options['transport'] = transport diff --git a/raven/contrib/django/serializers.py b/raven/contrib/django/serializers.py index f01107912..50c02362d 100644 --- a/raven/contrib/django/serializers.py +++ b/raven/contrib/django/serializers.py @@ -12,7 +12,7 @@ from django.http import HttpRequest from django.utils.functional import Promise from raven.utils.serializer import Serializer, register -from raven.utils import six +from raven._compat import text_type __all__ = ('PromiseSerializer',) @@ -34,12 +34,15 @@ def can(self, value): def serialize(self, value, **kwargs): # EPIC HACK - # handles lazy model instances (which are proxy values that don't easily give you the actual function) + # handles lazy model instances (which are proxy values that don't + # easily give you the actual function) pre = value.__class__.__name__[1:] if hasattr(value, '%s__func' % pre): - value = getattr(value, '%s__func' % pre)(*getattr(value, '%s__args' % pre), **getattr(value, '%s__kw' % pre)) + value = getattr(value, '%s__func' % pre)( + *getattr(value, '%s__args' % pre), + **getattr(value, '%s__kw' % pre)) else: - return self.recurse(six.text_type(value)) + return self.recurse(text_type(value)) return self.recurse(value, **kwargs) register(PromiseSerializer) diff --git a/raven/contrib/django/views.py b/raven/contrib/django/views.py index bba95e22c..aabbd71ea 100644 --- a/raven/contrib/django/views.py +++ b/raven/contrib/django/views.py @@ -15,9 +15,9 @@ from django.views.decorators.csrf import csrf_exempt from django.views.decorators.http import require_http_methods +from raven._compat import string_types from raven.contrib.django.models import client from raven.utils import json -from raven.utils import six def is_valid_origin(origin): @@ -32,7 +32,7 @@ def is_valid_origin(origin): origin = origin.lower() for value in settings.SENTRY_ALLOW_ORIGIN: - if isinstance(value, six.string_types): + if isinstance(value, string_types): if value.lower() == origin: return True else: diff --git a/raven/contrib/flask.py b/raven/contrib/flask.py index 0ab489899..d17817791 100644 --- a/raven/contrib/flask.py +++ b/raven/contrib/flask.py @@ -23,11 +23,11 @@ from flask.signals import got_request_exception, request_finished from werkzeug.exceptions import ClientDisconnected +from raven._compat import string_types from raven.conf import setup_logging from raven.base import Client from raven.middleware import Sentry as SentryMiddleware from raven.handlers.logging import SentryHandler -from raven.utils import six from raven.utils.compat import _urlparse from raven.utils.encoding import to_unicode from raven.utils.imports import import_string @@ -38,13 +38,14 @@ def make_client(client_cls, app, dsn=None): # TODO(dcramer): django and Flask share very similar concepts here, and # should be refactored transport = app.config.get('SENTRY_TRANSPORT') - if isinstance(transport, six.string_types): + if isinstance(transport, string_types): transport = import_string(transport) return client_cls( dsn=dsn or app.config.get('SENTRY_DSN') or os.environ.get('SENTRY_DSN'), transport=transport, - include_paths=set(app.config.get('SENTRY_INCLUDE_PATHS', [])) | set([app.import_name]), + include_paths=set(app.config.get( + 'SENTRY_INCLUDE_PATHS', [])) | set([app.import_name]), exclude_paths=app.config.get('SENTRY_EXCLUDE_PATHS'), name=app.config.get('SENTRY_NAME'), site=app.config.get('SENTRY_SITE_NAME'), @@ -131,17 +132,20 @@ def handle_exception(self, *args, **kwargs): if not self.client: return - ignored_exc_type_list = current_app.config.get('RAVEN_IGNORE_EXCEPTIONS', []) + ignored_exc_type_list = current_app.config.get( + 'RAVEN_IGNORE_EXCEPTIONS', []) exc = sys.exc_info()[1] - if any((isinstance(exc, ignored_exc_type) for ignored_exc_type in ignored_exc_type_list)): + if any((isinstance(exc, ignored_exc_type) + for ignored_exc_type in ignored_exc_type_list)): return self.captureException(exc_info=kwargs.get('exc_info')) def get_user_info(self, request): """ - Requires Flask-Login (https://pypi.python.org/pypi/Flask-Login/) to be installed + Requires Flask-Login (https://pypi.python.org/pypi/Flask-Login/) + to be installed and setup """ if not has_flask_login: diff --git a/raven/exceptions.py b/raven/exceptions.py index 4130839bb..ddf5f601c 100644 --- a/raven/exceptions.py +++ b/raven/exceptions.py @@ -1,6 +1,6 @@ from __future__ import absolute_import -from raven.utils import six +from raven._compat import text_type class APIError(Exception): @@ -9,7 +9,7 @@ def __init__(self, message, code=0): self.message = message def __unicode__(self): - return six.text_type("%s: %s" % (self.message, self.code)) + return text_type("%s: %s" % (self.message, self.code)) class RateLimited(APIError): diff --git a/raven/handlers/logbook.py b/raven/handlers/logbook.py index cae177c15..1edd2de14 100644 --- a/raven/handlers/logbook.py +++ b/raven/handlers/logbook.py @@ -13,16 +13,16 @@ import sys import traceback +from raven._compat import string_types from raven.base import Client from raven.utils.encoding import to_string -from raven.utils import six class SentryHandler(logbook.Handler): def __init__(self, *args, **kwargs): if len(args) == 1: arg = args[0] - if isinstance(arg, six.string_types): + if isinstance(arg, string_types): self.client = kwargs.pop('client_cls', Client)(dsn=arg, **kwargs) elif isinstance(arg, Client): self.client = arg diff --git a/raven/handlers/logging.py b/raven/handlers/logging.py index ad2e912b4..cc82550c4 100644 --- a/raven/handlers/logging.py +++ b/raven/handlers/logging.py @@ -14,8 +14,8 @@ import sys import traceback +from raven._compat import string_types, iteritems, text_type from raven.base import Client -from raven.utils import six from raven.utils.encoding import to_string from raven.utils.stacks import iter_stack_frames, label_from_frame @@ -31,15 +31,14 @@ def __init__(self, *args, **kwargs): client = kwargs.get('client_cls', Client) if len(args) == 1: arg = args[0] - if isinstance(arg, six.string_types): + if isinstance(arg, string_types): self.client = client(dsn=arg, **kwargs) elif isinstance(arg, Client): self.client = arg else: - raise ValueError('The first argument to %s must be either a Client instance or a DSN, got %r instead.' % ( - self.__class__.__name__, - arg, - )) + raise ValueError('The first argument to %s must be either a ' + 'Client instance or a DSN, got %r instead.' % + (self.__class__.__name__, arg,)) elif 'client' in kwargs: self.client = kwargs['client'] else: @@ -68,7 +67,8 @@ def emit(self, record): except Exception: if self.client.raise_send_errors: raise - print("Top level Sentry exception caught - failed creating log record", file=sys.stderr) + print("Top level Sentry exception caught - failed " + "creating log record", file=sys.stderr) print(to_string(record.msg), file=sys.stderr) print(to_string(traceback.format_exc()), file=sys.stderr) @@ -113,7 +113,7 @@ def _emit(self, record, **kwargs): else: extra = {} - for k, v in six.iteritems(vars(record)): + for k, v in iteritems(vars(record)): if k in RESERVED: continue if k.startswith('_'): @@ -136,13 +136,13 @@ def _emit(self, record, **kwargs): 'params': record.args, } try: - handler_kwargs['message'] = six.text_type(record.msg) + handler_kwargs['message'] = text_type(record.msg) except UnicodeDecodeError: # Handle binary strings where it should be unicode... handler_kwargs['message'] = repr(record.msg)[1:-1] try: - handler_kwargs['formatted'] = six.text_type(record.message) + handler_kwargs['formatted'] = text_type(record.message) except UnicodeDecodeError: # Handle binary strings where it should be unicode... handler_kwargs['formatted'] = repr(record.message)[1:-1] @@ -160,8 +160,12 @@ def _emit(self, record, **kwargs): handler_kwargs = {'exc_info': record.exc_info} # HACK: discover a culprit when we normally couldn't - elif not (data.get('stacktrace') or data.get('culprit')) and (record.name or record.funcName): - culprit = label_from_frame({'module': record.name, 'function': record.funcName}) + elif not (data.get('stacktrace') or data.get('culprit')) \ + and (record.name or record.funcName): + culprit = label_from_frame({ + 'module': record.name, + 'function': record.funcName + }) if culprit: data['culprit'] = culprit diff --git a/raven/processors.py b/raven/processors.py index bf4e69d61..3f1a74b68 100644 --- a/raven/processors.py +++ b/raven/processors.py @@ -9,8 +9,8 @@ import re +from raven._compat import string_types, text_type from raven.utils import varmap -from raven.utils import six class Processor(object): @@ -88,13 +88,13 @@ def sanitize(self, key, value): if value is None: return - if isinstance(value, six.string_types) and self.VALUES_RE.match(value): + if isinstance(value, string_types) and self.VALUES_RE.match(value): return self.MASK if not key: # key can be a NoneType return value - key = six.text_type(key).lower() + key = text_type(key).lower() for field in self.FIELDS: if field in key: # store mask as a fixed length for security @@ -112,7 +112,7 @@ def filter_http(self, data): if n not in data: continue - if isinstance(data[n], six.string_types) and '=' in data[n]: + if isinstance(data[n], string_types) and '=' in data[n]: # at this point we've assumed it's a standard HTTP query # or cookie if n == 'cookies': diff --git a/raven/transport/http.py b/raven/transport/http.py index a2b968bac..fd8405695 100644 --- a/raven/transport/http.py +++ b/raven/transport/http.py @@ -7,10 +7,10 @@ """ from __future__ import absolute_import +from raven._compat import string_types from raven.conf import defaults from raven.exceptions import APIError, RateLimited from raven.transport.base import Transport -from raven.utils import six from raven.utils.http import urlopen from raven.utils.compat import urllib2 @@ -23,9 +23,9 @@ def __init__(self, parsed_url, timeout=defaults.TIMEOUT, verify_ssl=True, self._parsed_url = parsed_url self._url = parsed_url.geturl().rsplit('+', 1)[-1] - if isinstance(timeout, six.string_types): + if isinstance(timeout, string_types): timeout = int(timeout) - if isinstance(verify_ssl, six.string_types): + if isinstance(verify_ssl, string_types): verify_ssl = bool(int(verify_ssl)) self.timeout = timeout diff --git a/raven/utils/__init__.py b/raven/utils/__init__.py index 4477ba36d..b4916932b 100644 --- a/raven/utils/__init__.py +++ b/raven/utils/__init__.py @@ -7,7 +7,7 @@ """ from __future__ import absolute_import -from raven.utils import six +from raven._compat import iteritems, string_types import logging try: import pkg_resources @@ -24,7 +24,7 @@ def merge_dicts(*dicts): if not d: continue - for k, v in six.iteritems(d): + for k, v in iteritems(d): out[k] = v return out @@ -42,7 +42,8 @@ def varmap(func, var, context=None, name=None): return func(name, '<...>') context[objid] = 1 if isinstance(var, dict): - ret = dict((k, varmap(func, v, context, k)) for k, v in six.iteritems(var)) + ret = dict((k, varmap(func, v, context, k)) + for k, v in iteritems(var)) elif isinstance(var, (list, tuple)): ret = [varmap(func, f, context, name) for f in var] else: @@ -79,7 +80,7 @@ def get_version_from_app(module_name, app): if callable(version): version = version() - if not isinstance(version, (six.string_types, list, tuple)): + if not isinstance(version, (string_types, list, tuple)): version = None if version is None: @@ -98,7 +99,8 @@ def get_versions(module_list=None): ext_module_list = set() for m in module_list: parts = m.split('.') - ext_module_list.update('.'.join(parts[:idx]) for idx in range(1, len(parts) + 1)) + ext_module_list.update('.'.join(parts[:idx]) + for idx in range(1, len(parts) + 1)) versions = {} for module_name in ext_module_list: @@ -128,7 +130,8 @@ def get_versions(module_list=None): return versions -def get_auth_header(protocol, timestamp, client, api_key, api_secret=None, **kwargs): +def get_auth_header(protocol, timestamp, client, api_key, + api_secret=None, **kwargs): header = [ ('sentry_timestamp', timestamp), ('sentry_client', client), diff --git a/raven/utils/encoding.py b/raven/utils/encoding.py index 3bb98291b..cf8bca5d4 100644 --- a/raven/utils/encoding.py +++ b/raven/utils/encoding.py @@ -8,7 +8,9 @@ from __future__ import absolute_import, unicode_literals import warnings -from raven.utils import six + +from raven._compat import integer_types, text_type, binary_type, \ + string_types, PY2 def is_protected_type(obj): @@ -19,7 +21,7 @@ def is_protected_type(obj): """ import Decimal import datetime - return isinstance(obj, six.integer_types + (type(None), float, Decimal, + return isinstance(obj, integer_types + (type(None), float, Decimal, datetime.datetime, datetime.date, datetime.time)) @@ -31,25 +33,25 @@ def force_text(s, encoding='utf-8', strings_only=False, errors='strict'): If strings_only is True, don't convert (some) non-string-like objects. """ # Handle the common case first, saves 30-40% when s is an instance of - # six.text_type. This function gets called often in that setting. - if isinstance(s, six.text_type): + # text_type. This function gets called often in that setting. + if isinstance(s, text_type): return s if strings_only and is_protected_type(s): return s try: - if not isinstance(s, six.string_types): + if not isinstance(s, string_types): if hasattr(s, '__unicode__'): s = s.__unicode__() else: - if six.PY3: + if not PY2: if isinstance(s, bytes): - s = six.text_type(s, encoding, errors) + s = text_type(s, encoding, errors) else: - s = six.text_type(s) + s = text_type(s) else: - s = six.text_type(bytes(s), encoding, errors) + s = text_type(bytes(s), encoding, errors) else: - # Note: We use .decode() here, instead of six.text_type(s, encoding, + # Note: We use .decode() here, instead of text_type(s, encoding, # errors), so that if s is a SafeBytes, it ends up being a # SafeText at the end. s = s.decode(encoding, errors) @@ -69,19 +71,20 @@ def force_text(s, encoding='utf-8', strings_only=False, errors='strict'): def transform(value): from raven.utils.serializer import transform - warnings.warn('You should switch to raven.utils.serializer.transform', DeprecationWarning) + warnings.warn('You should switch to raven.utils.serializer.' + 'transform', DeprecationWarning) return transform(value) def to_unicode(value): try: - value = six.text_type(force_text(value)) + value = text_type(force_text(value)) except (UnicodeEncodeError, UnicodeDecodeError): value = '(Error decoding value)' except Exception: # in some cases we get a different exception try: - value = six.binary_type(repr(type(value))) + value = binary_type(repr(type(value))) except Exception: value = '(Error decoding value)' return value @@ -89,6 +92,6 @@ def to_unicode(value): def to_string(value): try: - return six.binary_type(value.decode('utf-8').encode('utf-8')) + return binary_type(value.decode('utf-8').encode('utf-8')) except: return to_unicode(value).encode('utf-8') diff --git a/raven/utils/imports.py b/raven/utils/imports.py index 56ef18a7c..8086fc3d9 100644 --- a/raven/utils/imports.py +++ b/raven/utils/imports.py @@ -1,11 +1,11 @@ from __future__ import absolute_import -from . import six +from raven._compat import PY2 def import_string(key): # HACK(dcramer): Ensure a unicode key is still importable - if not six.PY3: + if PY2: key = str(key) if '.' not in key: diff --git a/raven/utils/serializer/base.py b/raven/utils/serializer/base.py index ab569dcea..1a5f32e8e 100644 --- a/raven/utils/serializer/base.py +++ b/raven/utils/serializer/base.py @@ -11,7 +11,8 @@ import itertools import types -from raven.utils import six +from raven._compat import text_type, binary_type, string_types, iteritems, \ + class_types, PY2 from raven.utils.encoding import to_unicode from .manager import manager as serialization_manager @@ -54,13 +55,14 @@ def recurse(self, value, max_depth=6, _depth=0, **kwargs): _depth += 1 if _depth >= max_depth: try: - value = six.text_type(repr(value))[:string_max_length] + value = text_type(repr(value))[:string_max_length] except Exception as e: import traceback traceback.print_exc() self.manager.logger.exception(e) - return six.text_type(type(value)) - return self.manager.transform(value, max_depth=max_depth, _depth=_depth, **kwargs) + return text_type(type(value)) + return self.manager.transform(value, max_depth=max_depth, + _depth=_depth, **kwargs) class IterableSerializer(Serializer): @@ -71,7 +73,8 @@ def serialize(self, value, **kwargs): return tuple( self.recurse(o, **kwargs) for n, o - in itertools.takewhile(lambda x: x[0] < list_max_length, enumerate(value)) + in itertools.takewhile(lambda x: x[0] < list_max_length, + enumerate(value)) ) @@ -79,7 +82,7 @@ class DictSerializer(Serializer): types = (dict,) def make_key(self, key): - if not isinstance(key, six.string_types): + if not isinstance(key, string_types): return to_unicode(key) return key @@ -88,12 +91,13 @@ def serialize(self, value, **kwargs): return dict( (self.make_key(self.recurse(k, **kwargs)), self.recurse(v, **kwargs)) for n, (k, v) - in itertools.takewhile(lambda x: x[0] < list_max_length, enumerate(six.iteritems(value))) + in itertools.takewhile(lambda x: x[0] < list_max_length, enumerate( + iteritems(value))) ) class UnicodeSerializer(Serializer): - types = (six.text_type,) + types = (text_type,) def serialize(self, value, **kwargs): # try to return a reasonable string that can be decoded @@ -101,21 +105,22 @@ def serialize(self, value, **kwargs): # unicode character # e.g. we want the output to be like: "u'רונית מגן'" string_max_length = kwargs.get('string_max_length', None) - return repr(six.text_type('%s')) % (value[:string_max_length],) + return repr(text_type('%s')) % (value[:string_max_length],) class StringSerializer(Serializer): - types = (six.binary_type,) + types = (binary_type,) def serialize(self, value, **kwargs): string_max_length = kwargs.get('string_max_length', None) - if six.PY3: + if not PY2: return repr(value[:string_max_length]) try: # Python2 madness: let's try to recover from developer's issues # Try to process the string as if it was a unicode. - return "'" + value.decode('utf8')[:string_max_length].encode('utf8') + "'" + return "'" + value.decode('utf8')[:string_max_length] \ + .encode('utf8') + "'" except UnicodeDecodeError: pass @@ -123,10 +128,11 @@ def serialize(self, value, **kwargs): class TypeSerializer(Serializer): - types = six.class_types + types = class_types def can(self, value): - return not super(TypeSerializer, self).can(value) and has_sentry_metadata(value) + return not super(TypeSerializer, self).can(value) \ + and has_sentry_metadata(value) def serialize(self, value, **kwargs): return self.recurse(value.__sentry__(), **kwargs) @@ -157,10 +163,11 @@ class FunctionSerializer(Serializer): types = (types.FunctionType,) def serialize(self, value, **kwargs): - return '' % (value.__name__, value.__module__, id(value)) + return '' % ( + value.__name__, value.__module__, id(value)) -if not six.PY3: +if PY2: class LongSerializer(Serializer): types = (long,) # noqa @@ -178,5 +185,5 @@ def serialize(self, value, **kwargs): serialization_manager.register(FloatSerializer) serialization_manager.register(IntegerSerializer) serialization_manager.register(FunctionSerializer) -if not six.PY3: +if PY2: serialization_manager.register(LongSerializer) diff --git a/raven/utils/serializer/manager.py b/raven/utils/serializer/manager.py index 652508d30..7343111cb 100644 --- a/raven/utils/serializer/manager.py +++ b/raven/utils/serializer/manager.py @@ -9,7 +9,7 @@ import logging from contextlib import closing -from raven.utils import six +from raven._compat import text_type __all__ = ('register', 'transform') @@ -69,16 +69,18 @@ def transform(self, value, **kwargs): return serializer.serialize(value, **kwargs) except Exception as e: logger.exception(e) - return six.text_type(type(value)) + return text_type(type(value)) # if all else fails, lets use the repr of the object try: return repr(value) except Exception as e: logger.exception(e) - # It's common case that a model's __unicode__ definition may try to query the database - # which if it was not cleaned up correctly, would hit a transaction aborted exception - return six.text_type(type(value)) + # It's common case that a model's __unicode__ definition + # may try to query the database which if it was not + # cleaned up correctly, would hit a transaction aborted + # exception + return text_type(type(value)) finally: self.context.remove(objid) diff --git a/raven/utils/six.py b/raven/utils/six.py deleted file mode 100644 index 966d36fb4..000000000 --- a/raven/utils/six.py +++ /dev/null @@ -1,406 +0,0 @@ -"""Utilities for writing code that runs on Python 2 and 3""" -# flake8: noqa - -# Copyright (c) 2010-2013 Benjamin Peterson -# -# 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. -from __future__ import absolute_import - -import operator -import sys -import types - -__author__ = "Benjamin Peterson " -__version__ = "1.3.0" - - -# True if we are running on Python 3. -PY3 = sys.version_info[0] == 3 - -if PY3: - string_types = str, - integer_types = int, - class_types = type, - text_type = str - binary_type = bytes - - MAXSIZE = sys.maxsize -else: - string_types = basestring, - integer_types = (int, long) - class_types = (type, types.ClassType) - text_type = unicode - binary_type = str - - if sys.platform.startswith("java"): - # Jython always uses 32 bits. - MAXSIZE = int((1 << 31) - 1) - else: - # It's possible to have sizeof(long) != sizeof(Py_ssize_t). - class X(object): - def __len__(self): - return 1 << 31 - try: - len(X()) - except OverflowError: - # 32-bit - MAXSIZE = int((1 << 31) - 1) - else: - # 64-bit - MAXSIZE = int((1 << 63) - 1) - del X - - -def _add_doc(func, doc): - """Add documentation to a function.""" - func.__doc__ = doc - - -def _import_module(name): - """Import module, returning the module after the last dot.""" - __import__(name) - return sys.modules[name] - - -class _LazyDescr(object): - - def __init__(self, name): - self.name = name - - def __get__(self, obj, tp): - result = self._resolve() - setattr(obj, self.name, result) - # This is a bit ugly, but it avoids running this again. - delattr(tp, self.name) - return result - - -class MovedModule(_LazyDescr): - - def __init__(self, name, old, new=None): - super(MovedModule, self).__init__(name) - if PY3: - if new is None: - new = name - self.mod = new - else: - self.mod = old - - def _resolve(self): - return _import_module(self.mod) - - -class MovedAttribute(_LazyDescr): - - def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None): - super(MovedAttribute, self).__init__(name) - if PY3: - if new_mod is None: - new_mod = name - self.mod = new_mod - if new_attr is None: - if old_attr is None: - new_attr = name - else: - new_attr = old_attr - self.attr = new_attr - else: - self.mod = old_mod - if old_attr is None: - old_attr = name - self.attr = old_attr - - def _resolve(self): - module = _import_module(self.mod) - return getattr(module, self.attr) - - -class _MovedItems(types.ModuleType): - """Lazy loading of moved objects""" - - -_moved_attributes = [ - MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"), - MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"), - MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"), - MovedAttribute("map", "itertools", "builtins", "imap", "map"), - MovedAttribute("reload_module", "__builtin__", "imp", "reload"), - MovedAttribute("reduce", "__builtin__", "functools"), - MovedAttribute("StringIO", "StringIO", "io"), - MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"), - MovedAttribute("zip", "itertools", "builtins", "izip", "zip"), - - MovedModule("builtins", "__builtin__"), - MovedModule("configparser", "ConfigParser"), - MovedModule("copyreg", "copy_reg"), - MovedModule("http_cookiejar", "cookielib", "http.cookiejar"), - MovedModule("http_cookies", "Cookie", "http.cookies"), - MovedModule("html_entities", "htmlentitydefs", "html.entities"), - MovedModule("html_parser", "HTMLParser", "html.parser"), - MovedModule("http_client", "httplib", "http.client"), - MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"), - MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"), - MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"), - MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"), - MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"), - MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"), - MovedModule("cPickle", "cPickle", "pickle"), - MovedModule("queue", "Queue"), - MovedModule("reprlib", "repr"), - MovedModule("socketserver", "SocketServer"), - MovedModule("tkinter", "Tkinter"), - MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"), - MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"), - MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"), - MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"), - MovedModule("tkinter_tix", "Tix", "tkinter.tix"), - MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"), - MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"), - MovedModule("tkinter_colorchooser", "tkColorChooser", - "tkinter.colorchooser"), - MovedModule("tkinter_commondialog", "tkCommonDialog", - "tkinter.commondialog"), - MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"), - MovedModule("tkinter_font", "tkFont", "tkinter.font"), - MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"), - MovedModule("tkinter_tksimpledialog", "tkSimpleDialog", - "tkinter.simpledialog"), - MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"), - MovedModule("winreg", "_winreg"), -] -for attr in _moved_attributes: - setattr(_MovedItems, attr.name, attr) -del attr - -moves = sys.modules[__name__ + ".moves"] = _MovedItems("moves") - - -def add_move(move): - """Add an item to six.moves.""" - setattr(_MovedItems, move.name, move) - - -def remove_move(name): - """Remove item from six.moves.""" - try: - delattr(_MovedItems, name) - except AttributeError: - try: - del moves.__dict__[name] - except KeyError: - raise AttributeError("no such move, %r" % (name,)) - - -if PY3: - _meth_func = "__func__" - _meth_self = "__self__" - - _func_closure = "__closure__" - _func_code = "__code__" - _func_defaults = "__defaults__" - _func_globals = "__globals__" - - _iterkeys = "keys" - _itervalues = "values" - _iteritems = "items" - _iterlists = "lists" -else: - _meth_func = "im_func" - _meth_self = "im_self" - - _func_closure = "func_closure" - _func_code = "func_code" - _func_defaults = "func_defaults" - _func_globals = "func_globals" - - _iterkeys = "iterkeys" - _itervalues = "itervalues" - _iteritems = "iteritems" - _iterlists = "iterlists" - - -try: - advance_iterator = next -except NameError: - def advance_iterator(it): - return it.next() -next = advance_iterator - - -try: - callable = callable -except NameError: - def callable(obj): - return any("__call__" in klass.__dict__ for klass in type(obj).__mro__) - -if PY3: - def get_unbound_function(unbound): - return unbound - - Iterator = object -else: - def get_unbound_function(unbound): # NOQA - return unbound.im_func - - class Iterator(object): - - def next(self): - return type(self).__next__(self) - - callable = callable -_add_doc(get_unbound_function, - """Get the function out of a possibly unbound function""") - - -get_method_function = operator.attrgetter(_meth_func) -get_method_self = operator.attrgetter(_meth_self) -get_function_closure = operator.attrgetter(_func_closure) -get_function_code = operator.attrgetter(_func_code) -get_function_defaults = operator.attrgetter(_func_defaults) -get_function_globals = operator.attrgetter(_func_globals) - - -def iterkeys(d, **kw): - """Return an iterator over the keys of a dictionary.""" - return iter(getattr(d, _iterkeys)(**kw)) - - -def itervalues(d, **kw): - """Return an iterator over the values of a dictionary.""" - return iter(getattr(d, _itervalues)(**kw)) - - -def iteritems(d, **kw): - """Return an iterator over the (key, value) pairs of a dictionary.""" - return iter(getattr(d, _iteritems)(**kw)) - - -def iterlists(d, **kw): - """Return an iterator over the (key, [values]) pairs of a dictionary.""" - return iter(getattr(d, _iterlists)(**kw)) - - -if PY3: - def b(s): - return s.encode("latin-1") - - def u(s): - return s - if sys.version_info[1] <= 1: - def int2byte(i): - return bytes((i,)) - else: - # This is about 2x faster than the implementation above on 3.2+ - int2byte = operator.methodcaller("to_bytes", 1, "big") - import io - StringIO = io.StringIO - BytesIO = io.BytesIO -else: - def b(s): # NOQA - return s - - def u(s): # NOQA - return unicode(s, "unicode_escape") - int2byte = chr - import StringIO - StringIO = BytesIO = StringIO.StringIO -_add_doc(b, """Byte literal""") -_add_doc(u, """Text literal""") - - -if PY3: - import builtins - exec_ = getattr(builtins, "exec") - - def reraise(tp, value, tb=None): - if value.__traceback__ is not tb: - raise value.with_traceback(tb) - raise value - - print_ = getattr(builtins, "print") - del builtins - -else: - def exec_(_code_, _globs_=None, _locs_=None): - """Execute code in a namespace.""" - if _globs_ is None: - frame = sys._getframe(1) - _globs_ = frame.f_globals - if _locs_ is None: - _locs_ = frame.f_locals - del frame - elif _locs_ is None: - _locs_ = _globs_ - exec("""exec _code_ in _globs_, _locs_""") - - exec_("""def reraise(tp, value, tb=None): - raise tp, value, tb -""") - - def print_(*args, **kwargs): - """The new-style print function.""" - fp = kwargs.pop("file", sys.stdout) - if fp is None: - return - - def write(data): - if not isinstance(data, basestring): - data = str(data) - fp.write(data) - want_unicode = False - sep = kwargs.pop("sep", None) - if sep is not None: - if isinstance(sep, unicode): - want_unicode = True - elif not isinstance(sep, str): - raise TypeError("sep must be None or a string") - end = kwargs.pop("end", None) - if end is not None: - if isinstance(end, unicode): - want_unicode = True - elif not isinstance(end, str): - raise TypeError("end must be None or a string") - if kwargs: - raise TypeError("invalid keyword arguments to print()") - if not want_unicode: - for arg in args: - if isinstance(arg, unicode): - want_unicode = True - break - if want_unicode: - newline = unicode("\n") - space = unicode(" ") - else: - newline = "\n" - space = " " - if sep is None: - sep = space - if end is None: - end = newline - for i, arg in enumerate(args): - if i: - write(sep) - write(arg) - write(end) - -_add_doc(reraise, """Reraise an exception.""") - - -def with_metaclass(meta, base=object): - """Create a base class with a metaclass.""" - return meta("NewBase", (base,), {}) diff --git a/raven/utils/stacks.py b/raven/utils/stacks.py index 62791ff66..518af0126 100644 --- a/raven/utils/stacks.py +++ b/raven/utils/stacks.py @@ -14,13 +14,14 @@ import warnings from raven.utils.serializer import transform -from raven.utils import six +from raven._compat import iteritems _coding_re = re.compile(r'coding[:=]\s*([-\w.]+)') -def get_lines_from_file(filename, lineno, context_lines, loader=None, module_name=None): +def get_lines_from_file(filename, lineno, context_lines, + loader=None, module_name=None): """ Returns context_lines before and after lineno from file. Returns (pre_context_lineno, pre_context, context_line, post_context). @@ -63,7 +64,8 @@ def get_lines_from_file(filename, lineno, context_lines, loader=None, module_nam try: pre_context = [line.strip('\r\n') for line in source[lower_bound:lineno]] context_line = source[lineno].strip('\r\n') - post_context = [line.strip('\r\n') for line in source[(lineno + 1):upper_bound]] + post_context = [line.strip('\r\n') for line in + source[(lineno + 1):upper_bound]] except IndexError: # the file may have changed since it was loaded into memory return None, None, None @@ -178,7 +180,7 @@ def get_frame_locals(frame, transformer=transform, max_var_size=4096): f_vars = {} f_size = 0 - for k, v in six.iteritems(f_locals): + for k, v in iteritems(f_locals): v = transformer(v) v_size = len(repr(v)) if v_size + f_size < 4096: @@ -253,7 +255,8 @@ def get_stack_info(frames, transformer=transform, capture_locals=True, lineno -= 1 if lineno is not None and abs_path: - pre_context, context_line, post_context = get_lines_from_file(abs_path, lineno, 5, loader, module_name) + pre_context, context_line, post_context = \ + get_lines_from_file(abs_path, lineno, 5, loader, module_name) else: pre_context, context_line, post_context = None, None, None @@ -261,7 +264,8 @@ def get_stack_info(frames, transformer=transform, capture_locals=True, # This changes /foo/site-packages/baz/bar.py into baz/bar.py try: base_filename = sys.modules[module_name.split('.', 1)[0]].__file__ - filename = abs_path.split(base_filename.rsplit('/', 2)[0], 1)[-1].lstrip("/") + filename = abs_path.split( + base_filename.rsplit('/', 2)[0], 1)[-1].lstrip("/") except: filename = abs_path diff --git a/raven/utils/wsgi.py b/raven/utils/wsgi.py index ad83bde33..24a8e812f 100644 --- a/raven/utils/wsgi.py +++ b/raven/utils/wsgi.py @@ -6,7 +6,7 @@ """ from __future__ import absolute_import -from raven.utils import six +from raven._compat import iteritems from raven.utils.compat import urllib_quote @@ -15,7 +15,7 @@ def get_headers(environ): """ Returns only proper HTTP headers. """ - for key, value in six.iteritems(environ): + for key, value in iteritems(environ): key = str(key) if key.startswith('HTTP_') and key not in \ ('HTTP_CONTENT_TYPE', 'HTTP_CONTENT_LENGTH'): diff --git a/raven/versioning.py b/raven/versioning.py index 5efa7e884..c36b71916 100644 --- a/raven/versioning.py +++ b/raven/versioning.py @@ -8,8 +8,7 @@ # pkg_resource is not available on Google App Engine pkg_resources = None -from raven.utils import six - +from raven._compat import text_type from .exceptions import InvalidGitRepository __all__ = ('fetch_git_sha', 'fetch_package_version') @@ -22,10 +21,11 @@ def fetch_git_sha(path, head=None): if not head: head_path = os.path.join(path, '.git', 'HEAD') if not os.path.exists(head_path): - raise InvalidGitRepository('Cannot identify HEAD for git repository at %s' % (path,)) + raise InvalidGitRepository( + 'Cannot identify HEAD for git repository at %s' % (path,)) with open(head_path, 'r') as fp: - head = six.text_type(fp.read()).strip() + head = text_type(fp.read()).strip() if head.startswith('ref: '): revision_file = os.path.join( @@ -38,12 +38,14 @@ def fetch_git_sha(path, head=None): if not os.path.exists(revision_file): if not os.path.exists(os.path.join(path, '.git')): - raise InvalidGitRepository('%s does not seem to be the root of a git repository' % (path,)) - raise InvalidGitRepository('Unable to find ref to head "%s" in repository' % (head,)) + raise InvalidGitRepository( + '%s does not seem to be the root of a git repository' % (path,)) + raise InvalidGitRepository( + 'Unable to find ref to head "%s" in repository' % (head,)) fh = open(revision_file, 'r') try: - return six.text_type(fh.read()).strip() + return text_type(fh.read()).strip() finally: fh.close() @@ -53,6 +55,7 @@ def fetch_package_version(dist_name): >>> fetch_package_version('sentry') """ if pkg_resources is None: - raise NotImplementedError('pkg_resources is not available on this Python install') + raise NotImplementedError('pkg_resources is not available ' + 'on this Python install') dist = pkg_resources.get_distribution(dist_name) return dist.version diff --git a/setup.py b/setup.py index 2c692c53f..9c254bf42 100755 --- a/setup.py +++ b/setup.py @@ -53,6 +53,7 @@ install_requires.remove('contextlib2') tests_require = [ + 'six', 'bottle', 'celery>=2.5', 'Django>=1.4', diff --git a/tests/base/tests.py b/tests/base/tests.py index 3cf290b39..685e045d3 100644 --- a/tests/base/tests.py +++ b/tests/base/tests.py @@ -5,13 +5,13 @@ import mock import raven import time +import six from raven.base import Client, ClientState from raven.exceptions import RateLimited from raven.transport import AsyncTransport from raven.transport.http import HTTPTransport from raven.utils.stacks import iter_stack_frames -from raven.utils import six from raven.utils.testutils import TestCase diff --git a/tests/contrib/django/tests.py b/tests/contrib/django/tests.py index de4b86697..8d96ec103 100644 --- a/tests/contrib/django/tests.py +++ b/tests/contrib/django/tests.py @@ -9,8 +9,10 @@ import mock import pytest import re +import six import sys # NOQA from exam import fixture +from six import StringIO from django.conf import settings from django.contrib.auth.models import User @@ -33,8 +35,6 @@ from raven.contrib.django.views import is_valid_origin from raven.transport import HTTPTransport from raven.utils.serializer import transform -from raven.utils import six -from raven.utils.six import StringIO from django.test.client import Client as TestClient, ClientHandler as TestClientHandler from .models import TestModel diff --git a/tests/contrib/tornado/tests.py b/tests/contrib/tornado/tests.py index a2bd1b713..d50253b1e 100644 --- a/tests/contrib/tornado/tests.py +++ b/tests/contrib/tornado/tests.py @@ -1,12 +1,12 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals +import six from mock import patch from tornado import web, gen, testing from tornado.concurrent import Future from tornado.httpclient import HTTPError from raven.contrib.tornado import SentryMixin, AsyncSentryClient -from raven.utils import six class AnErrorProneHandler(SentryMixin, web.RequestHandler): diff --git a/tests/handlers/logbook/tests.py b/tests/handlers/logbook/tests.py index aad74f56f..c3810176b 100644 --- a/tests/handlers/logbook/tests.py +++ b/tests/handlers/logbook/tests.py @@ -1,9 +1,9 @@ from __future__ import with_statement from __future__ import unicode_literals +import six import logbook from raven.utils.testutils import TestCase -from raven.utils import six from raven.base import Client from raven.handlers.logbook import SentryHandler diff --git a/tests/handlers/logging/tests.py b/tests/handlers/logging/tests.py index 239224e37..76b24e2ff 100644 --- a/tests/handlers/logging/tests.py +++ b/tests/handlers/logging/tests.py @@ -3,10 +3,10 @@ import logging import sys import mock +import six from raven.base import Client from raven.handlers.logging import SentryHandler -from raven.utils import six from raven.utils.stacks import iter_stack_frames from raven.utils.testutils import TestCase diff --git a/tests/utils/encoding/tests.py b/tests/utils/encoding/tests.py index 1cbf99128..2b063a76a 100644 --- a/tests/utils/encoding/tests.py +++ b/tests/utils/encoding/tests.py @@ -1,8 +1,9 @@ # -*- coding: utf-8 -*- import pytest import uuid +import six -from raven.utils import six, json +from raven.utils import json from raven.utils.testutils import TestCase from raven.utils.serializer import transform diff --git a/tests/utils/stacks/tests.py b/tests/utils/stacks/tests.py index 8ae22c475..4d1bc364c 100644 --- a/tests/utils/stacks/tests.py +++ b/tests/utils/stacks/tests.py @@ -1,9 +1,10 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals +import six + from mock import Mock from raven.utils.testutils import TestCase -from raven.utils import six from raven.utils.stacks import get_culprit, get_stack_info, get_lines_from_file diff --git a/tests/utils/test_imports.py b/tests/utils/test_imports.py index e131abc61..12d9dc9e0 100644 --- a/tests/utils/test_imports.py +++ b/tests/utils/test_imports.py @@ -1,8 +1,9 @@ from __future__ import absolute_import +import six + import raven -from raven.utils import six from raven.utils.imports import import_string diff --git a/tests/versioning/tests.py b/tests/versioning/tests.py index a39173c44..b3b49cb3d 100644 --- a/tests/versioning/tests.py +++ b/tests/versioning/tests.py @@ -3,11 +3,11 @@ import os.path import pytest import subprocess +import six from django.conf import settings from raven.versioning import fetch_git_sha, fetch_package_version -from raven.utils import six def has_git_requirements(): From 206f233ef9c1a30dd56da97f3da4dd6d8a2a7f07 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 13 Jan 2016 13:27:44 +0100 Subject: [PATCH 168/692] Restored broken import --- 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 62f2de8f3..b2014c946 100644 --- a/raven/contrib/django/models.py +++ b/raven/contrib/django/models.py @@ -19,7 +19,7 @@ from django.conf import settings from hashlib import md5 -from raven._compat import PY2, binary_type, text_type +from raven._compat import PY2, binary_type, text_type, string_types from raven.utils.imports import import_string from raven.contrib.django.management import patch_cli_runner From 168ffa2e130f874223251bf0062c21645daf39af Mon Sep 17 00:00:00 2001 From: sabricot Date: Wed, 13 Jan 2016 16:10:03 +0000 Subject: [PATCH 169/692] Fix disabling of ssl certs check with python 2.7.9/3.4.3 --- raven/utils/http.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/raven/utils/http.py b/raven/utils/http.py index 6d5d213de..f0d14a0c4 100644 --- a/raven/utils/http.py +++ b/raven/utils/http.py @@ -48,7 +48,11 @@ def https_open(self, req): if verify_ssl: handlers = [ValidHTTPSHandler] else: - handlers = [] + try: + handlers = [urllib2.HTTPSHandler( + context=ssl._create_unverified_context())] + except AttributeError: + handlers = [] opener = urllib2.build_opener(*handlers) From fb0b53a66fa16f9b6ebe0c302e4d639f0c9e4fed Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 13 Jan 2016 17:32:32 +0100 Subject: [PATCH 170/692] Make tests work with newer Django --- setup.py | 2 +- tests/contrib/django/tests.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 9c254bf42..230df4d8b 100755 --- a/setup.py +++ b/setup.py @@ -66,7 +66,7 @@ 'pep8', 'pytz', 'pytest', - 'pytest-django>=2.7.0,<2.8.0', + 'pytest-django==2.9.1', 'pytest-timeout==0.4', 'requests', 'tornado', diff --git a/tests/contrib/django/tests.py b/tests/contrib/django/tests.py index 8d96ec103..88e0b5184 100644 --- a/tests/contrib/django/tests.py +++ b/tests/contrib/django/tests.py @@ -319,6 +319,9 @@ def test_include_modules(self): assert event['culprit'].startswith('django.shortcuts in ') self.raven.include_paths = include_paths + # This is broken as of recently. It only works on older Django + # versions? + @pytest.mark.xfail def test_template_name_as_view(self): self.assertRaises(TemplateSyntaxError, self.client.get, reverse('sentry-template-exc')) From 0a2f65df9eb323be895a23e77b828031b5521ec2 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 13 Jan 2016 22:50:19 +0100 Subject: [PATCH 171/692] Restored template error handling for Django 1.9 and newer. --- raven/contrib/django/client.py | 21 ++++++++--- raven/contrib/django/utils.py | 45 ++++++++++++++--------- tests/contrib/django/templates/error.html | 21 ++++++++++- tests/contrib/django/tests.py | 3 -- 4 files changed, 63 insertions(+), 27 deletions(-) diff --git a/raven/contrib/django/client.py b/raven/contrib/django/client.py index 51cbda262..5552bb523 100644 --- a/raven/contrib/django/client.py +++ b/raven/contrib/django/client.py @@ -151,14 +151,23 @@ def capture(self, event_type, request=None, **kwargs): if kwargs.get('exc_info'): exc_value = kwargs['exc_info'][1] - # As of r16833 (Django) all exceptions may contain a ``django_template_source`` attribute (rather than the - # legacy ``TemplateSyntaxError.source`` check) which describes template information. - if hasattr(exc_value, 'django_template_source') or ((isinstance(exc_value, TemplateSyntaxError) and - isinstance(getattr(exc_value, 'source', None), (tuple, list)) and isinstance(exc_value.source[0], Origin))): - source = getattr(exc_value, 'django_template_source', getattr(exc_value, 'source', None)) + # As of r16833 (Django) all exceptions may contain a + # ``django_template_source`` attribute (rather than the legacy + # ``TemplateSyntaxError.source`` check) which describes + # template information. As of Django 1.9 or so the new + # template debug thing showed up. + if hasattr(exc_value, 'django_template_source') or \ + ((isinstance(exc_value, TemplateSyntaxError) and + isinstance(getattr(exc_value, 'source', None), + (tuple, list)) and + isinstance(exc_value.source[0], Origin))) or \ + hasattr(exc_value, 'template_debug'): + source = getattr(exc_value, 'django_template_source', + getattr(exc_value, 'source', None)) + debug = getattr(exc_value, 'template_debug', None) if source is None: self.logger.info('Unable to get template source from exception') - data.update(get_data_from_template(source)) + data.update(get_data_from_template(source, debug)) result = super(DjangoClient, self).capture(event_type, **kwargs) diff --git a/raven/contrib/django/utils.py b/raven/contrib/django/utils.py index cd4985232..81a43bcc2 100644 --- a/raven/contrib/django/utils.py +++ b/raven/contrib/django/utils.py @@ -8,6 +8,7 @@ from __future__ import absolute_import +import os from django.conf import settings @@ -20,21 +21,31 @@ def linebreak_iter(template_source): yield len(template_source) + 1 -def get_data_from_template(source): - origin, (start, end) = source - template_source = origin.reload() +def get_data_from_template(source, debug=None): + if debug is not None: + start = debug['start'] + end = debug['end'] + source_lines = debug['source_lines'] + lineno = debug['line'] + filename = debug['name'] + culprit = filename.split('/templates/')[-1] + elif source: + origin, (start, end) = source + filename = culprit = getattr(origin, 'loadname', None) + template_source = origin.reload() + lineno = None + upto = 0 + source_lines = [] + for num, next in enumerate(linebreak_iter(template_source)): + if start >= upto and end <= next: + lineno = num + source_lines.append(template_source[upto:next]) + upto = next - lineno = None - upto = 0 - source_lines = [] - for num, next in enumerate(linebreak_iter(template_source)): - if start >= upto and end <= next: - lineno = num - source_lines.append(template_source[upto:next]) - upto = next - - if not source_lines or lineno is None: - return {} + if not source_lines or lineno is None: + return {} + else: + raise TypeError('Source or debug needed') pre_context = source_lines[max(lineno - 3, 0):lineno] post_context = source_lines[(lineno + 1):(lineno + 4)] @@ -42,14 +53,14 @@ def get_data_from_template(source): return { 'template': { - 'filename': getattr(origin, 'loadname', None), - 'abs_path': origin.name, + 'filename': os.path.basename(filename), + 'abs_path': filename, 'pre_context': pre_context, 'context_line': context_line, 'lineno': lineno, 'post_context': post_context, }, - 'culprit': getattr(origin, 'loadname', None), + 'culprit': culprit, } diff --git a/tests/contrib/django/templates/error.html b/tests/contrib/django/templates/error.html index 7d36b9b21..9f601208a 100644 --- a/tests/contrib/django/templates/error.html +++ b/tests/contrib/django/templates/error.html @@ -1 +1,20 @@ -{% invalid template tag %} \ No newline at end of file +1 +2 +3 +4 +5 +6 +7 +8 +9 +{% invalid template tag %} +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 diff --git a/tests/contrib/django/tests.py b/tests/contrib/django/tests.py index 88e0b5184..8d96ec103 100644 --- a/tests/contrib/django/tests.py +++ b/tests/contrib/django/tests.py @@ -319,9 +319,6 @@ def test_include_modules(self): assert event['culprit'].startswith('django.shortcuts in ') self.raven.include_paths = include_paths - # This is broken as of recently. It only works on older Django - # versions? - @pytest.mark.xfail def test_template_name_as_view(self): self.assertRaises(TemplateSyntaxError, self.client.get, reverse('sentry-template-exc')) From 2526a328f80f63e0fd46a345b4fc37cf18df4474 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 13 Jan 2016 22:53:52 +0100 Subject: [PATCH 172/692] Added changelog entry --- CHANGES | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES b/CHANGES index e5e4d6c36..11ed0474a 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,9 @@ +Version 5.9.3 +------------- + +* Restore template debug support for Django 1.9 and newer. +* Correctly handle SSL verification disabling for newer Python versions. + Version 5.9.2 ------------- From 73b449d398c74c788a554ed485ae4a878453a2e9 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 13 Jan 2016 22:55:24 +0100 Subject: [PATCH 173/692] Corrected version number in changelog --- CHANGES | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 11ed0474a..392eaf72c 100644 --- a/CHANGES +++ b/CHANGES @@ -1,5 +1,5 @@ -Version 5.9.3 -------------- +Version 5.10.0 +-------------- * Restore template debug support for Django 1.9 and newer. * Correctly handle SSL verification disabling for newer Python versions. From 641254162cd3f33ed61b147306e8c036c35eea9c Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 14 Jan 2016 00:31:16 +0100 Subject: [PATCH 174/692] Stop using deprecated patterns() function --- tests/contrib/django/urls.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/contrib/django/urls.py b/tests/contrib/django/urls.py index 81ac1cef1..a1d292945 100644 --- a/tests/contrib/django/urls.py +++ b/tests/contrib/django/urls.py @@ -2,10 +2,10 @@ from django.conf import settings try: - from django.conf.urls import url, patterns + from django.conf.urls import url except ImportError: # for Django version less than 1.4 - from django.conf.urls.defaults import url, patterns # NOQA + from django.conf.urls.defaults import url # NOQA from django.http import HttpResponse @@ -20,7 +20,7 @@ def handler500(request): return HttpResponse('', status=500) -urlpatterns = patterns('', +urlpatterns = [ url(r'^no-error$', 'tests.contrib.django.views.no_error', name='sentry-no-error'), url(r'^fake-login$', 'tests.contrib.django.views.fake_login', name='sentry-fake-login'), url(r'^trigger-500$', 'tests.contrib.django.views.raise_exc', name='sentry-raise-exc'), @@ -29,4 +29,4 @@ def handler500(request): url(r'^trigger-500-django$', 'tests.contrib.django.views.django_exc', name='sentry-django-exc'), url(r'^trigger-500-template$', 'tests.contrib.django.views.template_exc', name='sentry-template-exc'), url(r'^trigger-500-log-request$', 'tests.contrib.django.views.logging_request_exc', name='sentry-log-request-exc'), -) +] From 23f0dae51eafcf2a0d504dc218935c4a91ecf924 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 14 Jan 2016 01:01:40 +0100 Subject: [PATCH 175/692] This is 5.10.0 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 230df4d8b..fc37fa3c8 100755 --- a/setup.py +++ b/setup.py @@ -97,7 +97,7 @@ def run_tests(self): setup( name='raven', - version='5.10.0.dev0', + version='5.10.0', author='David Cramer', author_email='dcramer@gmail.com', url='https://github.com/getsentry/raven-python', From e1390b827fd9567d839575ff6521c2b5fc583a4e Mon Sep 17 00:00:00 2001 From: Sander Hoentjen Date: Sat, 16 Jan 2016 19:10:57 +0100 Subject: [PATCH 176/692] remove unneeded shebang --- raven/contrib/zope/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/raven/contrib/zope/__init__.py b/raven/contrib/zope/__init__.py index 4550ece61..c41a8acc1 100644 --- a/raven/contrib/zope/__init__.py +++ b/raven/contrib/zope/__init__.py @@ -1,4 +1,3 @@ -#!/usr/bin/python # -*- coding: utf-8 -*- """ raven.contrib.zope From 3221f8d157bddda1956db5f23573ec3c1c19fc37 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 18 Jan 2016 13:03:57 -0800 Subject: [PATCH 177/692] Add os import to instructions --- docs/advanced.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/advanced.rst b/docs/advanced.rst index 2aec2b139..77f940d2f 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -47,6 +47,7 @@ settings: .. code-block:: python + import os import raven client = raven.Client( From ab4177b57b29255a0c00848f02095d61a1a2fef4 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 21 Jan 2016 09:22:15 -0800 Subject: [PATCH 178/692] Defer client loading in Django log handler (fixes GH-701) --- raven/contrib/django/handlers.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/raven/contrib/django/handlers.py b/raven/contrib/django/handlers.py index 14ef2d387..2a2942e5d 100644 --- a/raven/contrib/django/handlers.py +++ b/raven/contrib/django/handlers.py @@ -8,13 +8,25 @@ from __future__ import absolute_import -from raven.contrib.django.models import client +import logging + from raven.handlers.logging import SentryHandler as BaseSentryHandler +from raven.utils import memoize class SentryHandler(BaseSentryHandler): def __init__(self, *args, **kwargs): - super(SentryHandler, self).__init__(client=client, *args, **kwargs) + # TODO(dcramer): we'd like to avoid this duplicate code, but we need + # to currently defer loading client due to Django loading patterns. + self.tags = kwargs.pop('tags', None) + + logging.Handler.__init__(self, level=kwargs.get('level', logging.NOTSET)) + + @memoize + def client(self): + # Import must be lazy for deffered Django loading + from raven.contrib.django.models import client + return client def _emit(self, record): request = getattr(record, 'request', None) From 38dd40e80db9d12aec3b0c4e42a78f1529d0a742 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 21 Jan 2016 09:25:25 -0800 Subject: [PATCH 179/692] This is a dev version --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index fc37fa3c8..c29638e7b 100755 --- a/setup.py +++ b/setup.py @@ -97,7 +97,7 @@ def run_tests(self): setup( name='raven', - version='5.10.0', + version='5.10.1.dev.0', author='David Cramer', author_email='dcramer@gmail.com', url='https://github.com/getsentry/raven-python', From 54192ad2dfcf3b896be1e2bf24192bc80cd9fc72 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 21 Jan 2016 09:26:31 -0800 Subject: [PATCH 180/692] Fix non ascii bytes in password sanitization. This fixes #727 --- CHANGES | 6 ++++++ raven/processors.py | 9 +++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 392eaf72c..c2141ee94 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,9 @@ +Version 5.10.1 +-------------- + +* Fixed a problem where bytes as keys in dictionaries caused problems + on data sanitization if those bytes were outside of the ASCII range. + Version 5.10.0 -------------- diff --git a/raven/processors.py b/raven/processors.py index 3f1a74b68..3243d8308 100644 --- a/raven/processors.py +++ b/raven/processors.py @@ -9,7 +9,7 @@ import re -from raven._compat import string_types, text_type +from raven._compat import string_types from raven.utils import varmap @@ -94,7 +94,12 @@ def sanitize(self, key, value): if not key: # key can be a NoneType return value - key = text_type(key).lower() + # Just in case we have bytes here, we want to make them into text + # properly without failing so we can perform our check. + if isinstance(key, bytes): + key = key.decode('utf-8', 'replace') + + key = key.lower() for field in self.FIELDS: if field in key: # store mask as a fixed length for security From 475d379f09ab7981d892bd5ad0a0b45118d9e4f7 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 21 Jan 2016 09:31:07 -0800 Subject: [PATCH 181/692] Duck-type check the user object instead of doing an instance check. This fixes #718 --- CHANGES | 2 ++ raven/contrib/django/client.py | 17 +++++------------ 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/CHANGES b/CHANGES index c2141ee94..ade3b31a7 100644 --- a/CHANGES +++ b/CHANGES @@ -3,6 +3,8 @@ Version 5.10.1 * Fixed a problem where bytes as keys in dictionaries caused problems on data sanitization if those bytes were outside of the ASCII range. +* Django client no longer requires the user object to be a subclass + of the base model. Version 5.10.0 -------------- diff --git a/raven/contrib/django/client.py b/raven/contrib/django/client.py index 5552bb523..c61356e34 100644 --- a/raven/contrib/django/client.py +++ b/raven/contrib/django/client.py @@ -34,7 +34,8 @@ class DjangoClient(Client): logger = logging.getLogger('sentry.errors.client.django') def get_user_info(self, user): - if not user.is_authenticated(): + if hasattr(user, 'is_authenticated') and \ + not user.is_authenticated(): return {} user_info = { @@ -52,19 +53,11 @@ def get_user_info(self, user): return user_info def get_data_from_request(self, request): - try: - from django.contrib.auth.models import AbstractBaseUser as BaseUser - except ImportError: - from django.contrib.auth.models import User as BaseUser # NOQA - except RuntimeError: - # If the contenttype / user applications are not installed trying to - # import the user models will fail for django >= 1.9. - BaseUser = None - result = {} - if BaseUser and hasattr(request, 'user') and isinstance(request.user, BaseUser): - result['user'] = self.get_user_info(request.user) + user = getattr(request, 'user', None) + if user is not None: + result['user'] = self.get_user_info(user) try: uri = request.build_absolute_uri() From 41138e086271269ff9ff278ac50d7ecc7f8d7d99 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 21 Jan 2016 09:28:07 -0800 Subject: [PATCH 182/692] Setup pypi deploys for tags --- .travis.yml | 118 ++++++++++++++++++++++++++-------------------------- 1 file changed, 58 insertions(+), 60 deletions(-) diff --git a/.travis.yml b/.travis.yml index f4d84e6a8..a474b9cb8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,77 +1,75 @@ language: python - sudo: false - addons: apt: packages: - libevent-dev - cache: directories: - - $HOME/.cache/pip - + - "$HOME/.cache/pip" +deploy: + provider: pypi + user: getsentry + password: + 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" - +- '2.6' +- '2.7' +- '3.2' +- '3.3' +- '3.4' +- '3.5' +- 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="-e git+git://github.com/django/django.git#egg=Django"' - + - 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="-e git+git://github.com/django/django.git#egg=Django" install: - - time ci/setup - - pip install codecov "coverage<4" - +- time ci/setup +- pip install codecov "coverage<4" script: - - "if [[ ${TRAVIS_PYTHON_VERSION} != 'pypy' ]]; then make lint; fi" - - coverage run --source=raven -m py.test tests --timeout 10 - +- if [[ ${TRAVIS_PYTHON_VERSION} != 'pypy' ]]; then make lint; fi +- coverage run --source=raven -m py.test tests --timeout 10 after_success: - - codecov -e DJANGO - +- codecov -e DJANGO matrix: allow_failures: - - env: 'DJANGO="-e git+git://github.com/django/django.git#egg=Django"' + - 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.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.9 - - python: "2.6" - env: DJANGO=Django==1.8.7 - - python: "2.6" - env: DJANGO=Django==1.7.11 + - 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.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.9 + - python: '2.6' + env: DJANGO=Django==1.8.7 + - python: '2.6' + env: DJANGO=Django==1.7.11 From cf6d9b32e2b00d0c9e90e1ad5004d7524c3bdf9f Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 21 Jan 2016 09:31:50 -0800 Subject: [PATCH 183/692] Defer client loading in Django log handler (fixes GH-701) --- CHANGES | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES b/CHANGES index ade3b31a7..bb710940c 100644 --- a/CHANGES +++ b/CHANGES @@ -5,6 +5,7 @@ Version 5.10.1 on data sanitization if those bytes were outside of the ASCII range. * Django client no longer requires the user object to be a subclass of the base model. +* Corrected an issue with the Django log handler which would cause a recursive import. Version 5.10.0 -------------- From d3c1c1fcb6303e5aafd52bdfe786f5e493ff27fe Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 21 Jan 2016 09:32:51 -0800 Subject: [PATCH 184/692] Added test from #728 --- tests/processors/tests.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/processors/tests.py b/tests/processors/tests.py index 47608887e..a38cc56a4 100644 --- a/tests/processors/tests.py +++ b/tests/processors/tests.py @@ -244,6 +244,11 @@ def test_sanitize_credit_card_amex(self): result = proc.sanitize('foo', '424242424242424') self.assertEquals(result, proc.MASK) + def test_sanitize_non_ascii(self): + proc = SanitizePasswordsProcessor(Mock()) + result = proc.sanitize('__repr__: жили-были', '42') + self.assertEquals(result, '42') + class RemovePostDataProcessorTest(TestCase): def test_does_remove_data(self): From 005a6d9d3d2e3be4cff8d7d1c5fc5b6058536496 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 21 Jan 2016 10:38:47 -0800 Subject: [PATCH 185/692] Fixed a regression in key handling in the sanitizer. --- raven/processors.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/raven/processors.py b/raven/processors.py index 3243d8308..9df2a045b 100644 --- a/raven/processors.py +++ b/raven/processors.py @@ -9,7 +9,7 @@ import re -from raven._compat import string_types +from raven._compat import string_types, text_type from raven.utils import varmap @@ -98,6 +98,8 @@ def sanitize(self, key, value): # properly without failing so we can perform our check. if isinstance(key, bytes): key = key.decode('utf-8', 'replace') + else: + key = text_type(key) key = key.lower() for field in self.FIELDS: From 3db9fd2fd82f39eaf8b4e4839b7ee78a737123cd Mon Sep 17 00:00:00 2001 From: Matt Robenolt Date: Thu, 21 Jan 2016 11:00:32 -0800 Subject: [PATCH 186/692] Fix travis.yml --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index a474b9cb8..5d0af491b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,7 @@ deploy: password: secure: NMwOI1H9arp2vbgaidx9OY6y8990hiu0WsHtowEvEdGKXNzAQcy0sW3SoKcB6FN0bk11xhj49+5C++KAwMYwE/SL8Y5OoZ1/iYVI4/XlWNukr+1/pfPKVMgw3v5W+pL5Ba9TBdFfIoFPNYUDPLItSSjg94Bm95034gBkYWC5Hl0= on: - tags:: true + tags: true python: - '2.6' - '2.7' From 5e651b207a780020af69f40aa90f8d9dd5382f5f Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 21 Jan 2016 11:00:38 -0800 Subject: [PATCH 187/692] Reformat travis.yml --- .travis.yml | 104 ++++++++++++++++++++++++++-------------------------- 1 file changed, 52 insertions(+), 52 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5d0af491b..dac1f0f31 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,10 +3,10 @@ sudo: false addons: apt: packages: - - libevent-dev + - libevent-dev cache: directories: - - "$HOME/.cache/pip" + - "$HOME/.cache/pip" deploy: provider: pypi user: getsentry @@ -15,61 +15,61 @@ deploy: on: tags: true python: -- '2.6' -- '2.7' -- '3.2' -- '3.3' -- '3.4' -- '3.5' -- pypy + - '2.6' + - '2.7' + - '3.2' + - '3.3' + - '3.4' + - '3.5' + - 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="-e git+git://github.com/django/django.git#egg=Django" + - 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="-e git+git://github.com/django/django.git#egg=Django" install: -- time ci/setup -- pip install codecov "coverage<4" + - time ci/setup + - pip install codecov "coverage<4" script: -- if [[ ${TRAVIS_PYTHON_VERSION} != 'pypy' ]]; then make lint; fi -- coverage run --source=raven -m py.test tests --timeout 10 + - if [[ ${TRAVIS_PYTHON_VERSION} != 'pypy' ]]; then make lint; fi + - coverage run --source=raven -m py.test tests --timeout 10 after_success: -- codecov -e DJANGO + - codecov -e DJANGO matrix: allow_failures: - - env: DJANGO="-e git+git://github.com/django/django.git#egg=Django" + - 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.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.9 - - python: '2.6' - env: DJANGO=Django==1.8.7 - - python: '2.6' - env: DJANGO=Django==1.7.11 + - 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.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.9 + - python: '2.6' + env: DJANGO=Django==1.8.7 + - python: '2.6' + env: DJANGO=Django==1.7.11 From bb2cd9fc15399d5af8d18bba8ff8f942f20fcf8d Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 21 Jan 2016 11:03:34 -0800 Subject: [PATCH 188/692] Fixed missign user behavior in Django to be in line of what we did earlier. --- raven/contrib/django/client.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/raven/contrib/django/client.py b/raven/contrib/django/client.py index c61356e34..64ece8a77 100644 --- a/raven/contrib/django/client.py +++ b/raven/contrib/django/client.py @@ -36,7 +36,7 @@ class DjangoClient(Client): def get_user_info(self, user): if hasattr(user, 'is_authenticated') and \ not user.is_authenticated(): - return {} + return None user_info = { 'id': user.pk, @@ -57,7 +57,9 @@ def get_data_from_request(self, request): user = getattr(request, 'user', None) if user is not None: - result['user'] = self.get_user_info(user) + user_info = self.get_user_info(user) + if user_info: + result['user'] = user_info try: uri = request.build_absolute_uri() From 0e61fd4e9146be9a910e2de89ccfe731a21bbae1 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 21 Jan 2016 11:45:27 -0800 Subject: [PATCH 189/692] This is 5.10.1 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index c29638e7b..eda0eb044 100755 --- a/setup.py +++ b/setup.py @@ -97,7 +97,7 @@ def run_tests(self): setup( name='raven', - version='5.10.1.dev.0', + version='5.10.1', author='David Cramer', author_email='dcramer@gmail.com', url='https://github.com/getsentry/raven-python', From f4a7e9a97cfc10adf69076cc580b76e4ad4a8b6c Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 21 Jan 2016 11:47:14 -0800 Subject: [PATCH 190/692] Temporarily disable pypi release code --- .travis.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index dac1f0f31..2594ab90e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,13 +7,13 @@ addons: cache: directories: - "$HOME/.cache/pip" -deploy: - provider: pypi - user: getsentry - password: - secure: NMwOI1H9arp2vbgaidx9OY6y8990hiu0WsHtowEvEdGKXNzAQcy0sW3SoKcB6FN0bk11xhj49+5C++KAwMYwE/SL8Y5OoZ1/iYVI4/XlWNukr+1/pfPKVMgw3v5W+pL5Ba9TBdFfIoFPNYUDPLItSSjg94Bm95034gBkYWC5Hl0= - on: - tags: true +#deploy: +# provider: pypi +# user: getsentry +# password: +# secure: NMwOI1H9arp2vbgaidx9OY6y8990hiu0WsHtowEvEdGKXNzAQcy0sW3SoKcB6FN0bk11xhj49+5C++KAwMYwE/SL8Y5OoZ1/iYVI4/XlWNukr+1/pfPKVMgw3v5W+pL5Ba9TBdFfIoFPNYUDPLItSSjg94Bm95034gBkYWC5Hl0= +# on: +# tags: true python: - '2.6' - '2.7' From e8f0ccc10f5d9b223f38f25de1445e4ab0ef2dcb Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 21 Jan 2016 11:49:55 -0800 Subject: [PATCH 191/692] This is another dev version --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index eda0eb044..80f3f1f13 100755 --- a/setup.py +++ b/setup.py @@ -97,7 +97,7 @@ def run_tests(self): setup( name='raven', - version='5.10.1', + version='5.10.2.dev.0', author='David Cramer', author_email='dcramer@gmail.com', url='https://github.com/getsentry/raven-python', From f83aedbdeb109bdd68a19e26a6b8ee54cba7dc97 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Fri, 22 Jan 2016 13:31:15 -0800 Subject: [PATCH 192/692] Enforce raven.events import at runtime --- raven/base.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/raven/base.py b/raven/base.py index 21d7a178f..d0995704c 100644 --- a/raven/base.py +++ b/raven/base.py @@ -37,6 +37,10 @@ from raven.utils.stacks import get_stack_info, iter_stack_frames, get_culprit from raven.transport.registry import TransportRegistry, default_transports +# enforce imports to avoid obscure stacktraces with MemoryError +import raven.events # NOQA + + __all__ = ('Client',) __excepthook__ = None From 4fd846a84d7949b1e8046366cec12678e1dfe90b Mon Sep 17 00:00:00 2001 From: James O'Beirne Date: Fri, 22 Jan 2016 16:50:50 -0800 Subject: [PATCH 193/692] Remove unnecessary/confusing logger variable in celery --- raven/contrib/celery/__init__.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/raven/contrib/celery/__init__.py b/raven/contrib/celery/__init__.py index 56b724745..7cf250e44 100644 --- a/raven/contrib/celery/__init__.py +++ b/raven/contrib/celery/__init__.py @@ -7,7 +7,6 @@ """ from __future__ import absolute_import -import logging from celery.signals import after_setup_logger, task_failure from raven.handlers.logging import SentryHandler @@ -39,8 +38,6 @@ def process_failure_signal(sender, task_id, args, kwargs, **kw): def register_logger_signal(client, logger=None, loglevel=logging.ERROR): filter_ = CeleryFilter() - if logger is None: - logger = logging.getLogger() handler = SentryHandler(client) handler.setLevel(loglevel) handler.addFilter(filter_) From d0adbbf2084ca6220bdd208d3994f282890332cd Mon Sep 17 00:00:00 2001 From: James O'Beirne Date: Fri, 22 Jan 2016 17:44:10 -0800 Subject: [PATCH 194/692] Update __init__.py --- raven/contrib/celery/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/raven/contrib/celery/__init__.py b/raven/contrib/celery/__init__.py index 7cf250e44..ba6186e9d 100644 --- a/raven/contrib/celery/__init__.py +++ b/raven/contrib/celery/__init__.py @@ -7,6 +7,8 @@ """ from __future__ import absolute_import +import logging + from celery.signals import after_setup_logger, task_failure from raven.handlers.logging import SentryHandler From 615fa4c6099288fbf873f04a0cc9fb86a96616b9 Mon Sep 17 00:00:00 2001 From: Arnav Kumar Date: Wed, 20 Jan 2016 00:41:08 +0800 Subject: [PATCH 195/692] Allow logging exclusions during object creation and `init_app` --- raven/contrib/flask.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/raven/contrib/flask.py b/raven/contrib/flask.py index d17817791..8086d7f93 100644 --- a/raven/contrib/flask.py +++ b/raven/contrib/flask.py @@ -103,10 +103,11 @@ class Sentry(object): # TODO(dcramer): the client isn't using local context and therefore # gets shared by every app that does init on it def __init__(self, app=None, client=None, client_cls=Client, dsn=None, - logging=False, level=logging.NOTSET, wrap_wsgi=None, - register_signal=True): + logging=False, logging_exclusions=None, level=logging.NOTSET, + wrap_wsgi=None, register_signal=True): self.dsn = dsn self.logging = logging + self.logging_exclusions = logging_exclusions self.client_cls = client_cls self.client = client self.level = level @@ -238,7 +239,8 @@ def after_request(self, sender, response, *args, **kwargs): self.client.context.clear() return response - def init_app(self, app, dsn=None, logging=None, level=None, wrap_wsgi=None, + def init_app(self, app, dsn=None, logging=None, level=None, + logging_exclusions=None, wrap_wsgi=None, register_signal=None): if dsn is not None: self.dsn = dsn @@ -262,11 +264,18 @@ def init_app(self, app, dsn=None, logging=None, level=None, wrap_wsgi=None, if logging is not None: self.logging = logging + if logging_exclusions is not None: + self.logging_exclusions = logging_exclusions + if not self.client: self.client = make_client(self.client_cls, app, self.dsn) if self.logging: - setup_logging(SentryHandler(self.client, level=self.level)) + kwargs = {} + if self.logging_exclusions is not None: + kwargs['exclude'] = self.logging_exclusions + + setup_logging(SentryHandler(self.client, level=self.level), **kwargs) if self.wrap_wsgi: app.wsgi_app = SentryMiddleware(app.wsgi_app, self.client) From 0de40e8e191469127668158fb483f8020ad93ccd Mon Sep 17 00:00:00 2001 From: Arnav Kumar Date: Mon, 25 Jan 2016 12:09:45 +0800 Subject: [PATCH 196/692] Add test for excluding loggers during setup with Flask --- tests/contrib/flask/tests.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/contrib/flask/tests.py b/tests/contrib/flask/tests.py index 072cec805..dd0df2636 100644 --- a/tests/contrib/flask/tests.py +++ b/tests/contrib/flask/tests.py @@ -263,6 +263,19 @@ def test_captureMessage_sets_last_event_id(self): assert self.middleware.last_event_id == event_id assert g.sentry_event_id == event_id + def test_logging_setup_with_exclusion_list(self): + app = Flask(__name__) + raven = TempStoreClient() + + Sentry(app, client=raven, logging=True, + logging_exclusions=("excluded_logger",)) + + excluded_logger = logging.getLogger("excluded_logger") + self.assertFalse(excluded_logger.propagate) + + some_other_logger = logging.getLogger("some_other_logger") + self.assertTrue(some_other_logger.propagate) + class FlaskLoginTest(BaseTest): From 76042258f1e082539851c148b1c4375e7cc4c911 Mon Sep 17 00:00:00 2001 From: Arnav Kumar Date: Mon, 25 Jan 2016 13:18:35 +0800 Subject: [PATCH 197/692] Add docs to demonstrate logging exclusions --- docs/integrations/flask.rst | 6 ++++-- docs/integrations/logging.rst | 10 ++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/docs/integrations/flask.rst b/docs/integrations/flask.rst index 438a47958..8fc4e1ab8 100644 --- a/docs/integrations/flask.rst +++ b/docs/integrations/flask.rst @@ -30,7 +30,8 @@ You can optionally configure logging too:: import logging from raven.contrib.flask import Sentry - sentry = Sentry(app, logging=True, level=logging.ERROR) + 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:: @@ -48,7 +49,8 @@ You can pass parameters in the ``init_app`` hook:: def create_app(): app = Flask(__name__) sentry.init_app(app, dsn='___DSN___', logging=True, - level=logging.ERROR) + level=logging.ERROR, + logging_exclusions=("logger1", "logger2", ...)) return app Settings diff --git a/docs/integrations/logging.rst b/docs/integrations/logging.rst index da703aa6e..514d54a74 100644 --- a/docs/integrations/logging.rst +++ b/docs/integrations/logging.rst @@ -132,3 +132,13 @@ message within Sentry:: logger.error('There was some %s error', 'crazy') logger.error('There was some %s error', 'fun') logger.error('There was some %s error', 1) + +Exclusions +~~~~~~~~~~ + +You can also configure some logging exclusions during setup. These loggers +will not propagate their logs to the Sentry handler. + + from raven.conf import setup_logging + + setup_logging(handler, exclude=("logger1", "logger2", ...)) From 27cc40e6d5669e2275b89541711dea8bb6f6e309 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 27 Jan 2016 15:03:21 -0800 Subject: [PATCH 198/692] Remember exceptions that are logged and filter out duplicates. This fixes #724 --- CHANGES | 7 +++++++ raven/base.py | 22 ++++++++++++++++++++++ raven/context.py | 2 ++ tests/contrib/flask/tests.py | 7 +++++++ 4 files changed, 38 insertions(+) diff --git a/CHANGES b/CHANGES index bb710940c..0945e1702 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,10 @@ +Version 5.10.2 +-------------- + +* Remember exceptions in flight until the context is cleared so that two + reports with the same exception data do not result in two errors + being logged. + Version 5.10.1 -------------- diff --git a/raven/base.py b/raven/base.py index d0995704c..549073c91 100644 --- a/raven/base.py +++ b/raven/base.py @@ -282,6 +282,20 @@ def get_public_dsn(self, scheme=None): return url return '%s:%s' % (scheme, url) + def _get_exception_key(self, exc_info): + return ( + id(exc_info[1]), + id(exc_info[2]), + ) + + def skip_error_for_logging(self, exc_info): + key = self._get_exception_key(exc_info) + return key in self.context.exceptions_to_skip + + def record_exception_seen(self, exc_info): + key = self._get_exception_key(exc_info) + self.context.exceptions_to_skip.add(key) + def build_msg(self, event_type, data=None, date=None, time_spent=None, extra=None, stack=None, public_key=None, tags=None, fingerprint=None, **kwargs): @@ -530,6 +544,12 @@ def capture(self, event_type, data=None, date=None, time_spent=None, if not self.is_enabled(): return + exc_info = kwargs.get('exc_info') + if exc_info is not None: + if self.skip_error_for_logging(exc_info): + return + self.record_exception_seen(exc_info) + data = self.build_msg( event_type, data, date, time_spent, extra, stack, tags=tags, **kwargs) @@ -701,6 +721,8 @@ def captureException(self, exc_info=None, **kwargs): ``kwargs`` are passed through to ``.capture``. """ + if exc_info is None: + exc_info = sys.exc_info() return self.capture( 'raven.events.Exception', exc_info=exc_info, **kwargs) diff --git a/raven/context.py b/raven/context.py index e941360cc..b1c3d7b5f 100644 --- a/raven/context.py +++ b/raven/context.py @@ -27,6 +27,7 @@ class Context(local, Mapping, Iterable): """ def __init__(self): self.data = {} + self.exceptions_to_skip = set() def __getitem__(self, key): return self.data[key] @@ -58,3 +59,4 @@ def get(self): def clear(self): self.data = {} + self.exceptions_to_skip.clear() diff --git a/tests/contrib/flask/tests.py b/tests/contrib/flask/tests.py index 072cec805..90f5a58e5 100644 --- a/tests/contrib/flask/tests.py +++ b/tests/contrib/flask/tests.py @@ -9,6 +9,7 @@ from raven.base import Client from raven.contrib.flask import Sentry from raven.utils.testutils import TestCase +from raven.handlers.logging import SentryHandler class TempStoreClient(Client): @@ -125,6 +126,12 @@ def test_error_handler(self): self.assertEquals(event['message'], 'ValueError: hello world') self.assertEquals(event['culprit'], 'tests.contrib.flask.tests in an_error') + def test_capture_plus_logging(self): + client, raven, app = self.make_client_and_raven(debug=False) + app.logger.addHandler(SentryHandler(raven)) + client.get('/an-error/') + assert len(raven.events) == 1 + def test_get(self): response = self.client.get('/an-error/?foo=bar') self.assertEquals(response.status_code, 500) From a0a29a70d68e6ec17dbc6e8fe6440b690fd70b39 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 27 Jan 2016 15:07:33 -0800 Subject: [PATCH 199/692] Added changelog entry --- CHANGES | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES b/CHANGES index 0945e1702..665f7fd8d 100644 --- a/CHANGES +++ b/CHANGES @@ -4,6 +4,7 @@ Version 5.10.2 * Remember exceptions in flight until the context is cleared so that two reports with the same exception data do not result in two errors being logged. +* Allow logging exclusions. Version 5.10.1 -------------- From 01ca852a6ee7e6a9ecd8a7a292095c40db56404d Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 27 Jan 2016 15:07:56 -0800 Subject: [PATCH 200/692] This is 5.10.2 --- setup.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 80f3f1f13..72980bb0f 100755 --- a/setup.py +++ b/setup.py @@ -97,9 +97,9 @@ def run_tests(self): setup( name='raven', - version='5.10.2.dev.0', - author='David Cramer', - author_email='dcramer@gmail.com', + version='5.10.2', + author='Sentry', + author_email='hello@getsentry.com', url='https://github.com/getsentry/raven-python', description='Raven is a client for Sentry (https://getsentry.com)', long_description=__doc__, From 8412ac034e077f0197acc25276f54727dbc23497 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 27 Jan 2016 15:08:54 -0800 Subject: [PATCH 201/692] 5.10.3 dev --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 72980bb0f..60719ff1a 100755 --- a/setup.py +++ b/setup.py @@ -97,7 +97,7 @@ def run_tests(self): setup( name='raven', - version='5.10.2', + version='5.10.3.dev.0', author='Sentry', author_email='hello@getsentry.com', url='https://github.com/getsentry/raven-python', From d77dec802df465d5bb42c53860d7de91b6119461 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 27 Jan 2016 15:28:21 -0800 Subject: [PATCH 202/692] Improved error key --- raven/base.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/raven/base.py b/raven/base.py index 549073c91..83112d023 100644 --- a/raven/base.py +++ b/raven/base.py @@ -285,7 +285,8 @@ def get_public_dsn(self, scheme=None): def _get_exception_key(self, exc_info): return ( id(exc_info[1]), - id(exc_info[2]), + id(exc_info[2].tb_frame.f_code), + exc_info[2].tb_lasti, ) def skip_error_for_logging(self, exc_info): From 360df93052121b518b7c35597f458f4036a8f208 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 27 Jan 2016 18:39:40 -0800 Subject: [PATCH 203/692] Added changelog entry --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 665f7fd8d..8622dcb82 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 5.10.3 +-------------- + +* Improved double error check + Version 5.10.2 -------------- From dfd81a538cf684c0f1da621d752bd499b95dee5a Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 27 Jan 2016 19:22:55 -0800 Subject: [PATCH 204/692] More info into the exception info key --- raven/base.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/raven/base.py b/raven/base.py index 83112d023..c234d6bb2 100644 --- a/raven/base.py +++ b/raven/base.py @@ -284,8 +284,10 @@ def get_public_dsn(self, scheme=None): def _get_exception_key(self, exc_info): return ( + exc_info[0], id(exc_info[1]), id(exc_info[2].tb_frame.f_code), + id(exc_info[2]), exc_info[2].tb_lasti, ) From f1acd4b834564a8775961dd271e7b3a591295ee8 Mon Sep 17 00:00:00 2001 From: Omid Raha Date: Tue, 9 Feb 2016 09:51:16 +0330 Subject: [PATCH 205/692] Fixed typo of configuration --- docs/integrations/celery.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/integrations/celery.rst b/docs/integrations/celery.rst index 052d34a60..7690c9ac1 100644 --- a/docs/integrations/celery.rst +++ b/docs/integrations/celery.rst @@ -3,7 +3,7 @@ Celery `Celery `_ is a distributed task queue system for Python built on AMQP principles. For Celery built-in support -by Raven is provided but it requires some manual configuraiton. +by Raven is provided but it requires some manual configuration. To capture errors, you need to register a couple of signals to hijack Celery error handling:: From 666d89f25b31bccdd544eedc04b96d1a771aab2d Mon Sep 17 00:00:00 2001 From: Romain Guillebert Date: Thu, 11 Feb 2016 10:24:19 +0000 Subject: [PATCH 206/692] Fix documentation for the gevent transport --- docs/transports.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/transports.rst b/docs/transports.rst index 7c0a189ac..19cb12dba 100644 --- a/docs/transports.rst +++ b/docs/transports.rst @@ -51,9 +51,9 @@ Should only be used within a Gevent IO loop. .. code-block:: python - from raven.transport.gevent import GeventHTTPTransport + from raven.transport.gevent import GeventedHTTPTransport - Client('...', transport=GeventHTTPTransport) + Client('...', transport=GeventedHTTPTransport) Requests From d6caa99bfdf23f1869b3483049e21f7573fdde97 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 12 Feb 2016 09:48:54 -0800 Subject: [PATCH 207/692] Handle exc_info is True. This fixes #735 --- CHANGES | 1 + raven/base.py | 2 +- tests/base/tests.py | 18 ++++++++++++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 8622dcb82..ff40088a8 100644 --- a/CHANGES +++ b/CHANGES @@ -2,6 +2,7 @@ Version 5.10.3 -------------- * Improved double error check +* Restored support for exc_info is True. Version 5.10.2 -------------- diff --git a/raven/base.py b/raven/base.py index c234d6bb2..fdd96d75a 100644 --- a/raven/base.py +++ b/raven/base.py @@ -724,7 +724,7 @@ def captureException(self, exc_info=None, **kwargs): ``kwargs`` are passed through to ``.capture``. """ - if exc_info is None: + if exc_info is None or exc_info is True: exc_info = sys.exc_info() return self.capture( 'raven.events.Exception', exc_info=exc_info, **kwargs) diff --git a/tests/base/tests.py b/tests/base/tests.py index 685e045d3..690a01b92 100644 --- a/tests/base/tests.py +++ b/tests/base/tests.py @@ -296,6 +296,24 @@ def test_exception_event(self): self.assertEquals(frame['function'], 'test_exception_event') self.assertTrue('timestamp' in event) + def test_exception_event_true_exc_info(self): + try: + raise ValueError('foo') + except ValueError: + self.client.captureException(exc_info=True) + + self.assertEquals(len(self.client.events), 1) + event = self.client.events.pop(0) + self.assertEquals(event['message'], 'ValueError: foo') + self.assertTrue('exception' in event) + exc = event['exception']['values'][0] + stacktrace = exc['stacktrace'] + self.assertEquals(len(stacktrace['frames']), 1) + frame = stacktrace['frames'][0] + self.assertEquals(frame['abs_path'], __file__.replace('.pyc', '.py')) + self.assertEquals(frame['filename'], 'tests/base/tests.py') + self.assertEquals(frame['module'], __name__) + def test_decorator_preserves_function(self): @self.client.capture_exceptions def test1(): From 97ab6beff52e241a96383e60269406d2c9bc44de Mon Sep 17 00:00:00 2001 From: Matt Robenolt Date: Fri, 19 Feb 2016 14:05:34 -0800 Subject: [PATCH 208/692] Fix misleading docstring --- raven/base.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/raven/base.py b/raven/base.py index fdd96d75a..1cbee896e 100644 --- a/raven/base.py +++ b/raven/base.py @@ -656,8 +656,7 @@ def send(self, auth_header=None, **data): def send_encoded(self, message, auth_header=None, **kwargs): """ Given an already serialized message, signs the message and passes the - payload off to ``send_remote`` for each server specified in the servers - configuration. + payload off to ``send_remote``. """ client_string = 'raven-python/%s' % (raven.VERSION,) From 110344408c38c3d642191f46a97b7cba0d7aefc9 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Tue, 23 Feb 2016 16:15:11 -0800 Subject: [PATCH 209/692] Dont implicitly configure log handlers Because of the way logging propagation works this is complicated to ensure it's intelligent. Let's leave it up to the user to ensure LOGGING is configured appropriately for their application. --- CHANGES | 6 ++++++ raven/base.py | 10 ---------- setup.py | 2 +- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/CHANGES b/CHANGES index ff40088a8..dc995a753 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,9 @@ +Version 5.11.0 +-------------- + +* ``Client.configure_logging`` has been removed, and handlers will not automatically + be added to 'sentry' and 'raven' namespaces. + Version 5.10.3 -------------- diff --git a/raven/base.py b/raven/base.py index 1cbee896e..6cceca816 100644 --- a/raven/base.py +++ b/raven/base.py @@ -133,8 +133,6 @@ def __init__(self, dsn=None, raise_send_errors=False, transport=None, o = options - self.configure_logging() - self.raise_send_errors = raise_send_errors # configure loggers first @@ -225,14 +223,6 @@ def handle_exception(*exc_info): def register_scheme(cls, scheme, transport_class): cls._registry.register_scheme(scheme, transport_class) - def configure_logging(self): - for name in ('raven', 'sentry'): - logger = logging.getLogger(name) - if logger.handlers: - continue - logger.addHandler(logging.StreamHandler()) - logger.setLevel(logging.INFO) - def get_processors(self): for processor in self.processors: yield self.module_cache[processor](self) diff --git a/setup.py b/setup.py index 60719ff1a..4df9a04c8 100755 --- a/setup.py +++ b/setup.py @@ -97,7 +97,7 @@ def run_tests(self): setup( name='raven', - version='5.10.3.dev.0', + version='5.11.0.dev.0', author='Sentry', author_email='hello@getsentry.com', url='https://github.com/getsentry/raven-python', From f8e34fa59ca934668b2a6209602b43d51aa02cc2 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 29 Feb 2016 19:46:08 +0100 Subject: [PATCH 210/692] Changed release to 5.11.0 --- CHANGES | 4 ---- 1 file changed, 4 deletions(-) diff --git a/CHANGES b/CHANGES index dc995a753..91e368e95 100644 --- a/CHANGES +++ b/CHANGES @@ -3,10 +3,6 @@ Version 5.11.0 * ``Client.configure_logging`` has been removed, and handlers will not automatically be added to 'sentry' and 'raven' namespaces. - -Version 5.10.3 --------------- - * Improved double error check * Restored support for exc_info is True. From 66ddcabcb8b79849aa1cd9844d8d16d0db781ff8 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 29 Feb 2016 19:46:31 +0100 Subject: [PATCH 211/692] This is 5.11.0 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 4df9a04c8..2a23c0349 100755 --- a/setup.py +++ b/setup.py @@ -97,7 +97,7 @@ def run_tests(self): setup( name='raven', - version='5.11.0.dev.0', + version='5.11.0', author='Sentry', author_email='hello@getsentry.com', url='https://github.com/getsentry/raven-python', From d58722f005e6a883ed17417b2f1a821c1673694b Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 29 Feb 2016 19:47:31 +0100 Subject: [PATCH 212/692] Another dev version --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 2a23c0349..5ba0e503d 100755 --- a/setup.py +++ b/setup.py @@ -97,7 +97,7 @@ def run_tests(self): setup( name='raven', - version='5.11.0', + version='5.11.1.dev.0', author='Sentry', author_email='hello@getsentry.com', url='https://github.com/getsentry/raven-python', From 865b3f9ab459da2bf7e3ba3fce3293ec20346d45 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 7 Mar 2016 21:07:14 +0100 Subject: [PATCH 213/692] Support exceptions without stacktraces. This fixes #741 --- CHANGES | 6 ++++++ raven/base.py | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 91e368e95..93446c4ed 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,9 @@ +Version 5.11.1 +-------------- + +* The raven client supports the stacktrace to be absent. This improves support + with celery and multiprocessing. + Version 5.11.0 -------------- diff --git a/raven/base.py b/raven/base.py index 6cceca816..0b58fed0d 100644 --- a/raven/base.py +++ b/raven/base.py @@ -276,9 +276,9 @@ def _get_exception_key(self, exc_info): return ( exc_info[0], id(exc_info[1]), - id(exc_info[2].tb_frame.f_code), + id(exc_info[2] and exc_info[2].tb_frame.f_code), id(exc_info[2]), - exc_info[2].tb_lasti, + exc_info[2] and exc_info[2].tb_lasti, ) def skip_error_for_logging(self, exc_info): From ed91eef804eb1453ca625270fe1b67e2b687b89b Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 7 Mar 2016 21:07:57 +0100 Subject: [PATCH 214/692] This is 5.11.1 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 5ba0e503d..f47158047 100755 --- a/setup.py +++ b/setup.py @@ -97,7 +97,7 @@ def run_tests(self): setup( name='raven', - version='5.11.1.dev.0', + version='5.11.1', author='Sentry', author_email='hello@getsentry.com', url='https://github.com/getsentry/raven-python', From da688c9a9d306d21f585cece911ff4aa1e3b86fc Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 25 Mar 2016 22:21:42 +0100 Subject: [PATCH 215/692] Added a workaround for bad traceback objects --- CHANGES | 6 ++++++ raven/base.py | 13 +++++++++++-- raven/utils/stacks.py | 4 +++- setup.py | 2 +- 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/CHANGES b/CHANGES index 93446c4ed..0522ffb9c 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,9 @@ +Version 5.11.2 +-------------- + +* Added a workaround for back traceback objects passed to raven. In these + cases we now wobble further along to at least log something. + Version 5.11.1 -------------- diff --git a/raven/base.py b/raven/base.py index 0b58fed0d..fd1ac9f92 100644 --- a/raven/base.py +++ b/raven/base.py @@ -273,12 +273,21 @@ def get_public_dsn(self, scheme=None): return '%s:%s' % (scheme, url) def _get_exception_key(self, exc_info): + # On certain celery versions the tb_frame attribute might + # not exist or be `None`. + code_id = 0 + last_id = 0 + try: + code_id = id(exc_info[2] and exc_info[2].tb_frame.f_code) + last_id = exc_info[2] and exc_info[2].tb_lasti or 0 + except (AttributeError, IndexError): + pass return ( exc_info[0], id(exc_info[1]), - id(exc_info[2] and exc_info[2].tb_frame.f_code), + code_id, id(exc_info[2]), - exc_info[2] and exc_info[2].tb_lasti, + last_id, ) def skip_error_for_logging(self, exc_info): diff --git a/raven/utils/stacks.py b/raven/utils/stacks.py index 518af0126..bcd302384 100644 --- a/raven/utils/stacks.py +++ b/raven/utils/stacks.py @@ -140,7 +140,9 @@ def iter_traceback_frames(tb): frames that do not contain the ``__traceback_hide__`` local variable. """ - while tb: + # Some versions of celery have hacked traceback objects that might + # miss tb_frame. + while tb and hasattr(tb, 'tb_frame'): # support for __traceback_hide__ which is used by a few libraries # to hide internal frames. f_locals = getattr(tb.tb_frame, 'f_locals', {}) diff --git a/setup.py b/setup.py index f47158047..77dd5a582 100755 --- a/setup.py +++ b/setup.py @@ -97,7 +97,7 @@ def run_tests(self): setup( name='raven', - version='5.11.1', + version='5.11.2', author='Sentry', author_email='hello@getsentry.com', url='https://github.com/getsentry/raven-python', From 480d713f8e09c1857feba6336c0ef2dc36632613 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Sun, 27 Mar 2016 17:23:50 -0700 Subject: [PATCH 216/692] Re-raise exceptions with Bottle middleware (fixes GH-744) --- raven/contrib/bottle/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/raven/contrib/bottle/__init__.py b/raven/contrib/bottle/__init__.py index 90e56f28c..d9f845bb7 100644 --- a/raven/contrib/bottle/__init__.py +++ b/raven/contrib/bottle/__init__.py @@ -65,7 +65,8 @@ def session_start_response(status, headers, exc_info=None): # catch ANY exception that goes through... except Exception: self.handle_exception(exc_info=sys.exc_info()) - return self.app(environ, session_start_response) + # re-raise the exception to let parent handlers deal with it + raise def captureException(self, *args, **kwargs): assert self.client, 'captureException called before application configured' From 9b749bfeb4a4e65894c7d726e5c59e6016ea5e36 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 29 Mar 2016 23:01:19 +0200 Subject: [PATCH 217/692] Check for falsy dsn and not None dsn --- raven/base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/raven/base.py b/raven/base.py index fd1ac9f92..98d399d9a 100644 --- a/raven/base.py +++ b/raven/base.py @@ -187,13 +187,13 @@ def __init__(self, dsn=None, raise_send_errors=False, transport=None, self.install_sys_hook() def set_dsn(self, dsn=None, transport=None): - if dsn is None and os.environ.get('SENTRY_DSN'): + if not dsn and os.environ.get('SENTRY_DSN'): msg = "Configuring Raven from environment variable 'SENTRY_DSN'" self.logger.debug(msg) dsn = os.environ['SENTRY_DSN'] if dsn not in self._transport_cache: - if dsn is None: + if not dsn: result = RemoteConfig(transport=transport) else: result = RemoteConfig.from_string( From 3051a17908ff237601f6c3957b70f8518080ed0c Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 29 Mar 2016 23:35:07 +0200 Subject: [PATCH 218/692] Added tests for dns to empty string --- tests/base/tests.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/base/tests.py b/tests/base/tests.py index 690a01b92..83a124259 100644 --- a/tests/base/tests.py +++ b/tests/base/tests.py @@ -6,6 +6,7 @@ import raven import time import six +import os from raven.base import Client, ClientState from raven.exceptions import RateLimited @@ -94,6 +95,15 @@ def test_first_client_is_singleton(self): assert base.Raven is client assert client is not client2 + def test_client_picks_up_env_dsn(self): + DSN = 'sync+http://public:secret@example.com/1' + PUBLIC_DSN = '//public@example.com/1' + with mock.patch.dict(os.environ, {'SENTRY_DSN': DSN}): + client = Client() + assert client.remote.get_public_dsn() == PUBLIC_DSN + client = Client('') + assert client.remote.get_public_dsn() == PUBLIC_DSN + @mock.patch('raven.transport.http.HTTPTransport.send') @mock.patch('raven.base.ClientState.should_try') def test_send_remote_failover(self, should_try, send): From 2be2fd13970854377643a511db27f57bd1352090 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 30 Mar 2016 00:06:44 +0200 Subject: [PATCH 219/692] Added changelog entry for 5.12.0 --- CHANGES | 6 ++++++ raven/context.py | 26 ++++++++++++++++++++++++++ setup.py | 2 +- 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 0522ffb9c..017c69fce 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,9 @@ +Version 5.12.0 +-------------- + +* Empty and otherwise falsy (None, False, 0) DSN values are now assumed + to be equivalent to no DSN being provided. + Version 5.11.2 -------------- diff --git a/raven/context.py b/raven/context.py index b1c3d7b5f..6fd7891ff 100644 --- a/raven/context.py +++ b/raven/context.py @@ -7,12 +7,38 @@ """ from __future__ import absolute_import +import time + from collections import Mapping, Iterable +from datetime import datetime from threading import local from raven._compat import iteritems +class BreadcrumbBuffer(object): + + def __init__(self, limit=100): + self.buffer = [] + self.limit = limit + + def record(self, type, data=None, timestamp=None): + if timestamp is None: + timestamp = time.time() + elif isinstance(timestamp, datetime): + timestamp = datetime + + self.buffer.append({ + 'type': type, + 'timestamp': timestamp, + 'data': data or {}, + }) + del self.buffer[:-self.limit] + + def clear(self): + del self.buffer[:] + + class Context(local, Mapping, Iterable): """ Stores context until cleared. diff --git a/setup.py b/setup.py index 77dd5a582..4e9e32b52 100755 --- a/setup.py +++ b/setup.py @@ -97,7 +97,7 @@ def run_tests(self): setup( name='raven', - version='5.11.2', + version='5.12.0', author='Sentry', author_email='hello@getsentry.com', url='https://github.com/getsentry/raven-python', From ccb9b983a928110dbfb52ec4512b3b06854ae2af Mon Sep 17 00:00:00 2001 From: Dave McLain Date: Tue, 12 Apr 2016 15:03:16 +0000 Subject: [PATCH 220/692] Be more explicit that LOGGING integration only captures ERROR --- docs/integrations/django.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/integrations/django.rst b/docs/integrations/django.rst index 201a827f7..ec06969e1 100644 --- a/docs/integrations/django.rst +++ b/docs/integrations/django.rst @@ -78,8 +78,8 @@ override: Integration with :mod:`logging` ------------------------------- -To integrate with the standard library's :mod:`logging` module the -following config can be used:: +To integrate with the standard library's :mod:`logging` module, and send all +ERROR and above messages to sentry, the following config can be used:: LOGGING = { 'version': 1, @@ -96,7 +96,7 @@ following config can be used:: }, 'handlers': { 'sentry': { - 'level': 'ERROR', + 'level': 'ERROR', # To capture more than ERROR, change to WARNING, INFO, etc. 'class': 'raven.contrib.django.raven_compat.handlers.SentryHandler', 'tags': {'custom-tag': 'x'}, }, From 3c7402198ad503b083802636aa854b77fa8e6ba2 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Tue, 12 Apr 2016 11:01:18 -0700 Subject: [PATCH 221/692] Add .cache to ignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 267294f0e..b761872ac 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,7 @@ pip-log.txt bin/ include/ lib/ +.cache .idea .eggs venv From 1e8aad9a8d5d0e0a399d1571c61824df0eaf6c8f Mon Sep 17 00:00:00 2001 From: David Cramer Date: Tue, 12 Apr 2016 11:01:10 -0700 Subject: [PATCH 222/692] Add sdk attribute --- raven/__init__.py | 10 ++++++---- raven/base.py | 6 ++++++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/raven/__init__.py b/raven/__init__.py index 8bd7cd714..50cef0241 100644 --- a/raven/__init__.py +++ b/raven/__init__.py @@ -9,10 +9,6 @@ import os import os.path -from raven.base import * # NOQA -from raven.conf import * # NOQA -from raven.versioning import * # NOQA - __all__ = ('VERSION', 'Client', 'get_version') @@ -55,3 +51,9 @@ def get_version(): __build__ = get_revision() __docformat__ = 'restructuredtext en' + + +# Declare child imports last to prevent recursion +from raven.base import * # NOQA +from raven.conf import * # NOQA +from raven.versioning import * # NOQA diff --git a/raven/base.py b/raven/base.py index 98d399d9a..b778f48ad 100644 --- a/raven/base.py +++ b/raven/base.py @@ -47,6 +47,11 @@ PLATFORM_NAME = 'python' +SDK_VALUE = { + 'name': 'raven-python', + 'version': raven.VERSION, +} + # singleton for the client Raven = None @@ -429,6 +434,7 @@ def build_msg(self, event_type, data=None, date=None, data.setdefault('time_spent', time_spent) data.setdefault('event_id', event_id) data.setdefault('platform', PLATFORM_NAME) + data.setdefault('sdk', SDK_VALUE) return data From 0a90d8d3592caf2969273f0cff4de906ca8ff7d9 Mon Sep 17 00:00:00 2001 From: Matt Robenolt Date: Tue, 12 Apr 2016 21:29:13 -0700 Subject: [PATCH 223/692] Fix syntax highlighting in docs --- docs/integrations/logging.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/integrations/logging.rst b/docs/integrations/logging.rst index 514d54a74..a6980468c 100644 --- a/docs/integrations/logging.rst +++ b/docs/integrations/logging.rst @@ -137,7 +137,7 @@ Exclusions ~~~~~~~~~~ You can also configure some logging exclusions during setup. These loggers -will not propagate their logs to the Sentry handler. +will not propagate their logs to the Sentry handler:: from raven.conf import setup_logging From 92f8c8f1ba2d3a9341520da2b5d1a7c69090374f Mon Sep 17 00:00:00 2001 From: Marko Mrdjenovic Date: Thu, 14 Apr 2016 12:57:14 +0200 Subject: [PATCH 224/692] make sure send_encoded returns; in tornado.AsyncSentryClient return the future --- raven/base.py | 2 +- raven/contrib/tornado/__init__.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/raven/base.py b/raven/base.py index b778f48ad..dbe8038ab 100644 --- a/raven/base.py +++ b/raven/base.py @@ -682,7 +682,7 @@ def send_encoded(self, message, auth_header=None, **kwargs): 'Content-Type': 'application/octet-stream', } - self.send_remote( + return self.send_remote( url=self.remote.store_endpoint, data=message, headers=headers, diff --git a/raven/contrib/tornado/__init__.py b/raven/contrib/tornado/__init__.py index cb285c381..4d42f36a9 100644 --- a/raven/contrib/tornado/__init__.py +++ b/raven/contrib/tornado/__init__.py @@ -38,9 +38,9 @@ def capture(self, *args, **kwargs): data = self.build_msg(*args, **kwargs) - self.send(callback=kwargs.get('callback', None), **data) + future = self.send(callback=kwargs.get('callback', None), **data) - return (data['event_id'],) + return (data['event_id'], future) def send(self, auth_header=None, callback=None, **data): """ From e6e6a9f3a8df3104e2741976292cd90db86ec51a Mon Sep 17 00:00:00 2001 From: Matt Robenolt Date: Tue, 19 Apr 2016 10:46:38 -0700 Subject: [PATCH 225/692] Catch all exceptions raised from setuptools Newer versions of setuptools have changed the API to be more restrictive and whatnot, and now spits different errors. Instead of whitelisting specific errors, just catch them all so we never have to worry about it again. --- raven/utils/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/utils/__init__.py b/raven/utils/__init__.py index b4916932b..63f1b81d6 100644 --- a/raven/utils/__init__.py +++ b/raven/utils/__init__.py @@ -65,7 +65,7 @@ def get_version_from_app(module_name, app): # pull version from pkg_resources if distro exists try: return pkg_resources.get_distribution(module_name).version - except pkg_resources.DistributionNotFound: + except Exception: pass if hasattr(app, 'get_version'): From 69ab676e6e38844540204af7cd803525fd82b3ed Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 19 Apr 2016 19:50:51 +0200 Subject: [PATCH 226/692] This is 5.13.0 --- CHANGES | 7 +++++++ setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 017c69fce..4a79de707 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,10 @@ +Version 5.13.0 +-------------- + +* Resolved an issue where Raven would fail with an exception if the + package name did not match the setuptools name in some isolated + cases. + Version 5.12.0 -------------- diff --git a/setup.py b/setup.py index 4e9e32b52..34de0a995 100755 --- a/setup.py +++ b/setup.py @@ -97,7 +97,7 @@ def run_tests(self): setup( name='raven', - version='5.12.0', + version='5.13.0', author='Sentry', author_email='hello@getsentry.com', url='https://github.com/getsentry/raven-python', From 5015505043bb5f693fd1bde946bbfe4a74bf83ee Mon Sep 17 00:00:00 2001 From: David Cramer Date: Wed, 20 Apr 2016 15:16:20 -0700 Subject: [PATCH 227/692] Add docs for user feedback to Django --- docs/integrations/django.rst | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/docs/integrations/django.rst b/docs/integrations/django.rst index ec06969e1..38af3a9a6 100644 --- a/docs/integrations/django.rst +++ b/docs/integrations/django.rst @@ -230,6 +230,27 @@ level of your Django application:: application = Sentry(get_wsgi_application()) +User Feedback +------------- + +To enable user feedback for crash reports, you'll simply need to add a bit of +code to your ``500.html`` template: + + .. sourcecode:: html+django + + + + + {% if request.sentry.id %} + + {% endif %} Additional Settings ------------------- From 8054fddfe094816c53fb083e1585ea9802ba1ae2 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Wed, 20 Apr 2016 15:21:24 -0700 Subject: [PATCH 228/692] bad syntax --- 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 38af3a9a6..5e64110d0 100644 --- a/docs/integrations/django.rst +++ b/docs/integrations/django.rst @@ -236,7 +236,7 @@ User Feedback To enable user feedback for crash reports, you'll simply need to add a bit of code to your ``500.html`` template: - .. sourcecode:: html+django +.. sourcecode:: html+django From 41de5fe9fdd76d73f1812517f4eef31a76518308 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Wed, 20 Apr 2016 16:46:49 -0700 Subject: [PATCH 229/692] Add link to User Feedback guide --- docs/integrations/django.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/integrations/django.rst b/docs/integrations/django.rst index 5e64110d0..56dd0ca8b 100644 --- a/docs/integrations/django.rst +++ b/docs/integrations/django.rst @@ -252,6 +252,8 @@ code to your ``500.html`` template: {% endif %} +For more details on this feature, see the :doc:`User Feedback guide <../../../learn/user-feedback>`. + Additional Settings ------------------- From 661121da246f83259b287ca1e67ef5192d5867f9 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 21 Apr 2016 10:32:12 -0700 Subject: [PATCH 230/692] Expand user feedback in Django docs --- docs/integrations/django.rst | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/docs/integrations/django.rst b/docs/integrations/django.rst index 56dd0ca8b..c34b1f43e 100644 --- a/docs/integrations/django.rst +++ b/docs/integrations/django.rst @@ -233,8 +233,18 @@ level of your Django application:: User Feedback ------------- -To enable user feedback for crash reports, you'll simply need to add a bit of -code to your ``500.html`` template: +To enable user feedback for crash reports, start with ensuring the ``request`` +value is available in your context processors: + +.. sourcecode:: python + + TEMPLATE_CONTEXT_PROCESSORS = ( + # ... + 'django.core.context_processors.request', + ) + +By default Django will render ``500.html``, so simply drop the following snippet +into your template: .. sourcecode:: html+django @@ -252,6 +262,8 @@ code to your ``500.html`` template: {% endif %} +That's it! + For more details on this feature, see the :doc:`User Feedback guide <../../../learn/user-feedback>`. Additional Settings From ef48a4aedf965ba2df953756e526208291d2bd0c Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 21 Apr 2016 11:06:38 -0700 Subject: [PATCH 231/692] Add ref for user feedback --- docs/integrations/django.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/integrations/django.rst b/docs/integrations/django.rst index c34b1f43e..28598c21c 100644 --- a/docs/integrations/django.rst +++ b/docs/integrations/django.rst @@ -230,6 +230,8 @@ level of your Django application:: application = Sentry(get_wsgi_application()) +.. _python-django-user-feedback: + User Feedback ------------- From e57f2ce0399dfb0a315c6f22540669d79ecb071f Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 21 Apr 2016 20:33:37 +0200 Subject: [PATCH 232/692] Added user feedback to flask --- docs/integrations/flask.rst | 43 +++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/docs/integrations/flask.rst b/docs/integrations/flask.rst index 8fc4e1ab8..fd29ec91d 100644 --- a/docs/integrations/flask.rst +++ b/docs/integrations/flask.rst @@ -124,6 +124,49 @@ ID if have done a custom error 500 page.

The error identifier is {{ g.sentry_event_id }}

{% endif %} +User Feedback +------------- + +To enable user feedback for crash reports just make sure you have a custom +`500` error handler and render out a HTML snippet for bringing up the +crash dialog: + +.. sourcecode:: python + + from flask import Flask, g, render_template + from raven.contrib.flask import Sentry + + app = Flask(__name__) + sentry = Sentry(app, dsn='___DSN___') + + @app.errorhandler(500 + def internal_server_error(error): + return render_template('500.html', + event_id=g.sentry_event_id, + public_dsn=sentry.client.get_public_dsn('https') + ) + +And in the error template (``500.html``) you can then do this: + +.. sourcecode:: html+jinja + + + + + {% if event_id %} + + {% endif %} + +That's it! + +For more details on this feature, see the :doc:`User Feedback guide +<../../../learn/user-feedback>`. + Dealing With Proxies -------------------- From 6f4f7609a9dcc52a4bbaf2cff9b8a16078a5f8da Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 21 Apr 2016 12:50:43 -0700 Subject: [PATCH 233/692] Add ref for user feedback to flask --- docs/integrations/flask.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/integrations/flask.rst b/docs/integrations/flask.rst index fd29ec91d..da25b69cb 100644 --- a/docs/integrations/flask.rst +++ b/docs/integrations/flask.rst @@ -124,6 +124,8 @@ ID if have done a custom error 500 page.

The error identifier is {{ g.sentry_event_id }}

{% endif %} +.. _python-flask-user-feedback: + User Feedback ------------- From b649751c49b7b6b52977e70817f83a56f1f27e91 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 21 Apr 2016 22:20:40 +0200 Subject: [PATCH 234/692] Update flask.rst --- docs/integrations/flask.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/integrations/flask.rst b/docs/integrations/flask.rst index da25b69cb..97be55347 100644 --- a/docs/integrations/flask.rst +++ b/docs/integrations/flask.rst @@ -141,7 +141,7 @@ crash dialog: app = Flask(__name__) sentry = Sentry(app, dsn='___DSN___') - @app.errorhandler(500 + @app.errorhandler(500) def internal_server_error(error): return render_template('500.html', event_id=g.sentry_event_id, From aba2d7c40f234c999b8c8dffe3692a8ea0afbeee Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 22 Apr 2016 23:42:49 +0200 Subject: [PATCH 235/692] Simplified build matrix --- .travis.yml | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2594ab90e..68655fbe9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,17 +17,17 @@ cache: python: - '2.6' - '2.7' - - '3.2' +# - '3.2' - '3.3' - '3.4' - '3.5' - pypy env: matrix: - - DJANGO=Django==1.4.20 - - DJANGO=Django==1.5.12 +# - DJANGO=Django==1.4.20 +# - DJANGO=Django==1.5.12 - DJANGO=Django==1.6.11 - - DJANGO=Django==1.7.11 +# - DJANGO=Django==1.7.11 - DJANGO=Django==1.8.7 - DJANGO=Django==1.9 - DJANGO="-e git+git://github.com/django/django.git#egg=Django" @@ -43,24 +43,24 @@ 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.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.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.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.7.11 - python: '3.5' env: DJANGO=Django==1.8.7 - python: '2.6' From 719358b8d21024b6a7374683d5377d7354b4ec97 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 22 Apr 2016 23:45:18 +0200 Subject: [PATCH 236/692] Remove git django as well --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 68655fbe9..bf92c9e5c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,7 +30,7 @@ env: # - DJANGO=Django==1.7.11 - DJANGO=Django==1.8.7 - DJANGO=Django==1.9 - - DJANGO="-e git+git://github.com/django/django.git#egg=Django" +# - DJANGO="-e git+git://github.com/django/django.git#egg=Django" install: - time ci/setup - pip install codecov "coverage<4" @@ -40,8 +40,8 @@ script: after_success: - codecov -e DJANGO matrix: - allow_failures: - - env: DJANGO="-e git+git://github.com/django/django.git#egg=Django" +# allow_failures: +# - env: DJANGO="-e git+git://github.com/django/django.git#egg=Django" exclude: # - python: '3.2' # env: DJANGO=Django==1.4.20 From 45bc05f87f982f1c1b9b29b3e0acf700f508c2be Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 20 Apr 2016 23:34:01 +0200 Subject: [PATCH 237/692] Buffer log messages into breadcrumbs. --- raven/base.py | 11 +++++++++++ raven/context.py | 7 +++++++ raven/handlers/logging.py | 26 ++++++++++++++++++++++++-- 3 files changed, 42 insertions(+), 2 deletions(-) diff --git a/raven/base.py b/raven/base.py index dbe8038ab..1edcd6471 100644 --- a/raven/base.py +++ b/raven/base.py @@ -436,6 +436,11 @@ def build_msg(self, event_type, data=None, date=None, data.setdefault('platform', PLATFORM_NAME) data.setdefault('sdk', SDK_VALUE) + # insert breadcrumbs + crumbs = self.context.breadcrumbs.fetch() + if crumbs: + data.setdefault('breadcrumbs', crumbs) + return data def transform(self, data): @@ -797,6 +802,12 @@ def captureExceptions(self, **kwargs): DeprecationWarning) return self.context(**kwargs) + def captureBreadcrumb(self, type, **data): + """Records a breadcrumb.""" + self.context.breadcrumbs.record(type, data) + + capture_breadcrumb = captureBreadcrumb + class DummyClient(Client): "Sends messages into an empty void" diff --git a/raven/context.py b/raven/context.py index 6fd7891ff..bdda36a66 100644 --- a/raven/context.py +++ b/raven/context.py @@ -35,6 +35,11 @@ def record(self, type, data=None, timestamp=None): }) del self.buffer[:-self.limit] + def fetch(self): + rv = self.buffer[:] + self.clear() + return rv + def clear(self): del self.buffer[:] @@ -54,6 +59,7 @@ class Context(local, Mapping, Iterable): def __init__(self): self.data = {} self.exceptions_to_skip = set() + self.breadcrumbs = BreadcrumbBuffer() def __getitem__(self, key): return self.data[key] @@ -86,3 +92,4 @@ def get(self): def clear(self): self.data = {} self.exceptions_to_skip.clear() + self.breadcrumbs.clear() diff --git a/raven/handlers/logging.py b/raven/handlers/logging.py index cc82550c4..464b4222c 100644 --- a/raven/handlers/logging.py +++ b/raven/handlers/logging.py @@ -28,6 +28,7 @@ class SentryHandler(logging.Handler, object): def __init__(self, *args, **kwargs): + self.enable_breadcrumbs = not kwargs.pop('disable_breadcrumbs', False) client = kwargs.get('client_cls', Client) if len(args) == 1: arg = args[0] @@ -54,6 +55,13 @@ def can_record(self, record): record.name.startswith(('sentry.errors', 'raven.')) ) + def is_breadcrumb(self, record): + if not self.enable_breadcrumbs: + return False + if record.exc_info is not None: + return False + return record.levelno < logging.ERROR + def emit(self, record): try: # Beware to python3 bug (see #10805) if exc_info is (None, None, None) @@ -63,7 +71,10 @@ def emit(self, record): print(to_string(record.message), file=sys.stderr) return - return self._emit(record) + if self.is_breadcrumb(record): + self._emit_crumb(record) + else: + return self._emit_event(record) except Exception: if self.client.raise_send_errors: raise @@ -103,7 +114,18 @@ def _get_targetted_stack(self, stack, record): return frames - def _emit(self, record, **kwargs): + def _emit_crumb(self, record): + try: + msg = text_type(record.msg) + except UnicodeDecodeError: + msg = repr(record.msg)[1:-1] + self.client.record_breadcrumb('message', + message=msg, + level=record.level, + logger=record.name + ) + + def _emit_event(self, record, **kwargs): data = {} extra = getattr(record, 'data', None) From a1df81e050bd7e81a55dd2cf56cdc91ddcf0245d Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 21 Apr 2016 20:02:26 +0200 Subject: [PATCH 238/692] Fetch -> get_buffer --- raven/base.py | 2 +- raven/context.py | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/raven/base.py b/raven/base.py index 1edcd6471..4c1335845 100644 --- a/raven/base.py +++ b/raven/base.py @@ -437,7 +437,7 @@ def build_msg(self, event_type, data=None, date=None, data.setdefault('sdk', SDK_VALUE) # insert breadcrumbs - crumbs = self.context.breadcrumbs.fetch() + crumbs = self.context.breadcrumbs.get_buffer() if crumbs: data.setdefault('breadcrumbs', crumbs) diff --git a/raven/context.py b/raven/context.py index bdda36a66..1a8bd8938 100644 --- a/raven/context.py +++ b/raven/context.py @@ -35,14 +35,12 @@ def record(self, type, data=None, timestamp=None): }) del self.buffer[:-self.limit] - def fetch(self): - rv = self.buffer[:] - self.clear() - return rv - def clear(self): del self.buffer[:] + def get_buffer(self): + return self.buffer[:] + class Context(local, Mapping, Iterable): """ From e304edaec1629a1a795a43e17f63ecc8bb3596f8 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 22 Apr 2016 19:07:35 +0200 Subject: [PATCH 239/692] Hook logging globally to intercept breadcrumbs. --- raven/_compat.py | 7 ++ raven/base.py | 30 ++++++-- raven/breadcrumbs.py | 152 ++++++++++++++++++++++++++++++++++++++ raven/context.py | 75 ++++++++++++------- raven/handlers/logging.py | 25 +------ 5 files changed, 233 insertions(+), 56 deletions(-) create mode 100644 raven/breadcrumbs.py diff --git a/raven/_compat.py b/raven/_compat.py index bd5c5e79d..3d5e28de7 100644 --- a/raven/_compat.py +++ b/raven/_compat.py @@ -175,3 +175,10 @@ def exec_(_code_, _globs_=None, _locs_=None): def with_metaclass(meta, base=object): """Create a base class with a metaclass.""" return meta("NewBase", (base,), {}) + + +def get_code(func): + rv = getattr(func, '__code__', getattr(func, 'func_code', None)) + if rv is None: + raise TypeError('Could not get code from %r' % type(func).__name__) + return rv diff --git a/raven/base.py b/raven/base.py index 4c1335845..bfc353644 100644 --- a/raven/base.py +++ b/raven/base.py @@ -28,7 +28,6 @@ import raven from raven.conf import defaults from raven.conf.remote import RemoteConfig -from raven.context import Context from raven.exceptions import APIError, RateLimited from raven.utils import json, get_versions, get_auth_header, merge_dicts from raven._compat import text_type, iteritems @@ -133,7 +132,8 @@ class Client(object): _registry = TransportRegistry(transports=default_transports) def __init__(self, dsn=None, raise_send_errors=False, transport=None, - install_sys_hook=True, **options): + install_sys_hook=True, install_logging_hook=True, + **options): global Raven o = options @@ -186,11 +186,20 @@ def __init__(self, dsn=None, raise_send_errors=False, transport=None, if Raven is None: Raven = self - self._context = Context() + from raven.context import Context + self._context = Context(self) + + # We always activate the context for the calling thread. This + # means even in the absence of code that activates the context for + # threads otherwise. + self._context.activate() if install_sys_hook: self.install_sys_hook() + if install_logging_hook: + self.install_logging_hook() + def set_dsn(self, dsn=None, transport=None): if not dsn and os.environ.get('SENTRY_DSN'): msg = "Configuring Raven from environment variable 'SENTRY_DSN'" @@ -224,6 +233,10 @@ def handle_exception(*exc_info): __excepthook__(*exc_info) sys.excepthook = handle_exception + def install_logging_hook(self): + from raven.breadcrumbs import install_logging_hook + install_logging_hook() + @classmethod def register_scheme(cls, scheme, transport_class): cls._registry.register_scheme(scheme, transport_class) @@ -802,9 +815,14 @@ def captureExceptions(self, **kwargs): DeprecationWarning) return self.context(**kwargs) - def captureBreadcrumb(self, type, **data): - """Records a breadcrumb.""" - self.context.breadcrumbs.record(type, data) + def captureBreadcrumb(self, type, *args, **kwargs): + """Records a breadcrumb with the current context. They will be + sent with the next event. + """ + # Note: framework integration should not call this method but + # instead use the raven.breadcrumbs.record_breadcrumb function + # which will record to the correct client automatically. + self.context.breadcrumbs.record(type, dict(*args, **kwargs)) capture_breadcrumb = captureBreadcrumb diff --git a/raven/breadcrumbs.py b/raven/breadcrumbs.py new file mode 100644 index 000000000..d744e21e6 --- /dev/null +++ b/raven/breadcrumbs.py @@ -0,0 +1,152 @@ +import time +import logging +from threading import Lock +from datetime import datetime +from types import FunctionType + +from raven._compat import iteritems, get_code, text_type + + +_logging_lock = Lock() +_logging_hooked = False + + +class BreadcrumbBuffer(object): + + def __init__(self, limit=100): + self.buffer = [] + self.limit = limit + + def record(self, type, data=None, timestamp=None): + if timestamp is None: + timestamp = time.time() + elif isinstance(timestamp, datetime): + timestamp = datetime + + self.buffer.append({ + 'type': type, + 'timestamp': timestamp, + 'data': data or {}, + }) + del self.buffer[:-self.limit] + + def clear(self): + del self.buffer[:] + + def get_buffer(self): + return self.buffer[:] + + +def record_breadcrumb(type, data, timestamp=None): + if timestamp is None: + timestamp = time.time() + for ctx in raven.context.get_active_contexts(): + ctx.breadcrumbs.record(type, data, timestamp) + + +def _record_log_breadcrumb(logger, level, msg, *args, **kwargs): + formatted_msg = text_type(msg) + if args: + formatted_msg = msg % args + record_breadcrumb('message', { + 'message': formatted_msg, + 'logger': logger.name, + 'level': logging.getLevelName(level).lower(), + }) + + +def _wrap_logging_method(meth, level=None): + if not isinstance(meth, FunctionType): + func = meth.im_func + else: + func = meth + + # We were patched for raven before + if getattr(func, '__patched_for_raven__', False): + return + + if level is None: + args = ('level', 'msg') + fwd = 'level, msg' + else: + args = ('msg',) + fwd = '%d, msg' % level + + code = get_code(func) + + # This requires a bit of explanation why we're doing this. Due to how + # logging itself works we need to pretend that the method actually was + # created within the logging module. There are a few ways to detect + # this and we fake all of them: we use the same function globals (the + # one from the logging module), we create it entirely there which + # means that also the filename is set correctly. This fools the + # detection code in logging and it makes logging itself skip past our + # code when determining the code location. + # + # Because we point the globals to the logging module we now need to + # refer to our own functions (original and the crumb recording + # function) through a closure instead of the global scope. + # + # We also add a lot of newlines in front of the code so that the + # code location lines up again in case someone runs inspect.getsource + # on the function. + ns = {} + eval(compile('''%(offset)sif 1: + def factory(original, record_crumb): + def %(name)s(self, %(args)s, *args, **kwargs): + record_crumb(self, %(fwd)s, *args, **kwargs) + return original(self, %(args)s, *args, **kwargs) + return %(name)s + ''' % { + 'offset': '\n' * (code.co_firstlineno - 3), + 'name': func.__name__, + 'args': ', '.join(args), + 'fwd': fwd, + 'level': level, + }, logging.__file__, 'exec'), logging.__dict__, ns) + + new_func = ns['factory'](meth, _record_log_breadcrumb) + 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__ + + return new_func + + +def _patch_logger(): + cls = logging.Logger + + methods = { + 'debug': logging.DEBUG, + 'info': logging.INFO, + 'warning': logging.WARNING, + 'warn': logging.WARN, + 'error': logging.ERROR, + 'exception': logging.ERROR, + 'critical': logging.CRITICAL, + 'fatal': logging.FATAL + } + + for method_name, level in iteritems(methods): + new_func = _wrap_logging_method( + getattr(cls, method_name), level) + setattr(logging.Logger, method_name, new_func) + + logging.Logger.log = _wrap_logging_method( + logging.Logger.log) + + +def install_logging_hook(): + global _logging_hooked + if _logging_hooked: + return + with _logging_lock: + if _logging_hooked: + return + _patch_logger() + _logging_hooked = True + + +import raven.context diff --git a/raven/context.py b/raven/context.py index 1a8bd8938..d3ad4a34e 100644 --- a/raven/context.py +++ b/raven/context.py @@ -7,39 +7,22 @@ """ from __future__ import absolute_import -import time - from collections import Mapping, Iterable -from datetime import datetime from threading import local +from weakref import ref as weakref from raven._compat import iteritems -class BreadcrumbBuffer(object): - - def __init__(self, limit=100): - self.buffer = [] - self.limit = limit - - def record(self, type, data=None, timestamp=None): - if timestamp is None: - timestamp = time.time() - elif isinstance(timestamp, datetime): - timestamp = datetime - - self.buffer.append({ - 'type': type, - 'timestamp': timestamp, - 'data': data or {}, - }) - del self.buffer[:-self.limit] +_active_contexts = local() - def clear(self): - del self.buffer[:] - def get_buffer(self): - return self.buffer[:] +def get_active_contexts(): + """Returns all the active contexts for the current thread.""" + try: + return list(_active_contexts.contexts) + except AttributeError: + return [] class Context(local, Mapping, Iterable): @@ -54,10 +37,28 @@ class Context(local, Mapping, Iterable): >>> finally: >>> context.clear() """ - def __init__(self): + + def __init__(self, client): + self._client = weakref(client) self.data = {} self.exceptions_to_skip = set() - self.breadcrumbs = BreadcrumbBuffer() + self.breadcrumbs = raven.breadcrumbs.BreadcrumbBuffer() + + @property + def client(self): + rv = self._client() + if rv is None: + raise AttributeError('client') + return rv + + def __hash__(self): + return id(self) + + def __eq__(self, other): + return self is other + + def __ne__(self, other): + return not self.__eq__(other) def __getitem__(self, key): return self.data[key] @@ -71,6 +72,23 @@ def __len__(self): def __repr__(self): return '<%s: %s>' % (type(self).__name__, self.data) + def __enter__(self): + self.activate() + return self + + def __exit__(self, exc_type, exc_value, tb): + self.deactivate() + + def activate(self): + _active_contexts.__dict__.setdefault('contexts', set()).add(self) + + def deactivate(self): + self.clear() + try: + _active_contexts.contexts.discard(self) + except AttributeError: + pass + def merge(self, data): d = self.data for key, value in iteritems(data): @@ -91,3 +109,6 @@ def clear(self): self.data = {} self.exceptions_to_skip.clear() self.breadcrumbs.clear() + + +import raven.breadcrumbs diff --git a/raven/handlers/logging.py b/raven/handlers/logging.py index 464b4222c..fbe0e24e8 100644 --- a/raven/handlers/logging.py +++ b/raven/handlers/logging.py @@ -55,13 +55,6 @@ def can_record(self, record): record.name.startswith(('sentry.errors', 'raven.')) ) - def is_breadcrumb(self, record): - if not self.enable_breadcrumbs: - return False - if record.exc_info is not None: - return False - return record.levelno < logging.ERROR - def emit(self, record): try: # Beware to python3 bug (see #10805) if exc_info is (None, None, None) @@ -71,10 +64,7 @@ def emit(self, record): print(to_string(record.message), file=sys.stderr) return - if self.is_breadcrumb(record): - self._emit_crumb(record) - else: - return self._emit_event(record) + return self._emit(record) except Exception: if self.client.raise_send_errors: raise @@ -114,18 +104,7 @@ def _get_targetted_stack(self, stack, record): return frames - def _emit_crumb(self, record): - try: - msg = text_type(record.msg) - except UnicodeDecodeError: - msg = repr(record.msg)[1:-1] - self.client.record_breadcrumb('message', - message=msg, - level=record.level, - logger=record.name - ) - - def _emit_event(self, record, **kwargs): + def _emit(self, record, **kwargs): data = {} extra = getattr(record, 'data', None) From 7ec5c50c171c8f84d9217ccf8dc5cf031fa0600b Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 22 Apr 2016 19:26:58 +0200 Subject: [PATCH 240/692] Some performance improvements on the breadcrumb system --- raven/breadcrumbs.py | 53 ++++++++++++++++++++++++++------------------ raven/context.py | 13 ++++++----- 2 files changed, 39 insertions(+), 27 deletions(-) diff --git a/raven/breadcrumbs.py b/raven/breadcrumbs.py index d744e21e6..1d8b152a0 100644 --- a/raven/breadcrumbs.py +++ b/raven/breadcrumbs.py @@ -1,7 +1,6 @@ import time import logging from threading import Lock -from datetime import datetime from types import FunctionType from raven._compat import iteritems, get_code, text_type @@ -20,24 +19,34 @@ def __init__(self, limit=100): def record(self, type, data=None, timestamp=None): if timestamp is None: timestamp = time.time() - elif isinstance(timestamp, datetime): - timestamp = datetime - - self.buffer.append({ - 'type': type, - 'timestamp': timestamp, - 'data': data or {}, - }) + self.buffer.append((type, timestamp, data)) del self.buffer[:-self.limit] def clear(self): del self.buffer[:] def get_buffer(self): - return self.buffer[:] - - -def record_breadcrumb(type, data, timestamp=None): + rv = [] + for type, timestamp, data in self.buffer: + if data is None: + data = {} + elif callable(data): + data = data() + rv.append({ + 'type': type, + 'data': data, + 'timestamp': timestamp, + }) + return rv + + +def record_breadcrumb(type, data=None, timestamp=None): + """Records a breadcrumb for all active clients. This is what integration + code should use rather than invoking the `captureBreadcrumb` method + on a specific client. This also additionally permits data to be a + callable that will be invoked to generate the data if the crumb is not + discarded. + """ if timestamp is None: timestamp = time.time() for ctx in raven.context.get_active_contexts(): @@ -45,14 +54,16 @@ def record_breadcrumb(type, data, timestamp=None): def _record_log_breadcrumb(logger, level, msg, *args, **kwargs): - formatted_msg = text_type(msg) - if args: - formatted_msg = msg % args - record_breadcrumb('message', { - 'message': formatted_msg, - 'logger': logger.name, - 'level': logging.getLevelName(level).lower(), - }) + def _make_data(): + formatted_msg = text_type(msg) + if args: + formatted_msg = msg % args + return { + 'message': formatted_msg, + 'logger': logger.name, + 'level': logging.getLevelName(level).lower(), + } + record_breadcrumb('message', _make_data) def _wrap_logging_method(meth, level=None): diff --git a/raven/context.py b/raven/context.py index d3ad4a34e..9a66b98d5 100644 --- a/raven/context.py +++ b/raven/context.py @@ -38,18 +38,19 @@ class Context(local, Mapping, Iterable): >>> context.clear() """ - def __init__(self, client): - self._client = weakref(client) + def __init__(self, client=None): + if client is not None: + client = weakref(client) + self._client = client self.data = {} self.exceptions_to_skip = set() self.breadcrumbs = raven.breadcrumbs.BreadcrumbBuffer() @property def client(self): - rv = self._client() - if rv is None: - raise AttributeError('client') - return rv + if self._client is None: + return None + return self._client() def __hash__(self): return id(self) From ed9dfc30944ad52be35d4375b450da710b29b03f Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 22 Apr 2016 20:01:02 +0200 Subject: [PATCH 241/692] Remove unused code --- raven/handlers/logging.py | 1 - 1 file changed, 1 deletion(-) diff --git a/raven/handlers/logging.py b/raven/handlers/logging.py index fbe0e24e8..cc82550c4 100644 --- a/raven/handlers/logging.py +++ b/raven/handlers/logging.py @@ -28,7 +28,6 @@ class SentryHandler(logging.Handler, object): def __init__(self, *args, **kwargs): - self.enable_breadcrumbs = not kwargs.pop('disable_breadcrumbs', False) client = kwargs.get('client_cls', Client) if len(args) == 1: arg = args[0] From 4e376bdc6fe829c264d0ae69d7de00497ffe1e1f Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 22 Apr 2016 20:01:16 +0200 Subject: [PATCH 242/692] Add @once decorator instead of custom hackery in breadcrumbs --- raven/breadcrumbs.py | 19 ++++++------------- raven/utils/__init__.py | 21 +++++++++++++++++++++ 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/raven/breadcrumbs.py b/raven/breadcrumbs.py index 1d8b152a0..c8b133148 100644 --- a/raven/breadcrumbs.py +++ b/raven/breadcrumbs.py @@ -1,13 +1,9 @@ import time import logging -from threading import Lock from types import FunctionType from raven._compat import iteritems, get_code, text_type - - -_logging_lock = Lock() -_logging_hooked = False +from raven.utils import once class BreadcrumbBuffer(object): @@ -149,15 +145,12 @@ def _patch_logger(): logging.Logger.log) +@once def install_logging_hook(): - global _logging_hooked - if _logging_hooked: - return - with _logging_lock: - if _logging_hooked: - return - _patch_logger() - _logging_hooked = True + """Installs the logging hook if it was not installed yet. Otherwise + does nothing. + """ + _patch_logger() import raven.context diff --git a/raven/utils/__init__.py b/raven/utils/__init__.py index 63f1b81d6..c22f0bc8e 100644 --- a/raven/utils/__init__.py +++ b/raven/utils/__init__.py @@ -9,6 +9,8 @@ from raven._compat import iteritems, string_types import logging +import threading +from functools import update_wrapper try: import pkg_resources except ImportError: @@ -167,3 +169,22 @@ def __get__(self, obj, type=None): if n not in d: d[n] = self.func(obj) return d[n] + + +def once(func): + """Runs a thing once and once only.""" + lock = threading.Lock() + + def new_func(*args, **kwargs): + if new_func.called: + return + with lock: + if new_func.called: + return + rv = func(*args, **kwargs) + new_func.called = True + return rv + + new_func = update_wrapper(new_func, func) + new_func.called = False + return new_func From 84e1559c6b3d665e40fbffd04782c7b20538bfe2 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 22 Apr 2016 20:01:28 +0200 Subject: [PATCH 243/692] Compile with trailing newline which might fix some syntax errors --- raven/breadcrumbs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/raven/breadcrumbs.py b/raven/breadcrumbs.py index c8b133148..f79ab75ea 100644 --- a/raven/breadcrumbs.py +++ b/raven/breadcrumbs.py @@ -98,13 +98,13 @@ def _wrap_logging_method(meth, level=None): # code location lines up again in case someone runs inspect.getsource # on the function. ns = {} - eval(compile('''%(offset)sif 1: + eval(compile(('''%(offset)sif 1: def factory(original, record_crumb): def %(name)s(self, %(args)s, *args, **kwargs): record_crumb(self, %(fwd)s, *args, **kwargs) return original(self, %(args)s, *args, **kwargs) return %(name)s - ''' % { + ''' + '\n') % { 'offset': '\n' * (code.co_firstlineno - 3), 'name': func.__name__, 'args': ', '.join(args), From 07bf7a16a5f3bd46e9d7bc61bc42f025e58661a1 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 22 Apr 2016 20:23:19 +0200 Subject: [PATCH 244/692] Added missing absolute import --- raven/breadcrumbs.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/raven/breadcrumbs.py b/raven/breadcrumbs.py index f79ab75ea..02c7c15ed 100644 --- a/raven/breadcrumbs.py +++ b/raven/breadcrumbs.py @@ -1,3 +1,5 @@ +from __future__ import absolute_import + import time import logging from types import FunctionType From fd497197eae2db7af541a55a4c162710ab134326 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 22 Apr 2016 20:29:05 +0200 Subject: [PATCH 245/692] Nicer way to terminate with a newline --- raven/breadcrumbs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/raven/breadcrumbs.py b/raven/breadcrumbs.py index 02c7c15ed..f34032b91 100644 --- a/raven/breadcrumbs.py +++ b/raven/breadcrumbs.py @@ -100,13 +100,13 @@ def _wrap_logging_method(meth, level=None): # code location lines up again in case someone runs inspect.getsource # on the function. ns = {} - eval(compile(('''%(offset)sif 1: + eval(compile('''%(offset)sif 1: def factory(original, record_crumb): def %(name)s(self, %(args)s, *args, **kwargs): record_crumb(self, %(fwd)s, *args, **kwargs) return original(self, %(args)s, *args, **kwargs) return %(name)s - ''' + '\n') % { + \n''' % { 'offset': '\n' * (code.co_firstlineno - 3), 'name': func.__name__, 'args': ', '.join(args), From 37b01934a23bdcd033b47941b398d3006a3bb22b Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 22 Apr 2016 21:36:23 +0200 Subject: [PATCH 246/692] Mark patched functions so we skip things --- raven/breadcrumbs.py | 1 + 1 file changed, 1 insertion(+) diff --git a/raven/breadcrumbs.py b/raven/breadcrumbs.py index f34032b91..0ecec4829 100644 --- a/raven/breadcrumbs.py +++ b/raven/breadcrumbs.py @@ -120,6 +120,7 @@ def %(name)s(self, %(args)s, *args, **kwargs): assert code.co_firstlineno == get_code(func).co_firstlineno assert new_func.__module__ == func.__module__ assert new_func.__name__ == func.__name__ + new_func.__patched_for_raven__ = True return new_func From e1e0f1ce3ed9d5a3e349773a45f29d2db246cb5a Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 22 Apr 2016 22:16:02 +0200 Subject: [PATCH 247/692] Added support for overriding log handlers for breadcrumbs. --- raven/breadcrumbs.py | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/raven/breadcrumbs.py b/raven/breadcrumbs.py index 0ecec4829..51b438647 100644 --- a/raven/breadcrumbs.py +++ b/raven/breadcrumbs.py @@ -4,10 +4,13 @@ import logging from types import FunctionType -from raven._compat import iteritems, get_code, text_type +from raven._compat import iteritems, get_code, text_type, string_types from raven.utils import once +special_logger_handlers = {} + + class BreadcrumbBuffer(object): def __init__(self, limit=100): @@ -52,6 +55,12 @@ def record_breadcrumb(type, data=None, timestamp=None): def _record_log_breadcrumb(logger, level, msg, *args, **kwargs): + handler = special_logger_handlers.get(logger.name) + if handler is not None: + rv = handler(logger, level, msg, args, kwargs) + if rv: + return + def _make_data(): formatted_msg = text_type(msg) if args: @@ -156,4 +165,25 @@ def install_logging_hook(): _patch_logger() +def ignore_logger(name_or_logger): + """Ignores a logger for the regular breadcrumb code. This is useful + for framework integration code where some log messages should be + specially handled. + """ + register_special_log_handler(name_or_logger, lambda *args: True) + + +def register_special_log_handler(name_or_logger, 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. + """ + if isinstance(name_or_logger, string_types): + name = name_or_logger + else: + name = name_or_logger.name + special_logger_handlers[name] = callback + + import raven.context From 0c846afc2c4310981d0736b841035f22b82059a3 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 22 Apr 2016 22:28:11 +0200 Subject: [PATCH 248/692] Hook Django SQL code --- raven/contrib/django/client.py | 47 ++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/raven/contrib/django/client.py b/raven/contrib/django/client.py index 64ece8a77..9d089f778 100644 --- a/raven/contrib/django/client.py +++ b/raven/contrib/django/client.py @@ -8,6 +8,7 @@ from __future__ import absolute_import +import time import logging from django.conf import settings @@ -26,13 +27,59 @@ from raven.contrib.django.utils import get_data_from_template, get_host from raven.contrib.django.middleware import SentryLogMiddleware from raven.utils.wsgi import get_headers, get_environ +from raven.utils import once +from raven import breadcrumbs __all__ = ('DjangoClient',) +@once +def install_sql_hook(): + """If installed this causes Django's queries to be captured.""" + from django.db.backends.utils import CursorWrapper + + def record_sql(start, sql, params): + breadcrumbs.record_breadcrumb('query', { + 'query': sql, + 'params': params, + 'duration': time.time() - start, + 'classifier': 'django.db' + }) + + real_execute = CursorWrapper.execute + real_executemany = CursorWrapper.executemany + + def execute(self, sql, params=None): + start = time.time() + try: + return real_execute(self, sql, params) + finally: + record_sql(start, sql, params) + + def executemany(self, sql, param_list): + start = time.time() + try: + return real_executemany(self, sql, param_list) + finally: + record_sql(start, sql, param_list) + + CursorWrapper.execute = execute + CursorWrapper.executemany = executemany + breadcrumbs.ignore_logger('django.db.backends') + + class DjangoClient(Client): logger = logging.getLogger('sentry.errors.client.django') + def __init__(self, *args, **kwargs): + install_sql_hook = kwargs.pop('install_sql_hook', True) + Client.__init__(self, *args, **kwargs) + if install_sql_hook: + self.install_sql_hook() + + def install_sql_hook(self): + install_sql_hook() + def get_user_info(self, user): if hasattr(user, 'is_authenticated') and \ not user.is_authenticated(): From f86bdd9900343fd99c1af1d1418ce26a4d42c955 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 22 Apr 2016 22:30:39 +0200 Subject: [PATCH 249/692] Also support older django versions for sql hook --- raven/contrib/django/client.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/raven/contrib/django/client.py b/raven/contrib/django/client.py index 9d089f778..8058ec298 100644 --- a/raven/contrib/django/client.py +++ b/raven/contrib/django/client.py @@ -36,7 +36,10 @@ @once def install_sql_hook(): """If installed this causes Django's queries to be captured.""" - from django.db.backends.utils import CursorWrapper + try: + from django.db.backends.utils import CursorWrapper + except ImportError: + from django.db.backends.util import CursorWrapper def record_sql(start, sql, params): breadcrumbs.record_breadcrumb('query', { From 9416b79979ecce1aa2c0b18930a9fd87cd7f0432 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 22 Apr 2016 23:38:57 +0200 Subject: [PATCH 250/692] Fixed up context handling for django to make breadcrumbs work --- raven/context.py | 5 +++-- raven/contrib/django/models.py | 6 +++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/raven/context.py b/raven/context.py index 9a66b98d5..cb2efbff9 100644 --- a/raven/context.py +++ b/raven/context.py @@ -84,7 +84,6 @@ def activate(self): _active_contexts.__dict__.setdefault('contexts', set()).add(self) def deactivate(self): - self.clear() try: _active_contexts.contexts.discard(self) except AttributeError: @@ -106,10 +105,12 @@ def set(self, data): def get(self): return self.data - def clear(self): + def clear(self, deactivate=True): self.data = {} self.exceptions_to_skip.clear() self.breadcrumbs.clear() + if deactivate: + self.deactivate() import raven.breadcrumbs diff --git a/raven/contrib/django/models.py b/raven/contrib/django/models.py index b2014c946..490978a99 100644 --- a/raven/contrib/django/models.py +++ b/raven/contrib/django/models.py @@ -181,7 +181,11 @@ def sentry_exception_handler(request=None, **kwargs): def register_handlers(): - from django.core.signals import got_request_exception + from django.core.signals import got_request_exception, request_started + + def before_request(*args, **kwargs): + client.context.activate() + request_started.connect(before_request, weak=False) # HACK: support Sentry's internal communication if 'sentry' in settings.INSTALLED_APPS: From 469f985602270efd4282ccfc49424edb6a2afe8d Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 22 Apr 2016 23:50:48 +0200 Subject: [PATCH 251/692] Fix breakage for older django versions --- raven/contrib/django/client.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/raven/contrib/django/client.py b/raven/contrib/django/client.py index 8058ec298..a45102d58 100644 --- a/raven/contrib/django/client.py +++ b/raven/contrib/django/client.py @@ -41,6 +41,14 @@ def install_sql_hook(): except ImportError: from django.db.backends.util import CursorWrapper + try: + real_execute = CursorWrapper.execute + real_executemany = CursorWrapper.executemany + except AttributeError: + # XXX(mitsuhiko): On some very old django versions (<1.6) this + # trickery would have to look different but I can't be bothered. + return + def record_sql(start, sql, params): breadcrumbs.record_breadcrumb('query', { 'query': sql, @@ -49,9 +57,6 @@ def record_sql(start, sql, params): 'classifier': 'django.db' }) - real_execute = CursorWrapper.execute - real_executemany = CursorWrapper.executemany - def execute(self, sql, params=None): start = time.time() try: From d4053050f5a2edc84cd386121872a15b6811a8f4 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 25 Apr 2016 16:33:14 +0200 Subject: [PATCH 252/692] Detect disabled threads for uwsgi --- raven/transport/threaded.py | 3 ++- raven/utils/compat.py | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/raven/transport/threaded.py b/raven/transport/threaded.py index b46e78673..7bab84568 100644 --- a/raven/transport/threaded.py +++ b/raven/transport/threaded.py @@ -16,7 +16,7 @@ from raven.transport.base import AsyncTransport from raven.transport.http import HTTPTransport -from raven.utils.compat import Queue +from raven.utils.compat import Queue, check_threads DEFAULT_TIMEOUT = 10 @@ -27,6 +27,7 @@ class AsyncWorker(object): _terminator = object() def __init__(self, shutdown_timeout=DEFAULT_TIMEOUT): + check_threads() self._queue = Queue(-1) self._lock = threading.Lock() self._thread = None diff --git a/raven/utils/compat.py b/raven/utils/compat.py index e00b49984..5b31a8e28 100644 --- a/raven/utils/compat.py +++ b/raven/utils/compat.py @@ -46,3 +46,17 @@ from urllib import parse as _urlparse # NOQA urlparse = _urlparse + + +def check_threads(): + try: + from uwsgi import opt + except ImportError: + return + + if str(opt.get('enable-threads', '0')).lower() in ('false', 'off', 'no', '0'): + from warnings import warn + warn(Warning('We detected the use of uwsgi with disabled threads. ' + 'This will cause issues with the transport you are ' + 'trying to use. Please enable threading for uwsgi. ' + '(Enable the "enable-threads" flag).')) From 8122c92203d852ebeeb6d8a89d9bae13c9fc265e Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 26 Apr 2016 19:37:58 +0200 Subject: [PATCH 253/692] Record db vendor as classifier for django breadcrumbs --- raven/contrib/django/client.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/raven/contrib/django/client.py b/raven/contrib/django/client.py index a45102d58..341bd6a5e 100644 --- a/raven/contrib/django/client.py +++ b/raven/contrib/django/client.py @@ -49,12 +49,12 @@ def install_sql_hook(): # trickery would have to look different but I can't be bothered. return - def record_sql(start, sql, params): + def record_sql(vendor, start, sql, params): breadcrumbs.record_breadcrumb('query', { 'query': sql, 'params': params, 'duration': time.time() - start, - 'classifier': 'django.db' + 'classifier': 'django.db.%s' % vendor }) def execute(self, sql, params=None): @@ -62,14 +62,14 @@ def execute(self, sql, params=None): try: return real_execute(self, sql, params) finally: - record_sql(start, sql, params) + record_sql(self.db.vendor, start, sql, params) def executemany(self, sql, param_list): start = time.time() try: return real_executemany(self, sql, param_list) finally: - record_sql(start, sql, param_list) + record_sql(self.db.vendor, start, sql, param_list) CursorWrapper.execute = execute CursorWrapper.executemany = executemany From f1347309362376b695083d8bbbbc81e78b080bb3 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 27 Apr 2016 09:18:00 +0200 Subject: [PATCH 254/692] Reformat sql for breadcrumbs --- raven/contrib/django/client.py | 63 +++++++++++++++++++++++++++++----- 1 file changed, 54 insertions(+), 9 deletions(-) diff --git a/raven/contrib/django/client.py b/raven/contrib/django/client.py index 341bd6a5e..c7a0d9e73 100644 --- a/raven/contrib/django/client.py +++ b/raven/contrib/django/client.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """ raven.contrib.django.client ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -29,10 +30,45 @@ from raven.utils.wsgi import get_headers, get_environ from raven.utils import once from raven import breadcrumbs +from raven._compat import string_types, binary_type __all__ = ('DjangoClient',) +class _FormatConverter(object): + + def __init__(self, param_mapping): + self.param_mapping = param_mapping + self.params = [] + + def __getitem__(self, val): + self.params.append(self.param_mapping.get(val)) + return '%s' + + +def format_sql(sql, params): + rv = [] + + if isinstance(params, dict): + conv = _FormatConverter(params) + sql = sql % conv + params = conv.params + + for param in params: + if param is None: + rv.append('NULL') + elif isinstance(param, string_types): + if isinstance(param, binary_type): + param = param.decode('utf-8', 'replace') + if len(param) > 256: + param = param[:256] + u'…' + rv.append("'%s'" % param.replace("'", "''")) + else: + rv.append(repr(param)) + + return sql, rv + + @once def install_sql_hook(): """If installed this causes Django's queries to be captured.""" @@ -49,27 +85,36 @@ def install_sql_hook(): # trickery would have to look different but I can't be bothered. return - def record_sql(vendor, start, sql, params): - breadcrumbs.record_breadcrumb('query', { - 'query': sql, - 'params': params, - 'duration': time.time() - start, - 'classifier': 'django.db.%s' % vendor - }) + def record_sql(vendor, start, duration, sql, params): + def _make_data(): + real_sql, real_params = format_sql(sql, params) + return { + 'query': real_sql, + 'params': real_params, + 'duration': duration, + 'classifier': 'django.db.%s' % vendor + } + breadcrumbs.record_breadcrumb('query', _make_data) + + def record_many_sql(vendor, start, sql, param_list): + duration = time.time() - start + for params in param_list: + record_sql(vendor, start, duration, sql, params) def execute(self, sql, params=None): start = time.time() try: return real_execute(self, sql, params) finally: - record_sql(self.db.vendor, start, sql, params) + record_sql(self.db.vendor, start, time.time() - start, + sql, params) def executemany(self, sql, param_list): start = time.time() try: return real_executemany(self, sql, param_list) finally: - record_sql(self.db.vendor, start, sql, param_list) + record_many_sql(self.db.vendor, start, sql, param_list) CursorWrapper.execute = execute CursorWrapper.executemany = executemany From 9a911272e51abfdfe85a14f39f1278a4c77cede9 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 27 Apr 2016 09:26:56 +0200 Subject: [PATCH 255/692] Fixed empty params for django --- raven/contrib/django/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/contrib/django/client.py b/raven/contrib/django/client.py index c7a0d9e73..6da179d54 100644 --- a/raven/contrib/django/client.py +++ b/raven/contrib/django/client.py @@ -54,7 +54,7 @@ def format_sql(sql, params): sql = sql % conv params = conv.params - for param in params: + for param in params or (): if param is None: rv.append('NULL') elif isinstance(param, string_types): From bb660fc1eab73479252f42eba2ab62cefdb0a59c Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 27 Apr 2016 09:32:07 +0200 Subject: [PATCH 256/692] Hook requests for breadcrumbs --- raven/base.py | 8 ++++++- raven/breadcrumbs.py | 51 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/raven/base.py b/raven/base.py index bfc353644..4be8568be 100644 --- a/raven/base.py +++ b/raven/base.py @@ -133,7 +133,7 @@ class Client(object): def __init__(self, dsn=None, raise_send_errors=False, transport=None, install_sys_hook=True, install_logging_hook=True, - **options): + hook_libraries=None, **options): global Raven o = options @@ -200,6 +200,8 @@ def __init__(self, dsn=None, raise_send_errors=False, transport=None, if install_logging_hook: self.install_logging_hook() + self.hook_libraries(hook_libraries) + def set_dsn(self, dsn=None, transport=None): if not dsn and os.environ.get('SENTRY_DSN'): msg = "Configuring Raven from environment variable 'SENTRY_DSN'" @@ -237,6 +239,10 @@ def install_logging_hook(self): from raven.breadcrumbs import install_logging_hook install_logging_hook() + def hook_libraries(self, libraries): + from raven.breadcrumbs import hook_libraries + hook_libraries(libraries) + @classmethod def register_scheme(cls, scheme, transport_class): cls._registry.register_scheme(scheme, transport_class) diff --git a/raven/breadcrumbs.py b/raven/breadcrumbs.py index 51b438647..4c0fabb9b 100644 --- a/raven/breadcrumbs.py +++ b/raven/breadcrumbs.py @@ -186,4 +186,55 @@ def register_special_log_handler(name_or_logger, callback): special_logger_handlers[name] = callback +hooked_libraries = {} + + +def libraryhook(name): + def decorator(f): + f = once(f) + hooked_libraries[name] = f + return f + return decorator + + +@libraryhook('requests') +def _hook_requests(): + try: + from requests.sesisons import Session + except ImportError: + return + + real_send = Session.send + + def send(self, request, *args, **kwargs): + def _record_request(response): + record_breadcrumb('http_request', { + 'url': request.url, + 'method': request.method, + 'status_code': response and response.status or None, + 'reason': response and response.reason or None, + 'classifier': 'requests', + }) + try: + resp = real_send(self, request, *args, **kwargs) + except Exception: + _record_request(None) + raise + else: + _record_request(resp) + return resp + + Session.send = send + + +def hook_libraries(libraries): + if not libraries: + libraries = hooked_libraries.keys() + for lib in libraries: + func = hooked_libraries.get(lib) + if func is None: + raise RuntimeError('Unknown library %r for hooking' % lib) + func() + + import raven.context From 4baedc02013c9bbe2d4f8cdd1418903f74214130 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 27 Apr 2016 13:11:15 +0200 Subject: [PATCH 257/692] Added httplib support --- raven/breadcrumbs.py | 65 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 60 insertions(+), 5 deletions(-) diff --git a/raven/breadcrumbs.py b/raven/breadcrumbs.py index 4c0fabb9b..9d8bd7877 100644 --- a/raven/breadcrumbs.py +++ b/raven/breadcrumbs.py @@ -165,12 +165,17 @@ def install_logging_hook(): _patch_logger() -def ignore_logger(name_or_logger): +def ignore_logger(name_or_logger, allow_level=None): """Ignores a logger for the regular breadcrumb code. This is useful for framework integration code where some log messages should be specially handled. """ - register_special_log_handler(name_or_logger, lambda *args: True) + def handler(logger, level, msg, args, kwargs): + if allow_level is not None and \ + level >= allow_level: + return False + return True + register_special_log_handler(name_or_logger, handler) def register_special_log_handler(name_or_logger, callback): @@ -200,7 +205,7 @@ def decorator(f): @libraryhook('requests') def _hook_requests(): try: - from requests.sesisons import Session + from requests.sessions import Session except ImportError: return @@ -211,7 +216,7 @@ def _record_request(response): record_breadcrumb('http_request', { 'url': request.url, 'method': request.method, - 'status_code': response and response.status or None, + 'status_code': response and response.status_code or None, 'reason': response and response.reason or None, 'classifier': 'requests', }) @@ -226,9 +231,59 @@ def _record_request(response): Session.send = send + ignore_logger('requests.packages.urllib3.connectionpool', + allow_level=logging.WARNING) + + +@libraryhook('httplib') +def _install_httplib(): + try: + from httplib import HTTPConnection + except ImportError: + from http.client import HTTPConnection + + real_putrequest = HTTPConnection.putrequest + real_getresponse = HTTPConnection.getresponse + + def putrequest(self, method, url, *args, **kwargs): + self._raven_status_dict = status = {} + host = self.host + port = self.port + default_port = self.default_port + + def _make_data(): + real_url = url + if not real_url.startswith(('http://', 'https://')): + real_url = '%s://%s%s%s' % ( + default_port == 443 and 'https' or 'http', + host, + port != default_port and ':%s' % port or '', + url, + ) + data = { + 'url': real_url, + 'method': method, + 'classifier': 'httplib', + } + data.update(status) + return data + record_breadcrumb('http_request', _make_data) + return real_putrequest(self, method, url, *args, **kwargs) + + def getresponse(self, *args, **kwargs): + rv = real_getresponse(self, *args, **kwargs) + status = getattr(self, '_raven_status_dict', None) + if status is not None and 'status_code' not in status: + status['status_code'] = rv.status + status['reason'] = rv.reason + return rv + + HTTPConnection.putrequest = putrequest + HTTPConnection.getresponse = getresponse + def hook_libraries(libraries): - if not libraries: + if libraries is None: libraries = hooked_libraries.keys() for lib in libraries: func = hooked_libraries.get(lib) From 6073a9c3249e26402e8d7901a45594ba95248355 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 27 Apr 2016 16:02:13 +0200 Subject: [PATCH 258/692] Record db alias --- raven/contrib/django/client.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/raven/contrib/django/client.py b/raven/contrib/django/client.py index 6da179d54..d10b6e786 100644 --- a/raven/contrib/django/client.py +++ b/raven/contrib/django/client.py @@ -85,36 +85,37 @@ def install_sql_hook(): # trickery would have to look different but I can't be bothered. return - def record_sql(vendor, start, duration, sql, params): + def record_sql(vendor, alias, start, duration, sql, params): def _make_data(): real_sql, real_params = format_sql(sql, params) return { 'query': real_sql, 'params': real_params, 'duration': duration, - 'classifier': 'django.db.%s' % vendor + 'classifier': 'django.%s.%s' % (vendor, alias or 'default') } breadcrumbs.record_breadcrumb('query', _make_data) - def record_many_sql(vendor, start, sql, param_list): + def record_many_sql(vendor, alias, start, sql, param_list): duration = time.time() - start for params in param_list: - record_sql(vendor, start, duration, sql, params) + record_sql(vendor, alias, start, duration, sql, params) def execute(self, sql, params=None): start = time.time() try: return real_execute(self, sql, params) finally: - record_sql(self.db.vendor, start, time.time() - start, - sql, params) + record_sql(self.db.vendor, getattr(self.db, 'alias', None), + start, time.time() - start, sql, params) def executemany(self, sql, param_list): start = time.time() try: return real_executemany(self, sql, param_list) finally: - record_many_sql(self.db.vendor, start, sql, param_list) + record_many_sql(self.db.vendor, getattr(self.db, 'alias', None), + start, sql, param_list) CursorWrapper.execute = execute CursorWrapper.executemany = executemany From 25728c8190a7f008cdb00b1664cd4fd479060eaf Mon Sep 17 00:00:00 2001 From: Matt Robenolt Date: Fri, 29 Apr 2016 10:51:42 -0700 Subject: [PATCH 259/692] Read git sha from packed-refs file after a git-gc Fixes GH-758 GH-709 --- raven/versioning.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/raven/versioning.py b/raven/versioning.py index c36b71916..993a238cb 100644 --- a/raven/versioning.py +++ b/raven/versioning.py @@ -28,8 +28,9 @@ def fetch_git_sha(path, head=None): head = text_type(fp.read()).strip() if head.startswith('ref: '): + head = head[5:] revision_file = os.path.join( - path, '.git', *head.rsplit(' ', 1)[-1].split('/') + path, '.git', *head.split('/') ) else: return head @@ -40,6 +41,25 @@ def fetch_git_sha(path, head=None): if not os.path.exists(os.path.join(path, '.git')): raise InvalidGitRepository( '%s does not seem to be the root of a git repository' % (path,)) + + # Check for our .git/packed-refs' file since a `git gc` may have run + # https://git-scm.com/book/en/v2/Git-Internals-Maintenance-and-Data-Recovery + packed_file = os.path.join(path, '.git', 'packed-refs') + if os.path.exists(packed_file): + with open(packed_file, 'r') as fh: + for line in fh: + line = line.rstrip() + if not line: + continue + if line[:1] in ('#', '^'): + continue + try: + revision, ref = line.split(' ', 1) + except ValueError: + continue + if ref == head: + return text_type(revision) + raise InvalidGitRepository( 'Unable to find ref to head "%s" in repository' % (head,)) From 74a4e3fcef00ae18f3897f520585f4587a2f1048 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 2 May 2016 17:03:08 +0200 Subject: [PATCH 260/692] Converted to new submission system --- raven/base.py | 2 +- raven/breadcrumbs.py | 63 ++++++++++++++++++++++++-------------------- 2 files changed, 35 insertions(+), 30 deletions(-) diff --git a/raven/base.py b/raven/base.py index 4be8568be..4d03e2235 100644 --- a/raven/base.py +++ b/raven/base.py @@ -828,7 +828,7 @@ def captureBreadcrumb(self, type, *args, **kwargs): # Note: framework integration should not call this method but # instead use the raven.breadcrumbs.record_breadcrumb function # which will record to the correct client automatically. - self.context.breadcrumbs.record(type, dict(*args, **kwargs)) + self.context.breadcrumbs.record(type, *args, **kwargs) capture_breadcrumb = captureBreadcrumb diff --git a/raven/breadcrumbs.py b/raven/breadcrumbs.py index 9d8bd7877..8f4ed862e 100644 --- a/raven/breadcrumbs.py +++ b/raven/breadcrumbs.py @@ -17,10 +17,19 @@ def __init__(self, limit=100): self.buffer = [] self.limit = limit - def record(self, type, data=None, timestamp=None): + def record(self, type, timestamp=None, duration=None, level=None, + message=None, category=None, data=None, processor=None): if timestamp is None: timestamp = time.time() - self.buffer.append((type, timestamp, data)) + self.buffer.append(({ + 'type': type, + 'timestamp': timestamp, + 'duration': duration, + 'level': level, + 'message': message, + 'category': category, + 'data': data, + }, processor)) del self.buffer[:-self.limit] def clear(self): @@ -28,30 +37,26 @@ def clear(self): def get_buffer(self): rv = [] - for type, timestamp, data in self.buffer: - if data is None: - data = {} - elif callable(data): - data = data() - rv.append({ - 'type': type, - 'data': data, - 'timestamp': timestamp, - }) + for idx, (payload, processor) in enumerate(self.buffer): + if processor is not None: + processor(payload) + self.buffer[idx] = (payload, None) + rv.append(payload) return rv -def record_breadcrumb(type, data=None, timestamp=None): +def record_breadcrumb(type, timestamp=None, duration=None, level=None, + message=None, category=None, data=None, + processor=None): """Records a breadcrumb for all active clients. This is what integration code should use rather than invoking the `captureBreadcrumb` method - on a specific client. This also additionally permits data to be a - callable that will be invoked to generate the data if the crumb is not - discarded. + on a specific client. """ if timestamp is None: timestamp = time.time() for ctx in raven.context.get_active_contexts(): - ctx.breadcrumbs.record(type, data, timestamp) + ctx.breadcrumbs.record(type, timestamp, duration, level, message, + category, data, processor) def _record_log_breadcrumb(logger, level, msg, *args, **kwargs): @@ -61,16 +66,17 @@ def _record_log_breadcrumb(logger, level, msg, *args, **kwargs): if rv: return - def _make_data(): + def processor(data): formatted_msg = text_type(msg) if args: formatted_msg = msg % args - return { + data.update({ 'message': formatted_msg, - 'logger': logger.name, + 'category': logger.name, 'level': logging.getLevelName(level).lower(), - } - record_breadcrumb('message', _make_data) + 'data': kwargs, + }) + record_breadcrumb('default', processor=processor) def _wrap_logging_method(meth, level=None): @@ -213,12 +219,11 @@ def _hook_requests(): def send(self, request, *args, **kwargs): def _record_request(response): - record_breadcrumb('http_request', { + record_breadcrumb('http', category='requests', data={ 'url': request.url, 'method': request.method, 'status_code': response and response.status_code or None, 'reason': response and response.reason or None, - 'classifier': 'requests', }) try: resp = real_send(self, request, *args, **kwargs) @@ -251,7 +256,7 @@ def putrequest(self, method, url, *args, **kwargs): port = self.port default_port = self.default_port - def _make_data(): + def processor(data): real_url = url if not real_url.startswith(('http://', 'https://')): real_url = '%s://%s%s%s' % ( @@ -260,14 +265,14 @@ def _make_data(): port != default_port and ':%s' % port or '', url, ) - data = { + data['data'] = { 'url': real_url, 'method': method, - 'classifier': 'httplib', } - data.update(status) + data['data'].update(status) return data - record_breadcrumb('http_request', _make_data) + record_breadcrumb('http', category='requests', + processor=processor) return real_putrequest(self, method, url, *args, **kwargs) def getresponse(self, *args, **kwargs): From 79c4ae4b3b01e3953da7e132878b71f9728c79ec Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 2 May 2016 17:44:21 +0200 Subject: [PATCH 261/692] Send breadcrumbs as an object --- raven/base.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/raven/base.py b/raven/base.py index 4d03e2235..59388b257 100644 --- a/raven/base.py +++ b/raven/base.py @@ -458,7 +458,11 @@ def build_msg(self, event_type, data=None, date=None, # insert breadcrumbs crumbs = self.context.breadcrumbs.get_buffer() if crumbs: - data.setdefault('breadcrumbs', crumbs) + # Make sure we send the crumbs here as "values" as we use the + # raven client internally in sentry and the alternative + # submission option of a list here is not supported by the + # internal sender. + data.setdefault('breadcrumbs', {'values': crumbs}) return data From c3860d30a08ccb67e04c1963ba2fc04835d8af5e Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 2 May 2016 22:26:29 +0200 Subject: [PATCH 262/692] Strip category for logger --- raven/breadcrumbs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/breadcrumbs.py b/raven/breadcrumbs.py index 8f4ed862e..2aa44fd7b 100644 --- a/raven/breadcrumbs.py +++ b/raven/breadcrumbs.py @@ -72,7 +72,7 @@ def processor(data): formatted_msg = msg % args data.update({ 'message': formatted_msg, - 'category': logger.name, + 'category': logger.name.split('.')[0], 'level': logging.getLevelName(level).lower(), 'data': kwargs, }) From 45543822946bf79a8f71ed83c9ab1a866d9ccc07 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 2 May 2016 23:55:26 +0200 Subject: [PATCH 263/692] Fix query logging for django --- raven/contrib/django/client.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/raven/contrib/django/client.py b/raven/contrib/django/client.py index d10b6e786..00a555cbf 100644 --- a/raven/contrib/django/client.py +++ b/raven/contrib/django/client.py @@ -86,15 +86,18 @@ def install_sql_hook(): return def record_sql(vendor, alias, start, duration, sql, params): - def _make_data(): + def processor(data): real_sql, real_params = format_sql(sql, params) - return { - 'query': real_sql, - 'params': real_params, + if real_params: + real_sql = real_sql % tuple(real_params) + # maybe category to 'django.%s.%s' % (vendor, alias or + # 'default') ? + data.update({ + 'message': real_sql, 'duration': duration, - 'classifier': 'django.%s.%s' % (vendor, alias or 'default') - } - breadcrumbs.record_breadcrumb('query', _make_data) + 'category': 'query', + }) + breadcrumbs.record_breadcrumb('default', processor=processor) def record_many_sql(vendor, alias, start, sql, param_list): duration = time.time() - start From 91a93d5a8cd704e2a3fbb21ba2794cc5687f2389 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 3 May 2016 00:35:00 +0200 Subject: [PATCH 264/692] Updated changelog for 5.14.0 --- CHANGES | 5 +++++ setup.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 4a79de707..e07680df3 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 5.14.0 +-------------- + +* Added support for reading git sha's from packed references. + Version 5.13.0 -------------- diff --git a/setup.py b/setup.py index 34de0a995..e24b8f40d 100755 --- a/setup.py +++ b/setup.py @@ -97,7 +97,7 @@ def run_tests(self): setup( name='raven', - version='5.13.0', + version='5.14.0', author='Sentry', author_email='hello@getsentry.com', url='https://github.com/getsentry/raven-python', From 4bc43fd4314e6996b6b456cd0480b68c2a4e4a40 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 3 May 2016 00:35:44 +0200 Subject: [PATCH 265/692] Added changelog for uwsgi thread detection --- CHANGES | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES b/CHANGES index e07680df3..876c9f7cc 100644 --- a/CHANGES +++ b/CHANGES @@ -2,6 +2,7 @@ Version 5.14.0 -------------- * Added support for reading git sha's from packed references. +* Detect disabled thread support for uwsgi. Version 5.13.0 -------------- From b9ddc296ef5d5d33b7332cfecab645245510769a Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 3 May 2016 00:45:53 +0200 Subject: [PATCH 266/692] Added changelog for breadcrumbs --- CHANGES | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES b/CHANGES index 876c9f7cc..0461f5350 100644 --- a/CHANGES +++ b/CHANGES @@ -3,6 +3,7 @@ Version 5.14.0 * Added support for reading git sha's from packed references. * Detect disabled thread support for uwsgi. +* Added preliminary support for breadcrumbs. Version 5.13.0 -------------- From bf543c122d8e74a00d51e062b0e03764a2fa1d78 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 3 May 2016 18:39:25 +0200 Subject: [PATCH 267/692] Added breadcrumb docs --- docs/breadcrumbs.rst | 104 +++++++++++++++++++++++++++++++++++++++++++ docs/index.rst | 1 + 2 files changed, 105 insertions(+) create mode 100644 docs/breadcrumbs.rst diff --git a/docs/breadcrumbs.rst b/docs/breadcrumbs.rst new file mode 100644 index 000000000..dd54b0a19 --- /dev/null +++ b/docs/breadcrumbs.rst @@ -0,0 +1,104 @@ +Logging Breadcrumbs +=================== + +Newer Sentry versions support logging of breadcrumbs in addition of +errors. This means that whenever an error or other Sentry event is +submitted to the system, breadcrumbs that were logged before are sent +along to make it easier to reproduce what lead up to an error. + +In the default configuration the Python client instruments the logging +framework and a few popular libraries to emit crumbs. + +You can however also manually emit events if you want to do so. There are +a few ways this can be done. + +Enabling / Disabling Instrumentation +------------------------------------ + +When a sentry client is constructed then the raven library will by default +automatically instrument some popular libraries. There are a few ways +this can be controlled by passing parameters to the client constructor: + +``install_logging_hook``: + If this keyword argument is set to `False` the Python logging system + will not be instrumented. Note that this is a global instrumentation + so that if you are using multiple sentry clients at once you need to + disable this on all of them. + +``hook_libraries``: + This is a list of libraries that you want to hook. The default is to + hook all libraries that we have integrations for. If this is set to + an empty list then no libraries are hooked. + + The following libraries are supported currently: + + - ``'requests'``: hooks the Python requests library. + - ``'httplib'``: hooks the stdlib http library (also hooks urllib in + the process) + +Additionally framework integration will hook more things automatically. +For instance when you use Django, database queries will be recorded. + +Another option to control what happens is to register special handlers for +the logging system or to disable loggers entirely. For this you can use +the :func:`~raven.breadcrumbs.ignore_logger` and +:func:`~raven.breadcrumbs.register_special_log_handler` functions: + +.. py:function:: raven.breadcrumbs.ignore_logger(name_or_logger) + + If called with the name of a logger, this will ignore all messages + that come from that logger. For instance if you have a very spammy + logger you can disable it this way. + +.. py:function:: raven.breadcrumbs.register_special_log_handler(name_or_logger, callback) + + This registers a callback as a handler for a given logger. This can + be used to ignore or convert log messages. The callback is invoked + with the following arguments: ``logger, level, msg, args, kwargs``. + If the callback returns `False` nothing is logged, if it returns + `True` the default handling kicks in. + + Typically it makes sense to invoke + :func:`~raven.breadcrumbs.record_breadcrumb` from it. + +Manually Emitting Breadcrumbs +----------------------------- + +If you want to manually record breadcrumbs the most convenient way to do +taht is to use the :func:`~reaven.breadcrumbs.record_breadcrumb` function +which will automatically record the crumbs with the clients that are +working with the current thread. This is more convenient than to call the +`captureBreadcrumb` method on the client itself as you need to hold a +reference to that. + +.. py:function:: raven.breadcrumbs.record_breadcrumb(**options) + + This function accepts keyword arguments matching the attributes of a + breadcrumb. For more information see :doc:`/clientdev/interfaces`. + Additionally a `processor` callback can be passed which will be + invoked to process the data if the crumb was not rejected. + +Example: + +.. sourcecode:: python + + from raven.breadcrumbs import record_breadcrumb + + record_breadcrumb(message='This is an important message', + category='my_module', level='warning') + +Because crumbs go into a ring buffer, often it can be useful to defer +processing of expensive operations until the crumb is actually needed. +For this you can pass a processor which will be passed the data dict for +modifications: + +.. sourcecode:: python + + from raven.breadcrumbs import record_breadcrumb + + def process_crumb(data): + data['data'] = compute_expensive_data() + + record_breadcrumb(message='This is an important message', + category='my_module', level='warning', + processor=process_crumb) diff --git a/docs/index.rst b/docs/index.rst index b708d193a..6fe160ebd 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -82,6 +82,7 @@ discover: usage advanced + breadcrumbs integrations/index transports platform-support From bf786c6790be2378b32faf4d27e0d085e19288b9 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 3 May 2016 18:44:34 +0200 Subject: [PATCH 268/692] Added a note about breadcrumbs to the changelog --- CHANGES | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGES b/CHANGES index 0461f5350..76843b473 100644 --- a/CHANGES +++ b/CHANGES @@ -5,6 +5,10 @@ Version 5.14.0 * Detect disabled thread support for uwsgi. * Added preliminary support for breadcrumbs. +Note: this version adds breadcrumbs to events. This means that if you run a +Sentry version older than 8.5 you will see some warnings in the UI. Consider +using an older version of the client if you do not want to see it. + Version 5.13.0 -------------- From f8cd5e376f752a2907a2ea3603e2c4dfc4a3af75 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 3 May 2016 18:46:52 +0200 Subject: [PATCH 269/692] Fixed rst errors --- docs/breadcrumbs.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/breadcrumbs.rst b/docs/breadcrumbs.rst index dd54b0a19..ffdbb0f5d 100644 --- a/docs/breadcrumbs.rst +++ b/docs/breadcrumbs.rst @@ -41,8 +41,8 @@ For instance when you use Django, database queries will be recorded. Another option to control what happens is to register special handlers for the logging system or to disable loggers entirely. For this you can use -the :func:`~raven.breadcrumbs.ignore_logger` and -:func:`~raven.breadcrumbs.register_special_log_handler` functions: +the :py:func:`~raven.breadcrumbs.ignore_logger` and +:py:func:`~raven.breadcrumbs.register_special_log_handler` functions: .. py:function:: raven.breadcrumbs.ignore_logger(name_or_logger) @@ -59,13 +59,13 @@ the :func:`~raven.breadcrumbs.ignore_logger` and `True` the default handling kicks in. Typically it makes sense to invoke - :func:`~raven.breadcrumbs.record_breadcrumb` from it. + :py:func:`~raven.breadcrumbs.record_breadcrumb` from it. Manually Emitting Breadcrumbs ----------------------------- If you want to manually record breadcrumbs the most convenient way to do -taht is to use the :func:`~reaven.breadcrumbs.record_breadcrumb` function +that is to use the :py:func:`~reaven.breadcrumbs.record_breadcrumb` function which will automatically record the crumbs with the clients that are working with the current thread. This is more convenient than to call the `captureBreadcrumb` method on the client itself as you need to hold a From cf3a388395d18b44c25db3d3d5174b7e468633d4 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 3 May 2016 19:58:08 +0200 Subject: [PATCH 270/692] Added docs on context thread management --- docs/breadcrumbs.rst | 24 ++++++++++++++++++++++++ docs/usage.rst | 31 +++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/docs/breadcrumbs.rst b/docs/breadcrumbs.rst index ffdbb0f5d..0178ddf62 100644 --- a/docs/breadcrumbs.rst +++ b/docs/breadcrumbs.rst @@ -102,3 +102,27 @@ modifications: record_breadcrumb(message='This is an important message', category='my_module', level='warning', processor=process_crumb) + +Context Thread Binding +---------------------- + +Typically when you use breadcrumbs from a framework integration +breadcrumbs work automatically. However there are cases where you want to +do this yourself. If a context is not bound to the thread breadcrumbs +will not be recorded. The thread that created the client (typically the +main thread) is bound by default. + +To bind the context you can use the `activate()` method on it:: + + client.context.activate() + +To unbind the context you can `deactivate()` it:: + + client.context.deactivate() + +Alternatively you can use the context with the `with` statement:: + + with client.context: + ... + +The context is automatically deactivated if it's cleared. diff --git a/docs/usage.rst b/docs/usage.rst index 1d8f083f9..28a1c5471 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -51,6 +51,37 @@ and `clear()` function that can be used:: finally: client.context.clear() +Additionally starting with Raven 5.14 you can bind the context to the +current thread to enable crumb support by calling `activate()`. The +deactivation happens upon calling `clear()`. This can also be achieved by +using the context object with the `with` statement. This is needed to +enable breadcrumb capturing. Framework integrations typically do this +automatically. + +These two examples are equivalent:: + + def handle_request(request): + client.context.activate() + client.context.merge({'user': { + 'email': request.user.email + }}) + try: + ... + finally: + client.context.clear() + +With a context manager:: + + def handle_request(request): + with client.context: + client.context.merge({'user': { + 'email': request.user.email + }}) + try: + ... + finally: + client.context.clear() + Testing the Client ------------------ From 18529f23558a10ca9fdb33be25d57a4305152783 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 3 May 2016 23:12:51 +0200 Subject: [PATCH 271/692] Added support for improved context thread binding --- CHANGES | 7 +++++++ raven/base.py | 14 +++++++++----- raven/context.py | 25 +++++++++++++++++++++++-- tests/context/tests.py | 35 +++++++++++++++++++++++++++++++++++ 4 files changed, 74 insertions(+), 7 deletions(-) diff --git a/CHANGES b/CHANGES index 76843b473..5dcc0d35c 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,10 @@ +Version 5.15.0 +-------------- + +* Improve thread binding for the context. This makes the main thread never + deactivate the client automatically on clear which means that more code + should automatically support breadcrumbs without changes. + Version 5.14.0 -------------- diff --git a/raven/base.py b/raven/base.py index 59388b257..0136c88d3 100644 --- a/raven/base.py +++ b/raven/base.py @@ -25,6 +25,11 @@ else: import contextlib2 as contextlib +try: + from thread import get_ident as get_thread_ident +except ImportError: + from _thread import get_ident as get_thread_ident + import raven from raven.conf import defaults from raven.conf.remote import RemoteConfig @@ -186,14 +191,13 @@ def __init__(self, dsn=None, raise_send_errors=False, transport=None, if Raven is None: Raven = self + # We want to remember the creating thread id here because this + # comes in useful for the context special handling + self.main_thread_id = get_thread_ident() + from raven.context import Context self._context = Context(self) - # We always activate the context for the calling thread. This - # means even in the absence of code that activates the context for - # threads otherwise. - self._context.activate() - if install_sys_hook: self.install_sys_hook() diff --git a/raven/context.py b/raven/context.py index cb2efbff9..03309f119 100644 --- a/raven/context.py +++ b/raven/context.py @@ -13,6 +13,11 @@ from raven._compat import iteritems +try: + from thread import get_ident as get_thread_ident +except ImportError: + from _thread import get_ident as get_thread_ident + _active_contexts = local() @@ -42,6 +47,11 @@ def __init__(self, client=None): if client is not None: client = weakref(client) self._client = client + # Because the thread auto activates the thread local this also + # means that we auto activate this thing. Only if someone decides + # to deactivate manually later another call to activate is + # technically necessary. + self.activate() self.data = {} self.exceptions_to_skip = set() self.breadcrumbs = raven.breadcrumbs.BreadcrumbBuffer() @@ -80,7 +90,9 @@ def __enter__(self): def __exit__(self, exc_type, exc_value, tb): self.deactivate() - def activate(self): + def activate(self, sticky=False): + if sticky: + self._sticky_thread = get_thread_ident() _active_contexts.__dict__.setdefault('contexts', set()).add(self) def deactivate(self): @@ -105,10 +117,19 @@ def set(self, data): def get(self): return self.data - def clear(self, deactivate=True): + def clear(self, deactivate=None): self.data = {} self.exceptions_to_skip.clear() self.breadcrumbs.clear() + + # If the caller did not specify if it wants to deactivate the + # context for the thread we only deactivate it if we're not the + # thread that created the context (main thread). + if deactivate is None: + client = self.client + if client is not None: + deactivate = get_thread_ident() != client.main_thread_id + if deactivate: self.deactivate() diff --git a/tests/context/tests.py b/tests/context/tests.py index cc655644f..9332655cd 100644 --- a/tests/context/tests.py +++ b/tests/context/tests.py @@ -1,5 +1,7 @@ +import threading from raven.utils.testutils import TestCase +from raven.base import Client from raven.context import Context @@ -35,3 +37,36 @@ def test_extra(self): 'biz': 'baz', } } + + def test_thread_binding(self): + client = Client() + called = [] + + class TestContext(Context): + + def activate(self): + Context.activate(self) + called.append('activate') + + def deactivate(self): + called.append('deactivate') + Context.deactivate(self) + + # The main thread activates the context but clear does not + # deactivate. + context = TestContext(client) + context.clear() + assert called == ['activate'] + + # But another thread does. + del called[:] + + def test_thread(): + # This activate is unnecessary as the first activate happens + # automatically + context.activate() + context.clear() + t = threading.Thread(target=test_thread) + t.start() + t.join() + assert called == ['activate', 'activate', 'deactivate'] From 30b073440aebef34dc1289e643ffcc60f45453fa Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 3 May 2016 23:14:53 +0200 Subject: [PATCH 272/692] Improved docs on 5.15 context management --- docs/breadcrumbs.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/breadcrumbs.rst b/docs/breadcrumbs.rst index 0178ddf62..9e78680d6 100644 --- a/docs/breadcrumbs.rst +++ b/docs/breadcrumbs.rst @@ -125,4 +125,6 @@ Alternatively you can use the context with the `with` statement:: with client.context: ... -The context is automatically deactivated if it's cleared. +The context is automatically deactivated if it's cleared unless it's +managed from the main thread. Likewise raven will attempt to auto +activate the client. From 4121a03db5dd226039f585369941b955c9aba88e Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 3 May 2016 23:17:10 +0200 Subject: [PATCH 273/692] This is 5.15.0 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index e24b8f40d..4125f4a67 100755 --- a/setup.py +++ b/setup.py @@ -97,7 +97,7 @@ def run_tests(self): setup( name='raven', - version='5.14.0', + version='5.15.0', author='Sentry', author_email='hello@getsentry.com', url='https://github.com/getsentry/raven-python', From b6eed6e918b7252f6c6b073736cbbf1af033233b Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 4 May 2016 13:36:33 +0200 Subject: [PATCH 274/692] Remove exc_info in breadcrumbs --- CHANGES | 5 +++++ raven/breadcrumbs.py | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/CHANGES b/CHANGES index 5dcc0d35c..9e373ebac 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 5.16.0 +-------------- + +* exc_info is no longer included in logger based breadcrumbs. + Version 5.15.0 -------------- diff --git a/raven/breadcrumbs.py b/raven/breadcrumbs.py index 2aa44fd7b..0eae258c9 100644 --- a/raven/breadcrumbs.py +++ b/raven/breadcrumbs.py @@ -70,6 +70,11 @@ def processor(data): formatted_msg = text_type(msg) if args: formatted_msg = msg % args + # We do not want to include exc_info as argument because it often + # lies (set to a contant value like 1 or True) or even if it's a + # tuple it will not be particularly useful for us as we cannot + # process it anyways. + kwargs.pop('exc_info', None) data.update({ 'message': formatted_msg, 'category': logger.name.split('.')[0], From 37c3a892b308e0ae2b6962859a888628bf4ddbe1 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 4 May 2016 15:37:45 +0200 Subject: [PATCH 275/692] Log the entire logger name as category --- CHANGES | 1 + raven/breadcrumbs.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 9e373ebac..a976f3f86 100644 --- a/CHANGES +++ b/CHANGES @@ -2,6 +2,7 @@ Version 5.16.0 -------------- * exc_info is no longer included in logger based breadcrumbs. +* log the entire logger name as category. Version 5.15.0 -------------- diff --git a/raven/breadcrumbs.py b/raven/breadcrumbs.py index 0eae258c9..b6a35dc50 100644 --- a/raven/breadcrumbs.py +++ b/raven/breadcrumbs.py @@ -77,7 +77,7 @@ def processor(data): kwargs.pop('exc_info', None) data.update({ 'message': formatted_msg, - 'category': logger.name.split('.')[0], + 'category': logger.name, 'level': logging.getLevelName(level).lower(), 'data': kwargs, }) From 373f2817755bd54cb71309b366f96fa62fc1cf32 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 5 May 2016 12:31:00 +0200 Subject: [PATCH 276/692] Added flag for enabling or disabling breadcrumbs --- CHANGES | 2 ++ raven/base.py | 18 ++++++++++-------- raven/breadcrumbs.py | 11 +++++++++++ raven/context.py | 4 +++- 4 files changed, 26 insertions(+), 9 deletions(-) diff --git a/CHANGES b/CHANGES index a976f3f86..5035f54d5 100644 --- a/CHANGES +++ b/CHANGES @@ -3,6 +3,8 @@ Version 5.16.0 * exc_info is no longer included in logger based breadcrumbs. * log the entire logger name as category. +* added a `enable_breadcrumbs` flag to the client to allow the enabling or + disabling of breadcrumbs quickly. Version 5.15.0 -------------- diff --git a/raven/base.py b/raven/base.py index 0136c88d3..6c06d61aa 100644 --- a/raven/base.py +++ b/raven/base.py @@ -138,7 +138,7 @@ class Client(object): def __init__(self, dsn=None, raise_send_errors=False, transport=None, install_sys_hook=True, install_logging_hook=True, - hook_libraries=None, **options): + hook_libraries=None, enable_breadcrumbs=True, **options): global Raven o = options @@ -194,6 +194,7 @@ def __init__(self, dsn=None, raise_send_errors=False, transport=None, # We want to remember the creating thread id here because this # comes in useful for the context special handling self.main_thread_id = get_thread_ident() + self.enable_breadcrumbs = enable_breadcrumbs from raven.context import Context self._context = Context(self) @@ -460,13 +461,14 @@ def build_msg(self, event_type, data=None, date=None, data.setdefault('sdk', SDK_VALUE) # insert breadcrumbs - crumbs = self.context.breadcrumbs.get_buffer() - if crumbs: - # Make sure we send the crumbs here as "values" as we use the - # raven client internally in sentry and the alternative - # submission option of a list here is not supported by the - # internal sender. - data.setdefault('breadcrumbs', {'values': crumbs}) + if self.enable_breadcrumbs: + crumbs = self.context.breadcrumbs.get_buffer() + if crumbs: + # Make sure we send the crumbs here as "values" as we use the + # raven client internally in sentry and the alternative + # submission option of a list here is not supported by the + # internal sender. + data.setdefault('breadcrumbs', {'values': crumbs}) return data diff --git a/raven/breadcrumbs.py b/raven/breadcrumbs.py index b6a35dc50..66e579fd8 100644 --- a/raven/breadcrumbs.py +++ b/raven/breadcrumbs.py @@ -45,6 +45,17 @@ def get_buffer(self): return rv +class BlackholeBreadcrumbBuffer(BreadcrumbBuffer): + def record(self, *args, **kwargs): + pass + + +def make_buffer(enabled=True): + if enabled: + return BreadcrumbBuffer() + return BlackholeBreadcrumbBuffer() + + def record_breadcrumb(type, timestamp=None, duration=None, level=None, message=None, category=None, data=None, processor=None): diff --git a/raven/context.py b/raven/context.py index 03309f119..d27572a7a 100644 --- a/raven/context.py +++ b/raven/context.py @@ -44,6 +44,8 @@ class Context(local, Mapping, Iterable): """ def __init__(self, client=None): + breadcrumbs = raven.breadcrumbs.make_buffer( + client is None or client.enable_breadcrumbs) if client is not None: client = weakref(client) self._client = client @@ -54,7 +56,7 @@ def __init__(self, client=None): self.activate() self.data = {} self.exceptions_to_skip = set() - self.breadcrumbs = raven.breadcrumbs.BreadcrumbBuffer() + self.breadcrumbs = breadcrumbs @property def client(self): From c5f40fbc00ce4eb2522b7513547cf691bc036d5f Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 5 May 2016 13:34:58 +0200 Subject: [PATCH 277/692] Added tests for breadcrumbs --- tests/breadcrumbs/__init__.py | 0 tests/breadcrumbs/tests.py | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 tests/breadcrumbs/__init__.py create mode 100644 tests/breadcrumbs/tests.py diff --git a/tests/breadcrumbs/__init__.py b/tests/breadcrumbs/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/breadcrumbs/tests.py b/tests/breadcrumbs/tests.py new file mode 100644 index 000000000..a49db931a --- /dev/null +++ b/tests/breadcrumbs/tests.py @@ -0,0 +1,32 @@ +import logging + +from raven.utils.testutils import TestCase + +from raven.base import Client +from raven.breadcrumbs import record_breadcrumb + + +class BreadcrumbTestCase(TestCase): + + def test_crumb_buffer(self): + for enable in 1, 0: + client = Client('http://foo:bar@example.com/0', + enable_breadcrumbs=enable) + with client.context: + record_breadcrumb('foo', data={'bar': 'baz'}, + message='aha', category='huhu') + crumbs = client.context.breadcrumbs.get_buffer() + assert len(crumbs) == enable + + def test_log_crumb_reporting(self): + client = Client('http://foo:bar@example.com/0') + with client.context: + log = logging.getLogger('whatever.foo') + log.info('This is a message with %s!', 'foo', blah='baz') + crumbs = client.context.breadcrumbs.get_buffer() + + assert len(crumbs) == 1 + assert crumbs[0]['type'] == 'default' + assert crumbs[0]['category'] == 'whatever.foo' + assert crumbs[0]['data'] == {'blah': 'baz'} + assert crumbs[0]['message'] == 'This is a message with foo!' From 94fe077b5d0c00b0e88d87cb28b2450152b7725c Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 5 May 2016 13:44:45 +0200 Subject: [PATCH 278/692] Updated docs for breadrumb disabling --- docs/breadcrumbs.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/breadcrumbs.rst b/docs/breadcrumbs.rst index 9e78680d6..388ef31a3 100644 --- a/docs/breadcrumbs.rst +++ b/docs/breadcrumbs.rst @@ -12,6 +12,10 @@ framework and a few popular libraries to emit crumbs. You can however also manually emit events if you want to do so. There are a few ways this can be done. +Breadcrumbs are enabled by default but starting with Raven 5.15 you can +disable them on a per-client basis by passing ``enable_breadcrumbs=False`` +to the client constructor. + Enabling / Disabling Instrumentation ------------------------------------ From 76b29f1d41ff2e251f6630243a3a833651268463 Mon Sep 17 00:00:00 2001 From: Chris Streeter Date: Thu, 5 May 2016 08:20:56 -0700 Subject: [PATCH 279/692] Fix a typo (#762) --- raven/breadcrumbs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/breadcrumbs.py b/raven/breadcrumbs.py index 66e579fd8..389ce261e 100644 --- a/raven/breadcrumbs.py +++ b/raven/breadcrumbs.py @@ -82,7 +82,7 @@ def processor(data): if args: formatted_msg = msg % args # We do not want to include exc_info as argument because it often - # lies (set to a contant value like 1 or True) or even if it's a + # lies (set to a constant value like 1 or True) or even if it's a # tuple it will not be particularly useful for us as we cannot # process it anyways. kwargs.pop('exc_info', None) From bf91eb9f169c90c0771c1410c7d694ed818f0472 Mon Sep 17 00:00:00 2001 From: Chris Streeter Date: Thu, 5 May 2016 09:41:16 -0700 Subject: [PATCH 280/692] record_breadcrumb requires type as first arg (#763) * record_breadcrumb requires type as first arg * Use default breadcrumb type in examples --- docs/breadcrumbs.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/breadcrumbs.rst b/docs/breadcrumbs.rst index 388ef31a3..df0dcec1a 100644 --- a/docs/breadcrumbs.rst +++ b/docs/breadcrumbs.rst @@ -88,7 +88,7 @@ Example: from raven.breadcrumbs import record_breadcrumb - record_breadcrumb(message='This is an important message', + record_breadcrumb('default', message='This is an important message', category='my_module', level='warning') Because crumbs go into a ring buffer, often it can be useful to defer @@ -103,7 +103,7 @@ modifications: def process_crumb(data): data['data'] = compute_expensive_data() - record_breadcrumb(message='This is an important message', + record_breadcrumb('default', message='This is an important message', category='my_module', level='warning', processor=process_crumb) From a50ca80be1ca931a49fb27343e8fbb2fa52e2113 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 9 May 2016 20:48:52 +0200 Subject: [PATCH 281/692] Corrected an issue where logging locations were reported incorrectly. --- CHANGES | 3 +++ raven/breadcrumbs.py | 2 +- tests/breadcrumbs/tests.py | 29 +++++++++++++++++++++++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 5035f54d5..3af9d515b 100644 --- a/CHANGES +++ b/CHANGES @@ -5,6 +5,9 @@ Version 5.16.0 * log the entire logger name as category. * added a `enable_breadcrumbs` flag to the client to allow the enabling or disabling of breadcrumbs quickly. +* corrected an issue where python interpreters with bytecode writing enabled + would report incorrect logging locations when breadcrumb patching for + logging was enabled. Version 5.15.0 -------------- diff --git a/raven/breadcrumbs.py b/raven/breadcrumbs.py index 389ce261e..a0c91a680 100644 --- a/raven/breadcrumbs.py +++ b/raven/breadcrumbs.py @@ -143,7 +143,7 @@ def %(name)s(self, %(args)s, *args, **kwargs): 'args': ', '.join(args), 'fwd': fwd, 'level': level, - }, logging.__file__, 'exec'), logging.__dict__, ns) + }, logging._srcfile, 'exec'), logging.__dict__, ns) new_func = ns['factory'](meth, _record_log_breadcrumb) new_func.__doc__ = func.__doc__ diff --git a/tests/breadcrumbs/tests.py b/tests/breadcrumbs/tests.py index a49db931a..85f6192a2 100644 --- a/tests/breadcrumbs/tests.py +++ b/tests/breadcrumbs/tests.py @@ -1,3 +1,4 @@ +import sys import logging from raven.utils.testutils import TestCase @@ -5,6 +6,11 @@ from raven.base import Client from raven.breadcrumbs import record_breadcrumb +try: + from cStringIO import StringIO as BytesIO +except ImportError: + from io import BytesIO + class BreadcrumbTestCase(TestCase): @@ -30,3 +36,26 @@ def test_log_crumb_reporting(self): assert crumbs[0]['category'] == 'whatever.foo' assert crumbs[0]['data'] == {'blah': 'baz'} assert crumbs[0]['message'] == 'This is a message with foo!' + + def test_log_location(self): + out = BytesIO() + logger = logging.getLogger(__name__) + logger.setLevel(logging.DEBUG) + handler = logging.StreamHandler(out) + handler.setFormatter(logging.Formatter( + '%(name)s|%(filename)s|%(funcName)s|%(lineno)d|' + '%(levelname)s|%(message)s')) + logger.addHandler(handler) + + client = Client('http://foo:bar@example.com/0') + with client.context: + logger.info('Hello World!') + lineno = sys._getframe().f_lineno - 1 + + items = out.getvalue().strip().decode('utf-8').split('|') + assert items[0] == b'tests.breadcrumbs.tests' + assert items[1].rstrip(b'co') == b'tests.py' + assert items[2] == b'test_log_location' + assert int(items[3]) == lineno + assert items[4] == b'INFO' + assert items[5] == b'Hello World!' From 9e63feed9e4c053b969afe9ff51e27807b04d484 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 9 May 2016 20:49:13 +0200 Subject: [PATCH 282/692] This is 5.16.0 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 4125f4a67..575b11e6c 100755 --- a/setup.py +++ b/setup.py @@ -97,7 +97,7 @@ def run_tests(self): setup( name='raven', - version='5.15.0', + version='5.16.0', author='Sentry', author_email='hello@getsentry.com', url='https://github.com/getsentry/raven-python', From c3e3e4dcbb1459e8b93be3bddca4cbcbdb971362 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 9 May 2016 21:02:39 +0200 Subject: [PATCH 283/692] Fixed tests for python 3 --- tests/breadcrumbs/tests.py | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/tests/breadcrumbs/tests.py b/tests/breadcrumbs/tests.py index 85f6192a2..ab424d80b 100644 --- a/tests/breadcrumbs/tests.py +++ b/tests/breadcrumbs/tests.py @@ -6,10 +6,7 @@ from raven.base import Client from raven.breadcrumbs import record_breadcrumb -try: - from cStringIO import StringIO as BytesIO -except ImportError: - from io import BytesIO +from io import StringIO class BreadcrumbTestCase(TestCase): @@ -38,13 +35,13 @@ def test_log_crumb_reporting(self): assert crumbs[0]['message'] == 'This is a message with foo!' def test_log_location(self): - out = BytesIO() + out = StringIO() logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) handler = logging.StreamHandler(out) handler.setFormatter(logging.Formatter( - '%(name)s|%(filename)s|%(funcName)s|%(lineno)d|' - '%(levelname)s|%(message)s')) + u'%(name)s|%(filename)s|%(funcName)s|%(lineno)d|' + u'%(levelname)s|%(message)s')) logger.addHandler(handler) client = Client('http://foo:bar@example.com/0') @@ -52,10 +49,10 @@ def test_log_location(self): logger.info('Hello World!') lineno = sys._getframe().f_lineno - 1 - items = out.getvalue().strip().decode('utf-8').split('|') - assert items[0] == b'tests.breadcrumbs.tests' - assert items[1].rstrip(b'co') == b'tests.py' - assert items[2] == b'test_log_location' + items = out.getvalue().strip().split('|') + assert items[0] == 'tests.breadcrumbs.tests' + assert items[1].rstrip('co') == 'tests.py' + assert items[2] == 'test_log_location' assert int(items[3]) == lineno - assert items[4] == b'INFO' - assert items[5] == b'Hello World!' + assert items[4] == 'INFO' + assert items[5] == 'Hello World!' From a4c7f0c36b6ba17f736f76e356102fe214348210 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sat, 14 May 2016 01:37:41 +0200 Subject: [PATCH 284/692] More resilient log reporting. --- raven/breadcrumbs.py | 14 +++++++++++--- tests/breadcrumbs/tests.py | 12 ++++++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/raven/breadcrumbs.py b/raven/breadcrumbs.py index a0c91a680..2ef13c590 100644 --- a/raven/breadcrumbs.py +++ b/raven/breadcrumbs.py @@ -78,9 +78,17 @@ def _record_log_breadcrumb(logger, level, msg, *args, **kwargs): return def processor(data): - formatted_msg = text_type(msg) - if args: - formatted_msg = msg % args + formatted_msg = msg + + # If people log bad things, this can happen. Then just don't do + # anything. + try: + formatted_msg = text_type(msg) + if args: + formatted_msg = msg % args + except Exception: + pass + # We do not want to include exc_info as argument because it often # lies (set to a constant value like 1 or True) or even if it's a # tuple it will not be particularly useful for us as we cannot diff --git a/tests/breadcrumbs/tests.py b/tests/breadcrumbs/tests.py index ab424d80b..00fd463a8 100644 --- a/tests/breadcrumbs/tests.py +++ b/tests/breadcrumbs/tests.py @@ -56,3 +56,15 @@ def test_log_location(self): assert int(items[3]) == lineno assert items[4] == 'INFO' assert items[5] == 'Hello World!' + + def test_broken_logging(self): + client = Client('http://foo:bar@example.com/0') + with client.context: + log = logging.getLogger('whatever.foo') + log.info('This is a message with %s. %s!', 42) + crumbs = client.context.breadcrumbs.get_buffer() + + assert len(crumbs) == 1 + assert crumbs[0]['type'] == 'default' + assert crumbs[0]['category'] == 'whatever.foo' + assert crumbs[0]['message'] == 'This is a message with %s. %s!' From c4293b4e0f99e6550446e7e66825984935c6a2c1 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sat, 14 May 2016 01:42:40 +0200 Subject: [PATCH 285/692] Capture down breadcrumb failures --- raven/breadcrumbs.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/raven/breadcrumbs.py b/raven/breadcrumbs.py index 2ef13c590..803c9c58c 100644 --- a/raven/breadcrumbs.py +++ b/raven/breadcrumbs.py @@ -11,6 +11,9 @@ special_logger_handlers = {} +logger = logging.getLogger('raven') + + class BreadcrumbBuffer(object): def __init__(self, limit=100): @@ -39,9 +42,13 @@ def get_buffer(self): rv = [] for idx, (payload, processor) in enumerate(self.buffer): if processor is not None: - processor(payload) + try: + processor(payload) + except Exception: + logger.exception('Failed to process breadcrumbs. Ignored') self.buffer[idx] = (payload, None) - rv.append(payload) + if payload is not None: + rv.append(payload) return rv From d89c36b88a7e00db4d808c5bf586f9084a5ee5aa Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sat, 14 May 2016 02:38:10 +0200 Subject: [PATCH 286/692] Added changelog entry --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 3af9d515b..6a0fdeb5c 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 5.17.0 +-------------- + +* if breadcrumbs fail to process due to an error they are now skipped. + Version 5.16.0 -------------- From 17f4e5f04028af9de0dfafe2dcb1a50cbeba2cf5 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sat, 14 May 2016 02:38:21 +0200 Subject: [PATCH 287/692] This is 5.17.0 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 575b11e6c..cf74161d4 100755 --- a/setup.py +++ b/setup.py @@ -97,7 +97,7 @@ def run_tests(self): setup( name='raven', - version='5.16.0', + version='5.17.0', author='Sentry', author_email='hello@getsentry.com', url='https://github.com/getsentry/raven-python', From cd1e38d1138f7058c02a86fc97bd3ea5c89d0437 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 18 May 2016 00:34:57 +0200 Subject: [PATCH 288/692] Add basic deduplication for log messages. --- CHANGES | 6 ++++++ raven/breadcrumbs.py | 23 +++++++++++++++++------ tests/breadcrumbs/tests.py | 25 +++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 6 deletions(-) diff --git a/CHANGES b/CHANGES index 6a0fdeb5c..d4ad800e2 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,9 @@ +Version 5.18.0 +-------------- + +* Breadcrumbs are now attempted to be deduplicated to catch some common + cases where log messages just spam up the breadcrumbs. + Version 5.17.0 -------------- diff --git a/raven/breadcrumbs.py b/raven/breadcrumbs.py index 803c9c58c..6d298e91d 100644 --- a/raven/breadcrumbs.py +++ b/raven/breadcrumbs.py @@ -14,20 +14,29 @@ logger = logging.getLogger('raven') +def event_payload_considered_equal(a, b): + return ( + a['type'] == b['type'] and + a['level'] == b['level'] and + a['message'] == b['message'] and + a['category'] == b['category'] and + a['data'] == b['data'] + ) + + class BreadcrumbBuffer(object): def __init__(self, limit=100): self.buffer = [] self.limit = limit - def record(self, type, timestamp=None, duration=None, level=None, - message=None, category=None, data=None, processor=None): + def record(self, type, timestamp=None, level=None, message=None, + category=None, data=None, processor=None): if timestamp is None: timestamp = time.time() self.buffer.append(({ 'type': type, 'timestamp': timestamp, - 'duration': duration, 'level': level, 'message': message, 'category': category, @@ -46,8 +55,10 @@ def get_buffer(self): processor(payload) except Exception: logger.exception('Failed to process breadcrumbs. Ignored') + payload = None self.buffer[idx] = (payload, None) - if payload is not None: + if payload is not None and \ + (not rv or not event_payload_considered_equal(rv[-1], payload)): rv.append(payload) return rv @@ -63,7 +74,7 @@ def make_buffer(enabled=True): return BlackholeBreadcrumbBuffer() -def record_breadcrumb(type, timestamp=None, duration=None, level=None, +def record_breadcrumb(type, timestamp=None, level=None, message=None, category=None, data=None, processor=None): """Records a breadcrumb for all active clients. This is what integration @@ -73,7 +84,7 @@ def record_breadcrumb(type, timestamp=None, duration=None, level=None, if timestamp is None: timestamp = time.time() for ctx in raven.context.get_active_contexts(): - ctx.breadcrumbs.record(type, timestamp, duration, level, message, + ctx.breadcrumbs.record(type, timestamp, level, message, category, data, processor) diff --git a/tests/breadcrumbs/tests.py b/tests/breadcrumbs/tests.py index 00fd463a8..79b814baa 100644 --- a/tests/breadcrumbs/tests.py +++ b/tests/breadcrumbs/tests.py @@ -68,3 +68,28 @@ def test_broken_logging(self): assert crumbs[0]['type'] == 'default' assert crumbs[0]['category'] == 'whatever.foo' assert crumbs[0]['message'] == 'This is a message with %s. %s!' + + def test_dedup_logging(self): + client = Client('http://foo:bar@example.com/0') + with client.context: + log = logging.getLogger('whatever.foo') + log.info('This is a message with %s!', 42) + log.info('This is a message with %s!', 42) + log.info('This is a message with %s!', 42) + log.info('This is a message with %s!', 23) + log.info('This is a message with %s!', 23) + log.info('This is a message with %s!', 23) + log.info('This is a message with %s!', 42) + crumbs = client.context.breadcrumbs.get_buffer() + + print crumbs + assert len(crumbs) == 3 + assert crumbs[0]['type'] == 'default' + assert crumbs[0]['category'] == 'whatever.foo' + assert crumbs[0]['message'] == 'This is a message with 42!' + assert crumbs[1]['type'] == 'default' + assert crumbs[1]['category'] == 'whatever.foo' + assert crumbs[1]['message'] == 'This is a message with 23!' + assert crumbs[2]['type'] == 'default' + assert crumbs[2]['category'] == 'whatever.foo' + assert crumbs[2]['message'] == 'This is a message with 42!' From 520746d847682508e946811e8a05b2098079dadf Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 18 May 2016 01:21:53 +0200 Subject: [PATCH 289/692] Kill stray print --- tests/breadcrumbs/tests.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/breadcrumbs/tests.py b/tests/breadcrumbs/tests.py index 79b814baa..01825a46a 100644 --- a/tests/breadcrumbs/tests.py +++ b/tests/breadcrumbs/tests.py @@ -82,7 +82,6 @@ def test_dedup_logging(self): log.info('This is a message with %s!', 42) crumbs = client.context.breadcrumbs.get_buffer() - print crumbs assert len(crumbs) == 3 assert crumbs[0]['type'] == 'default' assert crumbs[0]['category'] == 'whatever.foo' From 8892bb12882a001f0ccb4f94df3c957da0a6ee61 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 18 May 2016 23:51:24 +0200 Subject: [PATCH 290/692] Handle user errors from django better. This fixes #771 --- raven/contrib/django/client.py | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/raven/contrib/django/client.py b/raven/contrib/django/client.py index 00a555cbf..3f2bd415c 100644 --- a/raven/contrib/django/client.py +++ b/raven/contrib/django/client.py @@ -142,19 +142,25 @@ def get_user_info(self, user): not user.is_authenticated(): return None - user_info = { - 'id': user.pk, - } - - if hasattr(user, 'email'): - user_info['email'] = user.email - - if hasattr(user, 'get_username'): - user_info['username'] = user.get_username() - elif hasattr(user, 'username'): - user_info['username'] = user.username - - return user_info + user_info = {} + try: + user_info['id'] = user.pk + + if hasattr(user, 'email'): + user_info['email'] = user.email + + if hasattr(user, 'get_username'): + user_info['username'] = user.get_username() + elif hasattr(user, 'username'): + user_info['username'] = user.username + except Exception: + # We expect that user objects can be somewhat broken at times + # and try to just handle as much as possible and ignore errors + # as good as possible here. + pass + + if user_info: + return user_info def get_data_from_request(self, request): result = {} From 75fd8e1883a8fc1014e1d30fce90aefc3901da87 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 19 May 2016 18:54:18 +0200 Subject: [PATCH 291/692] Streamlined the public breadcrumb api --- raven/base.py | 4 ++-- raven/breadcrumbs.py | 25 +++++++++++++++---------- raven/contrib/django/client.py | 2 +- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/raven/base.py b/raven/base.py index 6c06d61aa..c07edceec 100644 --- a/raven/base.py +++ b/raven/base.py @@ -831,14 +831,14 @@ def captureExceptions(self, **kwargs): DeprecationWarning) return self.context(**kwargs) - def captureBreadcrumb(self, type, *args, **kwargs): + def captureBreadcrumb(self, *args, **kwargs): """Records a breadcrumb with the current context. They will be sent with the next event. """ # Note: framework integration should not call this method but # instead use the raven.breadcrumbs.record_breadcrumb function # which will record to the correct client automatically. - self.context.breadcrumbs.record(type, *args, **kwargs) + self.context.breadcrumbs.record(*args, **kwargs) capture_breadcrumb = captureBreadcrumb diff --git a/raven/breadcrumbs.py b/raven/breadcrumbs.py index 6d298e91d..fa11da3e7 100644 --- a/raven/breadcrumbs.py +++ b/raven/breadcrumbs.py @@ -30,12 +30,12 @@ def __init__(self, limit=100): self.buffer = [] self.limit = limit - def record(self, type, timestamp=None, level=None, message=None, - category=None, data=None, processor=None): + def record(self, timestamp=None, level=None, message=None, + category=None, data=None, type=None, processor=None): if timestamp is None: timestamp = time.time() self.buffer.append(({ - 'type': type, + 'type': type or 'default', 'timestamp': timestamp, 'level': level, 'message': message, @@ -74,13 +74,19 @@ def make_buffer(enabled=True): return BlackholeBreadcrumbBuffer() -def record_breadcrumb(type, timestamp=None, level=None, - message=None, category=None, data=None, - processor=None): +def record_breadcrumb(type, *args, **kwargs): + # Legacy alias + kwargs['type'] = type + return record(*args, **kwargs) + + +def record(message=None, timestamp=None, level=None, category=None, + data=None, type=None, processor=None): """Records a breadcrumb for all active clients. This is what integration code should use rather than invoking the `captureBreadcrumb` method on a specific client. """ + if timestamp is None: timestamp = time.time() for ctx in raven.context.get_active_contexts(): @@ -118,7 +124,7 @@ def processor(data): 'level': logging.getLevelName(level).lower(), 'data': kwargs, }) - record_breadcrumb('default', processor=processor) + record(processor=processor) def _wrap_logging_method(meth, level=None): @@ -261,7 +267,7 @@ def _hook_requests(): def send(self, request, *args, **kwargs): def _record_request(response): - record_breadcrumb('http', category='requests', data={ + record(type='http', category='requests', data={ 'url': request.url, 'method': request.method, 'status_code': response and response.status_code or None, @@ -313,8 +319,7 @@ def processor(data): } data['data'].update(status) return data - record_breadcrumb('http', category='requests', - processor=processor) + record(type='http', category='requests', processor=processor) return real_putrequest(self, method, url, *args, **kwargs) def getresponse(self, *args, **kwargs): diff --git a/raven/contrib/django/client.py b/raven/contrib/django/client.py index 00a555cbf..a031953eb 100644 --- a/raven/contrib/django/client.py +++ b/raven/contrib/django/client.py @@ -97,7 +97,7 @@ def processor(data): 'duration': duration, 'category': 'query', }) - breadcrumbs.record_breadcrumb('default', processor=processor) + breadcrumbs.record(processor=processor) def record_many_sql(vendor, alias, start, sql, param_list): duration = time.time() - start From 1e2051395ed2430714ab7a907f1fb40e0d1f47a5 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 19 May 2016 19:00:55 +0200 Subject: [PATCH 292/692] Added some santiy cehcks for breadcrumb recording --- raven/breadcrumbs.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/raven/breadcrumbs.py b/raven/breadcrumbs.py index fa11da3e7..53c16ef69 100644 --- a/raven/breadcrumbs.py +++ b/raven/breadcrumbs.py @@ -32,6 +32,9 @@ def __init__(self, limit=100): def record(self, timestamp=None, level=None, message=None, category=None, data=None, type=None, processor=None): + if not (message or data or processor): + raise ValueError('You must pass either `message`, `data`, ' + 'or `processor`') if timestamp is None: timestamp = time.time() self.buffer.append(({ @@ -86,7 +89,6 @@ def record(message=None, timestamp=None, level=None, category=None, code should use rather than invoking the `captureBreadcrumb` method on a specific client. """ - if timestamp is None: timestamp = time.time() for ctx in raven.context.get_active_contexts(): From c978ba905506622073b668371165fb8a356f9551 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 19 May 2016 19:04:53 +0200 Subject: [PATCH 293/692] Improved breadcrumb documentation --- docs/api.rst | 9 +++++++++ docs/breadcrumbs.rst | 32 ++++++++++++++++++++++---------- 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index 94a7633f2..9592b7e29 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -84,6 +84,15 @@ Client except Exception: client.captureException() + .. py:method:: captureBreadcrumb(message=None, timestamp=None, + level=None, category=None, data=None, + type=None, processor=None) + + Manually captures a breadcrumb in the internal buffer for the + current client's context. Instead of using this method you are + encouraged to instead use the :py:func:`raven.breadcrumbs.record` + function which records to the correct client automatically. + .. py:method:: send(**data) Accepts all data parameters and serializes them, then sends then diff --git a/docs/breadcrumbs.rst b/docs/breadcrumbs.rst index df0dcec1a..2ce9997f3 100644 --- a/docs/breadcrumbs.rst +++ b/docs/breadcrumbs.rst @@ -63,33 +63,45 @@ the :py:func:`~raven.breadcrumbs.ignore_logger` and `True` the default handling kicks in. Typically it makes sense to invoke - :py:func:`~raven.breadcrumbs.record_breadcrumb` from it. + :py:func:`~raven.breadcrumbs.record` from it. Manually Emitting Breadcrumbs ----------------------------- If you want to manually record breadcrumbs the most convenient way to do -that is to use the :py:func:`~reaven.breadcrumbs.record_breadcrumb` function +that is to use the :py:func:`~reaven.breadcrumbs.record` function which will automatically record the crumbs with the clients that are working with the current thread. This is more convenient than to call the `captureBreadcrumb` method on the client itself as you need to hold a reference to that. -.. py:function:: raven.breadcrumbs.record_breadcrumb(**options) +.. py:function:: raven.breadcrumbs.record(**options) This function accepts keyword arguments matching the attributes of a breadcrumb. For more information see :doc:`/clientdev/interfaces`. Additionally a `processor` callback can be passed which will be invoked to process the data if the crumb was not rejected. + The most important parameters: + + `message`: + the message that should be recorded. + `data`: + a data dictionary that should be recorded with the event. + `category`: + The category for this error. This can be a module name, or just a + string that clearly identifies the crumb (eg: `http`, `rpc`, etc.) + `type`: + can override the type if a special type should be sent to Sentry. + Example: .. sourcecode:: python - from raven.breadcrumbs import record_breadcrumb + from raven import breadcrumbs - record_breadcrumb('default', message='This is an important message', - category='my_module', level='warning') + breadcrumbs.record(message='This is an important message', + category='my_module', level='warning') Because crumbs go into a ring buffer, often it can be useful to defer processing of expensive operations until the crumb is actually needed. @@ -98,14 +110,14 @@ modifications: .. sourcecode:: python - from raven.breadcrumbs import record_breadcrumb + from raven.breadcrumbs import record def process_crumb(data): data['data'] = compute_expensive_data() - record_breadcrumb('default', message='This is an important message', - category='my_module', level='warning', - processor=process_crumb) + breadcrumbs.record(message='This is an important message', + category='my_module', level='warning', + processor=process_crumb) Context Thread Binding ---------------------- From ecedc08051d67da5762720a3b38a24e1f7b299d3 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 19 May 2016 19:29:55 +0200 Subject: [PATCH 294/692] Automatically activate context on merge --- CHANGES | 2 ++ raven/context.py | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index d4ad800e2..c2cc37bfa 100644 --- a/CHANGES +++ b/CHANGES @@ -3,6 +3,8 @@ Version 5.18.0 * Breadcrumbs are now attempted to be deduplicated to catch some common cases where log messages just spam up the breadcrumbs. +* Improvements to the public breadcrumbs API and stabilized some. +* Automatically activate the context on calls to `merge` Version 5.17.0 -------------- diff --git a/raven/context.py b/raven/context.py index d27572a7a..d4a64b99e 100644 --- a/raven/context.py +++ b/raven/context.py @@ -103,7 +103,9 @@ def deactivate(self): except AttributeError: pass - def merge(self, data): + def merge(self, data, activate=True): + if activate: + self.activate() d = self.data for key, value in iteritems(data): if key in ('tags', 'extra'): From ef88cd68d1c10d70ccec728d5925ed27eb675ea4 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 19 May 2016 19:30:19 +0200 Subject: [PATCH 295/692] Updated documentation on context binding --- docs/api.rst | 28 +++++++++++++++++++++++++--- docs/breadcrumbs.rst | 17 ++++++++++------- 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index 9592b7e29..e3c7df51e 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -147,12 +147,26 @@ Context This means that you can modify this object over time to feed it with more appropriate information. - .. py:method:: merge(data) + .. py:method:: activate() + + Binds the context to the current thread. This normally happens + automatically on first usage but if the context was deactivated + then this needs to be called again to bind it again. Only if a + context is bound to the thread breadcrumbs will be recorded. + + .. py:method:: deactivate() + + This deactivates the thread binding of the context. In particular + it means that breadcrumbs of the current thread are no longer + recorded to this context. + + .. py:method:: merge(data, activate=True) Performs a merge of the current data in the context and the new - data provided. + data provided. This also automatically activates the context + by default. - .. py:method:: clear() + .. py:method:: clear(deactivate=None) Clears the context. It's important that you make sure to call this when you reuse the thread for something else. For instance @@ -161,3 +175,11 @@ Context Otherwise you run at risk of seeing incorrect information after the first use of the thread. + + Optionally `deactivate` parameter controls if the context should + automatically be deactivated. The default behavior is to + deactivate if the context was not created for the main thread. + + The context can also be used as a context manager. In that case + :py:meth:`activate` is called on enter and :py:meth:`deactivate` is + called on exit. diff --git a/docs/breadcrumbs.rst b/docs/breadcrumbs.rst index 2ce9997f3..ec304dd0e 100644 --- a/docs/breadcrumbs.rst +++ b/docs/breadcrumbs.rst @@ -122,13 +122,16 @@ modifications: Context Thread Binding ---------------------- -Typically when you use breadcrumbs from a framework integration -breadcrumbs work automatically. However there are cases where you want to -do this yourself. If a context is not bound to the thread breadcrumbs -will not be recorded. The thread that created the client (typically the -main thread) is bound by default. +Typically breadcrumbs work automatically. Both the raven client itself as +well as the framework integrations configure it to work as good as +possible out of the box. In some advanced scenarios however you might not +see breadcrumbs show up. -To bind the context you can use the `activate()` method on it:: +This can be because the context needs to be bound to a thread. The +default behavior is to automatically bind the context and to unbind it +when the context is cleared. + +To manually bind the context you can use the `activate()` method on it:: client.context.activate() @@ -143,4 +146,4 @@ Alternatively you can use the context with the `with` statement:: The context is automatically deactivated if it's cleared unless it's managed from the main thread. Likewise raven will attempt to auto -activate the client. +activate the client the first time a thread needs it. From ce09f6be370e83f0f094af8a66bb8ce3626a7589 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 19 May 2016 19:33:58 +0200 Subject: [PATCH 296/692] Kill context thread binding docs. It's on api anyways --- docs/breadcrumbs.rst | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/docs/breadcrumbs.rst b/docs/breadcrumbs.rst index ec304dd0e..71ab5b1f4 100644 --- a/docs/breadcrumbs.rst +++ b/docs/breadcrumbs.rst @@ -118,32 +118,3 @@ modifications: breadcrumbs.record(message='This is an important message', category='my_module', level='warning', processor=process_crumb) - -Context Thread Binding ----------------------- - -Typically breadcrumbs work automatically. Both the raven client itself as -well as the framework integrations configure it to work as good as -possible out of the box. In some advanced scenarios however you might not -see breadcrumbs show up. - -This can be because the context needs to be bound to a thread. The -default behavior is to automatically bind the context and to unbind it -when the context is cleared. - -To manually bind the context you can use the `activate()` method on it:: - - client.context.activate() - -To unbind the context you can `deactivate()` it:: - - client.context.deactivate() - -Alternatively you can use the context with the `with` statement:: - - with client.context: - ... - -The context is automatically deactivated if it's cleared unless it's -managed from the main thread. Likewise raven will attempt to auto -activate the client the first time a thread needs it. From 105fdf3fa5091ab1eccb894cfad34560e097622d Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 19 May 2016 19:42:02 +0200 Subject: [PATCH 297/692] This is 5.18.0 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index cf74161d4..013698b46 100755 --- a/setup.py +++ b/setup.py @@ -97,7 +97,7 @@ def run_tests(self): setup( name='raven', - version='5.17.0', + version='5.18.0', author='Sentry', author_email='hello@getsentry.com', url='https://github.com/getsentry/raven-python', From 08832aa36ec5aeada388fe7bdf28b9bd3f6490dc Mon Sep 17 00:00:00 2001 From: David Cramer Date: Tue, 24 May 2016 12:59:40 -0700 Subject: [PATCH 298/692] Add fingerprint to soft timeouts in Celery --- raven/contrib/celery/__init__.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/raven/contrib/celery/__init__.py b/raven/contrib/celery/__init__.py index ba6186e9d..567459641 100644 --- a/raven/contrib/celery/__init__.py +++ b/raven/contrib/celery/__init__.py @@ -9,6 +9,7 @@ import logging +from celery.exceptions import SoftTimeLimitExceeded from celery.signals import after_setup_logger, task_failure from raven.handlers.logging import SentryHandler @@ -24,15 +25,21 @@ def filter(self, record): def register_signal(client): - def process_failure_signal(sender, task_id, args, kwargs, **kw): + def process_failure_signal(sender, task_id, args, kwargs, einfo, **kw): # This signal is fired inside the stack so let raven do its magic + if isinstance(einfo.exception, SoftTimeLimitExceeded): + fingerprint = ['celery', 'SoftTimeLimitExceeded', sender] + else: + fingerprint = None client.captureException( extra={ 'task_id': task_id, 'task': sender, 'args': args, 'kwargs': kwargs, - }) + }, + fingerprint=fingerprint, + ) task_failure.connect(process_failure_signal, weak=False) From 59218dd73470b5f5e726097ef000f4a222441f45 Mon Sep 17 00:00:00 2001 From: Gilles Dubuc Date: Thu, 26 May 2016 10:50:27 +0200 Subject: [PATCH 299/692] Make implicit minimum Tornado version explicit TornadoTransportTests.test__sending_successfully_calls_success_callback uses gen.maybe_future, which appeared in Tornado 4.0 TornadoAsyncClientTestCase.test_sending_to_unresponsive_sentry_server_logs_error and TornadoTransportTests.test__sending_with_error_calls_error_callback use gen.sleep, which appeared in Tornado 4.1 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 013698b46..d14bea73c 100755 --- a/setup.py +++ b/setup.py @@ -69,7 +69,7 @@ 'pytest-django==2.9.1', 'pytest-timeout==0.4', 'requests', - 'tornado', + 'tornado>=4.1', 'webob', 'webtest', 'anyjson', From 82c8d9f4370617e98c5212764936104ec47bfbc7 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 26 May 2016 11:14:47 -0700 Subject: [PATCH 300/692] Prioritize trimming system frames This pulls in the functionality from Sentry for ``slim_frame_data``. @getsentry/python --- raven/utils/stacks.py | 46 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 38 insertions(+), 8 deletions(-) diff --git a/raven/utils/stacks.py b/raven/utils/stacks.py index bcd302384..cbb3284b9 100644 --- a/raven/utils/stacks.py +++ b/raven/utils/stacks.py @@ -5,7 +5,7 @@ :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 +from __future__ import absolute_import, division import inspect import linecache @@ -198,18 +198,48 @@ def slim_frame_data(frames, frame_allowance=25): Returns ``frames``. """ - frames_len = len(frames) + frames_len = 0 + app_frames = [] + system_frames = [] + for frame in frames: + frames_len += 1 + if frame.get('in_app'): + app_frames.append(frame) + else: + system_frames.append(frame) if frames_len <= frame_allowance: return frames - half_max = int(frame_allowance / 2) + remaining = frames_len - frame_allowance + app_count = len(app_frames) + system_allowance = max(frame_allowance - app_count, 0) + if system_allowance: + half_max = int(system_allowance / 2) + # prioritize trimming system frames + for frame in system_frames[half_max:-half_max]: + frame.pop('vars', None) + frame.pop('pre_context', None) + frame.pop('post_context', None) + remaining -= 1 + + else: + for frame in system_frames: + frame.pop('vars', None) + frame.pop('pre_context', None) + frame.pop('post_context', None) + remaining -= 1 + + if not remaining: + return frames + + app_allowance = app_count - remaining + half_max = int(app_allowance / 2) - for n in range(half_max, frames_len - half_max): - # remove heavy components - frames[n].pop('vars', None) - frames[n].pop('pre_context', None) - frames[n].pop('post_context', None) + for frame in app_frames[half_max:-half_max]: + frame.pop('vars', None) + frame.pop('pre_context', None) + frame.pop('post_context', None) return frames From 329afd79982e0851f3fe76dc05cff0b14b79d251 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 26 May 2016 11:29:20 -0700 Subject: [PATCH 301/692] Relax codecov hook --- codecov.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 codecov.yml diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 000000000..5a432480a --- /dev/null +++ b/codecov.yml @@ -0,0 +1,14 @@ +coverage: + status: + project: + default: + target: 0% + patch: + default: + target: 0% + ignore: + - hooks/.* + - ci/.* + - docs/.* + +comment: false From 7ab26cba348052fcf7089520b80f343adf348bd3 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 27 May 2016 18:24:12 +0200 Subject: [PATCH 302/692] Remove duration from SQL crumbs --- CHANGES | 7 +++++++ raven/contrib/django/client.py | 1 - 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index c2cc37bfa..4a9223592 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,10 @@ +Version 5.19.0 +-------------- + +* remove duration from SQL query breadcrumbs. This was not rendered + in the UI and will come back in future versions of Sentry with a + different interface. + Version 5.18.0 -------------- diff --git a/raven/contrib/django/client.py b/raven/contrib/django/client.py index a031953eb..77e23d2bc 100644 --- a/raven/contrib/django/client.py +++ b/raven/contrib/django/client.py @@ -94,7 +94,6 @@ def processor(data): # 'default') ? data.update({ 'message': real_sql, - 'duration': duration, 'category': 'query', }) breadcrumbs.record(processor=processor) From 6b5044ab72040aa5678f9c522dacb7099cbe8829 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 27 May 2016 18:37:41 +0200 Subject: [PATCH 303/692] Resolved an issue with badly recorded crumbs. --- CHANGES | 1 + raven/breadcrumbs.py | 4 ++-- tests/breadcrumbs/tests.py | 33 ++++++++++++++++++++++++++++++--- 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/CHANGES b/CHANGES index 4a9223592..6c6e820cb 100644 --- a/CHANGES +++ b/CHANGES @@ -4,6 +4,7 @@ Version 5.19.0 * remove duration from SQL query breadcrumbs. This was not rendered in the UI and will come back in future versions of Sentry with a different interface. +* resolved a bug that caused crumbs to be recorded incorrectly. Version 5.18.0 -------------- diff --git a/raven/breadcrumbs.py b/raven/breadcrumbs.py index 53c16ef69..96d33ef9f 100644 --- a/raven/breadcrumbs.py +++ b/raven/breadcrumbs.py @@ -92,8 +92,8 @@ def record(message=None, timestamp=None, level=None, category=None, if timestamp is None: timestamp = time.time() for ctx in raven.context.get_active_contexts(): - ctx.breadcrumbs.record(type, timestamp, level, message, - category, data, processor) + ctx.breadcrumbs.record(timestamp, level, message, category, + data, type, processor) def _record_log_breadcrumb(logger, level, msg, *args, **kwargs): diff --git a/tests/breadcrumbs/tests.py b/tests/breadcrumbs/tests.py index 01825a46a..008447920 100644 --- a/tests/breadcrumbs/tests.py +++ b/tests/breadcrumbs/tests.py @@ -4,7 +4,7 @@ from raven.utils.testutils import TestCase from raven.base import Client -from raven.breadcrumbs import record_breadcrumb +from raven import breadcrumbs from io import StringIO @@ -16,8 +16,8 @@ def test_crumb_buffer(self): client = Client('http://foo:bar@example.com/0', enable_breadcrumbs=enable) with client.context: - record_breadcrumb('foo', data={'bar': 'baz'}, - message='aha', category='huhu') + breadcrumbs.record(type='foo', data={'bar': 'baz'}, + message='aha', category='huhu') crumbs = client.context.breadcrumbs.get_buffer() assert len(crumbs) == enable @@ -92,3 +92,30 @@ def test_dedup_logging(self): assert crumbs[2]['type'] == 'default' assert crumbs[2]['category'] == 'whatever.foo' assert crumbs[2]['message'] == 'This is a message with 42!' + + def test_manual_record(self): + client = Client('http://foo:bar@example.com/0') + with client.context: + def processor(data): + assert data['message'] == 'whatever' + assert data['level'] == 'warning' + assert data['category'] == 'category' + assert data['type'] == 'the_type' + assert data['data'] == {'foo': 'bar'} + data['data']['extra'] = 'something' + + breadcrumbs.record(message='whatever', + level='warning', + category='category', + data={'foo': 'bar'}, + type='the_type', + processor=processor) + + crumbs = client.context.breadcrumbs.get_buffer() + assert len(crumbs) == 1 + data = crumbs[0] + assert data['message'] == 'whatever' + assert data['level'] == 'warning' + assert data['category'] == 'category' + assert data['type'] == 'the_type' + assert data['data'] == {'foo': 'bar', 'extra': 'something'} From f06185a3b22261231d7ca20c3aa5baf9c01756dd Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 27 May 2016 18:37:57 +0200 Subject: [PATCH 304/692] 5.19.0 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 013698b46..02d87a714 100755 --- a/setup.py +++ b/setup.py @@ -97,7 +97,7 @@ def run_tests(self): setup( name='raven', - version='5.18.0', + version='5.19.0', author='Sentry', author_email='hello@getsentry.com', url='https://github.com/getsentry/raven-python', From 1b84e267f5d2084d3918abb70ecb0daf9c6063ec Mon Sep 17 00:00:00 2001 From: Adam Chainz Date: Sun, 29 May 2016 21:02:11 +0100 Subject: [PATCH 305/692] Convert readthedocs link for their .org -> .io migration for hosted projects MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As per [their blog post of the 27th April](https://blog.readthedocs.com/securing-subdomains/) ‘Securing subdomains’: > Starting today, Read the Docs will start hosting projects from subdomains on the domain readthedocs.io, instead of on readthedocs.org. This change addresses some security concerns around site cookies while hosting user generated data on the same domain as our dashboard. Test Plan: Manually visited all the links I’ve modified. --- docs/conf.py | 2 +- docs/integrations/django.rst | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index f5e2d2ef1..1f80ba4b8 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -93,7 +93,7 @@ intersphinx_mapping = { 'http://docs.python.org/2.7': None, 'django': ('http://docs.djangoproject.com/en/dev/', 'http://docs.djangoproject.com/en/dev/_objects/'), - 'http://raven.readthedocs.org/en/latest': None + 'https://raven.readthedocs.io/en/latest': None } diff --git a/docs/integrations/django.rst b/docs/integrations/django.rst index 28598c21c..1219697fd 100644 --- a/docs/integrations/django.rst +++ b/docs/integrations/django.rst @@ -343,7 +343,7 @@ Circus ~~~~~~ If you are running Django with `circus `_ and -`chaussette `_ you will also need +`chaussette `_ you will also need to add a hook to circus to activate Raven:: from django.conf import settings diff --git a/setup.py b/setup.py index 02d87a714..c2e713d29 100755 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ full out-of-the-box support for many of the popular frameworks, including `Django `_, `Flask `_, and `Pylons `_. Raven also includes drop-in support for any -`WSGI `_-compatible web application. +`WSGI `_-compatible web application. """ # Hack to prevent stupid "TypeError: 'NoneType' object is not callable" error From 7035bcd928028f04f03f1aa7ac8675bfb80b975f Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 30 May 2016 23:03:06 +0200 Subject: [PATCH 306/692] Fixed a bug with empty params in query crumbs --- CHANGES | 6 ++++++ raven/contrib/django/client.py | 7 +++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 6c6e820cb..fc94b115e 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,9 @@ +Version 5.20.0 +-------------- + +* fixed an error that could cause certain SQL queries to fail to + record as breadcrumbs if no parameters were supplied. + Version 5.19.0 -------------- diff --git a/raven/contrib/django/client.py b/raven/contrib/django/client.py index 77e23d2bc..ad2f6e423 100644 --- a/raven/contrib/django/client.py +++ b/raven/contrib/django/client.py @@ -51,8 +51,11 @@ def format_sql(sql, params): if isinstance(params, dict): conv = _FormatConverter(params) - sql = sql % conv - params = conv.params + if params: + sql = sql % conv + params = conv.params + else: + params = () for param in params or (): if param is None: From 007d1837edf1252ad259cf34b22a703caf0424a0 Mon Sep 17 00:00:00 2001 From: Gilles Dubuc Date: Thu, 26 May 2016 11:09:27 +0200 Subject: [PATCH 307/692] Don't use simplejson It behaves differently than stdlib json. In fact, when simplejson is present, the JSONTest.test_decimal test fails, as the output is {"decimal": 123.45} instead of {"decimal": "Decimal(\'123.45\')"} --- raven/utils/json.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/raven/utils/json.py b/raven/utils/json.py index 2631d4fd5..71fefe139 100644 --- a/raven/utils/json.py +++ b/raven/utils/json.py @@ -11,10 +11,7 @@ import codecs import datetime import uuid -try: - import simplejson as json -except ImportError: - import json +import json try: JSONDecodeError = json.JSONDecodeError From 1caf6147b1acf3db0a747fecdbda960ad756fda8 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 8 Jun 2016 23:49:42 +0200 Subject: [PATCH 308/692] 5.20.0 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index c2e713d29..ad4ea7afe 100755 --- a/setup.py +++ b/setup.py @@ -97,7 +97,7 @@ def run_tests(self): setup( name='raven', - version='5.19.0', + version='5.20.0', author='Sentry', author_email='hello@getsentry.com', url='https://github.com/getsentry/raven-python', From 3996bf2a4d0704e060520c30cf5901e4ae055b6a Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 9 Jun 2016 15:27:36 -0700 Subject: [PATCH 309/692] Add formatted attribute to message events --- raven/events.py | 1 + tests/transport/tests.py | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/raven/events.py b/raven/events.py index e3dc25d62..5398f83af 100644 --- a/raven/events.py +++ b/raven/events.py @@ -106,6 +106,7 @@ def capture(self, message, params=(), formatted=None, **kwargs): self.name: { 'message': message, 'params': self.transform(params), + 'formatted': formatted, }, } if 'message' not in data: diff --git a/tests/transport/tests.py b/tests/transport/tests.py index 3c5d57a0b..2a69cf0b2 100644 --- a/tests/transport/tests.py +++ b/tests/transport/tests.py @@ -77,7 +77,11 @@ def test_build_then_send(self): msg = c.build_msg('raven.events.Message', message='foo', date=d) expected = { 'project': '1', - 'sentry.interfaces.Message': {'message': 'foo', 'params': ()}, + 'sentry.interfaces.Message': { + 'message': 'foo', + 'params': (), + 'formatted': None, + }, 'server_name': 'test_server', 'level': 40, 'tags': {}, From c441cf0b6ad46408a53ff15a8e82ca45c40389a1 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 16 Jun 2016 14:57:14 +0200 Subject: [PATCH 310/692] Fill in empty filename if django fails to give one. This fixes #791 --- raven/contrib/django/utils.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/raven/contrib/django/utils.py b/raven/contrib/django/utils.py index 81a43bcc2..07cb1a9ef 100644 --- a/raven/contrib/django/utils.py +++ b/raven/contrib/django/utils.py @@ -47,6 +47,9 @@ def get_data_from_template(source, debug=None): else: raise TypeError('Source or debug needed') + if filename is None: + filename = '' + pre_context = source_lines[max(lineno - 3, 0):lineno] post_context = source_lines[(lineno + 1):(lineno + 4)] context_line = source_lines[lineno] From 4ad5fe62f6eedd1a451057c25eed468a91cc32ec Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 16 Jun 2016 21:12:14 +0200 Subject: [PATCH 311/692] 5.21.0 --- CHANGES | 8 ++++++++ setup.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index fc94b115e..c75e19a15 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,11 @@ +Version 5.21.0 +-------------- + +* Add formatted attribute to message events +* Fill in empty filename if django fails to give one for + template information on newer Django versions with disabled + debug mode. + Version 5.20.0 -------------- diff --git a/setup.py b/setup.py index ad4ea7afe..54b664b5e 100755 --- a/setup.py +++ b/setup.py @@ -97,7 +97,7 @@ def run_tests(self): setup( name='raven', - version='5.20.0', + version='5.21.0', author='Sentry', author_email='hello@getsentry.com', url='https://github.com/getsentry/raven-python', From 667fb9daaafecea8e03879334bd68f8583544a26 Mon Sep 17 00:00:00 2001 From: Matthew Seal Date: Mon, 20 Jun 2016 15:56:17 -0700 Subject: [PATCH 312/692] Added IOError to get stack info for graceful zip import errors --- raven/utils/stacks.py | 2 +- tests/utils/stacks/tests.py | 33 +++++++++++++++++++++++++++++++-- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/raven/utils/stacks.py b/raven/utils/stacks.py index cbb3284b9..6ae096267 100644 --- a/raven/utils/stacks.py +++ b/raven/utils/stacks.py @@ -30,7 +30,7 @@ def get_lines_from_file(filename, lineno, context_lines, if loader is not None and hasattr(loader, "get_source"): try: source = loader.get_source(module_name) - except ImportError: + except (ImportError, IOError): # Traceback (most recent call last): # File "/Users/dcramer/Development/django-sentry/sentry/client/handlers.py", line 31, in emit # get_client().create_from_record(record, request=request) diff --git a/tests/utils/stacks/tests.py b/tests/utils/stacks/tests.py index 4d1bc364c..a0cc5beef 100644 --- a/tests/utils/stacks/tests.py +++ b/tests/utils/stacks/tests.py @@ -2,10 +2,10 @@ from __future__ import unicode_literals import six +import os.path from mock import Mock from raven.utils.testutils import TestCase - from raven.utils.stacks import get_culprit, get_stack_info, get_lines_from_file @@ -95,11 +95,40 @@ def test_frame_allowance(self): assert results['frames'][8]['filename'] == '8' assert results['frames'][9]['filename'] == '9' +class FailLoader(): + ''' + Recreating the built-in loaders from a fake stack trace was brittle. + This method ensures its testing the path where the loader is defined + but fails with known exceptions. + ''' + def get_source(self, module_name): + if '.py' in module_name: + raise ImportError('Cannot load .py files') + elif '.zip' in module_name: + raise IOError('Cannot load .zip files') + else: + raise ValueError('Invalid file extension') class GetLineFromFileTest(TestCase): + def setUp(self): + self.loader = FailLoader() + def test_non_ascii_file(self): - import os.path filename = os.path.join(os.path.dirname(__file__), 'utf8_file.txt') self.assertEqual( get_lines_from_file(filename, 3, 1), (['Some code here'], '', ['lorem ipsum'])) + + def test_missing_zip_get_source(self): + filename = 'does_not_exist.zip' + module = 'not.zip.loadable' + self.assertEqual( + get_lines_from_file(filename, 3, 1, self.loader, module), + (None, None, None)) + + def test_missing_get_source(self): + filename = 'does_not_exist.py' + module = 'not.py.loadable' + self.assertEqual( + get_lines_from_file(filename, 3, 1, self.loader, module), + (None, None, None)) From 0e1a27441d6e40a2d87256477975ba3935d74161 Mon Sep 17 00:00:00 2001 From: Damien Date: Thu, 16 Jun 2016 15:18:38 +0200 Subject: [PATCH 313/692] add type check for sentry initialization for flask --- raven/contrib/flask.py | 3 +++ tests/contrib/flask/tests.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/raven/contrib/flask.py b/raven/contrib/flask.py index 8086d7f93..c23b92bba 100644 --- a/raven/contrib/flask.py +++ b/raven/contrib/flask.py @@ -105,6 +105,9 @@ class Sentry(object): def __init__(self, app=None, client=None, client_cls=Client, dsn=None, logging=False, logging_exclusions=None, level=logging.NOTSET, wrap_wsgi=None, register_signal=True): + if client and not isinstance(client, Client): + raise TypeError('client should an instance of Client') + self.dsn = dsn self.logging = logging self.logging_exclusions = logging_exclusions diff --git a/tests/contrib/flask/tests.py b/tests/contrib/flask/tests.py index ab7619f30..05302163e 100644 --- a/tests/contrib/flask/tests.py +++ b/tests/contrib/flask/tests.py @@ -283,6 +283,9 @@ def test_logging_setup_with_exclusion_list(self): some_other_logger = logging.getLogger("some_other_logger") self.assertTrue(some_other_logger.propagate) + def test_check_client_type(self): + self.assertRaises(TypeError, lambda _: Sentry(self.app, "oops, I'm putting my DSN instead")) + class FlaskLoginTest(BaseTest): From 5d863173772cb53edcf95c7ba83060ebeac90c38 Mon Sep 17 00:00:00 2001 From: Quentin Pradet Date: Thu, 23 Jun 2016 09:15:42 +0400 Subject: [PATCH 314/692] Fix Django LOGGING example root logger --- 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 1219697fd..c4b09a40a 100644 --- a/docs/integrations/django.rst +++ b/docs/integrations/django.rst @@ -84,10 +84,6 @@ 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 ' @@ -107,6 +103,10 @@ 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 169dd04ffbc604d2553dd0ff0c6f3a3449758476 Mon Sep 17 00:00:00 2001 From: Christoph Heer Date: Mon, 27 Jun 2016 12:43:41 +0200 Subject: [PATCH 315/692] Name AsyncWorker thread --- raven/transport/threaded.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/transport/threaded.py b/raven/transport/threaded.py index 7bab84568..33777b497 100644 --- a/raven/transport/threaded.py +++ b/raven/transport/threaded.py @@ -117,7 +117,7 @@ def start(self): self._lock.acquire() try: if not self.is_alive(): - self._thread = threading.Thread(target=self._target) + self._thread = threading.Thread(target=self._target, name="raven.AsyncWorker") self._thread.setDaemon(True) self._thread.start() self._thread_for_pid = os.getpid() From 61291827033c352853a3f4aeb33386e8ee645344 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 27 Jun 2016 15:58:45 -0700 Subject: [PATCH 316/692] Enforce max length on lines of code (512 chars) --- raven/utils/stacks.py | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/raven/utils/stacks.py b/raven/utils/stacks.py index cbb3284b9..6c17cadfa 100644 --- a/raven/utils/stacks.py +++ b/raven/utils/stacks.py @@ -62,15 +62,24 @@ def get_lines_from_file(filename, lineno, context_lines, upper_bound = min(lineno + 1 + context_lines, len(source)) try: - pre_context = [line.strip('\r\n') for line in source[lower_bound:lineno]] + pre_context = [ + line.strip('\r\n') + for line in source[lower_bound:lineno] + ] context_line = source[lineno].strip('\r\n') - post_context = [line.strip('\r\n') for line in - source[(lineno + 1):upper_bound]] + post_context = [ + line.strip('\r\n') + for line in source[(lineno + 1):upper_bound] + ] except IndexError: # the file may have changed since it was loaded into memory return None, None, None - return pre_context, context_line, post_context + return ( + slim_string(pre_context), + slim_string(context_line), + slim_string(post_context) + ) def label_from_frame(frame): @@ -243,6 +252,14 @@ def slim_frame_data(frames, frame_allowance=25): return frames +def slim_string(value, length=512): + if not value: + return value + if len(value) > length: + return value[:length - 3] + '...' + return value[:length] + + def get_stack_info(frames, transformer=transform, capture_locals=True, frame_allowance=25): """ From c159f2f071666cf541f046446aa83904d9e6dc42 Mon Sep 17 00:00:00 2001 From: Scott Bragg Date: Wed, 29 Jun 2016 19:02:40 +1000 Subject: [PATCH 317/692] Fixes Django client get_data_from_template where source_lines is just a snippet (eg using Jinja2 engine) --- raven/contrib/django/utils.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/raven/contrib/django/utils.py b/raven/contrib/django/utils.py index 07cb1a9ef..05bae3701 100644 --- a/raven/contrib/django/utils.py +++ b/raven/contrib/django/utils.py @@ -23,12 +23,13 @@ def linebreak_iter(template_source): def get_data_from_template(source, debug=None): if debug is not None: - start = debug['start'] - end = debug['end'] - source_lines = debug['source_lines'] lineno = debug['line'] filename = debug['name'] - culprit = filename.split('/templates/')[-1] + source_lines = [] + source_lines += [''] * (debug['source_lines'][0][0]) + for num, line in debug['source_lines']: + source_lines.append(line) + source_lines += [''] * 4 elif source: origin, (start, end) = source filename = culprit = getattr(origin, 'loadname', None) @@ -49,6 +50,9 @@ def get_data_from_template(source, debug=None): if filename is None: filename = '' + culprit = '' + else: + culprit = filename.split('/templates/')[-1] pre_context = source_lines[max(lineno - 3, 0):lineno] post_context = source_lines[(lineno + 1):(lineno + 4)] From 157630a9bfd2f6e09c1bc074e68206b086f81ba1 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 7 Jul 2016 19:25:41 +0500 Subject: [PATCH 318/692] Strip line numbers if they exist for templates --- raven/contrib/django/utils.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/raven/contrib/django/utils.py b/raven/contrib/django/utils.py index 07cb1a9ef..2b431cc86 100644 --- a/raven/contrib/django/utils.py +++ b/raven/contrib/django/utils.py @@ -22,6 +22,16 @@ def linebreak_iter(template_source): def get_data_from_template(source, debug=None): + def _remove_numbers(items): + rv = [] + for item in items: + # Some debug info from django has tuples in the form (lineno, + # code) instead of just the code there. + if isinstance(item, (list, tuple)) and len(item) == 2: + item = item[1] + rv.append(item) + return rv + if debug is not None: start = debug['start'] end = debug['end'] @@ -50,9 +60,9 @@ def get_data_from_template(source, debug=None): if filename is None: filename = '' - pre_context = source_lines[max(lineno - 3, 0):lineno] - post_context = source_lines[(lineno + 1):(lineno + 4)] - context_line = source_lines[lineno] + pre_context = _remove_numbers(source_lines[max(lineno - 3, 0):lineno]) + post_context = _remove_numbers(source_lines[(lineno + 1):(lineno + 4)]) + context_line = _remove_numbers([source_lines[lineno]])[0] return { 'template': { From 844829514badd83f01e7a0d8bd858d02ed22c07e Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 7 Jul 2016 19:26:04 +0500 Subject: [PATCH 319/692] 5.22.0 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index c75e19a15..823919d1a 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 5.22.0 +-------------- + +* Fixed template reporting not working for certain versions of Django. + Version 5.21.0 -------------- From 6c03e17da2a6ad3482e953d27c7bc51d34ea96b0 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 7 Jul 2016 19:26:04 +0500 Subject: [PATCH 320/692] 5.22.0 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 54b664b5e..6b3175dce 100755 --- a/setup.py +++ b/setup.py @@ -97,7 +97,7 @@ def run_tests(self): setup( name='raven', - version='5.21.0', + version='5.22.0', author='Sentry', author_email='hello@getsentry.com', url='https://github.com/getsentry/raven-python', From fe6068a34cc1326548e73f294ba00c3255155a9b Mon Sep 17 00:00:00 2001 From: Felix Yan Date: Fri, 8 Jul 2016 15:28:49 +0800 Subject: [PATCH 321/692] Update to latest flake8 and use pycodestyle --- hooks/pre-commit.flake8 | 2 +- setup.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/hooks/pre-commit.flake8 b/hooks/pre-commit.flake8 index 1850e9083..d7b907176 100755 --- a/hooks/pre-commit.flake8 +++ b/hooks/pre-commit.flake8 @@ -6,7 +6,7 @@ import sys os.environ['PYFLAKES_NODOCTEST'] = '1' -# pep8.py uses sys.argv to find setup.cfg +# pycodestyle.py uses sys.argv to find setup.cfg sys.argv = [os.path.join(os.path.dirname(__file__), os.pardir, os.pardir)] # git usurbs your bin path for hooks and will always run system python diff --git a/setup.py b/setup.py index 6b3175dce..077e01684 100755 --- a/setup.py +++ b/setup.py @@ -59,11 +59,11 @@ 'Django>=1.4', 'django-celery>=2.5', 'exam>=0.5.2', - 'flake8>=2.0,<2.1', + 'flake8>=2.6,<2.7', 'logbook', 'mock', 'nose', - 'pep8', + 'pycodestyle', 'pytz', 'pytest', 'pytest-django==2.9.1', From 3c6496a0f5701608f86eaece2382e9f09a257540 Mon Sep 17 00:00:00 2001 From: Ahmed Saidi Date: Tue, 12 Jul 2016 19:10:32 +0000 Subject: [PATCH 322/692] Remove data from error message data variable can contain a lot of information, sending along with the error message will print too much informations --- raven/base.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/raven/base.py b/raven/base.py index c07edceec..bae7584c4 100644 --- a/raven/base.py +++ b/raven/base.py @@ -17,7 +17,6 @@ import warnings from datetime import datetime -from pprint import pformat from types import FunctionType if sys.version_info >= (3, 2): @@ -628,9 +627,10 @@ def _failed_send(self, exc, url, data): type(exc).__name__, exc.message) else: self.error_logger.error( - 'Sentry responded with an error: %s (url: %s)\n%s', - exc, url, pformat(data), - exc_info=True + 'Sentry responded with an error: %s (url: %s)', + exc, url, + exc_info=True, + extra={'data': data} ) self._log_failed_submission(data) From 02aa7bee9addd1aaf2016af181c27a6ef9f37d01 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 14 Jul 2016 15:10:56 +0500 Subject: [PATCH 323/692] 5.23.0 --- CHANGES | 6 ++++++ setup.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 823919d1a..fbff7f39f 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,9 @@ +Version 5.23.0 +-------------- + +* Sentry failures now no longer log the failure data in the error + message. + Version 5.22.0 -------------- diff --git a/setup.py b/setup.py index 077e01684..f7063eaef 100755 --- a/setup.py +++ b/setup.py @@ -97,7 +97,7 @@ def run_tests(self): setup( name='raven', - version='5.22.0', + version='5.23.0', author='Sentry', author_email='hello@getsentry.com', url='https://github.com/getsentry/raven-python', From 8a4c0a889dd2dd8eb50a822765a1748352d8ea58 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 19 Jul 2016 19:45:29 +0500 Subject: [PATCH 324/692] Do not log exceptions from the django cli client twice. This fixes #802 --- raven/base.py | 8 ++++++++ raven/contrib/django/management/__init__.py | 12 +++++++++--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/raven/base.py b/raven/base.py index bae7584c4..d0363799e 100644 --- a/raven/base.py +++ b/raven/base.py @@ -59,6 +59,13 @@ Raven = None +def get_excepthook_client(): + hook = sys.excepthook + client = getattr(hook, 'raven_client', None) + if client is not None: + return client + + class ModuleProxyCache(dict): def __missing__(self, key): module, class_name = key.rsplit('.', 1) @@ -237,6 +244,7 @@ def install_sys_hook(self): def handle_exception(*exc_info): self.captureException(exc_info=exc_info) __excepthook__(*exc_info) + handle_exception.raven_client = self sys.excepthook = handle_exception def install_logging_hook(self): diff --git a/raven/contrib/django/management/__init__.py b/raven/contrib/django/management/__init__.py index 07ed7d386..6e7a54877 100644 --- a/raven/contrib/django/management/__init__.py +++ b/raven/contrib/django/management/__init__.py @@ -18,6 +18,8 @@ def patch_cli_runner(): Patches ``cls.execute``, returning a boolean describing if the attempt was successful. """ + from raven.baes import get_excepthook_client + try: from django.core.management.base import BaseCommand except ImportError: @@ -42,9 +44,13 @@ def new_execute(self, *args, **kwargs): except Exception: from raven.contrib.django.models import client - client.captureException(extra={ - 'argv': sys.argv - }) + # Since this is an unhandled exception that falls through + # we only want to log it if the given client is not the + # one that handles the global exceptions. + if get_excepthook_client() is not client: + client.captureException(extra={ + 'argv': sys.argv + }) raise new_execute.__raven_patched = True From 6437e7a3c20619c39dc620ed8b89e6a2a2a52507 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 20 Jul 2016 01:01:41 +0500 Subject: [PATCH 325/692] Fixed a typo --- raven/contrib/django/management/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/contrib/django/management/__init__.py b/raven/contrib/django/management/__init__.py index 6e7a54877..722f6dbbe 100644 --- a/raven/contrib/django/management/__init__.py +++ b/raven/contrib/django/management/__init__.py @@ -18,7 +18,7 @@ def patch_cli_runner(): Patches ``cls.execute``, returning a boolean describing if the attempt was successful. """ - from raven.baes import get_excepthook_client + from raven.base import get_excepthook_client try: from django.core.management.base import BaseCommand From c3e170312df8d5efd64d33a55b7f66496117a86e Mon Sep 17 00:00:00 2001 From: David Cramer Date: Fri, 22 Jul 2016 10:54:22 -0700 Subject: [PATCH 326/692] Correct flake8 call --- hooks/pre-commit.flake8 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hooks/pre-commit.flake8 b/hooks/pre-commit.flake8 index d7b907176..7abe558e4 100755 --- a/hooks/pre-commit.flake8 +++ b/hooks/pre-commit.flake8 @@ -17,7 +17,7 @@ if 'VIRTUAL_ENV' in os.environ: def main(): - from flake8.main import DEFAULT_CONFIG + from flake8.main import USER_CONFIG from flake8.engine import get_style_guide from flake8.hooks import run @@ -37,7 +37,7 @@ def main(): lambda x: x.endswith('.py') and os.path.exists(x), files_modified) - flake8_style = get_style_guide(parse_argv=True, config_file=DEFAULT_CONFIG) + flake8_style = get_style_guide(parse_argv=True, config_file=USER_CONFIG) report = flake8_style.check_files(files_modified) return report.total_errors From 0c67be102f73f3b628af0d83ee5bc20190397133 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Sat, 23 Jul 2016 17:15:12 -0700 Subject: [PATCH 327/692] Add failing tests for issue #550: exception chaining. --- tests/events/tests.py | 68 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 tests/events/tests.py diff --git a/tests/events/tests.py b/tests/events/tests.py new file mode 100644 index 000000000..cb100af6b --- /dev/null +++ b/tests/events/tests.py @@ -0,0 +1,68 @@ +from raven.utils.testutils import TestCase + +from raven.base import Client +from raven.events import Exception as ExceptionEvent + + +class ExceptionTest(TestCase): + + # Handle compatibility. + if hasattr(Exception, '__suppress_context__'): + # Then exception chains are supported. + def transform_expected(self, expected): + return expected + else: + # Otherwise, we only report the first element. + def transform_expected(self, expected): + return expected[:1] + + def check_capture(self, expected): + """ + Check the return value of capture(). + + Args: + expected: the expected "type" values. + """ + c = Client() + event = ExceptionEvent(c) + result = event.capture() + info = result['exception'] + values = info['values'] + + type_names = [value['type'] for value in values] + expected = self.transform_expected(expected) + + self.assertEqual(type_names, expected) + + def test_simple(self): + try: + raise ValueError() + except Exception: + self.check_capture(['ValueError']) + + def test_nested(self): + try: + raise ValueError() + except Exception: + try: + raise KeyError() + except Exception: + self.check_capture(['KeyError', 'ValueError']) + + def test_raise_from(self): + try: + raise ValueError() + except Exception as exc: + try: + raise KeyError() from exc + except Exception: + self.check_capture(['KeyError', 'ValueError']) + + def test_raise_from_different(self): + try: + raise ValueError() + except Exception as exc: + try: + raise KeyError() from TypeError() + except Exception: + self.check_capture(['KeyError', 'TypeError']) From 731bc7cf922d6d747743dd29932f46c7b079a361 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Sat, 23 Jul 2016 17:21:11 -0700 Subject: [PATCH 328/692] Address issue #550: exception chaining. Enhance raven.events.Exception to report the exception chain when __suppress_context__ is supported. --- raven/events.py | 90 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 60 insertions(+), 30 deletions(-) diff --git a/raven/events.py b/raven/events.py index 5398f83af..67402bead 100644 --- a/raven/events.py +++ b/raven/events.py @@ -32,6 +32,35 @@ def transform(self, value): return self.client.transform(value) +# The __suppress_context__ attribute was added in Python 3.3. +# See PEP 415 for details: +# https://www.python.org/dev/peps/pep-0415/ +if hasattr(Exception, '__suppress_context__'): + def _chained_exceptions(exc_info): + """ + Return a generator iterator over an exception's chain. + + The exceptions are yielded from outermost to innermost (i.e. last to + first when viewing a stack trace). + """ + yield exc_info + exc_type, exc, exc_traceback = exc_info + + while True: + if exc.__suppress_context__: + # Then __cause__ should be used instead. + exc = exc.__cause__ + else: + exc = exc.__context__ + if exc is None: + break + yield type(exc), exc, exc.__traceback__ +else: + # Then we do not support reporting exception chains. + def _chained_exceptions(exc_info): + yield exc_info + + class Exception(BaseEvent): """ Exceptions store the following metadata: @@ -49,6 +78,28 @@ def to_string(self, data): return '%s: %s' % (exc['type'], exc['value']) return exc['type'] + def _get_value(self, exc_type, exc_value, exc_traceback): + """ + Convert exception info to a value for the values list. + """ + stack_info = get_stack_info( + iter_traceback_frames(exc_traceback), + transformer=self.transform, + capture_locals=self.client.capture_locals, + ) + + exc_module = getattr(exc_type, '__module__', None) + if exc_module: + exc_module = str(exc_module) + exc_type = getattr(exc_type, '__name__', '') + + return { + 'value': to_unicode(exc_value), + 'type': str(exc_type), + 'module': to_unicode(exc_module), + 'stacktrace': stack_info, + } + def capture(self, exc_info=None, **kwargs): if not exc_info or exc_info is True: exc_info = sys.exc_info() @@ -56,36 +107,15 @@ def capture(self, exc_info=None, **kwargs): if not exc_info: raise ValueError('No exception found') - exc_type, exc_value, exc_traceback = exc_info - - try: - stack_info = get_stack_info( - iter_traceback_frames(exc_traceback), - transformer=self.transform, - capture_locals=self.client.capture_locals, - ) - - exc_module = getattr(exc_type, '__module__', None) - if exc_module: - exc_module = str(exc_module) - exc_type = getattr(exc_type, '__name__', '') - - return { - 'level': kwargs.get('level', logging.ERROR), - self.name: { - 'values': [{ - 'value': to_unicode(exc_value), - 'type': str(exc_type), - 'module': to_unicode(exc_module), - 'stacktrace': stack_info, - }], - }, - } - finally: - try: - del exc_type, exc_value, exc_traceback - except Exception as e: - self.logger.exception(e) + values = [] + for exc_info in _chained_exceptions(exc_info): + value = self._get_value(*exc_info) + values.append(value) + + return { + 'level': kwargs.get('level', logging.ERROR), + self.name: {'values': values}, + } class Message(BaseEvent): From 63e77b9f077c1119a25ced32ec6710e0a70965b0 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Sat, 23 Jul 2016 17:36:00 -0700 Subject: [PATCH 329/692] Use six.raise_from() for Python 2 compatibility. --- tests/events/tests.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/events/tests.py b/tests/events/tests.py index cb100af6b..a75e326fe 100644 --- a/tests/events/tests.py +++ b/tests/events/tests.py @@ -1,7 +1,8 @@ -from raven.utils.testutils import TestCase +import six from raven.base import Client from raven.events import Exception as ExceptionEvent +from raven.utils.testutils import TestCase class ExceptionTest(TestCase): @@ -54,7 +55,7 @@ def test_raise_from(self): raise ValueError() except Exception as exc: try: - raise KeyError() from exc + six.raise_from(KeyError(), exc) except Exception: self.check_capture(['KeyError', 'ValueError']) @@ -63,6 +64,6 @@ def test_raise_from_different(self): raise ValueError() except Exception as exc: try: - raise KeyError() from TypeError() + six.raise_from(KeyError(), TypeError()) except Exception: self.check_capture(['KeyError', 'TypeError']) From 574418413804cb4a203fce3e986d1cadba637d7d Mon Sep 17 00:00:00 2001 From: Matt Robenolt Date: Sat, 23 Jul 2016 19:04:52 -0700 Subject: [PATCH 330/692] Removed rando tab --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 4067bf09d..3b8d26278 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,7 @@ [pytest] python_files=test*.py addopts=--tb=native -p no:doctest -norecursedirs=bin dist docs htmlcov hooks node_modules .* {args} +norecursedirs=bin dist docs htmlcov hooks node_modules .* {args} [flake8] ignore = F999,E501,E128,E124,E402,W503,E731,F841 From a100f659995f68753dffa066f2120783c5db5d01 Mon Sep 17 00:00:00 2001 From: Matt Robenolt Date: Sat, 23 Jul 2016 19:07:21 -0700 Subject: [PATCH 331/692] Replace py 3.2 classifier with 3.5 --- setup.py | 2 +- tox.ini | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index f7063eaef..dabe2a099 100755 --- a/setup.py +++ b/setup.py @@ -130,9 +130,9 @@ def run_tests(self): 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.2', 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', 'Programming Language :: Python', 'Topic :: Software Development', ], diff --git a/tox.ini b/tox.ini index 3d3cbd735..bd790da4e 100644 --- a/tox.ini +++ b/tox.ini @@ -4,9 +4,9 @@ # and then run "tox" from this directory. [tox] -envlist = py26, py27, py30, py31, py32, py33, py34, py35, pypy +envlist = py26, py27, py33, py34, py35, pypy [testenv] -commands = +commands = pip install -e .[tests] python setup.py test From 8b15b5ba064c014bda3b907d13443f8c17129697 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Tue, 26 Jul 2016 09:13:44 -0700 Subject: [PATCH 332/692] Mark process errors as fatal (#808) * Mark process errors as fatal @getsentry/python --- raven/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/base.py b/raven/base.py index bae7584c4..b1e87ec14 100644 --- a/raven/base.py +++ b/raven/base.py @@ -235,7 +235,7 @@ def install_sys_hook(self): __excepthook__ = sys.excepthook def handle_exception(*exc_info): - self.captureException(exc_info=exc_info) + self.captureException(exc_info=exc_info, level='fatal') __excepthook__(*exc_info) sys.excepthook = handle_exception From 16103070fed12441b4802c0410248c001515cf6f Mon Sep 17 00:00:00 2001 From: David Cramer Date: Tue, 26 Jul 2016 09:14:10 -0700 Subject: [PATCH 333/692] Add ignore_exceptions to client configuration (#807) * Add ignore_exceptions to client configuration This takes Django's IGNORE_EXCEPTIONS support and moves it into core. @getsentry/python --- docs/advanced.rst | 10 +++++++++ raven/base.py | 22 +++++++++++++++++++ raven/contrib/django/models.py | 12 +---------- tests/contrib/django/tests.py | 39 +++++++++++++++++----------------- 4 files changed, 53 insertions(+), 30 deletions(-) diff --git a/docs/advanced.rst b/docs/advanced.rst index 77f940d2f..5a9749c79 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -129,6 +129,16 @@ The following are valid arguments which may be passed to the Raven client: 'lxml.objectify', ] +.. describe:: ignore_exceptions + + A list of exceptions to ignore. + + ignore_exceptions = [ + 'Http404', + 'django.exceptions.http.Http404', + 'django.exceptions.*', + ] + .. describe:: max_list_length The maximum number of items a list-like container should store. diff --git a/raven/base.py b/raven/base.py index b1e87ec14..48c3e4d28 100644 --- a/raven/base.py +++ b/raven/base.py @@ -180,6 +180,8 @@ 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.ignore_exceptions = set(o.get('ignore_exceptions') or ()) + self.module_cache = ModuleProxyCache() if not self.is_enabled(): @@ -764,9 +766,29 @@ def captureException(self, exc_info=None, **kwargs): """ if exc_info is None or exc_info is True: exc_info = sys.exc_info() + + if not self.should_capture(exc_info): + self.logger.info( + 'Not capturing exception due to filters: %s', exc_info[0], + exc_info=sys.exc_info()) + return + return self.capture( 'raven.events.Exception', exc_info=exc_info, **kwargs) + def should_capture(self, exc_info): + exc_type = exc_info[0] + exc_name = '%s.%s' % (exc_type.__module__, exc_type.__name__) + exclusions = self.ignore_exceptions + + if exc_type.__name__ in exclusions: + return False + elif exc_name in exclusions: + return False + elif any(exc_name.startswith(e[:-1]) for e in exclusions if e.endswith('*')): + return False + return True + def capture_exceptions(self, function_or_exceptions=None, **kwargs): """ Wrap a function or code block in try/except and automatically call diff --git a/raven/contrib/django/models.py b/raven/contrib/django/models.py index 490978a99..bad6fc305 100644 --- a/raven/contrib/django/models.py +++ b/raven/contrib/django/models.py @@ -137,6 +137,7 @@ def get_client(client=None, reset=False): options.setdefault('dsn', ga('DSN')) options.setdefault('context', ga('CONTEXT')) options.setdefault('release', ga('RELEASE')) + options.setdefault('ignore_exceptions', ga('IGNORE_EXCEPTIONS')) transport = ga('TRANSPORT') or options.get('transport') if isinstance(transport, string_types): @@ -160,17 +161,6 @@ def get_client(client=None, reset=False): def sentry_exception_handler(request=None, **kwargs): - exc_type = sys.exc_info()[0] - - exclusions = set(get_option('IGNORE_EXCEPTIONS', ())) - - exc_name = '%s.%s' % (exc_type.__module__, exc_type.__name__) - if exc_type.__name__ in exclusions or exc_name in exclusions or any(exc_name.startswith(e[:-1]) for e in exclusions if e.endswith('*')): - logger.info( - 'Not capturing exception due to filters: %s', exc_type, - exc_info=sys.exc_info()) - return - try: client.captureException(exc_info=sys.exc_info(), request=request) except Exception as exc: diff --git a/tests/contrib/django/tests.py b/tests/contrib/django/tests.py index 8d96ec103..53bf09be5 100644 --- a/tests/contrib/django/tests.py +++ b/tests/contrib/django/tests.py @@ -755,39 +755,40 @@ def test_does_capture_exception(self, exc_info, captureException): captureException.assert_called_once_with(exc_info=self.exc_info, request=self.request) - @mock.patch.object(TempStoreClient, 'captureException') + @mock.patch.object(TempStoreClient, 'capture') @mock.patch('sys.exc_info') - @mock.patch('raven.contrib.django.models.get_option') - def test_does_exclude_filtered_types(self, get_option, exc_info, captureException): + def test_does_exclude_filtered_types(self, exc_info, mock_capture): exc_info.return_value = self.exc_info - get_option.return_value = ['ValueError'] + get_client().ignore_exceptions = set(['ValueError']) sentry_exception_handler(request=self.request) - assert not captureException.called + assert not mock_capture.called - @mock.patch.object(TempStoreClient, 'captureException') + @mock.patch.object(TempStoreClient, 'capture') @mock.patch('sys.exc_info') - @mock.patch('raven.contrib.django.models.get_option') - def test_ignore_exceptions_with_expression_match(self, get_option, exc_info, captureException): + def test_ignore_exceptions_with_expression_match(self, exc_info, mock_capture): exc_info.return_value = self.exc_info - get_option.return_value = ['builtins.*'] - if not six.PY3: - get_option.return_value = ['exceptions.*'] + + if six.PY3: + get_client().ignore_exceptions = set(['builtins.*']) + else: + get_client().ignore_exceptions = set(['exceptions.*']) sentry_exception_handler(request=self.request) - assert not captureException.called + assert not mock_capture.called - @mock.patch.object(TempStoreClient, 'captureException') + @mock.patch.object(TempStoreClient, 'capture') @mock.patch('sys.exc_info') - @mock.patch('raven.contrib.django.models.get_option') - def test_ignore_exceptions_with_module_match(self, get_option, exc_info, captureException): + def test_ignore_exceptions_with_module_match(self, exc_info, mock_capture): exc_info.return_value = self.exc_info - get_option.return_value = ['builtins.ValueError'] - if not six.PY3: - get_option.return_value = ['exceptions.ValueError'] + + if six.PY3: + get_client().ignore_exceptions = set(['builtins.ValueError']) + else: + get_client().ignore_exceptions = set(['exceptions.ValueError']) sentry_exception_handler(request=self.request) - assert not captureException.called + assert not mock_capture.called From 75cf90996ee22fc3bf9f988a6693e9171bc3a2d0 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 27 Jul 2016 13:30:39 +0200 Subject: [PATCH 334/692] Fixed rst syntax --- docs/advanced.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/advanced.rst b/docs/advanced.rst index 5a9749c79..bb9a55868 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -131,7 +131,7 @@ The following are valid arguments which may be passed to the Raven client: .. describe:: ignore_exceptions - A list of exceptions to ignore. + A list of exceptions to ignore:: ignore_exceptions = [ 'Http404', From c6c85c942d516ff9d4d0c7587a6be6eb7774e224 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 27 Jul 2016 13:58:33 +0200 Subject: [PATCH 335/692] Reset the exception filter after test runs --- tests/contrib/django/tests.py | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/tests/contrib/django/tests.py b/tests/contrib/django/tests.py index 53bf09be5..a25269163 100644 --- a/tests/contrib/django/tests.py +++ b/tests/contrib/django/tests.py @@ -759,9 +759,12 @@ def test_does_capture_exception(self, exc_info, captureException): @mock.patch('sys.exc_info') def test_does_exclude_filtered_types(self, exc_info, mock_capture): exc_info.return_value = self.exc_info - get_client().ignore_exceptions = set(['ValueError']) + try: + get_client().ignore_exceptions = set(['ValueError']) - sentry_exception_handler(request=self.request) + sentry_exception_handler(request=self.request) + finally: + get_client().ignore_exceptions.clear() assert not mock_capture.called @@ -770,12 +773,14 @@ def test_does_exclude_filtered_types(self, exc_info, mock_capture): def test_ignore_exceptions_with_expression_match(self, exc_info, mock_capture): exc_info.return_value = self.exc_info - if six.PY3: - get_client().ignore_exceptions = set(['builtins.*']) - else: - get_client().ignore_exceptions = set(['exceptions.*']) - - sentry_exception_handler(request=self.request) + try: + if six.PY3: + get_client().ignore_exceptions = set(['builtins.*']) + else: + get_client().ignore_exceptions = set(['exceptions.*']) + sentry_exception_handler(request=self.request) + finally: + get_client().ignore_exceptions.clear() assert not mock_capture.called @@ -784,11 +789,13 @@ def test_ignore_exceptions_with_expression_match(self, exc_info, mock_capture): def test_ignore_exceptions_with_module_match(self, exc_info, mock_capture): exc_info.return_value = self.exc_info - if six.PY3: - get_client().ignore_exceptions = set(['builtins.ValueError']) - else: - get_client().ignore_exceptions = set(['exceptions.ValueError']) - - sentry_exception_handler(request=self.request) + try: + if six.PY3: + get_client().ignore_exceptions = set(['builtins.ValueError']) + else: + get_client().ignore_exceptions = set(['exceptions.ValueError']) + sentry_exception_handler(request=self.request) + finally: + get_client().ignore_exceptions.clear() assert not mock_capture.called From 7294c4d00bbcb913d38f2798ec9404d14a11fba2 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 27 Jul 2016 14:25:00 +0200 Subject: [PATCH 336/692] Skip a broken test on 2.6 --- tests/contrib/django/tests.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/contrib/django/tests.py b/tests/contrib/django/tests.py index a25269163..21a4a7836 100644 --- a/tests/contrib/django/tests.py +++ b/tests/contrib/django/tests.py @@ -157,6 +157,7 @@ def test_signal_integration(self): assert event['message'], "TypeError: int() argument must be a string or a number == not 'NoneType'" assert event['culprit'] == 'tests.contrib.django.tests in test_signal_integration' + @pytest.mark.skipif('sys.version_info[:2] == (2, 6)') def test_view_exception(self): self.assertRaises(Exception, self.client.get, reverse('sentry-raise-exc')) From 38fc2d178fb2d8afd77cd3e8f62033d98869e863 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 29 Jul 2016 01:38:56 +0200 Subject: [PATCH 337/692] Hardcode version in package init to avoid issues with badly installed libraries misreporting. --- raven/__init__.py | 6 +----- setup.py | 12 +++++++++++- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/raven/__init__.py b/raven/__init__.py index 50cef0241..5e317cb5a 100644 --- a/raven/__init__.py +++ b/raven/__init__.py @@ -12,11 +12,7 @@ __all__ = ('VERSION', 'Client', 'get_version') -try: - VERSION = __import__('pkg_resources') \ - .get_distribution('raven').version -except Exception as e: - VERSION = 'unknown' +VERSION = '5.23.0' def _get_git_revision(path): diff --git a/setup.py b/setup.py index f7063eaef..11e9432f4 100755 --- a/setup.py +++ b/setup.py @@ -22,7 +22,17 @@ from setuptools import setup, find_packages from setuptools.command.test import test as TestCommand +import re import sys +import ast + + +_version_re = re.compile(r'VERSION\s+=\s+(.*)') + +with open('raven/__init__.py', 'rb') as f: + version = str(ast.literal_eval(_version_re.search( + f.read().decode('utf-8')).group(1))) + install_requires = [ 'contextlib2', @@ -97,7 +107,7 @@ def run_tests(self): setup( name='raven', - version='5.23.0', + version=version, author='Sentry', author_email='hello@getsentry.com', url='https://github.com/getsentry/raven-python', From 93dd4ab82fd611812392d760f577b2fe71f80f19 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Wed, 3 Aug 2016 13:43:05 -0700 Subject: [PATCH 338/692] Unify option configuration between Flask/Django Fixes GH-819 @getsentry/python --- raven/contrib/django/models.py | 34 +++++-------------- raven/contrib/flask.py | 52 ++++++++++------------------- raven/utils/conf.py | 61 ++++++++++++++++++++++++++++++++++ tests/contrib/flask/tests.py | 21 ------------ tests/utils/test_conf.py | 20 +++++++++++ 5 files changed, 107 insertions(+), 81 deletions(-) create mode 100644 raven/utils/conf.py create mode 100644 tests/utils/test_conf.py diff --git a/raven/contrib/django/models.py b/raven/contrib/django/models.py index bad6fc305..2e48d5fb3 100644 --- a/raven/contrib/django/models.py +++ b/raven/contrib/django/models.py @@ -11,18 +11,16 @@ from __future__ import absolute_import, unicode_literals -import copy import logging import sys import warnings from django.conf import settings -from hashlib import md5 from raven._compat import PY2, binary_type, text_type, string_types -from raven.utils.imports import import_string from raven.contrib.django.management import patch_cli_runner - +from raven.utils.conf import convert_options +from raven.utils.imports import import_string logger = logging.getLogger('sentry.errors.client') @@ -121,28 +119,12 @@ def get_client(client=None, reset=False): client = getattr(settings, 'SENTRY_CLIENT', 'raven.contrib.django.DjangoClient') if _client[0] != client or reset: - ga = lambda x, d=None: getattr(settings, 'SENTRY_%s' % x, d) - options = copy.deepcopy(getattr(settings, 'RAVEN_CONFIG', {})) - options.setdefault('include_paths', ga('INCLUDE_PATHS', [])) - if not options['include_paths']: - options['include_paths'] = get_installed_apps() - options.setdefault('exclude_paths', ga('EXCLUDE_PATHS')) - options.setdefault('timeout', ga('TIMEOUT')) - options.setdefault('name', ga('NAME')) - options.setdefault('auto_log_stacks', ga('AUTO_LOG_STACKS')) - options.setdefault('string_max_length', ga('MAX_LENGTH_STRING')) - options.setdefault('list_max_length', ga('MAX_LENGTH_LIST')) - options.setdefault('site', ga('SITE')) - options.setdefault('processors', ga('PROCESSORS')) - options.setdefault('dsn', ga('DSN')) - options.setdefault('context', ga('CONTEXT')) - options.setdefault('release', ga('RELEASE')) - options.setdefault('ignore_exceptions', ga('IGNORE_EXCEPTIONS')) - - transport = ga('TRANSPORT') or options.get('transport') - if isinstance(transport, string_types): - transport = import_string(transport) - options['transport'] = transport + options = convert_options( + settings, + defaults={ + 'include_paths': get_installed_apps(), + }, + ) try: Client = import_string(client) diff --git a/raven/contrib/flask.py b/raven/contrib/flask.py index c23b92bba..df7423a10 100644 --- a/raven/contrib/flask.py +++ b/raven/contrib/flask.py @@ -15,49 +15,41 @@ else: has_flask_login = True -import sys -import os import logging from flask import request, current_app, g from flask.signals import got_request_exception, request_finished from werkzeug.exceptions import ClientDisconnected -from raven._compat import string_types from raven.conf import setup_logging from raven.base import Client from raven.middleware import Sentry as SentryMiddleware from raven.handlers.logging import SentryHandler from raven.utils.compat import _urlparse from raven.utils.encoding import to_unicode -from raven.utils.imports import import_string from raven.utils.wsgi import get_headers, get_environ +from raven.utils.conf import convert_options def make_client(client_cls, app, dsn=None): - # TODO(dcramer): django and Flask share very similar concepts here, and - # should be refactored - transport = app.config.get('SENTRY_TRANSPORT') - if isinstance(transport, string_types): - transport = import_string(transport) - return client_cls( - dsn=dsn or app.config.get('SENTRY_DSN') or os.environ.get('SENTRY_DSN'), - transport=transport, - include_paths=set(app.config.get( - 'SENTRY_INCLUDE_PATHS', [])) | set([app.import_name]), - exclude_paths=app.config.get('SENTRY_EXCLUDE_PATHS'), - name=app.config.get('SENTRY_NAME'), - site=app.config.get('SENTRY_SITE_NAME'), - processors=app.config.get('SENTRY_PROCESSORS'), - string_max_length=app.config.get('SENTRY_MAX_LENGTH_STRING'), - list_max_length=app.config.get('SENTRY_MAX_LENGTH_LIST'), - auto_log_stacks=app.config.get('SENTRY_AUTO_LOG_STACKS'), - tags=app.config.get('SENTRY_TAGS'), - release=app.config.get('SENTRY_RELEASE'), - extra={ - 'app': app, - }, + **convert_options( + app.config, + defaults={ + 'include_paths': ( + set(app.config.get('SENTRY_INCLUDE_PATHS', [])) + | set([app.import_name]), + ), + # support legacy RAVEN_IGNORE_EXCEPTIONS + 'ignore_exceptions': [ + '{0}.{1}'.format(x.__module__, x.__name__) + for x in app.config.get('RAVEN_IGNORE_EXCEPTIONS', []) + ], + 'extra': { + 'app': app, + }, + }, + ) ) @@ -136,14 +128,6 @@ def handle_exception(self, *args, **kwargs): if not self.client: return - ignored_exc_type_list = current_app.config.get( - 'RAVEN_IGNORE_EXCEPTIONS', []) - exc = sys.exc_info()[1] - - if any((isinstance(exc, ignored_exc_type) - for ignored_exc_type in ignored_exc_type_list)): - return - self.captureException(exc_info=kwargs.get('exc_info')) def get_user_info(self, request): diff --git a/raven/utils/conf.py b/raven/utils/conf.py new file mode 100644 index 000000000..4cf8c0dc0 --- /dev/null +++ b/raven/utils/conf.py @@ -0,0 +1,61 @@ +from __future__ import absolute_import + +import copy +import os + +from raven._compat import string_types +from raven.utils.imports import import_string + + +def convert_options(settings, defaults=None): + """ + Convert a settings object (or dictionary) to parameters which may be passed + to a new ``Client()`` instance. + """ + if defaults is None: + defaults = {} + + if isinstance(settings, dict): + def getopt(key, default=None): + return settings.get( + 'SENTRY_%s' % key.upper(), + defaults.get(key, default) + ) + + options = copy.copy( + settings.get('SENTRY_CONFIG') + or settings.get('RAVEN_CONFIG') + or {} + ) + else: + def getopt(key, default=None): + return getattr(settings, 'SENTRY_%s' % key.upper(), defaults.get(key, default)) + + options = copy.copy( + getattr(settings, 'SENTRY_CONFIG', None) + or getattr(settings, 'RAVEN_CONFIG', None) + or {} + ) + + options.setdefault('include_paths', getopt('include_paths', [])) + options.setdefault('exclude_paths', getopt('exclude_paths', [])) + options.setdefault('timeout', getopt('timeout')) + options.setdefault('name', getopt('name')) + options.setdefault('auto_log_stacks', getopt('auto_log_stacks')) + options.setdefault('string_max_length', getopt('string_max_length')) + options.setdefault('list_max_length', getopt('list_max_length')) + options.setdefault('site', getopt('site')) + options.setdefault('processors', getopt('processors')) + options.setdefault('dsn', getopt('dsn', os.environ.get('SENTRY_DSN'))) + options.setdefault('context', getopt('context')) + options.setdefault('tags', getopt('tags')) + options.setdefault('release', getopt('release')) + options.setdefault('environment', getopt('environment')) + options.setdefault('ignore_exceptions', getopt('ignore_exceptions')) + + transport = getopt('transport') or options.get('transport') + if isinstance(transport, string_types): + transport = import_string(transport) + options['transport'] = transport + + return options diff --git a/tests/contrib/flask/tests.py b/tests/contrib/flask/tests.py index 05302163e..36bf573a3 100644 --- a/tests/contrib/flask/tests.py +++ b/tests/contrib/flask/tests.py @@ -228,27 +228,6 @@ def test_wrap_wsgi_status(self): _, _, app_ndebug = self.make_client_and_raven(debug=False) self.assertTrue(app_ndebug.extensions['sentry'].wrap_wsgi) - def test_error_handler_with_ignored_exception(self): - client, raven, _ = self.make_client_and_raven(ignore_exceptions=[NameError, ValueError]) - - response = client.get('/an-error/') - self.assertEquals(response.status_code, 500) - self.assertEquals(len(raven.events), 0) - - def test_error_handler_with_exception_not_ignored(self): - client, raven, _ = self.make_client_and_raven(ignore_exceptions=[NameError, KeyError]) - - response = client.get('/an-error/') - self.assertEquals(response.status_code, 500) - self.assertEquals(len(raven.events), 1) - - def test_error_handler_with_empty_ignore_exceptions_list(self): - client, raven, _ = self.make_client_and_raven(ignore_exceptions=[]) - - response = client.get('/an-error/') - self.assertEquals(response.status_code, 500) - self.assertEquals(len(raven.events), 1) - def test_captureException_sets_last_event_id(self): with self.app.test_request_context('/'): try: diff --git a/tests/utils/test_conf.py b/tests/utils/test_conf.py new file mode 100644 index 000000000..0a9221e0a --- /dev/null +++ b/tests/utils/test_conf.py @@ -0,0 +1,20 @@ +from __future__ import absolute_import + +from raven.utils.conf import convert_options + + +def test_convert_options_parses_dict(): + options = convert_options({ + 'SENTRY_FOO': 'foo', + 'FOO': 'bar', + 'SENTRY_RELEASE': 'a', + 'SENTRY_IGNORE_EXCEPTIONS': [ + 'b', + ] + }, defaults={'environment': 'production'}) + + assert options['release'] == 'a' + assert options['ignore_exceptions'] == [ + 'b', + ] + assert options['environment'] == 'production' From f6e9219936dfa8e76668eda834c53878a9f78428 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Wed, 3 Aug 2016 17:22:56 -0700 Subject: [PATCH 339/692] Disable pypy from builds --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index bf92c9e5c..18de603fb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,7 +21,8 @@ python: - '3.3' - '3.4' - '3.5' - - pypy + # pypy fails a lot, and generally should be compatible + # - pypy env: matrix: # - DJANGO=Django==1.4.20 From 5d7cc8cdaf1ba14e671282e725814ad24ba2792f Mon Sep 17 00:00:00 2001 From: David Cramer Date: Tue, 2 Aug 2016 15:21:45 -0700 Subject: [PATCH 340/692] Support for Django 1.10 Refs GH-818 --- .travis.yml | 5 ++ conftest.py | 26 +++++---- raven/contrib/django/management/__init__.py | 58 --------------------- raven/contrib/django/models.py | 3 -- raven/contrib/django/urls.py | 12 +++-- tests/contrib/django/tests.py | 57 ++++++++++++-------- tests/contrib/django/urls.py | 23 ++++---- 7 files changed, 75 insertions(+), 109 deletions(-) diff --git a/.travis.yml b/.travis.yml index 18de603fb..1f1e6300b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,6 +31,7 @@ env: # - 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 @@ -52,6 +53,8 @@ matrix: # 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' @@ -68,6 +71,8 @@ matrix: 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' diff --git a/conftest.py b/conftest.py index e35352361..55c641c3a 100644 --- a/conftest.py +++ b/conftest.py @@ -11,25 +11,22 @@ collect_ignore.append("tests/handlers/logbook") try: - import gevent + import gevent # NOQA except ImportError: collect_ignore.append("tests/transport/gevent") try: - import web + import web # NOQA except ImportError: collect_ignore.append("tests/contrib/webpy") INSTALLED_APPS = [ 'django.contrib.auth', - 'django.contrib.admin', + 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.sites', - # Included to fix Disqus' test Django which solves IntegrityMessage case - 'django.contrib.contenttypes', - 'raven.contrib.django', 'tests.contrib.django', ] @@ -37,7 +34,7 @@ use_djcelery = True try: - import djcelery + import djcelery # NOQA INSTALLED_APPS.append('djcelery') except ImportError: use_djcelery = False @@ -59,7 +56,7 @@ def pytest_configure(config): DATABASE_NAME=':memory:', TEST_DATABASE_NAME=':memory:', INSTALLED_APPS=INSTALLED_APPS, - ROOT_URLCONF='', + ROOT_URLCONF='tests.contrib.django.urls', DEBUG=False, SITE_ID=1, BROKER_HOST="localhost", @@ -70,7 +67,18 @@ def pytest_configure(config): 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')], + TEMPLATE_DIRS=[ + os.path.join(where_am_i, '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=['*'], ) diff --git a/raven/contrib/django/management/__init__.py b/raven/contrib/django/management/__init__.py index 722f6dbbe..b0860a4ba 100644 --- a/raven/contrib/django/management/__init__.py +++ b/raven/contrib/django/management/__init__.py @@ -1,59 +1 @@ -""" -raven.contrib.django.raven.management -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -:copyright: (c) 2010-2013 by the Sentry Team, see AUTHORS for more details -:license: BSD, see LICENSE for more details. -""" from __future__ import absolute_import, print_function - -import sys -import warnings - -from functools import wraps - - -def patch_cli_runner(): - """ - Patches ``cls.execute``, returning a boolean describing if the - attempt was successful. - """ - from raven.base import get_excepthook_client - - try: - from django.core.management.base import BaseCommand - except ImportError: - warnings.warn('Unable to patch Django management commands') - return - else: - cls = BaseCommand - - try: - original_func = cls.execute - except AttributeError: - # must not be a capable version of Django - return False - - if hasattr(original_func, '__raven_patched'): - return False - - @wraps(original_func) - def new_execute(self, *args, **kwargs): - try: - return original_func(self, *args, **kwargs) - except Exception: - from raven.contrib.django.models import client - - # Since this is an unhandled exception that falls through - # we only want to log it if the given client is not the - # one that handles the global exceptions. - if get_excepthook_client() is not client: - client.captureException(extra={ - 'argv': sys.argv - }) - raise - - new_execute.__raven_patched = True - cls.execute = new_execute - - return True diff --git a/raven/contrib/django/models.py b/raven/contrib/django/models.py index 2e48d5fb3..8411dddf5 100644 --- a/raven/contrib/django/models.py +++ b/raven/contrib/django/models.py @@ -18,7 +18,6 @@ from django.conf import settings from raven._compat import PY2, binary_type, text_type, string_types -from raven.contrib.django.management import patch_cli_runner from raven.utils.conf import convert_options from raven.utils.imports import import_string @@ -215,5 +214,3 @@ def register_serializers(): or 'raven.contrib.django.raven_compat' in settings.INSTALLED_APPS): register_handlers() register_serializers() - - patch_cli_runner() diff --git a/raven/contrib/django/urls.py b/raven/contrib/django/urls.py index c513e44b2..de3acec45 100644 --- a/raven/contrib/django/urls.py +++ b/raven/contrib/django/urls.py @@ -8,12 +8,14 @@ from __future__ import absolute_import try: - from django.conf.urls import patterns, url + from django.conf.urls import url except ImportError: # for Django version less than 1.4 - from django.conf.urls.defaults import patterns, url # NOQA + from django.conf.urls.defaults import url # NOQA -urlpatterns = patterns('', - url(r'^api/(?:(?P[\w_-]+)/)?store/$', 'raven.contrib.django.views.report', name='raven-report'), - url(r'^report/', 'raven.contrib.django.views.report'), +import raven.contrib.django.views + +urlpatterns = ( + url(r'^api/(?:(?P[\w_-]+)/)?store/$', raven.contrib.django.views.report, name='raven-report'), + url(r'^report/', raven.contrib.django.views.report), ) diff --git a/tests/contrib/django/tests.py b/tests/contrib/django/tests.py index 21a4a7836..3aa6b746d 100644 --- a/tests/contrib/django/tests.py +++ b/tests/contrib/django/tests.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- -from __future__ import absolute_import -from __future__ import with_statement +from __future__ import absolute_import, print_function, with_statement import datetime import django @@ -10,9 +9,7 @@ import pytest import re import six -import sys # NOQA -from exam import fixture -from six import StringIO +import sys from django.conf import settings from django.contrib.auth.models import User @@ -24,6 +21,8 @@ from django.template import TemplateSyntaxError from django.test import TestCase from django.utils.translation import gettext_lazy +from exam import fixture +from six import StringIO from raven.base import Client from raven.contrib.django.client import DjangoClient @@ -42,6 +41,7 @@ settings.SENTRY_CLIENT = 'tests.contrib.django.tests.TempStoreClient' DJANGO_15 = django.VERSION >= (1, 5, 0) +DJANGO_18 = django.VERSION >= (1, 8, 0) def make_request(): @@ -157,7 +157,7 @@ def test_signal_integration(self): assert event['message'], "TypeError: int() argument must be a string or a number == not 'NoneType'" assert event['culprit'] == 'tests.contrib.django.tests in test_signal_integration' - @pytest.mark.skipif('sys.version_info[:2] == (2, 6)') + @pytest.mark.skipif(sys.version_info[:2] == (2, 6), reason='Python 2.6') def test_view_exception(self): self.assertRaises(Exception, self.client.get, reverse('sentry-raise-exc')) @@ -199,7 +199,7 @@ def test_user_info(self): 'email': user.email, } - @pytest.mark.skipif(str('not DJANGO_15')) + @pytest.mark.skipif(not DJANGO_15, reason='< Django 1.5') def test_get_user_info_abstract_user(self): from django.db import models from django.contrib.auth.models import AbstractBaseUser @@ -320,6 +320,7 @@ def test_include_modules(self): assert event['culprit'].startswith('django.shortcuts in ') self.raven.include_paths = include_paths + @pytest.mark.skipif(DJANGO_18, reason='Django 1.8+ not supported') def test_template_name_as_view(self): self.assertRaises(TemplateSyntaxError, self.client.get, reverse('sentry-template-exc')) @@ -339,7 +340,7 @@ def test_template_name_as_view(self): # assert event['data']['META']['REMOTE_ADDR'] == '127.0.0.1' # TODO: Python bug #10805 - @pytest.mark.skipif(str('six.PY3')) + @pytest.mark.skipif(six.PY3, reason='Python 2') def test_record_none_exc_info(self): # sys.exc_info can return (None, None, None) if no exception is being # handled anywhere on the stack. See: @@ -630,62 +631,72 @@ class ReportViewTest(TestCase): urls = 'raven.contrib.django.urls' def setUp(self): - self.path = reverse('raven-report') + super(ReportViewTest, self).setUp() + self.path = reverse('raven-report', urlconf=self.urls) @mock.patch('raven.contrib.django.views.is_valid_origin') def test_calls_is_valid_origin_with_header(self, is_valid_origin): - self.client.post(self.path, HTTP_ORIGIN='http://example.com') + with self.settings(ROOT_URLCONF=self.urls): + self.client.post(self.path, HTTP_ORIGIN='http://example.com') is_valid_origin.assert_called_once_with('http://example.com') @mock.patch('raven.contrib.django.views.is_valid_origin') def test_calls_is_valid_origin_with_header_as_get(self, is_valid_origin): - self.client.get(self.path, HTTP_ORIGIN='http://example.com') + with self.settings(ROOT_URLCONF=self.urls): + self.client.get(self.path, HTTP_ORIGIN='http://example.com') is_valid_origin.assert_called_once_with('http://example.com') @mock.patch('raven.contrib.django.views.is_valid_origin', mock.Mock(return_value=False)) def test_fails_on_invalid_origin(self): - resp = self.client.post(self.path, HTTP_ORIGIN='http://example.com') + with self.settings(ROOT_URLCONF=self.urls): + resp = self.client.post(self.path, HTTP_ORIGIN='http://example.com') assert resp.status_code == 403 @mock.patch('raven.contrib.django.views.is_valid_origin', mock.Mock(return_value=True)) def test_options_call_sends_headers(self): - resp = self.client.options(self.path, HTTP_ORIGIN='http://example.com') + with self.settings(ROOT_URLCONF=self.urls): + resp = self.client.options(self.path, HTTP_ORIGIN='http://example.com') assert resp.status_code == 200 assert resp['Access-Control-Allow-Origin'] == 'http://example.com' assert resp['Access-Control-Allow-Methods'], 'GET, POST == OPTIONS' @mock.patch('raven.contrib.django.views.is_valid_origin', mock.Mock(return_value=True)) def test_missing_data(self): - resp = self.client.post(self.path, HTTP_ORIGIN='http://example.com') + with self.settings(ROOT_URLCONF=self.urls): + resp = self.client.post(self.path, HTTP_ORIGIN='http://example.com') assert resp.status_code == 400 @mock.patch('raven.contrib.django.views.is_valid_origin', mock.Mock(return_value=True)) def test_invalid_data(self): - resp = self.client.post(self.path, HTTP_ORIGIN='http://example.com', - data='[1', content_type='application/octet-stream') + with self.settings(ROOT_URLCONF=self.urls): + resp = self.client.post(self.path, HTTP_ORIGIN='http://example.com', + data='[1', content_type='application/octet-stream') assert resp.status_code == 400 @mock.patch('raven.contrib.django.views.is_valid_origin', mock.Mock(return_value=True)) def test_sends_data(self): - resp = self.client.post(self.path, HTTP_ORIGIN='http://example.com', - data='{}', content_type='application/octet-stream') + with self.settings(ROOT_URLCONF=self.urls): + resp = self.client.post(self.path, HTTP_ORIGIN='http://example.com', + data='{}', content_type='application/octet-stream') assert resp.status_code == 200 event = client.events.pop(0) assert event == {'auth_header': None} @mock.patch('raven.contrib.django.views.is_valid_origin', mock.Mock(return_value=True)) def test_sends_authorization_header(self): - resp = self.client.post(self.path, HTTP_ORIGIN='http://example.com', - HTTP_AUTHORIZATION='Sentry foo/bar', data='{}', content_type='application/octet-stream') + with self.settings(ROOT_URLCONF=self.urls): + resp = self.client.post(self.path, HTTP_ORIGIN='http://example.com', + HTTP_AUTHORIZATION='Sentry foo/bar', data='{}', content_type='application/octet-stream') assert resp.status_code == 200 event = client.events.pop(0) assert event == {'auth_header': 'Sentry foo/bar'} @mock.patch('raven.contrib.django.views.is_valid_origin', mock.Mock(return_value=True)) def test_sends_x_sentry_auth_header(self): - resp = self.client.post(self.path, HTTP_ORIGIN='http://example.com', - HTTP_X_SENTRY_AUTH='Sentry foo/bar', data='{}', - content_type='application/octet-stream') + with self.settings(ROOT_URLCONF=self.urls): + resp = self.client.post(self.path, HTTP_ORIGIN='http://example.com', + HTTP_X_SENTRY_AUTH='Sentry foo/bar', data='{}', + content_type='application/octet-stream') assert resp.status_code == 200 event = client.events.pop(0) assert event == {'auth_header': 'Sentry foo/bar'} diff --git a/tests/contrib/django/urls.py b/tests/contrib/django/urls.py index a1d292945..c4b3abfa3 100644 --- a/tests/contrib/django/urls.py +++ b/tests/contrib/django/urls.py @@ -19,14 +19,15 @@ def handler500(request): raise ValueError('handler500') return HttpResponse('', status=500) - -urlpatterns = [ - url(r'^no-error$', 'tests.contrib.django.views.no_error', name='sentry-no-error'), - url(r'^fake-login$', 'tests.contrib.django.views.fake_login', name='sentry-fake-login'), - url(r'^trigger-500$', 'tests.contrib.django.views.raise_exc', name='sentry-raise-exc'), - url(r'^trigger-500-ioerror$', 'tests.contrib.django.views.raise_ioerror', name='sentry-raise-ioerror'), - url(r'^trigger-500-decorated$', 'tests.contrib.django.views.decorated_raise_exc', name='sentry-raise-exc-decor'), - url(r'^trigger-500-django$', 'tests.contrib.django.views.django_exc', name='sentry-django-exc'), - url(r'^trigger-500-template$', 'tests.contrib.django.views.template_exc', name='sentry-template-exc'), - url(r'^trigger-500-log-request$', 'tests.contrib.django.views.logging_request_exc', name='sentry-log-request-exc'), -] +import tests.contrib.django.views + +urlpatterns = ( + url(r'^no-error$', tests.contrib.django.views.no_error, name='sentry-no-error'), + url(r'^fake-login$', tests.contrib.django.views.fake_login, name='sentry-fake-login'), + url(r'^trigger-500$', tests.contrib.django.views.raise_exc, name='sentry-raise-exc'), + url(r'^trigger-500-ioerror$', tests.contrib.django.views.raise_ioerror, name='sentry-raise-ioerror'), + url(r'^trigger-500-decorated$', tests.contrib.django.views.decorated_raise_exc, name='sentry-raise-exc-decor'), + url(r'^trigger-500-django$', tests.contrib.django.views.django_exc, name='sentry-django-exc'), + url(r'^trigger-500-template$', tests.contrib.django.views.template_exc, name='sentry-template-exc'), + url(r'^trigger-500-log-request$', tests.contrib.django.views.logging_request_exc, name='sentry-log-request-exc'), +) From 384e0058239b2712b5cc8ccae7eaecf63094406b Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 4 Aug 2016 11:33:30 -0700 Subject: [PATCH 341/692] 5.25.0.dev0 --- raven/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/__init__.py b/raven/__init__.py index 5e317cb5a..a0b273033 100644 --- a/raven/__init__.py +++ b/raven/__init__.py @@ -12,7 +12,7 @@ __all__ = ('VERSION', 'Client', 'get_version') -VERSION = '5.23.0' +VERSION = '5.25.0.dev0' def _get_git_revision(path): From 22895569321bb110a735110fd84f760d91e4a43a Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 4 Aug 2016 11:33:02 -0700 Subject: [PATCH 342/692] Changes for 5.24.0 --- CHANGES | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGES b/CHANGES index fbff7f39f..ad5e6f13e 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,10 @@ +Version 5.24.0 +-------------- + +* Added support for Django 1.10. +* Added support for chained exceptions in Python 3. +* Fixed various behavior with handling template errors in Django 1.8+. + Version 5.23.0 -------------- From 1cbb22b48d62885f2a5cd9bec86b7b9e507451f5 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 4 Aug 2016 15:52:51 -0700 Subject: [PATCH 343/692] Correct include_paths support --- raven/contrib/flask.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/contrib/flask.py b/raven/contrib/flask.py index df7423a10..e10e71b42 100644 --- a/raven/contrib/flask.py +++ b/raven/contrib/flask.py @@ -38,7 +38,7 @@ def make_client(client_cls, app, dsn=None): defaults={ 'include_paths': ( set(app.config.get('SENTRY_INCLUDE_PATHS', [])) - | set([app.import_name]), + | set([app.import_name]) ), # support legacy RAVEN_IGNORE_EXCEPTIONS 'ignore_exceptions': [ From fe75095d6f0c10a1f84ee8ef7ef79f111b6339de Mon Sep 17 00:00:00 2001 From: David Cramer Date: Fri, 5 Aug 2016 11:04:32 -0700 Subject: [PATCH 344/692] Fix behavior of raven command on Django 1.8+ --- .../django/management/commands/raven.py | 50 ++++++++++++++----- tests/contrib/django/management/__init__.py | 0 .../django/management/commands/__init__.py | 0 .../django/management/commands/test_raven.py | 24 +++++++++ 4 files changed, 61 insertions(+), 13 deletions(-) create mode 100644 tests/contrib/django/management/__init__.py create mode 100644 tests/contrib/django/management/commands/__init__.py create mode 100644 tests/contrib/django/management/commands/test_raven.py diff --git a/raven/contrib/django/management/commands/raven.py b/raven/contrib/django/management/commands/raven.py index 50f044f28..c5194372f 100644 --- a/raven/contrib/django/management/commands/raven.py +++ b/raven/contrib/django/management/commands/raven.py @@ -2,7 +2,7 @@ raven.contrib.django.management.commands.raven ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -:copyright: (c) 2010-2013 by the Sentry Team, see AUTHORS for more details +:copyright: (c) 2010-2016 by the Sentry Team, see AUTHORS for more details :license: BSD, see LICENSE for more details. """ from __future__ import absolute_import, print_function @@ -10,28 +10,52 @@ from django.core.management.base import BaseCommand from optparse import make_option from raven.scripts.runner import store_json, send_test_message + +import argparse +import django import sys import time +DJANGO_18 = django.VERSION >= (1, 8, 0) + + +class StoreJsonAction(argparse.Action): + def __call__(self, parser, namespace, values, option_string=None): + setattr(namespace, self.dest, store_json(values[0])) + class Command(BaseCommand): help = 'Commands to interact with the Sentry client' - option_list = BaseCommand.option_list + ( - make_option( - "--data", action="callback", callback=store_json, - type="string", nargs=1, dest="data"), - make_option( - "--tags", action="callback", callback=store_json, - type="string", nargs=1, dest="tags"), - ) - - def handle(self, *args, **options): - if len(args) != 1 or args[0] != 'test': + if not DJANGO_18: + option_list = BaseCommand.option_list + ( + make_option( + '--data', action='callback', callback=store_json, + type='string', nargs=1, dest='data'), + make_option( + '--tags', action='callback', callback=store_json, + type='string', nargs=1, dest='tags'), + ) + else: + def add_arguments(self, parser): + parser.add_argument( + '--data', action=StoreJsonAction, + nargs=1, dest='data', + ) + parser.add_argument( + '--tags', action=StoreJsonAction, + nargs=1, dest='tags', + ) + + def handle(self, command=None, *args, **options): + if command != 'test': print('Usage: manage.py raven test') sys.exit(1) from raven.contrib.django.models import client - send_test_message(client, options) + send_test_message(client, { + 'tags': options.get('tags'), + 'data': options.get('data'), + }) time.sleep(3) diff --git a/tests/contrib/django/management/__init__.py b/tests/contrib/django/management/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/contrib/django/management/commands/__init__.py b/tests/contrib/django/management/commands/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/contrib/django/management/commands/test_raven.py b/tests/contrib/django/management/commands/test_raven.py new file mode 100644 index 000000000..5825c4b81 --- /dev/null +++ b/tests/contrib/django/management/commands/test_raven.py @@ -0,0 +1,24 @@ +from __future__ import absolute_import + +import django + +from django.core.management import call_command +from django.test import TestCase +from mock import patch + +from raven.contrib.django.models import client + +DJANGO_18 = django.VERSION >= (1, 8, 0) + + +class RavenCommandTest(TestCase): + @patch('raven.contrib.django.management.commands.raven.send_test_message') + def test_basic(self, mock_send_test_message): + call_command('raven', command='test') + + mock_send_test_message.assert_called_once_with( + client, { + 'tags': None, + 'data': None, + } + ) From fd05de5c21e283fa972aa4b64028ac7e98fe0b4c Mon Sep 17 00:00:00 2001 From: David Cramer Date: Fri, 5 Aug 2016 16:35:31 -0700 Subject: [PATCH 345/692] Add test for Flask failure --- tests/contrib/flask/tests.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/contrib/flask/tests.py b/tests/contrib/flask/tests.py index 36bf573a3..7600924b6 100644 --- a/tests/contrib/flask/tests.py +++ b/tests/contrib/flask/tests.py @@ -265,9 +265,13 @@ def test_logging_setup_with_exclusion_list(self): def test_check_client_type(self): self.assertRaises(TypeError, lambda _: Sentry(self.app, "oops, I'm putting my DSN instead")) + def test_uses_dsn(self): + app = Flask(__name__) + sentry = Sentry(app, dsn='http://public:secret@example.com/1') + assert sentry.client.remote.base_url == 'http://example.com/1' -class FlaskLoginTest(BaseTest): +class FlaskLoginTest(BaseTest): @fixture def app(self): return create_app(SENTRY_USER_ATTRS=['name']) From e03c642d689ff9d69ba1e2f3cb08c52ea89c2db0 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Fri, 5 Aug 2016 16:36:19 -0700 Subject: [PATCH 346/692] Correct Flask dsn behavior --- raven/contrib/flask.py | 1 + tests/contrib/flask/tests.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/raven/contrib/flask.py b/raven/contrib/flask.py index e10e71b42..26b05482d 100644 --- a/raven/contrib/flask.py +++ b/raven/contrib/flask.py @@ -36,6 +36,7 @@ def make_client(client_cls, app, dsn=None): **convert_options( app.config, defaults={ + 'dsn': dsn, 'include_paths': ( set(app.config.get('SENTRY_INCLUDE_PATHS', [])) | set([app.import_name]) diff --git a/tests/contrib/flask/tests.py b/tests/contrib/flask/tests.py index 7600924b6..18586165f 100644 --- a/tests/contrib/flask/tests.py +++ b/tests/contrib/flask/tests.py @@ -268,7 +268,7 @@ def test_check_client_type(self): def test_uses_dsn(self): app = Flask(__name__) sentry = Sentry(app, dsn='http://public:secret@example.com/1') - assert sentry.client.remote.base_url == 'http://example.com/1' + assert sentry.client.remote.base_url == 'http://example.com' class FlaskLoginTest(BaseTest): From d53948b5f092c06b80808c0688b6d191cc125f1a Mon Sep 17 00:00:00 2001 From: Mervyn Chng Date: Mon, 8 Aug 2016 09:25:01 +0000 Subject: [PATCH 347/692] Add Celery config option to ignore expected exceptions --- docs/integrations/celery.rst | 11 ++++++++--- raven/contrib/celery/__init__.py | 5 ++++- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/docs/integrations/celery.rst b/docs/integrations/celery.rst index 7690c9ac1..1cc5583e7 100644 --- a/docs/integrations/celery.rst +++ b/docs/integrations/celery.rst @@ -16,14 +16,19 @@ Celery error handling:: # register a custom filter to filter out duplicate logs register_logger_signal(client) - # hook into the Celery error handler - register_signal(client) - # The register_logger_signal function can also take an optional argument # `loglevel` which is the level used for the handler created. # Defaults to `logging.ERROR` register_logger_signal(client, loglevel=logging.INFO) + # hook into the Celery error handler + register_signal(client) + + # The register_signal function can also take an optional argument + # `ignore_expected` which causes exception classes specified in Task.throws + # to be ignored + register_signal(client, ignore_expected=True) + A more complex version to encapsulate behavior: .. code-block:: python diff --git a/raven/contrib/celery/__init__.py b/raven/contrib/celery/__init__.py index 567459641..3fec34c26 100644 --- a/raven/contrib/celery/__init__.py +++ b/raven/contrib/celery/__init__.py @@ -24,8 +24,11 @@ def filter(self, record): return extra_data.get('internal', record.funcName != '_log_error') -def register_signal(client): +def register_signal(client, ignore_expected=False): def process_failure_signal(sender, task_id, args, kwargs, einfo, **kw): + if ignore_expected and isinstance(einfo.exception, sender.throws): + return + # This signal is fired inside the stack so let raven do its magic if isinstance(einfo.exception, SoftTimeLimitExceeded): fingerprint = ['celery', 'SoftTimeLimitExceeded', sender] From 5615a8a9791a224983743a0b5abe0014bb64c4f6 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 8 Aug 2016 11:37:25 -0700 Subject: [PATCH 348/692] Add example Django 1.10 app --- examples/django_110/app/__init__.py | 0 examples/django_110/app/settings.py | 128 +++++++++++++++++++++++++++ examples/django_110/app/urls.py | 24 +++++ examples/django_110/app/views.py | 8 ++ examples/django_110/app/wsgi.py | 16 ++++ examples/django_110/db.sqlite3 | Bin 0 -> 3072 bytes examples/django_110/manage.py | 22 +++++ examples/django_110/requirements.txt | 2 + 8 files changed, 200 insertions(+) create mode 100644 examples/django_110/app/__init__.py create mode 100644 examples/django_110/app/settings.py create mode 100644 examples/django_110/app/urls.py create mode 100644 examples/django_110/app/views.py create mode 100644 examples/django_110/app/wsgi.py create mode 100644 examples/django_110/db.sqlite3 create mode 100755 examples/django_110/manage.py create mode 100644 examples/django_110/requirements.txt diff --git a/examples/django_110/app/__init__.py b/examples/django_110/app/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/examples/django_110/app/settings.py b/examples/django_110/app/settings.py new file mode 100644 index 000000000..be9c0866c --- /dev/null +++ b/examples/django_110/app/settings.py @@ -0,0 +1,128 @@ +""" +Django settings for django_110 project. + +Generated by 'django-admin startproject' using Django 1.10. + +For more information on this file, see +https://docs.djangoproject.com/en/1.10/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/1.10/ref/settings/ +""" + +import os + +# Build paths inside the project like this: os.path.join(BASE_DIR, ...) +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = 'jlyce0zs(ux$*gn3t9hzdh!+%pf4%6)i$v-usu!o1-ab-+s*m)' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = [] + + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + + 'raven.contrib.django.raven_compat', +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'app.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'app.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/1.10/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + } +} + + +# Password validation +# https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/1.10/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_L10N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/1.10/howto/static-files/ + +STATIC_URL = '/static/' + +RAVEN_CONFIG = { + 'environment': 'development', + 'dsn': 'https://f0a3752c685647c6bf7830ce66a9ace5:cbf73b8a273b429a828e470e4c986bf8@app.getsentry.com/1254', + 'release': '1.0', +} diff --git a/examples/django_110/app/urls.py b/examples/django_110/app/urls.py new file mode 100644 index 000000000..00193fe9e --- /dev/null +++ b/examples/django_110/app/urls.py @@ -0,0 +1,24 @@ +"""django_110 URL Configuration + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/1.10/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.conf.urls import url, include + 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) +""" +from django.conf.urls import url +from django.contrib import admin + +from .views import home + +urlpatterns = [ + url(r'^$', home), + url(r'^admin/', admin.site.urls), +] diff --git a/examples/django_110/app/views.py b/examples/django_110/app/views.py new file mode 100644 index 000000000..8ff0df98b --- /dev/null +++ b/examples/django_110/app/views.py @@ -0,0 +1,8 @@ +import logging + +logger = logging.getLogger('app') + + +def home(request): + logger.info('Doing some division') + 1 / 0 diff --git a/examples/django_110/app/wsgi.py b/examples/django_110/app/wsgi.py new file mode 100644 index 000000000..c852ea9c3 --- /dev/null +++ b/examples/django_110/app/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for django_110 project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/1.10/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "app.settings") + +application = get_wsgi_application() diff --git a/examples/django_110/db.sqlite3 b/examples/django_110/db.sqlite3 new file mode 100644 index 0000000000000000000000000000000000000000..8e14485da7b7c0ddc3cc681f721e4c0acedc2276 GIT binary patch literal 3072 zcmeHGJ#WG=5VaH3PMuj#R)<7TA+=&}T4g{Dfu?52gk0jLuJQqi+r9mX{15&^FO@(7 zgqWxpeA3x^zI*4>v+w0OP+G!US>-~*9cd9t$vpregreK%Yin?84RCMpF{9*WwYV&1AjE@Ow{dx%iaDY!}*(k?(OIxc3nFu#@W)B9~oc zy5BLqZ1;NfNBq@HK17k0(_E#M(5fuzy#s5jF58PUaF|LMs6xwBRuGOjgp(kEaWoiu zQ4CLh4Bmu~1|e1&`XTQE6Z1KPFHz0jMdjS~`|gH@!KT;4QKV){;)RLOQmb5UsBSZx eTT^|~w=uWoz01 literal 0 HcmV?d00001 diff --git a/examples/django_110/manage.py b/examples/django_110/manage.py new file mode 100755 index 000000000..b62ddda69 --- /dev/null +++ b/examples/django_110/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +import os +import sys + +if __name__ == "__main__": + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "app.settings") + try: + from django.core.management import execute_from_command_line + except ImportError: + # The above import may fail for some other reason. Ensure that the + # issue is really that Django is missing to avoid masking other + # exceptions on Python 2. + try: + import django # NOQA + except ImportError: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) + raise + execute_from_command_line(sys.argv) diff --git a/examples/django_110/requirements.txt b/examples/django_110/requirements.txt new file mode 100644 index 000000000..acc0045da --- /dev/null +++ b/examples/django_110/requirements.txt @@ -0,0 +1,2 @@ +-e ../../ +Django>=1.10,<1.11 From db36e5f6df863b1ae97addd225f4d45f5e7f97b9 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 8 Aug 2016 12:32:06 -0700 Subject: [PATCH 349/692] Correct support for test command in Django 1.8+ --- raven/contrib/django/management/commands/raven.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/raven/contrib/django/management/commands/raven.py b/raven/contrib/django/management/commands/raven.py index c5194372f..eb4b4599a 100644 --- a/raven/contrib/django/management/commands/raven.py +++ b/raven/contrib/django/management/commands/raven.py @@ -38,6 +38,9 @@ class Command(BaseCommand): ) else: def add_arguments(self, parser): + parser.add_argument( + 'command', nargs=1, + ) parser.add_argument( '--data', action=StoreJsonAction, nargs=1, dest='data', @@ -48,7 +51,7 @@ def add_arguments(self, parser): ) def handle(self, command=None, *args, **options): - if command != 'test': + if command not in ('test', ['test']): print('Usage: manage.py raven test') sys.exit(1) From 3c17e45cd6eae85c6d54c82512f1c23dd3cd41ce Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 8 Aug 2016 12:37:19 -0700 Subject: [PATCH 350/692] Correct test for Django 1.8+ --- tests/contrib/django/management/commands/test_raven.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/contrib/django/management/commands/test_raven.py b/tests/contrib/django/management/commands/test_raven.py index 5825c4b81..b857dc9d4 100644 --- a/tests/contrib/django/management/commands/test_raven.py +++ b/tests/contrib/django/management/commands/test_raven.py @@ -14,7 +14,7 @@ class RavenCommandTest(TestCase): @patch('raven.contrib.django.management.commands.raven.send_test_message') def test_basic(self, mock_send_test_message): - call_command('raven', command='test') + call_command('raven', 'test') mock_send_test_message.assert_called_once_with( client, { From 83984a2ad4853e1689b0aee1a800aa42618ace90 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 8 Aug 2016 12:59:04 -0700 Subject: [PATCH 351/692] Discover Lambda and GAE In environments which look like AWS Lambda or Google App Engine utilize the synchronous transport. @getsentry/python Fixes GH-816 --- raven/conf/remote.py | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/raven/conf/remote.py b/raven/conf/remote.py index f0918315e..10149c42c 100644 --- a/raven/conf/remote.py +++ b/raven/conf/remote.py @@ -1,16 +1,39 @@ from __future__ import absolute_import +import logging +import os import warnings from raven._compat import PY2, text_type from raven.exceptions import InvalidDsn -from raven.transport.threaded import ThreadedHTTPTransport from raven.utils.encoding import to_string from raven.utils.urlparse import parse_qsl, urlparse ERR_UNKNOWN_SCHEME = 'Unsupported Sentry DSN scheme: {0} ({1})' -DEFAULT_TRANSPORT = ThreadedHTTPTransport +logger = logging.getLogger('raven') + + +def discover_default_transport(): + from raven.transport.threaded import ThreadedHTTPTransport + from raven.transport.http import HTTPTransport + + # Google App Engine + # https://cloud.google.com/appengine/docs/python/how-requests-are-handled#Python_The_environment + if 'CURRENT_VERSION_ID' in os.environ and 'INSTANCE_ID' in os.environ: + logger.info('Detected environment to be Google App Engine. Using synchronous HTTP transport.') + return HTTPTransport + + # AWS Lambda + # https://alestic.com/2014/11/aws-lambda-environment/ + if 'LAMBDA_TASK_ROOT' in os.environ: + logger.info('Detected environment to be AWS Lambda. Using synchronous HTTP transport.') + return HTTPTransport + + return ThreadedHTTPTransport + + +DEFAULT_TRANSPORT = discover_default_transport() class RemoteConfig(object): From c2be387c26b2069210d05fdc9aa6ccbf54abfe90 Mon Sep 17 00:00:00 2001 From: Eric Feng Date: Wed, 10 Aug 2016 11:44:16 -0700 Subject: [PATCH 352/692] docs typo fix --- docs/integrations/flask.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/integrations/flask.rst b/docs/integrations/flask.rst index 97be55347..459c400aa 100644 --- a/docs/integrations/flask.rst +++ b/docs/integrations/flask.rst @@ -76,7 +76,7 @@ using ``SENTRY_USER_ATTRS``:: SENTRY_USER_ATTRS = ['username', 'first_name', 'last_name', 'email'] ``email`` will be captured as ``sentry.interfaces.User.email``, and any -additionl attributes will be available under +additional attributes will be available under ``sentry.interfaces.User.data`` You can specify the types of exceptions that should not be reported by From e45244b54a0ae15e9835ea12f20896534cb427d8 Mon Sep 17 00:00:00 2001 From: Eric Feng Date: Wed, 10 Aug 2016 13:44:57 -0700 Subject: [PATCH 353/692] Flask config docs Adding instructions on how to add configuration to the Flask Sentry client. Resolves #829. /cc @armin --- docs/integrations/flask.rst | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/integrations/flask.rst b/docs/integrations/flask.rst index 459c400aa..637d07322 100644 --- a/docs/integrations/flask.rst +++ b/docs/integrations/flask.rst @@ -110,6 +110,17 @@ 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`` + + app.config['SENTRY_RELEASE'] = raven.fetch_git_sha(os.path.dirname(__file__)) + +Sentry automatically looks for ``SENTRY_RELEASE``, ``SENTRY_CONTEXT``, +``SENTRY_ENVIRONMENT``, ``SENTRY_TAGS``, and ``SENTRY_IGNORE_EXCEPTIONS``. + Getting The Last Event ID ------------------------- From d71e27c4af796012a016eb5c576803016182dc66 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 11 Aug 2016 11:33:03 -0700 Subject: [PATCH 354/692] Prevent chained exception recursion Fixes GH-832 @getsentry/python --- raven/events.py | 5 +++++ tests/events/tests.py | 31 +++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/raven/events.py b/raven/events.py index 67402bead..7434fe9b0 100644 --- a/raven/events.py +++ b/raven/events.py @@ -46,12 +46,17 @@ def _chained_exceptions(exc_info): yield exc_info exc_type, exc, exc_traceback = exc_info + context = set() + context.add(exc) while True: if exc.__suppress_context__: # Then __cause__ should be used instead. exc = exc.__cause__ else: exc = exc.__context__ + if exc in context: + break + context.add(exc) if exc is None: break yield type(exc), exc, exc.__traceback__ diff --git a/tests/events/tests.py b/tests/events/tests.py index a75e326fe..e5795b402 100644 --- a/tests/events/tests.py +++ b/tests/events/tests.py @@ -1,3 +1,6 @@ +from __future__ import absolute_import + +import pytest import six from raven.base import Client @@ -67,3 +70,31 @@ def test_raise_from_different(self): six.raise_from(KeyError(), TypeError()) except Exception: self.check_capture(['KeyError', 'TypeError']) + + def test_handles_self_referencing(self): + try: + raise ValueError() + except Exception as exc: + try: + six.raise_from(exc, exc) + except Exception: + self.check_capture(['ValueError']) + else: + pytest.fail() + else: + pytest.fail() + + try: + raise ValueError() + except Exception as exc: + try: + six.raise_from(KeyError(), exc) + except KeyError as exc2: + try: + six.raise_from(exc, exc2) + except Exception: + self.check_capture(['ValueError', 'KeyError']) + else: + pytest.fail() + else: + pytest.fail() From e0bc077aef563e55bef5ee0e49431cad1d3cb076 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 15 Aug 2016 11:28:41 -0700 Subject: [PATCH 355/692] Add test confirmining include_paths (refs GH-834) --- tests/contrib/flask/tests.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/contrib/flask/tests.py b/tests/contrib/flask/tests.py index 18586165f..cf87329ba 100644 --- a/tests/contrib/flask/tests.py +++ b/tests/contrib/flask/tests.py @@ -270,6 +270,17 @@ def test_uses_dsn(self): sentry = Sentry(app, dsn='http://public:secret@example.com/1') assert sentry.client.remote.base_url == 'http://example.com' + def test_binds_default_include_paths(self): + app = Flask(__name__) + sentry = Sentry(app, dsn='http://public:secret@example.com/1') + assert sentry.client.include_paths == set([app.import_name]) + + def test_overrides_default_include_paths(self): + app = Flask(__name__) + app.config['SENTRY_CONFIG'] = {'include_paths': ['foo.bar']} + sentry = Sentry(app, dsn='http://public:secret@example.com/1') + assert sentry.client.include_paths == set(['foo.bar']) + class FlaskLoginTest(BaseTest): @fixture From 10d0774a8c7d2e04e92dd97af1de7079a6441bb1 Mon Sep 17 00:00:00 2001 From: James Cunningham Date: Fri, 19 Aug 2016 10:57:37 -0700 Subject: [PATCH 356/692] Rebase closingiterator and apply changes since it was first posted. --- raven/middleware.py | 79 +++++++++++++++++++++++++-------------- tests/middleware/tests.py | 46 ++++++++++++++++------- 2 files changed, 83 insertions(+), 42 deletions(-) diff --git a/raven/middleware.py b/raven/middleware.py index 4607489c8..492800695 100644 --- a/raven/middleware.py +++ b/raven/middleware.py @@ -7,10 +7,59 @@ """ from __future__ import absolute_import +from six import Iterator, next + from raven.utils.wsgi import ( get_current_url, get_headers, get_environ) +class ClosingIterator(Iterator): + """ + An iterator that is implements a ``close`` method as-per + WSGI recommendation. + """ + def __init__(self, sentry, iterable, environ): + self.sentry = sentry + self.environ = environ + self.iterable = iter(iterable) + + def __iter__(self): + return self + + def __next__(self): + try: + return next(self.iterable) + except StopIteration: + # propagate up the normal StopIteration + raise + except Exception: + # but capture any other exception, then re-raise + self.sentry.handle_exception(self.environ) + raise + except KeyboardInterrupt: + self.sentry.handle_exception(self.environ) + raise + except SystemExit as e: + if e.code != 0: + self.sentry.handle_exception(self.environ) + raise + + def close(self): + if hasattr(self.iterable, 'close') and callable(self.iterable.close): + try: + self.iterable.close() + except Exception: + self.sentry.handle_exception(self.environ) + except KeyboardInterrupt: + self.sentry.handle_exception(self.environ) + raise + except SystemExit as e: + if e.code != 0: + self.sentry.handle_exception(self.environ) + raise + self.sentry.client.context.clear() + + class Sentry(object): """ A WSGI middleware which will attempt to capture any @@ -44,35 +93,7 @@ def __call__(self, environ, start_response): self.handle_exception(environ) raise - try: - for event in iterable: - yield event - except Exception: - self.handle_exception(environ) - raise - except KeyboardInterrupt: - self.handle_exception(environ) - raise - except SystemExit as e: - if e.code != 0: - self.handle_exception(environ) - raise - finally: - # wsgi spec requires iterable to call close if it exists - # see http://blog.dscpl.com.au/2012/10/obligations-for-calling-close-on.html - if iterable and hasattr(iterable, 'close') and callable(iterable.close): - try: - iterable.close() - except Exception: - self.handle_exception(environ) - except KeyboardInterrupt: - self.handle_exception(environ) - raise - except SystemExit as e: - if e.code != 0: - self.handle_exception(environ) - raise - self.client.context.clear() + return ClosingIterator(self, iterable, environ) def get_http_context(self, environ): return { diff --git a/tests/middleware/tests.py b/tests/middleware/tests.py index e5b4da278..d81a92703 100644 --- a/tests/middleware/tests.py +++ b/tests/middleware/tests.py @@ -3,6 +3,7 @@ import logging import webob from exam import fixture +from six import Iterator from raven.utils.testutils import TestCase from raven.base import Client @@ -21,11 +22,14 @@ def send(self, **kwargs): self.events.append(kwargs) -class ErroringIterable(object): +class ErroringIterable(Iterator): def __init__(self): self.closed = False def __iter__(self): + return self + + def __next__(self): raise ValueError('hello world') def close(self): @@ -37,9 +41,27 @@ def __init__(self, exc_func): self._exc_func = exc_func def __iter__(self): + return self + + def __next__(self): raise self._exc_func() +class SimpleIteratable(Iterator): + def __init__(self): + self.closed = False + self._iter = iter(['a']) + + def __iter__(self): + return self + + def __next__(self): + return next(self._iter) + + def close(self): + self.closed = True + + class ExampleApp(object): def __init__(self, iterable): self.iterable = iterable @@ -67,9 +89,6 @@ def test_captures_error_in_iteration(self): with self.assertRaises(ValueError): response = list(response) - # TODO: this should be a separate test - self.assertTrue(iterable.closed, True) - self.assertEquals(len(self.client.events), 1) event = self.client.events.pop(0) @@ -105,9 +124,6 @@ def test_systemexit_0_is_ignored(self): with self.assertRaises(SystemExit): response = list(response) - # TODO: this should be a separate test - self.assertTrue(iterable.closed, True) - self.assertEquals(len(self.client.events), 0) def test_systemexit_is_captured(self): @@ -120,9 +136,6 @@ def test_systemexit_is_captured(self): with self.assertRaises(SystemExit): response = list(response) - # TODO: this should be a separate test - self.assertTrue(iterable.closed, True) - self.assertEquals(len(self.client.events), 1) event = self.client.events.pop(0) @@ -143,9 +156,6 @@ def test_keyboard_interrupt_is_captured(self): with self.assertRaises(KeyboardInterrupt): response = list(response) - # TODO: this should be a separate test - self.assertTrue(iterable.closed, True) - self.assertEquals(len(self.client.events), 1) event = self.client.events.pop(0) @@ -154,3 +164,13 @@ def test_keyboard_interrupt_is_captured(self): self.assertEquals(exc['type'], 'KeyboardInterrupt') self.assertEquals(exc['value'], '') self.assertEquals(event['level'], logging.ERROR) + + def test_close(self): + iterable = SimpleIteratable() + app = ExampleApp(iterable) + middleware = Sentry(app, client=self.client) + + response = middleware(self.request.environ, lambda *args: None) + list(response) # exhaust iterator + response.close() + self.assertTrue(iterable.closed, True) From 71271c88b115dc568e7d84b2ae38a80364457c37 Mon Sep 17 00:00:00 2001 From: James Cunningham Date: Mon, 22 Aug 2016 09:14:13 -0700 Subject: [PATCH 357/692] Vendorize six.Iterator and six.next. --- raven/_compat.py | 41 +++++++++++++++++++++++++++++++++++++++++ raven/middleware.py | 3 +-- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/raven/_compat.py b/raven/_compat.py index 3d5e28de7..607c54d1a 100644 --- a/raven/_compat.py +++ b/raven/_compat.py @@ -182,3 +182,44 @@ def get_code(func): if rv is None: raise TypeError('Could not get code from %r' % type(func).__name__) return rv + +try: + advance_iterator = next +except NameError: + def advance_iterator(it): + return it.next() + next = advance_iterator + +try: + callable = callable +except NameError: + def callable(obj): + return any("__call__" in klass.__dict__ for klass in type(obj).__mro__) + + +if not PY2: + def get_unbound_function(unbound): + return unbound + + create_bound_method = types.MethodType + + def create_unbound_method(func, cls): + return func + + Iterator = object +else: + def get_unbound_function(unbound): + return unbound.im_func + + def create_bound_method(func, obj): + return types.MethodType(func, obj, obj.__class__) + + def create_unbound_method(func, cls): + return types.MethodType(func, None, cls) + + class Iterator(object): + + def next(self): + return type(self).__next__(self) + + callable = callable diff --git a/raven/middleware.py b/raven/middleware.py index 492800695..86fa5d9c2 100644 --- a/raven/middleware.py +++ b/raven/middleware.py @@ -7,8 +7,7 @@ """ from __future__ import absolute_import -from six import Iterator, next - +from raven._compat import Iterator, next from raven.utils.wsgi import ( get_current_url, get_headers, get_environ) From 789b84c1d5cbbbaafadb9d17a80736634d6623ed Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 22 Aug 2016 11:03:29 -0700 Subject: [PATCH 358/692] Unified exception handling in closing iterator --- raven/middleware.py | 70 +++++++++++++++++++-------------------------- 1 file changed, 30 insertions(+), 40 deletions(-) diff --git a/raven/middleware.py b/raven/middleware.py index 86fa5d9c2..a6b9b06c8 100644 --- a/raven/middleware.py +++ b/raven/middleware.py @@ -7,11 +7,34 @@ """ from __future__ import absolute_import +from contextlib import contextmanager + from raven._compat import Iterator, next from raven.utils.wsgi import ( get_current_url, get_headers, get_environ) +@contextmanager +def common_exception_handling(environ, client): + try: + yield + except (StopIteration, GeneratorExit): + # Make sure we do this explicitly here. At least GeneratorExit + # is handled implicitly by the rest of the logic but we want + # to make sure this does not regress + raise + except Exception: + client.handle_exception(environ) + raise + except KeyboardInterrupt: + client.handle_exception(environ) + raise + except SystemExit as e: + if e.code != 0: + client.handle_exception(environ) + raise + + class ClosingIterator(Iterator): """ An iterator that is implements a ``close`` method as-per @@ -26,36 +49,15 @@ def __iter__(self): return self def __next__(self): - try: + with common_exception_handling(self.environ, self.sentry): return next(self.iterable) - except StopIteration: - # propagate up the normal StopIteration - raise - except Exception: - # but capture any other exception, then re-raise - self.sentry.handle_exception(self.environ) - raise - except KeyboardInterrupt: - self.sentry.handle_exception(self.environ) - raise - except SystemExit as e: - if e.code != 0: - self.sentry.handle_exception(self.environ) - raise def close(self): - if hasattr(self.iterable, 'close') and callable(self.iterable.close): - try: - self.iterable.close() - except Exception: - self.sentry.handle_exception(self.environ) - except KeyboardInterrupt: - self.sentry.handle_exception(self.environ) - raise - except SystemExit as e: - if e.code != 0: - self.sentry.handle_exception(self.environ) - raise + try: + if hasattr(self.iterable, 'close'): + with common_exception_handling(self.environ, self.sentry): + self.iterable.close() + finally: self.sentry.client.context.clear() @@ -78,20 +80,8 @@ def __call__(self, environ, start_response): # TODO(dcramer): ideally this is lazy, but the context helpers must # support callbacks first self.client.http_context(self.get_http_context(environ)) - - try: + with common_exception_handling(environ, self): iterable = self.application(environ, start_response) - except Exception: - self.handle_exception(environ) - raise - except KeyboardInterrupt: - self.handle_exception(environ) - raise - except SystemExit as e: - if e.code != 0: - self.handle_exception(environ) - raise - return ClosingIterator(self, iterable, environ) def get_http_context(self, environ): From 1f8489e956566b8bb947b4089f26b448626e229a Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 23 Aug 2016 23:57:31 +0200 Subject: [PATCH 359/692] Added changelog --- CHANGES | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGES b/CHANGES index ad5e6f13e..c7fb2688c 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,13 @@ +Version 5.25.0 +-------------- + +* Added various improvements for the WSGI and Django support. +* Prevent chained exception recursion +* In environments which look like AWS Lambda or Google App Engine utilize the + synchronous transport. +* Added Celery config option to ignore expected exceptions +* Improved DSN handling in Flask client. + Version 5.24.0 -------------- From 8ad915783ee9ef68cb6e77941c3ce13e8758ea3d Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 23 Aug 2016 23:57:49 +0200 Subject: [PATCH 360/692] 5.25.0 --- raven/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/__init__.py b/raven/__init__.py index a0b273033..e35ee7bd4 100644 --- a/raven/__init__.py +++ b/raven/__init__.py @@ -12,7 +12,7 @@ __all__ = ('VERSION', 'Client', 'get_version') -VERSION = '5.25.0.dev0' +VERSION = '5.25.0' def _get_git_revision(path): From 69f36d86f57630cfb3936f31f954f45791e17d43 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 23 Aug 2016 23:59:06 +0200 Subject: [PATCH 361/692] Ready for 5.26 --- raven/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/__init__.py b/raven/__init__.py index e35ee7bd4..9ba41b204 100644 --- a/raven/__init__.py +++ b/raven/__init__.py @@ -12,7 +12,7 @@ __all__ = ('VERSION', 'Client', 'get_version') -VERSION = '5.25.0' +VERSION = '5.26.0.dev.0' def _get_git_revision(path): From f43e53aba830e6b4b9ad8440af5e6c3992ee3347 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 24 Aug 2016 00:05:46 +0200 Subject: [PATCH 362/692] Normalized version --- raven/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/__init__.py b/raven/__init__.py index 9ba41b204..55ac29043 100644 --- a/raven/__init__.py +++ b/raven/__init__.py @@ -12,7 +12,7 @@ __all__ = ('VERSION', 'Client', 'get_version') -VERSION = '5.26.0.dev.0' +VERSION = '5.26.0.dev0' def _get_git_revision(path): From e3d70afbe7bfe5e021d7027f13cc32104b191a2f Mon Sep 17 00:00:00 2001 From: Adam Chainz Date: Wed, 24 Aug 2016 11:42:57 +0100 Subject: [PATCH 363/692] 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 364/692] 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 365/692] 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 4f639c4c38aa24fa42a7cb8ff3ea0d9cc190f0b2 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 25 Aug 2016 14:46:57 +0200 Subject: [PATCH 366/692] Fixed potential concurrency issue with Flask event ids --- CHANGES | 5 +++++ raven/contrib/flask.py | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/CHANGES b/CHANGES index c7fb2688c..e8c259582 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 5.26.0 +-------------- + +* Fixed potential concurrency issue with event IDs in the Flask integration. + Version 5.25.0 -------------- diff --git a/raven/contrib/flask.py b/raven/contrib/flask.py index 26b05482d..a9733442e 100644 --- a/raven/contrib/flask.py +++ b/raven/contrib/flask.py @@ -115,6 +115,10 @@ def __init__(self, app=None, client=None, client_cls=Client, dsn=None, @property def last_event_id(self): + try: + return g.sentry_event_id + except Exception: + pass return getattr(self, '_last_event_id', None) @last_event_id.setter From 7de7b030895b6ad46c2a4702a7633574942ab3cb Mon Sep 17 00:00:00 2001 From: Jonas Trappenberg Date: Thu, 25 Aug 2016 10:33:07 -0700 Subject: [PATCH 367/692] Fix one usage of `get_ident` This gets rid of the corresponding DeprecationWarning when using DjangoClient.capture: /Users/abc/.venv/lib/python2.7/site-packages/raven/base.py:290: DeprecationWarning: Client.get_ident is deprecated. The event ID is now returned as the result of capture. DeprecationWarning) --- raven/contrib/django/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/contrib/django/client.py b/raven/contrib/django/client.py index b497c1b78..3fa5cc093 100644 --- a/raven/contrib/django/client.py +++ b/raven/contrib/django/client.py @@ -282,7 +282,7 @@ def capture(self, event_type, request=None, **kwargs): # attach the sentry object to the request request.sentry = { 'project_id': data.get('project', self.remote.project), - 'id': self.get_ident(result), + 'id': result, } return result From 2e88836fabd936da2a8d6f6dd03e758bc69acb2a Mon Sep 17 00:00:00 2001 From: Giang Manh Date: Fri, 26 Aug 2016 09:19:05 +0900 Subject: [PATCH 368/692] 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 962e47de4ea9f5138a197eb05aafe709085735f0 Mon Sep 17 00:00:00 2001 From: Andi Albrecht Date: Fri, 26 Aug 2016 11:45:59 +0200 Subject: [PATCH 369/692] Fix get_user_info for Django 1.10 when is_authenticated is a property (fixes #845). --- raven/contrib/django/client.py | 11 ++++++++--- tests/contrib/django/tests.py | 27 +++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/raven/contrib/django/client.py b/raven/contrib/django/client.py index b497c1b78..9b9495833 100644 --- a/raven/contrib/django/client.py +++ b/raven/contrib/django/client.py @@ -140,9 +140,14 @@ def install_sql_hook(self): install_sql_hook() def get_user_info(self, user): - if hasattr(user, 'is_authenticated') and \ - not user.is_authenticated(): - return None + if hasattr(user, 'is_authenticated'): + # is_authenticated was a method in Django < 1.10 + if callable(user.is_authenticated): + authenticated = user.is_authenticated() + else: + authenticated = user.is_authenticated + if not authenticated: + return None user_info = {} try: diff --git a/tests/contrib/django/tests.py b/tests/contrib/django/tests.py index 3aa6b746d..bb4657f2a 100644 --- a/tests/contrib/django/tests.py +++ b/tests/contrib/django/tests.py @@ -42,6 +42,7 @@ DJANGO_15 = django.VERSION >= (1, 5, 0) DJANGO_18 = django.VERSION >= (1, 8, 0) +DJANGO_110 = django.VERSION >= (1, 10, 0) def make_request(): @@ -222,6 +223,32 @@ class MyUser(AbstractBaseUser): 'email': user.email, } + @pytest.mark.skipif(not DJANGO_110, reason='< Django 1.10') + def test_get_user_info_is_authenticated_property(self): + from django.db import models + from django.contrib.auth.models import AbstractBaseUser + + class MyUser(AbstractBaseUser): + USERNAME_FIELD = 'username' + username = models.CharField(max_length=32) + email = models.EmailField() + + @property + def is_authenticated(self): + return True + + user = MyUser( + username='admin', + email='admin@example.com', + id=1, + ) + user_info = self.raven.get_user_info(user) + assert user_info == { + 'username': user.username, + 'id': user.id, + 'email': user.email, + } + def test_request_middleware_exception(self): with Settings(MIDDLEWARE_CLASSES=['tests.contrib.django.middleware.BrokenRequestMiddleware']): self.assertRaises(ImportError, self.client.get, reverse('sentry-raise-exc')) From 96437e207519037b359a419bc33a6c852d44cbee Mon Sep 17 00:00:00 2001 From: Marc Sibson Date: Fri, 26 Aug 2016 17:20:17 -0700 Subject: [PATCH 370/692] docfix signature of python captureException (#848) --- docs/api.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api.rst b/docs/api.rst index e3c7df51e..a84a2de82 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -66,7 +66,7 @@ Client client.captureMessage('This just happened!') - .. py:method:: captureException(message, exc_info=None, **kwargs) + .. py:method:: captureException(exc_info=None, **kwargs) This is a shorthand to reporting an exception via :meth:`capture`. It passes ``'raven.events.Exception'`` as `event_type` and the From 5cc06dbdded644720b62a28b48043f21f0b3af75 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 31 Aug 2016 23:17:27 +0200 Subject: [PATCH 371/692] Automatically close iterator on end of iteration --- raven/middleware.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/raven/middleware.py b/raven/middleware.py index a6b9b06c8..c9c556604 100644 --- a/raven/middleware.py +++ b/raven/middleware.py @@ -44,21 +44,33 @@ def __init__(self, sentry, iterable, environ): self.sentry = sentry self.environ = environ self.iterable = iter(iterable) + self.closed = False def __iter__(self): return self def __next__(self): - with common_exception_handling(self.environ, self.sentry): - return next(self.iterable) + try: + with common_exception_handling(self.environ, self.sentry): + return next(self.iterable) + except StopIteration: + # We auto close here if we reach the end because some WSGI + # middleware does not really like to close things. To avoid + # massive leaks we just close automatically at the end of + # iteration. + self.close() + raise def close(self): + if self.closed: + return try: if hasattr(self.iterable, 'close'): with common_exception_handling(self.environ, self.sentry): self.iterable.close() finally: self.sentry.client.context.clear() + self.closed = True class Sentry(object): From 2cebfc54efb3aaf744a5789978e3655477d19a9d Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 31 Aug 2016 23:28:59 +0200 Subject: [PATCH 372/692] This is 5.26.0 --- CHANGES | 2 ++ raven/__init__.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index e8c259582..3a4a55701 100644 --- a/CHANGES +++ b/CHANGES @@ -2,6 +2,8 @@ Version 5.26.0 -------------- * Fixed potential concurrency issue with event IDs in the Flask integration. +* Added a workaround for leakage when broken WSGI middlware or servers are + used that do not call `close()` on the iterat.r Version 5.25.0 -------------- diff --git a/raven/__init__.py b/raven/__init__.py index 55ac29043..20cb83d0b 100644 --- a/raven/__init__.py +++ b/raven/__init__.py @@ -12,7 +12,7 @@ __all__ = ('VERSION', 'Client', 'get_version') -VERSION = '5.26.0.dev0' +VERSION = '5.26.0' def _get_git_revision(path): From f468822feba6081524ca6783f9489c8ba4dc8d9c Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 31 Aug 2016 23:30:38 +0200 Subject: [PATCH 373/692] This is 5.27.0.dev0 --- raven/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/__init__.py b/raven/__init__.py index 20cb83d0b..13567e46e 100644 --- a/raven/__init__.py +++ b/raven/__init__.py @@ -12,7 +12,7 @@ __all__ = ('VERSION', 'Client', 'get_version') -VERSION = '5.26.0' +VERSION = '5.27.0.dev0' def _get_git_revision(path): From c6ed819c7b680938b9ba906489fc848c1992b3cc Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 7 Sep 2016 23:40:36 +0300 Subject: [PATCH 374/692] Honor exception ignores for logging handler --- raven/base.py | 11 +++++------ tests/contrib/django/tests.py | 18 +++++++++--------- tests/handlers/logging/tests.py | 17 +++++++++++++++++ 3 files changed, 31 insertions(+), 15 deletions(-) diff --git a/raven/base.py b/raven/base.py index a682c76de..f887af02a 100644 --- a/raven/base.py +++ b/raven/base.py @@ -599,6 +599,11 @@ def capture(self, event_type, data=None, date=None, time_spent=None, if exc_info is not None: if self.skip_error_for_logging(exc_info): return + elif not self.should_capture(exc_info): + self.logger.info( + 'Not capturing exception due to filters: %s', exc_info[0], + exc_info=sys.exc_info()) + return self.record_exception_seen(exc_info) data = self.build_msg( @@ -775,12 +780,6 @@ def captureException(self, exc_info=None, **kwargs): if exc_info is None or exc_info is True: exc_info = sys.exc_info() - if not self.should_capture(exc_info): - self.logger.info( - 'Not capturing exception due to filters: %s', exc_info[0], - exc_info=sys.exc_info()) - return - return self.capture( 'raven.events.Exception', exc_info=exc_info, **kwargs) diff --git a/tests/contrib/django/tests.py b/tests/contrib/django/tests.py index 3aa6b746d..93b1dda23 100644 --- a/tests/contrib/django/tests.py +++ b/tests/contrib/django/tests.py @@ -767,9 +767,9 @@ def test_does_capture_exception(self, exc_info, captureException): captureException.assert_called_once_with(exc_info=self.exc_info, request=self.request) - @mock.patch.object(TempStoreClient, 'capture') + @mock.patch.object(TempStoreClient, 'send') @mock.patch('sys.exc_info') - def test_does_exclude_filtered_types(self, exc_info, mock_capture): + def test_does_exclude_filtered_types(self, exc_info, mock_send): exc_info.return_value = self.exc_info try: get_client().ignore_exceptions = set(['ValueError']) @@ -778,11 +778,11 @@ def test_does_exclude_filtered_types(self, exc_info, mock_capture): finally: get_client().ignore_exceptions.clear() - assert not mock_capture.called + assert not mock_send.called - @mock.patch.object(TempStoreClient, 'capture') + @mock.patch.object(TempStoreClient, 'send') @mock.patch('sys.exc_info') - def test_ignore_exceptions_with_expression_match(self, exc_info, mock_capture): + def test_ignore_exceptions_with_expression_match(self, exc_info, mock_send): exc_info.return_value = self.exc_info try: @@ -794,11 +794,11 @@ def test_ignore_exceptions_with_expression_match(self, exc_info, mock_capture): finally: get_client().ignore_exceptions.clear() - assert not mock_capture.called + assert not mock_send.called - @mock.patch.object(TempStoreClient, 'capture') + @mock.patch.object(TempStoreClient, 'send') @mock.patch('sys.exc_info') - def test_ignore_exceptions_with_module_match(self, exc_info, mock_capture): + def test_ignore_exceptions_with_module_match(self, exc_info, mock_send): exc_info.return_value = self.exc_info try: @@ -810,4 +810,4 @@ def test_ignore_exceptions_with_module_match(self, exc_info, mock_capture): finally: get_client().ignore_exceptions.clear() - assert not mock_capture.called + assert not mock_send.called diff --git a/tests/handlers/logging/tests.py b/tests/handlers/logging/tests.py index 76b24e2ff..01feb0920 100644 --- a/tests/handlers/logging/tests.py +++ b/tests/handlers/logging/tests.py @@ -50,6 +50,23 @@ def test_logger_basic(self): self.assertEqual(msg['message'], 'This is a test error') self.assertEqual(msg['params'], ()) + def test_logger_ignore_exception(self): + class Foo(Exception): + pass + old = self.client.ignore_exceptions + self.client.ignore_exceptions = set(['Foo']) + try: + try: + raise Foo() + except Exception: + exc_info = sys.exc_info() + record = self.make_record('This is a test error', + exc_info=exc_info) + self.handler.emit(record) + self.assertEqual(len(self.client.events), 0) + finally: + self.client.ignore_exceptions = old + def test_can_record(self): tests = [ ("raven", False), From 2eaecc567f039fffdbbb1282d7945619f1aaac20 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 8 Sep 2016 01:00:00 +0300 Subject: [PATCH 375/692] Add update-ca makefile command --- Makefile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 6639f668b..f7ab43140 100644 --- a/Makefile +++ b/Makefile @@ -24,4 +24,7 @@ publish: rm -rf dist build python setup.py sdist bdist_wheel upload -.PHONY: bootstrap test lint coverage setup-git publish +update-ca: + curl -sSL https://mkcert.org/generate/ -o raven/data/cacert.pem + +.PHONY: bootstrap test lint coverage setup-git publish update-ca From 6913e97b4c350f12a400b5acda030e84b5b1c572 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 8 Sep 2016 01:04:09 +0300 Subject: [PATCH 376/692] Update CA bundle --- CHANGES | 5 + raven/data/cacert.pem | 527 +++++++++++++++++++++++++++--------------- 2 files changed, 346 insertions(+), 186 deletions(-) diff --git a/CHANGES b/CHANGES index 3a4a55701..1ff594f53 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 5.27.0 +-------------- + +* Updated CA bundle. + Version 5.26.0 -------------- diff --git a/raven/data/cacert.pem b/raven/data/cacert.pem index 672fb1feb..8f81e8ded 100644 --- a/raven/data/cacert.pem +++ b/raven/data/cacert.pem @@ -821,36 +821,6 @@ Tk6ezAyNlNzZRZxe7EJQY670XcSxEtzKO6gunRRaBXW37Ndj4ro1tgQIkejanZz2 ZrUYrAqmVCY0M9IbwdR/GjqOC6oybtv8TyWf2TLHllpwrN9M -----END CERTIFICATE----- -# Issuer: CN=Staat der Nederlanden Root CA O=Staat der Nederlanden -# Subject: CN=Staat der Nederlanden Root CA O=Staat der Nederlanden -# Label: "Staat der Nederlanden Root CA" -# Serial: 10000010 -# MD5 Fingerprint: 60:84:7c:5a:ce:db:0c:d4:cb:a7:e9:fe:02:c6:a9:c0 -# SHA1 Fingerprint: 10:1d:fa:3f:d5:0b:cb:bb:9b:b5:60:0c:19:55:a4:1a:f4:73:3a:04 -# SHA256 Fingerprint: d4:1d:82:9e:8c:16:59:82:2a:f9:3f:ce:62:bf:fc:de:26:4f:c8:4e:8b:95:0c:5f:f2:75:d0:52:35:46:95:a3 ------BEGIN CERTIFICATE----- -MIIDujCCAqKgAwIBAgIEAJiWijANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQGEwJO -TDEeMBwGA1UEChMVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSYwJAYDVQQDEx1TdGFh -dCBkZXIgTmVkZXJsYW5kZW4gUm9vdCBDQTAeFw0wMjEyMTcwOTIzNDlaFw0xNTEy -MTYwOTE1MzhaMFUxCzAJBgNVBAYTAk5MMR4wHAYDVQQKExVTdGFhdCBkZXIgTmVk -ZXJsYW5kZW4xJjAkBgNVBAMTHVN0YWF0IGRlciBOZWRlcmxhbmRlbiBSb290IENB -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmNK1URF6gaYUmHFtvszn -ExvWJw56s2oYHLZhWtVhCb/ekBPHZ+7d89rFDBKeNVU+LCeIQGv33N0iYfXCxw71 -9tV2U02PjLwYdjeFnejKScfST5gTCaI+Ioicf9byEGW07l8Y1Rfj+MX94p2i71MO -hXeiD+EwR+4A5zN9RGcaC1Hoi6CeUJhoNFIfLm0B8mBF8jHrqTFoKbt6QZ7GGX+U -tFE5A3+y3qcym7RHjm+0Sq7lr7HcsBthvJly3uSJt3omXdozSVtSnA71iq3DuD3o -BmrC1SoLbHuEvVYFy4ZlkuxEK7COudxwC0barbxjiDn622r+I/q85Ej0ZytqERAh -SQIDAQABo4GRMIGOMAwGA1UdEwQFMAMBAf8wTwYDVR0gBEgwRjBEBgRVHSAAMDww -OgYIKwYBBQUHAgEWLmh0dHA6Ly93d3cucGtpb3ZlcmhlaWQubmwvcG9saWNpZXMv -cm9vdC1wb2xpY3kwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSofeu8Y6R0E3QA -7Jbg0zTBLL9s+DANBgkqhkiG9w0BAQUFAAOCAQEABYSHVXQ2YcG70dTGFagTtJ+k -/rvuFbQvBgwp8qiSpGEN/KtcCFtREytNwiphyPgJWPwtArI5fZlmgb9uXJVFIGzm -eafR2Bwp/MIgJ1HI8XxdNGdphREwxgDS1/PTfLbwMVcoEoJz6TMvplW0C5GUR5z6 -u3pCMuiufi3IvKwUv9kP2Vv8wfl6leF9fpb8cbDCTMjfRTTJzg3ynGQI0DvDKcWy -7ZAEwbEpkcUwb8GpcjPM/l0WFywRaed+/sWDCN+83CI6LiBpIzlWYGeQiy52OfsR -iJf2fL1LuCAWZwWN4jvBcj+UlTfHXbme2JOhF4//DGYVwSR8MnwDHTuhWEUykw== ------END CERTIFICATE----- - # Issuer: CN=UTN-USERFirst-Hardware O=The USERTRUST Network OU=http://www.usertrust.com # Subject: CN=UTN-USERFirst-Hardware O=The USERTRUST Network OU=http://www.usertrust.com # Label: "UTN USERFirst Hardware Root CA" @@ -957,51 +927,6 @@ ecQwn+uOuFW114hcxWokPbLTBQNRxgfvzBRydD1ucs4YKIxKoHflCStFREest2d/ AYoFWpO+ocH/+OcOZ6RHSXZddZAa9SaP8A== -----END CERTIFICATE----- -# Issuer: CN=NetLock Kozjegyzoi (Class A) Tanusitvanykiado O=NetLock Halozatbiztonsagi Kft. OU=Tanusitvanykiadok -# Subject: CN=NetLock Kozjegyzoi (Class A) Tanusitvanykiado O=NetLock Halozatbiztonsagi Kft. OU=Tanusitvanykiadok -# Label: "NetLock Notary (Class A) Root" -# Serial: 259 -# MD5 Fingerprint: 86:38:6d:5e:49:63:6c:85:5c:db:6d:dc:94:b7:d0:f7 -# SHA1 Fingerprint: ac:ed:5f:65:53:fd:25:ce:01:5f:1f:7a:48:3b:6a:74:9f:61:78:c6 -# SHA256 Fingerprint: 7f:12:cd:5f:7e:5e:29:0e:c7:d8:51:79:d5:b7:2c:20:a5:be:75:08:ff:db:5b:f8:1a:b9:68:4a:7f:c9:f6:67 ------BEGIN CERTIFICATE----- -MIIGfTCCBWWgAwIBAgICAQMwDQYJKoZIhvcNAQEEBQAwga8xCzAJBgNVBAYTAkhV -MRAwDgYDVQQIEwdIdW5nYXJ5MREwDwYDVQQHEwhCdWRhcGVzdDEnMCUGA1UEChMe -TmV0TG9jayBIYWxvemF0Yml6dG9uc2FnaSBLZnQuMRowGAYDVQQLExFUYW51c2l0 -dmFueWtpYWRvazE2MDQGA1UEAxMtTmV0TG9jayBLb3pqZWd5em9pIChDbGFzcyBB -KSBUYW51c2l0dmFueWtpYWRvMB4XDTk5MDIyNDIzMTQ0N1oXDTE5MDIxOTIzMTQ0 -N1owga8xCzAJBgNVBAYTAkhVMRAwDgYDVQQIEwdIdW5nYXJ5MREwDwYDVQQHEwhC -dWRhcGVzdDEnMCUGA1UEChMeTmV0TG9jayBIYWxvemF0Yml6dG9uc2FnaSBLZnQu -MRowGAYDVQQLExFUYW51c2l0dmFueWtpYWRvazE2MDQGA1UEAxMtTmV0TG9jayBL -b3pqZWd5em9pIChDbGFzcyBBKSBUYW51c2l0dmFueWtpYWRvMIIBIjANBgkqhkiG -9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvHSMD7tM9DceqQWC2ObhbHDqeLVu0ThEDaiD -zl3S1tWBxdRL51uUcCbbO51qTGL3cfNk1mE7PetzozfZz+qMkjvN9wfcZnSX9EUi -3fRc4L9t875lM+QVOr/bmJBVOMTtplVjC7B4BPTjbsE/jvxReB+SnoPC/tmwqcm8 -WgD/qaiYdPv2LD4VOQ22BFWoDpggQrOxJa1+mm9dU7GrDPzr4PN6s6iz/0b2Y6LY -Oph7tqyF/7AlT3Rj5xMHpQqPBffAZG9+pyeAlt7ULoZgx2srXnN7F+eRP2QM2Esi -NCubMvJIH5+hCoR64sKtlz2O1cH5VqNQ6ca0+pii7pXmKgOM3wIDAQABo4ICnzCC -ApswDgYDVR0PAQH/BAQDAgAGMBIGA1UdEwEB/wQIMAYBAf8CAQQwEQYJYIZIAYb4 -QgEBBAQDAgAHMIICYAYJYIZIAYb4QgENBIICURaCAk1GSUdZRUxFTSEgRXplbiB0 -YW51c2l0dmFueSBhIE5ldExvY2sgS2Z0LiBBbHRhbGFub3MgU3pvbGdhbHRhdGFz -aSBGZWx0ZXRlbGVpYmVuIGxlaXJ0IGVsamFyYXNvayBhbGFwamFuIGtlc3p1bHQu -IEEgaGl0ZWxlc2l0ZXMgZm9seWFtYXRhdCBhIE5ldExvY2sgS2Z0LiB0ZXJtZWtm -ZWxlbG9zc2VnLWJpenRvc2l0YXNhIHZlZGkuIEEgZGlnaXRhbGlzIGFsYWlyYXMg -ZWxmb2dhZGFzYW5hayBmZWx0ZXRlbGUgYXogZWxvaXJ0IGVsbGVub3J6ZXNpIGVs -amFyYXMgbWVndGV0ZWxlLiBBeiBlbGphcmFzIGxlaXJhc2EgbWVndGFsYWxoYXRv -IGEgTmV0TG9jayBLZnQuIEludGVybmV0IGhvbmxhcGphbiBhIGh0dHBzOi8vd3d3 -Lm5ldGxvY2submV0L2RvY3MgY2ltZW4gdmFneSBrZXJoZXRvIGF6IGVsbGVub3J6 -ZXNAbmV0bG9jay5uZXQgZS1tYWlsIGNpbWVuLiBJTVBPUlRBTlQhIFRoZSBpc3N1 -YW5jZSBhbmQgdGhlIHVzZSBvZiB0aGlzIGNlcnRpZmljYXRlIGlzIHN1YmplY3Qg -dG8gdGhlIE5ldExvY2sgQ1BTIGF2YWlsYWJsZSBhdCBodHRwczovL3d3dy5uZXRs -b2NrLm5ldC9kb2NzIG9yIGJ5IGUtbWFpbCBhdCBjcHNAbmV0bG9jay5uZXQuMA0G -CSqGSIb3DQEBBAUAA4IBAQBIJEb3ulZv+sgoA0BO5TE5ayZrU3/b39/zcT0mwBQO -xmd7I6gMc90Bu8bKbjc5VdXHjFYgDigKDtIqpLBJUsY4B/6+CgmM0ZjPytoUMaFP -0jn8DxEsQ8Pdq5PHVT5HfBgaANzze9jyf1JsIPQLX2lS9O74silg6+NJMSEN1rUQ -QeJBCWziGppWS3cC9qCbmieH6FUpccKQn0V4GuEVZD3QDtigdp+uxdAu6tYPVuxk -f1qbFFgBJ34TUMdrKuZoPL9coAob4Q566eKAw+np9v1sEZ7Q5SgnK1QyQhSCdeZK -8CtmdWOMovsEPoMOmzbwGOQmIMOM8CgHrTwXZoi1/baI ------END CERTIFICATE----- - # Issuer: CN=XRamp Global Certification Authority O=XRamp Security Services Inc OU=www.xrampsecurity.com # Subject: CN=XRamp Global Certification Authority O=XRamp Security Services Inc OU=www.xrampsecurity.com # Label: "XRamp Global CA Root" @@ -2119,48 +2044,6 @@ h7U/2k3ZIQAw3pDaDtMaSKk+hQsUi4y8QZ5q9w5wwDX3OaJdZtB7WZ+oRxKaJyOk LY4ng5IgodcVf/EuGO70SH8vf/GhGLWhC5SgYiAynB321O+/TIho -----END CERTIFICATE----- -# Issuer: CN=EBG Elektronik Sertifika Hizmet Sağlayıcısı O=EBG Bilişim Teknolojileri ve Hizmetleri A.Ş. -# Subject: CN=EBG Elektronik Sertifika Hizmet Sağlayıcısı O=EBG Bilişim Teknolojileri ve Hizmetleri A.Ş. -# Label: "EBG Elektronik Sertifika Hizmet Sa\xC4\x9Flay\xc4\xb1\x63\xc4\xb1s\xc4\xb1" -# Serial: 5525761995591021570 -# MD5 Fingerprint: 2c:20:26:9d:cb:1a:4a:00:85:b5:b7:5a:ae:c2:01:37 -# SHA1 Fingerprint: 8c:96:ba:eb:dd:2b:07:07:48:ee:30:32:66:a0:f3:98:6e:7c:ae:58 -# SHA256 Fingerprint: 35:ae:5b:dd:d8:f7:ae:63:5c:ff:ba:56:82:a8:f0:0b:95:f4:84:62:c7:10:8e:e9:a0:e5:29:2b:07:4a:af:b2 ------BEGIN CERTIFICATE----- -MIIF5zCCA8+gAwIBAgIITK9zQhyOdAIwDQYJKoZIhvcNAQEFBQAwgYAxODA2BgNV -BAMML0VCRyBFbGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sx -c8SxMTcwNQYDVQQKDC5FQkcgQmlsacWfaW0gVGVrbm9sb2ppbGVyaSB2ZSBIaXpt -ZXRsZXJpIEEuxZ4uMQswCQYDVQQGEwJUUjAeFw0wNjA4MTcwMDIxMDlaFw0xNjA4 -MTQwMDMxMDlaMIGAMTgwNgYDVQQDDC9FQkcgRWxla3Ryb25payBTZXJ0aWZpa2Eg -SGl6bWV0IFNhxJ9sYXnEsWPEsXPEsTE3MDUGA1UECgwuRUJHIEJpbGnFn2ltIFRl -a25vbG9qaWxlcmkgdmUgSGl6bWV0bGVyaSBBLsWeLjELMAkGA1UEBhMCVFIwggIi -MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDuoIRh0DpqZhAy2DE4f6en5f2h -4fuXd7hxlugTlkaDT7byX3JWbhNgpQGR4lvFzVcfd2NR/y8927k/qqk153nQ9dAk -tiHq6yOU/im/+4mRDGSaBUorzAzu8T2bgmmkTPiab+ci2hC6X5L8GCcKqKpE+i4s -tPtGmggDg3KriORqcsnlZR9uKg+ds+g75AxuetpX/dfreYteIAbTdgtsApWjluTL -dlHRKJ2hGvxEok3MenaoDT2/F08iiFD9rrbskFBKW5+VQarKD7JK/oCZTqNGFav4 -c0JqwmZ2sQomFd2TkuzbqV9UIlKRcF0T6kjsbgNs2d1s/OsNA/+mgxKb8amTD8Um -TDGyY5lhcucqZJnSuOl14nypqZoaqsNW2xCaPINStnuWt6yHd6i58mcLlEOzrz5z -+kI2sSXFCjEmN1ZnuqMLfdb3ic1nobc6HmZP9qBVFCVMLDMNpkGMvQQxahByCp0O -Lna9XvNRiYuoP1Vzv9s6xiQFlpJIqkuNKgPlV5EQ9GooFW5Hd4RcUXSfGenmHmMW -OeMRFeNYGkS9y8RsZteEBt8w9DeiQyJ50hBs37vmExH8nYQKE3vwO9D8owrXieqW -fo1IhR5kX9tUoqzVegJ5a9KK8GfaZXINFHDk6Y54jzJ0fFfy1tb0Nokb+Clsi7n2 -l9GkLqq+CxnCRelwXQIDAJ3Zo2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB -/wQEAwIBBjAdBgNVHQ4EFgQU587GT/wWZ5b6SqMHwQSny2re2kcwHwYDVR0jBBgw -FoAU587GT/wWZ5b6SqMHwQSny2re2kcwDQYJKoZIhvcNAQEFBQADggIBAJuYml2+ -8ygjdsZs93/mQJ7ANtyVDR2tFcU22NU57/IeIl6zgrRdu0waypIN30ckHrMk2pGI -6YNw3ZPX6bqz3xZaPt7gyPvT/Wwp+BVGoGgmzJNSroIBk5DKd8pNSe/iWtkqvTDO -TLKBtjDOWU/aWR1qeqRFsIImgYZ29fUQALjuswnoT4cCB64kXPBfrAowzIpAoHME -wfuJJPaaHFy3PApnNgUIMbOv2AFoKuB4j3TeuFGkjGwgPaL7s9QJ/XvCgKqTbCmY -Iai7FvOpEl90tYeY8pUm3zTvilORiF0alKM/fCL414i6poyWqD1SNGKfAB5UVUJn -xk1Gj7sURT0KlhaOEKGXmdXTMIXM3rRyt7yKPBgpaP3ccQfuJDlq+u2lrDgv+R4Q -DgZxGhBM/nV+/x5XOULK1+EVoVZVWRvRo68R2E7DpSvvkL/A7IITW43WciyTTo9q -Kd+FPNMN4KIYEsxVL0e3p5sC/kH2iExt2qkBR4NkJ2IQgtYSe14DHzSpyZH+r11t -hie3I6p1GMog57AP14kOpmciY/SDQSsGS7tY1dHXt7kQY9iJSrSq3RZj9W6+YKH4 -7ejWkE8axsWgKdOnIaj1Wjz3x0miIZpKlVIglnKaZsv30oZDfCK+lvm9AahH3eU7 -QPl1K5srRmSGjR70j/sHd9DqSaIcjVIUpgqT ------END CERTIFICATE----- - # Issuer: O=certSIGN OU=certSIGN ROOT CA # Subject: O=certSIGN OU=certSIGN ROOT CA # Label: "certSIGN ROOT CA" @@ -2499,75 +2382,6 @@ Y7BXN0Ute4qcvwXqZVUz9zkQxSgqIXobisQk+T8VyJoVIPVVYpbtbZNQvOSqeK3Z ywplh6ZmwcSBo3c6WB4L7oOLnR7SUqTMHW+wmG2UMbX4cQrcufx9MmDm66+KAQ== -----END CERTIFICATE----- -# Issuer: CN=CA Disig O=Disig a.s. -# Subject: CN=CA Disig O=Disig a.s. -# Label: "CA Disig" -# Serial: 1 -# MD5 Fingerprint: 3f:45:96:39:e2:50:87:f7:bb:fe:98:0c:3c:20:98:e6 -# SHA1 Fingerprint: 2a:c8:d5:8b:57:ce:bf:2f:49:af:f2:fc:76:8f:51:14:62:90:7a:41 -# SHA256 Fingerprint: 92:bf:51:19:ab:ec:ca:d0:b1:33:2d:c4:e1:d0:5f:ba:75:b5:67:90:44:ee:0c:a2:6e:93:1f:74:4f:2f:33:cf ------BEGIN CERTIFICATE----- -MIIEDzCCAvegAwIBAgIBATANBgkqhkiG9w0BAQUFADBKMQswCQYDVQQGEwJTSzET -MBEGA1UEBxMKQnJhdGlzbGF2YTETMBEGA1UEChMKRGlzaWcgYS5zLjERMA8GA1UE -AxMIQ0EgRGlzaWcwHhcNMDYwMzIyMDEzOTM0WhcNMTYwMzIyMDEzOTM0WjBKMQsw -CQYDVQQGEwJTSzETMBEGA1UEBxMKQnJhdGlzbGF2YTETMBEGA1UEChMKRGlzaWcg -YS5zLjERMA8GA1UEAxMIQ0EgRGlzaWcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw -ggEKAoIBAQCS9jHBfYj9mQGp2HvycXXxMcbzdWb6UShGhJd4NLxs/LxFWYgmGErE -Nx+hSkS943EE9UQX4j/8SFhvXJ56CbpRNyIjZkMhsDxkovhqFQ4/61HhVKndBpnX -mjxUizkDPw/Fzsbrg3ICqB9x8y34dQjbYkzo+s7552oftms1grrijxaSfQUMbEYD -XcDtab86wYqg6I7ZuUUohwjstMoVvoLdtUSLLa2GDGhibYVW8qwUYzrG0ZmsNHhW -S8+2rT+MitcE5eN4TPWGqvWP+j1scaMtymfraHtuM6kMgiioTGohQBUgDCZbg8Kp -FhXAJIJdKxatymP2dACw30PEEGBWZ2NFAgMBAAGjgf8wgfwwDwYDVR0TAQH/BAUw -AwEB/zAdBgNVHQ4EFgQUjbJJaJ1yCCW5wCf1UJNWSEZx+Y8wDgYDVR0PAQH/BAQD -AgEGMDYGA1UdEQQvMC2BE2Nhb3BlcmF0b3JAZGlzaWcuc2uGFmh0dHA6Ly93d3cu -ZGlzaWcuc2svY2EwZgYDVR0fBF8wXTAtoCugKYYnaHR0cDovL3d3dy5kaXNpZy5z -ay9jYS9jcmwvY2FfZGlzaWcuY3JsMCygKqAohiZodHRwOi8vY2EuZGlzaWcuc2sv -Y2EvY3JsL2NhX2Rpc2lnLmNybDAaBgNVHSAEEzARMA8GDSuBHpGT5goAAAABAQEw -DQYJKoZIhvcNAQEFBQADggEBAF00dGFMrzvY/59tWDYcPQuBDRIrRhCA/ec8J9B6 -yKm2fnQwM6M6int0wHl5QpNt/7EpFIKrIYwvF/k/Ji/1WcbvgAa3mkkp7M5+cTxq -EEHA9tOasnxakZzArFvITV734VP/Q3f8nktnbNfzg9Gg4H8l37iYC5oyOGwwoPP/ -CBUz91BKez6jPiCp3C9WgArtQVCwyfTssuMmRAAOb54GvCKWU3BlxFAKRmukLyeB -EicTXxChds6KezfqwzlhA5WYOudsiCUI/HloDYd9Yvi0X/vF2Ey9WLw/Q1vUHgFN -PGO+I++MzVpQuGhU+QqZMxEA4Z7CRneC9VkGjCFMhwnN5ag= ------END CERTIFICATE----- - -# Issuer: CN=Juur-SK O=AS Sertifitseerimiskeskus -# Subject: CN=Juur-SK O=AS Sertifitseerimiskeskus -# Label: "Juur-SK" -# Serial: 999181308 -# MD5 Fingerprint: aa:8e:5d:d9:f8:db:0a:58:b7:8d:26:87:6c:82:35:55 -# SHA1 Fingerprint: 40:9d:4b:d9:17:b5:5c:27:b6:9b:64:cb:98:22:44:0d:cd:09:b8:89 -# SHA256 Fingerprint: ec:c3:e9:c3:40:75:03:be:e0:91:aa:95:2f:41:34:8f:f8:8b:aa:86:3b:22:64:be:fa:c8:07:90:15:74:e9:39 ------BEGIN CERTIFICATE----- -MIIE5jCCA86gAwIBAgIEO45L/DANBgkqhkiG9w0BAQUFADBdMRgwFgYJKoZIhvcN -AQkBFglwa2lAc2suZWUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKExlBUyBTZXJ0aWZp -dHNlZXJpbWlza2Vza3VzMRAwDgYDVQQDEwdKdXVyLVNLMB4XDTAxMDgzMDE0MjMw -MVoXDTE2MDgyNjE0MjMwMVowXTEYMBYGCSqGSIb3DQEJARYJcGtpQHNrLmVlMQsw -CQYDVQQGEwJFRTEiMCAGA1UEChMZQVMgU2VydGlmaXRzZWVyaW1pc2tlc2t1czEQ -MA4GA1UEAxMHSnV1ci1TSzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB -AIFxNj4zB9bjMI0TfncyRsvPGbJgMUaXhvSYRqTCZUXP00B841oiqBB4M8yIsdOB -SvZiF3tfTQou0M+LI+5PAk676w7KvRhj6IAcjeEcjT3g/1tf6mTll+g/mX8MCgkz -ABpTpyHhOEvWgxutr2TC+Rx6jGZITWYfGAriPrsfB2WThbkasLnE+w0R9vXW+RvH -LCu3GFH+4Hv2qEivbDtPL+/40UceJlfwUR0zlv/vWT3aTdEVNMfqPxZIe5EcgEMP -PbgFPtGzlc3Yyg/CQ2fbt5PgIoIuvvVoKIO5wTtpeyDaTpxt4brNj3pssAki14sL -2xzVWiZbDcDq5WDQn/413z8CAwEAAaOCAawwggGoMA8GA1UdEwEB/wQFMAMBAf8w -ggEWBgNVHSAEggENMIIBCTCCAQUGCisGAQQBzh8BAQEwgfYwgdAGCCsGAQUFBwIC -MIHDHoHAAFMAZQBlACAAcwBlAHIAdABpAGYAaQBrAGEAYQB0ACAAbwBuACAAdgDk -AGwAagBhAHMAdABhAHQAdQBkACAAQQBTAC0AaQBzACAAUwBlAHIAdABpAGYAaQB0 -AHMAZQBlAHIAaQBtAGkAcwBrAGUAcwBrAHUAcwAgAGEAbABhAG0ALQBTAEsAIABz -AGUAcgB0AGkAZgBpAGsAYQBhAHQAaQBkAGUAIABrAGkAbgBuAGkAdABhAG0AaQBz -AGUAawBzMCEGCCsGAQUFBwIBFhVodHRwOi8vd3d3LnNrLmVlL2Nwcy8wKwYDVR0f -BCQwIjAgoB6gHIYaaHR0cDovL3d3dy5zay5lZS9qdXVyL2NybC8wHQYDVR0OBBYE -FASqekej5ImvGs8KQKcYP2/v6X2+MB8GA1UdIwQYMBaAFASqekej5ImvGs8KQKcY -P2/v6X2+MA4GA1UdDwEB/wQEAwIB5jANBgkqhkiG9w0BAQUFAAOCAQEAe8EYlFOi -CfP+JmeaUOTDBS8rNXiRTHyoERF5TElZrMj3hWVcRrs7EKACr81Ptcw2Kuxd/u+g -kcm2k298gFTsxwhwDY77guwqYHhpNjbRxZyLabVAyJRld/JXIWY7zoVAtjNjGr95 -HvxcHdMdkxuLDF2FvZkwMhgJkVLpfKG6/2SSmuz+Ne6ML678IIbsSt4beDI3poHS -na9aEhbKmVv8b20OxaAehsmR0FyYgl9jDIpaq9iVpszLita/ZEuOyoqysOkhMp6q -qIWYNIE5ITuoOlIyPfZrN4YGWhWY3PARZv40ILcD9EEQfTmEeZZyY7aWAuVrua0Z -TbvGRNs2yyqcjg== ------END CERTIFICATE----- - # Issuer: CN=Hongkong Post Root CA 1 O=Hongkong Post # Subject: CN=Hongkong Post Root CA 1 O=Hongkong Post # Label: "Hongkong Post Root CA 1" @@ -5150,3 +4964,344 @@ FgQUqv3VWqP2h4syhf3RMluARZPzA7gwCgYIKoZIzj0EAwMDaAAwZQIxAOSkhLCB 1T2wdKyUpOgOPQB0TKGXa/kNUTyh2Tv0Daupn75OcsqF1NnstTJFGG+rrQIwfcf3 aWMvoeGY7xMQ0Xk/0f7qO3/eVvSQsRUR2LIiFdAvwyYua/GRspBl9JrmkO5K -----END CERTIFICATE----- + +# Issuer: CN=SZAFIR ROOT CA2 O=Krajowa Izba Rozliczeniowa S.A. +# Subject: CN=SZAFIR ROOT CA2 O=Krajowa Izba Rozliczeniowa S.A. +# Label: "SZAFIR ROOT CA2" +# Serial: 357043034767186914217277344587386743377558296292 +# MD5 Fingerprint: 11:64:c1:89:b0:24:b1:8c:b1:07:7e:89:9e:51:9e:99 +# SHA1 Fingerprint: e2:52:fa:95:3f:ed:db:24:60:bd:6e:28:f3:9c:cc:cf:5e:b3:3f:de +# SHA256 Fingerprint: a1:33:9d:33:28:1a:0b:56:e5:57:d3:d3:2b:1c:e7:f9:36:7e:b0:94:bd:5f:a7:2a:7e:50:04:c8:de:d7:ca:fe +-----BEGIN CERTIFICATE----- +MIIDcjCCAlqgAwIBAgIUPopdB+xV0jLVt+O2XwHrLdzk1uQwDQYJKoZIhvcNAQEL +BQAwUTELMAkGA1UEBhMCUEwxKDAmBgNVBAoMH0tyYWpvd2EgSXpiYSBSb3psaWN6 +ZW5pb3dhIFMuQS4xGDAWBgNVBAMMD1NaQUZJUiBST09UIENBMjAeFw0xNTEwMTkw +NzQzMzBaFw0zNTEwMTkwNzQzMzBaMFExCzAJBgNVBAYTAlBMMSgwJgYDVQQKDB9L +cmFqb3dhIEl6YmEgUm96bGljemVuaW93YSBTLkEuMRgwFgYDVQQDDA9TWkFGSVIg +Uk9PVCBDQTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC3vD5QqEvN +QLXOYeeWyrSh2gwisPq1e3YAd4wLz32ohswmUeQgPYUM1ljj5/QqGJ3a0a4m7utT +3PSQ1hNKDJA8w/Ta0o4NkjrcsbH/ON7Dui1fgLkCvUqdGw+0w8LBZwPd3BucPbOw +3gAeqDRHu5rr/gsUvTaE2g0gv/pby6kWIK05YO4vdbbnl5z5Pv1+TW9NL++IDWr6 +3fE9biCloBK0TXC5ztdyO4mTp4CEHCdJckm1/zuVnsHMyAHs6A6KCpbns6aH5db5 +BSsNl0BwPLqsdVqc1U2dAgrSS5tmS0YHF2Wtn2yIANwiieDhZNRnvDF5YTy7ykHN +XGoAyDw4jlivAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD +AgEGMB0GA1UdDgQWBBQuFqlKGLXLzPVvUPMjX/hd56zwyDANBgkqhkiG9w0BAQsF +AAOCAQEAtXP4A9xZWx126aMqe5Aosk3AM0+qmrHUuOQn/6mWmc5G4G18TKI4pAZw +8PRBEew/R40/cof5O/2kbytTAOD/OblqBw7rHRz2onKQy4I9EYKL0rufKq8h5mOG +nXkZ7/e7DDWQw4rtTw/1zBLZpD67oPwglV9PJi8RI4NOdQcPv5vRtB3pEAT+ymCP +oky4rc/hkA/NrgrHXXu3UNLUYfrVFdvXn4dRVOul4+vJhaAlIDf7js4MNIThPIGy +d05DpYhfhmehPea0XGG2Ptv+tyjFogeutcrKjSoS75ftwjCkySp6+/NNIxuZMzSg +LvWpCz/UXeHPhJ/iGcJfitYgHuNztw== +-----END CERTIFICATE----- + +# Issuer: CN=Certum Trusted Network CA 2 O=Unizeto Technologies S.A. OU=Certum Certification Authority +# Subject: CN=Certum Trusted Network CA 2 O=Unizeto Technologies S.A. OU=Certum Certification Authority +# Label: "Certum Trusted Network CA 2" +# Serial: 44979900017204383099463764357512596969 +# MD5 Fingerprint: 6d:46:9e:d9:25:6d:08:23:5b:5e:74:7d:1e:27:db:f2 +# SHA1 Fingerprint: d3:dd:48:3e:2b:bf:4c:05:e8:af:10:f5:fa:76:26:cf:d3:dc:30:92 +# SHA256 Fingerprint: b6:76:f2:ed:da:e8:77:5c:d3:6c:b0:f6:3c:d1:d4:60:39:61:f4:9e:62:65:ba:01:3a:2f:03:07:b6:d0:b8:04 +-----BEGIN CERTIFICATE----- +MIIF0jCCA7qgAwIBAgIQIdbQSk8lD8kyN/yqXhKN6TANBgkqhkiG9w0BAQ0FADCB +gDELMAkGA1UEBhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVzIFMu +QS4xJzAlBgNVBAsTHkNlcnR1bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIG +A1UEAxMbQ2VydHVtIFRydXN0ZWQgTmV0d29yayBDQSAyMCIYDzIwMTExMDA2MDgz +OTU2WhgPMjA0NjEwMDYwODM5NTZaMIGAMQswCQYDVQQGEwJQTDEiMCAGA1UEChMZ +VW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRp +ZmljYXRpb24gQXV0aG9yaXR5MSQwIgYDVQQDExtDZXJ0dW0gVHJ1c3RlZCBOZXR3 +b3JrIENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC9+Xj45tWA +DGSdhhuWZGc/IjoedQF97/tcZ4zJzFxrqZHmuULlIEub2pt7uZld2ZuAS9eEQCsn +0+i6MLs+CRqnSZXvK0AkwpfHp+6bJe+oCgCXhVqqndwpyeI1B+twTUrWwbNWuKFB +OJvR+zF/j+Bf4bE/D44WSWDXBo0Y+aomEKsq09DRZ40bRr5HMNUuctHFY9rnY3lE +fktjJImGLjQ/KUxSiyqnwOKRKIm5wFv5HdnnJ63/mgKXwcZQkpsCLL2puTRZCr+E +Sv/f/rOf69me4Jgj7KZrdxYq28ytOxykh9xGc14ZYmhFV+SQgkK7QtbwYeDBoz1m +o130GO6IyY0XRSmZMnUCMe4pJshrAua1YkV/NxVaI2iJ1D7eTiew8EAMvE0Xy02i +sx7QBlrd9pPPV3WZ9fqGGmd4s7+W/jTcvedSVuWz5XV710GRBdxdaeOVDUO5/IOW +OZV7bIBaTxNyxtd9KXpEulKkKtVBRgkg/iKgtlswjbyJDNXXcPiHUv3a76xRLgez +Tv7QCdpw75j6VuZt27VXS9zlLCUVyJ4ueE742pyehizKV/Ma5ciSixqClnrDvFAS +adgOWkaLOusm+iPJtrCBvkIApPjW/jAux9JG9uWOdf3yzLnQh1vMBhBgu4M1t15n +3kfsmUjxpKEV/q2MYo45VU85FrmxY53/twIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MB0GA1UdDgQWBBS2oVQ5AsOgP46KvPrU+Bym0ToO/TAOBgNVHQ8BAf8EBAMC +AQYwDQYJKoZIhvcNAQENBQADggIBAHGlDs7k6b8/ONWJWsQCYftMxRQXLYtPU2sQ +F/xlhMcQSZDe28cmk4gmb3DWAl45oPePq5a1pRNcgRRtDoGCERuKTsZPpd1iHkTf +CVn0W3cLN+mLIMb4Ck4uWBzrM9DPhmDJ2vuAL55MYIR4PSFk1vtBHxgP58l1cb29 +XN40hz5BsA72udY/CROWFC/emh1auVbONTqwX3BNXuMp8SMoclm2q8KMZiYcdywm +djWLKKdpoPk79SPdhRB0yZADVpHnr7pH1BKXESLjokmUbOe3lEu6LaTaM4tMpkT/ +WjzGHWTYtTHkpjx6qFcL2+1hGsvxznN3Y6SHb0xRONbkX8eftoEq5IVIeVheO/jb +AoJnwTnbw3RLPTYe+SmTiGhbqEQZIfCn6IENLOiTNrQ3ssqwGyZ6miUfmpqAnksq +P/ujmv5zMnHCnsZy4YpoJ/HkD7TETKVhk/iXEAcqMCWpuchxuO9ozC1+9eB+D4Ko +b7a6bINDd82Kkhehnlt4Fj1F4jNy3eFmypnTycUm/Q1oBEauttmbjL4ZvrHG8hnj +XALKLNhvSgfZyTXaQHXyxKcZb55CEJh15pWLYLztxRLXis7VmFxWlgPF7ncGNf/P +5O4/E2Hu29othfDNrp2yGAlFw5Khchf8R7agCyzxxN5DaAhqXzvwdmP7zAYspsbi +DrW5viSP +-----END CERTIFICATE----- + +# Issuer: CN=Hellenic Academic and Research Institutions RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority +# Subject: CN=Hellenic Academic and Research Institutions RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority +# Label: "Hellenic Academic and Research Institutions RootCA 2015" +# Serial: 0 +# MD5 Fingerprint: ca:ff:e2:db:03:d9:cb:4b:e9:0f:ad:84:fd:7b:18:ce +# SHA1 Fingerprint: 01:0c:06:95:a6:98:19:14:ff:bf:5f:c6:b0:b6:95:ea:29:e9:12:a6 +# SHA256 Fingerprint: a0:40:92:9a:02:ce:53:b4:ac:f4:f2:ff:c6:98:1c:e4:49:6f:75:5e:6d:45:fe:0b:2a:69:2b:cd:52:52:3f:36 +-----BEGIN CERTIFICATE----- +MIIGCzCCA/OgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBpjELMAkGA1UEBhMCR1Ix +DzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5k +IFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMT +N0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9v +dENBIDIwMTUwHhcNMTUwNzA3MTAxMTIxWhcNNDAwNjMwMTAxMTIxWjCBpjELMAkG +A1UEBhMCR1IxDzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNh +ZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkx +QDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1 +dGlvbnMgUm9vdENBIDIwMTUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC +AQDC+Kk/G4n8PDwEXT2QNrCROnk8ZlrvbTkBSRq0t89/TSNTt5AA4xMqKKYx8ZEA +4yjsriFBzh/a/X0SWwGDD7mwX5nh8hKDgE0GPt+sr+ehiGsxr/CL0BgzuNtFajT0 +AoAkKAoCFZVedioNmToUW/bLy1O8E00BiDeUJRtCvCLYjqOWXjrZMts+6PAQZe10 +4S+nfK8nNLspfZu2zwnI5dMK/IhlZXQK3HMcXM1AsRzUtoSMTFDPaI6oWa7CJ06C +ojXdFPQf/7J31Ycvqm59JCfnxssm5uX+Zwdj2EUN3TpZZTlYepKZcj2chF6IIbjV +9Cz82XBST3i4vTwri5WY9bPRaM8gFH5MXF/ni+X1NYEZN9cRCLdmvtNKzoNXADrD +gfgXy5I2XdGj2HUb4Ysn6npIQf1FGQatJ5lOwXBH3bWfgVMS5bGMSF0xQxfjjMZ6 +Y5ZLKTBOhE5iGV48zpeQpX8B653g+IuJ3SWYPZK2fu/Z8VFRfS0myGlZYeCsargq +NhEEelC9MoS+L9xy1dcdFkfkR2YgP/SWxa+OAXqlD3pk9Q0Yh9muiNX6hME6wGko +LfINaFGq46V3xqSQDqE3izEjR8EJCOtu93ib14L8hCCZSRm2Ekax+0VVFqmjZayc +Bw/qa9wfLgZy7IaIEuQt218FL+TwA9MmM+eAws1CoRc0CwIDAQABo0IwQDAPBgNV +HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUcRVnyMjJvXVd +ctA4GGqd83EkVAswDQYJKoZIhvcNAQELBQADggIBAHW7bVRLqhBYRjTyYtcWNl0I +XtVsyIe9tC5G8jH4fOpCtZMWVdyhDBKg2mF+D1hYc2Ryx+hFjtyp8iY/xnmMsVMI +M4GwVhO+5lFc2JsKT0ucVlMC6U/2DWDqTUJV6HwbISHTGzrMd/K4kPFox/la/vot +9L/J9UUbzjgQKjeKeaO04wlshYaT/4mWJ3iBj2fjRnRUjtkNaeJK9E10A/+yd+2V +Z5fkscWrv2oj6NSU4kQoYsRL4vDY4ilrGnB+JGGTe08DMiUNRSQrlrRGar9KC/ea +j8GsGsVn82800vpzY4zvFrCopEYq+OsS7HK07/grfoxSwIuEVPkvPuNVqNxmsdnh +X9izjFk0WaSrT2y7HxjbdavYy5LNlDhhDgcGH0tGEPEVvo2FXDtKK4F5D7Rpn0lQ +l033DlZdwJVqwjbDG2jJ9SrcR5q+ss7FJej6A7na+RZukYT1HCjI/CbM1xyQVqdf +bzoEvM14iQuODy+jqk+iGxI9FghAD/FGTNeqewjBCvVtJ94Cj8rDtSvK6evIIVM4 +pcw72Hc3MKJP2W/R8kCtQXoXxdZKNYm3QdV8hn9VTYNKpXMgwDqvkPGaJI7ZjnHK +e7iG2rKPmT4dEw0SEe7Uq/DpFXYC5ODfqiAeW2GFZECpkJcNrVPSWh2HagCXZWK0 +vm9qp/UsQu0yrbYhnr68 +-----END CERTIFICATE----- + +# Issuer: CN=Hellenic Academic and Research Institutions ECC RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority +# Subject: CN=Hellenic Academic and Research Institutions ECC RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority +# Label: "Hellenic Academic and Research Institutions ECC RootCA 2015" +# Serial: 0 +# MD5 Fingerprint: 81:e5:b4:17:eb:c2:f5:e1:4b:0d:41:7b:49:92:fe:ef +# SHA1 Fingerprint: 9f:f1:71:8d:92:d5:9a:f3:7d:74:97:b4:bc:6f:84:68:0b:ba:b6:66 +# SHA256 Fingerprint: 44:b5:45:aa:8a:25:e6:5a:73:ca:15:dc:27:fc:36:d2:4c:1c:b9:95:3a:06:65:39:b1:15:82:dc:48:7b:48:33 +-----BEGIN CERTIFICATE----- +MIICwzCCAkqgAwIBAgIBADAKBggqhkjOPQQDAjCBqjELMAkGA1UEBhMCR1IxDzAN +BgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl +c2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxRDBCBgNVBAMTO0hl +bGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgRUNDIFJv +b3RDQSAyMDE1MB4XDTE1MDcwNzEwMzcxMloXDTQwMDYzMDEwMzcxMlowgaoxCzAJ +BgNVBAYTAkdSMQ8wDQYDVQQHEwZBdGhlbnMxRDBCBgNVBAoTO0hlbGxlbmljIEFj +YWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ2VydC4gQXV0aG9yaXR5 +MUQwQgYDVQQDEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0 +dXRpb25zIEVDQyBSb290Q0EgMjAxNTB2MBAGByqGSM49AgEGBSuBBAAiA2IABJKg +QehLgoRc4vgxEZmGZE4JJS+dQS8KrjVPdJWyUWRrjWvmP3CV8AVER6ZyOFB2lQJa +jq4onvktTpnvLEhvTCUp6NFxW98dwXU3tNf6e3pCnGoKVlp8aQuqgAkkbH7BRqNC +MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFLQi +C4KZJAEOnLvkDv2/+5cgk5kqMAoGCCqGSM49BAMCA2cAMGQCMGfOFmI4oqxiRaep +lSTAGiecMjvAwNW6qef4BENThe5SId6d9SWDPp5YSy/XZxMOIQIwBeF1Ad5o7Sof +TUwJCA3sS61kFyjndc5FZXIhF8siQQ6ME5g4mlRtm8rifOoCWCKR +-----END CERTIFICATE----- + +# Issuer: CN=Certplus Root CA G1 O=Certplus +# Subject: CN=Certplus Root CA G1 O=Certplus +# Label: "Certplus Root CA G1" +# Serial: 1491911565779898356709731176965615564637713 +# MD5 Fingerprint: 7f:09:9c:f7:d9:b9:5c:69:69:56:d5:37:3e:14:0d:42 +# SHA1 Fingerprint: 22:fd:d0:b7:fd:a2:4e:0d:ac:49:2c:a0:ac:a6:7b:6a:1f:e3:f7:66 +# SHA256 Fingerprint: 15:2a:40:2b:fc:df:2c:d5:48:05:4d:22:75:b3:9c:7f:ca:3e:c0:97:80:78:b0:f0:ea:76:e5:61:a6:c7:43:3e +-----BEGIN CERTIFICATE----- +MIIFazCCA1OgAwIBAgISESBVg+QtPlRWhS2DN7cs3EYRMA0GCSqGSIb3DQEBDQUA +MD4xCzAJBgNVBAYTAkZSMREwDwYDVQQKDAhDZXJ0cGx1czEcMBoGA1UEAwwTQ2Vy +dHBsdXMgUm9vdCBDQSBHMTAeFw0xNDA1MjYwMDAwMDBaFw0zODAxMTUwMDAwMDBa +MD4xCzAJBgNVBAYTAkZSMREwDwYDVQQKDAhDZXJ0cGx1czEcMBoGA1UEAwwTQ2Vy +dHBsdXMgUm9vdCBDQSBHMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB +ANpQh7bauKk+nWT6VjOaVj0W5QOVsjQcmm1iBdTYj+eJZJ+622SLZOZ5KmHNr49a +iZFluVj8tANfkT8tEBXgfs+8/H9DZ6itXjYj2JizTfNDnjl8KvzsiNWI7nC9hRYt +6kuJPKNxQv4c/dMcLRC4hlTqQ7jbxofaqK6AJc96Jh2qkbBIb6613p7Y1/oA/caP +0FG7Yn2ksYyy/yARujVjBYZHYEMzkPZHogNPlk2dT8Hq6pyi/jQu3rfKG3akt62f +6ajUeD94/vI4CTYd0hYCyOwqaK/1jpTvLRN6HkJKHRUxrgwEV/xhc/MxVoYxgKDE +EW4wduOU8F8ExKyHcomYxZ3MVwia9Az8fXoFOvpHgDm2z4QTd28n6v+WZxcIbekN +1iNQMLAVdBM+5S//Ds3EC0pd8NgAM0lm66EYfFkuPSi5YXHLtaW6uOrc4nBvCGrc +h2c0798wct3zyT8j/zXhviEpIDCB5BmlIOklynMxdCm+4kLV87ImZsdo/Rmz5yCT +mehd4F6H50boJZwKKSTUzViGUkAksnsPmBIgJPaQbEfIDbsYIC7Z/fyL8inqh3SV +4EJQeIQEQWGw9CEjjy3LKCHyamz0GqbFFLQ3ZU+V/YDI+HLlJWvEYLF7bY5KinPO +WftwenMGE9nTdDckQQoRb5fc5+R+ob0V8rqHDz1oihYHAgMBAAGjYzBhMA4GA1Ud +DwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSowcCbkahDFXxd +Bie0KlHYlwuBsTAfBgNVHSMEGDAWgBSowcCbkahDFXxdBie0KlHYlwuBsTANBgkq +hkiG9w0BAQ0FAAOCAgEAnFZvAX7RvUz1isbwJh/k4DgYzDLDKTudQSk0YcbX8ACh +66Ryj5QXvBMsdbRX7gp8CXrc1cqh0DQT+Hern+X+2B50ioUHj3/MeXrKls3N/U/7 +/SMNkPX0XtPGYX2eEeAC7gkE2Qfdpoq3DIMku4NQkv5gdRE+2J2winq14J2by5BS +S7CTKtQ+FjPlnsZlFT5kOwQ/2wyPX1wdaR+v8+khjPPvl/aatxm2hHSco1S1cE5j +2FddUyGbQJJD+tZ3VTNPZNX70Cxqjm0lpu+F6ALEUz65noe8zDUa3qHpimOHZR4R +Kttjd5cUvpoUmRGywO6wT/gUITJDT5+rosuoD6o7BlXGEilXCNQ314cnrUlZp5Gr +RHpejXDbl85IULFzk/bwg2D5zfHhMf1bfHEhYxQUqq/F3pN+aLHsIqKqkHWetUNy +6mSjhEv9DKgma3GX7lZjZuhCVPnHHd/Qj1vfyDBviP4NxDMcU6ij/UgQ8uQKTuEV +V/xuZDDCVRHc6qnNSlSsKWNEz0pAoNZoWRsz+e86i9sgktxChL8Bq4fA1SCC28a5 +g4VCXA9DO2pJNdWY9BW/+mGBDAkgGNLQFwzLSABQ6XaCjGTXOqAHVcweMcDvOrRl +++O/QmueD6i9a5jc2NvLi6Td11n0bt3+qsOR0C5CB8AMTVPNJLFMWx5R9N/pkvo= +-----END CERTIFICATE----- + +# Issuer: CN=Certplus Root CA G2 O=Certplus +# Subject: CN=Certplus Root CA G2 O=Certplus +# Label: "Certplus Root CA G2" +# Serial: 1492087096131536844209563509228951875861589 +# MD5 Fingerprint: a7:ee:c4:78:2d:1b:ee:2d:b9:29:ce:d6:a7:96:32:31 +# SHA1 Fingerprint: 4f:65:8e:1f:e9:06:d8:28:02:e9:54:47:41:c9:54:25:5d:69:cc:1a +# SHA256 Fingerprint: 6c:c0:50:41:e6:44:5e:74:69:6c:4c:fb:c9:f8:0f:54:3b:7e:ab:bb:44:b4:ce:6f:78:7c:6a:99:71:c4:2f:17 +-----BEGIN CERTIFICATE----- +MIICHDCCAaKgAwIBAgISESDZkc6uo+jF5//pAq/Pc7xVMAoGCCqGSM49BAMDMD4x +CzAJBgNVBAYTAkZSMREwDwYDVQQKDAhDZXJ0cGx1czEcMBoGA1UEAwwTQ2VydHBs +dXMgUm9vdCBDQSBHMjAeFw0xNDA1MjYwMDAwMDBaFw0zODAxMTUwMDAwMDBaMD4x +CzAJBgNVBAYTAkZSMREwDwYDVQQKDAhDZXJ0cGx1czEcMBoGA1UEAwwTQ2VydHBs +dXMgUm9vdCBDQSBHMjB2MBAGByqGSM49AgEGBSuBBAAiA2IABM0PW1aC3/BFGtat +93nwHcmsltaeTpwftEIRyoa/bfuFo8XlGVzX7qY/aWfYeOKmycTbLXku54uNAm8x +Ik0G42ByRZ0OQneezs/lf4WbGOT8zC5y0xaTTsqZY1yhBSpsBqNjMGEwDgYDVR0P +AQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNqDYwJ5jtpMxjwj +FNiPwyCrKGBZMB8GA1UdIwQYMBaAFNqDYwJ5jtpMxjwjFNiPwyCrKGBZMAoGCCqG +SM49BAMDA2gAMGUCMHD+sAvZ94OX7PNVHdTcswYO/jOYnYs5kGuUIe22113WTNch +p+e/IQ8rzfcq3IUHnQIxAIYUFuXcsGXCwI4Un78kFmjlvPl5adytRSv3tjFzzAal +U5ORGpOucGpnutee5WEaXw== +-----END CERTIFICATE----- + +# Issuer: CN=OpenTrust Root CA G1 O=OpenTrust +# Subject: CN=OpenTrust Root CA G1 O=OpenTrust +# Label: "OpenTrust Root CA G1" +# Serial: 1492036577811947013770400127034825178844775 +# MD5 Fingerprint: 76:00:cc:81:29:cd:55:5e:88:6a:7a:2e:f7:4d:39:da +# SHA1 Fingerprint: 79:91:e8:34:f7:e2:ee:dd:08:95:01:52:e9:55:2d:14:e9:58:d5:7e +# SHA256 Fingerprint: 56:c7:71:28:d9:8c:18:d9:1b:4c:fd:ff:bc:25:ee:91:03:d4:75:8e:a2:ab:ad:82:6a:90:f3:45:7d:46:0e:b4 +-----BEGIN CERTIFICATE----- +MIIFbzCCA1egAwIBAgISESCzkFU5fX82bWTCp59rY45nMA0GCSqGSIb3DQEBCwUA +MEAxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlPcGVuVHJ1c3QxHTAbBgNVBAMMFE9w +ZW5UcnVzdCBSb290IENBIEcxMB4XDTE0MDUyNjA4NDU1MFoXDTM4MDExNTAwMDAw +MFowQDELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCU9wZW5UcnVzdDEdMBsGA1UEAwwU +T3BlblRydXN0IFJvb3QgQ0EgRzEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK +AoICAQD4eUbalsUwXopxAy1wpLuwxQjczeY1wICkES3d5oeuXT2R0odsN7faYp6b +wiTXj/HbpqbfRm9RpnHLPhsxZ2L3EVs0J9V5ToybWL0iEA1cJwzdMOWo010hOHQX +/uMftk87ay3bfWAfjH1MBcLrARYVmBSO0ZB3Ij/swjm4eTrwSSTilZHcYTSSjFR0 +77F9jAHiOH3BX2pfJLKOYheteSCtqx234LSWSE9mQxAGFiQD4eCcjsZGT44ameGP +uY4zbGneWK2gDqdkVBFpRGZPTBKnjix9xNRbxQA0MMHZmf4yzgeEtE7NCv82TWLx +p2NX5Ntqp66/K7nJ5rInieV+mhxNaMbBGN4zK1FGSxyO9z0M+Yo0FMT7MzUj8czx +Kselu7Cizv5Ta01BG2Yospb6p64KTrk5M0ScdMGTHPjgniQlQ/GbI4Kq3ywgsNw2 +TgOzfALU5nsaqocTvz6hdLubDuHAk5/XpGbKuxs74zD0M1mKB3IDVedzagMxbm+W +G+Oin6+Sx+31QrclTDsTBM8clq8cIqPQqwWyTBIjUtz9GVsnnB47ev1CI9sjgBPw +vFEVVJSmdz7QdFG9URQIOTfLHzSpMJ1ShC5VkLG631UAC9hWLbFJSXKAqWLXwPYY +EQRVzXR7z2FwefR7LFxckvzluFqrTJOVoSfupb7PcSNCupt2LQIDAQABo2MwYTAO +BgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUl0YhVyE1 +2jZVx/PxN3DlCPaTKbYwHwYDVR0jBBgwFoAUl0YhVyE12jZVx/PxN3DlCPaTKbYw +DQYJKoZIhvcNAQELBQADggIBAB3dAmB84DWn5ph76kTOZ0BP8pNuZtQ5iSas000E +PLuHIT839HEl2ku6q5aCgZG27dmxpGWX4m9kWaSW7mDKHyP7Rbr/jyTwyqkxf3kf +gLMtMrpkZ2CvuVnN35pJ06iCsfmYlIrM4LvgBBuZYLFGZdwIorJGnkSI6pN+VxbS +FXJfLkur1J1juONI5f6ELlgKn0Md/rcYkoZDSw6cMoYsYPXpSOqV7XAp8dUv/TW0 +V8/bhUiZucJvbI/NeJWsZCj9VrDDb8O+WVLhX4SPgPL0DTatdrOjteFkdjpY3H1P +XlZs5VVZV6Xf8YpmMIzUUmI4d7S+KNfKNsSbBfD4Fdvb8e80nR14SohWZ25g/4/I +i+GOvUKpMwpZQhISKvqxnUOOBZuZ2mKtVzazHbYNeS2WuOvyDEsMpZTGMKcmGS3t +TAZQMPH9WD25SxdfGbRqhFS0OE85og2WaMMolP3tLR9Ka0OWLpABEPs4poEL0L91 +09S5zvE/bw4cHjdx5RiHdRk/ULlepEU0rbDK5uUTdg8xFKmOLZTW1YVNcxVPS/Ky +Pu1svf0OnWZzsD2097+o4BGkxK51CUpjAEggpsadCwmKtODmzj7HPiY46SvepghJ +AwSQiumPv+i2tCqjI40cHLI5kqiPAlxAOXXUc0ECd97N4EOH1uS6SsNsEn/+KuYj +1oxx +-----END CERTIFICATE----- + +# Issuer: CN=OpenTrust Root CA G2 O=OpenTrust +# Subject: CN=OpenTrust Root CA G2 O=OpenTrust +# Label: "OpenTrust Root CA G2" +# Serial: 1492012448042702096986875987676935573415441 +# MD5 Fingerprint: 57:24:b6:59:24:6b:ae:c8:fe:1c:0c:20:f2:c0:4e:eb +# SHA1 Fingerprint: 79:5f:88:60:c5:ab:7c:3d:92:e6:cb:f4:8d:e1:45:cd:11:ef:60:0b +# SHA256 Fingerprint: 27:99:58:29:fe:6a:75:15:c1:bf:e8:48:f9:c4:76:1d:b1:6c:22:59:29:25:7b:f4:0d:08:94:f2:9e:a8:ba:f2 +-----BEGIN CERTIFICATE----- +MIIFbzCCA1egAwIBAgISESChaRu/vbm9UpaPI+hIvyYRMA0GCSqGSIb3DQEBDQUA +MEAxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlPcGVuVHJ1c3QxHTAbBgNVBAMMFE9w +ZW5UcnVzdCBSb290IENBIEcyMB4XDTE0MDUyNjAwMDAwMFoXDTM4MDExNTAwMDAw +MFowQDELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCU9wZW5UcnVzdDEdMBsGA1UEAwwU +T3BlblRydXN0IFJvb3QgQ0EgRzIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK +AoICAQDMtlelM5QQgTJT32F+D3Y5z1zCU3UdSXqWON2ic2rxb95eolq5cSG+Ntmh +/LzubKh8NBpxGuga2F8ORAbtp+Dz0mEL4DKiltE48MLaARf85KxP6O6JHnSrT78e +CbY2albz4e6WiWYkBuTNQjpK3eCasMSCRbP+yatcfD7J6xcvDH1urqWPyKwlCm/6 +1UWY0jUJ9gNDlP7ZvyCVeYCYitmJNbtRG6Q3ffyZO6v/v6wNj0OxmXsWEH4db0fE +FY8ElggGQgT4hNYdvJGmQr5J1WqIP7wtUdGejeBSzFfdNTVY27SPJIjki9/ca1TS +gSuyzpJLHB9G+h3Ykst2Z7UJmQnlrBcUVXDGPKBWCgOz3GIZ38i1MH/1PCZ1Eb3X +G7OHngevZXHloM8apwkQHZOJZlvoPGIytbU6bumFAYueQ4xncyhZW+vj3CzMpSZy +YhK05pyDRPZRpOLAeiRXyg6lPzq1O4vldu5w5pLeFlwoW5cZJ5L+epJUzpM5ChaH +vGOz9bGTXOBut9Dq+WIyiET7vycotjCVXRIouZW+j1MY5aIYFuJWpLIsEPUdN6b4 +t/bQWVyJ98LVtZR00dX+G7bw5tYee9I8y6jj9RjzIR9u701oBnstXW5DiabA+aC/ +gh7PU3+06yzbXfZqfUAkBXKJOAGTy3HCOV0GEfZvePg3DTmEJwIDAQABo2MwYTAO +BgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUajn6QiL3 +5okATV59M4PLuG53hq8wHwYDVR0jBBgwFoAUajn6QiL35okATV59M4PLuG53hq8w +DQYJKoZIhvcNAQENBQADggIBAJjLq0A85TMCl38th6aP1F5Kr7ge57tx+4BkJamz +Gj5oXScmp7oq4fBXgwpkTx4idBvpkF/wrM//T2h6OKQQbA2xx6R3gBi2oihEdqc0 +nXGEL8pZ0keImUEiyTCYYW49qKgFbdEfwFFEVn8nNQLdXpgKQuswv42hm1GqO+qT +RmTFAHneIWv2V6CG1wZy7HBGS4tz3aAhdT7cHcCP009zHIXZ/n9iyJVvttN7jLpT +wm+bREx50B1ws9efAvSyB7DH5fitIw6mVskpEndI2S9G/Tvw/HRwkqWOOAgfZDC2 +t0v7NqwQjqBSM2OdAzVWxWm9xiNaJ5T2pBL4LTM8oValX9YZ6e18CL13zSdkzJTa +TkZQh+D5wVOAHrut+0dSixv9ovneDiK3PTNZbNTe9ZUGMg1RGUFcPk8G97krgCf2 +o6p6fAbhQ8MTOWIaNr3gKC6UAuQpLmBVrkA9sHSSXvAgZJY/X0VdiLWK2gKgW0VU +3jg9CcCoSmVGFvyqv1ROTVu+OEO3KMqLM6oaJbolXCkvW0pujOotnCr2BXbgd5eA +iN1nE28daCSLT7d0geX0YJ96Vdc+N9oWaz53rK4YcJUIeSkDiv7BO7M/Gg+kO14f +WKGVyasvc0rQLW6aWQ9VGHgtPFGml4vmu7JwqkwR3v98KzfUetF3NI/n+UL3PIEM +S1IK +-----END CERTIFICATE----- + +# Issuer: CN=OpenTrust Root CA G3 O=OpenTrust +# Subject: CN=OpenTrust Root CA G3 O=OpenTrust +# Label: "OpenTrust Root CA G3" +# Serial: 1492104908271485653071219941864171170455615 +# MD5 Fingerprint: 21:37:b4:17:16:92:7b:67:46:70:a9:96:d7:a8:13:24 +# SHA1 Fingerprint: 6e:26:64:f3:56:bf:34:55:bf:d1:93:3f:7c:01:de:d8:13:da:8a:a6 +# SHA256 Fingerprint: b7:c3:62:31:70:6e:81:07:8c:36:7c:b8:96:19:8f:1e:32:08:dd:92:69:49:dd:8f:57:09:a4:10:f7:5b:62:92 +-----BEGIN CERTIFICATE----- +MIICITCCAaagAwIBAgISESDm+Ez8JLC+BUCs2oMbNGA/MAoGCCqGSM49BAMDMEAx +CzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlPcGVuVHJ1c3QxHTAbBgNVBAMMFE9wZW5U +cnVzdCBSb290IENBIEczMB4XDTE0MDUyNjAwMDAwMFoXDTM4MDExNTAwMDAwMFow +QDELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCU9wZW5UcnVzdDEdMBsGA1UEAwwUT3Bl +blRydXN0IFJvb3QgQ0EgRzMwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAARK7liuTcpm +3gY6oxH84Bjwbhy6LTAMidnW7ptzg6kjFYwvWYpa3RTqnVkrQ7cG7DK2uu5Bta1d +oYXM6h0UZqNnfkbilPPntlahFVmhTzeXuSIevRHr9LIfXsMUmuXZl5mjYzBhMA4G +A1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRHd8MUi2I5 +DMlv4VBN0BBY3JWIbTAfBgNVHSMEGDAWgBRHd8MUi2I5DMlv4VBN0BBY3JWIbTAK +BggqhkjOPQQDAwNpADBmAjEAj6jcnboMBBf6Fek9LykBl7+BFjNAk2z8+e2AcG+q +j9uEwov1NcoG3GRvaBbhj5G5AjEA2Euly8LQCGzpGPta3U1fJAuwACEl74+nBCZx +4nxp5V2a+EEfOzmTk51V6s2N8fvB +-----END CERTIFICATE----- + +# Issuer: CN=ISRG Root X1 O=Internet Security Research Group +# Subject: CN=ISRG Root X1 O=Internet Security Research Group +# Label: "ISRG Root X1" +# Serial: 172886928669790476064670243504169061120 +# MD5 Fingerprint: 0c:d2:f9:e0:da:17:73:e9:ed:86:4d:a5:e3:70:e7:4e +# SHA1 Fingerprint: ca:bd:2a:79:a1:07:6a:31:f2:1d:25:36:35:cb:03:9d:43:29:a5:e8 +# SHA256 Fingerprint: 96:bc:ec:06:26:49:76:f3:74:60:77:9a:cf:28:c5:a7:cf:e8:a3:c0:aa:e1:1a:8f:fc:ee:05:c0:bd:df:08:c6 +-----BEGIN CERTIFICATE----- +MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw +TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh +cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 +WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu +ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY +MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc +h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ +0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U +A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW +T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH +B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC +B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv +KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn +OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn +jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw +qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI +rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq +hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL +ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ +3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK +NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 +ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur +TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC +jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc +oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq +4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA +mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d +emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= +-----END CERTIFICATE----- From be58c9e231f873a76b8d31f26930259f4da6b385 Mon Sep 17 00:00:00 2001 From: Benjamin Sergeant Date: Tue, 13 Sep 2016 17:33:54 -0700 Subject: [PATCH 377/692] _log_failed_submission fails with malformed exceptions This will manifest in those kind of errors: ``` if 'exception' in data and 'stacktrace' in data['exception']['values'][0]: # try to reconstruct a reasonable version of the exception for frame in data['exception']['values'][0]['stacktrace']['frames']: output.append(' File "%(fn)s", line %(lineno)s, in %(func)s' % { > 'fn': frame['filename'], 'lineno': frame['lineno'], 'func': frame['function'], }) E KeyError: 'filename' ``` --- raven/base.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/raven/base.py b/raven/base.py index a682c76de..9496592bf 100644 --- a/raven/base.py +++ b/raven/base.py @@ -657,9 +657,9 @@ def _log_failed_submission(self, data): # try to reconstruct a reasonable version of the exception for frame in data['exception']['values'][0]['stacktrace']['frames']: output.append(' File "%(fn)s", line %(lineno)s, in %(func)s' % { - 'fn': frame['filename'], - 'lineno': frame['lineno'], - 'func': frame['function'], + 'fn': frame.get('filename', 'unknown_filename'), + 'lineno': frame.get('lineno', -1), + 'func': frame.get('function', 'unknown_function'), }) self.uncaught_logger.error(output) From c3e504a0bf77deb9a79bd841e9cacc62c8127901 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 14 Mar 2016 20:51:54 -0700 Subject: [PATCH 378/692] Replace culprits with transaction names - Implemented in Django via middleware - Defer empty culprit to server (Sentry deals fills it) - Implemented Celery as task names - Cleaned up Django and Celery abstractions to ease testing --- conftest.py | 1 + raven/base.py | 11 +- raven/contrib/celery/__init__.py | 67 +++++++---- raven/contrib/django/middleware/__init__.py | 49 +++++++- raven/contrib/django/models.py | 121 +++++++++++++------- raven/contrib/django/resolver.py | 69 +++++++++++ raven/contrib/flask.py | 4 + raven/handlers/logging.py | 12 +- raven/middleware.py | 1 + raven/utils/stacks.py | 36 ------ raven/utils/testutils.py | 14 +++ raven/utils/transaction.py | 51 +++++++++ tests/contrib/django/test_resolver.py | 21 ++++ tests/contrib/django/tests.py | 69 ++++------- tests/contrib/flask/tests.py | 22 +--- tests/contrib/test_celery.py | 40 +++++++ tests/contrib/webpy/tests.py | 1 - tests/handlers/logging/tests.py | 3 - tests/utils/stacks/tests.py | 31 +---- tests/utils/test_transaction.py | 41 +++++++ 20 files changed, 447 insertions(+), 217 deletions(-) create mode 100644 raven/contrib/django/resolver.py create mode 100644 raven/utils/transaction.py create mode 100644 tests/contrib/django/test_resolver.py create mode 100644 tests/contrib/test_celery.py create mode 100644 tests/utils/test_transaction.py diff --git a/conftest.py b/conftest.py index 55c641c3a..5c3b03f34 100644 --- a/conftest.py +++ b/conftest.py @@ -81,4 +81,5 @@ def pytest_configure(config): ], }], ALLOWED_HOSTS=['*'], + DISABLE_SENTRY_INSTRUMENTATION=True, ) diff --git a/raven/base.py b/raven/base.py index 3b4c891ec..6ff424c36 100644 --- a/raven/base.py +++ b/raven/base.py @@ -37,7 +37,8 @@ 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, get_culprit +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 # enforce imports to avoid obscure stacktraces with MemoryError @@ -186,6 +187,7 @@ def __init__(self, dsn=None, raise_send_errors=False, transport=None, self.tags = o.get('tags') or {} self.environment = o.get('environment') or None self.release = o.get('release') or os.environ.get('HEROKU_SLUG_COMMIT') + self.transaction = TransactionStack() self.ignore_exceptions = set(o.get('ignore_exceptions') or ()) @@ -410,12 +412,7 @@ def build_msg(self, event_type, data=None, date=None, ) if not culprit: - if 'stacktrace' in data: - culprit = get_culprit(data['stacktrace']['frames']) - elif 'exception' in data: - stacktrace = data['exception']['values'][0].get('stacktrace') - if stacktrace: - culprit = get_culprit(stacktrace['frames']) + culprit = self.transaction.peek() if not data.get('level'): data['level'] = kwargs.get('level') or logging.ERROR diff --git a/raven/contrib/celery/__init__.py b/raven/contrib/celery/__init__.py index 3fec34c26..cfc34cbf4 100644 --- a/raven/contrib/celery/__init__.py +++ b/raven/contrib/celery/__init__.py @@ -10,7 +10,9 @@ import logging from celery.exceptions import SoftTimeLimitExceeded -from celery.signals import after_setup_logger, task_failure +from celery.signals import ( + after_setup_logger, task_failure, task_prerun, task_postrun +) from raven.handlers.logging import SentryHandler @@ -25,26 +27,7 @@ def filter(self, record): def register_signal(client, ignore_expected=False): - def process_failure_signal(sender, task_id, args, kwargs, einfo, **kw): - if ignore_expected and isinstance(einfo.exception, sender.throws): - return - - # This signal is fired inside the stack so let raven do its magic - if isinstance(einfo.exception, SoftTimeLimitExceeded): - fingerprint = ['celery', 'SoftTimeLimitExceeded', sender] - else: - fingerprint = None - client.captureException( - extra={ - 'task_id': task_id, - 'task': sender, - 'args': args, - 'kwargs': kwargs, - }, - fingerprint=fingerprint, - ) - - task_failure.connect(process_failure_signal, weak=False) + SentryCeleryHandler(client, ignore_expected=ignore_expected).install() def register_logger_signal(client, logger=None, loglevel=logging.ERROR): @@ -67,3 +50,45 @@ def process_logger_event(sender, logger, loglevel, logfile, format, logger.addHandler(handler) after_setup_logger.connect(process_logger_event, weak=False) + + +class SentryCeleryHandler(object): + def __init__(self, client, ignore_expected=False): + self.client = client + self.ignore_expected = ignore_expected + + def install(self): + task_prerun.connect(self.handle_task_prerun, weak=False) + task_postrun.connect(self.handle_task_postrun, weak=False) + task_failure.connect(self.process_failure_signal, weak=False) + + def uninstall(self): + task_prerun.disconnect(self.handle_task_prerun) + task_postrun.disconnect(self.handle_task_postrun) + 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): + return + + # This signal is fired inside the stack so let raven do its magic + if isinstance(einfo.exception, SoftTimeLimitExceeded): + fingerprint = ['celery', 'SoftTimeLimitExceeded', sender] + else: + fingerprint = None + + self.client.captureException( + extra={ + 'task_id': task_id, + 'task': sender, + 'args': args, + 'kwargs': kwargs, + }, + fingerprint=fingerprint, + ) + + def handle_task_prerun(self, sender, task_id, task, **kw): + self.client.transaction.push(task.name) + + def handle_task_postrun(self, sender, task_id, task, **kw): + self.client.transaction.pop(task.name) diff --git a/raven/contrib/django/middleware/__init__.py b/raven/contrib/django/middleware/__init__.py index 275aef73f..650c949f3 100644 --- a/raven/contrib/django/middleware/__init__.py +++ b/raven/contrib/django/middleware/__init__.py @@ -8,11 +8,13 @@ from __future__ import absolute_import -import threading import logging +import threading from django.conf import settings +from raven.contrib.django.resolver import RouteResolver + def is_ignorable_404(uri): """ @@ -61,9 +63,48 @@ def process_response(self, request, response): return response -class SentryLogMiddleware(object): - # Create a threadlocal variable to store the session in for logging - thread = threading.local() +class SentryMiddleware(threading.local): + resolver = RouteResolver() + + # backwards compat + @property + def thread(self): + return self + + def _get_transaction_from_request(self, request): + # TODO(dcramer): it'd be nice to pull out parameters + # and make this a normalized path + return self.resolver.resolve(request.path) def process_request(self, request): + self._txid = None self.thread.request = request + + def process_view(self, request, func, args, kwargs): + from raven.contrib.django.models import client + + try: + self._txid = client.transaction.push( + self._get_transaction_from_request(request) + ) + except Exception as exc: + raise + client.error_logger.exception(repr(exc)) + return None + + def process_response(self, request, response): + from raven.contrib.django.models import client + + if self._txid: + client.transaction.pop(self._txid) + self._txid = None + return response + + # def process_exception(self, request, exception): + # from raven.contrib.django.models import client + + # if self._txid: + # client.transaction.pop(self._txid) + # self._txid = None + +SentryLogMiddleware = SentryMiddleware diff --git a/raven/contrib/django/models.py b/raven/contrib/django/models.py index 8411dddf5..7aeb563c4 100644 --- a/raven/contrib/django/models.py +++ b/raven/contrib/django/models.py @@ -16,13 +16,17 @@ import warnings from django.conf import settings +from django.core.signals import got_request_exception, request_started +from threading import Lock -from raven._compat import PY2, binary_type, text_type, string_types +from raven._compat import PY2, binary_type, text_type from raven.utils.conf import convert_options from raven.utils.imports import import_string logger = logging.getLogger('sentry.errors.client') +settings_lock = Lock() + def get_installed_apps(): """ @@ -151,58 +155,64 @@ def sentry_exception_handler(request=None, **kwargs): warnings.warn('Unable to process log entry: %s' % (exc,)) -def register_handlers(): - from django.core.signals import got_request_exception, request_started - - def before_request(*args, **kwargs): - client.context.activate() - request_started.connect(before_request, weak=False) +class SentryDjangoHandler(object): + def __init__(self, client=client): + self.client = client - # HACK: support Sentry's internal communication - if 'sentry' in settings.INSTALLED_APPS: - from django.db import transaction - # Django 1.6 - if hasattr(transaction, 'atomic'): - commit_on_success = transaction.atomic + try: + import celery + except ImportError: + self.has_celery = False else: - commit_on_success = transaction.commit_on_success + self.has_celery = celery.VERSION >= (2, 5) - @commit_on_success - def wrap_sentry(request, **kwargs): - if transaction.is_dirty(): - transaction.rollback() - return sentry_exception_handler(request, **kwargs) + self.celery_handler = None - exception_handler = wrap_sentry - else: - exception_handler = sentry_exception_handler + def install_celery(self): + from raven.contrib.celery import ( + SentryCeleryHandler, register_logger_signal + ) - # Connect to Django's internal signal handler - got_request_exception.connect(exception_handler, weak=False) + self.celery_handler = SentryCeleryHandler(client).install() - # If Celery is installed, register a signal handler - if 'djcelery' in settings.INSTALLED_APPS: - try: - # Celery < 2.5? is not supported - from raven.contrib.celery import ( - register_signal, register_logger_signal) - except ImportError: - logger.exception('Failed to install Celery error handler') - else: + # try: + # ga = lambda x, d=None: getattr(settings, 'SENTRY_%s' % x, d) + # options = getattr(settings, 'RAVEN_CONFIG', {}) + # loglevel = options.get('celery_loglevel', + # ga('CELERY_LOGLEVEL', logging.ERROR)) + + # register_logger_signal(client, loglevel=loglevel) + # except Exception: + # logger.exception('Failed to install Celery error handler') + + def install(self): + request_started.connect(self.before_request, weak=False) + got_request_exception.connect(self.exception_handler, weak=False) + + if self.has_celery: try: - register_signal(client) + self.install_celery() except Exception: logger.exception('Failed to install Celery error handler') + def uninstall(self): + request_started.disconnect(self.before_request) + got_request_exception.disconnect(self.exception_handler) + + if self.celery_handler: + self.celery_handler.uninstall() + + def exception_handler(self, request=None, **kwargs): + try: + self.client.captureException(exc_info=sys.exc_info(), request=request) + except Exception as exc: try: - ga = lambda x, d=None: getattr(settings, 'SENTRY_%s' % x, d) - options = getattr(settings, 'RAVEN_CONFIG', {}) - loglevel = options.get('celery_loglevel', - ga('CELERY_LOGLEVEL', logging.ERROR)) + logger.exception('Unable to process log entry: %s' % (exc,)) + except Exception as exc: + warnings.warn('Unable to process log entry: %s' % (exc,)) - register_logger_signal(client, loglevel=loglevel) - except Exception: - logger.exception('Failed to install Celery error handler') + def before_request(self, *args, **kwargs): + self.client.context.activate() def register_serializers(): @@ -210,7 +220,30 @@ def register_serializers(): import raven.contrib.django.serializers # NOQA -if ('raven.contrib.django' in settings.INSTALLED_APPS - or 'raven.contrib.django.raven_compat' in settings.INSTALLED_APPS): - register_handlers() +def install_middleware(): + """ + Force installation of SentryMiddlware if it's not explicitly present. + + This ensures things like request context and transaction names are made + available. + """ + 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) + + +if ( + 'raven.contrib.django' in settings.INSTALLED_APPS or + 'raven.contrib.django.raven_compat' in settings.INSTALLED_APPS +): register_serializers() + install_middleware() + + if not getattr(settings, 'DISABLE_SENTRY_INSTRUMENTATION', False): + handler = SentryDjangoHandler() + handler.install() diff --git a/raven/contrib/django/resolver.py b/raven/contrib/django/resolver.py new file mode 100644 index 000000000..910382274 --- /dev/null +++ b/raven/contrib/django/resolver.py @@ -0,0 +1,69 @@ +from __future__ import absolute_import + +import re + +from django.urls import get_resolver, Resolver404 + + +class RouteResolver(object): + _optional_group_matcher = re.compile(r'\(\?\:([^\)]+)\)') + _named_group_matcher = re.compile(r'\(\?P<(\w+)>[^\)]+\)') + _non_named_group_matcher = re.compile(r'\([^\)]+\)') + # [foo|bar|baz] + _either_option_matcher = re.compile(r'\[([^\]]+)\|([^\]]+)\]') + _camel_re = re.compile(r'([A-Z]+)([a-z])') + + _cache = {} + + def _simplify(self, pattern): + """ + Clean up urlpattern regexes into something readable by humans: + + From: + > "^(?P\w+)/athletes/(?P\w+)/$" + + To: + > "{sport_slug}/athletes/{athlete_slug}/" + """ + # remove optional params + pattern = self._optional_group_matcher.sub(lambda m: '[%s]' % m.group(1), pattern) + + # handle named groups first + pattern = self._named_group_matcher.sub(lambda m: '{%s}' % m.group(1), pattern) + + # handle non-named groups + pattern = self._non_named_group_matcher.sub("{var}", pattern) + + # handle optional params + pattern = self._either_option_matcher.sub(lambda m: m.group(1), pattern) + + # clean up any outstanding regex-y characters. + pattern = pattern.replace('^', '').replace('$', '') \ + .replace('?', '').replace('//', '/').replace('\\', '') + if not pattern.startswith('/'): + pattern = '/' + pattern + return pattern + + def resolve(self, path, urlconf=None): + # TODO(dcramer): it'd be nice to pull out parameters + # and make this a normalized path + resolver = get_resolver(urlconf) + match = resolver.regex.search(path) + if match: + new_path = path[match.end():] + for pattern in resolver.url_patterns: + try: + sub_match = pattern.resolve(new_path) + except Resolver404: + continue + if sub_match: + pattern = pattern.regex.pattern + try: + return self._cache[pattern] + except KeyError: + pass + + pattern_name = self._simplify(pattern) + self._cache[pattern] = pattern + return pattern_name + return path diff --git a/raven/contrib/flask.py b/raven/contrib/flask.py index a9733442e..670e2ec03 100644 --- a/raven/contrib/flask.py +++ b/raven/contrib/flask.py @@ -216,6 +216,9 @@ def get_http_info_with_retriever(self, request, retriever=None): def before_request(self, *args, **kwargs): self.last_event_id = None + + self.client.transaction.push(request.url_rule.rule) + try: self.client.http_context(self.get_http_info(request)) except Exception as e: @@ -229,6 +232,7 @@ def after_request(self, sender, response, *args, **kwargs): if self.last_event_id: response.headers['X-Sentry-ID'] = self.last_event_id self.client.context.clear() + self.client.transaction.pop(request.url_rule.rule) return response def init_app(self, app, dsn=None, logging=None, level=None, diff --git a/raven/handlers/logging.py b/raven/handlers/logging.py index cc82550c4..180e54b8b 100644 --- a/raven/handlers/logging.py +++ b/raven/handlers/logging.py @@ -17,7 +17,7 @@ from raven._compat import string_types, iteritems, text_type from raven.base import Client from raven.utils.encoding import to_string -from raven.utils.stacks import iter_stack_frames, label_from_frame +from raven.utils.stacks import iter_stack_frames RESERVED = frozenset(( 'stack', 'name', 'module', 'funcName', 'args', 'msg', 'levelno', @@ -159,16 +159,6 @@ def _emit(self, record, **kwargs): event_type = 'raven.events.Exception' handler_kwargs = {'exc_info': record.exc_info} - # HACK: discover a culprit when we normally couldn't - elif not (data.get('stacktrace') or data.get('culprit')) \ - and (record.name or record.funcName): - culprit = label_from_frame({ - 'module': record.name, - 'function': record.funcName - }) - if culprit: - data['culprit'] = culprit - data['level'] = record.levelno data['logger'] = record.name diff --git a/raven/middleware.py b/raven/middleware.py index c9c556604..cc2b8248a 100644 --- a/raven/middleware.py +++ b/raven/middleware.py @@ -70,6 +70,7 @@ def close(self): self.iterable.close() finally: self.sentry.client.context.clear() + self.sentry.transaction.clear() self.closed = True diff --git a/raven/utils/stacks.py b/raven/utils/stacks.py index 7933313b6..ee95d7a57 100644 --- a/raven/utils/stacks.py +++ b/raven/utils/stacks.py @@ -11,7 +11,6 @@ import linecache import re import sys -import warnings from raven.utils.serializer import transform from raven._compat import iteritems @@ -82,41 +81,6 @@ def get_lines_from_file(filename, lineno, context_lines, ) -def label_from_frame(frame): - module = frame.get('module') or '?' - function = frame.get('function') or '?' - if module == function == '?': - return '' - return '%s in %s' % (module, function) - - -def get_culprit(frames, *args, **kwargs): - # We iterate through each frame looking for a deterministic culprit - # When one is found, we mark it as last "best guess" (best_guess) and then - # check it against ``exclude_paths``. If it isn't listed, then we - # use this option. If nothing is found, we use the "best guess". - if args or kwargs: - warnings.warn('get_culprit no longer does application detection') - - best_guess = None - culprit = None - for frame in reversed(frames): - culprit = label_from_frame(frame) - if not culprit: - culprit = None - continue - - if frame.get('in_app'): - return culprit - elif not best_guess: - best_guess = culprit - elif best_guess: - break - - # Return either the best guess or the last frames call - return best_guess or culprit - - def _getitem_from_frame(f_locals, key, default=None): """ f_locals is not guaranteed to have .get(), but it will always diff --git a/raven/utils/testutils.py b/raven/utils/testutils.py index 52df1b1c6..4859f435b 100644 --- a/raven/utils/testutils.py +++ b/raven/utils/testutils.py @@ -7,6 +7,8 @@ """ from __future__ import absolute_import +import raven + from exam import Exam try: @@ -17,3 +19,15 @@ class TestCase(Exam, BaseTestCase): pass + + +class InMemoryClient(raven.Client): + def __init__(self, **kwargs): + self.events = [] + super(InMemoryClient, self).__init__(**kwargs) + + def is_enabled(self): + return True + + def send(self, **kwargs): + self.events.append(kwargs) diff --git a/raven/utils/transaction.py b/raven/utils/transaction.py new file mode 100644 index 000000000..8dc6e064d --- /dev/null +++ b/raven/utils/transaction.py @@ -0,0 +1,51 @@ +from __future__ import absolute_import + +from threading import local + + +class TransactionContext(object): + def __init__(self, stack, context): + self.stack = stack + self.context = context + + def __enter__(self): + self.stack.push(self.context) + return self + + def __exit__(self, *exc_info): + self.stack.pop(self.context) + + +class TransactionStack(local): + def __init__(self): + self.stack = [] + + def __len__(self): + return len(self.stack) + + def __iter__(self): + return iter(self.stack) + + def __call__(self, context): + return TransactionContext(self, context) + + def clear(self): + self.stack = [] + + def peek(self): + try: + return self.stack[-1] + except IndexError: + return None + + def push(self, context): + self.stack.append(context) + return context + + def pop(self, context=None): + if context is None: + return self.stack.pop() + + while self.stack: + if self.stack.pop() is context: + return context diff --git a/tests/contrib/django/test_resolver.py b/tests/contrib/django/test_resolver.py new file mode 100644 index 000000000..930d28718 --- /dev/null +++ b/tests/contrib/django/test_resolver.py @@ -0,0 +1,21 @@ +from __future__ import absolute_import + +from raven.contrib.django.resolver import RouteResolver + + +def test_no_match(): + resolver = RouteResolver() + result = resolver.resolve('/foo/bar', 'raven.contrib.django.urls') + assert result == '/foo/bar' + + +def test_simple_match(): + resolver = RouteResolver() + result = resolver.resolve('/report/', 'raven.contrib.django.urls') + assert result == '/report/' + + +def test_complex_match(): + resolver = RouteResolver() + result = resolver.resolve('/api/1234/store/', 'raven.contrib.django.urls') + assert result == '/api/{project_id}/store/' diff --git a/tests/contrib/django/tests.py b/tests/contrib/django/tests.py index 6fca81cc7..d369cc402 100644 --- a/tests/contrib/django/tests.py +++ b/tests/contrib/django/tests.py @@ -28,7 +28,9 @@ from raven.contrib.django.client import DjangoClient from raven.contrib.django.celery import CeleryClient from raven.contrib.django.handlers import SentryHandler -from raven.contrib.django.models import client, get_client, sentry_exception_handler +from raven.contrib.django.models import ( + SentryDjangoHandler, client, get_client +) from raven.contrib.django.middleware.wsgi import Sentry from raven.contrib.django.templatetags.raven import sentry_public_dsn from raven.contrib.django.views import is_valid_origin @@ -36,6 +38,7 @@ from raven.utils.serializer import transform from django.test.client import Client as TestClient, ClientHandler as TestClientHandler + from .models import TestModel settings.SENTRY_CLIENT = 'tests.contrib.django.tests.TempStoreClient' @@ -128,6 +131,9 @@ class DjangoClientTest(TestCase): def setUp(self): self.raven = get_client() + self.handler = SentryDjangoHandler(self.raven) + self.handler.install() + self.addCleanup(self.handler.uninstall) def test_basic(self): self.raven.captureMessage(message='foo') @@ -156,7 +162,6 @@ def test_signal_integration(self): assert exc['value'], "int() argument must be a string or a number == not 'NoneType'" assert event['level'] == logging.ERROR assert event['message'], "TypeError: int() argument must be a string or a number == not 'NoneType'" - assert event['culprit'] == 'tests.contrib.django.tests in test_signal_integration' @pytest.mark.skipif(sys.version_info[:2] == (2, 6), reason='Python 2.6') def test_view_exception(self): @@ -170,7 +175,6 @@ def test_view_exception(self): assert exc['value'] == 'view exception' assert event['level'] == logging.ERROR assert event['message'] == 'Exception: view exception' - assert event['culprit'] == 'tests.contrib.django.views in raise_exc' def test_user_info(self): with Settings(MIDDLEWARE_CLASSES=[ @@ -262,7 +266,6 @@ def test_request_middleware_exception(self): assert exc['value'] == 'request' assert event['level'] == logging.ERROR assert event['message'] == 'ImportError: request' - assert event['culprit'] == 'tests.contrib.django.middleware in process_request' def test_response_middlware_exception(self): if django.VERSION[:2] < (1, 3): @@ -279,7 +282,6 @@ def test_response_middlware_exception(self): assert exc['value'] == 'response' assert event['level'] == logging.ERROR assert event['message'] == 'ImportError: response' - assert event['culprit'] == 'tests.contrib.django.middleware in process_response' def test_broken_500_handler_with_middleware(self): with Settings(BREAK_THAT_500=True, INSTALLED_APPS=['raven.contrib.django']): @@ -297,7 +299,6 @@ def test_broken_500_handler_with_middleware(self): assert exc['value'] == 'view exception' assert event['level'] == logging.ERROR assert event['message'] == 'Exception: view exception' - assert event['culprit'] == 'tests.contrib.django.views in raise_exc' event = self.raven.events.pop(0) @@ -307,7 +308,6 @@ def test_broken_500_handler_with_middleware(self): assert exc['value'] == 'handler500' assert event['level'] == logging.ERROR assert event['message'] == 'ValueError: handler500' - assert event['culprit'] == 'tests.contrib.django.urls in handler500' def test_view_middleware_exception(self): with Settings(MIDDLEWARE_CLASSES=['tests.contrib.django.middleware.BrokenViewMiddleware']): @@ -322,30 +322,6 @@ def test_view_middleware_exception(self): assert exc['value'] == 'view' assert event['level'] == logging.ERROR assert event['message'] == 'ImportError: view' - assert event['culprit'] == 'tests.contrib.django.middleware in process_view' - - def test_exclude_modules_view(self): - exclude_paths = self.raven.exclude_paths - self.raven.exclude_paths = ['tests.views'] - self.assertRaises(Exception, self.client.get, reverse('sentry-raise-exc-decor')) - - assert len(self.raven.events) == 1 - event = self.raven.events.pop(0) - - assert event['culprit'] == 'tests.contrib.django.views in raise_exc' - self.raven.exclude_paths = exclude_paths - - def test_include_modules(self): - include_paths = self.raven.include_paths - self.raven.include_paths = ['django.shortcuts'] - - self.assertRaises(Exception, self.client.get, reverse('sentry-django-exc')) - - assert len(self.raven.events) == 1 - event = self.raven.events.pop(0) - - assert event['culprit'].startswith('django.shortcuts in ') - self.raven.include_paths = include_paths @pytest.mark.skipif(DJANGO_18, reason='Django 1.8+ not supported') def test_template_name_as_view(self): @@ -393,7 +369,7 @@ def test_404_middleware(self): resp = self.client.get('/non-existent-page') assert resp.status_code == 404 - assert len(self.raven.events) == 1 + assert len(self.raven.events) == 1, [e['message'] for e in self.raven.events] event = self.raven.events.pop(0) assert event['level'] == logging.INFO @@ -786,11 +762,16 @@ def request(self): def exc_info(self): return (ValueError, ValueError('lol world'), None) + def setUp(self): + super(SentryExceptionHandlerTest, self).setUp() + self.client = get_client() + self.handler = SentryDjangoHandler(self.client) + @mock.patch.object(TempStoreClient, 'captureException') @mock.patch('sys.exc_info') def test_does_capture_exception(self, exc_info, captureException): exc_info.return_value = self.exc_info - sentry_exception_handler(request=self.request) + self.handler.exception_handler(request=self.request) captureException.assert_called_once_with(exc_info=self.exc_info, request=self.request) @@ -799,11 +780,11 @@ def test_does_capture_exception(self, exc_info, captureException): def test_does_exclude_filtered_types(self, exc_info, mock_send): exc_info.return_value = self.exc_info try: - get_client().ignore_exceptions = set(['ValueError']) + self.client.ignore_exceptions = set(['ValueError']) - sentry_exception_handler(request=self.request) + self.handler.exception_handler(request=self.request) finally: - get_client().ignore_exceptions.clear() + self.client.ignore_exceptions.clear() assert not mock_send.called @@ -814,12 +795,12 @@ def test_ignore_exceptions_with_expression_match(self, exc_info, mock_send): try: if six.PY3: - get_client().ignore_exceptions = set(['builtins.*']) + self.client.ignore_exceptions = set(['builtins.*']) else: - get_client().ignore_exceptions = set(['exceptions.*']) - sentry_exception_handler(request=self.request) + self.client.ignore_exceptions = set(['exceptions.*']) + self.handler.exception_handler(request=self.request) finally: - get_client().ignore_exceptions.clear() + self.client.ignore_exceptions.clear() assert not mock_send.called @@ -830,11 +811,11 @@ def test_ignore_exceptions_with_module_match(self, exc_info, mock_send): try: if six.PY3: - get_client().ignore_exceptions = set(['builtins.ValueError']) + self.client.ignore_exceptions = set(['builtins.ValueError']) else: - get_client().ignore_exceptions = set(['exceptions.ValueError']) - sentry_exception_handler(request=self.request) + self.client.ignore_exceptions = set(['exceptions.ValueError']) + self.handler.exception_handler(request=self.request) finally: - get_client().ignore_exceptions.clear() + self.client.ignore_exceptions.clear() assert not mock_send.called diff --git a/tests/contrib/flask/tests.py b/tests/contrib/flask/tests.py index cf87329ba..73dff6a76 100644 --- a/tests/contrib/flask/tests.py +++ b/tests/contrib/flask/tests.py @@ -6,24 +6,11 @@ from flask import Flask, current_app, g from flask.ext.login import LoginManager, AnonymousUserMixin, login_user -from raven.base import Client from raven.contrib.flask import Sentry -from raven.utils.testutils import TestCase +from raven.utils.testutils import InMemoryClient, TestCase from raven.handlers.logging import SentryHandler -class TempStoreClient(Client): - def __init__(self, **kwargs): - self.events = [] - super(TempStoreClient, self).__init__(**kwargs) - - def is_enabled(self): - return True - - def send(self, **kwargs): - self.events.append(kwargs) - - class User(AnonymousUserMixin): is_active = lambda x: True is_authenticated = lambda x: True @@ -96,12 +83,12 @@ def client(self): @before def bind_sentry(self): - self.raven = TempStoreClient() + self.raven = InMemoryClient() self.middleware = Sentry(self.app, client=self.raven) def make_client_and_raven(self, *args, **kwargs): app = create_app(*args, **kwargs) - raven = TempStoreClient() + raven = InMemoryClient() Sentry(app, client=raven) return app.test_client(), raven, app @@ -124,7 +111,6 @@ def test_error_handler(self): self.assertEquals(exc['value'], 'hello world') self.assertEquals(event['level'], logging.ERROR) self.assertEquals(event['message'], 'ValueError: hello world') - self.assertEquals(event['culprit'], 'tests.contrib.flask.tests in an_error') def test_capture_plus_logging(self): client, raven, app = self.make_client_and_raven(debug=False) @@ -251,7 +237,7 @@ def test_captureMessage_sets_last_event_id(self): def test_logging_setup_with_exclusion_list(self): app = Flask(__name__) - raven = TempStoreClient() + raven = InMemoryClient() Sentry(app, client=raven, logging=True, logging_exclusions=("excluded_logger",)) diff --git a/tests/contrib/test_celery.py b/tests/contrib/test_celery.py new file mode 100644 index 000000000..bc3de0fef --- /dev/null +++ b/tests/contrib/test_celery.py @@ -0,0 +1,40 @@ +from __future__ import absolute_import + +import celery + +from raven.contrib.celery import SentryCeleryHandler +from raven.utils.testutils import InMemoryClient, TestCase + + +class CeleryTestCase(TestCase): + def setUp(self): + super(CeleryTestCase, self).setUp() + self.celery = celery.Celery(__name__) + self.celery.conf.CELERY_ALWAYS_EAGER = True + + self.client = InMemoryClient() + self.handler = SentryCeleryHandler(self.client, ignore_expected=True) + self.handler.install() + self.addCleanup(self.handler.uninstall) + + def test_simple(self): + @self.celery.task(name='dummy_task') + def dummy_task(x, y): + return x / y + + dummy_task.delay(1, 2) + dummy_task.delay(1, 0) + assert len(self.client.events) == 1 + event = self.client.events[0] + exception = event['exception']['values'][0] + assert event['culprit'] == 'dummy_task' + assert exception['type'] == 'ZeroDivisionError' + + def test_ignore_expected(self): + @self.celery.task(name='dummy_task', throws=(ZeroDivisionError,)) + def dummy_task(x, y): + return x / y + + dummy_task.delay(1, 2) + dummy_task.delay(1, 0) + assert len(self.client.events) == 0 diff --git a/tests/contrib/webpy/tests.py b/tests/contrib/webpy/tests.py index d96cb9c20..7d63e5089 100644 --- a/tests/contrib/webpy/tests.py +++ b/tests/contrib/webpy/tests.py @@ -57,7 +57,6 @@ def test_get(self): 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) diff --git a/tests/handlers/logging/tests.py b/tests/handlers/logging/tests.py index 01feb0920..bf8063a71 100644 --- a/tests/handlers/logging/tests.py +++ b/tests/handlers/logging/tests.py @@ -167,7 +167,6 @@ def test_record_stack(self): self.assertEqual(frame['module'], 'raven.handlers.logging') assert 'exception' not in event self.assertTrue('sentry.interfaces.Message' in event) - self.assertEqual(event['culprit'], 'root in make_record') self.assertEqual(event['message'], 'This is a test of stacks') def test_no_record_stack(self): @@ -186,8 +185,6 @@ def test_explicit_stack(self): self.assertEqual(len(self.client.events), 1) event = self.client.events.pop(0) assert 'stacktrace' in event - assert 'culprit' in event - assert event['culprit'] == 'root in make_record' self.assertTrue('message' in event, event) self.assertEqual(event['message'], 'This is a test of stacks') assert 'exception' not in event diff --git a/tests/utils/stacks/tests.py b/tests/utils/stacks/tests.py index a0cc5beef..bfd92153b 100644 --- a/tests/utils/stacks/tests.py +++ b/tests/utils/stacks/tests.py @@ -6,7 +6,7 @@ from mock import Mock from raven.utils.testutils import TestCase -from raven.utils.stacks import get_culprit, get_stack_info, get_lines_from_file +from raven.utils.stacks import get_stack_info, get_lines_from_file class Context(object): @@ -18,33 +18,6 @@ def __init__(self, dict): iterkeys = lambda s, *a: six.iterkeys(s.dict, *a) -class GetCulpritTest(TestCase): - def test_empty_module(self): - culprit = get_culprit([{ - 'module': None, - 'function': 'foo', - }]) - assert culprit == '? in foo' - - def test_empty_function(self): - culprit = get_culprit([{ - 'module': 'foo', - 'function': None, - }]) - assert culprit == 'foo in ?' - - def test_no_module_or_function(self): - culprit = get_culprit([{}]) - assert culprit is None - - def test_all_params(self): - culprit = get_culprit([{ - 'module': 'package.name', - 'function': 'foo', - }]) - assert culprit == 'package.name in foo' - - class GetStackInfoTest(TestCase): def test_bad_locals_in_frame(self): frame = Mock() @@ -95,6 +68,7 @@ def test_frame_allowance(self): assert results['frames'][8]['filename'] == '8' assert results['frames'][9]['filename'] == '9' + class FailLoader(): ''' Recreating the built-in loaders from a fake stack trace was brittle. @@ -109,6 +83,7 @@ def get_source(self, module_name): else: raise ValueError('Invalid file extension') + class GetLineFromFileTest(TestCase): def setUp(self): self.loader = FailLoader() diff --git a/tests/utils/test_transaction.py b/tests/utils/test_transaction.py new file mode 100644 index 000000000..3ffe0cdb3 --- /dev/null +++ b/tests/utils/test_transaction.py @@ -0,0 +1,41 @@ +from __future__ import absolute_import + +from raven.utils.transaction import TransactionStack + + +def test_simple(): + stack = TransactionStack() + + stack.push('foo') + + assert len(stack) == 1 + assert stack.peek() == 'foo' + + bar = stack.push(['bar']) + + assert len(stack) == 2 + assert stack.peek() == ['bar'] + + stack.push({'baz': True}) + + assert len(stack) == 3 + assert stack.peek() == {'baz': True} + + stack.pop(bar) + + assert len(stack) == 1 + assert stack.peek() == 'foo' + + stack.pop() + + assert len(stack) == 0 + assert stack.peek() == None + + +def test_context_manager(): + stack = TransactionStack() + + with stack('foo'): + assert stack.peek() == 'foo' + + assert stack.peek() is None From 10882935560a4fdc20f6630fb22a7af71236b617 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 8 Aug 2016 15:17:02 -0700 Subject: [PATCH 379/692] Fix url resolver import --- raven/contrib/django/resolver.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/raven/contrib/django/resolver.py b/raven/contrib/django/resolver.py index 910382274..916de3bae 100644 --- a/raven/contrib/django/resolver.py +++ b/raven/contrib/django/resolver.py @@ -2,7 +2,10 @@ import re -from django.urls import get_resolver, Resolver404 +try: + from django.urls import get_resolver, Resolver404 +except ImportError: + from django.core.urlresolvers import get_resolver, Resolver404 class RouteResolver(object): From cd323386b61cd280fcf3e599ae6a02b889f81a40 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 8 Aug 2016 15:23:57 -0700 Subject: [PATCH 380/692] Remove support for old style api urls --- raven/contrib/django/urls.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/contrib/django/urls.py b/raven/contrib/django/urls.py index de3acec45..884621c6d 100644 --- a/raven/contrib/django/urls.py +++ b/raven/contrib/django/urls.py @@ -16,6 +16,6 @@ import raven.contrib.django.views urlpatterns = ( - url(r'^api/(?:(?P[\w_-]+)/)?store/$', raven.contrib.django.views.report, name='raven-report'), + url(r'^api/(?P[\w_-]+)/store/$', raven.contrib.django.views.report, name='raven-report'), url(r'^report/', raven.contrib.django.views.report), ) From e3b63ab59968c2d1f2f5ccd8c06b0df7f45686d2 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 8 Aug 2016 15:24:22 -0700 Subject: [PATCH 381/692] Remove [] around optional params --- raven/contrib/django/resolver.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/raven/contrib/django/resolver.py b/raven/contrib/django/resolver.py index 916de3bae..cd5c5c09a 100644 --- a/raven/contrib/django/resolver.py +++ b/raven/contrib/django/resolver.py @@ -29,7 +29,10 @@ def _simplify(self, pattern): > "{sport_slug}/athletes/{athlete_slug}/" """ # remove optional params - pattern = self._optional_group_matcher.sub(lambda m: '[%s]' % m.group(1), pattern) + # TODO(dcramer): it'd be nice to change these into [%s] but it currently + # conflicts with the other rules because we're doing regexp matches + # rather than parsing tokens + pattern = self._optional_group_matcher.sub(lambda m: '%s' % m.group(1), pattern) # handle named groups first pattern = self._named_group_matcher.sub(lambda m: '{%s}' % m.group(1), pattern) From a5562363634fe0773798a31e449d2f4eca46e423 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 8 Aug 2016 15:30:51 -0700 Subject: [PATCH 382/692] Correct test --- tests/contrib/django/tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/contrib/django/tests.py b/tests/contrib/django/tests.py index d369cc402..f0e89c746 100644 --- a/tests/contrib/django/tests.py +++ b/tests/contrib/django/tests.py @@ -635,7 +635,7 @@ class ReportViewTest(TestCase): def setUp(self): super(ReportViewTest, self).setUp() - self.path = reverse('raven-report', urlconf=self.urls) + self.path = reverse('raven-report', args=['1'], urlconf=self.urls) @mock.patch('raven.contrib.django.views.is_valid_origin') def test_calls_is_valid_origin_with_header(self, is_valid_origin): From e5c3e91f2d24ddc177ba81e953f94378319c863b Mon Sep 17 00:00:00 2001 From: David Cramer Date: Tue, 9 Aug 2016 10:56:20 -0700 Subject: [PATCH 383/692] Dont raise hard error on transaction parsing --- raven/contrib/django/middleware/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/raven/contrib/django/middleware/__init__.py b/raven/contrib/django/middleware/__init__.py index 650c949f3..57be2988a 100644 --- a/raven/contrib/django/middleware/__init__.py +++ b/raven/contrib/django/middleware/__init__.py @@ -88,7 +88,6 @@ def process_view(self, request, func, args, kwargs): self._get_transaction_from_request(request) ) except Exception as exc: - raise client.error_logger.exception(repr(exc)) return None From 031d282241e1146f83983886d34dbd1e81b5ca4e Mon Sep 17 00:00:00 2001 From: David Cramer Date: Fri, 12 Aug 2016 00:49:18 -0700 Subject: [PATCH 384/692] Capture process as initial transaction --- raven/base.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/raven/base.py b/raven/base.py index 6ff424c36..5d5a996ce 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 +from raven.utils.stacks import get_stack_info, iter_stack_frames, slim_string from raven.utils.transaction import TransactionStack from raven.transport.registry import TransportRegistry, default_transports @@ -188,6 +188,9 @@ 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 ab365cd35d2950f57f96084324363d1b5f0e66e2 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 15 Aug 2016 13:20:36 -0700 Subject: [PATCH 385/692] Utilize request_finished for clearing URL transaction --- raven/contrib/django/middleware/__init__.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/raven/contrib/django/middleware/__init__.py b/raven/contrib/django/middleware/__init__.py index 57be2988a..f9fd2c122 100644 --- a/raven/contrib/django/middleware/__init__.py +++ b/raven/contrib/django/middleware/__init__.py @@ -12,6 +12,7 @@ import threading from django.conf import settings +from django.core.signals import request_finished from raven.contrib.django.resolver import RouteResolver @@ -89,21 +90,21 @@ def process_view(self, request, func, args, kwargs): ) except Exception as exc: client.error_logger.exception(repr(exc)) + else: + # we utilize request_finished as the exception gets reported + # *after* process_response is executed, and thus clearing the + # transaction there would leave it empty + request_finished.connect(self.request_finished) + return None - def process_response(self, request, response): + def request_finished(self, **kwargs): from raven.contrib.django.models import client if self._txid: client.transaction.pop(self._txid) self._txid = None - return response - - # def process_exception(self, request, exception): - # from raven.contrib.django.models import client - # if self._txid: - # client.transaction.pop(self._txid) - # self._txid = None + request_finished.disconnect(self.request_finished) SentryLogMiddleware = SentryMiddleware From 780e5fe63883e9e2d29f76145a34c9da1875c3ba Mon Sep 17 00:00:00 2001 From: David Cramer Date: Tue, 30 Aug 2016 12:53:51 -0700 Subject: [PATCH 386/692] Dont utilize weakrefs --- raven/contrib/django/middleware/__init__.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/raven/contrib/django/middleware/__init__.py b/raven/contrib/django/middleware/__init__.py index f9fd2c122..d8c1df997 100644 --- a/raven/contrib/django/middleware/__init__.py +++ b/raven/contrib/django/middleware/__init__.py @@ -94,7 +94,11 @@ def process_view(self, request, func, args, kwargs): # we utilize request_finished as the exception gets reported # *after* process_response is executed, and thus clearing the # transaction there would leave it empty - request_finished.connect(self.request_finished) + # XXX(dcramer): weakref's cause a threading issue in certain + # versions of Django (e.g. 1.6). While they'd be ideal, we're under + # the assumption that Django will always call our function except + # in the situation of a process or thread dying. + request_finished.connect(self.request_finished, weak=False) return None From 0142b7b3bd79fbd129121e4ab3d38cd8459bb048 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Tue, 13 Sep 2016 12:04:10 -0700 Subject: [PATCH 387/692] Handle included patterns --- raven/contrib/django/resolver.py | 76 ++++++++++++++++----------- tests/contrib/django/test_resolver.py | 27 ++++++++-- 2 files changed, 69 insertions(+), 34 deletions(-) diff --git a/raven/contrib/django/resolver.py b/raven/contrib/django/resolver.py index cd5c5c09a..12d22e4d5 100644 --- a/raven/contrib/django/resolver.py +++ b/raven/contrib/django/resolver.py @@ -3,9 +3,9 @@ import re try: - from django.urls import get_resolver, Resolver404 + from django.urls import get_resolver except ImportError: - from django.core.urlresolvers import get_resolver, Resolver404 + from django.core.urlresolvers import get_resolver class RouteResolver(object): @@ -32,44 +32,58 @@ def _simplify(self, pattern): # TODO(dcramer): it'd be nice to change these into [%s] but it currently # conflicts with the other rules because we're doing regexp matches # rather than parsing tokens - pattern = self._optional_group_matcher.sub(lambda m: '%s' % m.group(1), pattern) + result = self._optional_group_matcher.sub(lambda m: '%s' % m.group(1), pattern) # handle named groups first - pattern = self._named_group_matcher.sub(lambda m: '{%s}' % m.group(1), pattern) + result = self._named_group_matcher.sub(lambda m: '{%s}' % m.group(1), result) # handle non-named groups - pattern = self._non_named_group_matcher.sub("{var}", pattern) + result = self._non_named_group_matcher.sub('{var}', result) # handle optional params - pattern = self._either_option_matcher.sub(lambda m: m.group(1), pattern) + result = self._either_option_matcher.sub(lambda m: m.group(1), result) # clean up any outstanding regex-y characters. - pattern = pattern.replace('^', '').replace('$', '') \ + result = result.replace('^', '').replace('$', '') \ .replace('?', '').replace('//', '/').replace('\\', '') - if not pattern.startswith('/'): - pattern = '/' + pattern - return pattern + + return result + + def _resolve(self, resolver, path, parents=None): + match = resolver.regex.search(path) + if not match: + return + + if parents is None: + parents = [resolver] + else: + parents.append(resolver) + + new_path = path[match.end():] + for pattern in resolver.url_patterns: + # this is an include() + if not pattern.callback: + match = self._resolve(pattern, new_path, parents) + if match: + return match + continue + + elif not pattern.regex.search(new_path): + continue + + try: + return self._cache[pattern] + except KeyError: + pass + + prefix = ''.join(self._simplify(p.regex.pattern) for p in parents) + result = prefix + self._simplify(pattern.regex.pattern) + if not result.startswith('/'): + result = '/' + result + self._cache[pattern] = result + return result def resolve(self, path, urlconf=None): - # TODO(dcramer): it'd be nice to pull out parameters - # and make this a normalized path resolver = get_resolver(urlconf) - match = resolver.regex.search(path) - if match: - new_path = path[match.end():] - for pattern in resolver.url_patterns: - try: - sub_match = pattern.resolve(new_path) - except Resolver404: - continue - if sub_match: - pattern = pattern.regex.pattern - try: - return self._cache[pattern] - except KeyError: - pass - - pattern_name = self._simplify(pattern) - self._cache[pattern] = pattern - return pattern_name - return path + match = self._resolve(resolver, path) + return match or path diff --git a/tests/contrib/django/test_resolver.py b/tests/contrib/django/test_resolver.py index 930d28718..396016194 100644 --- a/tests/contrib/django/test_resolver.py +++ b/tests/contrib/django/test_resolver.py @@ -1,21 +1,42 @@ from __future__ import absolute_import +try: + from django.conf.urls import url, include +except ImportError: + # for Django version less than 1.4 + from django.conf.urls.defaults import url, include # NOQA + from raven.contrib.django.resolver import RouteResolver +included_url_conf = ( + url(r'^foo/bar/(?P[\w]+)', lambda x: ''), +), '', '' + +example_url_conf = ( + url(r'^api/(?P[\w_-]+)/store/$', lambda x: ''), + url(r'^example/', include(included_url_conf)), +) + def test_no_match(): resolver = RouteResolver() - result = resolver.resolve('/foo/bar', 'raven.contrib.django.urls') + result = resolver.resolve('/foo/bar', example_url_conf) assert result == '/foo/bar' def test_simple_match(): resolver = RouteResolver() - result = resolver.resolve('/report/', 'raven.contrib.django.urls') + result = resolver.resolve('/report/', example_url_conf) assert result == '/report/' def test_complex_match(): resolver = RouteResolver() - result = resolver.resolve('/api/1234/store/', 'raven.contrib.django.urls') + result = resolver.resolve('/api/1234/store/', example_url_conf) assert result == '/api/{project_id}/store/' + + +def test_included_match(): + resolver = RouteResolver() + result = resolver.resolve('/example/foo/bar/baz', example_url_conf) + assert result == '/example/foo/bar/{param}' From 313bc2be03dfa9365d439e3ac7eee3417417d62f Mon Sep 17 00:00:00 2001 From: David Cramer Date: Wed, 14 Sep 2016 09:25:49 -0700 Subject: [PATCH 388/692] Bad ref to client --- raven/middleware.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/middleware.py b/raven/middleware.py index cc2b8248a..e00a5f243 100644 --- a/raven/middleware.py +++ b/raven/middleware.py @@ -70,7 +70,7 @@ def close(self): self.iterable.close() finally: self.sentry.client.context.clear() - self.sentry.transaction.clear() + self.sentry.client.transaction.clear() self.closed = True From 0513442d5aee6952c7651033bb72229c4a21853a Mon Sep 17 00:00:00 2001 From: Andrew Date: Fri, 16 Sep 2016 12:40:51 -0400 Subject: [PATCH 389/692] Improve exception handling in get_user_info --- raven/contrib/django/client.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/raven/contrib/django/client.py b/raven/contrib/django/client.py index 62b2c0f44..c59405020 100644 --- a/raven/contrib/django/client.py +++ b/raven/contrib/django/client.py @@ -140,17 +140,18 @@ def install_sql_hook(self): install_sql_hook() def get_user_info(self, user): - if hasattr(user, 'is_authenticated'): - # is_authenticated was a method in Django < 1.10 - if callable(user.is_authenticated): - authenticated = user.is_authenticated() - else: - authenticated = user.is_authenticated - if not authenticated: - return None - - user_info = {} try: + if hasattr(user, 'is_authenticated'): + # is_authenticated was a method in Django < 1.10 + if callable(user.is_authenticated): + authenticated = user.is_authenticated() + else: + authenticated = user.is_authenticated + if not authenticated: + return None + + user_info = {} + user_info['id'] = user.pk if hasattr(user, 'email'): From 722287c1b641cde1d408249e0fe7eb22e4160d4b Mon Sep 17 00:00:00 2001 From: David Cramer Date: Fri, 16 Sep 2016 12:31:01 -0700 Subject: [PATCH 390/692] Changes for 5.27.0 --- CHANGES | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES b/CHANGES index 1ff594f53..de4ff60bb 100644 --- a/CHANGES +++ b/CHANGES @@ -2,6 +2,8 @@ Version 5.27.0 -------------- * Updated CA bundle. +* Added transaction-based culprits for Celery, Django, and Flask. +* Fixed an issue where ``ignore_exceptions`` wasn't respected. Version 5.26.0 -------------- From 3a2dfe1249696d9764dc00440732952a810319e3 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Fri, 16 Sep 2016 12:35:41 -0700 Subject: [PATCH 391/692] 5.28.0.dev0 --- raven/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/__init__.py b/raven/__init__.py index 13567e46e..d6e1c1fb8 100644 --- a/raven/__init__.py +++ b/raven/__init__.py @@ -12,7 +12,7 @@ __all__ = ('VERSION', 'Client', 'get_version') -VERSION = '5.27.0.dev0' +VERSION = '5.28.0.dev0' def _get_git_revision(path): From da71ccb58b05a7c379a647dffbb112780c531016 Mon Sep 17 00:00:00 2001 From: David Tran Date: Wed, 14 Sep 2016 14:06:49 -0700 Subject: [PATCH 392/692] Add support for Django 1.10-style middleware via `MiddlewareMixin` Update docs to mention `MIDDLEWARE` for Django 1.10 --- docs/integrations/django.rst | 10 +++++----- raven/contrib/django/middleware/__init__.py | 12 ++++++++++-- raven/contrib/django/middleware/wsgi.py | 10 +++++++++- 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/docs/integrations/django.rst b/docs/integrations/django.rst index c4b09a40a..c0ca5dc42 100644 --- a/docs/integrations/django.rst +++ b/docs/integrations/django.rst @@ -144,11 +144,11 @@ In certain conditions you may wish to log 404 events to the Sentry server. To do this, you simply need to enable a Django middleware: .. sourcecode:: python - - MIDDLEWARE_CLASSES = ( + # Use ``MIDDLEWARE_CLASSES`` prior to Django 1.10 + MIDDLEWARE = ( 'raven.contrib.django.raven_compat.middleware.Sentry404CatchMiddleware', ..., - ) + MIDDLEWARE_CLASSES + ) + MIDDLEWARE It is recommended to put the middleware at the top, so that any only 404s that bubbled all the way up get logged. Certain middlewares (e.g. flatpages) @@ -175,8 +175,8 @@ Sentry supports sending a message ID to your clients so that they can be tracked easily by your development team. There are two ways to access this information, the first is via the ``X-Sentry-ID`` HTTP response header. Adding this is as simple as appending a middleware to your stack:: - - MIDDLEWARE_CLASSES = MIDDLEWARE_CLASSES + ( + # Use ``MIDDLEWARE_CLASSES`` prior to Django 1.10 + MIDDLEWARE = MIDDLEWARE + ( # We recommend putting this as high in the chain as possible 'raven.contrib.django.raven_compat.middleware.SentryResponseErrorIdMiddleware', ..., diff --git a/raven/contrib/django/middleware/__init__.py b/raven/contrib/django/middleware/__init__.py index d8c1df997..a817473ce 100644 --- a/raven/contrib/django/middleware/__init__.py +++ b/raven/contrib/django/middleware/__init__.py @@ -14,6 +14,14 @@ from django.conf import settings from django.core.signals import request_finished +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.contrib.django.resolver import RouteResolver @@ -27,7 +35,7 @@ def is_ignorable_404(uri): ) -class Sentry404CatchMiddleware(object): +class Sentry404CatchMiddleware(MiddlewareMixin): def process_response(self, request, response): from raven.contrib.django.models import client @@ -52,7 +60,7 @@ def process_response(self, request, response): # sentry_exception_handler(sender=Sentry404CatchMiddleware, request=request) -class SentryResponseErrorIdMiddleware(object): +class SentryResponseErrorIdMiddleware(MiddlewareMixin): """ Appends the X-Sentry-ID response header for referencing a message within the Sentry datastore. diff --git a/raven/contrib/django/middleware/wsgi.py b/raven/contrib/django/middleware/wsgi.py index 86e4e7ece..e762f0099 100644 --- a/raven/contrib/django/middleware/wsgi.py +++ b/raven/contrib/django/middleware/wsgi.py @@ -7,11 +7,19 @@ """ 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): +class Sentry(Sentry, MiddlewareMixin): """ Identical to the default WSGI middleware except that the client comes dynamically via ``get_client From b0fc6792c9d9da36f99558bcd628b374016ae823 Mon Sep 17 00:00:00 2001 From: David Tran Date: Sat, 17 Sep 2016 15:45:05 -0700 Subject: [PATCH 393/692] Add `MiddlewareMixin` for these test classes as well --- tests/contrib/django/middleware.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/tests/contrib/django/middleware.py b/tests/contrib/django/middleware.py index 629a837f9..d3d9b5a63 100644 --- a/tests/contrib/django/middleware.py +++ b/tests/contrib/django/middleware.py @@ -1,13 +1,22 @@ -class BrokenRequestMiddleware(object): +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 + + +class BrokenRequestMiddleware(MiddlewareMixin): def process_request(self, request): raise ImportError('request') -class BrokenResponseMiddleware(object): +class BrokenResponseMiddleware(MiddlewareMixin): def process_response(self, request, response): raise ImportError('response') -class BrokenViewMiddleware(object): +class BrokenViewMiddleware(MiddlewareMixin): def process_view(self, request, func, args, kwargs): raise ImportError('view') From 521f272cd6ca4c6774e8c2265fb0f8a912612250 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 19 Sep 2016 08:40:57 -0700 Subject: [PATCH 394/692] Gracefully handle no txid (fixes GH-863) --- raven/contrib/django/middleware/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/contrib/django/middleware/__init__.py b/raven/contrib/django/middleware/__init__.py index d8c1df997..2ea45b006 100644 --- a/raven/contrib/django/middleware/__init__.py +++ b/raven/contrib/django/middleware/__init__.py @@ -105,7 +105,7 @@ def process_view(self, request, func, args, kwargs): def request_finished(self, **kwargs): from raven.contrib.django.models import client - if self._txid: + if getattr(self, '_txid', None): client.transaction.pop(self._txid) self._txid = None From 920d1b9d16a7277fd96a4b85b0d7bd8f82c8d377 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 19 Sep 2016 08:43:35 -0700 Subject: [PATCH 395/692] Handle optional url_rule in Flask (fixes GH-863) --- raven/contrib/flask.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/raven/contrib/flask.py b/raven/contrib/flask.py index 670e2ec03..2e9593fa0 100644 --- a/raven/contrib/flask.py +++ b/raven/contrib/flask.py @@ -217,7 +217,8 @@ def get_http_info_with_retriever(self, request, retriever=None): def before_request(self, *args, **kwargs): self.last_event_id = None - self.client.transaction.push(request.url_rule.rule) + if request.url_rule: + self.client.transaction.push(request.url_rule.rule) try: self.client.http_context(self.get_http_info(request)) @@ -232,7 +233,8 @@ def after_request(self, sender, response, *args, **kwargs): if self.last_event_id: response.headers['X-Sentry-ID'] = self.last_event_id self.client.context.clear() - self.client.transaction.pop(request.url_rule.rule) + if request.url_rule: + self.client.transaction.pop(request.url_rule.rule) return response def init_app(self, app, dsn=None, logging=None, level=None, From 40750403efa19c427be068ed1f59e9751c949e0a Mon Sep 17 00:00:00 2001 From: Matt Robenolt Date: Mon, 19 Sep 2016 13:00:36 -0700 Subject: [PATCH 396/692] Slight speed improvement for the common case of not needing to report (#866) --- raven/contrib/django/middleware/__init__.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/raven/contrib/django/middleware/__init__.py b/raven/contrib/django/middleware/__init__.py index 2ea45b006..9ce8777be 100644 --- a/raven/contrib/django/middleware/__init__.py +++ b/raven/contrib/django/middleware/__init__.py @@ -29,9 +29,15 @@ def is_ignorable_404(uri): class Sentry404CatchMiddleware(object): def process_response(self, request, response): + if response.status_code != 404: + return response + + if is_ignorable_404(request.get_full_path()): + return response + from raven.contrib.django.models import client - if response.status_code != 404 or is_ignorable_404(request.get_full_path()) or not client.is_enabled(): + if not client.is_enabled(): return response data = client.get_data_from_request(request) From 40cb915ac277779a2dec413c28c26f2774dcb7e8 Mon Sep 17 00:00:00 2001 From: Pradip Caulagi Date: Wed, 21 Sep 2016 16:01:52 +0200 Subject: [PATCH 397/692] Add missing comma for keyword arguments to function call (#867) --- docs/advanced.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/advanced.rst b/docs/advanced.rst index bb9a55868..e55c2c31c 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -51,7 +51,7 @@ settings: import raven client = raven.Client( - dsn='___DSN___' + dsn='___DSN___', # inform the client which parts of code are yours # include_paths=['my.app'] From b4a7c714cfcfb90e5c995b6838f335afdbd28ec0 Mon Sep 17 00:00:00 2001 From: Pradip Caulagi Date: Wed, 21 Sep 2016 23:43:13 +0200 Subject: [PATCH 398/692] Add missing import (#868) --- docs/integrations/django.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/integrations/django.rst b/docs/integrations/django.rst index c4b09a40a..57db5cd42 100644 --- a/docs/integrations/django.rst +++ b/docs/integrations/django.rst @@ -24,6 +24,7 @@ Using the Django integration is as simple as adding Additional settings for the client are configured using the ``RAVEN_CONFIG`` dictionary:: + import os import raven RAVEN_CONFIG = { From 3a2be3ad5a59c51f2b687afe5ae27042e10af94a Mon Sep 17 00:00:00 2001 From: Eric Feng Date: Thu, 22 Sep 2016 11:55:42 -0700 Subject: [PATCH 399/692] Adding environment argument documentation --- docs/advanced.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/advanced.rst b/docs/advanced.rst index e55c2c31c..4b0376b3a 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -105,6 +105,12 @@ The following are valid arguments which may be passed to the Raven client: release = '1.0.3' +.. describe:: environment + + The environment your application is running in:: + + environment = 'staging' + .. describe:: exclude_paths Extending this allow you to ignore module prefixes when we attempt to From 309609994c394fb71c6d36ca2285c3294c04d45b Mon Sep 17 00:00:00 2001 From: David Cramer Date: Fri, 23 Sep 2016 17:00:40 -0700 Subject: [PATCH 400/692] Remove some opinions --- docs/integrations/django.rst | 5 +---- docs/integrations/flask.rst | 4 ---- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/docs/integrations/django.rst b/docs/integrations/django.rst index 57db5cd42..296d6071d 100644 --- a/docs/integrations/django.rst +++ b/docs/integrations/django.rst @@ -3,10 +3,7 @@ Django .. default-domain:: py -`Django `_ is arguably Python's most popular web -framework. Support is built into Raven but needs some configuration. While -older versions of Django will likely work, officially only version 1.4 and -newer are supported. +`Django `_ version 1.4 and newer are supported. Setup ----- diff --git a/docs/integrations/flask.rst b/docs/integrations/flask.rst index 637d07322..0b31392a7 100644 --- a/docs/integrations/flask.rst +++ b/docs/integrations/flask.rst @@ -1,10 +1,6 @@ Flask ===== -`Flask `_ is a popular Python micro webframework. -Support for Flask is provided by Raven directly but for some dependencies -you need to install raven with the flask feature set. - Installation ------------ From c46f17727d4b980e55e4813082f1dbce3d3ab7bd Mon Sep 17 00:00:00 2001 From: slai Date: Mon, 26 Sep 2016 15:19:29 +0100 Subject: [PATCH 401/692] `list_max_length` not `max_list_length` Fixes a small typo in the name of the configuration parameter. I believe https://github.com/getsentry/raven-python/blob/256667349bf883624b2caa6b02ebec62b4deac15/raven/utils/conf.py#L46 confirms the actual name of the dictionary key read. --- docs/advanced.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/advanced.rst b/docs/advanced.rst index 4b0376b3a..47656f34e 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -145,7 +145,7 @@ The following are valid arguments which may be passed to the Raven client: 'django.exceptions.*', ] -.. describe:: max_list_length +.. describe:: list_max_length The maximum number of items a list-like container should store. From 7b0176094ee9c8a2c0aafd833df5f1458f055272 Mon Sep 17 00:00:00 2001 From: Andrew Date: Thu, 29 Sep 2016 10:39:46 -0400 Subject: [PATCH 402/692] Fix unreferenced variable in get_user_info The previous fix left the code broken, since in case of exception, `user_info` was not declared. --- raven/contrib/django/client.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/raven/contrib/django/client.py b/raven/contrib/django/client.py index c59405020..30f966922 100644 --- a/raven/contrib/django/client.py +++ b/raven/contrib/django/client.py @@ -161,14 +161,14 @@ def get_user_info(self, user): user_info['username'] = user.get_username() elif hasattr(user, 'username'): user_info['username'] = user.username + + return user_info except Exception: # We expect that user objects can be somewhat broken at times # and try to just handle as much as possible and ignore errors # as good as possible here. - pass + return None - if user_info: - return user_info def get_data_from_request(self, request): result = {} From 64482ba273f4f54c727c933e0a1f3fddcb0a4b42 Mon Sep 17 00:00:00 2001 From: Andrew Date: Thu, 29 Sep 2016 10:44:29 -0400 Subject: [PATCH 403/692] Remove extra blank line --- raven/contrib/django/client.py | 1 - 1 file changed, 1 deletion(-) diff --git a/raven/contrib/django/client.py b/raven/contrib/django/client.py index 30f966922..d343c62ae 100644 --- a/raven/contrib/django/client.py +++ b/raven/contrib/django/client.py @@ -169,7 +169,6 @@ def get_user_info(self, user): # as good as possible here. return None - def get_data_from_request(self, request): result = {} From f01c5de4259e5560846a25dc60aa3f6c05573cc0 Mon Sep 17 00:00:00 2001 From: Jonas Trappenberg Date: Sat, 1 Oct 2016 12:18:00 -0700 Subject: [PATCH 404/692] Fix another usage of `get_ident` This gets rid of the corresponding `DeprecationWarning` when running `./manage.py raven test`: 2016-10-01 12:16:45,452 py.warnings WARNING: /Users/abc/.venv/lib/python2.7/site-packages/raven/base.py:297: DeprecationWarning: Client.get_ident is deprecated. The event ID is now returned as the result of capture. DeprecationWarning) --- raven/scripts/runner.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/raven/scripts/runner.py b/raven/scripts/runner.py index 74f29b52d..9b06dd184 100644 --- a/raven/scripts/runner.py +++ b/raven/scripts/runner.py @@ -70,7 +70,7 @@ def send_test_message(client, options): sys.stdout.write('Sending a test message... ') sys.stdout.flush() - ident = client.get_ident(client.captureMessage( + ident = client.captureMessage( message='This is a test message generated using ``raven test``', data=data, level=logging.INFO, @@ -80,7 +80,7 @@ def send_test_message(client, options): 'user': get_uid(), 'loadavg': get_loadavg(), }, - )) + ) sys.stdout.write('Event ID was %r\n' % (ident,)) From aa176d969051f8c9faedb4c90d5c439dee051639 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Then=C3=B3rio?= Date: Tue, 4 Oct 2016 23:32:49 -0300 Subject: [PATCH 405/692] fix typo in exception text --- raven/contrib/flask.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/contrib/flask.py b/raven/contrib/flask.py index 2e9593fa0..59ebe2b50 100644 --- a/raven/contrib/flask.py +++ b/raven/contrib/flask.py @@ -99,7 +99,7 @@ def __init__(self, app=None, client=None, client_cls=Client, dsn=None, logging=False, logging_exclusions=None, level=logging.NOTSET, wrap_wsgi=None, register_signal=True): if client and not isinstance(client, Client): - raise TypeError('client should an instance of Client') + raise TypeError('client should be an instance of Client') self.dsn = dsn self.logging = logging From 7ca8746a8375d69f07de13103fcfbab91a868fc6 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Tue, 11 Oct 2016 15:04:56 -0700 Subject: [PATCH 406/692] Fix various code blocks --- docs/integrations/django.rst | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/docs/integrations/django.rst b/docs/integrations/django.rst index fcb16e4fd..333f75aab 100644 --- a/docs/integrations/django.rst +++ b/docs/integrations/django.rst @@ -142,6 +142,7 @@ In certain conditions you may wish to log 404 events to the Sentry server. To do this, you simply need to enable a Django middleware: .. sourcecode:: python + # Use ``MIDDLEWARE_CLASSES`` prior to Django 1.10 MIDDLEWARE = ( 'raven.contrib.django.raven_compat.middleware.Sentry404CatchMiddleware', @@ -172,7 +173,10 @@ Message References Sentry supports sending a message ID to your clients so that they can be tracked easily by your development team. There are two ways to access this information, the first is via the ``X-Sentry-ID`` HTTP response header. -Adding this is as simple as appending a middleware to your stack:: +Adding this is as simple as appending a middleware to your stack: + +.. sourcecode:: python + # Use ``MIDDLEWARE_CLASSES`` prior to Django 1.10 MIDDLEWARE = MIDDLEWARE + ( # We recommend putting this as high in the chain as possible @@ -185,7 +189,9 @@ Sentry will attach :attr:`request.sentry` when it catches a Django exception. In our example, we will use this information to modify the default :file:`500.html` which is rendered, and show the user a case reference ID. The first step in doing this is creating a custom -:func:`handler500` in your :file:`urls.py` file:: +:func:`handler500` in your :file:`urls.py` file: + +.. sourcecode:: python from django.conf.urls.defaults import * @@ -304,14 +310,18 @@ If you already have middleware in place that handles :func:`process_exception` you will need to take extra care when using Sentry. For example, the following middleware would suppress Sentry logging due to it -returning a response:: +returning a response: + +.. sourcecode:: python class MyMiddleware(object): def process_exception(self, request, exception): return HttpResponse('foo') To work around this, you can either disable your error handling middleware, or -add something like the following:: +add something like the following: + +.. sourcecode:: python from django.core.signals import got_request_exception @@ -327,7 +337,9 @@ Note that this technique may break unit tests using the Django test client because the exceptions won't be translated into the expected 404 or 403 response codes. -Or, alternatively, you can just enable Sentry responses:: +Or, alternatively, you can just enable Sentry responses: + +.. sourcecode:: python from raven.contrib.django.raven_compat.models import sentry_exception_handler @@ -342,7 +354,9 @@ Circus If you are running Django with `circus `_ and `chaussette `_ you will also need -to add a hook to circus to activate Raven:: +to add a hook to circus to activate Raven: + +.. sourcecode:: python from django.conf import settings from django.core.management import call_command From 9b54e0297f6cb65657f590f9c088aa4a882ae4bd Mon Sep 17 00:00:00 2001 From: Patrick Gerken Date: Thu, 13 Oct 2016 17:15:28 +0200 Subject: [PATCH 407/692] Minimal support for rest_framework --- CHANGES | 1 + raven/contrib/django/client.py | 10 +++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index de4ff60bb..681954cc1 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,7 @@ 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 d343c62ae..38c78df00 100644 --- a/raven/contrib/django/client.py +++ b/raven/contrib/django/client.py @@ -34,6 +34,14 @@ __all__ = ('DjangoClient',) +VALID_HTTP_REQUESTS = (HttpRequest, ) + +try: + from rest_framework.request import Request + VALID_HTTP_REQUESTS += (Request, ) +except ImportError: + pass + class _FormatConverter(object): @@ -257,7 +265,7 @@ def capture(self, event_type, request=None, **kwargs): if request is None: request = getattr(SentryLogMiddleware.thread, 'request', None) - is_http_request = isinstance(request, HttpRequest) + is_http_request = isinstance(request, VALID_HTTP_REQUESTS) if is_http_request: data.update(self.get_data_from_request(request)) From 8d1692067d638a8971a38284dfadfbc4773d71a7 Mon Sep 17 00:00:00 2001 From: Si Feng Date: Sat, 15 Oct 2016 19:59:49 -0700 Subject: [PATCH 408/692] 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 409/692] 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 410/692] 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 411/692] 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 412/692] 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 413/692] 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 414/692] 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 415/692] 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 416/692] 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 417/692] 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 418/692] 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 419/692] 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 420/692] 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 421/692] 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 422/692] 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 423/692] 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 424/692] 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 425/692] 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 426/692] 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 427/692] 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 428/692] 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 429/692] 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 430/692] 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 431/692] 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 432/692] 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 433/692] 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 434/692] 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 435/692] 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 436/692] 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 437/692] 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 438/692] 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 439/692] 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 440/692] 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 441/692] 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 442/692] 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 443/692] 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 444/692] 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 445/692] 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 446/692] 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 447/692] 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 448/692] 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 449/692] 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