├── .gitmodules ├── tests ├── base │ └── __init__.py ├── conf │ └── __init__.py ├── utils │ ├── __init__.py │ ├── json │ │ ├── __init__.py │ │ └── tests.py │ ├── wsgi │ │ ├── __init__.py │ │ └── tests.py │ ├── encoding │ │ └── __init__.py │ ├── stacks │ │ ├── __init__.py │ │ ├── utf8_file.txt │ │ └── tests.py │ ├── tests.py │ ├── test_conf.py │ ├── test_imports.py │ └── test_transaction.py ├── context │ ├── __init__.py │ └── tests.py ├── contrib │ ├── __init__.py │ ├── async │ │ ├── tests.py │ │ └── __init__.py │ ├── bottle │ │ ├── __init__.py │ │ └── tests.py │ ├── django │ │ ├── __init__.py │ │ ├── management │ │ │ ├── __init__.py │ │ │ └── commands │ │ │ │ ├── __init__.py │ │ │ │ └── test_raven.py │ │ ├── templates │ │ │ ├── 404.html │ │ │ ├── __init__.py │ │ │ └── error.html │ │ ├── models.py │ │ ├── middleware.py │ │ ├── conftest.py │ │ ├── views.py │ │ ├── settings.py │ │ ├── test_resolver.py │ │ ├── urls.py │ │ ├── test_tastypie.py │ │ └── api.py │ ├── flask │ │ └── __init__.py │ ├── pylons │ │ ├── __init__.py │ │ └── tests.py │ ├── sanic │ │ └── __init__.py │ ├── tornado │ │ └── __init__.py │ ├── webpy │ │ ├── __init__.py │ │ └── tests.py │ ├── zconfig │ │ ├── __init__.py │ │ └── tests.py │ ├── zerorpc │ │ ├── __init__.py │ │ └── tests.py │ └── awslambda │ │ ├── test_lambda.py │ │ └── conftest.py ├── events │ ├── __init__.py │ └── tests.py ├── functional │ ├── __init__.py │ └── tests.py ├── handlers │ ├── __init__.py │ ├── logbook │ │ └── __init__.py │ └── logging │ │ └── __init__.py ├── middleware │ └── __init__.py ├── processors │ └── __init__.py ├── transport │ ├── __init__.py │ ├── gevent │ │ ├── __init__.py │ │ └── tests.py │ ├── requests │ │ ├── __init__.py │ │ ├── tests.py │ │ └── test_threaded_requests.py │ ├── threaded │ │ └── __init__.py │ ├── tornado │ │ ├── __init__.py │ │ └── tests.py │ └── tests.py ├── versioning │ ├── __init__.py │ └── tests.py ├── breadcrumbs │ └── __init__.py ├── __init__.py └── templates │ └── sentry-tests │ └── error.html ├── .gitattributes ├── examples └── django_110 │ ├── app │ ├── __init__.py │ ├── management │ │ ├── __init__.py │ │ └── commands │ │ │ ├── __init__.py │ │ │ └── example.py │ ├── views.py │ ├── wsgi.py │ ├── urls.py │ └── settings.py │ ├── requirements.txt │ ├── db.sqlite3 │ └── manage.py ├── AUTHORS ├── raven ├── contrib │ ├── django │ │ ├── management │ │ │ ├── __init__.py │ │ │ └── commands │ │ │ │ ├── __init__.py │ │ │ │ └── raven.py │ │ ├── templatetags │ │ │ ├── __init__.py │ │ │ └── raven.py │ │ ├── celery │ │ │ ├── tasks.py │ │ │ ├── models.py │ │ │ └── __init__.py │ │ ├── raven_compat │ │ │ ├── __init__.py │ │ │ ├── models.py │ │ │ ├── handlers.py │ │ │ ├── middleware │ │ │ │ ├── __init__.py │ │ │ │ └── wsgi.py │ │ │ ├── management │ │ │ │ ├── __init__.py │ │ │ │ └── commands │ │ │ │ │ ├── __init__.py │ │ │ │ │ └── raven.py │ │ │ └── templatetags │ │ │ │ ├── __init__.py │ │ │ │ └── raven.py │ │ ├── __init__.py │ │ ├── apps.py │ │ ├── logging.py │ │ ├── urls.py │ │ ├── middleware │ │ │ └── wsgi.py │ │ ├── handlers.py │ │ ├── serializers.py │ │ ├── views.py │ │ ├── utils.py │ │ └── resolver.py │ ├── __init__.py │ ├── paste.py │ ├── webpy │ │ ├── utils.py │ │ └── __init__.py │ ├── async.py │ ├── pylons │ │ └── __init__.py │ ├── zconfig │ │ ├── component.xml │ │ └── __init__.py │ ├── zope │ │ └── component.xml │ ├── bottle │ │ ├── utils.py │ │ └── __init__.py │ ├── zerorpc │ │ └── __init__.py │ └── celery │ │ └── __init__.py ├── scripts │ ├── __init__.py │ └── runner.py ├── handlers │ ├── __init__.py │ └── logbook.py ├── utils │ ├── serializer │ │ ├── __init__.py │ │ └── manager.py │ ├── imports.py │ ├── urlparse.py │ ├── testutils.py │ ├── transaction.py │ ├── http.py │ ├── conf.py │ ├── basic.py │ ├── json.py │ ├── encoding.py │ ├── __init__.py │ ├── ssl_match_hostname.py │ └── wsgi.py ├── transport │ ├── exceptions.py │ ├── __init__.py │ ├── requests.py │ ├── threaded_requests.py │ ├── base.py │ ├── eventlet.py │ ├── gevent.py │ ├── http.py │ ├── tornado.py │ ├── twisted.py │ └── registry.py ├── exceptions.py ├── __init__.py ├── conf │ ├── defaults.py │ └── __init__.py ├── versioning.py └── middleware.py ├── .python-version-example ├── .craft.yml ├── codecov.yml ├── hooks ├── pre-commit └── pre-commit.flake8 ├── MANIFEST.in ├── .bandit.yml ├── ci ├── runtox.sh ├── test └── setup ├── .gitignore ├── .bumpversion.cfg ├── setup.cfg ├── scripts └── bump-version.sh ├── .vscode └── settings.json ├── Makefile ├── .travis.yml ├── LICENSE └── conftest.py /.gitmodules: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/base/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/conf/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/utils/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/context/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/contrib/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/contrib/async/tests.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/events/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/functional/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/handlers/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/middleware/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/processors/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/transport/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/utils/json/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/utils/wsgi/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/versioning/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/breadcrumbs/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/contrib/async/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/contrib/bottle/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/contrib/django/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/contrib/flask/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/contrib/pylons/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/contrib/sanic/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/contrib/tornado/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/contrib/webpy/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/contrib/zconfig/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/contrib/zerorpc/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/utils/encoding/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/utils/stacks/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | CHANGES merge=union 2 | -------------------------------------------------------------------------------- /examples/django_110/app/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | VERSION = 1.0 2 | -------------------------------------------------------------------------------- /tests/handlers/logbook/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/handlers/logging/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/transport/gevent/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/transport/requests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/transport/threaded/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/transport/tornado/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/contrib/django/management/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/contrib/django/templates/404.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/contrib/django/templates/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/django_110/app/management/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/contrib/django/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/django_110/app/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | http://github.com/getsentry/raven-python/contributors -------------------------------------------------------------------------------- /tests/templates/sentry-tests/error.html: -------------------------------------------------------------------------------- 1 | {% invalid template tag %} -------------------------------------------------------------------------------- /examples/django_110/requirements.txt: -------------------------------------------------------------------------------- 1 | -e ../../ 2 | Django>=1.10,<1.11 3 | -------------------------------------------------------------------------------- /raven/contrib/django/management/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, print_function 2 | -------------------------------------------------------------------------------- /.python-version-example: -------------------------------------------------------------------------------- 1 | 2.6.9 2 | 2.7.13 3 | 3.2.6 4 | 3.3.6 5 | 3.4.6 6 | 3.5.3 7 | 3.6.1 8 | pypy2.7-5.8.0 9 | -------------------------------------------------------------------------------- /tests/utils/stacks/utf8_file.txt: -------------------------------------------------------------------------------- 1 | This is an UTF8 file with éùàØa 2 | 3 | Some code here 4 | 5 | lorem ipsum 6 | -------------------------------------------------------------------------------- /examples/django_110/db.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getsentry/raven-python/HEAD/examples/django_110/db.sqlite3 -------------------------------------------------------------------------------- /tests/contrib/django/models.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | from django.db import models 4 | 5 | 6 | class MyTestModel(models.Model): 7 | pass 8 | -------------------------------------------------------------------------------- /examples/django_110/app/views.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | logger = logging.getLogger('app') 4 | 5 | 6 | def home(request): 7 | logger.info('Doing some division') 8 | 1 / 0 9 | -------------------------------------------------------------------------------- /.craft.yml: -------------------------------------------------------------------------------- 1 | --- 2 | minVersion: '0.7.0' 3 | changelogPolicy: simple 4 | github: 5 | owner: getsentry 6 | repo: raven-python 7 | targets: 8 | - name: pypi 9 | - name: github 10 | -------------------------------------------------------------------------------- /tests/contrib/django/templates/error.html: -------------------------------------------------------------------------------- 1 | 1 2 | 2 3 | 3 4 | 4 5 | 5 6 | 6 7 | 7 8 | 8 9 | 9 10 | {% invalid template tag %} 11 | 11 12 | 12 13 | 13 14 | 14 15 | 15 16 | 16 17 | 17 18 | 18 19 | 19 20 | 20 21 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | project: 4 | default: 5 | target: 0% 6 | patch: 7 | default: 8 | target: 0% 9 | ignore: 10 | - hooks/.* 11 | - ci/.* 12 | 13 | comment: false 14 | -------------------------------------------------------------------------------- /hooks/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | failed=0 4 | for filename in hooks/pre-commit.*; do 5 | if [ -f "$filename" ]; then 6 | if ! $filename; then 7 | failed=1 8 | fi 9 | fi 10 | done 11 | 12 | exit $failed 13 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include setup.py conftest.py README.rst MANIFEST.in LICENSE *.txt 2 | recursive-include raven/contrib/zope *.xml 3 | recursive-include raven/contrib/zconfig *.xml 4 | recursive-include raven/data * 5 | graft tests 6 | global-exclude *~ 7 | -------------------------------------------------------------------------------- /raven/scripts/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | raven.scripts 3 | ~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2012 by the Sentry Team, see AUTHORS for more details. 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | from __future__ import absolute_import 9 | -------------------------------------------------------------------------------- /raven/contrib/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | raven.contrib 3 | ~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2010-2013 by the Sentry Team, see AUTHORS for more details. 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | from __future__ import absolute_import 9 | -------------------------------------------------------------------------------- /raven/handlers/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | raven.handlers 3 | ~~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details. 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | from __future__ import absolute_import 9 | -------------------------------------------------------------------------------- /examples/django_110/app/management/commands/example.py: -------------------------------------------------------------------------------- 1 | from django.core.management.base import BaseCommand 2 | 3 | 4 | class Command(BaseCommand): 5 | help = 'Examples' 6 | 7 | def handle(self, *args, **options): 8 | raise Exception('oops') 9 | -------------------------------------------------------------------------------- /.bandit.yml: -------------------------------------------------------------------------------- 1 | tests: 2 | skips: 3 | - B404 # Ignore warnings about importing subprocess 4 | - B603 # Ignore warnings about calling subprocess.Popen without shell=True 5 | - B607 # Ignore warnings about calling subprocess.Popen without a full path to executable 6 | -------------------------------------------------------------------------------- /ci/runtox.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | if [ -z "$1" ]; then 3 | searchstring="$(echo py$TRAVIS_PYTHON_VERSION | tr -d . | sed -e 's/pypypy/pypy/g' -e 's/-dev//g')" 4 | else 5 | searchstring="$1" 6 | fi 7 | 8 | exec tox -e $(tox -l | grep $searchstring | tr '\n' ',') 9 | -------------------------------------------------------------------------------- /raven/contrib/django/templatetags/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | raven.contrib.django.templatetags 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2010-2013 by the Sentry Team, see AUTHORS for more details. 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | from __future__ import absolute_import 9 | -------------------------------------------------------------------------------- /raven/contrib/django/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | raven.contrib.django.management.commands 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2010-2013 by the Sentry Team, see AUTHORS for more details 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | from __future__ import absolute_import, print_function 9 | -------------------------------------------------------------------------------- /raven/contrib/django/celery/tasks.py: -------------------------------------------------------------------------------- 1 | """ 2 | raven.contrib.django.celery.tasks 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details. 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | from __future__ import absolute_import 9 | 10 | from raven.contrib.django.models import client # NOQA 11 | -------------------------------------------------------------------------------- /raven/contrib/django/raven_compat/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | raven.contrib.django.raven_compat 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details. 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | 9 | from __future__ import absolute_import 10 | 11 | from raven.contrib.django import * # NOQA 12 | -------------------------------------------------------------------------------- /raven/contrib/django/raven_compat/models.py: -------------------------------------------------------------------------------- 1 | """ 2 | raven.contrib.django.raven_compat.models 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details. 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | 9 | from __future__ import absolute_import 10 | 11 | from raven.contrib.django.models import * # NOQA 12 | -------------------------------------------------------------------------------- /raven/contrib/django/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | raven.contrib.django 3 | ~~~~~~~~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details. 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | from __future__ import absolute_import 9 | 10 | default_app_config = 'raven.contrib.django.apps.RavenConfig' 11 | 12 | from .client import DjangoClient # NOQA 13 | -------------------------------------------------------------------------------- /raven/contrib/django/apps.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import 3 | 4 | from django.apps import AppConfig 5 | 6 | 7 | class RavenConfig(AppConfig): 8 | name = 'raven.contrib.django' 9 | label = 'raven_contrib_django' 10 | verbose_name = 'Raven' 11 | 12 | def ready(self): 13 | from .models import initialize 14 | initialize() 15 | -------------------------------------------------------------------------------- /ci/test: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | if [[ $TEST_SUITE == 'django' ]]; then 4 | TEST_PATH=tests/contrib/django 5 | elif [[ $TEST_SUITE == 'flask' ]]; then 6 | TEST_PATH=tests/contrib/flask 7 | elif [[ $TEST_SUITE == 'celery' ]]; then 8 | TEST_PATH=tests/contrib/test_celery.py 9 | else 10 | TEST_PATH=tests 11 | fi 12 | 13 | coverage run --source=raven -m py.test $TEST_PATH --timeout 10 14 | -------------------------------------------------------------------------------- /raven/contrib/django/raven_compat/handlers.py: -------------------------------------------------------------------------------- 1 | """ 2 | raven.contrib.django.raven_compat.handlers 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details. 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | 9 | from __future__ import absolute_import 10 | 11 | from raven.contrib.django.handlers import * # NOQA 12 | -------------------------------------------------------------------------------- /raven/utils/serializer/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | raven.utils.serializer 3 | ~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details. 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | from __future__ import absolute_import 9 | 10 | from raven.utils.serializer.base import * # NOQA 11 | from raven.utils.serializer.manager import * # NOQA 12 | -------------------------------------------------------------------------------- /raven/contrib/django/raven_compat/middleware/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | raven.contrib.django.raven_compat.middleware 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details. 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | 9 | from __future__ import absolute_import 10 | 11 | from raven.contrib.django.middleware import * # NOQA 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.log 3 | *.egg 4 | *.db 5 | *.pid 6 | .python-version 7 | .coverage* 8 | .DS_Store 9 | .tox 10 | pip-log.txt 11 | /.tox 12 | /*.egg-info 13 | /build 14 | /cover 15 | /dist 16 | /example_project/local_settings.py 17 | /docs/_build 18 | /sentry_index/ 19 | /sentry_test_index 20 | /example_project/*.db 21 | bin/ 22 | include/ 23 | lib/ 24 | .cache 25 | .idea 26 | .eggs 27 | venv 28 | .vscode/tags 29 | -------------------------------------------------------------------------------- /raven/contrib/django/raven_compat/management/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | raven.contrib.django.raven_compat.management 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2010-2013 by the Sentry Team, see AUTHORS for more details 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | from __future__ import absolute_import, print_function 9 | 10 | from raven.contrib.django.management import * # NOQA 11 | -------------------------------------------------------------------------------- /raven/contrib/django/raven_compat/middleware/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | raven.contrib.django.raven_compat.middleware.wsgi 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details. 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | 9 | from __future__ import absolute_import 10 | 11 | from raven.contrib.django.middleware.wsgi import * # NOQA 12 | -------------------------------------------------------------------------------- /raven/contrib/django/raven_compat/templatetags/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | raven.contrib.django.raven_compat.templatetags 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2010-2013 by the Sentry Team, see AUTHORS for more details. 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | 9 | from __future__ import absolute_import 10 | 11 | from raven.contrib.django.templatetags import * # NOQA 12 | -------------------------------------------------------------------------------- /raven/contrib/django/raven_compat/templatetags/raven.py: -------------------------------------------------------------------------------- 1 | """ 2 | raven.contrib.django.raven_compat.templatetags.raven 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2010-2013 by the Sentry Team, see AUTHORS for more details. 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | 9 | from __future__ import absolute_import 10 | 11 | from raven.contrib.django.templatetags.raven import * # NOQA 12 | -------------------------------------------------------------------------------- /raven/contrib/django/raven_compat/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | raven.contrib.django.raven_compat.management.commands 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2010-2013 by the Sentry Team, see AUTHORS for more details 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | from __future__ import absolute_import, print_function 9 | 10 | from raven.contrib.django.management.commands import * # NOQA 11 | -------------------------------------------------------------------------------- /raven/contrib/django/raven_compat/management/commands/raven.py: -------------------------------------------------------------------------------- 1 | """ 2 | raven.contrib.django.raven_compat.management.commands.raven 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2010-2013 by the Sentry Team, see AUTHORS for more details 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | from __future__ import absolute_import, print_function 9 | 10 | from raven.contrib.django.management.commands.raven import * # NOQA 11 | -------------------------------------------------------------------------------- /raven/contrib/paste.py: -------------------------------------------------------------------------------- 1 | """ 2 | raven.contrib.paste 3 | ~~~~~~~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details. 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | from __future__ import absolute_import 9 | 10 | from raven.middleware import Sentry 11 | from raven.base import Client 12 | 13 | 14 | def sentry_filter_factory(app, global_conf, **kwargs): 15 | client = Client(**kwargs) 16 | return Sentry(app, client) 17 | -------------------------------------------------------------------------------- /examples/django_110/app/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for django_110 project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.10/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "app.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /raven/utils/imports.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | from raven.utils.compat import PY2 4 | 5 | 6 | def import_string(key): 7 | # HACK(dcramer): Ensure a unicode key is still importable 8 | if PY2: 9 | key = str(key) 10 | 11 | if '.' not in key: 12 | return __import__(key) 13 | 14 | module_name, class_name = key.rsplit('.', 1) 15 | module = __import__(module_name, {}, {}, [class_name], 0) 16 | return getattr(module, class_name) 17 | -------------------------------------------------------------------------------- /tests/contrib/pylons/tests.py: -------------------------------------------------------------------------------- 1 | from raven.utils.testutils import TestCase 2 | from raven.contrib.pylons import Sentry 3 | 4 | 5 | def example_app(environ, start_response): 6 | raise ValueError('hello world') 7 | 8 | 9 | class MiddlewareTest(TestCase): 10 | def setUp(self): 11 | self.app = example_app 12 | 13 | def test_init(self): 14 | config = { 15 | 'sentry.dsn': 'http://public:secret@example.com/1', 16 | } 17 | middleware = Sentry(self.app, config) 18 | -------------------------------------------------------------------------------- /.bumpversion.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | commit = False 3 | tag = False 4 | tag_name = {new_version} 5 | current_version = 6.10.0 6 | parse = (?P\d+)\.(?P\d+)\.(?P\d+)(\.(?P[a-z]+)(?P\d+))? 7 | serialize = 8 | {major}.{minor}.{patch}.{release}{dev} 9 | {major}.{minor}.{patch} 10 | message = Release: {current_version} → {new_version} 11 | 12 | [bumpversion:file:raven/__init__.py] 13 | 14 | [bumpversion:part:release] 15 | optional_value = production 16 | values = 17 | dev 18 | production 19 | 20 | -------------------------------------------------------------------------------- /tests/utils/tests.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from raven.utils.testutils import TestCase 4 | 5 | import raven 6 | from raven.utils import get_versions 7 | 8 | 9 | class GetVersionsTest(TestCase): 10 | def test_exact_match(self): 11 | versions = get_versions(['raven']) 12 | self.assertEquals(versions.get('raven'), raven.VERSION) 13 | 14 | def test_parent_match(self): 15 | versions = get_versions(['raven.contrib.django']) 16 | self.assertEquals(versions.get('raven'), raven.VERSION) 17 | -------------------------------------------------------------------------------- /raven/contrib/django/logging.py: -------------------------------------------------------------------------------- 1 | """ 2 | raven.contrib.django.logging 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details. 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | 9 | from __future__ import absolute_import 10 | 11 | import warnings 12 | 13 | warnings.warn('raven.contrib.django.logging is deprecated. Use raven.contrib.django.handlers instead.', DeprecationWarning) 14 | 15 | from raven.contrib.django.handlers import SentryHandler # NOQA 16 | 17 | __all__ = ('SentryHandler',) 18 | -------------------------------------------------------------------------------- /raven/utils/urlparse.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | # Can't use the compat module here because of an import loop 4 | try: 5 | import urlparse as _urlparse 6 | except ImportError: 7 | from urllib import parse as _urlparse 8 | 9 | 10 | def register_scheme(scheme): 11 | for method in filter(lambda s: s.startswith('uses_'), dir(_urlparse)): 12 | uses = getattr(_urlparse, method) 13 | if scheme not in uses: 14 | uses.append(scheme) 15 | 16 | 17 | urlparse = _urlparse.urlparse 18 | parse_qsl = _urlparse.parse_qsl 19 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [tool:pytest] 2 | python_files=test*.py 3 | addopts=--tb=native -p no:doctest -p no:logging --cov=raven 4 | norecursedirs=raven build bin dist htmlcov hooks node_modules .* {args} 5 | DJANGO_SETTINGS_MODULE = tests.contrib.django.settings 6 | python_paths = tests 7 | flake8-ignore = 8 | tests/ ALL 9 | 10 | [flake8] 11 | ignore = F999,E501,E128,E124,E402,W503,E731,F841,D100,D101,D102,D103,D104,D105,D107,D200,D201,D205,D400,D401,D402,D403,I100,I101, I201, I202 12 | max-line-length = 100 13 | exclude = .tox,.git,tests 14 | 15 | [bdist_wheel] 16 | universal = 1 17 | -------------------------------------------------------------------------------- /raven/contrib/django/templatetags/raven.py: -------------------------------------------------------------------------------- 1 | """ 2 | raven.contrib.django.templatetags.raven 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2010-2013 by the Sentry Team, see AUTHORS for more details. 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | 9 | from __future__ import absolute_import 10 | 11 | from django import template 12 | 13 | register = template.Library() 14 | 15 | 16 | @register.simple_tag 17 | def sentry_public_dsn(scheme=None): 18 | from raven.contrib.django.models import client 19 | return client.get_public_dsn(scheme) or '' 20 | -------------------------------------------------------------------------------- /scripts/bump-version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eux 3 | 4 | SCRIPT_DIR="$( dirname "$0" )" 5 | cd $SCRIPT_DIR/.. 6 | 7 | OLD_VERSION="${1}" 8 | NEW_VERSION="${2}" 9 | 10 | echo "Current version: $OLD_VERSION" 11 | echo "Bumping version: $NEW_VERSION" 12 | 13 | function replace() { 14 | ! grep "$2" $3 15 | perl -i -pe "s/$1/$2/g" $3 16 | grep "$2" $3 # verify that replacement was successful 17 | } 18 | 19 | replace "current_version = [0-9.]+" "current_version = $NEW_VERSION" ./.bumpversion.cfg 20 | replace "VERSION = '[0-9.]+'" "VERSION = '$NEW_VERSION'" ./raven/__init__.py 21 | -------------------------------------------------------------------------------- /tests/utils/test_conf.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | from raven.utils.conf import convert_options 4 | 5 | 6 | def test_convert_options_parses_dict(): 7 | options = convert_options({ 8 | 'SENTRY_FOO': 'foo', 9 | 'FOO': 'bar', 10 | 'SENTRY_RELEASE': 'a', 11 | 'SENTRY_IGNORE_EXCEPTIONS': [ 12 | 'b', 13 | ] 14 | }, defaults={'environment': 'production'}) 15 | 16 | assert options['release'] == 'a' 17 | assert options['ignore_exceptions'] == [ 18 | 'b', 19 | ] 20 | assert options['environment'] == 'production' 21 | -------------------------------------------------------------------------------- /raven/contrib/django/celery/models.py: -------------------------------------------------------------------------------- 1 | """ 2 | raven.contrib.django.celery.models 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details. 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | from __future__ import absolute_import 9 | 10 | from django.conf import settings 11 | from django.core.exceptions import ImproperlyConfigured 12 | 13 | if 'djcelery' not in settings.INSTALLED_APPS: 14 | raise ImproperlyConfigured( 15 | "Put 'djcelery' in your INSTALLED_APPS setting in order to use the " 16 | "sentry celery client.") 17 | -------------------------------------------------------------------------------- /raven/transport/exceptions.py: -------------------------------------------------------------------------------- 1 | """ 2 | raven.transport.exceptions 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details. 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | from __future__ import absolute_import 9 | 10 | 11 | class InvalidScheme(ValueError): 12 | """ 13 | Raised when a transport is constructed using a URI which is not 14 | handled by the transport 15 | """ 16 | 17 | 18 | class DuplicateScheme(Exception): 19 | """ 20 | Raised when registering a handler for a particular scheme which 21 | is already registered 22 | """ 23 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | "**/*.pyc": true, 4 | "**/*.min.js": true, 5 | "**/*.js.map": true, 6 | "htmlcov": true, 7 | "build": true, 8 | "*.log": true, 9 | "*.egg-info": true 10 | }, 11 | "files.trimTrailingWhitespace": true, 12 | 13 | "python.linting.pylintEnabled": false, 14 | "python.linting.flake8Enabled": true, 15 | 16 | "python.pythonPath": "${env.WORKON_HOME}/raven/bin/python", 17 | "python.unitTest.pyTestEnabled": true, 18 | "python.unitTest.unittestEnabled": false, 19 | "python.unitTest.nosetestsEnabled": false 20 | } -------------------------------------------------------------------------------- /tests/utils/test_imports.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | import raven 4 | 5 | from raven.utils.compat import text_type 6 | from raven.utils.imports import import_string 7 | 8 | 9 | def test_import_string(): 10 | new_raven = import_string('raven') 11 | assert new_raven is raven 12 | 13 | # this will test unicode on python2 14 | new_raven = import_string(text_type('raven')) 15 | assert new_raven is raven 16 | 17 | new_client = import_string('raven.Client') 18 | assert new_client is raven.Client 19 | 20 | # this will test unicode on python2 21 | new_client = import_string(text_type('raven.Client')) 22 | assert new_client is raven.Client 23 | -------------------------------------------------------------------------------- /raven/contrib/django/urls.py: -------------------------------------------------------------------------------- 1 | """ 2 | raven.contrib.django.urls 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details. 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | from __future__ import absolute_import 9 | 10 | try: 11 | from django.conf.urls import url 12 | except ImportError: 13 | # for Django version less than 1.4 14 | from django.conf.urls.defaults import url # NOQA 15 | 16 | import raven.contrib.django.views 17 | 18 | urlpatterns = ( 19 | url(r'^api/(?P[\w_-]+)/store/$', raven.contrib.django.views.report, name='raven-report'), 20 | url(r'^report/', raven.contrib.django.views.report), 21 | ) 22 | -------------------------------------------------------------------------------- /tests/transport/requests/tests.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | import mock 4 | 5 | from raven.utils.testutils import TestCase 6 | from raven.base import Client 7 | 8 | 9 | class RequestsTransportTest(TestCase): 10 | def setUp(self): 11 | self.client = Client( 12 | dsn="requests+http://some_username:some_password@localhost:8143/1", 13 | ) 14 | 15 | @mock.patch('raven.transport.requests.post') 16 | def test_does_send(self, post): 17 | self.client.captureMessage(message='foo') 18 | self.assertEqual(post.call_count, 1) 19 | expected_url = 'http://localhost:8143/api/1/store/' 20 | self.assertEqual(expected_url, post.call_args[0][0]) 21 | -------------------------------------------------------------------------------- /raven/exceptions.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | from raven.utils.compat import text_type 4 | 5 | 6 | class APIError(Exception): 7 | def __init__(self, message, code=0): 8 | self.code = code 9 | self.message = message 10 | 11 | def __unicode__(self): 12 | return text_type("%s: %s" % (self.message, self.code)) 13 | 14 | 15 | class RateLimited(APIError): 16 | def __init__(self, message, retry_after=0): 17 | self.retry_after = retry_after 18 | super(RateLimited, self).__init__(message, 429) 19 | 20 | 21 | class InvalidGitRepository(Exception): 22 | pass 23 | 24 | 25 | class ConfigurationError(ValueError): 26 | pass 27 | 28 | 29 | class InvalidDsn(ConfigurationError): 30 | pass 31 | -------------------------------------------------------------------------------- /tests/contrib/django/management/commands/test_raven.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | import django 4 | 5 | from django.core.management import call_command 6 | from django.test import TestCase 7 | from mock import patch 8 | 9 | from raven.contrib.django.models import client 10 | 11 | DJANGO_18 = django.VERSION >= (1, 8, 0) 12 | 13 | 14 | class RavenCommandTest(TestCase): 15 | @patch('raven.contrib.django.management.commands.raven.send_test_message') 16 | def test_basic(self, mock_send_test_message): 17 | call_command('raven', 'test') 18 | 19 | mock_send_test_message.assert_called_once_with( 20 | client, { 21 | 'tags': None, 22 | 'data': None, 23 | } 24 | ) 25 | -------------------------------------------------------------------------------- /tests/contrib/django/middleware.py: -------------------------------------------------------------------------------- 1 | try: 2 | # Django >= 1.10 3 | from django.utils.deprecation import MiddlewareMixin 4 | except ImportError: 5 | # Not required for Django <= 1.9, see: 6 | # https://docs.djangoproject.com/en/1.10/topics/http/middleware/#upgrading-pre-django-1-10-style-middleware 7 | MiddlewareMixin = object 8 | 9 | 10 | class BrokenRequestMiddleware(MiddlewareMixin): 11 | def process_request(self, request): 12 | raise ImportError('request') 13 | 14 | 15 | class BrokenResponseMiddleware(MiddlewareMixin): 16 | def process_response(self, request, response): 17 | raise ImportError('response') 18 | 19 | 20 | class BrokenViewMiddleware(MiddlewareMixin): 21 | def process_view(self, request, func, args, kwargs): 22 | raise ImportError('view') 23 | -------------------------------------------------------------------------------- /raven/utils/testutils.py: -------------------------------------------------------------------------------- 1 | """ 2 | raven.utils.testutils 3 | ~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2010-2013 by the Sentry Team, see AUTHORS for more details. 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | from __future__ import absolute_import 9 | 10 | import raven 11 | 12 | from exam import Exam 13 | 14 | try: 15 | from unittest2 import TestCase as BaseTestCase 16 | except ImportError: 17 | from unittest import TestCase as BaseTestCase # NOQA 18 | 19 | 20 | class TestCase(Exam, BaseTestCase): 21 | pass 22 | 23 | 24 | class InMemoryClient(raven.Client): 25 | def __init__(self, **kwargs): 26 | self.events = [] 27 | super(InMemoryClient, self).__init__(**kwargs) 28 | 29 | def is_enabled(self): 30 | return True 31 | 32 | def send(self, **kwargs): 33 | self.events.append(kwargs) 34 | -------------------------------------------------------------------------------- /raven/contrib/webpy/utils.py: -------------------------------------------------------------------------------- 1 | """ 2 | raven.contrib.webpy.utils 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details. 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | from __future__ import absolute_import 9 | 10 | import web 11 | 12 | from raven.utils.wsgi import get_headers, get_environ 13 | 14 | 15 | def get_data_from_request(): 16 | """Returns request data extracted from web.ctx.""" 17 | return { 18 | 'request': { 19 | 'url': '%s://%s%s' % (web.ctx['protocol'], web.ctx['host'], web.ctx['path']), 20 | 'query_string': web.ctx.query, 21 | 'method': web.ctx.method, 22 | 'data': web.data(), 23 | 'headers': dict(get_headers(web.ctx.environ)), 24 | 'env': dict(get_environ(web.ctx.environ)), 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /raven/transport/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | raven.transport 3 | ~~~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details. 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | # TODO: deprecate this namespace and force non-default (sync + threaded) to 9 | # manually import/register transports somehow 10 | from __future__ import absolute_import 11 | 12 | from raven.transport.base import * # NOQA 13 | from raven.transport.eventlet import * # NOQA 14 | from raven.transport.exceptions import * # NOQA 15 | from raven.transport.gevent import * # NOQA 16 | from raven.transport.http import * # NOQA 17 | from raven.transport.requests import * # NOQA 18 | from raven.transport.registry import * # NOQA 19 | from raven.transport.twisted import * # NOQA 20 | from raven.transport.threaded import * # NOQA 21 | from raven.transport.tornado import * # NOQA 22 | -------------------------------------------------------------------------------- /raven/contrib/django/middleware/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | raven.contrib.django.middleware.wsgi 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details. 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | from __future__ import absolute_import 9 | 10 | from raven.middleware import Sentry 11 | from raven.utils import memoize 12 | 13 | 14 | class Sentry(Sentry): 15 | """ 16 | Identical to the default WSGI middleware except that 17 | the client comes dynamically via ``get_client 18 | 19 | >>> from raven.contrib.django.middleware.wsgi import Sentry 20 | >>> application = Sentry(application) 21 | """ 22 | 23 | def __init__(self, application): 24 | self.application = application 25 | 26 | @memoize 27 | def client(self): 28 | from raven.contrib.django.models import client 29 | return client 30 | -------------------------------------------------------------------------------- /examples/django_110/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "app.settings") 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError: 10 | # The above import may fail for some other reason. Ensure that the 11 | # issue is really that Django is missing to avoid masking other 12 | # exceptions on Python 2. 13 | try: 14 | import django # NOQA 15 | except ImportError: 16 | raise ImportError( 17 | "Couldn't import Django. Are you sure it's installed and " 18 | "available on your PYTHONPATH environment variable? Did you " 19 | "forget to activate a virtual environment?" 20 | ) 21 | raise 22 | execute_from_command_line(sys.argv) 23 | -------------------------------------------------------------------------------- /examples/django_110/app/urls.py: -------------------------------------------------------------------------------- 1 | """django_110 URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/1.10/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.conf.urls import url, include 14 | 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) 15 | """ 16 | from django.conf.urls import url 17 | from django.contrib import admin 18 | 19 | from .views import home 20 | 21 | urlpatterns = [ 22 | url(r'^$', home), 23 | url(r'^admin/', admin.site.urls), 24 | ] 25 | -------------------------------------------------------------------------------- /tests/utils/test_transaction.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | from raven.utils.transaction import TransactionStack 4 | 5 | 6 | def test_simple(): 7 | stack = TransactionStack() 8 | 9 | stack.push('foo') 10 | 11 | assert len(stack) == 1 12 | assert stack.peek() == 'foo' 13 | 14 | bar = stack.push(['bar']) 15 | 16 | assert len(stack) == 2 17 | assert stack.peek() == ['bar'] 18 | 19 | stack.push({'baz': True}) 20 | 21 | assert len(stack) == 3 22 | assert stack.peek() == {'baz': True} 23 | 24 | stack.pop(bar) 25 | 26 | assert len(stack) == 1 27 | assert stack.peek() == 'foo' 28 | 29 | stack.pop() 30 | 31 | assert len(stack) == 0 32 | assert stack.peek() == None 33 | 34 | 35 | def test_context_manager(): 36 | stack = TransactionStack() 37 | 38 | with stack('foo'): 39 | assert stack.peek() == 'foo' 40 | 41 | assert stack.peek() is None 42 | -------------------------------------------------------------------------------- /raven/contrib/async.py: -------------------------------------------------------------------------------- 1 | """ 2 | raven.contrib.async 3 | ~~~~~~~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details. 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | from __future__ import absolute_import 9 | 10 | import warnings 11 | 12 | from raven.base import Client 13 | from raven.transport.threaded import AsyncWorker 14 | 15 | 16 | class AsyncClient(Client): 17 | """ 18 | This client uses a single background thread to dispatch errors. 19 | """ 20 | 21 | def __init__(self, worker=None, *args, **kwargs): 22 | warnings.warn('AsyncClient is deprecated. Use the threaded+http transport instead.', DeprecationWarning) 23 | self.worker = worker or AsyncWorker() 24 | super(AsyncClient, self).__init__(*args, **kwargs) 25 | 26 | def send_sync(self, **kwargs): 27 | super(AsyncClient, self).send(**kwargs) 28 | 29 | def send(self, **kwargs): 30 | self.worker.queue(self.send_sync, **kwargs) 31 | -------------------------------------------------------------------------------- /tests/functional/tests.py: -------------------------------------------------------------------------------- 1 | import fnmatch 2 | import os 3 | 4 | from subprocess import call 5 | from raven.utils.testutils import BaseTestCase as TestCase 6 | 7 | 8 | ROOT = os.path.normpath( 9 | os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, 'raven')) 10 | 11 | 12 | def find_files(root, pattern='*'): 13 | matches = [] 14 | for root, _, filenames in os.walk(root): 15 | for filename in fnmatch.filter(filenames, pattern): 16 | matches.append(os.path.join(root, filename)) 17 | return matches 18 | 19 | 20 | class FutureImportsTest(TestCase): 21 | def test_absolute_import(self): 22 | string = 'from __future__ import absolute_import' 23 | kwargs = { 24 | 'stdout': open('/dev/null', 'a'), 25 | 'stderr': open('/dev/null', 'a'), 26 | } 27 | for filename in find_files(ROOT, '*.py'): 28 | assert not call(['grep', string, filename], **kwargs), \ 29 | "Missing %r in %s" % (string, filename[len(ROOT) - 5:]) 30 | -------------------------------------------------------------------------------- /raven/contrib/pylons/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | raven.contrib.pylons 3 | ~~~~~~~~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details. 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | from __future__ import absolute_import 9 | 10 | from raven.middleware import Sentry as Middleware 11 | from raven.base import Client 12 | 13 | 14 | def list_from_setting(config, setting): 15 | value = config.get(setting) 16 | if not value: 17 | return None 18 | return value.split() 19 | 20 | 21 | class Sentry(Middleware): 22 | def __init__(self, app, config, client_cls=Client): 23 | client = client_cls( 24 | dsn=config.get('sentry.dsn'), 25 | name=config.get('sentry.name'), 26 | site=config.get('sentry.site'), 27 | include_paths=list_from_setting(config, 'sentry.include_paths'), 28 | exclude_paths=list_from_setting(config, 'sentry.exclude_paths'), 29 | ) 30 | super(Sentry, self).__init__(app, client) 31 | -------------------------------------------------------------------------------- /tests/versioning/tests.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | import os.path 4 | import pytest 5 | import subprocess 6 | 7 | from raven.utils.compat import string_types 8 | from raven.versioning import fetch_git_sha, fetch_package_version 9 | 10 | 11 | # Python 2.6 does not contain subprocess.check_output 12 | def check_output(cmd, **kwargs): 13 | return subprocess.Popen( 14 | cmd, 15 | stdout=subprocess.PIPE, 16 | **kwargs 17 | ).communicate()[0] 18 | 19 | 20 | @pytest.mark.has_git_requirements 21 | def test_fetch_git_sha(project_root): 22 | result = fetch_git_sha(project_root) 23 | assert result is not None 24 | assert len(result) == 40 25 | assert isinstance(result, string_types) 26 | assert result == check_output( 27 | 'git rev-parse --verify HEAD', shell=True, cwd=project_root 28 | ).decode('latin1').strip() 29 | 30 | 31 | def test_fetch_package_version(): 32 | result = fetch_package_version('raven') 33 | assert result is not None 34 | assert isinstance(result, string_types) 35 | -------------------------------------------------------------------------------- /raven/contrib/zconfig/component.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | bootstrap: 2 | pip install -e "file://`pwd`#egg=raven[tests]" 3 | make setup-git 4 | 5 | test: bootstrap lint 6 | @echo "Running Python tests" 7 | py.test -f tests 8 | @echo "" 9 | 10 | lint: 11 | @echo "Linting Python files" 12 | PYFLAKES_NODOCTEST=1 flake8 raven || exit 1 13 | @echo "" 14 | 15 | coverage: 16 | coverage run runtests.py --include=raven/* && \ 17 | coverage html --omit=*/migrations/* -d cover 18 | 19 | setup-git: 20 | git config branch.autosetuprebase always 21 | cd .git/hooks && ln -sf ../../hooks/* ./ 22 | 23 | clean: 24 | rm -rf dist build 25 | 26 | publish: clean 27 | python setup.py sdist bdist_wheel upload 28 | 29 | dist: clean 30 | python setup.py sdist bdist_wheel 31 | 32 | install-zeus-cli: 33 | npm install -g @zeus-ci/cli 34 | 35 | travis-upload-dist: dist install-zeus-cli 36 | zeus upload -t "application/zip+wheel" dist/* \ 37 | || [[ ! "$(TRAVIS_BRANCH)" =~ ^release/ ]] 38 | 39 | update-ca: 40 | curl -sSL https://mkcert.org/generate/ -o raven/data/cacert.pem 41 | 42 | .PHONY: bootstrap test lint coverage setup-git publish update-ca dist clean install-zeus-cli travis-upload-dist 43 | -------------------------------------------------------------------------------- /raven/contrib/django/celery/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | raven.contrib.django.celery 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details. 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | from __future__ import absolute_import 9 | 10 | from raven.contrib.django.client import DjangoClient 11 | try: 12 | from celery.task import task 13 | except ImportError: 14 | from celery.decorators import task # NOQA 15 | 16 | 17 | class CeleryClient(DjangoClient): 18 | def send_integrated(self, kwargs): 19 | return send_raw_integrated.delay(kwargs) 20 | 21 | def send_encoded(self, *args, **kwargs): 22 | return send_raw.delay(*args, **kwargs) 23 | 24 | 25 | @task(routing_key='sentry') 26 | def send_raw_integrated(kwargs): 27 | from raven.contrib.django.models import get_client 28 | super(DjangoClient, get_client()).send_integrated(kwargs) 29 | 30 | 31 | @task(routing_key='sentry') 32 | def send_raw(*args, **kwargs): 33 | from raven.contrib.django.models import get_client 34 | super(DjangoClient, get_client()).send_encoded(*args, **kwargs) 35 | -------------------------------------------------------------------------------- /tests/transport/gevent/tests.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | import mock 4 | import time 5 | import socket 6 | import gevent.monkey 7 | 8 | try: 9 | from importlib import reload 10 | except ImportError: 11 | from imp import reload 12 | 13 | from raven.utils.testutils import TestCase 14 | from raven.base import Client 15 | from raven.transport.gevent import GeventedHTTPTransport 16 | 17 | 18 | class GeventTransportTest(TestCase): 19 | def setUp(self): 20 | gevent.monkey.patch_socket() 21 | self.addCleanup(reload, socket) 22 | gevent.monkey.patch_time() 23 | self.addCleanup(reload, time) 24 | self.client = Client( 25 | dsn="gevent+http://some_username:some_password@localhost:8143/1", 26 | ) 27 | 28 | @mock.patch.object(GeventedHTTPTransport, '_done') 29 | @mock.patch('raven.transport.http.HTTPTransport.send') 30 | def test_does_send(self, send, done): 31 | self.client.captureMessage(message='foo') 32 | time.sleep(0) 33 | self.assertEqual(send.call_count, 1) 34 | time.sleep(0) 35 | self.assertEquals(done.call_count, 1) 36 | -------------------------------------------------------------------------------- /raven/contrib/zope/component.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /raven/transport/requests.py: -------------------------------------------------------------------------------- 1 | """ 2 | raven.transport.requests 3 | ~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details. 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | from __future__ import absolute_import 9 | 10 | from raven.transport.http import HTTPTransport 11 | 12 | try: 13 | import requests 14 | has_requests = True 15 | except ImportError: 16 | has_requests = False 17 | 18 | 19 | class RequestsHTTPTransport(HTTPTransport): 20 | 21 | scheme = ['requests+http', 'requests+https'] 22 | 23 | def __init__(self, *args, **kwargs): 24 | if not has_requests: 25 | raise ImportError('RequestsHTTPTransport requires requests.') 26 | 27 | super(RequestsHTTPTransport, self).__init__(*args, **kwargs) 28 | 29 | def send(self, url, data, headers): 30 | if self.verify_ssl: 31 | # If SSL verification is enabled use the provided CA bundle to 32 | # perform the verification. 33 | self.verify_ssl = self.ca_certs 34 | requests.post(url, data=data, headers=headers, 35 | verify=self.verify_ssl, timeout=self.timeout) 36 | -------------------------------------------------------------------------------- /raven/contrib/django/handlers.py: -------------------------------------------------------------------------------- 1 | """ 2 | raven.contrib.django.handlers 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details. 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | 9 | from __future__ import absolute_import 10 | 11 | import logging 12 | 13 | from raven.handlers.logging import SentryHandler as BaseSentryHandler 14 | from raven.utils import memoize 15 | 16 | 17 | class SentryHandler(BaseSentryHandler): 18 | def __init__(self, *args, **kwargs): 19 | # TODO(dcramer): we'd like to avoid this duplicate code, but we need 20 | # to currently defer loading client due to Django loading patterns. 21 | self.tags = kwargs.pop('tags', None) 22 | 23 | logging.Handler.__init__(self, level=kwargs.get('level', logging.NOTSET)) 24 | 25 | @memoize 26 | def client(self): 27 | # Import must be lazy for deffered Django loading 28 | from raven.contrib.django.models import client 29 | return client 30 | 31 | def _emit(self, record): 32 | request = getattr(record, 'request', None) 33 | return super(SentryHandler, self)._emit(record, request=request) 34 | -------------------------------------------------------------------------------- /tests/contrib/django/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import django 4 | 5 | from raven.contrib.django.resolver import RouteResolver 6 | 7 | try: 8 | from django.conf.urls import url, include 9 | except ImportError: 10 | # for Django version less than 1.4 11 | from django.conf.urls.defaults import url, include 12 | 13 | 14 | @pytest.fixture 15 | def route_resolver(): 16 | return RouteResolver() 17 | 18 | 19 | @pytest.fixture 20 | def urlconf(): 21 | if django.VERSION < (1, 9): 22 | included_url_conf = ( 23 | url(r'^foo/bar/(?P[\w]+)', lambda x: ''), 24 | ), '', '' 25 | else: 26 | included_url_conf = (( 27 | url(r'^foo/bar/(?P[\w]+)', lambda x: ''), 28 | ), '') 29 | 30 | if django.VERSION >= (2, 0): 31 | from django.urls import path, re_path 32 | 33 | example_url_conf = ( 34 | re_path(r'^api/(?P[\w_-]+)/store/$', lambda x: ''), 35 | re_path(r'^report/', lambda x: ''), 36 | re_path(r'^example/', include(included_url_conf)), 37 | path('api/v2//store/', lambda x: '') 38 | ) 39 | return example_url_conf 40 | -------------------------------------------------------------------------------- /raven/contrib/bottle/utils.py: -------------------------------------------------------------------------------- 1 | """ 2 | raven.contrib.bottle.utils 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details. 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | from __future__ import absolute_import 9 | 10 | import logging 11 | from raven.utils.compat import urlparse 12 | 13 | from raven.utils.wsgi import get_headers, get_environ 14 | 15 | logger = logging.getLogger(__name__) 16 | 17 | 18 | def get_data_from_request(request): 19 | urlparts = urlparse.urlsplit(request.url) 20 | 21 | try: 22 | form_dict = request.forms.dict 23 | # we only are about the most recent one 24 | formdata = dict([(k, form_dict[k][-1]) for k in form_dict]) 25 | except Exception: 26 | formdata = {} 27 | 28 | data = { 29 | 'request': { 30 | 'url': '%s://%s%s' % (urlparts.scheme, urlparts.netloc, urlparts.path), 31 | 'query_string': urlparts.query, 32 | 'method': request.method, 33 | 'data': formdata, 34 | 'headers': dict(get_headers(request.environ)), 35 | 'env': dict(get_environ(request.environ)), 36 | } 37 | } 38 | 39 | return data 40 | -------------------------------------------------------------------------------- /raven/transport/threaded_requests.py: -------------------------------------------------------------------------------- 1 | """ 2 | raven.transport.threaded_requests 3 | ~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details. 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | from __future__ import absolute_import 9 | 10 | from raven.transport.base import AsyncTransport 11 | from raven.transport import RequestsHTTPTransport 12 | from raven.transport.threaded import AsyncWorker 13 | 14 | 15 | class ThreadedRequestsHTTPTransport(AsyncTransport, RequestsHTTPTransport): 16 | 17 | scheme = ['threaded+requests+http', 'threaded+requests+https'] 18 | 19 | def get_worker(self): 20 | if not hasattr(self, '_worker'): 21 | self._worker = AsyncWorker() 22 | return self._worker 23 | 24 | def send_sync(self, url, data, headers, success_cb, failure_cb): 25 | try: 26 | super(ThreadedRequestsHTTPTransport, self).send(url, data, headers) 27 | except Exception as e: 28 | failure_cb(e) 29 | else: 30 | success_cb() 31 | 32 | def async_send(self, url, data, headers, success_cb, failure_cb): 33 | self.get_worker().queue( 34 | self.send_sync, url, data, headers, success_cb, failure_cb) 35 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" 4 | - "pypy" 5 | - "3.4" 6 | - "3.5" 7 | - "3.6" 8 | 9 | matrix: 10 | include: 11 | - python: "3.7" 12 | dist: xenial 13 | 14 | - name: Flake8 15 | dist: xenial 16 | python: "3.7" 17 | install: 18 | - pip install tox 19 | script: tox -e flake8 20 | 21 | - name: Distribution packages 22 | python: "3.6" 23 | install: false 24 | script: make travis-upload-dist 25 | 26 | sudo: false 27 | addons: 28 | apt: 29 | packages: 30 | - libevent-dev 31 | cache: 32 | directories: 33 | - "$HOME/.cache/pip" 34 | branches: 35 | only: 36 | - master 37 | - /^(?i:feature)-.*$/ 38 | jobs: 39 | fast_finish: true 40 | # allow_failures: 41 | # - python: 3.5 42 | # env: TOXENV=py35-django-dev-fix 43 | 44 | script: sh ci/runtox.sh 45 | install: 46 | - make 47 | - pip install codecov 48 | before_script: 49 | - pip freeze 50 | after_success: 51 | - codecov -e DJANGO 52 | 53 | notifications: 54 | webhooks: 55 | urls: 56 | - https://zeus.ci/hooks/0b589ca4-1165-11e8-b03b-0a580a28023f/public/provider/travis/webhook 57 | on_success: always 58 | on_failure: always 59 | on_start: always 60 | on_cancel: always 61 | on_error: always 62 | -------------------------------------------------------------------------------- /ci/setup: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | # we want wheel support when possible 4 | pip install "pip>=8.1" 5 | 6 | if [ -n "$DJANGO" ]; then 7 | if [[ "$DJANGO" == "dev" ]]; then 8 | pip install -e git+git://github.com/django/django.git#egg=Django 9 | else 10 | pip install "Django==$DJANGO" 11 | fi 12 | fi 13 | if [ -n "$FLASK" ]; then 14 | pip install "Flask==$FLASK" 15 | fi 16 | if [ -n "$CELERY" ]; then 17 | pip install "celery==$CELERY" 18 | fi 19 | make bootstrap 20 | if [[ ${TRAVIS_PYTHON_VERSION::1} == '2' ]]; then 21 | pip install gevent 22 | fi 23 | # if [[ ${TRAVIS_PYTHON_VERSION} == '3.2' ]]; then 24 | # pip install -I https://github.com/celery/celery/archive/3.0.zip 25 | # fi 26 | 27 | DJANGO_BITS=(${DJANGO//./ }) 28 | if [[ ${DJANGO_BITS[0]} -eq 1 && ${DJANGO_BITS[1]} -lt 8 ]]; then 29 | # django-celery has fickle dependencies as its deprecated 30 | pip install "django-celery>=3.1" "celery>=3.1,<4" 31 | # newer versions of pytest-django dont support older versions of django 32 | pip install "pytest-django<3.0" 33 | elif [ -n "$DJANGO" ]; then 34 | pip install "pytest-django>=3.0,<3.1" 35 | fi 36 | 37 | if [[ ${DJANGO_BITS[0]} -eq 1 && ${DJANGO_BITS[1]} -lt 10 && ${DJANGO_BITS[1]} -gt 7 ]]; then 38 | pip install "django-tastypie==0.13.3" 39 | fi 40 | -------------------------------------------------------------------------------- /raven/utils/transaction.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | from threading import local 4 | 5 | 6 | class TransactionContext(object): 7 | def __init__(self, stack, context): 8 | self.stack = stack 9 | self.context = context 10 | 11 | def __enter__(self): 12 | self.stack.push(self.context) 13 | return self 14 | 15 | def __exit__(self, *exc_info): 16 | self.stack.pop(self.context) 17 | 18 | 19 | class TransactionStack(local): 20 | def __init__(self): 21 | self.stack = [] 22 | 23 | def __len__(self): 24 | return len(self.stack) 25 | 26 | def __iter__(self): 27 | return iter(self.stack) 28 | 29 | def __call__(self, context): 30 | return TransactionContext(self, context) 31 | 32 | def clear(self): 33 | self.stack = [] 34 | 35 | def peek(self): 36 | try: 37 | return self.stack[-1] 38 | except IndexError: 39 | return None 40 | 41 | def push(self, context): 42 | self.stack.append(context) 43 | return context 44 | 45 | def pop(self, context=None): 46 | if context is None: 47 | return self.stack.pop() 48 | 49 | while self.stack: 50 | if self.stack.pop() is context: 51 | return context 52 | -------------------------------------------------------------------------------- /hooks/pre-commit.flake8: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import glob 4 | import os 5 | import sys 6 | 7 | os.environ['PYFLAKES_NODOCTEST'] = '1' 8 | 9 | # pycodestyle.py uses sys.argv to find setup.cfg 10 | sys.argv = [os.path.join(os.path.dirname(__file__), os.pardir, os.pardir)] 11 | 12 | # git usurbs your bin path for hooks and will always run system python 13 | if 'VIRTUAL_ENV' in os.environ: 14 | site_packages = glob.glob( 15 | '%s/lib/*/site-packages' % os.environ['VIRTUAL_ENV'])[0] 16 | sys.path.insert(0, site_packages) 17 | 18 | 19 | def main(): 20 | from flake8.api.legacy import get_style_guide 21 | from flake8.hooks import run 22 | 23 | gitcmd = "git diff-index --cached --name-only HEAD" 24 | 25 | _, files_modified, _ = run(gitcmd) 26 | 27 | try: 28 | text_type = unicode 29 | except NameError: 30 | text_type = str 31 | 32 | files_modified = [text_type(x) for x in files_modified] 33 | 34 | # remove non-py files and files which no longer exist 35 | files_modified = filter( 36 | lambda x: x.endswith('.py') and os.path.exists(x), 37 | files_modified) 38 | 39 | flake8_style = get_style_guide(parse_argv=True) 40 | report = flake8_style.check_files(files_modified) 41 | 42 | return report.total_errors != 0 43 | 44 | if __name__ == '__main__': 45 | sys.exit(main()) 46 | -------------------------------------------------------------------------------- /raven/transport/base.py: -------------------------------------------------------------------------------- 1 | """ 2 | raven.transport.base 3 | ~~~~~~~~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details. 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | from __future__ import absolute_import 9 | 10 | 11 | # Helper for external transports 12 | has_newstyle_transports = True 13 | 14 | 15 | class Transport(object): 16 | """ 17 | All transport implementations need to subclass this class 18 | 19 | You must implement a send method (or an async_send method if 20 | sub-classing AsyncTransport). 21 | """ 22 | 23 | is_async = False 24 | scheme = [] 25 | 26 | def send(self, url, data, headers): 27 | """ 28 | You need to override this to do something with the actual 29 | data. Usually - this is sending to a server 30 | """ 31 | raise NotImplementedError 32 | 33 | 34 | class AsyncTransport(Transport): 35 | """ 36 | All asynchronous transport implementations should subclass this 37 | class. 38 | 39 | You must implement a async_send method. 40 | """ 41 | 42 | is_async = True 43 | 44 | def async_send(self, url, data, headers, success_cb, error_cb): 45 | """ 46 | Override this method for asynchronous transports. Call 47 | `success_cb()` if the send succeeds or `error_cb(exception)` 48 | if the send fails. 49 | """ 50 | raise NotImplementedError 51 | -------------------------------------------------------------------------------- /raven/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | raven 3 | ~~~~~ 4 | 5 | :copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details. 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | from __future__ import absolute_import 9 | 10 | import os 11 | import os.path 12 | 13 | __all__ = ('VERSION', 'Client', 'get_version') # noqa 14 | 15 | VERSION = '6.10.0' 16 | 17 | 18 | def _get_git_revision(path): 19 | revision_file = os.path.join(path, 'refs', 'heads', 'master') 20 | if os.path.exists(revision_file): 21 | with open(revision_file) as fh: 22 | return fh.read().strip()[:7] 23 | 24 | 25 | def get_revision(): 26 | """ 27 | :returns: Revision number of this branch/checkout, if available. None if 28 | no revision number can be determined. 29 | """ 30 | package_dir = os.path.dirname(__file__) 31 | checkout_dir = os.path.normpath(os.path.join(package_dir, os.pardir, os.pardir)) 32 | path = os.path.join(checkout_dir, '.git') 33 | if os.path.exists(path): 34 | return _get_git_revision(path) 35 | 36 | 37 | def get_version(): 38 | base = VERSION 39 | if __build__: 40 | base = '%s (%s)' % (base, __build__) 41 | return base 42 | 43 | 44 | __build__ = get_revision() 45 | __docformat__ = 'restructuredtext en' 46 | 47 | 48 | # Declare child imports last to prevent recursion 49 | from raven.base import * # NOQA 50 | from raven.conf import * # NOQA 51 | from raven.versioning import * # NOQA 52 | -------------------------------------------------------------------------------- /tests/contrib/django/views.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | from django.http import HttpResponse 4 | from django.shortcuts import get_object_or_404, render_to_response 5 | from raven.contrib.django.models import client 6 | 7 | import logging 8 | 9 | 10 | class AppError(Exception): 11 | pass 12 | 13 | 14 | def no_error(request): 15 | return HttpResponse('') 16 | 17 | 18 | def fake_login(request): 19 | return HttpResponse('') 20 | 21 | 22 | def django_exc(request): 23 | return get_object_or_404(Exception, pk=1) 24 | 25 | 26 | def raise_exc(request): 27 | raise Exception(request.GET.get('message', 'view exception')) 28 | 29 | 30 | def read_request_and_raise_exc(request): 31 | request.read() 32 | raise AppError() 33 | 34 | 35 | def raise_ioerror(request): 36 | raise IOError(request.GET.get('message', 'view exception')) 37 | 38 | 39 | def decorated_raise_exc(request): 40 | return raise_exc(request) 41 | 42 | 43 | def template_exc(request): 44 | return render_to_response('error.html') 45 | 46 | 47 | def logging_request_exc(request): 48 | logger = logging.getLogger(__name__) 49 | try: 50 | raise Exception(request.GET.get('message', 'view exception')) 51 | except Exception as e: 52 | logger.error(e, exc_info=True, extra={'request': request}) 53 | return HttpResponse('') 54 | 55 | 56 | def capture_event(request): 57 | client.captureMessage('test') 58 | return HttpResponse('') 59 | -------------------------------------------------------------------------------- /raven/contrib/zconfig/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import 3 | """ 4 | raven.contrib.zconfig 5 | ~~~~~~~~~~~~~~~~~~~~~ 6 | 7 | :copyright: (c) 2010-2013 by the Sentry Team, see AUTHORS for more details. 8 | :license: BSD, see LICENSE for more details. 9 | """ 10 | import logging 11 | import ZConfig.components.logger.factory 12 | import raven.handlers.logging 13 | 14 | 15 | class Factory(ZConfig.components.logger.factory.Factory): 16 | 17 | def __init__(self, section): 18 | ZConfig.components.logger.factory.Factory.__init__(self) 19 | self.section = section 20 | self.section.level = self.section.level or logging.ERROR 21 | 22 | def getLevel(self): 23 | return self.section.level 24 | 25 | def create(self): 26 | return raven.handlers.logging.SentryHandler( 27 | dsn=self.section.dsn, 28 | site=self.section.site, 29 | name=self.section.name, 30 | release=self.section.release, 31 | environment=self.section.environment, 32 | exclude_paths=self.section.exclude_paths, 33 | include_paths=self.section.include_paths, 34 | sample_rate=self.section.sample_rate, 35 | list_max_length=self.section.list_max_length, 36 | string_max_length=self.section.string_max_length, 37 | auto_log_stacks=self.section.auto_log_stacks, 38 | processors=self.section.processors, 39 | level=self.section.level) 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Functional Software, Inc and individual contributors. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | 8 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 9 | 10 | 3. Neither the name of the Raven nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 11 | 12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 13 | -------------------------------------------------------------------------------- /tests/utils/json/tests.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import datetime 4 | import uuid 5 | from decimal import Decimal 6 | from raven.utils.testutils import TestCase 7 | 8 | from raven.utils import json 9 | 10 | 11 | class JSONTest(TestCase): 12 | def test_uuid(self): 13 | res = uuid.uuid4() 14 | json.dumps(res) == '"%s"' % res.hex 15 | 16 | def test_datetime(self): 17 | res = datetime.datetime(day=1, month=1, year=2011, hour=1, minute=1, second=1) 18 | assert json.dumps(res) == '"2011-01-01T01:01:01Z"' 19 | 20 | def test_set(self): 21 | res = set(['foo', 'bar']) 22 | assert json.dumps(res) in ('["foo", "bar"]', '["bar", "foo"]') 23 | 24 | def test_frozenset(self): 25 | res = frozenset(['foo', 'bar']) 26 | assert json.dumps(res) in ('["foo", "bar"]', '["bar", "foo"]') 27 | 28 | def test_unknown_type(self): 29 | 30 | class Unknown(object): 31 | def __repr__(self): 32 | return 'Unknown object' 33 | 34 | obj = Unknown() 35 | assert json.dumps(obj) == '"Unknown object"' 36 | 37 | def test_unknown_type_with_repr_error(self): 38 | 39 | class Unknown(object): 40 | def __repr__(self): 41 | raise Exception 42 | 43 | obj = Unknown() 44 | s = json.dumps(obj) 45 | assert isinstance(s, str) 46 | assert 'Unknown object at 0x' in s 47 | 48 | def test_decimal(self): 49 | d = {'decimal': Decimal('123.45')} 50 | assert json.dumps(d) == '{"decimal": "Decimal(\'123.45\')"}' 51 | -------------------------------------------------------------------------------- /raven/conf/defaults.py: -------------------------------------------------------------------------------- 1 | """ 2 | raven.conf.defaults 3 | ~~~~~~~~~~~~~~~~~~~ 4 | 5 | Represents the default values for all Sentry settings. 6 | 7 | :copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details. 8 | :license: BSD, see LICENSE for more details. 9 | """ 10 | from __future__ import absolute_import 11 | 12 | import os 13 | import os.path 14 | import socket 15 | 16 | ROOT = os.path.normpath(os.path.join(os.path.dirname(__file__), os.pardir)) 17 | 18 | TIMEOUT = 5 19 | 20 | # TODO: this is specific to Django 21 | CLIENT = 'raven.contrib.django.DjangoClient' 22 | 23 | # Not all environments have access to socket module, for example Google App Engine 24 | # Need to check to see if the socket module has ``gethostname``, if it doesn't we 25 | # will set it to None and require it passed in to ``Client`` on initializtion. 26 | NAME = socket.gethostname() if hasattr(socket, 'gethostname') else None 27 | 28 | # The maximum number of elements to store for a list-like structure. 29 | MAX_LENGTH_LIST = 50 30 | 31 | # The maximum length to store of a string-like structure. 32 | MAX_LENGTH_STRING = 400 33 | 34 | # Automatically log frame stacks from all ``logging`` messages. 35 | AUTO_LOG_STACKS = False 36 | 37 | # Collect locals variables 38 | CAPTURE_LOCALS = True 39 | 40 | # Client-side data processors to apply 41 | PROCESSORS = ( 42 | 'raven.processors.SanitizePasswordsProcessor', 43 | ) 44 | 45 | try: 46 | # Try for certifi first since they likely keep their bundle more up to date 47 | import certifi 48 | CA_BUNDLE = certifi.where() 49 | except ImportError: 50 | CA_BUNDLE = os.path.join(ROOT, 'data', 'cacert.pem') 51 | -------------------------------------------------------------------------------- /tests/contrib/django/settings.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | 4 | # ---- SENTRY CONFIG ---- # 5 | SENTRY_CLIENT = 'tests.contrib.django.tests.MockClient' 6 | DISABLE_SENTRY_INSTRUMENTATION = True 7 | SENTRY_ALLOW_ORIGIN = '*' 8 | 9 | # ---- GENERIC DJANGO SETTINGS ---- # 10 | PROJECT_ROOT = os.path.dirname(os.path.abspath(__file__)) 11 | 12 | SECRET_KEY = "Change this!" 13 | INSTALLED_APPS = [ 14 | 'django.contrib.auth', 15 | 'django.contrib.contenttypes', 16 | 'django.contrib.sessions', 17 | 'django.contrib.sites', 18 | 19 | 'raven.contrib.django', 20 | 'tests.contrib.django', 21 | ] 22 | 23 | 24 | DATABASE_ENGINE='sqlite3' 25 | DATABASES = { 26 | 'default': { 27 | 'NAME': ':memory:', 28 | 'ENGINE': 'django.db.backends.sqlite3', 29 | 'TEST_NAME': ':memory:', 30 | }, 31 | } 32 | DATABASE_NAME = ':memory:' 33 | TEST_DATABASE_NAME = ':memory:' 34 | ROOT_URLCONF = 'tests.contrib.django.urls' 35 | DEBUG = False 36 | SITE_ID = 1 37 | 38 | TEMPLATE_DEBUG = True 39 | LANGUAGE_CODE = 'en' 40 | LANGUAGES = (('en', 'English'),) 41 | TEMPLATE_DIRS = [ 42 | os.path.join(PROJECT_ROOT, 'templates'), 43 | ] 44 | TEMPLATES = [{ 45 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 46 | 'APP_DIRS': True, 47 | 'DIRS': TEMPLATE_DIRS, 48 | }] 49 | ALLOWED_HOSTS = ['*'] 50 | 51 | # ---- CELERY SETTINGS ---- # 52 | 53 | try: 54 | import djcelery # NOQA 55 | INSTALLED_APPS.append('djcelery') 56 | except ImportError: 57 | pass 58 | 59 | BROKER_HOST = "localhost" 60 | BROKER_PORT = 5672 61 | BROKER_USER = "guest" 62 | BROKER_PASSWORD = "guest" 63 | BROKER_VHOST = "/" 64 | CELERY_ALWAYS_EAGER = True 65 | -------------------------------------------------------------------------------- /raven/conf/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | raven.conf 3 | ~~~~~~~~~~ 4 | 5 | :copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details. 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | from __future__ import absolute_import 9 | 10 | import logging 11 | 12 | __all__ = ['setup_logging'] 13 | 14 | EXCLUDE_LOGGER_DEFAULTS = ( 15 | 'raven', 16 | 'gunicorn', 17 | 'south', 18 | 'sentry.errors', 19 | 'django.request', 20 | # dill produces a lot of garbage debug logs that are just a stream of what 21 | # another developer would use print/pdb for 22 | 'dill', 23 | ) 24 | 25 | 26 | def setup_logging(handler, exclude=EXCLUDE_LOGGER_DEFAULTS): 27 | """ 28 | Configures logging to pipe to Sentry. 29 | 30 | - ``exclude`` is a list of loggers that shouldn't go to Sentry. 31 | 32 | For a typical Python install: 33 | 34 | >>> from raven.handlers.logging import SentryHandler 35 | >>> client = Sentry(...) 36 | >>> setup_logging(SentryHandler(client)) 37 | 38 | Within Django: 39 | 40 | >>> from raven.contrib.django.handlers import SentryHandler 41 | >>> setup_logging(SentryHandler()) 42 | 43 | Returns a boolean based on if logging was configured or not. 44 | """ 45 | logger = logging.getLogger() 46 | if handler.__class__ in map(type, logger.handlers): 47 | return False 48 | 49 | logger.addHandler(handler) 50 | 51 | # Add StreamHandler to sentry's default so you can catch missed exceptions 52 | for logger_name in exclude: 53 | logger = logging.getLogger(logger_name) 54 | logger.propagate = False 55 | logger.addHandler(logging.StreamHandler()) 56 | 57 | return True 58 | -------------------------------------------------------------------------------- /raven/transport/eventlet.py: -------------------------------------------------------------------------------- 1 | """ 2 | raven.transport.eventlet 3 | ~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details. 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | from __future__ import absolute_import 9 | 10 | import sys 11 | 12 | from raven.transport.http import HTTPTransport 13 | 14 | try: 15 | import eventlet 16 | try: 17 | from eventlet.green import urllib2 as eventlet_urllib2 18 | except ImportError: 19 | from eventlet.green.urllib import request as eventlet_urllib2 20 | has_eventlet = True 21 | except ImportError: 22 | has_eventlet = False 23 | 24 | 25 | class EventletHTTPTransport(HTTPTransport): 26 | 27 | scheme = ['eventlet+http', 'eventlet+https'] 28 | 29 | def __init__(self, pool_size=100, **kwargs): 30 | if not has_eventlet: 31 | raise ImportError('EventletHTTPTransport requires eventlet.') 32 | super(EventletHTTPTransport, self).__init__(**kwargs) 33 | 34 | def _send_payload(self, payload): 35 | url, data, headers = payload 36 | req = eventlet_urllib2.Request(url, headers=headers) 37 | try: 38 | if sys.version_info < (2, 6): 39 | response = eventlet_urllib2.urlopen(req, data).read() 40 | else: 41 | response = eventlet_urllib2.urlopen(req, data, 42 | self.timeout).read() 43 | return response 44 | except Exception as err: 45 | return err 46 | 47 | def send(self, url, data, headers): 48 | """ 49 | Spawn an async request to a remote webserver. 50 | """ 51 | eventlet.spawn(self._send_payload, (url, data, headers)) 52 | -------------------------------------------------------------------------------- /raven/transport/gevent.py: -------------------------------------------------------------------------------- 1 | """ 2 | raven.transport.gevent 3 | ~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details. 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | from __future__ import absolute_import 9 | 10 | from raven.transport.base import AsyncTransport 11 | from raven.transport.http import HTTPTransport 12 | 13 | try: 14 | import gevent 15 | # gevent 1.0bN renamed coros to lock 16 | try: 17 | from gevent.lock import Semaphore 18 | except ImportError: 19 | from gevent.coros import Semaphore # NOQA 20 | has_gevent = True 21 | except ImportError: 22 | has_gevent = None 23 | 24 | 25 | class GeventedHTTPTransport(AsyncTransport, HTTPTransport): 26 | 27 | scheme = ['gevent+http', 'gevent+https'] 28 | 29 | def __init__(self, maximum_outstanding_requests=100, *args, **kwargs): 30 | if not has_gevent: 31 | raise ImportError('GeventedHTTPTransport requires gevent.') 32 | 33 | self._lock = Semaphore(maximum_outstanding_requests) 34 | 35 | super(GeventedHTTPTransport, self).__init__(*args, **kwargs) 36 | 37 | def async_send(self, url, data, headers, success_cb, failure_cb): 38 | """ 39 | Spawn an async request to a remote webserver. 40 | """ 41 | # this can be optimized by making a custom self.send that does not 42 | # read the response since we don't use it. 43 | self._lock.acquire() 44 | return gevent.spawn( 45 | super(GeventedHTTPTransport, self).send, url, data, headers 46 | ).link(lambda x: self._done(x, success_cb, failure_cb)) 47 | 48 | def _done(self, greenlet, success_cb, failure_cb, *args): 49 | self._lock.release() 50 | if greenlet.successful(): 51 | success_cb() 52 | else: 53 | failure_cb(greenlet.exception) 54 | -------------------------------------------------------------------------------- /tests/contrib/django/test_resolver.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | import pytest 4 | import django 5 | 6 | try: 7 | from django.conf.urls import url, include 8 | except ImportError: 9 | # for Django version less than 1.4 10 | from django.conf.urls.defaults import url, include # NOQA 11 | 12 | from raven.contrib.django.resolver import RouteResolver 13 | 14 | 15 | if django.VERSION < (1, 9): 16 | included_url_conf = ( 17 | url(r'^foo/bar/(?P[\w]+)', lambda x: ''), 18 | ), '', '' 19 | else: 20 | included_url_conf = (( 21 | url(r'^foo/bar/(?P[\w]+)', lambda x: ''), 22 | ), '') 23 | 24 | example_url_conf = ( 25 | url(r'^api/(?P[\w_-]+)/store/$', lambda x: ''), 26 | url(r'^report/', lambda x: ''), 27 | url(r'^example/', include(included_url_conf)), 28 | ) 29 | 30 | 31 | def test_no_match(): 32 | resolver = RouteResolver() 33 | result = resolver.resolve('/foo/bar', example_url_conf) 34 | assert result == '/foo/bar' 35 | 36 | 37 | def test_simple_match(): # TODO: ash add matchedstring to make this test actually test something 38 | resolver = RouteResolver() 39 | result = resolver.resolve('/report/', example_url_conf) 40 | assert result == '/report/' 41 | 42 | 43 | def test_complex_match(): 44 | resolver = RouteResolver() 45 | result = resolver.resolve('/api/1234/store/', example_url_conf) 46 | assert result == '/api/{project_id}/store/' 47 | 48 | 49 | def test_included_match(): 50 | resolver = RouteResolver() 51 | result = resolver.resolve('/example/foo/bar/baz', example_url_conf) 52 | assert result == '/example/foo/bar/{param}' 53 | 54 | 55 | @pytest.mark.skipif(django.VERSION < (2, 0), reason="Requires Django > 2.0") 56 | def test_newstyle_django20_urlconf(urlconf, route_resolver): 57 | result = route_resolver.resolve('/api/v2/1234/store/', urlconf) 58 | assert result == '/api/v2/{project_id}/store/' -------------------------------------------------------------------------------- /tests/transport/requests/test_threaded_requests.py: -------------------------------------------------------------------------------- 1 | import mock 2 | import time 3 | from raven.utils.testutils import TestCase 4 | 5 | from raven.base import Client 6 | from raven.transport.threaded_requests import ThreadedRequestsHTTPTransport 7 | from raven.utils.urlparse import urlparse 8 | 9 | 10 | class DummyThreadedScheme(ThreadedRequestsHTTPTransport): 11 | def __init__(self, *args, **kwargs): 12 | super(ThreadedRequestsHTTPTransport, self).__init__(*args, **kwargs) 13 | self.events = [] 14 | self.send_delay = 0 15 | 16 | def send_sync(self, url, data, headers, success_cb, failure_cb): 17 | # delay sending the message, to allow us to test that the shutdown 18 | # hook waits correctly 19 | time.sleep(self.send_delay) 20 | 21 | self.events.append((url, data, headers, success_cb, failure_cb)) 22 | 23 | 24 | class ThreadedTransportTest(TestCase): 25 | def setUp(self): 26 | self.url = "threaded+requests+http://some_username:some_password@localhost:8143/1" 27 | self.client = Client(dsn=self.url) 28 | 29 | @mock.patch('raven.transport.requests.post') 30 | def test_does_send(self, send): 31 | self.client.captureMessage(message='foo') 32 | 33 | time.sleep(0.1) 34 | 35 | self.assertEqual(send.call_count, 1) 36 | expected_url = 'http://localhost:8143/api/1/store/' 37 | self.assertEqual(expected_url, send.call_args[0][0]) 38 | 39 | def test_shutdown_waits_for_send(self): 40 | url = urlparse(self.url) 41 | transport = DummyThreadedScheme() 42 | transport.send_delay = 0.5 43 | 44 | data = self.client.build_msg('raven.events.Message', message='foo') 45 | transport.async_send(url, data, None, None, None) 46 | 47 | time.sleep(0.1) 48 | 49 | # this should wait for the message to get sent 50 | transport.get_worker().main_thread_terminated() 51 | 52 | self.assertEqual(len(transport.events), 1) 53 | -------------------------------------------------------------------------------- /tests/contrib/django/urls.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | from django.conf import settings 4 | try: 5 | from django.conf.urls import url, include 6 | except ImportError: 7 | # for Django version less than 1.4 8 | from django.conf.urls.defaults import url, include # NOQA 9 | 10 | from django.http import HttpResponse 11 | 12 | from tests.contrib.django import views 13 | 14 | 15 | def handler404(request, exception=None): 16 | return HttpResponse('', status=404) 17 | 18 | 19 | def handler500(request, exception=None): 20 | if getattr(settings, 'BREAK_THAT_500', False): 21 | raise ValueError('handler500') 22 | return HttpResponse('', status=500) 23 | 24 | 25 | urlpatterns = ( 26 | url(r'^no-error$', views.no_error, name='sentry-no-error'), 27 | url(r'^fake-login$', views.fake_login, name='sentry-fake-login'), 28 | url(r'^trigger-500$', views.raise_exc, name='sentry-raise-exc'), 29 | url(r'^trigger-500-readrequest$', views.read_request_and_raise_exc, name='sentry-readrequest-raise-exc'), 30 | url(r'^trigger-500-ioerror$', views.raise_ioerror, name='sentry-raise-ioerror'), 31 | url(r'^trigger-500-decorated$', views.decorated_raise_exc, name='sentry-raise-exc-decor'), 32 | url(r'^trigger-500-django$', views.django_exc, name='sentry-django-exc'), 33 | url(r'^trigger-500-template$', views.template_exc, name='sentry-template-exc'), 34 | url(r'^trigger-500-log-request$', views.logging_request_exc, name='sentry-log-request-exc'), 35 | url(r'^trigger-event$', views.capture_event, name='sentry-trigger-event'), 36 | ) 37 | 38 | try: 39 | from tastypie.api import Api 40 | except ImportError: 41 | pass 42 | else: 43 | from tests.contrib.django.api import ExampleResource, AnotherExampleResource 44 | 45 | v1_api = Api(api_name='v1') 46 | v1_api.register(ExampleResource()) 47 | v1_api.register(AnotherExampleResource()) 48 | 49 | urlpatterns += ( 50 | url(r'^api/', include(v1_api.urls)), 51 | ) 52 | -------------------------------------------------------------------------------- /raven/transport/http.py: -------------------------------------------------------------------------------- 1 | """ 2 | raven.transport.http 3 | ~~~~~~~~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details. 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | from __future__ import absolute_import 9 | 10 | from raven.utils.compat import string_types, urllib2 11 | from raven.conf import defaults 12 | from raven.exceptions import APIError, RateLimited 13 | from raven.transport.base import Transport 14 | from raven.utils.http import urlopen 15 | 16 | 17 | class HTTPTransport(Transport): 18 | scheme = ['sync+http', 'sync+https'] 19 | 20 | def __init__(self, timeout=defaults.TIMEOUT, verify_ssl=True, 21 | ca_certs=defaults.CA_BUNDLE): 22 | if isinstance(timeout, string_types): 23 | timeout = int(timeout) 24 | if isinstance(verify_ssl, string_types): 25 | verify_ssl = bool(int(verify_ssl)) 26 | 27 | self.timeout = timeout 28 | self.verify_ssl = verify_ssl 29 | self.ca_certs = ca_certs 30 | 31 | def send(self, url, data, headers): 32 | """ 33 | Sends a request to a remote webserver using HTTP POST. 34 | """ 35 | req = urllib2.Request(url, headers=headers) 36 | 37 | try: 38 | response = urlopen( 39 | url=req, 40 | data=data, 41 | timeout=self.timeout, 42 | verify_ssl=self.verify_ssl, 43 | ca_certs=self.ca_certs, 44 | ) 45 | except urllib2.HTTPError as exc: 46 | msg = exc.headers.get('x-sentry-error') 47 | code = exc.getcode() 48 | if code == 429: 49 | try: 50 | retry_after = int(exc.headers.get('retry-after')) 51 | except (ValueError, TypeError): 52 | retry_after = 0 53 | raise RateLimited(msg, retry_after) 54 | elif msg: 55 | raise APIError(msg, code) 56 | else: 57 | raise 58 | return response 59 | -------------------------------------------------------------------------------- /raven/transport/tornado.py: -------------------------------------------------------------------------------- 1 | """ 2 | raven.transport.tornado 3 | ~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details. 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | from __future__ import absolute_import 9 | 10 | from functools import partial 11 | 12 | from raven.transport.base import AsyncTransport 13 | from raven.transport.http import HTTPTransport 14 | 15 | try: 16 | from tornado import ioloop 17 | from tornado.httpclient import AsyncHTTPClient, HTTPClient 18 | has_tornado = True 19 | except ImportError: 20 | has_tornado = False 21 | 22 | 23 | class TornadoHTTPTransport(AsyncTransport, HTTPTransport): 24 | 25 | scheme = ['tornado+http', 'tornado+https'] 26 | 27 | def __init__(self, *args, **kwargs): 28 | if not has_tornado: 29 | raise ImportError('TornadoHTTPTransport requires tornado.') 30 | 31 | super(TornadoHTTPTransport, self).__init__(*args, **kwargs) 32 | 33 | def async_send(self, url, data, headers, success_cb, failure_cb): 34 | kwargs = dict(method='POST', headers=headers, body=data) 35 | kwargs["validate_cert"] = self.verify_ssl 36 | kwargs["connect_timeout"] = self.timeout 37 | kwargs["ca_certs"] = self.ca_certs 38 | 39 | # only use async if ioloop is running, otherwise it will never send 40 | if ioloop.IOLoop.initialized(): 41 | client = AsyncHTTPClient() 42 | kwargs['callback'] = None 43 | 44 | future = client.fetch(url, **kwargs) 45 | ioloop.IOLoop.current().add_future(future, partial(self.handler, success_cb, failure_cb)) 46 | else: 47 | client = HTTPClient() 48 | try: 49 | client.fetch(url, **kwargs) 50 | success_cb() 51 | except Exception as e: 52 | failure_cb(e) 53 | 54 | @staticmethod 55 | def handler(success, error, future): 56 | try: 57 | future.result() 58 | success() 59 | except Exception as e: 60 | error(e) 61 | -------------------------------------------------------------------------------- /conftest.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | import os.path 4 | import pytest 5 | import sys 6 | 7 | collect_ignore = [ 8 | 'tests/contrib/awslambda' 9 | ] 10 | 11 | if sys.version_info[0] > 2: 12 | if sys.version_info[1] < 3: 13 | collect_ignore.append('tests/contrib/flask') 14 | if sys.version_info[1] == 2: 15 | collect_ignore.append('tests/handlers/logbook') 16 | 17 | try: 18 | import gevent # NOQA 19 | except ImportError: 20 | collect_ignore.append('tests/transport/gevent') 21 | 22 | try: 23 | import web # NOQA 24 | except ImportError: 25 | collect_ignore.append('tests/contrib/webpy') 26 | 27 | try: 28 | import django # NOQA 29 | except ImportError: 30 | django = None 31 | collect_ignore.append('tests/contrib/django') 32 | 33 | try: 34 | import Sanic # NOQA 35 | except ImportError: 36 | collect_ignore.append('tests/contrib/sanic') 37 | 38 | try: 39 | import tastypie # NOQA 40 | except ImportError: 41 | collect_ignore.append('tests/contrib/django/test_tastypie.py') 42 | 43 | 44 | use_djcelery = True 45 | try: 46 | import djcelery # NOQA 47 | # INSTALLED_APPS.append('djcelery') 48 | except ImportError: 49 | use_djcelery = False 50 | 51 | 52 | def pytest_runtest_teardown(item): 53 | if django: 54 | from raven.contrib.django.models import client 55 | client.events = [] 56 | 57 | 58 | @pytest.fixture 59 | def project_root(): 60 | return os.path.dirname(os.path.abspath(__file__)) 61 | 62 | 63 | @pytest.fixture 64 | def mytest_model(): 65 | 66 | from tests.contrib.django.models import MyTestModel 67 | return MyTestModel 68 | 69 | 70 | @pytest.fixture(scope='function', autouse=False) 71 | def user_instance(request, admin_user): 72 | request.cls.user = admin_user 73 | 74 | 75 | @pytest.fixture(autouse=True) 76 | def has_git_requirements(request, project_root): 77 | if request.node.get_marker('has_git_requirements'): 78 | if not os.path.exists(os.path.join(project_root, '.git', 'refs', 'heads', 'master')): 79 | pytest.skip('skipped test as project is not a git repo') 80 | -------------------------------------------------------------------------------- /tests/context/tests.py: -------------------------------------------------------------------------------- 1 | import threading 2 | from raven.utils.testutils import TestCase 3 | 4 | from raven.base import Client 5 | from raven.context import Context 6 | 7 | 8 | class ContextTest(TestCase): 9 | def test_simple(self): 10 | context = Context() 11 | context.merge({'foo': 'bar'}) 12 | context.merge({'biz': 'baz'}) 13 | context.merge({'biz': 'boz'}) 14 | assert context.get() == { 15 | 'foo': 'bar', 16 | 'biz': 'boz', 17 | } 18 | 19 | def test_tags(self): 20 | context = Context() 21 | context.merge({'tags': {'foo': 'bar'}}) 22 | context.merge({'tags': {'biz': 'baz'}}) 23 | assert context.get() == { 24 | 'tags': { 25 | 'foo': 'bar', 26 | 'biz': 'baz', 27 | } 28 | } 29 | 30 | def test_extra(self): 31 | context = Context() 32 | context.merge({'extra': {'foo': 'bar'}}) 33 | context.merge({'extra': {'biz': 'baz'}}) 34 | assert context.get() == { 35 | 'extra': { 36 | 'foo': 'bar', 37 | 'biz': 'baz', 38 | } 39 | } 40 | 41 | def test_thread_binding(self): 42 | client = Client() 43 | called = [] 44 | 45 | class TestContext(Context): 46 | 47 | def activate(self): 48 | Context.activate(self) 49 | called.append('activate') 50 | 51 | def deactivate(self): 52 | called.append('deactivate') 53 | Context.deactivate(self) 54 | 55 | # The main thread activates the context but clear does not 56 | # deactivate. 57 | context = TestContext(client) 58 | context.clear() 59 | assert called == ['activate'] 60 | 61 | # But another thread does. 62 | del called[:] 63 | 64 | def test_thread(): 65 | # This activate is unnecessary as the first activate happens 66 | # automatically 67 | context.activate() 68 | context.clear() 69 | t = threading.Thread(target=test_thread) 70 | t.start() 71 | t.join() 72 | assert called == ['activate', 'activate', 'deactivate'] 73 | -------------------------------------------------------------------------------- /raven/utils/http.py: -------------------------------------------------------------------------------- 1 | """ 2 | raven.utils.http 3 | ~~~~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details. 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | from __future__ import absolute_import 9 | 10 | import socket 11 | import ssl 12 | import sys 13 | 14 | from raven.conf import defaults 15 | from raven.utils.compat import urllib2, httplib 16 | from raven.utils.ssl_match_hostname import match_hostname 17 | 18 | 19 | def urlopen(url, data=None, timeout=defaults.TIMEOUT, ca_certs=None, 20 | verify_ssl=False, assert_hostname=None): 21 | 22 | class ValidHTTPSConnection(httplib.HTTPConnection): 23 | default_port = httplib.HTTPS_PORT 24 | 25 | def __init__(self, *args, **kwargs): 26 | httplib.HTTPConnection.__init__(self, *args, **kwargs) 27 | 28 | def connect(self): 29 | sock = socket.create_connection( 30 | address=(self.host, self.port), 31 | timeout=self.timeout, 32 | ) 33 | if self._tunnel_host: 34 | self.sock = sock 35 | self._tunnel() 36 | 37 | self.sock = ssl.wrap_socket( 38 | sock, ca_certs=ca_certs, cert_reqs=ssl.CERT_REQUIRED) 39 | 40 | if assert_hostname is not None: 41 | match_hostname(self.sock.getpeercert(), 42 | self.assert_hostname or self.host) 43 | 44 | class ValidHTTPSHandler(urllib2.HTTPSHandler): 45 | def https_open(self, req): 46 | return self.do_open(ValidHTTPSConnection, req) 47 | 48 | if verify_ssl: 49 | handlers = [ValidHTTPSHandler] 50 | else: 51 | try: 52 | handlers = [urllib2.HTTPSHandler( 53 | context=ssl._create_unverified_context())] 54 | except AttributeError: 55 | handlers = [] 56 | 57 | opener = urllib2.build_opener(*handlers) 58 | 59 | if sys.version_info < (2, 6): 60 | default_timeout = socket.getdefaulttimeout() 61 | socket.setdefaulttimeout(timeout) 62 | try: 63 | return opener.open(url, data) 64 | finally: 65 | socket.setdefaulttimeout(default_timeout) 66 | return opener.open(url, data, timeout) 67 | -------------------------------------------------------------------------------- /tests/contrib/awslambda/test_lambda.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from raven.transport.http import HTTPTransport 3 | 4 | 5 | class MyException(Exception): 6 | pass 7 | 8 | 9 | def test_decorator_exception(lambda_env, mock_client, lambda_event, lambda_context): 10 | 11 | client = mock_client() 12 | 13 | @client.capture_exceptions 14 | def test_func(event, context): 15 | raise MyException('There was an error.') 16 | 17 | with pytest.raises(MyException): 18 | test_func(event=lambda_event(), context=lambda_context(function_name='test_func')) 19 | 20 | assert client.events 21 | assert isinstance(client.remote.get_transport(), HTTPTransport) 22 | assert 'user' in client.events[0].keys() 23 | assert 'request' in client.events[0].keys() 24 | 25 | 26 | def test_decorator_with_args(lambda_env, mock_client, lambda_event, lambda_context): 27 | client = mock_client() 28 | 29 | @client.capture_exceptions((MyException,)) 30 | def test_func(event, context): 31 | raise Exception 32 | 33 | with pytest.raises(Exception): 34 | test_func(event=lambda_event(), context=lambda_context(function_name='test_func')) 35 | 36 | assert not client.events 37 | 38 | @client.capture_exceptions((MyException,)) 39 | def test_func(event, context): 40 | raise MyException 41 | 42 | with pytest.raises(Exception): 43 | test_func(event=lambda_event(), context=lambda_context(function_name='test_func')) 44 | 45 | assert client.events 46 | 47 | 48 | def test_decorator_without_exceptions(lambda_env, mock_client, lambda_event, lambda_context): 49 | client = mock_client() 50 | 51 | @client.capture_exceptions((MyException,)) 52 | def test_func(event, context): 53 | return 0 54 | 55 | assert test_func(event=lambda_event(), context=lambda_context(function_name='test_func')) == 0 56 | 57 | 58 | def test_decorator_without_kwargs(lambda_env, mock_client, lambda_event, lambda_context): 59 | 60 | client = mock_client() 61 | 62 | @client.capture_exceptions((MyException,)) 63 | def test_func(event, context): 64 | raise MyException 65 | 66 | with pytest.raises(Exception): 67 | test_func(lambda_event(), lambda_context(function_name='test_func')) 68 | 69 | assert client.events 70 | -------------------------------------------------------------------------------- /tests/contrib/django/test_tastypie.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | import django 4 | import pytest 5 | 6 | from django.test import TestCase 7 | from django.core.urlresolvers import get_resolver 8 | from tastypie.test import ResourceTestCaseMixin 9 | 10 | from raven.contrib.django.models import client 11 | from raven.contrib.django.resolver import RouteResolver 12 | 13 | DJANGO_19 = django.VERSION >= (1, 9, 0) and django.VERSION <= (1, 10, 0) 14 | 15 | 16 | class TastypieTest(ResourceTestCaseMixin, TestCase): 17 | def setUp(self): 18 | super(TastypieTest, self).setUp() 19 | self.path = '/api/v1/example/' 20 | 21 | def test_list_break(self): 22 | self.api_client.get(self.path) 23 | 24 | assert len(client.events) == 1 25 | event = client.events.pop(0) 26 | assert 'exception' in event 27 | exc = event['exception']['values'][-1] 28 | assert exc['type'] == 'Exception' 29 | assert exc['value'] == 'oops' 30 | assert 'request' in event 31 | assert event['request']['url'] == 'http://testserver/api/v1/example/' 32 | 33 | def test_create_break(self): 34 | self.api_client.post('/api/v1/example/') 35 | 36 | assert len(client.events) == 1 37 | event = client.events.pop(0) 38 | assert 'exception' in event 39 | exc = event['exception']['values'][-1] 40 | assert exc['type'] == 'Exception' 41 | assert exc['value'] == 'oops' 42 | assert 'request' in event 43 | assert event['request']['url'] == 'http://testserver/api/v1/example/' 44 | 45 | def test_update_break(self): 46 | self.api_client.put('/api/v1/example/foo/', data={'name': 'bar'}) 47 | 48 | assert len(client.events) == 1 49 | event = client.events.pop(0) 50 | assert 'exception' in event 51 | exc = event['exception']['values'][-1] 52 | assert exc['type'] == 'Exception' 53 | assert exc['value'] == 'oops' 54 | assert 'request' in event 55 | assert event['request']['url'] == 'http://testserver/api/v1/example/foo/' 56 | 57 | @pytest.mark.skipif(not DJANGO_19, reason='Django != 1.9') 58 | def test_resolver(self): 59 | resolver = get_resolver() 60 | route_resolver = RouteResolver() 61 | result = route_resolver._resolve(resolver, '/api/v1/example/') 62 | assert result == '/api/{api_name}/{resource_name}/' 63 | -------------------------------------------------------------------------------- /tests/contrib/django/api.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | from uuid import uuid4 4 | from tastypie.bundle import Bundle 5 | from tastypie.resources import Resource 6 | 7 | from raven.contrib.django.models import client 8 | 9 | 10 | class Item(object): 11 | def __init__(self, pk=None, name=None): 12 | self.pk = pk or uuid4().hex 13 | self.name = name or '' 14 | 15 | 16 | class ExampleResource(Resource): 17 | class Meta: 18 | resource_name = 'example' 19 | object_class = Item 20 | 21 | def detail_uri_kwargs(self, bundle_or_obj): 22 | kwargs = {} 23 | 24 | if isinstance(bundle_or_obj, Bundle): 25 | kwargs['pk'] = bundle_or_obj.obj.pk 26 | else: 27 | kwargs['pk'] = bundle_or_obj.pk 28 | 29 | return kwargs 30 | 31 | def obj_get_list(self, bundle, **kwargs): 32 | try: 33 | raise Exception('oops') 34 | except: 35 | client.captureException() 36 | return [] 37 | 38 | def obj_create(self, bundle, **kwargs): 39 | try: 40 | raise Exception('oops') 41 | except: 42 | client.captureException() 43 | 44 | bundle.obj = Item(**kwargs) 45 | bundle = self.full_hydrate(bundle) 46 | return bundle 47 | 48 | def obj_update(self, bundle, **kwargs): 49 | return self.obj_create(bundle, **kwargs) 50 | 51 | 52 | class AnotherExampleResource(Resource): 53 | class Meta: 54 | resource_name = 'another' 55 | object_class = Item 56 | 57 | def detail_uri_kwargs(self, bundle_or_obj): 58 | kwargs = {} 59 | 60 | if isinstance(bundle_or_obj, Bundle): 61 | kwargs['pk'] = bundle_or_obj.obj.pk 62 | else: 63 | kwargs['pk'] = bundle_or_obj.pk 64 | 65 | return kwargs 66 | 67 | def obj_get_list(self, bundle, **kwargs): 68 | try: 69 | raise Exception('oops') 70 | except: 71 | client.captureException() 72 | return [] 73 | 74 | def obj_create(self, bundle, **kwargs): 75 | try: 76 | raise Exception('oops') 77 | except: 78 | client.captureException() 79 | 80 | bundle.obj = Item(**kwargs) 81 | bundle = self.full_hydrate(bundle) 82 | return bundle 83 | 84 | def obj_update(self, bundle, **kwargs): 85 | return self.obj_create(bundle, **kwargs) 86 | -------------------------------------------------------------------------------- /raven/contrib/django/management/commands/raven.py: -------------------------------------------------------------------------------- 1 | """ 2 | raven.contrib.django.management.commands.raven 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2010-2016 by the Sentry Team, see AUTHORS for more details 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | from __future__ import absolute_import, print_function 9 | 10 | from django.core.management.base import BaseCommand 11 | from optparse import make_option 12 | from raven.scripts.runner import store_json, send_test_message 13 | 14 | import argparse 15 | import django 16 | import json 17 | import sys 18 | import time 19 | 20 | DJANGO_18 = django.VERSION >= (1, 8, 0) 21 | 22 | 23 | class StoreJsonAction(argparse.Action): 24 | def __call__(self, parser, namespace, values, option_string=None): 25 | try: 26 | value = json.loads(values[0]) 27 | except ValueError: 28 | print("Invalid JSON was used for option %s. Received: %s" % (self.dest, values[0])) 29 | sys.exit(1) 30 | 31 | setattr(namespace, self.dest, value) 32 | 33 | 34 | class Command(BaseCommand): 35 | help = 'Commands to interact with the Sentry client' 36 | 37 | if not DJANGO_18: 38 | option_list = BaseCommand.option_list + ( 39 | make_option( 40 | '--data', action='callback', callback=store_json, 41 | type='string', nargs=1, dest='data'), 42 | make_option( 43 | '--tags', action='callback', callback=store_json, 44 | type='string', nargs=1, dest='tags'), 45 | ) 46 | else: 47 | def add_arguments(self, parser): 48 | parser.add_argument( 49 | 'command', nargs=1, 50 | ) 51 | parser.add_argument( 52 | '--data', action=StoreJsonAction, 53 | nargs=1, dest='data', 54 | ) 55 | parser.add_argument( 56 | '--tags', action=StoreJsonAction, 57 | nargs=1, dest='tags', 58 | ) 59 | 60 | def handle(self, command=None, *args, **options): 61 | if command not in ('test', ['test']): 62 | print('Usage: manage.py raven test') 63 | sys.exit(1) 64 | 65 | from raven.contrib.django.models import client 66 | 67 | send_test_message(client, { 68 | 'tags': options.get('tags'), 69 | 'data': options.get('data'), 70 | }) 71 | time.sleep(3) 72 | -------------------------------------------------------------------------------- /raven/contrib/django/serializers.py: -------------------------------------------------------------------------------- 1 | """ 2 | raven.contrib.django.serializers 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details. 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | from __future__ import absolute_import 9 | from __future__ import unicode_literals 10 | 11 | from django.conf import settings 12 | from django.http import HttpRequest 13 | from django.utils.functional import Promise 14 | from raven.utils.serializer import Serializer, register 15 | from raven.utils.compat import text_type 16 | 17 | __all__ = ('PromiseSerializer',) 18 | 19 | 20 | class PromiseSerializer(Serializer): 21 | types = (Promise,) 22 | 23 | def can(self, value): 24 | if not super(PromiseSerializer, self).can(value): 25 | return False 26 | 27 | pre = value.__class__.__name__[1:] 28 | if not (hasattr(value, '%s__func' % pre) 29 | or hasattr(value, '%s__unicode_cast' % pre) 30 | or hasattr(value, '%s__text_cast' % pre)): 31 | return False 32 | 33 | return True 34 | 35 | def serialize(self, value, **kwargs): 36 | # EPIC HACK 37 | # handles lazy model instances (which are proxy values that don't 38 | # easily give you the actual function) 39 | pre = value.__class__.__name__[1:] 40 | if hasattr(value, '%s__func' % pre): 41 | value = getattr(value, '%s__func' % pre)( 42 | *getattr(value, '%s__args' % pre), 43 | **getattr(value, '%s__kw' % pre)) 44 | else: 45 | return self.recurse(text_type(value)) 46 | return self.recurse(value, **kwargs) 47 | 48 | 49 | register(PromiseSerializer) 50 | 51 | 52 | class HttpRequestSerializer(Serializer): 53 | types = (HttpRequest,) 54 | 55 | def serialize(self, value, **kwargs): 56 | return '<%s at 0x%s>' % (type(value).__name__, id(value)) 57 | 58 | 59 | register(HttpRequestSerializer) 60 | 61 | 62 | if getattr(settings, 'DATABASES', None): 63 | from django.db.models.query import QuerySet 64 | 65 | class QuerySetSerializer(Serializer): 66 | types = (QuerySet,) 67 | 68 | def serialize(self, value, **kwargs): 69 | qs_name = type(value).__name__ 70 | if value.model: 71 | return '<%s: model=%s>' % (qs_name, value.model.__name__) 72 | return '<%s: (Unbound)>' % (qs_name,) 73 | 74 | register(QuerySetSerializer) 75 | -------------------------------------------------------------------------------- /raven/contrib/webpy/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | raven.contrib.webpy 3 | ~~~~~~~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2013 by the Sentry Team, see AUTHORS for more details. 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | from __future__ import absolute_import 9 | 10 | import sys 11 | 12 | import web 13 | 14 | from raven.conf import setup_logging 15 | from raven.handlers.logging import SentryHandler 16 | from raven.contrib.webpy.utils import get_data_from_request 17 | 18 | 19 | class SentryApplication(web.application): 20 | """ 21 | Web.py application for Sentry. 22 | 23 | >>> sentry = Sentry(client, mapping=urls, fvars=globals()) 24 | 25 | Automatically configure logging:: 26 | 27 | >>> sentry = Sentry(client, logging=True, mapping=urls, fvars=globals()) 28 | 29 | Capture an exception:: 30 | 31 | >>> try: 32 | >>> 1 / 0 33 | >>> except ZeroDivisionError: 34 | >>> sentry.captureException() 35 | 36 | Capture a message:: 37 | 38 | >>> sentry.captureMessage('hello, world!') 39 | """ 40 | 41 | def __init__(self, client, logging=False, **kwargs): 42 | self.client = client 43 | self.logging = logging 44 | if self.logging: 45 | setup_logging(SentryHandler(self.client)) 46 | web.application.__init__(self, **kwargs) 47 | 48 | def handle_exception(self, *args, **kwargs): 49 | self.client.captureException( 50 | exc_info=kwargs.get('exc_info'), 51 | data=get_data_from_request(), 52 | extra={ 53 | 'app': self, 54 | }, 55 | ) 56 | 57 | def handle(self): 58 | try: 59 | return web.application.handle(self) 60 | except Exception: 61 | self.handle_exception(exc_info=sys.exc_info()) 62 | raise 63 | 64 | def captureException(self, *args, **kwargs): 65 | assert self.client, 'captureException called before application configured' 66 | data = kwargs.get('data') 67 | if data is None: 68 | kwargs['data'] = get_data_from_request() 69 | 70 | return self.client.captureException(*args, **kwargs) 71 | 72 | def captureMessage(self, *args, **kwargs): 73 | assert self.client, 'captureMessage called before application configured' 74 | data = kwargs.get('data') 75 | if data is None: 76 | kwargs['data'] = get_data_from_request() 77 | 78 | return self.client.captureMessage(*args, **kwargs) 79 | -------------------------------------------------------------------------------- /raven/utils/conf.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | import copy 4 | import os 5 | 6 | from raven.utils.compat import string_types 7 | from raven.utils.imports import import_string 8 | 9 | 10 | def convert_options(settings, defaults=None): 11 | """ 12 | Convert a settings object (or dictionary) to parameters which may be passed 13 | to a new ``Client()`` instance. 14 | """ 15 | if defaults is None: 16 | defaults = {} 17 | 18 | if isinstance(settings, dict): 19 | def getopt(key, default=None): 20 | return settings.get( 21 | 'SENTRY_%s' % key.upper(), 22 | defaults.get(key, default) 23 | ) 24 | 25 | options = copy.copy( 26 | settings.get('SENTRY_CONFIG') 27 | or settings.get('RAVEN_CONFIG') 28 | or {} 29 | ) 30 | else: 31 | def getopt(key, default=None): 32 | return getattr(settings, 'SENTRY_%s' % key.upper(), defaults.get(key, default)) 33 | 34 | options = copy.copy( 35 | getattr(settings, 'SENTRY_CONFIG', None) 36 | or getattr(settings, 'RAVEN_CONFIG', None) 37 | or {} 38 | ) 39 | 40 | options.setdefault('include_paths', getopt('include_paths', [])) 41 | options.setdefault('exclude_paths', getopt('exclude_paths', [])) 42 | options.setdefault('timeout', getopt('timeout')) 43 | options.setdefault('name', getopt('name')) 44 | options.setdefault('auto_log_stacks', getopt('auto_log_stacks')) 45 | options.setdefault('string_max_length', getopt('string_max_length')) 46 | options.setdefault('list_max_length', getopt('list_max_length')) 47 | options.setdefault('site', getopt('site')) 48 | options.setdefault('processors', getopt('processors')) 49 | options.setdefault('sanitize_keys', getopt('sanitize_keys')) 50 | options.setdefault('dsn', getopt('dsn', os.environ.get('SENTRY_DSN'))) 51 | options.setdefault('context', getopt('context')) 52 | options.setdefault('tags', getopt('tags')) 53 | options.setdefault('release', getopt('release')) 54 | options.setdefault('repos', getopt('repos')) 55 | options.setdefault('environment', getopt('environment')) 56 | options.setdefault('ignore_exceptions', getopt('ignore_exceptions')) 57 | options.setdefault('sample_rate', getopt('sample_rate')) 58 | 59 | transport = getopt('transport') or options.get('transport') 60 | if isinstance(transport, string_types): 61 | transport = import_string(transport) 62 | options['transport'] = transport 63 | 64 | return options 65 | -------------------------------------------------------------------------------- /raven/transport/twisted.py: -------------------------------------------------------------------------------- 1 | """ 2 | raven.transport.twisted 3 | ~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details. 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | from __future__ import absolute_import 9 | 10 | 11 | from raven.utils.compat import BytesIO 12 | from raven.transport.base import AsyncTransport 13 | from raven.transport.http import HTTPTransport 14 | 15 | try: 16 | from twisted.web.client import ( 17 | Agent, FileBodyProducer, HTTPConnectionPool, ResponseNeverReceived, 18 | readBody, 19 | ) 20 | from twisted.web.http_headers import Headers 21 | has_twisted = True 22 | except ImportError: 23 | has_twisted = False 24 | 25 | 26 | class TwistedHTTPTransport(AsyncTransport, HTTPTransport): 27 | scheme = ['twisted+http', 'twisted+https'] 28 | 29 | def __init__(self, *args, **kwargs): 30 | if not has_twisted: 31 | raise ImportError('TwistedHTTPTransport requires twisted.web.') 32 | 33 | super(TwistedHTTPTransport, self).__init__(*args, **kwargs) 34 | 35 | # Import reactor as late as possible. 36 | from twisted.internet import reactor 37 | 38 | # Use a persistent connection pool. 39 | self._agent = Agent(reactor, pool=HTTPConnectionPool(reactor)) 40 | 41 | def async_send(self, url, data, headers, success_cb, failure_cb): 42 | d = self._agent.request( 43 | b"POST", url, 44 | bodyProducer=FileBodyProducer(BytesIO(data)), 45 | headers=Headers(dict((k, [v]) for k, v in headers.items())) 46 | ) 47 | 48 | def on_failure(failure): 49 | ex = failure.check(ResponseNeverReceived) 50 | if ex: 51 | # ResponseNeverReceived wraps the actual error(s). 52 | failure_cb([f.value for f in failure.value.reasons]) 53 | else: 54 | failure_cb(failure.value) 55 | 56 | def on_success(response): 57 | """ 58 | Success only means that the request succeeded, *not* that the 59 | actual submission was successful. 60 | """ 61 | if response.code == 200: 62 | success_cb() 63 | else: 64 | def on_error_body(body): 65 | failure_cb(Exception(response.code, response.phrase, body)) 66 | 67 | return readBody(response).addCallback( 68 | on_error_body, 69 | ) 70 | 71 | d.addCallback( 72 | on_success, 73 | ).addErrback( 74 | on_failure, 75 | ) 76 | -------------------------------------------------------------------------------- /tests/contrib/zconfig/tests.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | import logging 3 | import mock 4 | import unittest 5 | from ZConfig import configureLoggers 6 | 7 | 8 | class TestConfig(unittest.TestCase): 9 | 10 | @mock.patch("raven.handlers.logging.Client") 11 | def test_minimal(self, Client): 12 | configureLoggers(""" 13 | 14 | %import raven.contrib.zconfig 15 | 16 | dsn https://abc:def@example.com/42 17 | 18 | 19 | """) 20 | handler = logging.getLogger().handlers[-1] 21 | logging.getLogger().handlers.remove(handler) 22 | self.assertEqual(handler.level, logging.ERROR) 23 | Client.assert_called_with( 24 | dsn='https://abc:def@example.com/42', 25 | site=None, 26 | name=None, 27 | release=None, 28 | environment=None, 29 | exclude_paths=None, 30 | include_paths=None, 31 | sample_rate=1.0, 32 | list_max_length=None, 33 | string_max_length=None, 34 | auto_log_stacks=None, 35 | processors=None, 36 | level=logging.ERROR) 37 | 38 | @mock.patch("raven.handlers.logging.Client") 39 | def test_many(self, Client): 40 | configureLoggers(""" 41 | 42 | %import raven.contrib.zconfig 43 | 44 | level WARNING 45 | dsn https://abc:def@example.com/42 46 | site test-site 47 | name test 48 | release 42.0 49 | environment testing 50 | exclude_paths /a /b 51 | include_paths /c /d 52 | sample_rate 0.5 53 | list_max_length 9 54 | string_max_length 99 55 | auto_log_stacks true 56 | processors x y z 57 | 58 | 59 | """) 60 | handler = logging.getLogger().handlers[-1] 61 | logging.getLogger().handlers.remove(handler) 62 | self.assertEqual(handler.level, logging.WARNING) 63 | Client.assert_called_with( 64 | dsn='https://abc:def@example.com/42', 65 | site='test-site', 66 | name='test', 67 | release='42.0', 68 | environment='testing', 69 | exclude_paths=['/a', '/b'], 70 | include_paths=['/c', '/d'], 71 | sample_rate=0.5, 72 | list_max_length=9, 73 | string_max_length=99, 74 | auto_log_stacks=True, 75 | processors=['x', 'y', 'z'], 76 | level=logging.WARNING) 77 | -------------------------------------------------------------------------------- /tests/transport/tornado/tests.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | import mock 4 | 5 | from raven.base import Client 6 | from tornado import gen, testing, httpclient 7 | 8 | 9 | class TornadoTransportTests(testing.AsyncTestCase): 10 | 11 | def get_new_ioloop(self): 12 | io_loop = super(TornadoTransportTests, self).get_new_ioloop() 13 | io_loop.make_current() 14 | return io_loop 15 | 16 | @mock.patch("raven.transport.tornado.HTTPClient") 17 | def test_send(self, fake_client): 18 | url = "https://user:pass@host:1234/1" 19 | timeout = 1 20 | verify_ssl = 1 21 | ca_certs = "/some/path/somefile" 22 | 23 | fake = fake_client.return_value 24 | raven_client = Client( 25 | dsn="tornado+{0}?timeout={1}&verify_ssl={2}&ca_certs={3}". 26 | format(url, timeout, verify_ssl, ca_certs)) 27 | 28 | raven_client.captureMessage(message="test") 29 | 30 | # make sure an instance of HTTPClient was created, since we are not in 31 | # an IOLoop 32 | fake_client.assert_called_once_with() 33 | fake_fetch = fake.fetch 34 | 35 | # make sure we called fetch() which does the sending 36 | self.assertEqual(fake_fetch.call_count, 1) 37 | # only verify the special kwargs that we should be passing through, 38 | # no need to verify the urls and whatnot 39 | args, kwargs = fake_fetch.call_args 40 | self.assertEqual(kwargs["connect_timeout"], timeout) 41 | self.assertEqual(kwargs["validate_cert"], bool(verify_ssl)) 42 | self.assertEqual(kwargs["ca_certs"], ca_certs) 43 | 44 | @testing.gen_test 45 | def test__sending_with_error_calls_error_callback(self): 46 | c = Client(dsn='tornado+http://uver:pass@localhost:46754/1') 47 | 48 | with mock.patch.object(Client, '_failed_send') as mock_failed: 49 | c.captureMessage(message='test') 50 | yield gen.sleep(0.01) # we need to run after the async send 51 | 52 | assert mock_failed.called 53 | 54 | @testing.gen_test 55 | def test__sending_successfully_calls_success_callback(self): 56 | c = Client(dsn='tornado+http://uver:pass@localhost:46754/1') 57 | with mock.patch.object(Client, '_successful_send') as mock_successful: 58 | with mock.patch.object(httpclient.AsyncHTTPClient, 'fetch') as mock_fetch: 59 | mock_fetch.return_value = gen.maybe_future(True) 60 | c.captureMessage(message='test') 61 | yield gen.sleep(0.01) # we need to run after the async send 62 | 63 | assert mock_successful.called 64 | -------------------------------------------------------------------------------- /tests/contrib/bottle/tests.py: -------------------------------------------------------------------------------- 1 | from exam import fixture 2 | 3 | from webtest import TestApp as WebtestApp # prevent pytest-warning 4 | 5 | import bottle 6 | 7 | from raven.base import Client 8 | from raven.contrib.bottle import Sentry 9 | from raven.utils.testutils import TestCase 10 | 11 | 12 | class TempStoreClient(Client): 13 | def __init__(self, **kwargs): 14 | self.events = [] 15 | super(TempStoreClient, self).__init__(**kwargs) 16 | 17 | def is_enabled(self): 18 | return True 19 | 20 | def send(self, **kwargs): 21 | self.events.append(kwargs) 22 | 23 | 24 | def create_app(raven): 25 | app = bottle.app() 26 | app.catchall = False 27 | app = Sentry(app, client=raven) 28 | tapp = WebtestApp(app) 29 | 30 | @bottle.route('/error/', ['GET', 'POST']) 31 | def an_error(): 32 | raise ValueError('hello world') 33 | 34 | @bottle.route('/capture/', ['GET', 'POST']) 35 | def capture_exception(): 36 | try: 37 | raise ValueError('Boom') 38 | except: 39 | tapp.app.captureException() 40 | return 'Hello' 41 | 42 | @bottle.route('/message/', ['GET', 'POST']) 43 | def capture_message(): 44 | tapp.app.captureMessage('Interesting') 45 | return 'World' 46 | 47 | return tapp 48 | 49 | 50 | class BaseTest(TestCase): 51 | @fixture 52 | def app(self): 53 | self.raven = TempStoreClient() 54 | return create_app(self.raven) 55 | 56 | @fixture 57 | def client(self): 58 | return self.app 59 | 60 | 61 | class BottleTest(BaseTest): 62 | def test_error(self): 63 | self.assertRaises(ValueError, self.client.get, '/error/?foo=bar') 64 | 65 | self.assertEquals(len(self.raven.events), 1) 66 | event = self.raven.events.pop(0) 67 | assert 'exception' in event 68 | 69 | exc = event['exception']['values'][-1] 70 | self.assertEquals(exc['type'], 'ValueError') 71 | 72 | def test_captureException_captures_http(self): 73 | response = self.client.get('/capture/?foo=bar') 74 | self.assertEquals(response.status_code, 200) 75 | self.assertEquals(len(self.raven.events), 1) 76 | 77 | event = self.raven.events.pop(0) 78 | 79 | assert event['message'] == 'ValueError: Boom' 80 | assert 'request' in event 81 | assert 'exception' in event 82 | 83 | def test_captureMessage_captures_http(self): 84 | response = self.client.get('/message/?foo=bar') 85 | self.assertEquals(response.status_code, 200) 86 | self.assertEquals(len(self.raven.events), 1) 87 | 88 | event = self.raven.events.pop(0) 89 | 90 | assert 'sentry.interfaces.Message' in event 91 | assert 'request' in event 92 | -------------------------------------------------------------------------------- /raven/versioning.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | import os.path 4 | 5 | from raven.utils.compat import text_type 6 | from .exceptions import InvalidGitRepository 7 | 8 | __all__ = ('fetch_git_sha', 'fetch_package_version') 9 | 10 | 11 | def fetch_git_sha(path, head=None): 12 | """ 13 | >>> fetch_git_sha(os.path.dirname(__file__)) 14 | """ 15 | if not head: 16 | head_path = os.path.join(path, '.git', 'HEAD') 17 | if not os.path.exists(head_path): 18 | raise InvalidGitRepository( 19 | 'Cannot identify HEAD for git repository at %s' % (path,)) 20 | 21 | with open(head_path, 'r') as fp: 22 | head = text_type(fp.read()).strip() 23 | 24 | if head.startswith('ref: '): 25 | head = head[5:] 26 | revision_file = os.path.join( 27 | path, '.git', *head.split('/') 28 | ) 29 | else: 30 | return head 31 | else: 32 | revision_file = os.path.join(path, '.git', 'refs', 'heads', head) 33 | 34 | if not os.path.exists(revision_file): 35 | if not os.path.exists(os.path.join(path, '.git')): 36 | raise InvalidGitRepository( 37 | '%s does not seem to be the root of a git repository' % (path,)) 38 | 39 | # Check for our .git/packed-refs' file since a `git gc` may have run 40 | # https://git-scm.com/book/en/v2/Git-Internals-Maintenance-and-Data-Recovery 41 | packed_file = os.path.join(path, '.git', 'packed-refs') 42 | if os.path.exists(packed_file): 43 | with open(packed_file) as fh: 44 | for line in fh: 45 | line = line.rstrip() 46 | if line and line[:1] not in ('#', '^'): 47 | try: 48 | revision, ref = line.split(' ', 1) 49 | except ValueError: 50 | continue 51 | if ref == head: 52 | return text_type(revision) 53 | 54 | raise InvalidGitRepository( 55 | 'Unable to find ref to head "%s" in repository' % (head,)) 56 | 57 | with open(revision_file) as fh: 58 | return text_type(fh.read()).strip() 59 | 60 | 61 | def fetch_package_version(dist_name): 62 | """ 63 | >>> fetch_package_version('sentry') 64 | """ 65 | try: 66 | # Importing pkg_resources can be slow, so only import it 67 | # if we need it. 68 | import pkg_resources 69 | except ImportError: 70 | # pkg_resource is not available on Google App Engine 71 | raise NotImplementedError('pkg_resources is not available ' 72 | 'on this Python install') 73 | dist = pkg_resources.get_distribution(dist_name) 74 | return dist.version 75 | -------------------------------------------------------------------------------- /raven/transport/registry.py: -------------------------------------------------------------------------------- 1 | """ 2 | raven.transport.registry 3 | ~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details. 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | from __future__ import absolute_import 9 | 10 | # TODO(dcramer): we really should need to import all of these by default 11 | from raven.transport.eventlet import EventletHTTPTransport 12 | from raven.transport.exceptions import DuplicateScheme 13 | from raven.transport.http import HTTPTransport 14 | from raven.transport.gevent import GeventedHTTPTransport 15 | from raven.transport.requests import RequestsHTTPTransport 16 | from raven.transport.threaded import ThreadedHTTPTransport 17 | from raven.transport.threaded_requests import ThreadedRequestsHTTPTransport 18 | from raven.transport.twisted import TwistedHTTPTransport 19 | from raven.transport.tornado import TornadoHTTPTransport 20 | from raven.utils import urlparse 21 | 22 | 23 | class TransportRegistry(object): 24 | def __init__(self, transports=None): 25 | # setup a default list of senders 26 | self._schemes = {} 27 | self._transports = {} 28 | 29 | if transports: 30 | for transport in transports: 31 | self.register_transport(transport) 32 | 33 | def register_transport(self, transport): 34 | if not hasattr(transport, 'scheme') or not hasattr(transport.scheme, '__iter__'): 35 | raise AttributeError('Transport %s must have a scheme list', transport.__class__.__name__) 36 | 37 | for scheme in transport.scheme: 38 | self.register_scheme(scheme, transport) 39 | 40 | def register_scheme(self, scheme, cls): 41 | """ 42 | It is possible to inject new schemes at runtime 43 | """ 44 | if scheme in self._schemes: 45 | raise DuplicateScheme() 46 | 47 | urlparse.register_scheme(scheme) 48 | # TODO (vng): verify the interface of the new class 49 | self._schemes[scheme] = cls 50 | 51 | def supported_scheme(self, scheme): 52 | return scheme in self._schemes 53 | 54 | def get_transport(self, parsed_url, **options): 55 | full_url = parsed_url.geturl() 56 | if full_url not in self._transports: 57 | # Remove the options from the parsed_url 58 | parsed_url = urlparse.urlparse(full_url.split('?')[0]) 59 | self._transports[full_url] = self._schemes[parsed_url.scheme](parsed_url, **options) 60 | return self._transports[full_url] 61 | 62 | def get_transport_cls(self, scheme): 63 | return self._schemes[scheme] 64 | 65 | 66 | default_transports = [ 67 | HTTPTransport, 68 | ThreadedHTTPTransport, 69 | GeventedHTTPTransport, 70 | TwistedHTTPTransport, 71 | RequestsHTTPTransport, 72 | ThreadedRequestsHTTPTransport, 73 | TornadoHTTPTransport, 74 | EventletHTTPTransport, 75 | ] 76 | -------------------------------------------------------------------------------- /raven/contrib/zerorpc/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | raven.contrib.zerorpc 3 | ~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2010-2013 by the Sentry Team, see AUTHORS for more details. 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | from __future__ import absolute_import 9 | 10 | import inspect 11 | 12 | from raven.base import Client 13 | 14 | 15 | class SentryMiddleware(object): 16 | """Sentry/Raven middleware for ZeroRPC. 17 | 18 | >>> import zerorpc 19 | >>> from raven.contrib.zerorpc import SentryMiddleware 20 | >>> sentry = SentryMiddleware(dsn='udp://..../') 21 | >>> zerorpc.Context.get_instance().register_middleware(sentry) 22 | 23 | Exceptions detected server-side in ZeroRPC will be submitted to Sentry (and 24 | propagated to the client as well). 25 | """ 26 | 27 | def __init__(self, hide_zerorpc_frames=True, client=None, **kwargs): 28 | """ 29 | Create a middleware object that can be injected in a ZeroRPC server. 30 | 31 | - hide_zerorpc_frames: modify the exception stacktrace to remove the 32 | internal zerorpc frames (True by default to make 33 | the stacktrace as readable as possible); 34 | - client: use an existing raven.Client object, otherwise one will be 35 | instantiated from the keyword arguments. 36 | """ 37 | self._sentry_client = client or Client(**kwargs) 38 | self._hide_zerorpc_frames = hide_zerorpc_frames 39 | 40 | def server_inspect_exception(self, req_event, rep_event, task_ctx, exc_info): 41 | """ 42 | Called when an exception has been raised in the code run by ZeroRPC 43 | """ 44 | # Hide the zerorpc internal frames for readability, for a REQ/REP or 45 | # REQ/STREAM server the frames to hide are: 46 | # - core.ServerBase._async_task 47 | # - core.Pattern*.process_call 48 | # - core.DecoratorBase.__call__ 49 | # 50 | # For a PUSH/PULL or PUB/SUB server the frame to hide is: 51 | # - core.Puller._receiver 52 | if self._hide_zerorpc_frames: 53 | traceback = exc_info[2] 54 | while traceback: 55 | zerorpc_frame = traceback.tb_frame 56 | zerorpc_frame.f_locals['__traceback_hide__'] = True 57 | frame_info = inspect.getframeinfo(zerorpc_frame) 58 | # Is there a better way than this (or looking up the filenames 59 | # or hardcoding the number of frames to skip) to know when we 60 | # are out of zerorpc? 61 | if frame_info.function == '__call__' \ 62 | or frame_info.function == '_receiver': 63 | break 64 | traceback = traceback.tb_next 65 | 66 | self._sentry_client.captureException( 67 | exc_info, 68 | extra=task_ctx 69 | ) 70 | -------------------------------------------------------------------------------- /raven/utils/basic.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | try: 4 | from collections.abc import Mapping 5 | except ImportError: 6 | # Python < 3.3 7 | from collections import Mapping 8 | 9 | from functools import update_wrapper 10 | import threading 11 | 12 | from raven.utils.compat import iteritems 13 | 14 | 15 | def merge_dicts(*dicts): 16 | out = {} 17 | for d in dicts: 18 | if not d: 19 | continue 20 | 21 | for k, v in iteritems(d): 22 | out[k] = v 23 | return out 24 | 25 | 26 | def varmap(func, var, context=None, name=None): 27 | """ 28 | Executes ``func(key_name, value)`` on all values 29 | recurisively discovering dict and list scoped 30 | values. 31 | """ 32 | if context is None: 33 | context = {} 34 | objid = id(var) 35 | if objid in context: 36 | return func(name, '<...>') 37 | context[objid] = 1 38 | 39 | if isinstance(var, (list, tuple)) and not is_namedtuple(var): 40 | ret = [varmap(func, f, context, name) for f in var] 41 | else: 42 | ret = func(name, var) 43 | if isinstance(ret, Mapping): 44 | ret = dict((k, varmap(func, v, context, k)) 45 | for k, v in iteritems(var)) 46 | del context[objid] 47 | return ret 48 | 49 | 50 | class memoize(object): 51 | """ 52 | Memoize the result of a property call. 53 | 54 | >>> class A(object): 55 | >>> @memoize 56 | >>> def func(self): 57 | >>> return 'foo' 58 | """ 59 | 60 | def __init__(self, func): 61 | self.__name__ = func.__name__ 62 | self.__module__ = func.__module__ 63 | self.__doc__ = func.__doc__ 64 | self.func = func 65 | 66 | def __get__(self, obj, type=None): 67 | if obj is None: 68 | return self 69 | d, n = vars(obj), self.__name__ 70 | if n not in d: 71 | d[n] = self.func(obj) 72 | return d[n] 73 | 74 | 75 | def once(func): 76 | """Runs a thing once and once only.""" 77 | lock = threading.Lock() 78 | 79 | def new_func(*args, **kwargs): 80 | if new_func.called: 81 | return 82 | with lock: 83 | if new_func.called: 84 | return 85 | rv = func(*args, **kwargs) 86 | new_func.called = True 87 | return rv 88 | 89 | new_func = update_wrapper(new_func, func) 90 | new_func.called = False 91 | return new_func 92 | 93 | 94 | def is_namedtuple(value): 95 | # https://stackoverflow.com/a/2166841/1843746 96 | # But modified to handle subclasses of namedtuples. 97 | if not isinstance(value, tuple): 98 | return False 99 | f = getattr(type(value), '_fields', None) 100 | if not isinstance(f, tuple): 101 | return False 102 | return all(type(n) == str for n in f) 103 | -------------------------------------------------------------------------------- /tests/contrib/webpy/tests.py: -------------------------------------------------------------------------------- 1 | from exam import fixture 2 | from paste.fixture import TestApp as PasteTestApp # prevent pytest-warning 3 | 4 | from raven.base import Client 5 | from raven.contrib.webpy import SentryApplication 6 | from raven.utils.testutils import TestCase 7 | 8 | 9 | class TempStoreClient(Client): 10 | def __init__(self, **kwargs): 11 | self.events = [] 12 | super(TempStoreClient, self).__init__(**kwargs) 13 | 14 | def is_enabled(self): 15 | return True 16 | 17 | def send(self, **kwargs): 18 | self.events.append(kwargs) 19 | 20 | 21 | class TestEndpoint(object): 22 | def GET(self): 23 | raise ValueError('That\'s what she said') 24 | 25 | def POST(self): 26 | raise TypeError('Potato') 27 | 28 | 29 | urls = ( 30 | '/test', TestEndpoint 31 | ) 32 | 33 | 34 | def create_app(client): 35 | return SentryApplication(client=client, mapping=urls) 36 | 37 | 38 | class WebPyTest(TestCase): 39 | @fixture 40 | def app(self): 41 | self.store = TempStoreClient() 42 | return create_app(self.store) 43 | 44 | @fixture 45 | def client(self): 46 | return PasteTestApp(self.app.wsgifunc()) 47 | 48 | def test_get(self): 49 | resp = self.client.get('/test', expect_errors=True) 50 | 51 | self.assertEquals(resp.status, 500) 52 | self.assertEquals(len(self.store.events), 1) 53 | 54 | event = self.store.events.pop() 55 | assert 'exception' in event 56 | exc = event['exception']['values'][-1] 57 | self.assertEquals(exc['type'], 'ValueError') 58 | self.assertEquals(exc['value'], 'That\'s what she said') 59 | self.assertEquals(event['message'], 'ValueError: That\'s what she said') 60 | 61 | def test_post(self): 62 | response = self.client.post('/test?biz=baz', params={'foo': 'bar'}, expect_errors=True) 63 | self.assertEquals(response.status, 500) 64 | self.assertEquals(len(self.store.events), 1) 65 | 66 | event = self.store.events.pop() 67 | 68 | assert 'request' in event 69 | http = event['request'] 70 | self.assertEquals(http['url'], 'http://localhost/test') 71 | self.assertEquals(http['query_string'], '?biz=baz') 72 | self.assertEquals(http['method'], 'POST') 73 | self.assertEquals(http['data'], 'foo=bar') 74 | self.assertTrue('headers' in http) 75 | headers = http['headers'] 76 | self.assertTrue('Content-Length' in headers, headers.keys()) 77 | self.assertEquals(headers['Content-Length'], '7') 78 | self.assertTrue('Content-Type' in headers, headers.keys()) 79 | self.assertEquals(headers['Content-Type'], 'application/x-www-form-urlencoded') 80 | self.assertTrue('Host' in headers, headers.keys()) 81 | self.assertEquals(headers['Host'], 'localhost') 82 | env = http['env'] 83 | self.assertTrue('SERVER_NAME' in env, env.keys()) 84 | self.assertEquals(env['SERVER_NAME'], 'localhost') 85 | -------------------------------------------------------------------------------- /raven/utils/serializer/manager.py: -------------------------------------------------------------------------------- 1 | """ 2 | raven.utils.serializer.manager 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details. 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | from __future__ import absolute_import 9 | 10 | import logging 11 | from contextlib import closing 12 | from raven.utils.compat import text_type 13 | 14 | __all__ = ('register', 'transform') 15 | 16 | logger = logging.getLogger('sentry.errors.serializer') 17 | 18 | 19 | class SerializationManager(object): 20 | logger = logger 21 | 22 | def __init__(self): 23 | self.__registry = [] 24 | self.__serializers = {} 25 | 26 | @property 27 | def serializers(self): 28 | # XXX: Would serializers ever need state that we shouldn't cache them? 29 | for serializer in self.__registry: 30 | yield serializer 31 | 32 | def register(self, serializer): 33 | if serializer not in self.__registry: 34 | self.__registry.append(serializer) 35 | return serializer 36 | 37 | 38 | class Serializer(object): 39 | logger = logger 40 | 41 | def __init__(self, manager): 42 | self.manager = manager 43 | self.context = set() 44 | self.serializers = [] 45 | for serializer in manager.serializers: 46 | self.serializers.append(serializer(self)) 47 | 48 | def close(self): 49 | del self.serializers 50 | del self.context 51 | 52 | def transform(self, value, **kwargs): 53 | """ 54 | Primary function which handles recursively transforming 55 | values via their serializers 56 | """ 57 | if value is None: 58 | return None 59 | 60 | objid = id(value) 61 | if objid in self.context: 62 | return '<...>' 63 | self.context.add(objid) 64 | 65 | try: 66 | for serializer in self.serializers: 67 | try: 68 | if serializer.can(value): 69 | return serializer.serialize(value, **kwargs) 70 | except Exception as e: 71 | logger.exception(e) 72 | return text_type(type(value)) 73 | 74 | # if all else fails, lets use the repr of the object 75 | try: 76 | return repr(value) 77 | except Exception as e: 78 | logger.exception(e) 79 | # It's common case that a model's __unicode__ definition 80 | # may try to query the database which if it was not 81 | # cleaned up correctly, would hit a transaction aborted 82 | # exception 83 | return text_type(type(value)) 84 | finally: 85 | self.context.remove(objid) 86 | 87 | 88 | manager = SerializationManager() 89 | register = manager.register 90 | 91 | 92 | def transform(value, manager=manager, **kwargs): 93 | with closing(Serializer(manager)) as serializer: 94 | return serializer.transform(value, **kwargs) 95 | -------------------------------------------------------------------------------- /tests/contrib/zerorpc/tests.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pytest 3 | import random 4 | import shutil 5 | import tempfile 6 | from raven.utils.testutils import TestCase 7 | 8 | from raven.base import Client 9 | from raven.contrib.zerorpc import SentryMiddleware 10 | 11 | zerorpc = pytest.importorskip("zerorpc") 12 | gevent = pytest.importorskip("gevent") 13 | 14 | 15 | class TempStoreClient(Client): 16 | def __init__(self, **kwargs): 17 | self.events = [] 18 | super(TempStoreClient, self).__init__(**kwargs) 19 | 20 | def is_enabled(self): 21 | return True 22 | 23 | def send(self, **kwargs): 24 | self.events.append(kwargs) 25 | 26 | 27 | class ZeroRPCTest(TestCase): 28 | def setUp(self): 29 | self._socket_dir = tempfile.mkdtemp(prefix='ravenzerorpcunittest') 30 | self._server_endpoint = 'ipc://{0}'.format( 31 | os.path.join(self._socket_dir, 'random_zeroserver')) 32 | 33 | self._sentry = TempStoreClient() 34 | zerorpc.Context.get_instance().register_middleware( 35 | SentryMiddleware(client=self._sentry)) 36 | 37 | def test_zerorpc_middleware_with_reqrep(self): 38 | self._server = zerorpc.Server(random) 39 | self._server.bind(self._server_endpoint) 40 | gevent.spawn(self._server.run) 41 | 42 | self._client = zerorpc.Client() 43 | self._client.connect(self._server_endpoint) 44 | 45 | try: 46 | self._client.choice([]) 47 | except zerorpc.exceptions.RemoteError as ex: 48 | self.assertEqual(ex.name, 'IndexError') 49 | self.assertEqual(len(self._sentry.events), 1) 50 | exc = self._sentry.events[0]['exception'] 51 | self.assertEqual(exc['type'], 'IndexError') 52 | frames = exc['stacktrace']['frames'] 53 | self.assertEqual(frames[0]['function'], 'choice') 54 | self.assertEqual(frames[0]['module'], 'random') 55 | else: 56 | self.fail('An IndexError exception should have been raised an catched') 57 | 58 | def test_zerorpc_middleware_with_pushpull(self): 59 | self._server = zerorpc.Puller(random) 60 | self._server.bind(self._server_endpoint) 61 | gevent.spawn(self._server.run) 62 | 63 | self._client = zerorpc.Pusher() 64 | self._client.connect(self._server_endpoint) 65 | 66 | self._client.choice([]) 67 | 68 | for attempt in range(0, 10): 69 | gevent.sleep(0.1) 70 | if len(self._sentry.events): 71 | exc = self._sentry.events[0]['exception'] 72 | self.assertEqual(exc['type'], 'IndexError') 73 | frames = exc['stacktrace']['frames'] 74 | self.assertEqual(frames[0]['function'], 'choice') 75 | self.assertEqual(frames[0]['module'], 'random') 76 | return 77 | 78 | self.fail('An IndexError exception should have been sent to Sentry') 79 | 80 | def tearDown(self): 81 | self._client.close() 82 | self._server.close() 83 | shutil.rmtree(self._socket_dir, ignore_errors=True) 84 | -------------------------------------------------------------------------------- /raven/contrib/bottle/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | raven.contrib.bottle 3 | ~~~~~~~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2013 by the Sentry Team, see AUTHORS for more details. 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | 9 | from __future__ import absolute_import 10 | 11 | import sys 12 | 13 | from bottle import request 14 | from raven.conf import setup_logging 15 | from raven.contrib.bottle.utils import get_data_from_request 16 | from raven.handlers.logging import SentryHandler 17 | 18 | 19 | class Sentry(object): 20 | """ 21 | Bottle application for Sentry. 22 | 23 | >>> sentry = Sentry(app, client) 24 | 25 | Automatically configure logging:: 26 | 27 | >>> sentry = Sentry(app, client, logging=True) 28 | 29 | Capture an exception:: 30 | 31 | >>> try: 32 | >>> 1 / 0 33 | >>> except ZeroDivisionError: 34 | >>> sentry.captureException() 35 | 36 | Capture a message:: 37 | 38 | >>> sentry.captureMessage('hello, world!') 39 | """ 40 | 41 | def __init__(self, app, client, logging=False): 42 | self.app = app 43 | self.client = client 44 | self.logging = logging 45 | if self.logging: 46 | setup_logging(SentryHandler(self.client)) 47 | self.app.sentry = self 48 | 49 | def handle_exception(self, *args, **kwargs): 50 | self.client.captureException( 51 | exc_info=kwargs.get('exc_info'), 52 | data=get_data_from_request(request), 53 | extra={ 54 | 'app': self.app, 55 | }, 56 | ) 57 | 58 | def __call__(self, environ, start_response): 59 | def session_start_response(status, headers, exc_info=None): 60 | if exc_info is not None: 61 | self.handle_exception(exc_info=exc_info) 62 | return start_response(status, headers, exc_info) 63 | 64 | try: 65 | return self.app(environ, session_start_response) 66 | # catch ANY exception that goes through... 67 | except Exception: 68 | self.handle_exception(exc_info=sys.exc_info()) 69 | # re-raise the exception to let parent handlers deal with it 70 | raise 71 | 72 | def captureException(self, *args, **kwargs): 73 | assert self.client, 'captureException called before application configured' 74 | data = kwargs.get('data') 75 | if data is None: 76 | try: 77 | kwargs['data'] = get_data_from_request(request) 78 | except RuntimeError: 79 | # app is probably not configured yet 80 | pass 81 | return self.client.captureException(*args, **kwargs) 82 | 83 | def captureMessage(self, *args, **kwargs): 84 | assert self.client, 'captureMessage called before application configured' 85 | data = kwargs.get('data') 86 | if data is None: 87 | try: 88 | kwargs['data'] = get_data_from_request(request) 89 | except RuntimeError: 90 | # app is probably not configured yet 91 | pass 92 | return self.client.captureMessage(*args, **kwargs) 93 | -------------------------------------------------------------------------------- /tests/transport/tests.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from raven.utils.testutils import TestCase 5 | from raven.base import Client 6 | 7 | # Some internal stuff to extend the transport layer 8 | from raven.transport import Transport 9 | from raven.transport.exceptions import DuplicateScheme 10 | 11 | # Simplify comparing dicts with primitive values: 12 | from raven.utils import json 13 | 14 | import datetime 15 | import calendar 16 | import pytz 17 | import zlib 18 | 19 | 20 | class DummyScheme(Transport): 21 | 22 | scheme = ['mock'] 23 | 24 | def __init__(self, timeout=5): 25 | self.timeout = timeout 26 | 27 | def send(self, url, data, headers): 28 | """ 29 | Sends a request to a remote webserver 30 | """ 31 | self._url = url 32 | self._data = data 33 | self._headers = headers 34 | 35 | 36 | class TransportTest(TestCase): 37 | def setUp(self): 38 | try: 39 | Client.register_scheme('mock', DummyScheme) 40 | except DuplicateScheme: 41 | pass 42 | 43 | def test_basic_config(self): 44 | c = Client( 45 | dsn="mock://some_username:some_password@localhost:8143/1?timeout=1", 46 | name="test_server" 47 | ) 48 | assert c.remote.options == { 49 | 'timeout': '1', 50 | } 51 | 52 | def test_custom_transport(self): 53 | c = Client(dsn="mock://some_username:some_password@localhost:8143/1") 54 | 55 | data = dict(a=42, b=55, c=list(range(50))) 56 | c.send(**data) 57 | 58 | mock_cls = c._transport_cache['mock://some_username:some_password@localhost:8143/1'].get_transport() 59 | print(mock_cls.__dict__) 60 | 61 | expected_message = zlib.decompress(c.encode(data)) 62 | actual_message = zlib.decompress(mock_cls._data) 63 | 64 | # These loads()/dumps() pairs order the dict keys before comparing the string. 65 | # See GH504 66 | self.assertEqual( 67 | json.dumps(json.loads(expected_message.decode('utf-8')), sort_keys=True), 68 | json.dumps(json.loads(actual_message.decode('utf-8')), sort_keys=True) 69 | ) 70 | 71 | def test_build_then_send(self): 72 | c = Client( 73 | dsn="mock://some_username:some_password@localhost:8143/1", 74 | name="test_server") 75 | 76 | mydate = datetime.datetime(2012, 5, 4, tzinfo=pytz.utc) 77 | d = calendar.timegm(mydate.timetuple()) 78 | msg = c.build_msg('raven.events.Message', message='foo', date=d) 79 | expected = { 80 | 'project': '1', 81 | 'sentry.interfaces.Message': { 82 | 'message': 'foo', 83 | 'params': (), 84 | 'formatted': None, 85 | }, 86 | 'server_name': 'test_server', 87 | 'level': 40, 88 | 'tags': {}, 89 | 'time_spent': None, 90 | 'timestamp': 1336089600, 91 | 'message': 'foo', 92 | } 93 | 94 | # The event_id is always overridden 95 | del msg['event_id'] 96 | 97 | self.assertDictContainsSubset(expected, msg) 98 | -------------------------------------------------------------------------------- /tests/events/tests.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | import pytest 4 | 5 | from raven.base import Client 6 | from raven.events import Exception as ExceptionEvent 7 | from raven.utils.testutils import TestCase 8 | from raven.utils.compat import raise_from 9 | 10 | 11 | class ExceptionTest(TestCase): 12 | 13 | # Handle compatibility. 14 | if hasattr(Exception, '__suppress_context__'): 15 | # Then exception chains are supported. 16 | def transform_expected(self, expected): 17 | return expected 18 | else: 19 | # Otherwise, we only report the first element. 20 | def transform_expected(self, expected): 21 | return expected[-1:] 22 | 23 | def check_capture(self, expected): 24 | """ 25 | Check the return value of capture(). 26 | 27 | Args: 28 | expected: the expected "type" values. 29 | """ 30 | c = Client() 31 | event = ExceptionEvent(c) 32 | result = event.capture() 33 | info = result['exception'] 34 | values = info['values'] 35 | 36 | type_names = [value['type'] for value in values] 37 | expected = self.transform_expected(expected) 38 | 39 | self.assertEqual(type_names, expected) 40 | 41 | def test_simple(self): 42 | try: 43 | raise ValueError() 44 | except Exception: 45 | self.check_capture(['ValueError']) 46 | 47 | def test_nested(self): 48 | try: 49 | raise ValueError() 50 | except Exception: 51 | try: 52 | raise KeyError() 53 | except Exception: 54 | self.check_capture(['ValueError', 'KeyError']) 55 | 56 | def test_raise_from(self): 57 | try: 58 | raise ValueError() 59 | except Exception as exc: 60 | try: 61 | raise_from(KeyError(), exc) 62 | except Exception: 63 | self.check_capture(['ValueError', 'KeyError']) 64 | 65 | def test_raise_from_different(self): 66 | try: 67 | raise ValueError() 68 | except Exception as exc: 69 | try: 70 | raise_from(KeyError(), TypeError()) 71 | except Exception: 72 | self.check_capture(['TypeError', 'KeyError']) 73 | 74 | def test_handles_self_referencing(self): 75 | try: 76 | raise ValueError() 77 | except Exception as exc: 78 | try: 79 | raise_from(exc, exc) 80 | except Exception: 81 | self.check_capture(['ValueError']) 82 | else: 83 | pytest.fail() 84 | else: 85 | pytest.fail() 86 | 87 | try: 88 | raise ValueError() 89 | except Exception as exc: 90 | try: 91 | raise_from(KeyError(), exc) 92 | except KeyError as exc2: 93 | try: 94 | raise_from(exc, exc2) 95 | except Exception: 96 | self.check_capture(['KeyError', 'ValueError']) 97 | else: 98 | pytest.fail() 99 | else: 100 | pytest.fail() 101 | -------------------------------------------------------------------------------- /raven/contrib/django/views.py: -------------------------------------------------------------------------------- 1 | """ 2 | raven.contrib.django.views 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details. 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | from __future__ import absolute_import 9 | 10 | from functools import wraps 11 | 12 | from django.conf import settings 13 | from django.http import HttpResponse, HttpResponseForbidden, HttpResponseBadRequest 14 | from django.views.decorators.cache import never_cache 15 | from django.views.decorators.csrf import csrf_exempt 16 | from django.views.decorators.http import require_http_methods 17 | 18 | from raven.utils.compat import string_types 19 | from raven.contrib.django.models import client 20 | from raven.utils import json 21 | 22 | 23 | def is_valid_origin(origin): 24 | if not settings.SENTRY_ALLOW_ORIGIN: 25 | return False 26 | 27 | if settings.SENTRY_ALLOW_ORIGIN == '*': 28 | return True 29 | 30 | if not origin: 31 | return False 32 | 33 | origin = origin.lower() 34 | for value in settings.SENTRY_ALLOW_ORIGIN: 35 | if isinstance(value, string_types): 36 | if value.lower() == origin: 37 | return True 38 | else: 39 | if value.match(origin): 40 | return True 41 | 42 | return False 43 | 44 | 45 | def with_origin(func): 46 | @wraps(func) 47 | def wrapped(request, *args, **kwargs): 48 | origin = request.META.get('HTTP_ORIGIN') 49 | 50 | if not is_valid_origin(origin): 51 | return HttpResponseForbidden() 52 | 53 | response = func(request, *args, **kwargs) 54 | response['Access-Control-Allow-Origin'] = origin 55 | response['Access-Control-Allow-Methods'] = 'GET, POST, OPTIONS' 56 | 57 | return response 58 | return wrapped 59 | 60 | 61 | def extract_auth_vars(request): 62 | """ 63 | raven-js will pass both Authorization and X-Sentry-Auth depending on the browser 64 | and server configurations. 65 | """ 66 | if request.META.get('HTTP_X_SENTRY_AUTH', '').startswith('Sentry'): 67 | return request.META['HTTP_X_SENTRY_AUTH'] 68 | elif request.META.get('HTTP_AUTHORIZATION', '').startswith('Sentry'): 69 | return request.META['HTTP_AUTHORIZATION'] 70 | else: 71 | # Try to construct from GET request 72 | args = [ 73 | '%s=%s' % i 74 | for i in request.GET.items() 75 | if i[0].startswith('sentry_') and i[0] != 'sentry_data' 76 | ] 77 | if args: 78 | return 'Sentry %s' % ', '.join(args) 79 | return None 80 | 81 | 82 | @csrf_exempt 83 | @require_http_methods(['GET', 'POST', 'OPTIONS']) 84 | @never_cache 85 | @with_origin 86 | def report(request, project_id=None): 87 | if request.method == 'OPTIONS': 88 | return HttpResponse() 89 | 90 | if request.method == 'POST': 91 | if hasattr(request, 'body'): 92 | data = request.body 93 | else: 94 | data = request.raw_post_data 95 | else: 96 | data = request.GET.get('sentry_data') 97 | 98 | if not data: 99 | return HttpResponseBadRequest() 100 | 101 | try: 102 | decoded = json.loads(data.decode('utf8')) 103 | except json.JSONDecodeError: 104 | return HttpResponseBadRequest() 105 | 106 | client.send(auth_header=extract_auth_vars(request), **decoded) 107 | 108 | return HttpResponse() 109 | -------------------------------------------------------------------------------- /raven/utils/json.py: -------------------------------------------------------------------------------- 1 | """ 2 | raven.utils.json 3 | ~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details. 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | 9 | from __future__ import absolute_import 10 | 11 | import codecs 12 | import collections 13 | import datetime 14 | import uuid 15 | import json 16 | 17 | from .basic import is_namedtuple 18 | from .compat import PY2 19 | 20 | 21 | try: 22 | JSONDecodeError = json.JSONDecodeError 23 | except AttributeError: 24 | JSONDecodeError = ValueError 25 | 26 | 27 | class BetterJSONEncoder(json.JSONEncoder): 28 | ENCODER_BY_TYPE = { 29 | uuid.UUID: lambda o: o.hex, 30 | datetime.datetime: lambda o: o.strftime('%Y-%m-%dT%H:%M:%SZ'), 31 | set: list, 32 | frozenset: list, 33 | bytes: lambda o: o.decode('utf-8', errors='replace'), 34 | collections.namedtuple: lambda o: o._asdict(), 35 | } 36 | 37 | def default(self, obj): 38 | obj_type = type(obj) 39 | if obj_type not in self.ENCODER_BY_TYPE and is_namedtuple(obj): 40 | obj_type = collections.namedtuple 41 | 42 | try: 43 | encoder = self.ENCODER_BY_TYPE[obj_type] 44 | except KeyError: 45 | try: 46 | return super(BetterJSONEncoder, self).default(obj) 47 | except Exception: 48 | try: 49 | return repr(obj) 50 | except Exception: 51 | return object.__repr__(obj) 52 | return encoder(obj) 53 | 54 | 55 | def better_decoder(data): 56 | return data 57 | 58 | 59 | def dumps(value, **kwargs): 60 | if PY2: 61 | kwargs['encoding'] = 'safe-utf-8' 62 | 63 | return json.dumps(value, cls=BetterJSONEncoder, **kwargs) 64 | 65 | 66 | def loads(value, **kwargs): 67 | return json.loads(value, object_hook=better_decoder) 68 | 69 | 70 | _utf8_encoder = codecs.getencoder('utf-8') 71 | 72 | 73 | def safe_encode(input, errors='backslashreplace'): 74 | return _utf8_encoder(input, errors) 75 | 76 | 77 | _utf8_decoder = codecs.getdecoder('utf-8') 78 | 79 | 80 | def safe_decode(input, errors='replace'): 81 | return _utf8_decoder(input, errors) 82 | 83 | 84 | class Codec(codecs.Codec): 85 | 86 | def encode(self, input, errors='backslashreplace'): 87 | return safe_encode(input, errors) 88 | 89 | def decode(self, input, errors='replace'): 90 | return safe_decode(input, errors) 91 | 92 | 93 | class IncrementalEncoder(codecs.IncrementalEncoder): 94 | def encode(self, input, final=False): 95 | return safe_encode(input, self.errors)[0] 96 | 97 | 98 | class IncrementalDecoder(codecs.IncrementalDecoder): 99 | def decode(self, input, final=False): 100 | return safe_decode(input, self.errors)[0] 101 | 102 | 103 | class StreamWriter(Codec, codecs.StreamWriter): 104 | pass 105 | 106 | 107 | class StreamReader(Codec, codecs.StreamReader): 108 | pass 109 | 110 | 111 | def getregentry(name): 112 | if name == 'safe-utf-8': 113 | return codecs.CodecInfo( 114 | name=name, 115 | encode=safe_encode, 116 | decode=safe_decode, 117 | incrementalencoder=IncrementalEncoder, 118 | incrementaldecoder=IncrementalDecoder, 119 | streamreader=StreamReader, 120 | streamwriter=StreamWriter, 121 | ) 122 | 123 | 124 | codecs.register(getregentry) 125 | -------------------------------------------------------------------------------- /tests/utils/wsgi/tests.py: -------------------------------------------------------------------------------- 1 | from raven.utils.testutils import TestCase 2 | from raven.utils.wsgi import get_headers, get_host, get_environ, get_client_ip 3 | 4 | 5 | class GetHeadersTest(TestCase): 6 | def test_tuple_as_key(self): 7 | result = dict(get_headers({ 8 | ('a', 'tuple'): 'foo', 9 | })) 10 | self.assertEquals(result, {}) 11 | 12 | def test_coerces_http_name(self): 13 | result = dict(get_headers({ 14 | 'HTTP_ACCEPT': 'text/plain', 15 | })) 16 | self.assertIn('Accept', result) 17 | self.assertEquals(result['Accept'], 'text/plain') 18 | 19 | def test_coerces_content_type(self): 20 | result = dict(get_headers({ 21 | 'CONTENT_TYPE': 'text/plain', 22 | })) 23 | self.assertIn('Content-Type', result) 24 | self.assertEquals(result['Content-Type'], 'text/plain') 25 | 26 | def test_coerces_content_length(self): 27 | result = dict(get_headers({ 28 | 'CONTENT_LENGTH': '134', 29 | })) 30 | self.assertIn('Content-Length', result) 31 | self.assertEquals(result['Content-Length'], '134') 32 | 33 | 34 | class GetEnvironTest(TestCase): 35 | def test_has_remote_addr(self): 36 | result = dict(get_environ({'REMOTE_ADDR': '127.0.0.1'})) 37 | self.assertIn('REMOTE_ADDR', result) 38 | self.assertEquals(result['REMOTE_ADDR'], '127.0.0.1') 39 | 40 | def test_has_server_name(self): 41 | result = dict(get_environ({'SERVER_NAME': '127.0.0.1'})) 42 | self.assertIn('SERVER_NAME', result) 43 | self.assertEquals(result['SERVER_NAME'], '127.0.0.1') 44 | 45 | def test_has_server_port(self): 46 | result = dict(get_environ({'SERVER_PORT': 80})) 47 | self.assertIn('SERVER_PORT', result) 48 | self.assertEquals(result['SERVER_PORT'], 80) 49 | 50 | def test_hides_wsgi_input(self): 51 | result = list(get_environ({'wsgi.input': 'foo'})) 52 | self.assertNotIn('wsgi.input', result) 53 | 54 | 55 | class GetHostTest(TestCase): 56 | def test_http_x_forwarded_host(self): 57 | result = get_host({'HTTP_X_FORWARDED_HOST': 'example.com'}) 58 | self.assertEquals(result, 'example.com') 59 | 60 | def test_http_host(self): 61 | result = get_host({'HTTP_HOST': 'example.com'}) 62 | self.assertEquals(result, 'example.com') 63 | 64 | def test_http_strips_port(self): 65 | result = get_host({ 66 | 'wsgi.url_scheme': 'http', 67 | 'SERVER_NAME': 'example.com', 68 | 'SERVER_PORT': '80', 69 | }) 70 | self.assertEquals(result, 'example.com') 71 | 72 | def test_https_strips_port(self): 73 | result = get_host({ 74 | 'wsgi.url_scheme': 'https', 75 | 'SERVER_NAME': 'example.com', 76 | 'SERVER_PORT': '443', 77 | }) 78 | self.assertEquals(result, 'example.com') 79 | 80 | def test_http_nonstandard_port(self): 81 | result = get_host({ 82 | 'wsgi.url_scheme': 'http', 83 | 'SERVER_NAME': 'example.com', 84 | 'SERVER_PORT': '81', 85 | }) 86 | self.assertEquals(result, 'example.com:81') 87 | 88 | 89 | class GetClientIpTest(TestCase): 90 | def test_has_remote_addr(self): 91 | result = get_client_ip({'REMOTE_ADDR': '127.0.0.1'}) 92 | self.assertEquals(result, '127.0.0.1') 93 | 94 | def test_xff(self): 95 | result = get_client_ip({'HTTP_X_FORWARDED_FOR': '1.1.1.1, 127.0.0.1'}) 96 | self.assertEquals(result, '1.1.1.1') 97 | -------------------------------------------------------------------------------- /raven/contrib/django/utils.py: -------------------------------------------------------------------------------- 1 | """ 2 | raven.contrib.django.utils 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details. 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | 9 | from __future__ import absolute_import 10 | 11 | import os 12 | from django.conf import settings 13 | 14 | 15 | def linebreak_iter(template_source): 16 | yield 0 17 | p = template_source.find('\n') 18 | while p >= 0: 19 | yield p + 1 20 | p = template_source.find('\n', p + 1) 21 | yield len(template_source) + 1 22 | 23 | 24 | def get_data_from_template(source, debug=None): 25 | def _remove_numbers(items): 26 | rv = [] 27 | for item in items: 28 | # Some debug info from django has tuples in the form (lineno, 29 | # code) instead of just the code there. 30 | if isinstance(item, (list, tuple)) and len(item) == 2: 31 | item = item[1] 32 | rv.append(item) 33 | return rv 34 | 35 | if debug is not None: 36 | lineno = debug['line'] 37 | filename = debug['name'] 38 | source_lines = [] 39 | source_lines += [''] * (debug['source_lines'][0][0]) 40 | for num, line in debug['source_lines']: 41 | source_lines.append(line) 42 | source_lines += [''] * 4 43 | elif source: 44 | origin, (start, end) = source 45 | filename = culprit = getattr(origin, 'loadname', None) 46 | template_source = origin.reload() 47 | lineno = None 48 | upto = 0 49 | source_lines = [] 50 | for num, next in enumerate(linebreak_iter(template_source)): 51 | if start >= upto and end <= next: 52 | lineno = num 53 | source_lines.append(template_source[upto:next]) 54 | upto = next 55 | 56 | if not source_lines or lineno is None: 57 | return {} 58 | else: 59 | raise TypeError('Source or debug needed') 60 | 61 | if filename is None: 62 | filename = '' 63 | culprit = '' 64 | else: 65 | culprit = filename.split('/templates/')[-1] 66 | 67 | pre_context = _remove_numbers(source_lines[max(lineno - 3, 0):lineno]) 68 | post_context = _remove_numbers(source_lines[(lineno + 1):(lineno + 4)]) 69 | context_line = _remove_numbers([source_lines[lineno]])[0] 70 | 71 | return { 72 | 'template': { 73 | 'filename': os.path.basename(filename), 74 | 'abs_path': filename, 75 | 'pre_context': pre_context, 76 | 'context_line': context_line, 77 | 'lineno': lineno, 78 | 'post_context': post_context, 79 | }, 80 | 'culprit': culprit, 81 | } 82 | 83 | 84 | def get_host(request): 85 | """ 86 | A reimplementation of Django's get_host, without the 87 | SuspiciousOperation check. 88 | """ 89 | # We try three options, in order of decreasing preference. 90 | if settings.USE_X_FORWARDED_HOST and ( 91 | 'HTTP_X_FORWARDED_HOST' in request.META): 92 | host = request.META['HTTP_X_FORWARDED_HOST'] 93 | elif 'HTTP_HOST' in request.META: 94 | host = request.META['HTTP_HOST'] 95 | else: 96 | # Reconstruct the host using the algorithm from PEP 333. 97 | host = request.META['SERVER_NAME'] 98 | server_port = str(request.META['SERVER_PORT']) 99 | if server_port != (request.is_secure() and '443' or '80'): 100 | host = '%s:%s' % (host, server_port) 101 | return host 102 | -------------------------------------------------------------------------------- /raven/utils/encoding.py: -------------------------------------------------------------------------------- 1 | """ 2 | raven.utils.encoding 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details. 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | from __future__ import absolute_import, unicode_literals 9 | 10 | import warnings 11 | 12 | from raven.utils.compat import integer_types, text_type, binary_type, \ 13 | string_types, PY2 14 | 15 | 16 | def is_protected_type(obj): 17 | """Determine if the object instance is of a protected type. 18 | 19 | Objects of protected types are preserved as-is when passed to 20 | force_text(strings_only=True). 21 | """ 22 | import Decimal 23 | import datetime 24 | return isinstance(obj, integer_types + (type(None), float, Decimal, 25 | datetime.datetime, datetime.date, datetime.time)) 26 | 27 | 28 | def force_text(s, encoding='utf-8', strings_only=False, errors='strict'): 29 | """ 30 | Similar to smart_text, except that lazy instances are resolved to 31 | strings, rather than kept as lazy objects. 32 | 33 | If strings_only is True, don't convert (some) non-string-like objects. 34 | """ 35 | # Handle the common case first, saves 30-40% when s is an instance of 36 | # text_type. This function gets called often in that setting. 37 | if isinstance(s, text_type): 38 | return s 39 | if strings_only and is_protected_type(s): 40 | return s 41 | try: 42 | if not isinstance(s, string_types): 43 | if hasattr(s, '__unicode__'): 44 | s = s.__unicode__() 45 | else: 46 | if not PY2: 47 | if isinstance(s, bytes): 48 | s = text_type(s, encoding, errors) 49 | else: 50 | s = text_type(s) 51 | else: 52 | s = text_type(bytes(s), encoding, errors) 53 | else: 54 | # Note: We use .decode() here, instead of text_type(s, encoding, 55 | # errors), so that if s is a SafeBytes, it ends up being a 56 | # SafeText at the end. 57 | s = s.decode(encoding, errors) 58 | except UnicodeDecodeError as e: 59 | if not isinstance(s, Exception): 60 | raise UnicodeDecodeError(*e.args) 61 | else: 62 | # If we get to here, the caller has passed in an Exception 63 | # subclass populated with non-ASCII bytestring data without a 64 | # working unicode method. Try to handle this without raising a 65 | # further exception by individually forcing the exception args 66 | # to unicode. 67 | s = ' '.join([force_text(arg, encoding, strings_only, 68 | errors) for arg in s]) 69 | return s 70 | 71 | 72 | def transform(value): 73 | from raven.utils.serializer import transform 74 | warnings.warn('You should switch to raven.utils.serializer.' 75 | 'transform', DeprecationWarning) 76 | 77 | return transform(value) 78 | 79 | 80 | def to_unicode(value): 81 | try: 82 | value = text_type(force_text(value)) 83 | except (UnicodeEncodeError, UnicodeDecodeError): 84 | value = '(Error decoding value)' 85 | except Exception: # in some cases we get a different exception 86 | try: 87 | value = text_type(force_text(repr(type(value)))) 88 | except Exception: 89 | value = '(Error decoding value)' 90 | return value 91 | 92 | 93 | def to_string(value): 94 | try: 95 | return binary_type(value.decode('utf-8').encode('utf-8')) 96 | except Exception: 97 | return to_unicode(value).encode('utf-8') 98 | -------------------------------------------------------------------------------- /raven/contrib/django/resolver.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | import re 4 | 5 | try: 6 | from django.urls import get_resolver 7 | except ImportError: 8 | from django.core.urlresolvers import get_resolver 9 | 10 | 11 | def get_regex(resolver_or_pattern): 12 | """Utility method for django's deprecated resolver.regex""" 13 | try: 14 | regex = resolver_or_pattern.regex 15 | except AttributeError: 16 | regex = resolver_or_pattern.pattern.regex 17 | return regex 18 | 19 | 20 | class RouteResolver(object): 21 | _optional_group_matcher = re.compile(r'\(\?\:([^\)]+)\)') 22 | _named_group_matcher = re.compile(r'\(\?P<(\w+)>[^\)]+\)') 23 | _non_named_group_matcher = re.compile(r'\([^\)]+\)') 24 | # [foo|bar|baz] 25 | _either_option_matcher = re.compile(r'\[([^\]]+)\|([^\]]+)\]') 26 | _camel_re = re.compile(r'([A-Z]+)([a-z])') 27 | 28 | _cache = {} 29 | 30 | def _simplify(self, pattern): 31 | r""" 32 | Clean up urlpattern regexes into something readable by humans: 33 | 34 | From: 35 | > "^(?P\w+)/athletes/(?P\w+)/$" 36 | 37 | To: 38 | > "{sport_slug}/athletes/{athlete_slug}/" 39 | """ 40 | # remove optional params 41 | # TODO(dcramer): it'd be nice to change these into [%s] but it currently 42 | # conflicts with the other rules because we're doing regexp matches 43 | # rather than parsing tokens 44 | result = self._optional_group_matcher.sub(lambda m: '%s' % m.group(1), pattern) 45 | 46 | # handle named groups first 47 | result = self._named_group_matcher.sub(lambda m: '{%s}' % m.group(1), result) 48 | 49 | # handle non-named groups 50 | result = self._non_named_group_matcher.sub('{var}', result) 51 | 52 | # handle optional params 53 | result = self._either_option_matcher.sub(lambda m: m.group(1), result) 54 | 55 | # clean up any outstanding regex-y characters. 56 | result = result.replace('^', '').replace('$', '') \ 57 | .replace('?', '').replace('//', '/').replace('\\', '') 58 | 59 | return result 60 | 61 | def _resolve(self, resolver, path, parents=None): 62 | 63 | match = get_regex(resolver).search(path) # Django < 2.0 64 | 65 | if not match: 66 | return 67 | 68 | if parents is None: 69 | parents = [resolver] 70 | elif resolver not in parents: 71 | parents = parents + [resolver] 72 | 73 | new_path = path[match.end():] 74 | for pattern in resolver.url_patterns: 75 | # this is an include() 76 | if not pattern.callback: 77 | match = self._resolve(pattern, new_path, parents) 78 | if match: 79 | return match 80 | continue 81 | elif not get_regex(pattern).search(new_path): 82 | continue 83 | 84 | try: 85 | return self._cache[pattern] 86 | except KeyError: 87 | pass 88 | 89 | prefix = ''.join(self._simplify(get_regex(p).pattern) for p in parents) 90 | result = prefix + self._simplify(get_regex(pattern).pattern) 91 | if not result.startswith('/'): 92 | result = '/' + result 93 | self._cache[pattern] = result 94 | return result 95 | 96 | def resolve(self, path, urlconf=None): 97 | resolver = get_resolver(urlconf) 98 | match = self._resolve(resolver, path) 99 | return match or path 100 | -------------------------------------------------------------------------------- /tests/contrib/awslambda/conftest.py: -------------------------------------------------------------------------------- 1 | 2 | import pytest 3 | from raven.contrib.awslambda import LambdaClient 4 | import uuid 5 | import time 6 | 7 | 8 | class MockClient(LambdaClient): 9 | def __init__(self, *args, **kwargs): 10 | self.events = [] 11 | super(MockClient, self).__init__(*args, **kwargs) 12 | 13 | def send(self, **kwargs): 14 | self.events.append(kwargs) 15 | 16 | def is_enabled(self, **kwargs): 17 | return True 18 | 19 | 20 | class LambdaIndentityStub(object): 21 | def __init__(self, id=1, pool_id=1): 22 | self.cognito_identity_id = id 23 | self.cognito_identity_pool_id = pool_id 24 | 25 | def __getitem__(self, item): 26 | return getattr(self, item) 27 | 28 | def get(self, name, default=None): 29 | return getattr(self, name, default) 30 | 31 | 32 | class LambdaContextStub(object): 33 | 34 | def __init__(self, function_name, memory_limit_in_mb=128, timeout=300, function_version='$LATEST'): 35 | self.function_name = function_name 36 | self.memory_limit_in_mb = memory_limit_in_mb 37 | self.timeout = timeout 38 | self.function_version = function_version 39 | self.timeout = timeout 40 | self.invoked_function_arn = 'invoked_function_arn' 41 | self.log_group_name = 'log_group_name' 42 | self.log_stream_name = 'log_stream_name' 43 | self.identity = LambdaIndentityStub(id=0, pool_id=0) 44 | self.client_context = None 45 | self.aws_request_id = str(uuid.uuid4()) 46 | self.start_time = time.time() * 1000 47 | 48 | def __getitem__(self, item, default): 49 | return getattr(self, item, default) 50 | 51 | def get(self, name, default=None): 52 | return getattr(self, name, default) 53 | 54 | def get_remaining_time_in_millis(self): 55 | return max(self.timeout * 1000 - int((time.time() * 1000) - self.start_time), 0) 56 | 57 | 58 | class LambdaEventStub(object): 59 | def __init__(self, body=None, headers=None, http_method='GET', path='/test', query_string=None): 60 | self.body = body 61 | self.headers = headers 62 | self.httpMethod = http_method 63 | self.isBase64Encoded = False 64 | self.path = path 65 | self.queryStringParameters = query_string 66 | self.resource = path 67 | self.stageVariables = None 68 | self.requestContext = { 69 | 'accountId': '0000000', 70 | 'apiId': 'AAAAAAAA', 71 | 'httpMethod': http_method, 72 | 'identity': LambdaIndentityStub(), 73 | 'path': path, 74 | 'requestId': 'test-request', 75 | 'resourceId': 'bbzeyv', 76 | 'resourcePath': '/test', 77 | 'stage': 'test-stage' 78 | } 79 | 80 | def __getitem__(self, name): 81 | return getattr(self, name) 82 | 83 | def get(self, name, default=None): 84 | return getattr(self, name, default) 85 | 86 | 87 | @pytest.fixture 88 | def lambda_env(monkeypatch): 89 | monkeypatch.setenv('SENTRY_DSN', 'http://public:secret@example.com/1') 90 | monkeypatch.setenv('AWS_LAMBDA_FUNCTION_NAME', 'test_func') 91 | monkeypatch.setenv('AWS_LAMBDA_FUNCTION_VERSION', '$LATEST') 92 | monkeypatch.setenv('SENTRY_RELEASE', '$LATEST') 93 | monkeypatch.setenv('SENTRY_ENVIRONMENT', 'testing') 94 | 95 | 96 | @pytest.fixture 97 | def mock_client(): 98 | return MockClient 99 | 100 | @pytest.fixture 101 | def lambda_event(): 102 | return LambdaEventStub 103 | 104 | @pytest.fixture 105 | def lambda_context(): 106 | return LambdaContextStub 107 | -------------------------------------------------------------------------------- /raven/contrib/celery/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | raven.contrib.celery 3 | ~~~~~~~~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details. 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | from __future__ import absolute_import 9 | 10 | import logging 11 | 12 | from celery.exceptions import SoftTimeLimitExceeded 13 | from celery.signals import ( 14 | after_setup_logger, task_failure, task_prerun, task_postrun 15 | ) 16 | from raven.handlers.logging import SentryHandler 17 | 18 | 19 | class CeleryFilter(logging.Filter): 20 | def filter(self, record): 21 | # Context is fixed in Celery 3.x so use internal flag instead 22 | extra_data = getattr(record, 'data', {}) 23 | if not isinstance(extra_data, dict): 24 | return record.funcName != '_log_error' 25 | # Fallback to funcName for Celery 2.5 26 | return extra_data.get('internal', record.funcName != '_log_error') 27 | 28 | 29 | def register_signal(client, ignore_expected=False): 30 | SentryCeleryHandler(client, ignore_expected=ignore_expected).install() 31 | 32 | 33 | def register_logger_signal(client, logger=None, loglevel=logging.ERROR): 34 | filter_ = CeleryFilter() 35 | 36 | handler = SentryHandler(client) 37 | handler.setLevel(loglevel) 38 | handler.addFilter(filter_) 39 | 40 | def process_logger_event(sender, logger, loglevel, logfile, format, 41 | colorize, **kw): 42 | # Attempt to find an existing SentryHandler, and if it exists ensure 43 | # that the CeleryFilter is installed. 44 | # If one is found, we do not attempt to install another one. 45 | for h in logger.handlers: 46 | if isinstance(h, SentryHandler): 47 | h.addFilter(filter_) 48 | return False 49 | 50 | logger.addHandler(handler) 51 | 52 | after_setup_logger.connect(process_logger_event, weak=False) 53 | 54 | 55 | class SentryCeleryHandler(object): 56 | def __init__(self, client, ignore_expected=False): 57 | self.client = client 58 | self.ignore_expected = ignore_expected 59 | 60 | def install(self): 61 | task_prerun.connect(self.handle_task_prerun, weak=False) 62 | task_postrun.connect(self.handle_task_postrun, weak=False) 63 | task_failure.connect(self.process_failure_signal, weak=False) 64 | 65 | def uninstall(self): 66 | task_prerun.disconnect(self.handle_task_prerun) 67 | task_postrun.disconnect(self.handle_task_postrun) 68 | task_failure.disconnect(self.process_failure_signal) 69 | 70 | def process_failure_signal(self, sender, task_id, args, kwargs, einfo, **kw): 71 | if self.ignore_expected and hasattr(sender, 'throws') and isinstance(einfo.exception, sender.throws): 72 | return 73 | 74 | # This signal is fired inside the stack so let raven do its magic 75 | if isinstance(einfo.exception, SoftTimeLimitExceeded): 76 | fingerprint = ['celery', 'SoftTimeLimitExceeded', getattr(sender, 'name', sender)] 77 | else: 78 | fingerprint = None 79 | 80 | self.client.captureException( 81 | extra={ 82 | 'task_id': task_id, 83 | 'task': sender, 84 | 'args': args, 85 | 'kwargs': kwargs, 86 | }, 87 | fingerprint=fingerprint, 88 | ) 89 | 90 | def handle_task_prerun(self, sender, task_id, task, **kw): 91 | self.client.context.activate() 92 | self.client.transaction.push(task.name) 93 | 94 | def handle_task_postrun(self, sender, task_id, task, **kw): 95 | self.client.transaction.pop(task.name) 96 | self.client.context.clear() 97 | -------------------------------------------------------------------------------- /raven/handlers/logbook.py: -------------------------------------------------------------------------------- 1 | """ 2 | raven.handlers.logbook 3 | ~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details. 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | 9 | from __future__ import absolute_import 10 | from __future__ import print_function 11 | 12 | import logbook 13 | import sys 14 | import traceback 15 | 16 | from raven.utils.compat import string_types 17 | from raven.base import Client 18 | from raven.utils.encoding import to_string 19 | 20 | 21 | class SentryHandler(logbook.Handler): 22 | def __init__(self, *args, **kwargs): 23 | if len(args) == 1: 24 | arg = args[0] 25 | if isinstance(arg, string_types): 26 | self.client = kwargs.pop('client_cls', Client)(dsn=arg, **kwargs) 27 | elif isinstance(arg, Client): 28 | self.client = arg 29 | else: 30 | raise ValueError('The first argument to %s must be either a Client instance or a DSN, got %r instead.' % ( 31 | self.__class__.__name__, 32 | arg, 33 | )) 34 | args = [] 35 | else: 36 | try: 37 | self.client = kwargs.pop('client') 38 | except KeyError: 39 | raise TypeError('Expected keyword argument for SentryHandler: client') 40 | super(SentryHandler, self).__init__(*args, **kwargs) 41 | 42 | def emit(self, record): 43 | try: 44 | # Avoid typical config issues by overriding loggers behavior 45 | if record.channel.startswith(('sentry.errors', 'raven')): 46 | print(to_string(self.format(record)), file=sys.stderr) 47 | return 48 | 49 | return self._emit(record) 50 | except Exception: 51 | if self.client.raise_send_errors: 52 | raise 53 | print("Top level Sentry exception caught - failed creating log record", file=sys.stderr) 54 | print(to_string(record.msg), file=sys.stderr) 55 | print(to_string(traceback.format_exc())) 56 | 57 | try: 58 | self.client.captureException() 59 | except Exception: 60 | pass 61 | 62 | def _emit(self, record): 63 | data = { 64 | 'level': logbook.get_level_name(record.level).lower(), 65 | 'logger': record.channel, 66 | } 67 | 68 | event_type = 'raven.events.Message' 69 | 70 | handler_kwargs = { 71 | 'message': record.msg, 72 | 'params': record.args, 73 | 'formatted': self.format(record), 74 | } 75 | 76 | if 'tags' in record.kwargs: 77 | handler_kwargs['tags'] = record.kwargs['tags'] 78 | 79 | # If there's no exception being processed, exc_info may be a 3-tuple of None 80 | # http://docs.python.org/library/sys.html#sys.exc_info 81 | if record.exc_info is True or (record.exc_info and all(record.exc_info)): 82 | handler = self.client.get_handler(event_type) 83 | data.update(handler.capture(**handler_kwargs)) 84 | 85 | event_type = 'raven.events.Exception' 86 | handler_kwargs['exc_info'] = record.exc_info 87 | 88 | extra = { 89 | 'lineno': record.lineno, 90 | 'filename': record.filename, 91 | 'function': record.func_name, 92 | 'process': record.process, 93 | 'process_name': record.process_name, 94 | } 95 | extra.update(record.extra) 96 | 97 | return self.client.capture(event_type, 98 | data=data, 99 | extra=extra, 100 | **handler_kwargs 101 | ) 102 | -------------------------------------------------------------------------------- /raven/utils/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | raven.utils 3 | ~~~~~~~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details. 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | from __future__ import absolute_import 9 | 10 | import logging 11 | import sys 12 | 13 | # Using "NOQA" to preserve export compatibility 14 | from raven.utils.compat import iteritems, string_types # NOQA 15 | from raven.utils.basic import ( # NOQA 16 | merge_dicts, varmap, memoize, once, is_namedtuple 17 | ) 18 | 19 | 20 | logger = logging.getLogger('raven.errors') 21 | 22 | 23 | # We store a cache of module_name->version string to avoid 24 | # continuous imports and lookups of modules 25 | _VERSION_CACHE = {} 26 | 27 | 28 | def get_version_from_app(module_name, app): 29 | version = None 30 | 31 | # Try to pull version from pkg_resources first 32 | # as it is able to detect version tagged with egg_info -b 33 | try: 34 | # Importing pkg_resources can be slow, so only import it 35 | # if we need it. 36 | import pkg_resources 37 | except ImportError: 38 | # pkg_resource is not available on Google App Engine 39 | pass 40 | else: 41 | # pull version from pkg_resources if distro exists 42 | try: 43 | return pkg_resources.get_distribution(module_name).version 44 | except Exception: 45 | pass 46 | 47 | if hasattr(app, 'get_version'): 48 | version = app.get_version 49 | elif hasattr(app, '__version__'): 50 | version = app.__version__ 51 | elif hasattr(app, 'VERSION'): 52 | version = app.VERSION 53 | elif hasattr(app, 'version'): 54 | version = app.version 55 | 56 | if callable(version): 57 | version = version() 58 | 59 | if not isinstance(version, (string_types, list, tuple)): 60 | version = None 61 | 62 | if version is None: 63 | return None 64 | 65 | if isinstance(version, (list, tuple)): 66 | version = '.'.join(map(str, version)) 67 | 68 | return str(version) 69 | 70 | 71 | def get_versions(module_list=None): 72 | if not module_list: 73 | return {} 74 | 75 | ext_module_list = set() 76 | for m in module_list: 77 | parts = m.split('.') 78 | ext_module_list.update('.'.join(parts[:idx]) 79 | for idx in range(1, len(parts) + 1)) 80 | 81 | versions = {} 82 | for module_name in ext_module_list: 83 | if module_name not in _VERSION_CACHE: 84 | try: 85 | __import__(module_name) 86 | except ImportError: 87 | continue 88 | 89 | try: 90 | app = sys.modules[module_name] 91 | except KeyError: 92 | continue 93 | 94 | try: 95 | version = get_version_from_app(module_name, app) 96 | except Exception as e: 97 | logger.exception(e) 98 | version = None 99 | 100 | _VERSION_CACHE[module_name] = version 101 | else: 102 | version = _VERSION_CACHE[module_name] 103 | if version is not None: 104 | versions[module_name] = version 105 | return versions 106 | 107 | 108 | def get_auth_header(protocol, timestamp, client, api_key, 109 | api_secret=None, **kwargs): 110 | header = [ 111 | ('sentry_timestamp', timestamp), 112 | ('sentry_client', client), 113 | ('sentry_version', protocol), 114 | ('sentry_key', api_key), 115 | ] 116 | if api_secret: 117 | header.append(('sentry_secret', api_secret)) 118 | 119 | return 'Sentry %s' % ', '.join('%s=%s' % (k, v) for k, v in header) 120 | -------------------------------------------------------------------------------- /raven/scripts/runner.py: -------------------------------------------------------------------------------- 1 | """ 2 | raven.scripts.runner 3 | ~~~~~~~~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2012 by the Sentry Team, see AUTHORS for more details. 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | 9 | from __future__ import absolute_import 10 | from __future__ import print_function 11 | 12 | import logging 13 | import os 14 | import sys 15 | import time 16 | 17 | from optparse import OptionParser 18 | 19 | from raven import Client, get_version 20 | from raven.utils.json import json 21 | 22 | 23 | def store_json(option, opt_str, value, parser): 24 | try: 25 | value = json.loads(value) 26 | except ValueError: 27 | print("Invalid JSON was used for option %s. Received: %s" % (opt_str, value)) 28 | sys.exit(1) 29 | setattr(parser.values, option.dest, value) 30 | 31 | 32 | def get_loadavg(): 33 | if hasattr(os, 'getloadavg'): 34 | return os.getloadavg() 35 | return None 36 | 37 | 38 | def get_uid(): 39 | try: 40 | import pwd 41 | except ImportError: 42 | return None 43 | try: 44 | return pwd.getpwuid(os.geteuid())[0] 45 | except KeyError: # Sometimes fails in containers 46 | return None 47 | 48 | 49 | def send_test_message(client, options): 50 | sys.stdout.write("Client configuration:\n") 51 | for k in ('base_url', 'project', 'public_key', 'secret_key'): 52 | sys.stdout.write(' %-15s: %s\n' % (k, getattr(client.remote, k))) 53 | sys.stdout.write('\n') 54 | 55 | remote_config = client.remote 56 | if not remote_config.is_active(): 57 | sys.stdout.write("Error: DSN configuration is not valid!\n") 58 | sys.exit(1) 59 | 60 | if not client.is_enabled(): 61 | sys.stdout.write('Error: Client reports as being disabled!\n') 62 | sys.exit(1) 63 | 64 | data = options.get('data', { 65 | 'culprit': 'raven.scripts.runner', 66 | 'logger': 'raven.test', 67 | 'request': { 68 | 'method': 'GET', 69 | 'url': 'http://example.com', 70 | } 71 | }) 72 | 73 | sys.stdout.write('Sending a test message... ') 74 | sys.stdout.flush() 75 | 76 | ident = client.captureMessage( 77 | message='This is a test message generated using ``raven test``', 78 | data=data, 79 | level=logging.INFO, 80 | stack=True, 81 | tags=options.get('tags', {}), 82 | extra={ 83 | 'user': get_uid(), 84 | 'loadavg': get_loadavg(), 85 | }, 86 | ) 87 | 88 | sys.stdout.write('Event ID was %r\n' % (ident,)) 89 | 90 | 91 | def main(): 92 | root = logging.getLogger('sentry.errors') 93 | root.setLevel(logging.DEBUG) 94 | # if len(root.handlers) == 0: 95 | # root.addHandler(logging.StreamHandler()) 96 | 97 | parser = OptionParser(version=get_version()) 98 | parser.add_option("--data", action="callback", callback=store_json, 99 | type="string", nargs=1, dest="data") 100 | parser.add_option("--tags", action="callback", callback=store_json, 101 | type="string", nargs=1, dest="tags") 102 | (opts, args) = parser.parse_args() 103 | 104 | dsn = ' '.join(args[1:]) or os.environ.get('SENTRY_DSN') 105 | if not dsn: 106 | print("Error: No configuration detected!") 107 | print("You must either pass a DSN to the command, or set the SENTRY_DSN environment variable.") 108 | sys.exit(1) 109 | 110 | print("Using DSN configuration:") 111 | print(" ", dsn) 112 | print() 113 | 114 | client = Client(dsn, include_paths=['raven']) 115 | 116 | send_test_message(client, opts.__dict__) 117 | 118 | # TODO(dcramer): correctly support async models 119 | time.sleep(3) 120 | if client.state.did_fail(): 121 | sys.stdout.write('error!\n') 122 | sys.exit(1) 123 | 124 | sys.stdout.write('success!\n') 125 | -------------------------------------------------------------------------------- /examples/django_110/app/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for django_110 project. 3 | 4 | Generated by 'django-admin startproject' using Django 1.10. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.10/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/1.10/ref/settings/ 11 | """ 12 | 13 | import os 14 | 15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = 'jlyce0zs(ux$*gn3t9hzdh!+%pf4%6)i$v-usu!o1-ab-+s*m)' 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = [] 29 | 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = [ 34 | 'django.contrib.admin', 35 | 'django.contrib.auth', 36 | 'django.contrib.contenttypes', 37 | 'django.contrib.sessions', 38 | 'django.contrib.messages', 39 | 'django.contrib.staticfiles', 40 | 'app', 41 | 'raven.contrib.django.raven_compat', 42 | ] 43 | 44 | MIDDLEWARE = [ 45 | 'django.middleware.security.SecurityMiddleware', 46 | 'django.contrib.sessions.middleware.SessionMiddleware', 47 | 'django.middleware.common.CommonMiddleware', 48 | 'django.middleware.csrf.CsrfViewMiddleware', 49 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 50 | 'django.contrib.messages.middleware.MessageMiddleware', 51 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 52 | ] 53 | 54 | ROOT_URLCONF = 'app.urls' 55 | 56 | TEMPLATES = [ 57 | { 58 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 59 | 'DIRS': [], 60 | 'APP_DIRS': True, 61 | 'OPTIONS': { 62 | 'context_processors': [ 63 | 'django.template.context_processors.debug', 64 | 'django.template.context_processors.request', 65 | 'django.contrib.auth.context_processors.auth', 66 | 'django.contrib.messages.context_processors.messages', 67 | ], 68 | }, 69 | }, 70 | ] 71 | 72 | WSGI_APPLICATION = 'app.wsgi.application' 73 | 74 | 75 | # Database 76 | # https://docs.djangoproject.com/en/1.10/ref/settings/#databases 77 | 78 | DATABASES = { 79 | 'default': { 80 | 'ENGINE': 'django.db.backends.sqlite3', 81 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 82 | } 83 | } 84 | 85 | 86 | # Password validation 87 | # https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators 88 | 89 | AUTH_PASSWORD_VALIDATORS = [ 90 | { 91 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 92 | }, 93 | { 94 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 95 | }, 96 | { 97 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 98 | }, 99 | { 100 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 101 | }, 102 | ] 103 | 104 | 105 | # Internationalization 106 | # https://docs.djangoproject.com/en/1.10/topics/i18n/ 107 | 108 | LANGUAGE_CODE = 'en-us' 109 | 110 | TIME_ZONE = 'UTC' 111 | 112 | USE_I18N = True 113 | 114 | USE_L10N = True 115 | 116 | USE_TZ = True 117 | 118 | 119 | # Static files (CSS, JavaScript, Images) 120 | # https://docs.djangoproject.com/en/1.10/howto/static-files/ 121 | 122 | STATIC_URL = '/static/' 123 | 124 | RAVEN_CONFIG = { 125 | 'environment': 'development', 126 | 'dsn': 'https://f0a3752c685647c6bf7830ce66a9ace5:cbf73b8a273b429a828e470e4c986bf8@app.getsentry.com/1254', 127 | 'release': '1.0', 128 | } 129 | -------------------------------------------------------------------------------- /tests/utils/stacks/tests.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | import os.path 5 | 6 | from mock import Mock 7 | from raven.utils.compat import iterkeys, PY3 8 | from raven.utils.testutils import TestCase 9 | from raven.utils.stacks import get_stack_info, get_lines_from_file 10 | 11 | 12 | class Context(object): 13 | def __init__(self, dict): 14 | self.dict = dict 15 | 16 | __getitem__ = lambda s, *a: s.dict.__getitem__(*a) 17 | __setitem__ = lambda s, *a: s.dict.__setitem__(*a) 18 | iterkeys = lambda s, *a: iterkeys(s.dict, *a) 19 | 20 | 21 | class GetStackInfoTest(TestCase): 22 | def test_bad_locals_in_frame(self): 23 | frame = Mock() 24 | frame.f_locals = Context({ 25 | 'foo': 'bar', 26 | 'biz': 'baz', 27 | }) 28 | frame.f_lineno = 1 29 | frame.f_globals = {} 30 | frame.f_code.co_filename = __file__ 31 | frame.f_code.co_name = __name__ 32 | 33 | frames = [(frame, 1)] 34 | results = get_stack_info(frames) 35 | assert len(results['frames']) == 1 36 | result = results['frames'][0] 37 | assert 'vars' in result 38 | if PY3: 39 | expected = { 40 | "foo": "'bar'", 41 | "biz": "'baz'", 42 | } 43 | else: 44 | expected = { 45 | "foo": "u'bar'", 46 | "biz": "u'baz'", 47 | } 48 | assert result['vars'] == expected 49 | 50 | def test_frame_allowance(self): 51 | frames = [] 52 | for x in range(10): 53 | frame = Mock() 54 | frame.f_locals = {'k': 'v'} 55 | frame.f_lineno = None 56 | frame.f_globals = {} 57 | frame.f_code.co_filename = str(x) 58 | frame.f_code.co_name = __name__ 59 | frames.append((frame, 1)) 60 | 61 | results = get_stack_info(frames, frame_allowance=4) 62 | assert len(results['frames']) == 10 63 | assert results['frames'][0]['filename'] == '0' 64 | assert results['frames'][1]['filename'] == '1' 65 | for idx, frame in enumerate(results['frames'][2:8]): 66 | assert frame['filename'] == str(idx + 2) 67 | assert 'vars' not in frame 68 | assert results['frames'][8]['filename'] == '8' 69 | assert results['frames'][9]['filename'] == '9' 70 | 71 | 72 | class FailLoader(): 73 | ''' 74 | Recreating the built-in loaders from a fake stack trace was brittle. 75 | This method ensures its testing the path where the loader is defined 76 | but fails with known exceptions. 77 | ''' 78 | def get_source(self, module_name): 79 | if '.py' in module_name: 80 | raise ImportError('Cannot load .py files') 81 | elif '.zip' in module_name: 82 | raise IOError('Cannot load .zip files') 83 | else: 84 | raise ValueError('Invalid file extension') 85 | 86 | 87 | class GetLineFromFileTest(TestCase): 88 | def setUp(self): 89 | self.loader = FailLoader() 90 | 91 | def test_non_ascii_file(self): 92 | filename = os.path.join(os.path.dirname(__file__), 'utf8_file.txt') 93 | self.assertEqual( 94 | get_lines_from_file(filename, 3, 1), 95 | (['Some code here'], '', ['lorem ipsum'])) 96 | 97 | def test_missing_zip_get_source(self): 98 | filename = 'does_not_exist.zip' 99 | module = 'not.zip.loadable' 100 | self.assertEqual( 101 | get_lines_from_file(filename, 3, 1, self.loader, module), 102 | (None, None, None)) 103 | 104 | def test_missing_get_source(self): 105 | filename = 'does_not_exist.py' 106 | module = 'not.py.loadable' 107 | self.assertEqual( 108 | get_lines_from_file(filename, 3, 1, self.loader, module), 109 | (None, None, None)) 110 | -------------------------------------------------------------------------------- /raven/utils/ssl_match_hostname.py: -------------------------------------------------------------------------------- 1 | """The match_hostname() function from Python 3.2, essential when using SSL.""" 2 | 3 | from __future__ import absolute_import 4 | 5 | import re 6 | 7 | __version__ = '3.2.2' 8 | 9 | 10 | class CertificateError(ValueError): 11 | pass 12 | 13 | 14 | def _dnsname_match(dn, hostname, max_wildcards=1): 15 | """Matching according to RFC 6125, section 6.4.3 16 | 17 | http://tools.ietf.org/html/rfc6125#section-6.4.3 18 | """ 19 | pats = [] 20 | if not dn: 21 | return False 22 | 23 | parts = dn.split(r'.') 24 | leftmost = parts[0] 25 | 26 | wildcards = leftmost.count('*') 27 | if wildcards > max_wildcards: 28 | # Issue #17980: avoid denials of service by refusing more 29 | # than one wildcard per fragment. A survey of established 30 | # policy among SSL implementations showed it to be a 31 | # reasonable choice. 32 | raise CertificateError( 33 | "too many wildcards in certificate DNS name: " + repr(dn)) 34 | 35 | # speed up common case w/o wildcards 36 | if not wildcards: 37 | return dn.lower() == hostname.lower() 38 | 39 | # RFC 6125, section 6.4.3, subitem 1. 40 | # The client SHOULD NOT attempt to match a presented identifier in which 41 | # the wildcard character comprises a label other than the left-most label. 42 | if leftmost == '*': 43 | # When '*' is a fragment by itself, it matches a non-empty dotless 44 | # fragment. 45 | pats.append('[^.]+') 46 | elif leftmost.startswith('xn--') or hostname.startswith('xn--'): 47 | # RFC 6125, section 6.4.3, subitem 3. 48 | # The client SHOULD NOT attempt to match a presented identifier 49 | # where the wildcard character is embedded within an A-label or 50 | # U-label of an internationalized domain name. 51 | pats.append(re.escape(leftmost)) 52 | else: 53 | # Otherwise, '*' matches any dotless string, e.g. www* 54 | pats.append(re.escape(leftmost).replace(r'\*', '[^.]*')) 55 | 56 | # add the remaining fragments, ignore any wildcards 57 | for frag in parts[1:]: 58 | pats.append(re.escape(frag)) 59 | 60 | pat = re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE) 61 | return pat.match(hostname) 62 | 63 | 64 | def match_hostname(cert, hostname): 65 | """Verify that *cert* (in decoded format as returned by 66 | SSLSocket.getpeercert()) matches the *hostname*. RFC 2818 and RFC 6125 67 | rules are followed, but IP addresses are not accepted for *hostname*. 68 | 69 | CertificateError is raised on failure. On success, the function 70 | returns nothing. 71 | """ 72 | if not cert: 73 | raise ValueError("empty or no certificate") 74 | dnsnames = [] 75 | san = cert.get('subjectAltName', ()) 76 | for key, value in san: 77 | if key == 'DNS': 78 | if _dnsname_match(value, hostname): 79 | return 80 | dnsnames.append(value) 81 | if not dnsnames: 82 | # The subject is only checked when there is no dNSName entry 83 | # in subjectAltName 84 | for sub in cert.get('subject', ()): 85 | for key, value in sub: 86 | # XXX according to RFC 2818, the most specific Common Name 87 | # must be used. 88 | if key == 'commonName': 89 | if _dnsname_match(value, hostname): 90 | return 91 | dnsnames.append(value) 92 | if len(dnsnames) > 1: 93 | raise CertificateError( 94 | "hostname %r doesn't match either of %s" % ( 95 | hostname, ', '.join(map(repr, dnsnames))) 96 | ) 97 | elif len(dnsnames) == 1: 98 | raise CertificateError( 99 | "hostname %r doesn't match %r" % (hostname, dnsnames[0]) 100 | ) 101 | else: 102 | raise CertificateError( 103 | "no appropriate commonName or subjectAltName fields were found" 104 | ) 105 | -------------------------------------------------------------------------------- /raven/middleware.py: -------------------------------------------------------------------------------- 1 | """ 2 | raven.middleware 3 | ~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details. 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | from __future__ import absolute_import 9 | 10 | from contextlib import contextmanager 11 | 12 | from raven.utils.compat import Iterator, next 13 | from raven.utils.wsgi import ( 14 | get_current_url, get_headers, get_environ) 15 | 16 | 17 | @contextmanager 18 | def common_exception_handling(environ, client): 19 | try: 20 | yield 21 | except (StopIteration, GeneratorExit): 22 | # Make sure we do this explicitly here. At least GeneratorExit 23 | # is handled implicitly by the rest of the logic but we want 24 | # to make sure this does not regress 25 | raise 26 | except Exception: 27 | client.handle_exception(environ) 28 | raise 29 | except KeyboardInterrupt: 30 | client.handle_exception(environ) 31 | raise 32 | except SystemExit as e: 33 | if e.code != 0: 34 | client.handle_exception(environ) 35 | raise 36 | 37 | 38 | class ClosingIterator(Iterator): 39 | """ 40 | An iterator that is implements a ``close`` method as-per 41 | WSGI recommendation. 42 | """ 43 | 44 | def __init__(self, sentry, iterable, environ): 45 | self.sentry = sentry 46 | self.environ = environ 47 | self._close = getattr(iterable, 'close', None) 48 | self.iterable = iter(iterable) 49 | self.closed = False 50 | 51 | def __iter__(self): 52 | return self 53 | 54 | def __next__(self): 55 | try: 56 | with common_exception_handling(self.environ, self.sentry): 57 | return next(self.iterable) 58 | except StopIteration: 59 | # We auto close here if we reach the end because some WSGI 60 | # middleware does not really like to close things. To avoid 61 | # massive leaks we just close automatically at the end of 62 | # iteration. 63 | self.close() 64 | raise 65 | 66 | def close(self): 67 | if self.closed: 68 | return 69 | try: 70 | if self._close is not None: 71 | with common_exception_handling(self.environ, self.sentry): 72 | self._close() 73 | finally: 74 | self.sentry.client.context.clear() 75 | self.sentry.client.transaction.clear() 76 | self.closed = True 77 | 78 | 79 | class Sentry(object): 80 | """ 81 | A WSGI middleware which will attempt to capture any 82 | uncaught exceptions and send them to Sentry. 83 | 84 | >>> from raven.base import Client 85 | >>> application = Sentry(application, Client()) 86 | """ 87 | 88 | def __init__(self, application, client=None): 89 | self.application = application 90 | if client is None: 91 | from raven.base import Client 92 | client = Client() 93 | self.client = client 94 | 95 | def __call__(self, environ, start_response): 96 | # TODO(dcramer): ideally this is lazy, but the context helpers must 97 | # support callbacks first 98 | self.client.http_context(self.get_http_context(environ)) 99 | with common_exception_handling(environ, self): 100 | iterable = self.application(environ, start_response) 101 | return ClosingIterator(self, iterable, environ) 102 | 103 | def get_http_context(self, environ): 104 | return { 105 | 'method': environ.get('REQUEST_METHOD'), 106 | 'url': get_current_url(environ, strip_querystring=True), 107 | 'query_string': environ.get('QUERY_STRING'), 108 | # TODO 109 | # 'data': environ.get('wsgi.input'), 110 | 'headers': dict(get_headers(environ)), 111 | 'env': dict(get_environ(environ)), 112 | } 113 | 114 | def handle_exception(self, environ=None): 115 | return self.client.captureException() 116 | -------------------------------------------------------------------------------- /raven/utils/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module implements WSGI related helpers adapted from ``werkzeug.wsgi`` 3 | 4 | :copyright: (c) 2010 by the Werkzeug Team, see AUTHORS for more details. 5 | :license: BSD, see LICENSE for more details. 6 | """ 7 | from __future__ import absolute_import 8 | 9 | from raven.utils.compat import iteritems, urllib_quote 10 | 11 | 12 | # `get_headers` comes from `werkzeug.datastructures.EnvironHeaders` 13 | def get_headers(environ): 14 | """ 15 | Returns only proper HTTP headers. 16 | """ 17 | for key, value in iteritems(environ): 18 | key = str(key) 19 | if key.startswith('HTTP_') and key not in \ 20 | ('HTTP_CONTENT_TYPE', 'HTTP_CONTENT_LENGTH'): 21 | yield key[5:].replace('_', '-').title(), value 22 | elif key in ('CONTENT_TYPE', 'CONTENT_LENGTH'): 23 | yield key.replace('_', '-').title(), value 24 | 25 | 26 | def get_environ(environ): 27 | """ 28 | Returns our whitelisted environment variables. 29 | """ 30 | for key in ('REMOTE_ADDR', 'SERVER_NAME', 'SERVER_PORT'): 31 | if key in environ: 32 | yield key, environ[key] 33 | 34 | 35 | # `get_host` comes from `werkzeug.wsgi` 36 | def get_host(environ): 37 | """Return the real host for the given WSGI environment. This takes care 38 | of the `X-Forwarded-Host` header. 39 | 40 | :param environ: the WSGI environment to get the host of. 41 | """ 42 | scheme = environ.get('wsgi.url_scheme') 43 | if 'HTTP_X_FORWARDED_HOST' in environ: 44 | result = environ['HTTP_X_FORWARDED_HOST'] 45 | elif 'HTTP_HOST' in environ: 46 | result = environ['HTTP_HOST'] 47 | else: 48 | result = environ['SERVER_NAME'] 49 | if (scheme, str(environ['SERVER_PORT'])) not \ 50 | in (('https', '443'), ('http', '80')): 51 | result += ':' + environ['SERVER_PORT'] 52 | if result.endswith(':80') and scheme == 'http': 53 | result = result[:-3] 54 | elif result.endswith(':443') and scheme == 'https': 55 | result = result[:-4] 56 | return result 57 | 58 | 59 | # `get_current_url` comes from `werkzeug.wsgi` 60 | def get_current_url(environ, root_only=False, strip_querystring=False, 61 | host_only=False): 62 | """A handy helper function that recreates the full URL for the current 63 | request or parts of it. Here an example: 64 | 65 | >>> from werkzeug import create_environ 66 | >>> env = create_environ("/?param=foo", "http://localhost/script") 67 | >>> get_current_url(env) 68 | 'http://localhost/script/?param=foo' 69 | >>> get_current_url(env, root_only=True) 70 | 'http://localhost/script/' 71 | >>> get_current_url(env, host_only=True) 72 | 'http://localhost/' 73 | >>> get_current_url(env, strip_querystring=True) 74 | 'http://localhost/script/' 75 | 76 | :param environ: the WSGI environment to get the current URL from. 77 | :param root_only: set `True` if you only want the root URL. 78 | :param strip_querystring: set to `True` if you don't want the querystring. 79 | :param host_only: set to `True` if the host URL should be returned. 80 | """ 81 | tmp = [environ['wsgi.url_scheme'], '://', get_host(environ)] 82 | cat = tmp.append 83 | if host_only: 84 | return ''.join(tmp) + '/' 85 | cat(urllib_quote(environ.get('SCRIPT_NAME', '').rstrip('/'))) 86 | if root_only: 87 | cat('/') 88 | else: 89 | cat(urllib_quote('/' + environ.get('PATH_INFO', '').lstrip('/'))) 90 | if not strip_querystring: 91 | qs = environ.get('QUERY_STRING') 92 | if qs: 93 | cat('?' + qs) 94 | return ''.join(tmp) 95 | 96 | 97 | def get_client_ip(environ): 98 | """ 99 | Naively yank the first IP address in an X-Forwarded-For header 100 | and assume this is correct. 101 | 102 | Note: Don't use this in security sensitive situations since this 103 | value may be forged from a client. 104 | """ 105 | try: 106 | return environ['HTTP_X_FORWARDED_FOR'].split(',')[0].strip() 107 | except (KeyError, IndexError): 108 | return environ.get('REMOTE_ADDR') 109 | --------------------------------------------------------------------------------