Compare commits

...

No commits in common. "master" and "debian" have entirely different histories.

206 changed files with 0 additions and 20000 deletions

25
.gitignore vendored
View File

@ -1,25 +0,0 @@
*.pyc
*.log
*.egg
*.db
*.pid
.coverage
.DS_Store
.tox
pip-log.txt
/.tox
/*.egg-info
/build
/cover
/dist
/example_project/local_settings.py
/docs/_build
/sentry_index/
/sentry_test_index
/example_project/*.db
bin/
include/
lib/
.idea
.eggs
venv

3
.gitmodules vendored
View File

@ -1,3 +0,0 @@
[submodule "docs/_sentryext"]
path = docs/_sentryext
url = https://github.com/getsentry/sentry-doc-support.git

View File

@ -1,75 +0,0 @@
language: python
sudo: false
addons:
apt:
packages:
- libevent-dev
cache:
directories:
- "$HOME/.cache/pip"
#deploy:
# provider: pypi
# user: getsentry
# password:
# secure: NMwOI1H9arp2vbgaidx9OY6y8990hiu0WsHtowEvEdGKXNzAQcy0sW3SoKcB6FN0bk11xhj49+5C++KAwMYwE/SL8Y5OoZ1/iYVI4/XlWNukr+1/pfPKVMgw3v5W+pL5Ba9TBdFfIoFPNYUDPLItSSjg94Bm95034gBkYWC5Hl0=
# on:
# tags: true
python:
- '2.6'
- '2.7'
- '3.2'
- '3.3'
- '3.4'
- '3.5'
- pypy
env:
matrix:
- DJANGO=Django==1.4.20
- DJANGO=Django==1.5.12
- DJANGO=Django==1.6.11
- DJANGO=Django==1.7.11
- DJANGO=Django==1.8.7
- DJANGO=Django==1.9
- DJANGO="-e git+git://github.com/django/django.git#egg=Django"
install:
- time ci/setup
- pip install codecov "coverage<4"
script:
- if [[ ${TRAVIS_PYTHON_VERSION} != 'pypy' ]]; then make lint; fi
- coverage run --source=raven -m py.test tests --timeout 10
after_success:
- codecov -e DJANGO
matrix:
allow_failures:
- env: DJANGO="-e git+git://github.com/django/django.git#egg=Django"
exclude:
- python: '3.2'
env: DJANGO=Django==1.4.20
- python: '3.2'
env: DJANGO=Django==1.9
- python: '3.3'
env: DJANGO=Django==1.4.20
- python: '3.3'
env: DJANGO=Django==1.9
- python: '3.4'
env: DJANGO=Django==1.4.20
- python: '3.5'
env: DJANGO=Django==1.4.20
- python: '3.5'
env: DJANGO=Django==1.5.12
- python: '3.5'
env: DJANGO=Django==1.6.11
- python: '3.5'
env: DJANGO=Django==1.7.11
- python: '3.5'
env: DJANGO=Django==1.8.7
- python: '2.6'
env: DJANGO="-e git+git://github.com/django/django.git#egg=Django"
- python: '3.2'
env: DJANGO="-e git+git://github.com/django/django.git#egg=Django"
- python: '2.6'
env: DJANGO=Django==1.9
- python: '2.6'
env: DJANGO=Django==1.8.7
- python: '2.6'
env: DJANGO=Django==1.7.11

View File

@ -1 +0,0 @@
http://github.com/getsentry/raven-python/contributors

500
CHANGES
View File

@ -1,500 +0,0 @@
Version 5.11.1
--------------
* The raven client supports the stacktrace to be absent. This improves support
with celery and multiprocessing.
Version 5.11.0
--------------
* ``Client.configure_logging`` has been removed, and handlers will not automatically
be added to 'sentry' and 'raven' namespaces.
* Improved double error check
* Restored support for exc_info is True.
Version 5.10.2
--------------
* Remember exceptions in flight until the context is cleared so that two
reports with the same exception data do not result in two errors
being logged.
* Allow logging exclusions.
Version 5.10.1
--------------
* Fixed a problem where bytes as keys in dictionaries caused problems
on data sanitization if those bytes were outside of the ASCII range.
* Django client no longer requires the user object to be a subclass
of the base model.
* Corrected an issue with the Django log handler which would cause a recursive import.
Version 5.10.0
--------------
* Restore template debug support for Django 1.9 and newer.
* Correctly handle SSL verification disabling for newer Python versions.
Version 5.9.2
-------------
* Correct behavior introduced for Django 1.9.
Version 5.9.1
-------------
* Support for isolated apps in Django 1.9.
Version 5.9.0
-------------
* The threaded worker will now correctly handle forking.
* The 'environment' parameter is now supported (requires a Sentry 8.0 server ).
* 'tags' can now be specified as part of a LoggingHandler's constructor.
Version 5.8.0
-------------
* Added support for detecting `release` on Heroku.
* pkg_resources is now prioritized for default version detection.
* Updated `in_app` support to include exception frames.
* Fixed support for `SENTRY_USER_ATTRS` in Flask.
* Handle DSNs which are sent as unicode values in Python 2.
Version 5.7.2
-------------
* Handle passing ``fingerprint`` through logging handler.
Version 5.7.1
-------------
* Correctly handle SHAs in .git/HEAD.
* Fixed several cases of invalid Python3 syntax.
Version 5.7.0
-------------
* Reverted changes to Celery which incorrectly caused some configurations
to log unwanted messages.
* Improved behavior in ``fetch_git_sha``.
* Removed ``is_authenticated`` property from most integrations.
* Better error handling for errors within Flask context.
* Support for new versions of Flask-Login.
* Update Tornado support for modern versions.
* Update stacktrace truncation code to match current versions of Sentry server.
Version 5.6.0
-------------
* Content is no longer base64-encoded.
* ``fingerprint`` is now correctly supported.
* Django: 1.9 compatibility.
* Celery: Filter ``celery.redirect`` logger.
Version 5.5.0
-------------
* Added ``sys.excepthook`` handler (installed by default).
* Fixed an issue where ``wrap_wsgi`` wasn't being respected.
* Various deprecated code removed.
Version 5.4.4
-------------
* Enforce string-type imports.
Version 5.4.3
-------------
* Python 3 compatibility fixes.
Version 5.4.2
-------------
* Remove scheme checking on transports.
* Added ``SENTRY_TRANSPORT`` to Flask and Django configurations.
Version 5.4.1
-------------
* Fixed packaging of 5.4.0 which erronously kept the ``aiohttp.py`` file in the wheel only.
Version 5.4.0
-------------
* Binding transports via a scheme prefix on DSNs is now deprecated.
* ``raven.conf.load`` has been removed.
* Upstream-related configuration (such as url, project_id, and keys) is now contained in ``RemoteConfig``
attached to ``Client.remote``
* The ``aiohttp`` transport has been moved to ``raven-aiohttp`` package.
Version 5.3.1
-------------
* Restored support for patching Django's BaseCommand.execute.
Version 5.3.0
-------------
* The UDP transport has been removed.
* The integrated Sentry+Django client has been removed. This is now part of Sentry core.
* Server configuration *must* now be specified with a DSN.
* Upstream errors now have increased verbosity in logs.
* Unsent events now log to 'sentry.errors.uncaught'.
* Django management commands should now effectively autopatch (when run from the CLI).
* Flask wrapper now includes user_context, tags_context, and extra_context helpers.
* Python version is now reported with modules.
Version 5.2.0
-------------
* Protocol version is now 6 (requires Sentry 7.0 or newer).
* Added ``release`` option to Client.
* Added ``fetch_git_sha`` helper.
* Added ``fetch_package_version`` helper.
* Added cookie string sanitizing.
* Added threaded request transport: "threaded+requests+http(s)".
Version 5.1.0
-------------
* Added aiohttp transport.
* Corrected behavior with auto_log_stacks and exceptions.
* Add support for certifi.
* Expanded Flask support.
* Expanded Django support.
* Corrected an issue where processors were not correctly applying.
Version 5.0.0
-------------
* Sentry client protocol is now version 5.
* Various improvements to threaded transport.
Version 4.2.0
-------------
* SSL verification is now on by default.
* Rate limits and other valid API errors are now handled more gracefully.
* Added ``last_event_id`` and ``X-Sentry-ID`` header to Flask.
Version 4.1.0
-------------
* Added verify_ssl option to HTTP transport (defaults to False).
* Added capture_locals option (defaults to True).
* message can now be passed to capture* functions.
* Django <1.4 is no longer supported.
* Function object serialization has been improved.
* SanitizePasswordsProcessor removes API keys.
Version 4.0.0
-------------
* Sentry client protocol is now version 4.
Version 3.6.0
-------------
This changelog does not attempt to account for all changes between 3.6.0 and 3.0.0, but
rather focuses on recent important changes
* Transport modules paths have been refactored.
* The threaded transport is now the default.
* Client.context has changed. Please see documentation for new API.
* Client.user_context was added.
* Client.http_context was added.
* Client.extra_context was added.
* Client.tags_context was added.
* Flask support has been greatly improved.
* raven.contrib.celery.Client has been removed as it was invalid.
Version 3.0.0
-------------
Version 3.0 of Raven requires a Sentry server running at least version 5.1, as it implements
version 3 of the protocol.
Support includes:
* Sending 'python' as the platform.
* The 'tags' option (on all constructors that support options).
* Updated authentication header.
Additionally, the following has changed:
* Configuring the client with an empty DSN value will disable sending of messages.
* All clients should now check ``Client.is_enabled()`` to verify if they should send data.
* ``Client.create_from_text`` and ``Client.create_from_exception`` have been removed.
* ``Client.message`` and ``Client.exception`` have been removed.
* The ``key`` setting has been removed.
* The ``DEBUG`` setting in Django no longer disables Raven.
* The ``register_signals`` option in RAVEN_CONFIG (Django) is no longer used.
* A new helper, ``Client.context()`` is now available for scoping options.
* ``Client.captureExceptions`` is now deprecated in favor of ``Client.context``.
* Credit card values will now be sanitized with the default processors.
* A new eventlet+http transport exists.
* A new threaded+http transport exists.
* PyPy is now supported.
* Django 1.5 should now be supported (experimental).
* Gevent 1.0 should now be supported (experimental).
* Python 2.5 is no longer supported.
* [Django] The ``skip_sentry`` attribute is no longer supported. A new option config option has replaced this: ``SENTRY_IGNORE_EXCEPTIONS``.
Version 2.0.0
-------------
* New serializers exist (and can be registered) against Raven. See ``raven.utils.serializer`` for more information.
* You can now pass ``tags`` to the ``capture`` method. This will require a Sentry server compatible with the new
tags protocol.
* A new gevent+http transport exists.
* A new tornado+http transport exists.
* A new twisted+http transport exists.
* Zope integration has been added. See docs for more information.
* PasteDeploy integration has been added. See docs for more information.
* A Django endpoint now exists for proxying requests to Sentry. See ``raven.contrib.django.views`` for more information.
Version 1.9.0
-------------
* Signatures are no longer sent with messages. This requires the server version to be at least 4.4.6.
* Several fixes and additions were added to the Django report view.
* ``long`` types are now handled in transform().
* Improved integration with Celery (and django-celery) for capturing errors.
Version 1.8.0
-------------
* There is now a builtin view as part of the Django integration for sending events server-side
(from the client) to Sentry. The view is currently undocumented, but is available as ``{% url raven-report %}``
and will use your server side credentials. To use this view you'd simply swap out the servers configuration in
raven-js and point it to the given URL.
* A new middleware for ZeroRPC now exists.
* A new protocol for registering transports now exists.
* Corrected some behavior in the UDP transport.
* Celery signals are now connected by default within the Django integration.
Version 1.7.0
-------------
* The password sanitizer will now attempt to sanitize key=value pairs within strings (such as the querystring).
* Two new santiziers were added: RemoveStackLocalsProcessor and RemovePostDataProcessor
Version 1.6.0
-------------
* Stacks must now be passed as a list of tuples (frame, lineno) rather than a list of frames. This
includes calls to logging (extra={'stack': []}), as well as explicit client calls (capture(stack=[])).
This corrects some issues (mostly in tracebacks) with the wrong lineno being reported for a frame.
Version 1.4.0
-------------
* Raven now tracks the state of the Sentry server. If it receives an error, it will slow down
requests to the server (by passing them into a named logger, sentry.errors), and increasingly
delay the next try with repeated failures, up to about a minute.
Version 1.3.6
-------------
* gunicorn is now disabled in default logging configuration
Version 1.3.5
-------------
* Moved exception and message methods to capture{Exception,Message}.
* Added captureQuery method.
Version 1.3.4
-------------
* Corrected duplicate DSN behavior in Django client.
Version 1.3.3
-------------
* Django can now be configured by setting SENTRY_DSN.
* Improve logging for send_remote failures (and correct issue created when
send_encoded was introduced).
* Renamed SantizePassworsProcessor to SanitizePassworsProcessor.
Version 1.3.2
-------------
* Support sending the culprit with logging messages as part of extra.
Version 1.3.1
-------------
* Added client.exception and client.message shortcuts.
Version 1.3.0
-------------
* Refactored client send API to be more easily extensible.
* MOAR TESTS!
Version 1.2.2
-------------
* Gracefully handle exceptions in Django client when using integrated
setup.
* Added Client.error_logger as a new logger instance that points to
``sentry.errors``.
Version 1.2.1
-------------
* Corrected behavior with raven logging errors to send_remote
which could potentially cause a very large backlog to Sentry
when it should just log to ``sentry.errors``.
* Ensure the ``site`` argument is sent to the server.
Version 1.2.0
-------------
* Made DSN a first-class citizen throughout Raven.
* Added a Pylons-specific WSGI middleware.
* Improved the generic WSGI middleware to capture HTTP information.
* Improved logging and logbook handlers.
Version 1.1.6
-------------
* Corrected logging stack behavior so that it doesnt capture raven+logging
extensions are part of the frames.
Version 1.1.5
-------------
* Remove logging attr magic.
Version 1.1.4
-------------
* Correct encoding behavior on bool and float types.
Version 1.1.3
-------------
* Fix 'request' attribute on Django logging.
Version 1.1.2
-------------
* Corrected logging behavior with extra data to match pre 1.x behavior.
Version 1.1.1
-------------
* Handle frames that are missing f_globals and f_locals.
* Stricter conversion of int and boolean values.
* Handle invalid sources for templates in Django.
Version 1.1.0
-------------
* varmap was refactored to send keys back to callbacks.
* SanitizePasswordProcessor now handles http data.
Version 1.0.5
-------------
* Renaming raven2 to raven as it causes too many issues.
Version 1.0.4
-------------
* Corrected a bug in setup_logging.
* Raven now sends "sentry_version" header which is the expected
server version.
Version 1.0.3
-------------
* Handle more edge cases on stack iteration.
Version 1.0.2
-------------
* Gracefully handle invalid f_locals.
Version 1.0.1
-------------
* All datetimes are assumed to be utcnow() as of Sentry 2.0.0-RC5
Version 1.0.0
-------------
* Now only works with Sentry>=2.0.0 server.
* Raven is now listed as raven2 on PyPi.
Version 0.8.0
-------------
* raven.contrib.celery is now useable.
* raven.contrib.django.celery is now useable.
* Fixed a bug with request.raw_post_data buffering in Django.
Version 0.7.1
-------------
* Servers would stop iterating after the first successful post which was not the
intended behavior.
Version 0.7.0
-------------
* You can now explicitly pass a list of frame objects to the process method.
Version 0.6.1
-------------
* The default logging handler (SentryHandler) will now accept a set of kwargs to instantiate
a new client with (GH-10).
* Fixed a bug with checksum generation when module or function were missing (GH-9).
Version 0.6.0
-------------
* Added a Django-specific WSGI middleware.
Version 0.5.1
-------------
* Two minor fixes for the Django client:
* Ensure the __sentry__ key exists in data in (GH-8).
* properly set kwargs['data'] to an empty list when its a NoneType (GH-6).
Version 0.5.0
-------------
* Require ``servers`` on base Client.
* Added support for the ``site`` option in Client.
* Moved raven.contrib.django.logging to raven.contrib.django.handlers.
Version 0.4.0
-------------
* Fixed an infinite loop in iter_tb.
Version 0.3.0
-------------
* Removed the ``thrashed`` key in ``request.sentry`` for the Django integration.
* Changed the logging handler to correctly inherit old-style classes (GH-1).
* Added a ``client`` argument to ``raven.contrib.django.models.get_client()``.
Version 0.2.0
-------------
* auto_log_stacks now works with create_from_text
* added Client.get_ident
Version 0.1.0
-------------
* Initial version of Raven (extracted from django-sentry 1.12.1).

12
LICENSE
View File

@ -1,12 +0,0 @@
Copyright (c) 2015 Functional Software, Inc and individual contributors.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
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.
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.
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.

View File

@ -1,5 +0,0 @@
include setup.py README.rst MANIFEST.in LICENSE *.txt
recursive-include raven/contrib/zope *.xml
recursive-include raven/data *
graft tests
global-exclude *~

View File

@ -1,27 +0,0 @@
bootstrap:
pip install -e "file://`pwd`#egg=raven[tests]"
make setup-git
test: bootstrap lint
@echo "Running Python tests"
py.test -x tests
@echo ""
lint:
@echo "Linting Python files"
PYFLAKES_NODOCTEST=1 flake8 raven || exit 1
@echo ""
coverage:
coverage run runtests.py --include=raven/* && \
coverage html --omit=*/migrations/* -d cover
setup-git:
git config branch.autosetuprebase always
cd .git/hooks && ln -sf ../../hooks/* ./
publish:
rm -rf dist build
python setup.py sdist bdist_wheel upload
.PHONY: bootstrap test lint coverage setup-git publish

View File

@ -1,24 +0,0 @@
Raven
======
.. image:: https://travis-ci.org/getsentry/raven-python.svg?branch=master
:target: https://travis-ci.org/getsentry/raven-python
Raven is a Python client for `Sentry <http://getsentry.com/>`_. It provides
full out-of-the-box support for many of the popular frameworks, including
Django, and Flask. Raven also includes drop-in support for any WSGI-compatible
web application.
Your application doesn't live on the web? No problem! Raven is easy to use in
any Python application.
Resources
---------
* `Documentation <https://docs.getsentry.com/hosted/clients/python/>`_
* `Bug Tracker <http://github.com/getsentry/raven-python/issues>`_
* `Code <http://github.com/getsentry/raven-python>`_
* `Mailing List <https://groups.google.com/group/getsentry>`_
* `IRC <irc://irc.freenode.net/sentry>`_ (irc.freenode.net, #sentry)
* `Travis CI <http://travis-ci.org/getsentry/raven-python>`_

View File

@ -1,13 +0,0 @@
#!/bin/bash -eu
pip install $DJANGO
make bootstrap
if [[ ${TRAVIS_PYTHON_VERSION::1} == '2' ]]; then
pip install gevent
fi
if [[ ${TRAVIS_PYTHON_VERSION} == '3.2' ]]; then
pip install -I https://github.com/celery/celery/archive/3.0.zip
fi
if [[ ${TRAVIS_PYTHON_VERSION::1} == '3' ]]; then
pip uninstall django-celery -y
fi

View File

@ -1,76 +0,0 @@
from django.conf import settings
import os.path
import sys
collect_ignore = []
if sys.version_info[0] > 2:
if sys.version_info[1] < 3:
collect_ignore.append("tests/contrib/flask")
if sys.version_info[1] == 2:
collect_ignore.append("tests/handlers/logbook")
try:
import gevent
except ImportError:
collect_ignore.append("tests/transport/gevent")
try:
import web
except ImportError:
collect_ignore.append("tests/contrib/webpy")
INSTALLED_APPS = [
'django.contrib.auth',
'django.contrib.admin',
'django.contrib.sessions',
'django.contrib.sites',
# Included to fix Disqus' test Django which solves IntegrityMessage case
'django.contrib.contenttypes',
'raven.contrib.django',
'tests.contrib.django',
]
use_djcelery = True
try:
import djcelery
INSTALLED_APPS.append('djcelery')
except ImportError:
use_djcelery = False
def pytest_configure(config):
where_am_i = os.path.dirname(os.path.abspath(__file__))
if not settings.configured:
settings.configure(
DATABASE_ENGINE='sqlite3',
DATABASES={
'default': {
'NAME': ':memory:',
'ENGINE': 'django.db.backends.sqlite3',
'TEST_NAME': ':memory:',
},
},
DATABASE_NAME=':memory:',
TEST_DATABASE_NAME=':memory:',
INSTALLED_APPS=INSTALLED_APPS,
ROOT_URLCONF='',
DEBUG=False,
SITE_ID=1,
BROKER_HOST="localhost",
BROKER_PORT=5672,
BROKER_USER="guest",
BROKER_PASSWORD="guest",
BROKER_VHOST="/",
SENTRY_ALLOW_ORIGIN='*',
CELERY_ALWAYS_EAGER=True,
TEMPLATE_DEBUG=True,
PROJECT_ROOT=where_am_i,
TEMPLATE_DIRS=[os.path.join(where_am_i, 'tests', 'contrib', 'django', 'templates')],
ALLOWED_HOSTS=['*'],
)

View File

@ -1,130 +0,0 @@
# Makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
PAPER =
BUILDDIR = ./_build
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest
help:
@echo "Please use \`make <target>' where <target> is one of"
@echo " html to make standalone HTML files"
@echo " dirhtml to make HTML files named index.html in directories"
@echo " singlehtml to make a single large HTML file"
@echo " pickle to make pickle files"
@echo " json to make JSON files"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " qthelp to make HTML files and a qthelp project"
@echo " devhelp to make HTML files and a Devhelp project"
@echo " epub to make an epub"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " latexpdf to make LaTeX files and run them through pdflatex"
@echo " text to make text files"
@echo " man to make manual pages"
@echo " changes to make an overview of all changed/added/deprecated items"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
clean:
-rm -rf $(BUILDDIR)/*
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
dirhtml:
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
singlehtml:
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
@echo
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
pickle:
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."
json:
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo
@echo "Build finished; now you can process the JSON files."
htmlhelp:
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp."
qthelp:
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Sentry.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Sentry.qhc"
devhelp:
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
@echo
@echo "Build finished."
@echo "To view the help file:"
@echo "# mkdir -p $$HOME/.local/share/devhelp/Sentry"
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Sentry"
@echo "# devhelp"
epub:
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
@echo
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
latex:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
@echo "Run \`make' in that directory to run these through (pdf)latex" \
"(use \`make latexpdf' here to do that automatically)."
latexpdf:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through pdflatex..."
make -C $(BUILDDIR)/latex all-pdf
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
text:
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
@echo
@echo "Build finished. The text files are in $(BUILDDIR)/text."
man:
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
@echo
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
changes:
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo
@echo "The overview file is in $(BUILDDIR)/changes."
linkcheck:
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
@echo
@echo "Link check complete; look for any errors in the above output " \
"or in $(BUILDDIR)/linkcheck/output.txt."
doctest:
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."

@ -1 +0,0 @@
Subproject commit ace38d37571b53dcbf6c00128b216fd9b53ec2bf

BIN
docs/_static/logo.png vendored

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

View File

@ -1,16 +0,0 @@
{%- extends "basic/layout.html" %}
{%- block extrahead %}
{{ super() }}
{% if theme_touch_icon %}
<link rel="apple-touch-icon" href="{{ pathto('_static/' ~ theme_touch_icon, 1) }}" />
{% endif %}
<link media="only screen and (max-device-width: 480px)" href="{{
pathto('_static/small_flask.css', 1) }}" type= "text/css" rel="stylesheet" />
<meta name="viewport" content="width=device-width, initial-scale=0.9, maximum-scale=0.9">
{% endblock %}
{%- block relbar2 %}{% endblock %}
{%- block footer %}
<div class="footer">
&copy; Copyright {{ copyright }}.
</div>
{%- endblock %}

View File

@ -1,19 +0,0 @@
<h3>Related Topics</h3>
<ul>
<li><a href="{{ pathto(master_doc) }}">Documentation overview</a><ul>
{%- for parent in parents %}
<li><a href="{{ parent.link|e }}">{{ parent.title }}</a><ul>
{%- endfor %}
{%- if prev %}
<li>Previous: <a href="{{ prev.link|e }}" title="{{ _('previous chapter')
}}">{{ prev.title }}</a></li>
{%- endif %}
{%- if next %}
<li>Next: <a href="{{ next.link|e }}" title="{{ _('next chapter')
}}">{{ next.title }}</a></li>
{%- endif %}
{%- for parent in parents %}
</ul></li>
{%- endfor %}
</ul></li>
</ul>

View File

@ -1,449 +0,0 @@
/*
* flasky.css_t
* ~~~~~~~~~~~~
*
* :copyright: Copyright 2010 by Armin Ronacher. Modifications by Kenneth Reitz.
* :license: Flask Design License, see LICENSE for details.
*/
{% set page_width = '940px' %}
{% set sidebar_width = '220px' %}
@import url("basic.css");
/* -- page layout ----------------------------------------------------------- */
img {
max-width: 100%;
}
body {
font-family: 'goudy old style', 'minion pro', 'bell mt', Georgia, 'Hiragino Mincho Pro';
font-size: 17px;
background-color: white;
color: #000;
margin: 0;
padding: 0;
}
div.document {
width: {{ page_width }};
margin: 30px auto 0 auto;
}
div.documentwrapper {
float: left;
width: 100%;
}
div.bodywrapper {
margin: 0 0 0 {{ sidebar_width }};
}
div.sphinxsidebar {
width: {{ sidebar_width }};
}
hr {
border: 1px solid #B1B4B6;
}
div.body {
background-color: #ffffff;
color: #3E4349;
padding: 0 30px 0 30px;
}
img.floatingflask {
padding: 0 0 10px 10px;
float: right;
}
div.footer {
width: {{ page_width }};
margin: 20px auto 30px auto;
font-size: 14px;
color: #888;
text-align: right;
}
div.footer a {
color: #888;
}
div.related {
display: none;
}
div.sphinxsidebar a {
color: #444;
text-decoration: none;
border-bottom: 1px dotted #999;
}
div.sphinxsidebar a:hover {
border-bottom: 1px solid #999;
}
div.sphinxsidebar {
font-size: 14px;
line-height: 1.5;
}
div.sphinxsidebarwrapper {
padding: 18px 10px;
}
div.sphinxsidebarwrapper p.logo {
padding: 0;
margin: -10px 0 0 -20px;
text-align: center;
}
div.sphinxsidebar h3,
div.sphinxsidebar h4 {
font-family: 'Garamond', 'Georgia', serif;
color: #444;
font-size: 24px;
font-weight: normal;
margin: 0 0 5px 0;
padding: 0;
}
div.sphinxsidebar h4 {
font-size: 20px;
}
div.sphinxsidebar h3 a {
color: #444;
}
div.sphinxsidebar p.logo a,
div.sphinxsidebar h3 a,
div.sphinxsidebar p.logo a:hover,
div.sphinxsidebar h3 a:hover {
border: none;
}
div.sphinxsidebar p {
color: #555;
margin: 10px 0;
}
div.sphinxsidebar ul {
margin: 10px 0;
padding: 0;
color: #000;
}
div.sphinxsidebar input {
border: 1px solid #ccc;
font-family: 'Georgia', serif;
font-size: 1em;
}
/* -- body styles ----------------------------------------------------------- */
a {
color: #004B6B;
text-decoration: underline;
}
a:hover {
color: #6D4100;
text-decoration: underline;
}
div.body h1,
div.body h2,
div.body h3,
div.body h4,
div.body h5,
div.body h6 {
font-family: 'Garamond', 'Georgia', serif;
font-weight: normal;
margin: 30px 0px 10px 0px;
padding: 0;
}
div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; }
div.body h2 { font-size: 180%; }
div.body h3 { font-size: 150%; }
div.body h4 { font-size: 130%; }
div.body h5 { font-size: 100%; }
div.body h6 { font-size: 100%; }
a.headerlink {
color: #ddd;
padding: 0 4px;
text-decoration: none;
}
a.headerlink:hover {
color: #444;
background: #eaeaea;
}
div.body p, div.body dd, div.body li {
line-height: 1.4em;
}
div.admonition {
background: #fafafa;
margin: 20px -30px;
padding: 10px 30px;
border-top: 1px solid #ccc;
border-bottom: 1px solid #ccc;
}
div.admonition tt.xref, div.admonition a tt {
border-bottom: 1px solid #fafafa;
}
dd div.admonition {
margin-left: -60px;
padding-left: 60px;
}
div.admonition p.admonition-title {
font-family: 'Garamond', 'Georgia', serif;
font-weight: normal;
font-size: 24px;
margin: 0 0 10px 0;
padding: 0;
line-height: 1;
}
div.admonition p.last {
margin-bottom: 0;
}
div.highlight {
background-color: white;
}
dt:target, .highlight {
background: #FAF3E8;
}
div.note {
background-color: #eee;
border: 1px solid #ccc;
}
div.seealso {
background-color: #ffc;
border: 1px solid #ff6;
}
div.topic {
background-color: #eee;
}
p.admonition-title {
display: inline;
}
p.admonition-title:after {
content: ":";
}
pre, tt {
font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
font-size: 0.9em;
}
img.screenshot {
}
tt.descname, tt.descclassname {
font-size: 0.95em;
}
tt.descname {
padding-right: 0.08em;
}
img.screenshot {
-moz-box-shadow: 2px 2px 4px #eee;
-webkit-box-shadow: 2px 2px 4px #eee;
box-shadow: 2px 2px 4px #eee;
}
table.docutils {
border: 1px solid #888;
-moz-box-shadow: 2px 2px 4px #eee;
-webkit-box-shadow: 2px 2px 4px #eee;
box-shadow: 2px 2px 4px #eee;
}
table.docutils td, table.docutils th {
border: 1px solid #888;
padding: 0.25em 0.7em;
}
table.field-list, table.footnote {
border: none;
-moz-box-shadow: none;
-webkit-box-shadow: none;
box-shadow: none;
}
table.footnote {
margin: 15px 0;
width: 100%;
border: 1px solid #eee;
background: #fdfdfd;
font-size: 0.9em;
}
table.footnote + table.footnote {
margin-top: -15px;
border-top: none;
}
table.field-list th {
padding: 0 0.8em 0 0;
}
table.field-list td {
padding: 0;
}
table.footnote td.label {
width: 0px;
padding: 0.3em 0 0.3em 0.5em;
}
table.footnote td {
padding: 0.3em 0.5em;
}
dl {
margin: 0;
padding: 0;
}
dl dd {
margin-left: 30px;
}
blockquote {
margin: 0 0 0 30px;
padding: 0;
}
ul, ol {
margin: 10px 0 10px 30px;
padding: 0;
}
pre {
background: #eee;
padding: 7px 30px;
margin: 15px -30px;
line-height: 1.3em;
}
dl pre, blockquote pre, li pre {
margin-left: -60px;
padding-left: 60px;
}
dl dl pre {
margin-left: -90px;
padding-left: 90px;
}
tt {
background-color: #ecf0f3;
color: #222;
/* padding: 1px 2px; */
}
tt.xref, a tt {
background-color: #FBFBFB;
border-bottom: 1px solid white;
}
a.reference {
text-decoration: none;
border-bottom: 1px dotted #004B6B;
}
a.reference:hover {
border-bottom: 1px solid #6D4100;
}
a.footnote-reference {
text-decoration: none;
font-size: 0.7em;
vertical-align: top;
border-bottom: 1px dotted #004B6B;
}
a.footnote-reference:hover {
border-bottom: 1px solid #6D4100;
}
a:hover tt {
background: #EEE;
}
@media screen and (max-width: 600px) {
div.sphinxsidebar {
display: none;
}
div.document {
width: 100%;
}
div.documentwrapper {
margin-left: 0;
margin-top: 0;
margin-right: 0;
margin-bottom: 0;
}
div.bodywrapper {
margin-top: 0;
margin-right: 0;
margin-bottom: 0;
margin-left: 0;
}
ul {
margin-left: 0;
}
.document {
width: auto;
}
.footer {
width: auto;
}
.bodywrapper {
margin: 0;
}
.footer {
width: auto;
}
.github {
display: none;
}
}
/* misc. */
.revsys-inline {
display: none!important;
}

View File

@ -1,90 +0,0 @@
/*
* small_flask.css_t
* ~~~~~~~~~~~~~~~~~
*
* :copyright: Copyright 2010 by Armin Ronacher.
* :license: Flask Design License, see LICENSE for details.
*/
body {
margin: 0;
padding: 20px 30px;
}
div.documentwrapper {
float: none;
background: white;
}
div.sphinxsidebar {
display: block;
float: none;
width: 102.5%;
margin: 50px -30px -20px -30px;
padding: 10px 20px;
background: #333;
color: white;
}
div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p,
div.sphinxsidebar h3 a {
color: white;
}
div.sphinxsidebar a {
color: #aaa;
}
div.sphinxsidebar p.logo {
display: none;
}
div.document {
width: 100%;
margin: 0;
}
div.related {
display: block;
margin: 0;
padding: 10px 0 20px 0;
}
div.related ul,
div.related ul li {
margin: 0;
padding: 0;
}
div.footer {
display: none;
}
div.bodywrapper {
margin: 0;
}
div.body {
min-height: 0;
padding: 0;
}
.rtd_doc_footer {
display: none;
}
.document {
width: auto;
}
.footer {
width: auto;
}
.footer {
width: auto;
}
.github {
display: none;
}

View File

@ -1,7 +0,0 @@
[theme]
inherit = basic
stylesheet = flasky.css
pygments_style = flask_theme_support.FlaskyStyle
[options]
touch_icon =

View File

@ -1,219 +0,0 @@
Advanced Usage
==============
This covers some advanced usage scenarios for raven Python.
Alternative Installations
-------------------------
If you want to use the latest git version you can get it from `the github
repository <https://github.com/getsentry/raven-python>`_::
git clone https://github.com/getsentry/raven-python
pip install raven-python
Certain additional features can be installed by defining the feature when
``pip`` installing it. For instance to install all dependencies needed to
use the Flask integration, you can depend on ``raven[flask]``::
pip install raven[flask]
For more information refer to the individual integration documentation.
.. _python-client-config:
Configuring the Client
----------------------
Settings are specified as part of the initialization of the client. The
client is a class that can be instantiated with a specific configuration
and all reporting can then happen from the instance of that object.
Typically an instance is created somewhere globally and then imported as
necessary.
.. code-block:: python
from raven import Client
# Read configuration from the ``SENTRY_DSN`` environment variable
client = Client()
# Manually specify a DSN
client = Client('___DSN___')
A reasonably configured client should generally include a few additional
settings:
.. code-block:: python
import os
import raven
client = raven.Client(
dsn='___DSN___'
# inform the client which parts of code are yours
# include_paths=['my.app']
include_paths=[__name__.split('.', 1)[0]],
# pass along the version of your application
# release='1.0.0'
# release=raven.fetch_package_version('my-app')
release=raven.fetch_git_sha(os.path.dirname(__file__)),
)
.. versionadded:: 5.2.0
The *fetch_package_version* and *fetch_git_sha* helpers.
Client Arguments
----------------
The following are valid arguments which may be passed to the Raven client:
.. describe:: dsn
A Sentry compatible DSN as mentioned before::
dsn = '___DSN___'
.. describe:: transport
The HTTP transport class to use. By default this is an asynchronous worker
thread that runs in-process.
For more information see :doc:`transports`.
.. describe:: site
An optional, arbitrary string to identify this client installation::
site = 'my site name'
.. describe:: name
This will override the ``server_name`` value for this installation.
Defaults to ``socket.gethostname()``::
name = 'sentry_rocks_' + socket.gethostname()
.. describe:: release
The version of your application. This will map up into a Release in
Sentry::
release = '1.0.3'
.. describe:: exclude_paths
Extending this allow you to ignore module prefixes when we attempt to
discover which function an error comes from (typically a view)::
exclude_paths = [
'django',
'sentry',
'raven',
'lxml.objectify',
]
.. describe:: include_paths
For example, in Django this defaults to your list of ``INSTALLED_APPS``,
and is used for drilling down where an exception is located::
include_paths = [
'django',
'sentry',
'raven',
'lxml.objectify',
]
.. describe:: max_list_length
The maximum number of items a list-like container should store.
If an iterable is longer than the specified length, the left-most
elements up to length will be kept.
.. note:: This affects sets as well, which are unordered.
::
list_max_length = 50
.. describe:: string_max_length
The maximum characters of a string that should be stored.
If a string is longer than the given length, it will be truncated down
to the specified size::
string_max_length = 200
.. describe:: auto_log_stacks
Should Raven automatically log frame stacks (including locals) for all
calls as it would for exceptions::
auto_log_stacks = True
.. describe:: processors
A list of processors to apply to events before sending them to the
Sentry server. Useful for sending additional global state data or
sanitizing data that you want to keep off of the server::
processors = (
'raven.processors.SanitizePasswordsProcessor',
)
Sanitizing Data
---------------
Several processors are included with Raven to assist in data
sanitiziation. These are configured with the ``processors`` value.
.. describe:: raven.processors.SanitizePasswordsProcessor
Removes all keys which resemble ``password``, ``secret``, or
``api_key`` within stacktrace contexts, HTTP bits (such as cookies,
POST data, the querystring, and environment), and extra data.
.. describe:: raven.processors.RemoveStackLocalsProcessor
Removes all stacktrace context variables. This will cripple the
functionality of Sentry, as you'll only get raw tracebacks, but it will
ensure no local scoped information is available to the server.
.. describe:: raven.processors.RemovePostDataProcessor
Removes the ``body`` of all HTTP data.
Custom Grouping Behavior
------------------------
In some cases you may see issues where Sentry groups multiple events together
when they should be separate entities. In other cases, Sentry simply doesn't
group events together because they're so sporadic that they never look the same.
Both of these problems can be addressed by specifying the ``fingerprint``
attribute.
For example, if you have HTTP 404 (page not found) errors, and you'd prefer they
deduplicate by taking into account the URL:
.. code-block:: python
client.captureException(fingerprint=['{{ default }}', 'http://my-url/'])
.. sentry:edition:: hosted, on-premise
For more information, see :ref:`custom-grouping`.
A Note on uWSGI
---------------
If you're using uWSGI you will need to add ``enable-threads`` to the
default invocation, or you will need to switch off of the threaded default
transport.

View File

@ -1,154 +0,0 @@
API Reference
=============
.. default-domain:: py
This gives you an overview of the public API that raven-python exposes.
Client
------
.. py:class:: raven.Client(dsn=None, **kwargs)
The client needs to be instanciated once and can then be used for
submitting events to the Sentry server. For information about the
configuration of that client and which parameters are accepted see
:ref:`python-client-config`.
.. py:method:: capture(event_type, data=None, date=None, \
time_spent=None, extra=None, stack=False, tags=None, **kwargs)
This method is the low-level method for reporting events to
Sentry. It captures and processes an event and pipes it via the
configured transport to Sentry.
Example::
capture('raven.events.Message', message='foo', data={
'request': {
'url': '...',
'data': {},
'query_string': '...',
'method': 'POST',
},
'logger': 'logger.name',
}, extra={
'key': 'value',
})
:param event_type: the module path to the Event class. Builtins can
use shorthand class notation and exclude the
full module path.
:param data: the data base, useful for specifying structured data
interfaces. Any key which contains a '.' will be
assumed to be a data interface.
:param date: the datetime of this event. If not supplied the
current timestamp is used.
:param time_spent: a integer value representing the duration of the
event (in milliseconds)
:param extra: a dictionary of additional standard metadata.
:param stack: If set to `True` a stack frame is recorded together
with the event.
:param tags: dict of extra tags
:param kwargs: extra keyword arguments are handled specific to the
reported event type.
:return: a tuple with a 32-length string identifying this event
.. py:method:: captureMessage(message, **kwargs)
This is a shorthand to reporting a message via :meth:`capture`.
It passes ``'raven.events.Message'`` as `event_type` and the
message along. All other keyword arguments are regularly
forwarded.
Example::
client.captureMessage('This just happened!')
.. py:method:: captureException(message, exc_info=None, **kwargs)
This is a shorthand to reporting an exception via :meth:`capture`.
It passes ``'raven.events.Exception'`` as `event_type` and the
traceback along. All other keyword arguments are regularly
forwarded.
If exc_info is not provided, or is set to True, then this method
will perform the ``exc_info = sys.exc_info()`` and the requisite
clean-up for you.
Example::
try:
1 / 0
except Exception:
client.captureException()
.. py:method:: send(**data)
Accepts all data parameters and serializes them, then sends then
onwards via the transport to Sentry. This can be used as to send
low-level protocol data to the server.
.. py:attribute:: context
Returns a reference to the thread local context object. See
:py:class:`raven.context.Context` for more information.
.. py:method:: user_context(data)
Updates the user context for future events.
Equivalent to this::
client.context.merge({'user': data})
.. py:method:: http_context(data)
Updates the HTTP context for future events.
Equivalent to this::
client.context.merge({'request': data})
.. py:method:: extra_context(data)
Update the extra context for future events.
Equivalent to this::
client.context.merge({'extra': data})
.. py:method:: tags_context(data)
Update the tags context for future events.
Equivalent to this::
client.context.merge({'tags': data})
Context
-------
.. py:class:: raven.context.Context()
The context object works similar to a dictionary and is used to record
information that should be submitted with events automatically. It is
available through :py:attr:`raven.Client.context` and is thread local.
This means that you can modify this object over time to feed it with
more appropriate information.
.. py:method:: merge(data)
Performs a merge of the current data in the context and the new
data provided.
.. py:method:: clear()
Clears the context. It's important that you make sure to call
this when you reuse the thread for something else. For instance
for web frameworks it's generally a good idea to call this at the
end of the HTTP request.
Otherwise you run at risk of seeing incorrect information after
the first use of the thread.

View File

@ -1,231 +0,0 @@
# -*- coding: utf-8 -*-
#
# Sentry documentation build configuration file, created by
# sphinx-quickstart on Wed Oct 20 16:21:42 2010.
#
# This file is execfile()d with the current directory set to its containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
import os
import sys
import datetime
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#sys.path.insert(0, os.path.abspath('.'))
# -- General configuration -----------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
#extensions = ['sphinxtogithub']
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx']
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix of source filenames.
source_suffix = '.rst'
# The encoding of source files.
#source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = u'Raven'
copyright = u'%s, David Cramer' % datetime.datetime.today().year
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = __import__('pkg_resources').get_distribution('raven').version
# The full version, including alpha/beta/rc tags.
release = version
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ['_build']
# The reST default role (used for this markup: `text`) to use for all documents.
#default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []
intersphinx_mapping = {
'http://docs.python.org/2.7': None,
'django': ('http://docs.djangoproject.com/en/dev/', 'http://docs.djangoproject.com/en/dev/_objects/'),
'http://raven.readthedocs.org/en/latest': None
}
# -- Options for HTML output ---------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
html_theme = 'kr'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
# html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
html_theme_path = ['_themes']
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
#html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
#html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
html_logo = "_static/logo.png"
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}
# If false, no module index is generated.
#html_domain_indices = True
# If false, no index is generated.
#html_use_index = True
# If true, the index is split into individual pages for each letter.
#html_split_index = False
# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None
# Output file base name for HTML help builder.
htmlhelp_basename = 'Ravendoc'
# -- Options for LaTeX output --------------------------------------------------
# The paper size ('letter' or 'a4').
#latex_paper_size = 'letter'
# The font size ('10pt', '11pt' or '12pt').
#latex_font_size = '10pt'
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [
('index', 'Raven.tex', u'Raven Documentation',
u'David Cramer', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False
# If true, show page references after internal links.
#latex_show_pagerefs = False
# If true, show URL addresses after external links.
#latex_show_urls = False
# Additional stuff for the LaTeX preamble.
#latex_preamble = ''
# Documents to append as an appendix to all manuals.
#latex_appendices = []
# If false, no module index is generated.
#latex_domain_indices = True
# -- Options for manual page output --------------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
('index', 'raven', u'Raven Documentation',
[u'David Cramer'], 1)
]
if os.environ.get('SENTRY_FEDERATED_DOCS') != '1':
sys.path.insert(0, os.path.abspath('_sentryext'))
import sentryext
sentryext.activate()

View File

@ -1,42 +0,0 @@
Contributing
============
Want to contribute back to Sentry? This page describes the general development flow,
our philosophy, the test suite, and issue tracking.
(Though it actually doesn't describe all of that, yet)
Setting up an Environment
-------------------------
Sentry is designed to run off of setuptools with minimal work. Because of this
setting up a development environment requires only a few steps.
The first thing you're going to want to do, is build a virtualenv and install
any base dependancies.
::
virtualenv ~/.virtualenvs/raven
source ~/.virtualenvs/raven/bin/activate
make
That's it :)
Running the Test Suite
----------------------
The test suite is also powered off of py.test, and can be run in a number of ways. Usually though,
you'll just want to use our helper method to make things easy:
::
make test
Contributing Back Code
----------------------
Ideally all patches should be sent as a pull request on GitHub, and include tests. If you're fixing a bug or making a large change the patch **must** include test coverage.
You can see a list of open pull requests (pending changes) by visiting https://github.com/getsentry/raven-python/pulls

View File

@ -1,131 +0,0 @@
.. sentry:edition:: self
Raven Python
============
.. sentry:edition:: hosted, on-premise
.. class:: platform-python
Python
======
For pairing Sentry up with Python you can use the Raven for Python
(raven-python) library. It is the official standalone Python client for
Sentry. It can be used with any modern Python interpreter be it CPython
2.x or 3.x, PyPy or Jython. It's an Open Source project and available
under a very liberal BSD license.
Installation
------------
If you haven't already, start by downloading Raven. The easiest way is
with *pip*::
pip install raven --upgrade
Configuring the Client
----------------------
Settings are specified as part of the initialization of the client. The
client is a class that can be instanciated with a specific configuration
and all reporting can then happen from the instance of that object.
Typically an instance is created somewhere globally and then imported as
necessary. For getting started all you need is your DSN:
.. sourcecode:: python
from raven import Client
client = Client('___DSN___')
Capture an Error
----------------
The most basic use for raven is to record one specific error that occurs::
from raven import Client
client = Client('___DSN___')
try:
1 / 0
except ZeroDivisionError:
client.captureException()
Adding Context
--------------
Much of the usefulness of Sentry comes from additional context data with
the events. The Python client makes this very convenient by providing
methods to set thread local context data that is then submitted
automatically with all events. For instance you can use
:py:meth:`~raven.Client.user_context` to set the information about the
current user:
.. sourcecode:: python
def handle_request(request):
client.user_context({
'email': request.user.email
})
Deep Dive
---------
Raven Python is more than that however. To dive deeper into what it does,
how it works and how it integrates into other systems there is more to
discover:
.. toctree::
:maxdepth: 2
:titlesonly:
usage
advanced
integrations/index
transports
platform-support
api
.. sentry:edition:: self
For Developers
--------------
.. toctree::
:maxdepth: 2
:titlesonly:
contributing
Supported Platforms
-------------------
- Python 2.6
- Python 2.7
- Python 3.2
- Python 3.3
- Python 3.4
- Python 3.5
- PyPy
- Google App Engine
Deprecation Notes
-----------------
Milestones releases are 1.3 or 1.4, and our deprecation policy is to a two
version step. For example, a feature will be deprecated in 1.3, and
completely removed in 1.4.
Resources
---------
.. sentry:edition:: hosted, on-premise
Resources:
* `Documentation <https://docs.getsentry.com/hosted/clients/python/>`_
* `Bug Tracker <http://github.com/getsentry/raven-python/issues>`_
* `Code <http://github.com/getsentry/raven-python>`_
* `Mailing List <https://groups.google.com/group/getsentry>`_
* `IRC <irc://irc.freenode.net/sentry>`_ (irc.freenode.net, #sentry)

View File

@ -1,46 +0,0 @@
Bottle
======
`Bottle <http://bottlepy.org/>`_ is a microframework for Python. Raven
supports this framework through the WSGI integration.
Setup
-----
The first thing you'll need to do is to disable catchall in your Bottle app::
import bottle
app = bottle.app()
app.catchall = False
.. note:: Bottle will not propagate exceptions to the underlying WSGI
middleware by default. Setting catchall to False disables that.
Sentry will then act as Middleware::
from raven import Client
from raven.contrib.bottle import Sentry
client = Client('___DSN___')
app = Sentry(app, client)
Usage
-----
Once you've configured the Sentry application you need only call run with it::
run(app=app)
If you want to send additional events, a couple of shortcuts are provided
on the Bottle request app object.
Capture an arbitrary exception by calling ``captureException``::
try:
1 / 0
except ZeroDivisionError:
request.app.sentry.captureException()
Log a generic message with ``captureMessage``::
request.app.sentry.captureMessage('Hello, world!')

View File

@ -1,47 +0,0 @@
Celery
======
`Celery <http://www.celeryproject.org/>`_ is a distributed task queue
system for Python built on AMQP principles. For Celery built-in support
by Raven is provided but it requires some manual configuration.
To capture errors, you need to register a couple of signals to hijack
Celery error handling::
from raven import Client
from raven.contrib.celery import register_signal, register_logger_signal
client = Client('___DSN___')
# register a custom filter to filter out duplicate logs
register_logger_signal(client)
# hook into the Celery error handler
register_signal(client)
# The register_logger_signal function can also take an optional argument
# `loglevel` which is the level used for the handler created.
# Defaults to `logging.ERROR`
register_logger_signal(client, loglevel=logging.INFO)
A more complex version to encapsulate behavior:
.. code-block:: python
import celery
import raven
from raven.contrib.celery import register_signal, register_logger_signal
class Celery(celery.Celery):
def on_configure(self):
client = raven.Client('___DSN___')
# register a custom filter to filter out duplicate logs
register_logger_signal(client)
# hook into the Celery error handler
register_signal(client)
app = Celery(__name__)
app.config_from_object('django.conf:settings')

View File

@ -1,337 +0,0 @@
Django
======
.. default-domain:: py
`Django <http://djangoproject.com/>`_ is arguably Python's most popular web
framework. Support is built into Raven but needs some configuration. While
older versions of Django will likely work, officially only version 1.4 and
newer are supported.
Setup
-----
Using the Django integration is as simple as adding
:mod:`raven.contrib.django.raven_compat` to your installed apps::
INSTALLED_APPS = (
'raven.contrib.django.raven_compat',
)
.. note:: This causes Raven to install a hook in Django that will
automatically report uncaught exceptions.
Additional settings for the client are configured using the
``RAVEN_CONFIG`` dictionary::
import raven
RAVEN_CONFIG = {
'dsn': '___DSN___',
# If you are using git, you can also automatically configure the
# release based on the git info.
'release': raven.fetch_git_sha(os.path.dirname(__file__)),
}
Once you've configured the client, you can test it using the standard Django
management interface::
python manage.py raven test
You'll be referencing the client slightly differently in Django as well::
from raven.contrib.django.raven_compat.models import client
client.captureException()
Using with Raven.js
-------------------
A Django template tag is provided to render a proper public DSN inside
your templates, you must first load ``raven``:
.. sourcecode:: django
{% load raven %}
Inside your template, you can now use:
.. sourcecode:: html+django
<script>Raven.config('{% sentry_public_dsn %}').install()</script>
By default, the DSN is generated in a protocol relative fashion, e.g.
``//public@example.com/1``. If you need a specific protocol, you can
override:
.. sourcecode:: html+django
{% sentry_public_dsn 'https' %}
.. sentry:edition:: hosted, on-premise
See the :doc:`Raven.js documentation <../../../clients/javascript/index>`
for more information.
Integration with :mod:`logging`
-------------------------------
To integrate with the standard library's :mod:`logging` module the
following config can be used::
LOGGING = {
'version': 1,
'disable_existing_loggers': True,
'root': {
'level': 'WARNING',
'handlers': ['sentry'],
},
'formatters': {
'verbose': {
'format': '%(levelname)s %(asctime)s %(module)s '
'%(process)d %(thread)d %(message)s'
},
},
'handlers': {
'sentry': {
'level': 'ERROR',
'class': 'raven.contrib.django.raven_compat.handlers.SentryHandler',
'tags': {'custom-tag': 'x'},
},
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
'formatter': 'verbose'
}
},
'loggers': {
'django.db.backends': {
'level': 'ERROR',
'handlers': ['console'],
'propagate': False,
},
'raven': {
'level': 'DEBUG',
'handlers': ['console'],
'propagate': False,
},
'sentry.errors': {
'level': 'DEBUG',
'handlers': ['console'],
'propagate': False,
},
},
}
Usage
~~~~~
Logging usage works the same way as it does outside of Django, with the
addition of an optional ``request`` key in the extra data::
logger.error('There was some crazy error', exc_info=True, extra={
# Optionally pass a request and we'll grab any information we can
'request': request,
})
404 Logging
-----------
In certain conditions you may wish to log 404 events to the Sentry server. To
do this, you simply need to enable a Django middleware:
.. sourcecode:: python
MIDDLEWARE_CLASSES = (
'raven.contrib.django.raven_compat.middleware.Sentry404CatchMiddleware',
...,
) + MIDDLEWARE_CLASSES
It is recommended to put the middleware at the top, so that any only 404s
that bubbled all the way up get logged. Certain middlewares (e.g. flatpages)
capture 404s and replace the response.
It is also possible to configure this middleware to ignore 404s on particular
pages by defining the ``IGNORABLE_404_URLS`` setting as an iterable of regular
expression patterns. If any pattern produces a match against the full requested
URL (as defined by the regular expression's ``search`` method), then the 404
will not be reported to Sentry.
.. sourcecode:: python
import re
IGNORABLE_404_URLS = (
re.compile('/foo'),
)
Message References
------------------
Sentry supports sending a message ID to your clients so that they can be
tracked easily by your development team. There are two ways to access this
information, the first is via the ``X-Sentry-ID`` HTTP response header.
Adding this is as simple as appending a middleware to your stack::
MIDDLEWARE_CLASSES = MIDDLEWARE_CLASSES + (
# We recommend putting this as high in the chain as possible
'raven.contrib.django.raven_compat.middleware.SentryResponseErrorIdMiddleware',
...,
)
Another alternative method is rendering it within a template. By default,
Sentry will attach :attr:`request.sentry` when it catches a Django
exception. In our example, we will use this information to modify the
default :file:`500.html` which is rendered, and show the user a case
reference ID. The first step in doing this is creating a custom
:func:`handler500` in your :file:`urls.py` file::
from django.conf.urls.defaults import *
from django.views.defaults import page_not_found, server_error
from django.template import Context, loader
from django.http import HttpResponseServerError
def handler500(request):
"""500 error handler which includes ``request`` in the context.
Templates: `500.html`
Context: None
"""
t = loader.get_template('500.html') # You need to create a 500.html template.
return HttpResponseServerError(t.render(Context({
'request': request,
})))
Once we've successfully added the :data:`request` context variable, adding the
Sentry reference ID to our :file:`500.html` is simple:
.. sourcecode:: html+django
<p>You've encountered an error, oh noes!</p>
{% if request.sentry.id %}
<p>If you need assistance, you may reference this error as
<strong>{{ request.sentry.id }}</strong>.</p>
{% endif %}
WSGI Middleware
---------------
If you are using a WSGI interface to serve your app, you can also apply a
middleware which will ensure that you catch errors even at the fundamental
level of your Django application::
from raven.contrib.django.raven_compat.middleware.wsgi import Sentry
from django.core.wsgi import get_wsgi_application
application = Sentry(get_wsgi_application())
Additional Settings
-------------------
.. describe:: SENTRY_CLIENT
In some situations you may wish for a slightly different behavior to
how Sentry communicates with your server. For this, Raven allows you
to specify a custom client::
SENTRY_CLIENT = 'raven.contrib.django.raven_compat.DjangoClient'
.. describe:: SENTRY_CELERY_LOGLEVEL
If you are also using Celery, there is a handler being automatically
registered for you that captures the errors from workers. The default
logging level for that handler is ``logging.ERROR`` and can be
customized using this setting::
SENTRY_CELERY_LOGLEVEL = logging.INFO
Alternatively you can use a similarly named key in ``RAVEN_CONFIG``::
RAVEN_CONFIG = {
'CELERY_LOGLEVEL': logging.INFO
}
Caveats
-------
The following things you should keep in mind when using Raven with Django.
Error Handling Middleware
~~~~~~~~~~~~~~~~~~~~~~~~~
If you already have middleware in place that handles :func:`process_exception`
you will need to take extra care when using Sentry.
For example, the following middleware would suppress Sentry logging due to it
returning a response::
class MyMiddleware(object):
def process_exception(self, request, exception):
return HttpResponse('foo')
To work around this, you can either disable your error handling middleware, or
add something like the following::
from django.core.signals import got_request_exception
class MyMiddleware(object):
def process_exception(self, request, exception):
# Make sure the exception signal is fired for Sentry
got_request_exception.send(sender=self, request=request)
return HttpResponse('foo')
Note that this technique may break unit tests using the Django test client
(:class:`django.test.client.Client`) if a view under test generates a
:exc:`Http404 <django.http.Http404>` or :exc:`PermissionDenied` exception,
because the exceptions won't be translated into the expected 404 or 403
response codes.
Or, alternatively, you can just enable Sentry responses::
from raven.contrib.django.raven_compat.models import sentry_exception_handler
class MyMiddleware(object):
def process_exception(self, request, exception):
# Make sure the exception signal is fired for Sentry
sentry_exception_handler(request=request)
return HttpResponse('foo')
Circus
~~~~~~
If you are running Django with `circus <http://circus.rtfd.org/>`_ and
`chaussette <http://chaussette.readthedocs.org/>`_ you will also need
to add a hook to circus to activate Raven::
from django.conf import settings
from django.core.management import call_command
def run_raven(*args, **kwargs):
"""Set up raven for django by running a django command.
It is necessary because chaussette doesn't run a django command.
"""
if not settings.configured:
settings.configure()
call_command('validate')
return True
And in your circus configuration:
.. sourcecode:: ini
[socket:dwebapp]
host = 127.0.0.1
port = 8080
[watcher:dwebworker]
cmd = chaussette --fd $(circus.sockets.dwebapp) dproject.wsgi.application
use_sockets = True
numprocesses = 2
hooks.after_start = dproject.hooks.run_raven

View File

@ -1,143 +0,0 @@
Flask
=====
`Flask <http://flask.pocoo.org/>`_ is a popular Python micro webframework.
Support for Flask is provided by Raven directly but for some dependencies
you need to install raven with the flask feature set.
Installation
------------
If you haven't already, install raven with its explicit Flask dependencies::
pip install raven[flask]
Setup
-----
The first thing you'll need to do is to initialize Raven under your application::
from raven.contrib.flask import Sentry
sentry = Sentry(app, dsn='___DSN___')
If you don't specify the ``dsn`` value, we will attempt to read it from
your environment under the ``SENTRY_DSN`` key.
Extended Setup
--------------
You can optionally configure logging too::
import logging
from raven.contrib.flask import Sentry
sentry = Sentry(app, logging=True, level=logging.ERROR, \
logging_exclusions=("logger1", "logger2", ...))
Building applications on the fly? You can use Raven's ``init_app`` hook::
sentry = Sentry(dsn='http://public_key:secret_key@example.com/1')
def create_app():
app = Flask(__name__)
sentry.init_app(app)
return app
You can pass parameters in the ``init_app`` hook::
sentry = Sentry()
def create_app():
app = Flask(__name__)
sentry.init_app(app, dsn='___DSN___', logging=True,
level=logging.ERROR,
logging_exclusions=("logger1", "logger2", ...))
return app
Settings
--------
Additional settings for the client can be configured using
``SENTRY_<setting name>`` in your application's configuration::
class MyConfig(object):
SENTRY_DSN = '___DSN___'
SENTRY_INCLUDE_PATHS = ['myproject']
If `Flask-Login <https://pypi.python.org/pypi/Flask-Login/>`_ is used by
your application (including `Flask-Security
<https://pypi.python.org/pypi/Flask-Security/>`_), user information will
be captured when an exception or message is captured. By default, only
the ``id`` (current_user.get_id()), ``is_authenticated``, and
``is_anonymous`` is captured for the user. If you would like additional
attributes on the ``current_user`` to be captured, you can configure them
using ``SENTRY_USER_ATTRS``::
class MyConfig(object):
SENTRY_USER_ATTRS = ['username', 'first_name', 'last_name', 'email']
``email`` will be captured as ``sentry.interfaces.User.email``, and any
additionl attributes will be available under
``sentry.interfaces.User.data``
You can specify the types of exceptions that should not be reported by
Sentry client in your application by setting the
``RAVEN_IGNORE_EXCEPTIONS`` configuration value on your Flask app
configuration::
class MyExceptionType(Exception):
def __init__(self, message):
super(MyExceptionType, self).__init__(message)
app = Flask(__name__)
app.config["RAVEN_IGNORE_EXCEPTIONS"] = [MyExceptionType]
Usage
-----
Once you've configured the Sentry application it will automatically
capture uncaught exceptions within Flask. If you want to send additional
events, a couple of shortcuts are provided on the Sentry Flask middleware
object.
Capture an arbitrary exception by calling ``captureException``::
try:
1 / 0
except ZeroDivisionError:
sentry.captureException()
Log a generic message with ``captureMessage``::
sentry.captureMessage('hello, world!')
Getting The Last Event ID
-------------------------
If possible, the last Sentry event ID is stored in the request context
``g.sentry_event_id`` variable. This allow to present the user an error
ID if have done a custom error 500 page.
.. code-block:: html+jinja
<h2>Error 500</h2>
{% if g.sentry_event_id %}
<p>The error identifier is {{ g.sentry_event_id }}</p>
{% endif %}
Dealing With Proxies
--------------------
When your Flask application is behind a proxy such as nginx, Sentry will
use the remote address from the proxy, rather than from the actual
requesting computer. By using ``ProxyFix`` from `werkzeug.contrib.fixers
<http://werkzeug.pocoo.org/docs/0.10/contrib/fixers/#werkzeug.contrib.fixers.ProxyFix>`_
the Flask ``.wsgi_app`` can be modified to send the actual ``REMOTE_ADDR``
along to Sentry. ::
from werkzeug.contrib.fixers import ProxyFix
app.wsgi_app = ProxyFix(app.wsgi_app)
This may also require `changes
<http://flask.pocoo.org/docs/0.10/deploying/wsgi-standalone/#proxy-setups>`_
to the proxy configuration to pass the right headers if it isn't doing so
already.

View File

@ -1,28 +0,0 @@
Integrations
============
The Raven Python module also comes with integration for some commonly used
libraries to automatically capture errors from common environments. This
means that once you have such an integration configured you typically do
not need to report errors manually.
Some integrations allow specifying these in a standard configuration,
otherwise they are generally passed upon instantiation of the Sentry
client.
.. toctree::
:maxdepth: 1
bottle
celery
django
flask
logbook
logging
pylons
pyramid
rq
tornado
wsgi
zerorpc
zope

View File

@ -1,28 +0,0 @@
Logbook
=======
Raven provides a `logbook <http://logbook.pocoo.org>`_ handler which will pipe
messages to Sentry.
First you'll need to configure a handler::
from raven.handlers.logbook import SentryHandler
# Manually specify a client
client = Client(...)
handler = SentryHandler(client)
You can also automatically configure the default client with a DSN::
# Configure the default client
handler = SentryHandler('___DSN___')
Finally, bind your handler to your context::
from raven.handlers.logbook import SentryHandler
client = Client(...)
sentry_handler = SentryHandler(client)
with sentry_handler.applicationbound():
# everything logged here will go to sentry.
...

View File

@ -1,144 +0,0 @@
Logging
=======
.. default-domain:: py
Sentry supports the ability to directly tie into the :mod:`logging`
module. To use it simply add :class:`SentryHandler` to your logger.
First you'll need to configure a handler::
from raven.handlers.logging import SentryHandler
# Manually specify a client
client = Client(...)
handler = SentryHandler(client)
You can also automatically configure the default client with a DSN::
# Configure the default client
handler = SentryHandler('___DSN___')
Finally, call the :func:`setup_logging` helper function::
from raven.conf import setup_logging
setup_logging(handler)
Another option is to use :mod:`logging.config.dictConfig`::
LOGGING = {
'version': 1,
'disable_existing_loggers': True,
'formatters': {
'console': {
'format': '[%(asctime)s][%(levelname)s] %(name)s '
'%(filename)s:%(funcName)s:%(lineno)d | %(message)s',
'datefmt': '%H:%M:%S',
},
},
'handlers': {
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
'formatter': 'console'
},
'sentry': {
'level': 'ERROR',
'class': 'raven.handlers.logging.SentryHandler',
'dsn': '___DSN___',
},
},
'loggers': {
'': {
'handlers': ['console', 'sentry'],
'level': 'DEBUG',
'propagate': False,
},
'your_app': {
'level': 'DEBUG',
'propagate': True,
},
}
}
Usage
~~~~~
A recommended pattern in logging is to simply reference the modules name for
each logger, so for example, you might at the top of your module define the
following::
import logging
logger = logging.getLogger(__name__)
You can also use the ``exc_info`` and ``extra={'stack': True}`` arguments on
your ``log`` methods. This will store the appropriate information and allow
Sentry to render it based on that information::
# If you're actually catching an exception, use `exc_info=True`
logger.error('There was an error, with a stacktrace!', exc_info=True)
# If you don't have an exception, but still want to capture a
# stacktrace, use the `stack` arg
logger.error('There was an error, with a stacktrace!', extra={
'stack': True,
})
.. note:: Depending on the version of Python you're using, ``extra`` might
not be an acceptable keyword argument for a logger's ``.exception()``
method (``.debug()``, ``.info()``, ``.warning()``, ``.error()`` and
``.critical()`` should work fine regardless of Python version). This
should be fixed as of Python 3.2. Official issue here:
http://bugs.python.org/issue15541.
While we don't recommend this, you can also enable implicit stack
capturing for all messages::
client = Client(..., auto_log_stacks=True)
handler = SentryHandler(client)
logger.error('There was an error, with a stacktrace!')
You may also pass additional information to be stored as meta information with
the event. As long as the key name is not reserved and not private (_foo) it
will be displayed on the Sentry dashboard. To do this, pass it as ``data``
within your ``extra`` clause::
logger.error('There was some crazy error', exc_info=True, extra={
# Optionally you can pass additional arguments to specify request info
'culprit': 'my.view.name',
'fingerprint': [...],
'data': {
# You may specify any values here and Sentry will log and output them
'username': request.user.username,
}
})
.. note:: The ``url`` and ``view`` keys are used internally by Sentry
within the extra data.
.. note:: Any key (in ``data``) prefixed with ``_`` will not automatically
output on the Sentry details view.
Sentry will intelligently group messages if you use proper string
formatting. For example, the following messages would be seen as the same
message within Sentry::
logger.error('There was some %s error', 'crazy')
logger.error('There was some %s error', 'fun')
logger.error('There was some %s error', 1)
Exclusions
~~~~~~~~~~
You can also configure some logging exclusions during setup. These loggers
will not propagate their logs to the Sentry handler.
from raven.conf import setup_logging
setup_logging(handler, exclude=("logger1", "logger2", ...))

View File

@ -1,69 +0,0 @@
Pylons
======
Pylons is a framework for Python.
WSGI Middleware
---------------
A Pylons-specific middleware exists to enable easy configuration from settings:
::
from raven.contrib.pylons import Sentry
application = Sentry(application, config)
Configuration is handled via the sentry namespace:
.. code-block:: ini
[sentry]
dsn=___DSN___
include_paths=my.package,my.other.package,
exclude_paths=my.package.crud
Logger setup
------------
Add the following lines to your project's `.ini` file to setup `SentryHandler`:
.. code-block:: ini
[loggers]
keys = root, sentry
[handlers]
keys = console, sentry
[formatters]
keys = generic
[logger_root]
level = INFO
handlers = console, sentry
[logger_sentry]
level = WARN
handlers = console
qualname = sentry.errors
propagate = 0
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[handler_sentry]
class = raven.handlers.logging.SentryHandler
args = ('SENTRY_DSN',)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(asctime)s,%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S
.. note:: You may want to setup other loggers as well.

View File

@ -1,71 +0,0 @@
Pyramid
=======
PasteDeploy Filter
------------------
A filter factory for `PasteDeploy <http://pythonpaste.org/deploy/>`_ exists to allow easily inserting Raven into a WSGI pipeline:
.. code-block:: ini
[pipeline:main]
pipeline =
raven
tm
MyApp
[filter:raven]
use = egg:raven#raven
dsn = ___DSN___
include_paths = my.package, my.other.package
exclude_paths = my.package.crud
In the ``[filter:raven]`` section, you must specify the entry-point for raven with the ``use =`` key. All other raven client parameters can be included in this section as well.
See the `Pyramid PasteDeploy Configuration Documentation <http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/paste.html>`_ for more information.
Logger setup
------------
Add the following lines to your project's `.ini` file to setup `SentryHandler`:
.. code-block:: ini
[loggers]
keys = root, sentry
[handlers]
keys = console, sentry
[formatters]
keys = generic
[logger_root]
level = INFO
handlers = console, sentry
[logger_sentry]
level = WARN
handlers = console
qualname = sentry.errors
propagate = 0
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[handler_sentry]
class = raven.handlers.logging.SentryHandler
args = ('___DSN___',)
level = WARNING
formatter = generic
[formatter_generic]
format = %(asctime)s,%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S
.. note:: You may want to setup other loggers as well. See the `Pyramid Logging Documentation <http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html>`_ for more information.
Instead of defining the DSN in the `.ini` file you can also use the environment variable ``SENTRY_DSN`` which overwrites the setting in this file. Because of a syntax check you cannot remove the ``args`` setting completely, as workaround you can define an empty list of arguments ``args = ()``.

View File

@ -1,30 +0,0 @@
RQ
==
Starting with RQ version 0.3.1, support for Sentry has been built in.
Usage
-----
RQ natively supports binding with Sentry by passing your ``SENTRY_DSN`` through ``rqworker``::
$ rqworker --sentry-dsn="___DSN___"
Extended Setup
--------------
If you want to pass additional information, such as ``release``, you'll need to bind your
own instance of the Sentry ``Client``:
.. code-block:: python
from raven import Client
from raven.transport.http import HTTPTransport
from rq.contrib.sentry import register_sentry
client = Client('___DSN___', transport=HTTPTransport)
register_sentry(client, worker)
Please see ``rq``'s documentation for more information:
http://python-rq.org/patterns/sentry/

View File

@ -1,98 +0,0 @@
Tornado
=======
Tornado is an async web framework for Python.
Setup
-----
The first thing you'll need to do is to initialize sentry client under
your application
.. code-block:: python
import tornado.web
from raven.contrib.tornado import AsyncSentryClient
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("Hello, world")
application = tornado.web.Application([
(r"/", MainHandler),
])
application.sentry_client = AsyncSentryClient(
'___DSN___'
)
Usage
-----
Once the sentry client is attached to the application, request handlers
can automatically capture uncaught exceptions by inheriting the `SentryMixin` class.
.. code-block:: python
import tornado.web
from raven.contrib.tornado import SentryMixin
class UncaughtExceptionExampleHandler(
SentryMixin, tornado.web.RequestHandler):
def get(self):
1/0
You can also send events manually using the shortcuts defined in `SentryMixin`.
The shortcuts can be used for both asynchronous and synchronous usage.
Asynchronous
~~~~~~~~~~~~
.. code-block:: python
import tornado.web
import tornado.gen
from raven.contrib.tornado import SentryMixin
class AsyncMessageHandler(SentryMixin, tornado.web.RequestHandler):
@tornado.web.asynchronous
@tornado.gen.engine
def get(self):
self.write("You requested the main page")
yield tornado.gen.Task(
self.captureMessage, "Request for main page served"
)
self.finish()
class AsyncExceptionHandler(SentryMixin, tornado.web.RequestHandler):
@tornado.web.asynchronous
@tornado.gen.engine
def get(self):
try:
raise ValueError()
except Exception as e:
response = yield tornado.gen.Task(
self.captureException, exc_info=True
)
self.finish()
.. tip::
The value returned by the yield is a ``HTTPResponse`` object.
Synchronous
~~~~~~~~~~~
.. code-block:: python
import tornado.web
from raven.contrib.tornado import SentryMixin
class AsyncExampleHandler(SentryMixin, tornado.web.RequestHandler):
def get(self):
self.write("You requested the main page")
self.captureMessage("Request for main page served")

View File

@ -1,16 +0,0 @@
WSGI Middleware
===============
Raven includes a simple to use WSGI middleware.
::
from raven import Client
from raven.middleware import Sentry
application = Sentry(
application,
Client('___DSN___')
)
.. note:: Many frameworks will not propagate exceptions to the underlying WSGI middleware by default.

View File

@ -1,31 +0,0 @@
ZeroRPC
=======
ZeroRPC is a light-weight, reliable and language-agnostic library for
distributed communication between server-side processes.
Setup
-----
The ZeroRPC integration comes as middleware for ZeroRPC. The middleware can be
configured like the original Raven client (using keyword arguments) and
registered into ZeroRPC's context manager::
import zerorpc
from raven.contrib.zerorpc import SentryMiddleware
sentry = SentryMiddleware(dsn='___DSN___')
zerorpc.Context.get_instance().register_middleware(sentry)
By default, the middleware will hide internal frames from ZeroRPC when it
submits exceptions to Sentry. This behavior can be disabled by passing the
``hide_zerorpc_frames`` parameter to the middleware::
sentry = SentryMiddleware(hide_zerorpc_frames=False, dsn='___DSN___')
Compatibility
-------------
- ZeroRPC-Python < 0.4.0 is compatible with Raven <= 3.1.0;
- ZeroRPC-Python >= 0.4.0 requires Raven > 3.1.0.

View File

@ -1,48 +0,0 @@
Zope/Plone
==========
zope.conf
---------
Zope has extensible logging configuration options.
A basic setup for logging looks like that:
.. code-block:: xml
<eventlog>
level INFO
<logfile>
path ${buildout:directory}/var/{:_buildout_section_name_}.log
level INFO
</logfile>
%import raven.contrib.zope
<sentry>
dsn ___DSN___
level ERROR
</sentry>
</eventlog>
This configuration keeps the regular logging to a logfile, but adds
logging to sentry for ERRORs.
All options of :py:class:`raven.base.Client` are supported.
Nobody writes zope.conf files these days, instead buildout recipe does
that. To add the equivalent configuration, you would do this:
.. code-block:: ini
[instance]
recipe = plone.recipe.zope2instance
...
event-log-custom =
%import raven.contrib.zope
<logfile>
path ${buildout:directory}/var/instance.log
level INFO
</logfile>
<sentry>
dsn ___DSN___
level ERROR
</sentry>

View File

@ -1,155 +0,0 @@
@ECHO OFF
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set BUILDDIR=_build
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
if NOT "%PAPER%" == "" (
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
)
if "%1" == "" goto help
if "%1" == "help" (
:help
echo.Please use `make ^<target^>` where ^<target^> is one of
echo. html to make standalone HTML files
echo. dirhtml to make HTML files named index.html in directories
echo. singlehtml to make a single large HTML file
echo. pickle to make pickle files
echo. json to make JSON files
echo. htmlhelp to make HTML files and a HTML help project
echo. qthelp to make HTML files and a qthelp project
echo. devhelp to make HTML files and a Devhelp project
echo. epub to make an epub
echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
echo. text to make text files
echo. man to make manual pages
echo. changes to make an overview over all changed/added/deprecated items
echo. linkcheck to check all external links for integrity
echo. doctest to run all doctests embedded in the documentation if enabled
goto end
)
if "%1" == "clean" (
for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
del /q /s %BUILDDIR%\*
goto end
)
if "%1" == "html" (
%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/html.
goto end
)
if "%1" == "dirhtml" (
%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
goto end
)
if "%1" == "singlehtml" (
%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
goto end
)
if "%1" == "pickle" (
%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
echo.
echo.Build finished; now you can process the pickle files.
goto end
)
if "%1" == "json" (
%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
echo.
echo.Build finished; now you can process the JSON files.
goto end
)
if "%1" == "htmlhelp" (
%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
echo.
echo.Build finished; now you can run HTML Help Workshop with the ^
.hhp project file in %BUILDDIR%/htmlhelp.
goto end
)
if "%1" == "qthelp" (
%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
echo.
echo.Build finished; now you can run "qcollectiongenerator" with the ^
.qhcp project file in %BUILDDIR%/qthelp, like this:
echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Sentry.qhcp
echo.To view the help file:
echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Sentry.ghc
goto end
)
if "%1" == "devhelp" (
%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
echo.
echo.Build finished.
goto end
)
if "%1" == "epub" (
%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
echo.
echo.Build finished. The epub file is in %BUILDDIR%/epub.
goto end
)
if "%1" == "latex" (
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
echo.
echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
goto end
)
if "%1" == "text" (
%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
echo.
echo.Build finished. The text files are in %BUILDDIR%/text.
goto end
)
if "%1" == "man" (
%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
echo.
echo.Build finished. The manual pages are in %BUILDDIR%/man.
goto end
)
if "%1" == "changes" (
%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
echo.
echo.The overview file is in %BUILDDIR%/changes.
goto end
)
if "%1" == "linkcheck" (
%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
echo.
echo.Link check complete; look for any errors in the above output ^
or in %BUILDDIR%/linkcheck/output.txt.
goto end
)
if "%1" == "doctest" (
%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
echo.
echo.Testing of doctests in the sources finished, look at the ^
results in %BUILDDIR%/doctest/output.txt.
goto end
)
:end

View File

@ -1,9 +0,0 @@
Supported Platforms
===================
- Python 2.6
- Python 2.7
- Python 3.2
- Python 3.3
- PyPy
- Google App Engine

View File

@ -1,93 +0,0 @@
{
"support_level": "production",
"platforms": {
"python": {
"name": "Python",
"type": "language",
"doc_link": "",
"wizard": [
"index#installation",
"usage#capture-an-error",
"usage#reporting-an-event"
]
},
"python.flask": {
"name": "Flask",
"type": "framework",
"doc_link": "integrations/flask/",
"wizard": [
"index#installation",
"integrations/flask#installation",
"integrations/flask#setup"
]
},
"python.bottle": {
"name": "Bottle",
"type": "framework",
"doc_link": "integrations/bottle/",
"wizard": [
"index#installation",
"integrations/bottle#setup",
"integrations/bottle#usage"
]
},
"python.celery": {
"name": "Celery",
"type": "library",
"doc_link": "integrations/celery/",
"wizard": [
"index#installation",
"integrations/celery"
]
},
"python.django": {
"name": "Django",
"type": "framework",
"doc_link": "integrations/django/",
"wizard": [
"index#installation",
"integrations/django#setup"
]
},
"python.pylons": {
"name": "Pylons",
"type": "framework",
"doc_link": "integrations/pylons/",
"wizard": [
"index#installation",
"integrations/pylons#wsgi-middleware",
"integrations/pylons#logger-setup"
]
},
"python.pyramid": {
"name": "Pyramid",
"type": "framework",
"doc_link": "integrations/pyramid/",
"wizard": [
"index#installation",
"integrations/pyramid#pastedeploy-filter",
"integrations/pyramid#logger-setup"
]
},
"python.rq": {
"name": "RQ",
"type": "framework",
"doc_link": "integrations/rq/",
"wizard": [
"index#installation",
"integrations/rq#usage",
"integrations/rq#extended-setup"
]
},
"python.tornado": {
"name": "Tornado",
"type": "framework",
"doc_link": "integrations/tornado/",
"wizard": [
"index#installation",
"integrations/tornado#setup",
"integrations/tornado#usage"
]
}
}
}

View File

@ -1,124 +0,0 @@
Transports
==========
A transport is the mechanism in which Raven sends the HTTP request to the
Sentry server. By default, Raven uses a threaded asynchronous transport,
but you can easily adjust this by passing your own transport class.
The transport class is passed via the ``transport`` parameter on ``Client``:
.. code-block:: python
from raven import Client
Client('...', transport=TransportClass)
Options are passed to transports via the querystring.
All transports should support at least the following options:
``timeout = 1``
The time to wait for a response from the server, in seconds.
``verify_ssl = 1``
If the connection is HTTPS, validate the certificate and hostname.
``ca_certs = [raven]/data/cacert.pem``
A certificate bundle to use when validating SSL connections.
For example, to increase the timeout and to disable SSL verification::
SENTRY_DSN = '___DSN___?timeout=5&verify_ssl=0'
Eventlet
--------
Should only be used within an Eventlet IO loop.
.. code-block:: python
from raven.transport.eventlet import EventletHTTPTransport
Client('...', transport=EventletHTTPTransport)
Gevent
------
Should only be used within a Gevent IO loop.
.. code-block:: python
from raven.transport.gevent import GeventedHTTPTransport
Client('...', transport=GeventedHTTPTransport)
Requests
--------
Requires the ``requests`` library. Synchronous.
.. code-block:: python
from raven.transport.requests import RequestsHTTPTransport
Client('...', transport=RequestsHTTPTransport)
Alternatively, a threaded client also exists for Requests:
.. code-block:: python
from raven.transport.threaded_requests import ThreadedRequestsHTTPTransport
Client('...', transport=ThreadedRequestsHTTPTransport)
Sync
----
A synchronous blocking transport.
.. code-block:: python
from raven.transport.http import HTTPTransport
Client('...', transport=HTTPTransport)
Threaded (Default)
------------------
Spawns an async worker for processing messages.
.. code-block:: python
from raven.transport.threaded import ThreadedHTTPTransport
Client('...', transport=ThreadedHTTPTransport)
Tornado
-------
Should only be used within a Tornado IO loop.
.. code-block:: python
from raven.transport.tornado import TornadoHTTPTransport
Client('...', transport=TornadoHTTPTransport)
Twisted
-------
Should only be used within a Twisted event loop.
.. code-block:: python
from raven.transport.twisted import TwistedHTTPTransport
Client('...', transport=TwistedHTTPTransport)

View File

@ -1,79 +0,0 @@
Basic Usage
===========
This gives a basic overview of how to use the raven client with Python
directly.
Capture an Error
----------------
The most basic use for raven is to record one specific error that occurs::
from raven import Client
client = Client('___DSN___')
try:
1 / 0
except ZeroDivisionError:
client.captureException()
Reporting an Event
------------------
To report an arbitrary event you can use the
:py:meth:`~raven.Client.capture` method. This is the most low-level
method available. In most cases you would want to use the
:py:meth:`~raven.Client.captureMessage` method instead however which
directly reports a message::
client.captureMessage('Something went fundamentally wrong')
Adding Context
--------------
The raven client internally keeps a thread local mapping that can carry
additional information. Whenever a message is submitted to Sentry that
additional data will be passed along.
For instance if you use a web framework, you can use this to inject
additional information into the context. The basic primitive for this is
the :py:attr:`~raven.Client.context` attribute. It provides a `merge()`
and `clear()` function that can be used::
def handle_request(request):
client.context.merge({'user': {
'email': request.user.email
}})
try:
...
finally:
client.context.clear()
Testing the Client
------------------
Once you've got your server configured, you can test the Raven client by
using its CLI::
raven test ___DSN___
If you've configured your environment to have ``SENTRY_DSN`` available, you
can simply drop the optional DSN argument::
raven test
You should get something like the following, assuming you're configured everything correctly::
$ raven test sync+___DSN___
Using DSN configuration:
sync+___DSN___
Client configuration:
servers : ___API_URL___/api/store/
project : ___PROJECT_ID___
public_key : ___PUBLIC_KEY___
secret_key : ___SECRET_KEY___
Sending a test message... success!

View File

@ -1,12 +0,0 @@
#!/bin/bash
failed=0
for filename in hooks/pre-commit.*; do
if [ -f "$filename" ]; then
if ! $filename; then
failed=1
fi
fi
done
exit $failed

View File

@ -1,46 +0,0 @@
#!/usr/bin/env python
import glob
import os
import sys
os.environ['PYFLAKES_NODOCTEST'] = '1'
# pep8.py uses sys.argv to find setup.cfg
sys.argv = [os.path.join(os.path.dirname(__file__), os.pardir, os.pardir)]
# git usurbs your bin path for hooks and will always run system python
if 'VIRTUAL_ENV' in os.environ:
site_packages = glob.glob(
'%s/lib/*/site-packages' % os.environ['VIRTUAL_ENV'])[0]
sys.path.insert(0, site_packages)
def main():
from flake8.main import DEFAULT_CONFIG
from flake8.engine import get_style_guide
from flake8.hooks import run
gitcmd = "git diff-index --cached --name-only HEAD"
_, files_modified, _ = run(gitcmd)
try:
text_type = unicode
except NameError:
text_type = str
files_modified = [text_type(x) for x in files_modified]
# remove non-py files and files which no longer exist
files_modified = filter(
lambda x: x.endswith('.py') and os.path.exists(x),
files_modified)
flake8_style = get_style_guide(parse_argv=True, config_file=DEFAULT_CONFIG)
report = flake8_style.check_files(files_modified)
return report.total_errors
if __name__ == '__main__':
sys.exit(main())

View File

@ -1 +0,0 @@
../docs/_sentryext/verify-docs.py

View File

@ -1,57 +0,0 @@
"""
raven
~~~~~
:copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
from __future__ import absolute_import
import os
import os.path
from raven.base import * # NOQA
from raven.conf import * # NOQA
from raven.versioning import * # NOQA
__all__ = ('VERSION', 'Client', 'get_version')
try:
VERSION = __import__('pkg_resources') \
.get_distribution('raven').version
except Exception as e:
VERSION = 'unknown'
def _get_git_revision(path):
revision_file = os.path.join(path, 'refs', 'heads', 'master')
if not os.path.exists(revision_file):
return None
fh = open(revision_file, 'r')
try:
return fh.read().strip()[:7]
finally:
fh.close()
def get_revision():
"""
:returns: Revision number of this branch/checkout, if available. None if
no revision number can be determined.
"""
package_dir = os.path.dirname(__file__)
checkout_dir = os.path.normpath(os.path.join(package_dir, os.pardir, os.pardir))
path = os.path.join(checkout_dir, '.git')
if os.path.exists(path):
return _get_git_revision(path)
return None
def get_version():
base = VERSION
if __build__:
base = '%s (%s)' % (base, __build__)
return base
__build__ = get_revision()
__docformat__ = 'restructuredtext en'

View File

@ -1,177 +0,0 @@
"""Utilities for writing code that runs on Python 2 and 3"""
# flake8: noqa
# Copyright (c) 2010-2013 Benjamin Peterson
#
# Permission is hereby granted, free of charge, to any person obtaining a copy of
# this software and associated documentation files (the "Software"), to deal in
# the Software without restriction, including without limitation the rights to
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
# the Software, and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
from __future__ import absolute_import
import operator
import sys
import types
__author__ = "Benjamin Peterson <benjamin@python.org>"
__version__ = "1.3.0"
PY2 = sys.version_info[0] == 2
if not PY2:
string_types = str,
integer_types = int,
class_types = type,
text_type = str
binary_type = bytes
MAXSIZE = sys.maxsize
else:
string_types = basestring,
integer_types = (int, long)
class_types = (type, types.ClassType)
text_type = unicode
binary_type = str
if sys.platform.startswith("java"):
# Jython always uses 32 bits.
MAXSIZE = int((1 << 31) - 1)
else:
# It's possible to have sizeof(long) != sizeof(Py_ssize_t).
class X(object):
def __len__(self):
return 1 << 31
try:
len(X())
except OverflowError:
# 32-bit
MAXSIZE = int((1 << 31) - 1)
else:
# 64-bit
MAXSIZE = int((1 << 63) - 1)
del X
def _import_module(name):
"""Import module, returning the module after the last dot."""
__import__(name)
return sys.modules[name]
if not PY2:
_iterkeys = "keys"
_itervalues = "values"
_iteritems = "items"
_iterlists = "lists"
else:
_iterkeys = "iterkeys"
_itervalues = "itervalues"
_iteritems = "iteritems"
_iterlists = "iterlists"
try:
advance_iterator = next
except NameError:
def advance_iterator(it):
return it.next()
next = advance_iterator
try:
callable = callable
except NameError:
def callable(obj):
return any("__call__" in klass.__dict__ for klass in type(obj).__mro__)
def iterkeys(d, **kw):
"""Return an iterator over the keys of a dictionary."""
return iter(getattr(d, _iterkeys)(**kw))
def itervalues(d, **kw):
"""Return an iterator over the values of a dictionary."""
return iter(getattr(d, _itervalues)(**kw))
def iteritems(d, **kw):
"""Return an iterator over the (key, value) pairs of a dictionary."""
return iter(getattr(d, _iteritems)(**kw))
def iterlists(d, **kw):
"""Return an iterator over the (key, [values]) pairs of a dictionary."""
return iter(getattr(d, _iterlists)(**kw))
if not PY2:
def b(s):
return s.encode("latin-1")
def u(s):
return s
if sys.version_info[1] <= 1:
def int2byte(i):
return bytes((i,))
else:
# This is about 2x faster than the implementation above on 3.2+
int2byte = operator.methodcaller("to_bytes", 1, "big")
import io
StringIO = io.StringIO
BytesIO = io.BytesIO
else:
def b(s): # NOQA
return s
def u(s): # NOQA
return unicode(s, "unicode_escape")
int2byte = chr
import StringIO
StringIO = BytesIO = StringIO.StringIO
if not PY2:
import builtins
exec_ = getattr(builtins, "exec")
def reraise(tp, value, tb=None):
if value.__traceback__ is not tb:
raise value.with_traceback(tb)
raise value
del builtins
else:
def exec_(_code_, _globs_=None, _locs_=None):
"""Execute code in a namespace."""
if _globs_ is None:
frame = sys._getframe(1)
_globs_ = frame.f_globals
if _locs_ is None:
_locs_ = frame.f_locals
del frame
elif _locs_ is None:
_locs_ = _globs_
exec("""exec _code_ in _globs_, _locs_""")
exec_("""def reraise(tp, value, tb=None):
raise tp, value, tb
""")
def with_metaclass(meta, base=object):
"""Create a base class with a metaclass."""
return meta("NewBase", (base,), {})

View File

@ -1,789 +0,0 @@
"""
raven.base
~~~~~~~~~~
:copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
from __future__ import absolute_import
import zlib
import logging
import os
import sys
import time
import uuid
import warnings
from datetime import datetime
from pprint import pformat
from types import FunctionType
if sys.version_info >= (3, 2):
import contextlib
else:
import contextlib2 as contextlib
import raven
from raven.conf import defaults
from raven.conf.remote import RemoteConfig
from raven.context import Context
from raven.exceptions import APIError, RateLimited
from raven.utils import json, get_versions, get_auth_header, merge_dicts
from raven._compat import text_type, iteritems
from raven.utils.encoding import to_unicode
from raven.utils.serializer import transform
from raven.utils.stacks import get_stack_info, iter_stack_frames, get_culprit
from raven.transport.registry import TransportRegistry, default_transports
# enforce imports to avoid obscure stacktraces with MemoryError
import raven.events # NOQA
__all__ = ('Client',)
__excepthook__ = None
PLATFORM_NAME = 'python'
# singleton for the client
Raven = None
class ModuleProxyCache(dict):
def __missing__(self, key):
module, class_name = key.rsplit('.', 1)
handler = getattr(__import__(
module, {}, {}, [class_name]), class_name)
self[key] = handler
return handler
class ClientState(object):
ONLINE = 1
ERROR = 0
def __init__(self):
self.status = self.ONLINE
self.last_check = None
self.retry_number = 0
self.retry_after = 0
def should_try(self):
if self.status == self.ONLINE:
return True
interval = self.retry_after or min(self.retry_number, 6) ** 2
if time.time() - self.last_check > interval:
return True
return False
def set_fail(self, retry_after=0):
self.status = self.ERROR
self.retry_number += 1
self.last_check = time.time()
self.retry_after = retry_after
def set_success(self):
self.status = self.ONLINE
self.last_check = None
self.retry_number = 0
self.retry_after = 0
def did_fail(self):
return self.status == self.ERROR
class Client(object):
"""
The base Raven client.
Will read default configuration from the environment variable
``SENTRY_DSN`` if available.
>>> from raven import Client
>>> # Read configuration from ``os.environ['SENTRY_DSN']``
>>> client = Client()
>>> # Specify a DSN explicitly
>>> client = Client(dsn='https://public_key:secret_key@sentry.local/project_id')
>>> # Record an exception
>>> try:
>>> 1/0
>>> except ZeroDivisionError:
>>> ident = client.get_ident(client.captureException())
>>> print "Exception caught; reference is %s" % ident
"""
logger = logging.getLogger('raven')
protocol_version = '6'
_registry = TransportRegistry(transports=default_transports)
def __init__(self, dsn=None, raise_send_errors=False, transport=None,
install_sys_hook=True, **options):
global Raven
o = options
self.raise_send_errors = raise_send_errors
# configure loggers first
cls = self.__class__
self.state = ClientState()
self.logger = logging.getLogger(
'%s.%s' % (cls.__module__, cls.__name__))
self.error_logger = logging.getLogger('sentry.errors')
self.uncaught_logger = logging.getLogger('sentry.errors.uncaught')
self._transport_cache = {}
self.set_dsn(dsn, transport)
self.include_paths = set(o.get('include_paths') or [])
self.exclude_paths = set(o.get('exclude_paths') or [])
self.name = text_type(o.get('name') or o.get('machine') or defaults.NAME)
self.auto_log_stacks = bool(
o.get('auto_log_stacks') or defaults.AUTO_LOG_STACKS)
self.capture_locals = bool(
o.get('capture_locals', defaults.CAPTURE_LOCALS))
self.string_max_length = int(
o.get('string_max_length') or defaults.MAX_LENGTH_STRING)
self.list_max_length = int(
o.get('list_max_length') or defaults.MAX_LENGTH_LIST)
self.site = o.get('site')
self.include_versions = o.get('include_versions', True)
self.processors = o.get('processors')
if self.processors is None:
self.processors = defaults.PROCESSORS
context = o.get('context')
if context is None:
context = {'sys.argv': sys.argv[:]}
self.extra = context
self.tags = o.get('tags') or {}
self.environment = o.get('environment') or None
self.release = o.get('release') or os.environ.get('HEROKU_SLUG_COMMIT')
self.module_cache = ModuleProxyCache()
if not self.is_enabled():
self.logger.info(
'Raven is not configured (logging is disabled). Please see the'
' documentation for more information.')
if Raven is None:
Raven = self
self._context = Context()
if install_sys_hook:
self.install_sys_hook()
def set_dsn(self, dsn=None, transport=None):
if dsn is None and os.environ.get('SENTRY_DSN'):
msg = "Configuring Raven from environment variable 'SENTRY_DSN'"
self.logger.debug(msg)
dsn = os.environ['SENTRY_DSN']
if dsn not in self._transport_cache:
if dsn is None:
result = RemoteConfig(transport=transport)
else:
result = RemoteConfig.from_string(
dsn,
transport=transport,
transport_registry=self._registry,
)
self._transport_cache[dsn] = result
self.remote = result
else:
self.remote = self._transport_cache[dsn]
self.logger.debug("Configuring Raven for host: {0}".format(self.remote))
def install_sys_hook(self):
global __excepthook__
if __excepthook__ is None:
__excepthook__ = sys.excepthook
def handle_exception(*exc_info):
self.captureException(exc_info=exc_info)
__excepthook__(*exc_info)
sys.excepthook = handle_exception
@classmethod
def register_scheme(cls, scheme, transport_class):
cls._registry.register_scheme(scheme, transport_class)
def get_processors(self):
for processor in self.processors:
yield self.module_cache[processor](self)
def get_module_versions(self):
if not self.include_versions:
return {}
version_info = sys.version_info
modules = get_versions(self.include_paths)
modules['python'] = '{0}.{1}.{2}'.format(
version_info[0], version_info[1], version_info[2],
)
return modules
def get_ident(self, result):
"""
Returns a searchable string representing a message.
>>> result = client.capture(**kwargs)
>>> ident = client.get_ident(result)
"""
warnings.warn('Client.get_ident is deprecated. The event ID is now '
'returned as the result of capture.',
DeprecationWarning)
return result
def get_handler(self, name):
return self.module_cache[name](self)
def get_public_dsn(self, scheme=None):
"""
Returns a public DSN which is consumable by raven-js
>>> # Return scheme-less DSN
>>> print client.get_public_dsn()
>>> # Specify a scheme to use (http or https)
>>> print client.get_public_dsn('https')
"""
if not self.is_enabled():
return
url = self.remote.get_public_dsn()
if not scheme:
return url
return '%s:%s' % (scheme, url)
def _get_exception_key(self, exc_info):
return (
exc_info[0],
id(exc_info[1]),
id(exc_info[2] and exc_info[2].tb_frame.f_code),
id(exc_info[2]),
exc_info[2] and exc_info[2].tb_lasti,
)
def skip_error_for_logging(self, exc_info):
key = self._get_exception_key(exc_info)
return key in self.context.exceptions_to_skip
def record_exception_seen(self, exc_info):
key = self._get_exception_key(exc_info)
self.context.exceptions_to_skip.add(key)
def build_msg(self, event_type, data=None, date=None,
time_spent=None, extra=None, stack=None, public_key=None,
tags=None, fingerprint=None, **kwargs):
"""
Captures, processes and serializes an event into a dict object
The result of ``build_msg`` should be a standardized dict, with
all default values available.
"""
# create ID client-side so that it can be passed to application
event_id = uuid.uuid4().hex
data = merge_dicts(self.context.data, data)
data.setdefault('tags', {})
data.setdefault('extra', {})
if '.' not in event_type:
# Assume it's a builtin
event_type = 'raven.events.%s' % event_type
handler = self.get_handler(event_type)
result = handler.capture(**kwargs)
# data (explicit) culprit takes over auto event detection
culprit = result.pop('culprit', None)
if data.get('culprit'):
culprit = data['culprit']
for k, v in iteritems(result):
if k not in data:
data[k] = v
# auto_log_stacks only applies to events that are not exceptions
# due to confusion about which stack is which and the automatic
# application of stacktrace to exception objects by Sentry
if stack is None and 'exception' not in data:
stack = self.auto_log_stacks
if stack and 'stacktrace' not in data:
if stack is True:
frames = iter_stack_frames()
else:
frames = stack
stack_info = get_stack_info(
frames,
transformer=self.transform,
capture_locals=self.capture_locals,
)
data.update({
'stacktrace': stack_info,
})
if self.include_paths:
for frame in self._iter_frames(data):
if frame.get('in_app') is not None:
continue
path = frame.get('module')
if not path:
continue
if path.startswith('raven.'):
frame['in_app'] = False
else:
frame['in_app'] = (
any(path.startswith(x) for x in self.include_paths) and
not any(path.startswith(x) for x in self.exclude_paths)
)
if not culprit:
if 'stacktrace' in data:
culprit = get_culprit(data['stacktrace']['frames'])
elif 'exception' in data:
stacktrace = data['exception']['values'][0].get('stacktrace')
if stacktrace:
culprit = get_culprit(stacktrace['frames'])
if not data.get('level'):
data['level'] = kwargs.get('level') or logging.ERROR
if not data.get('server_name'):
data['server_name'] = self.name
if not data.get('modules'):
data['modules'] = self.get_module_versions()
if self.release is not None:
data['release'] = self.release
if self.environment is not None:
data['environment'] = self.environment
data['tags'] = merge_dicts(self.tags, data['tags'], tags)
data['extra'] = merge_dicts(self.extra, data['extra'], extra)
# Legacy support for site attribute
site = data.pop('site', None) or self.site
if site:
data['tags'].setdefault('site', site)
if culprit:
data['culprit'] = culprit
if fingerprint:
data['fingerprint'] = fingerprint
# Run the data through processors
for processor in self.get_processors():
data.update(processor.process(data))
if 'message' not in data:
data['message'] = kwargs.get('message', handler.to_string(data))
# tags should only be key=>u'value'
for key, value in iteritems(data['tags']):
data['tags'][key] = to_unicode(value)
# extra data can be any arbitrary value
for k, v in iteritems(data['extra']):
data['extra'][k] = self.transform(v)
# It's important date is added **after** we serialize
data.setdefault('project', self.remote.project)
data.setdefault('timestamp', date or datetime.utcnow())
data.setdefault('time_spent', time_spent)
data.setdefault('event_id', event_id)
data.setdefault('platform', PLATFORM_NAME)
return data
def transform(self, data):
return transform(
data, list_max_length=self.list_max_length,
string_max_length=self.string_max_length)
@property
def context(self):
"""
Updates this clients thread-local context for future events.
>>> def view_handler(view_func, *args, **kwargs):
>>> client.context.merge(tags={'key': 'value'})
>>> try:
>>> return view_func(*args, **kwargs)
>>> finally:
>>> client.context.clear()
"""
return self._context
def user_context(self, data):
"""
Update the user context for future events.
>>> client.user_context({'email': 'foo@example.com'})
"""
return self.context.merge({
'user': data,
})
def http_context(self, data, **kwargs):
"""
Update the http context for future events.
>>> client.http_context({'url': 'http://example.com'})
"""
return self.context.merge({
'request': data,
})
def extra_context(self, data, **kwargs):
"""
Update the extra context for future events.
>>> client.extra_context({'foo': 'bar'})
"""
return self.context.merge({
'extra': data,
})
def tags_context(self, data, **kwargs):
"""
Update the tags context for future events.
>>> client.tags_context({'version': '1.0'})
"""
return self.context.merge({
'tags': data,
})
def capture(self, event_type, data=None, date=None, time_spent=None,
extra=None, stack=None, tags=None, **kwargs):
"""
Captures and processes an event and pipes it off to SentryClient.send.
To use structured data (interfaces) with capture:
>>> capture('raven.events.Message', message='foo', data={
>>> 'request': {
>>> 'url': '...',
>>> 'data': {},
>>> 'query_string': '...',
>>> 'method': 'POST',
>>> },
>>> 'logger': 'logger.name',
>>> }, extra={
>>> 'key': 'value',
>>> })
The finalized ``data`` structure contains the following (some optional)
builtin values:
>>> {
>>> # the culprit and version information
>>> 'culprit': 'full.module.name', # or /arbitrary/path
>>>
>>> # all detectable installed modules
>>> 'modules': {
>>> 'full.module.name': 'version string',
>>> },
>>>
>>> # arbitrary data provided by user
>>> 'extra': {
>>> 'key': 'value',
>>> }
>>> }
:param event_type: the module path to the Event class. Builtins can use
shorthand class notation and exclude the full module
path.
:param data: the data base, useful for specifying structured data
interfaces. Any key which contains a '.' will be
assumed to be a data interface.
:param date: the datetime of this event
:param time_spent: a integer value representing the duration of the
event (in milliseconds)
:param extra: a dictionary of additional standard metadata
:param stack: a stacktrace for the event
:param tags: dict of extra tags
:return: a tuple with a 32-length string identifying this event
"""
if not self.is_enabled():
return
exc_info = kwargs.get('exc_info')
if exc_info is not None:
if self.skip_error_for_logging(exc_info):
return
self.record_exception_seen(exc_info)
data = self.build_msg(
event_type, data, date, time_spent, extra, stack, tags=tags,
**kwargs)
self.send(**data)
return data['event_id']
def is_enabled(self):
"""
Return a boolean describing whether the client should attempt to send
events.
"""
return self.remote.is_active()
def _iter_frames(self, data):
if 'stacktrace' in data:
for frame in data['stacktrace']['frames']:
yield frame
if 'exception' in data:
for frame in data['exception']['values'][0]['stacktrace']['frames']:
yield frame
def _successful_send(self):
self.state.set_success()
def _failed_send(self, exc, url, data):
retry_after = 0
if isinstance(exc, APIError):
if isinstance(exc, RateLimited):
retry_after = exc.retry_after
self.error_logger.error(
'Sentry responded with an API error: %s(%s)',
type(exc).__name__, exc.message)
else:
self.error_logger.error(
'Sentry responded with an error: %s (url: %s)\n%s',
exc, url, pformat(data),
exc_info=True
)
self._log_failed_submission(data)
self.state.set_fail(retry_after=retry_after)
def _log_failed_submission(self, data):
"""
Log a reasonable representation of an event that should have been sent
to Sentry
"""
message = data.pop('message', '<no message value>')
output = [message]
if 'exception' in data and 'stacktrace' in data['exception']['values'][0]:
# try to reconstruct a reasonable version of the exception
for frame in data['exception']['values'][0]['stacktrace']['frames']:
output.append(' File "%(fn)s", line %(lineno)s, in %(func)s' % {
'fn': frame['filename'],
'lineno': frame['lineno'],
'func': frame['function'],
})
self.uncaught_logger.error(output)
def send_remote(self, url, data, headers=None):
# If the client is configured to raise errors on sending,
# the implication is that the backoff and retry strategies
# will be handled by the calling application
if headers is None:
headers = {}
if not self.raise_send_errors and not self.state.should_try():
data = self.decode(data)
self._log_failed_submission(data)
return
self.logger.debug('Sending message of length %d to %s', len(data), url)
def failed_send(e):
self._failed_send(e, url, self.decode(data))
try:
transport = self.remote.get_transport()
if transport.async:
transport.async_send(data, headers, self._successful_send,
failed_send)
else:
transport.send(data, headers)
self._successful_send()
except Exception as e:
if self.raise_send_errors:
raise
failed_send(e)
def send(self, auth_header=None, **data):
"""
Serializes the message and passes the payload onto ``send_encoded``.
"""
message = self.encode(data)
return self.send_encoded(message, auth_header=auth_header)
def send_encoded(self, message, auth_header=None, **kwargs):
"""
Given an already serialized message, signs the message and passes the
payload off to ``send_remote``.
"""
client_string = 'raven-python/%s' % (raven.VERSION,)
if not auth_header:
timestamp = time.time()
auth_header = get_auth_header(
protocol=self.protocol_version,
timestamp=timestamp,
client=client_string,
api_key=self.remote.public_key,
api_secret=self.remote.secret_key,
)
headers = {
'User-Agent': client_string,
'X-Sentry-Auth': auth_header,
'Content-Encoding': self.get_content_encoding(),
'Content-Type': 'application/octet-stream',
}
self.send_remote(
url=self.remote.store_endpoint,
data=message,
headers=headers,
**kwargs
)
def get_content_encoding(self):
return 'deflate'
def encode(self, data):
"""
Serializes ``data`` into a raw string.
"""
return zlib.compress(json.dumps(data).encode('utf8'))
def decode(self, data):
"""
Unserializes a string, ``data``.
"""
return json.loads(zlib.decompress(data).decode('utf8'))
def captureMessage(self, message, **kwargs):
"""
Creates an event from ``message``.
>>> client.captureMessage('My event just happened!')
"""
return self.capture('raven.events.Message', message=message, **kwargs)
def captureException(self, exc_info=None, **kwargs):
"""
Creates an event from an exception.
>>> try:
>>> exc_info = sys.exc_info()
>>> client.captureException(exc_info)
>>> finally:
>>> del exc_info
If exc_info is not provided, or is set to True, then this method will
perform the ``exc_info = sys.exc_info()`` and the requisite clean-up
for you.
``kwargs`` are passed through to ``.capture``.
"""
if exc_info is None or exc_info is True:
exc_info = sys.exc_info()
return self.capture(
'raven.events.Exception', exc_info=exc_info, **kwargs)
def capture_exceptions(self, function_or_exceptions=None, **kwargs):
"""
Wrap a function or code block in try/except and automatically call
``.captureException`` if it raises an exception, then the exception
is reraised.
By default, it will capture ``Exception``
>>> @client.capture_exceptions
>>> def foo():
>>> raise Exception()
>>> with client.capture_exceptions():
>>> raise Exception()
You can also specify exceptions to be caught specifically
>>> @client.capture_exceptions((IOError, LookupError))
>>> def bar():
>>> ...
>>> with client.capture_exceptions((IOError, LookupError)):
>>> ...
``kwargs`` are passed through to ``.captureException``.
"""
function = None
exceptions = (Exception,)
if isinstance(function_or_exceptions, FunctionType):
function = function_or_exceptions
elif function_or_exceptions is not None:
exceptions = function_or_exceptions
# In python3.2 contextmanager acts both as contextmanager and decorator
@contextlib.contextmanager
def make_decorator(exceptions):
try:
yield
except exceptions:
self.captureException(**kwargs)
raise
decorator = make_decorator(exceptions)
if function:
return decorator(function)
return decorator
def captureQuery(self, query, params=(), engine=None, **kwargs):
"""
Creates an event for a SQL query.
>>> client.captureQuery('SELECT * FROM foo')
"""
return self.capture(
'raven.events.Query', query=query, params=params, engine=engine,
**kwargs)
def captureExceptions(self, **kwargs):
warnings.warn(
'captureExceptions is deprecated, used context() instead.',
DeprecationWarning)
return self.context(**kwargs)
class DummyClient(Client):
"Sends messages into an empty void"
def send(self, **kwargs):
return None

View File

@ -1,54 +0,0 @@
"""
raven.conf
~~~~~~~~~~
:copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
from __future__ import absolute_import
import logging
__all__ = ['setup_logging']
EXCLUDE_LOGGER_DEFAULTS = (
'raven',
'gunicorn',
'south',
'sentry.errors',
'django.request',
)
def setup_logging(handler, exclude=EXCLUDE_LOGGER_DEFAULTS):
"""
Configures logging to pipe to Sentry.
- ``exclude`` is a list of loggers that shouldn't go to Sentry.
For a typical Python install:
>>> from raven.handlers.logging import SentryHandler
>>> client = Sentry(...)
>>> setup_logging(SentryHandler(client))
Within Django:
>>> from raven.contrib.django.handlers import SentryHandler
>>> setup_logging(SentryHandler())
Returns a boolean based on if logging was configured or not.
"""
logger = logging.getLogger()
if handler.__class__ in map(type, logger.handlers):
return False
logger.addHandler(handler)
# Add StreamHandler to sentry's default so you can catch missed exceptions
for logger_name in exclude:
logger = logging.getLogger(logger_name)
logger.propagate = False
logger.addHandler(logging.StreamHandler())
return True

View File

@ -1,50 +0,0 @@
"""
raven.conf.defaults
~~~~~~~~~~~~~~~~~~~
Represents the default values for all Sentry settings.
:copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
from __future__ import absolute_import
import os
import os.path
import socket
ROOT = os.path.normpath(os.path.join(os.path.dirname(__file__), os.pardir))
TIMEOUT = 1
# TODO: this is specific to Django
CLIENT = 'raven.contrib.django.DjangoClient'
# Not all environments have access to socket module, for example Google App Engine
# Need to check to see if the socket module has ``gethostname``, if it doesn't we
# will set it to None and require it passed in to ``Client`` on initializtion.
NAME = socket.gethostname() if hasattr(socket, 'gethostname') else None
# The maximum number of elements to store for a list-like structure.
MAX_LENGTH_LIST = 50
# The maximum length to store of a string-like structure.
MAX_LENGTH_STRING = 400
# Automatically log frame stacks from all ``logging`` messages.
AUTO_LOG_STACKS = False
# Collect locals variables
CAPTURE_LOCALS = True
# Client-side data processors to apply
PROCESSORS = (
'raven.processors.SanitizePasswordsProcessor',
)
try:
# Try for certifi first since they likely keep their bundle more up to date
import certifi
CA_BUNDLE = certifi.where()
except ImportError:
CA_BUNDLE = os.path.join(ROOT, 'data', 'cacert.pem')

View File

@ -1,102 +0,0 @@
from __future__ import absolute_import
import warnings
from raven._compat import PY2, text_type
from raven.exceptions import InvalidDsn
from raven.transport.threaded import ThreadedHTTPTransport
from raven.utils.encoding import to_string
from raven.utils.urlparse import parse_qsl, urlparse
ERR_UNKNOWN_SCHEME = 'Unsupported Sentry DSN scheme: {0} ({1})'
DEFAULT_TRANSPORT = ThreadedHTTPTransport
class RemoteConfig(object):
def __init__(self, base_url=None, project=None, public_key=None,
secret_key=None, transport=None, options=None):
if base_url:
base_url = base_url.rstrip('/')
store_endpoint = '%s/api/%s/store/' % (base_url, project)
else:
store_endpoint = None
self.base_url = base_url
self.project = project
self.public_key = public_key
self.secret_key = secret_key
self.options = options or {}
self.store_endpoint = store_endpoint
self._transport_cls = transport or DEFAULT_TRANSPORT
def __unicode__(self):
return text_type(self.base_url)
def is_active(self):
return all([self.base_url, self.project, self.public_key, self.secret_key])
# TODO(dcramer): we dont want transports bound to a URL
def get_transport(self):
if not self.store_endpoint:
return
if not hasattr(self, '_transport'):
parsed = urlparse(self.store_endpoint)
self._transport = self._transport_cls(parsed, **self.options)
return self._transport
def get_public_dsn(self):
url = urlparse(self.base_url)
netloc = url.hostname
if url.port:
netloc += ':%s' % url.port
return '//%s@%s%s/%s' % (self.public_key, netloc, url.path, self.project)
@classmethod
def from_string(cls, value, transport=None, transport_registry=None):
# in Python 2.x sending the DSN as a unicode value will eventually
# cause issues in httplib
if PY2:
value = to_string(value)
url = urlparse(value)
if url.scheme not in ('http', 'https'):
warnings.warn('Transport selection via DSN is deprecated. You should explicitly pass the transport class to Client() instead.')
if transport is None:
if not transport_registry:
from raven.transport import TransportRegistry, default_transports
transport_registry = TransportRegistry(default_transports)
if not transport_registry.supported_scheme(url.scheme):
raise InvalidDsn(ERR_UNKNOWN_SCHEME.format(url.scheme, value))
transport = transport_registry.get_transport_cls(url.scheme)
netloc = url.hostname
if url.port:
netloc += ':%s' % url.port
path_bits = url.path.rsplit('/', 1)
if len(path_bits) > 1:
path = path_bits[0]
else:
path = ''
project = path_bits[-1]
if not all([netloc, project, url.username, url.password]):
raise InvalidDsn('Invalid Sentry DSN: %r' % url.geturl())
base_url = '%s://%s%s' % (url.scheme.rsplit('+', 1)[-1], netloc, path)
return cls(
base_url=base_url,
project=project,
public_key=url.username,
secret_key=url.password,
options=dict(parse_qsl(url.query)),
transport=transport,
)

View File

@ -1,62 +0,0 @@
"""
raven.context
~~~~~~~~~~~~~
:copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
from __future__ import absolute_import
from collections import Mapping, Iterable
from threading import local
from raven._compat import iteritems
class Context(local, Mapping, Iterable):
"""
Stores context until cleared.
>>> def view_handler(view_func, *args, **kwargs):
>>> context = Context()
>>> context.merge(tags={'key': 'value'})
>>> try:
>>> return view_func(*args, **kwargs)
>>> finally:
>>> context.clear()
"""
def __init__(self):
self.data = {}
self.exceptions_to_skip = set()
def __getitem__(self, key):
return self.data[key]
def __iter__(self):
return iter(self.data)
def __len__(self):
return len(self.data)
def __repr__(self):
return '<%s: %s>' % (type(self).__name__, self.data)
def merge(self, data):
d = self.data
for key, value in iteritems(data):
if key in ('tags', 'extra'):
d.setdefault(key, {})
for t_key, t_value in iteritems(value):
d[key][t_key] = t_value
else:
d[key] = value
def set(self, data):
self.data = data
def get(self):
return self.data
def clear(self):
self.data = {}
self.exceptions_to_skip.clear()

View File

@ -1,8 +0,0 @@
"""
raven.contrib
~~~~~~~~~~~~~
:copyright: (c) 2010-2013 by the Sentry Team, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
from __future__ import absolute_import

View File

@ -1,29 +0,0 @@
"""
raven.contrib.async
~~~~~~~~~~~~~~~~~~~
:copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
from __future__ import absolute_import
import warnings
from raven.base import Client
from raven.transport.threaded import AsyncWorker
class AsyncClient(Client):
"""
This client uses a single background thread to dispatch errors.
"""
def __init__(self, worker=None, *args, **kwargs):
warnings.warn('AsyncClient is deprecated. Use the threaded+http transport instead.', DeprecationWarning)
self.worker = worker or AsyncWorker()
super(AsyncClient, self).__init__(*args, **kwargs)
def send_sync(self, **kwargs):
super(AsyncClient, self).send(**kwargs)
def send(self, **kwargs):
self.worker.queue(self.send_sync, **kwargs)

View File

@ -1,90 +0,0 @@
"""
raven.contrib.bottle
~~~~~~~~~~~~~~~~~~~
:copyright: (c) 2013 by the Sentry Team, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
from __future__ import absolute_import
import sys
from bottle import request
from raven.conf import setup_logging
from raven.contrib.bottle.utils import get_data_from_request
from raven.handlers.logging import SentryHandler
class Sentry(object):
"""
Bottle application for Sentry.
>>> sentry = Sentry(app, client)
Automatically configure logging::
>>> sentry = Sentry(app, client, logging=True)
Capture an exception::
>>> try:
>>> 1 / 0
>>> except ZeroDivisionError:
>>> sentry.captureException()
Capture a message::
>>> sentry.captureMessage('hello, world!')
"""
def __init__(self, app, client, logging=False):
self.app = app
self.client = client
self.logging = logging
if self.logging:
setup_logging(SentryHandler(self.client))
self.app.sentry = self
def handle_exception(self, *args, **kwargs):
self.client.captureException(
exc_info=kwargs.get('exc_info'),
data=get_data_from_request(request),
extra={
'app': self.app,
},
)
def __call__(self, environ, start_response):
def session_start_response(status, headers, exc_info=None):
if exc_info is not None:
self.handle_exception(exc_info=exc_info)
return start_response(status, headers, exc_info)
try:
return self.app(environ, session_start_response)
# catch ANY exception that goes through...
except Exception:
self.handle_exception(exc_info=sys.exc_info())
return self.app(environ, session_start_response)
def captureException(self, *args, **kwargs):
assert self.client, 'captureException called before application configured'
data = kwargs.get('data')
if data is None:
try:
kwargs['data'] = get_data_from_request(request)
except RuntimeError:
# app is probably not configured yet
pass
return self.client.captureException(*args, **kwargs)
def captureMessage(self, *args, **kwargs):
assert self.client, 'captureMessage called before application configured'
data = kwargs.get('data')
if data is None:
try:
kwargs['data'] = get_data_from_request(request)
except RuntimeError:
# app is probably not configured yet
pass
return self.client.captureMessage(*args, **kwargs)

View File

@ -1,39 +0,0 @@
"""
raven.contrib.bottle.utils
~~~~~~~~~~~~~~~~~~~~~~~~~~
:copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
from __future__ import absolute_import
import logging
from raven.utils.compat import _urlparse
from raven.utils.wsgi import get_headers, get_environ
logger = logging.getLogger(__name__)
def get_data_from_request(request):
urlparts = _urlparse.urlsplit(request.url)
try:
form_dict = request.forms.dict
# we only are about the most recent one
formdata = dict([(k, form_dict[k][-1]) for k in form_dict])
except Exception:
formdata = {}
data = {
'request': {
'url': '%s://%s%s' % (urlparts.scheme, urlparts.netloc, urlparts.path),
'query_string': urlparts.query,
'method': request.method,
'data': formdata,
'headers': dict(get_headers(request.environ)),
'env': dict(get_environ(request.environ)),
}
}
return data

View File

@ -1,59 +0,0 @@
"""
raven.contrib.celery
~~~~~~~~~~~~~~~~~~~~
:copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
from __future__ import absolute_import
import logging
from celery.signals import after_setup_logger, task_failure
from raven.handlers.logging import SentryHandler
class CeleryFilter(logging.Filter):
def filter(self, record):
# Context is fixed in Celery 3.x so use internal flag instead
extra_data = getattr(record, 'data', {})
if not isinstance(extra_data, dict):
return record.funcName != '_log_error'
# Fallback to funcName for Celery 2.5
return extra_data.get('internal', record.funcName != '_log_error')
def register_signal(client):
def process_failure_signal(sender, task_id, args, kwargs, **kw):
# This signal is fired inside the stack so let raven do its magic
client.captureException(
extra={
'task_id': task_id,
'task': sender,
'args': args,
'kwargs': kwargs,
})
task_failure.connect(process_failure_signal, weak=False)
def register_logger_signal(client, logger=None, loglevel=logging.ERROR):
filter_ = CeleryFilter()
handler = SentryHandler(client)
handler.setLevel(loglevel)
handler.addFilter(filter_)
def process_logger_event(sender, logger, loglevel, logfile, format,
colorize, **kw):
# Attempt to find an existing SentryHandler, and if it exists ensure
# that the CeleryFilter is installed.
# If one is found, we do not attempt to install another one.
for h in logger.handlers:
if type(h) == SentryHandler:
h.addFilter(filter_)
return False
logger.addHandler(handler)
after_setup_logger.connect(process_logger_event, weak=False)

View File

@ -1,12 +0,0 @@
"""
raven.contrib.django
~~~~~~~~~~~~~~~~~~~~
:copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
from __future__ import absolute_import
default_app_config = 'raven.contrib.django.apps.RavenConfig'
from .client import DjangoClient # NOQA

View File

@ -1,9 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import
from django.apps import AppConfig
class RavenConfig(AppConfig):
name = 'raven.contrib.django'
label = 'raven_contrib_django'
verbose_name = 'Raven'

View File

@ -1,34 +0,0 @@
"""
raven.contrib.django.celery
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
:copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
from __future__ import absolute_import
from raven.contrib.django.client import DjangoClient
try:
from celery.task import task
except ImportError:
from celery.decorators import task # NOQA
class CeleryClient(DjangoClient):
def send_integrated(self, kwargs):
return send_raw_integrated.delay(kwargs)
def send_encoded(self, *args, **kwargs):
return send_raw.delay(*args, **kwargs)
@task(routing_key='sentry')
def send_raw_integrated(kwargs):
from raven.contrib.django.models import get_client
super(DjangoClient, get_client()).send_integrated(kwargs)
@task(routing_key='sentry')
def send_raw(*args, **kwargs):
from raven.contrib.django.models import get_client
super(DjangoClient, get_client()).send_encoded(*args, **kwargs)

View File

@ -1,16 +0,0 @@
"""
raven.contrib.django.celery.models
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
:copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
from __future__ import absolute_import
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
if 'djcelery' not in settings.INSTALLED_APPS:
raise ImproperlyConfigured(
"Put 'djcelery' in your INSTALLED_APPS setting in order to use the "
"sentry celery client.")

View File

@ -1,10 +0,0 @@
"""
raven.contrib.django.celery.tasks
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
:copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
from __future__ import absolute_import
from raven.contrib.django.models import client # NOQA

View File

@ -1,176 +0,0 @@
"""
raven.contrib.django.client
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
:copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
from __future__ import absolute_import
import logging
from django.conf import settings
from django.core.exceptions import SuspiciousOperation
from django.http import HttpRequest
from django.template import TemplateSyntaxError
try:
# support Django 1.9
from django.template.base import Origin
except ImportError:
# backward compatibility
from django.template.loader import LoaderOrigin as Origin
from raven.base import Client
from raven.contrib.django.utils import get_data_from_template, get_host
from raven.contrib.django.middleware import SentryLogMiddleware
from raven.utils.wsgi import get_headers, get_environ
__all__ = ('DjangoClient',)
class DjangoClient(Client):
logger = logging.getLogger('sentry.errors.client.django')
def get_user_info(self, user):
if hasattr(user, 'is_authenticated') and \
not user.is_authenticated():
return None
user_info = {
'id': user.pk,
}
if hasattr(user, 'email'):
user_info['email'] = user.email
if hasattr(user, 'get_username'):
user_info['username'] = user.get_username()
elif hasattr(user, 'username'):
user_info['username'] = user.username
return user_info
def get_data_from_request(self, request):
result = {}
user = getattr(request, 'user', None)
if user is not None:
user_info = self.get_user_info(user)
if user_info:
result['user'] = user_info
try:
uri = request.build_absolute_uri()
except SuspiciousOperation:
# attempt to build a URL for reporting as Django won't allow us to
# use get_host()
if request.is_secure():
scheme = 'https'
else:
scheme = 'http'
host = get_host(request)
uri = '%s://%s%s' % (scheme, host, request.path)
if request.method not in ('GET', 'HEAD'):
try:
data = request.body
except Exception:
try:
data = request.raw_post_data
except Exception:
# assume we had a partial read.
try:
data = request.POST or '<unavailable>'
except Exception:
data = '<unavailable>'
else:
data = None
environ = request.META
result.update({
'request': {
'method': request.method,
'url': uri,
'query_string': request.META.get('QUERY_STRING'),
'data': data,
'cookies': dict(request.COOKIES),
'headers': dict(get_headers(environ)),
'env': dict(get_environ(environ)),
}
})
return result
def build_msg(self, *args, **kwargs):
data = super(DjangoClient, self).build_msg(*args, **kwargs)
for frame in self._iter_frames(data):
module = frame.get('module')
if not module:
continue
if module.startswith('django.'):
frame['in_app'] = False
if not self.site and 'django.contrib.sites' in settings.INSTALLED_APPS:
try:
from django.contrib.sites.models import Site
site = Site.objects.get_current()
site_name = site.name or site.domain
data['tags'].setdefault('site', site_name)
except Exception:
# Database error? Fallback to the id
try:
data['tags'].setdefault('site', settings.SITE_ID)
except AttributeError:
# SITE_ID wasn't set, so just ignore
pass
return data
def capture(self, event_type, request=None, **kwargs):
if 'data' not in kwargs:
kwargs['data'] = data = {}
else:
data = kwargs['data']
if request is None:
request = getattr(SentryLogMiddleware.thread, 'request', None)
is_http_request = isinstance(request, HttpRequest)
if is_http_request:
data.update(self.get_data_from_request(request))
if kwargs.get('exc_info'):
exc_value = kwargs['exc_info'][1]
# As of r16833 (Django) all exceptions may contain a
# ``django_template_source`` attribute (rather than the legacy
# ``TemplateSyntaxError.source`` check) which describes
# template information. As of Django 1.9 or so the new
# template debug thing showed up.
if hasattr(exc_value, 'django_template_source') or \
((isinstance(exc_value, TemplateSyntaxError) and
isinstance(getattr(exc_value, 'source', None),
(tuple, list)) and
isinstance(exc_value.source[0], Origin))) or \
hasattr(exc_value, 'template_debug'):
source = getattr(exc_value, 'django_template_source',
getattr(exc_value, 'source', None))
debug = getattr(exc_value, 'template_debug', None)
if source is None:
self.logger.info('Unable to get template source from exception')
data.update(get_data_from_template(source, debug))
result = super(DjangoClient, self).capture(event_type, **kwargs)
if is_http_request and result:
# attach the sentry object to the request
request.sentry = {
'project_id': data.get('project', self.remote.project),
'id': self.get_ident(result),
}
return result

View File

@ -1,33 +0,0 @@
"""
raven.contrib.django.handlers
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
:copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
from __future__ import absolute_import
import logging
from raven.handlers.logging import SentryHandler as BaseSentryHandler
from raven.utils import memoize
class SentryHandler(BaseSentryHandler):
def __init__(self, *args, **kwargs):
# TODO(dcramer): we'd like to avoid this duplicate code, but we need
# to currently defer loading client due to Django loading patterns.
self.tags = kwargs.pop('tags', None)
logging.Handler.__init__(self, level=kwargs.get('level', logging.NOTSET))
@memoize
def client(self):
# Import must be lazy for deffered Django loading
from raven.contrib.django.models import client
return client
def _emit(self, record):
request = getattr(record, 'request', None)
return super(SentryHandler, self)._emit(record, request=request)

View File

@ -1,17 +0,0 @@
"""
raven.contrib.django.logging
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
:copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
from __future__ import absolute_import
import warnings
warnings.warn('raven.contrib.django.logging is deprecated. Use raven.contrib.django.handlers instead.', DeprecationWarning)
from raven.contrib.django.handlers import SentryHandler # NOQA
__all__ = ('SentryHandler',)

View File

@ -1,53 +0,0 @@
"""
raven.contrib.django.raven.management
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
:copyright: (c) 2010-2013 by the Sentry Team, see AUTHORS for more details
:license: BSD, see LICENSE for more details.
"""
from __future__ import absolute_import, print_function
import sys
import warnings
from functools import wraps
def patch_cli_runner():
"""
Patches ``cls.execute``, returning a boolean describing if the
attempt was successful.
"""
try:
from django.core.management.base import BaseCommand
except ImportError:
warnings.warn('Unable to patch Django management commands')
return
else:
cls = BaseCommand
try:
original_func = cls.execute
except AttributeError:
# must not be a capable version of Django
return False
if hasattr(original_func, '__raven_patched'):
return False
@wraps(original_func)
def new_execute(self, *args, **kwargs):
try:
return original_func(self, *args, **kwargs)
except Exception:
from raven.contrib.django.models import client
client.captureException(extra={
'argv': sys.argv
})
raise
new_execute.__raven_patched = True
cls.execute = new_execute
return True

View File

@ -1,8 +0,0 @@
"""
raven.contrib.django.management.commands
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
:copyright: (c) 2010-2013 by the Sentry Team, see AUTHORS for more details
:license: BSD, see LICENSE for more details.
"""
from __future__ import absolute_import, print_function

View File

@ -1,37 +0,0 @@
"""
raven.contrib.django.management.commands.raven
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
:copyright: (c) 2010-2013 by the Sentry Team, see AUTHORS for more details
:license: BSD, see LICENSE for more details.
"""
from __future__ import absolute_import, print_function
from django.core.management.base import BaseCommand
from optparse import make_option
from raven.scripts.runner import store_json, send_test_message
import sys
import time
class Command(BaseCommand):
help = 'Commands to interact with the Sentry client'
option_list = BaseCommand.option_list + (
make_option(
"--data", action="callback", callback=store_json,
type="string", nargs=1, dest="data"),
make_option(
"--tags", action="callback", callback=store_json,
type="string", nargs=1, dest="tags"),
)
def handle(self, *args, **options):
if len(args) != 1 or args[0] != 'test':
print('Usage: manage.py raven test')
sys.exit(1)
from raven.contrib.django.models import client
send_test_message(client, options)
time.sleep(3)

View File

@ -1,69 +0,0 @@
"""
raven.contrib.django.middleware
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
:copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
from __future__ import absolute_import
import threading
import logging
from django.conf import settings
def is_ignorable_404(uri):
"""
Returns True if a 404 at the given URL *shouldn't* notify the site managers.
"""
return any(
pattern.search(uri)
for pattern in getattr(settings, 'IGNORABLE_404_URLS', ())
)
class Sentry404CatchMiddleware(object):
def process_response(self, request, response):
from raven.contrib.django.models import client
if response.status_code != 404 or is_ignorable_404(request.get_full_path()) or not client.is_enabled():
return response
data = client.get_data_from_request(request)
data.update({
'level': logging.INFO,
'logger': 'http404',
})
result = client.captureMessage(message='Page Not Found: %s' % request.build_absolute_uri(), data=data)
if not result:
return
request.sentry = {
'project_id': data.get('project', client.remote.project),
'id': client.get_ident(result),
}
return response
# sentry_exception_handler(sender=Sentry404CatchMiddleware, request=request)
class SentryResponseErrorIdMiddleware(object):
"""
Appends the X-Sentry-ID response header for referencing a message within
the Sentry datastore.
"""
def process_response(self, request, response):
if not getattr(request, 'sentry', None):
return response
response['X-Sentry-ID'] = request.sentry['id']
return response
class SentryLogMiddleware(object):
# Create a threadlocal variable to store the session in for logging
thread = threading.local()
def process_request(self, request):
self.thread.request = request

View File

@ -1,28 +0,0 @@
"""
raven.contrib.django.middleware.wsgi
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
:copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
from __future__ import absolute_import
from raven.middleware import Sentry
from raven.utils import memoize
class Sentry(Sentry):
"""
Identical to the default WSGI middleware except that
the client comes dynamically via ``get_client
>>> from raven.contrib.django.middleware.wsgi import Sentry
>>> application = Sentry(application)
"""
def __init__(self, application):
self.application = application
@memoize
def client(self):
from raven.contrib.django.models import client
return client

View File

@ -1,243 +0,0 @@
"""
raven.contrib.django.models
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Acts as an implicit hook for Django installs.
:copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
# flake8: noqa
from __future__ import absolute_import, unicode_literals
import copy
import logging
import sys
import warnings
from django.conf import settings
from hashlib import md5
from raven._compat import PY2, binary_type, text_type, string_types
from raven.utils.imports import import_string
from raven.contrib.django.management import patch_cli_runner
logger = logging.getLogger('sentry.errors.client')
def get_installed_apps():
"""
Modules in settings.INSTALLED_APPS as a set.
"""
return set(settings.INSTALLED_APPS)
_client = (None, None)
class ProxyClient(object):
"""
A proxy which represents the currently client at all times.
"""
# introspection support:
__members__ = property(lambda x: x.__dir__())
# Need to pretend to be the wrapped class, for the sake of objects that care
# about this (especially in equality tests)
__class__ = property(lambda x: get_client().__class__)
__dict__ = property(lambda o: get_client().__dict__)
__repr__ = lambda x: repr(get_client())
__getattr__ = lambda x, o: getattr(get_client(), o)
__setattr__ = lambda x, o, v: setattr(get_client(), o, v)
__delattr__ = lambda x, o: delattr(get_client(), o)
__lt__ = lambda x, o: get_client() < o
__le__ = lambda x, o: get_client() <= o
__eq__ = lambda x, o: get_client() == o
__ne__ = lambda x, o: get_client() != o
__gt__ = lambda x, o: get_client() > o
__ge__ = lambda x, o: get_client() >= o
if PY2:
__cmp__ = lambda x, o: cmp(get_client(), o) # NOQA
__hash__ = lambda x: hash(get_client())
# attributes are currently not callable
# __call__ = lambda x, *a, **kw: get_client()(*a, **kw)
__nonzero__ = lambda x: bool(get_client())
__len__ = lambda x: len(get_client())
__getitem__ = lambda x, i: get_client()[i]
__iter__ = lambda x: iter(get_client())
__contains__ = lambda x, i: i in get_client()
__getslice__ = lambda x, i, j: get_client()[i:j]
__add__ = lambda x, o: get_client() + o
__sub__ = lambda x, o: get_client() - o
__mul__ = lambda x, o: get_client() * o
__floordiv__ = lambda x, o: get_client() // o
__mod__ = lambda x, o: get_client() % o
__divmod__ = lambda x, o: get_client().__divmod__(o)
__pow__ = lambda x, o: get_client() ** o
__lshift__ = lambda x, o: get_client() << o
__rshift__ = lambda x, o: get_client() >> o
__and__ = lambda x, o: get_client() & o
__xor__ = lambda x, o: get_client() ^ o
__or__ = lambda x, o: get_client() | o
__div__ = lambda x, o: get_client().__div__(o)
__truediv__ = lambda x, o: get_client().__truediv__(o)
__neg__ = lambda x: -(get_client())
__pos__ = lambda x: +(get_client())
__abs__ = lambda x: abs(get_client())
__invert__ = lambda x: ~(get_client())
__complex__ = lambda x: complex(get_client())
__int__ = lambda x: int(get_client())
if PY2:
__long__ = lambda x: long(get_client()) # NOQA
__float__ = lambda x: float(get_client())
__str__ = lambda x: binary_type(get_client())
__unicode__ = lambda x: text_type(get_client())
__oct__ = lambda x: oct(get_client())
__hex__ = lambda x: hex(get_client())
__index__ = lambda x: get_client().__index__()
__coerce__ = lambda x, o: x.__coerce__(x, o)
__enter__ = lambda x: x.__enter__()
__exit__ = lambda x, *a, **kw: x.__exit__(*a, **kw)
client = ProxyClient()
def get_option(x, d=None):
options = getattr(settings, 'RAVEN_CONFIG', {})
return getattr(settings, 'SENTRY_%s' % x, options.get(x, d))
def get_client(client=None, reset=False):
global _client
tmp_client = client is not None
if not tmp_client:
client = getattr(settings, 'SENTRY_CLIENT', 'raven.contrib.django.DjangoClient')
if _client[0] != client or reset:
ga = lambda x, d=None: getattr(settings, 'SENTRY_%s' % x, d)
options = copy.deepcopy(getattr(settings, 'RAVEN_CONFIG', {}))
options.setdefault('include_paths', ga('INCLUDE_PATHS', []))
if not options['include_paths']:
options['include_paths'] = get_installed_apps()
options.setdefault('exclude_paths', ga('EXCLUDE_PATHS'))
options.setdefault('timeout', ga('TIMEOUT'))
options.setdefault('name', ga('NAME'))
options.setdefault('auto_log_stacks', ga('AUTO_LOG_STACKS'))
options.setdefault('string_max_length', ga('MAX_LENGTH_STRING'))
options.setdefault('list_max_length', ga('MAX_LENGTH_LIST'))
options.setdefault('site', ga('SITE'))
options.setdefault('processors', ga('PROCESSORS'))
options.setdefault('dsn', ga('DSN'))
options.setdefault('context', ga('CONTEXT'))
options.setdefault('release', ga('RELEASE'))
transport = ga('TRANSPORT') or options.get('transport')
if isinstance(transport, string_types):
transport = import_string(transport)
options['transport'] = transport
try:
Client = import_string(client)
except ImportError:
logger.exception('Failed to import client: %s', client)
if not _client[1]:
# If there is no previous client, set the default one.
client = 'raven.contrib.django.DjangoClient'
_client = (client, get_client(client))
else:
instance = Client(**options)
if not tmp_client:
_client = (client, instance)
return instance
return _client[1]
def sentry_exception_handler(request=None, **kwargs):
exc_type = sys.exc_info()[0]
exclusions = set(get_option('IGNORE_EXCEPTIONS', ()))
exc_name = '%s.%s' % (exc_type.__module__, exc_type.__name__)
if exc_type.__name__ in exclusions or exc_name in exclusions or any(exc_name.startswith(e[:-1]) for e in exclusions if e.endswith('*')):
logger.info(
'Not capturing exception due to filters: %s', exc_type,
exc_info=sys.exc_info())
return
try:
client.captureException(exc_info=sys.exc_info(), request=request)
except Exception as exc:
try:
logger.exception('Unable to process log entry: %s' % (exc,))
except Exception as exc:
warnings.warn('Unable to process log entry: %s' % (exc,))
def register_handlers():
from django.core.signals import got_request_exception
# HACK: support Sentry's internal communication
if 'sentry' in settings.INSTALLED_APPS:
from django.db import transaction
# Django 1.6
if hasattr(transaction, 'atomic'):
commit_on_success = transaction.atomic
else:
commit_on_success = transaction.commit_on_success
@commit_on_success
def wrap_sentry(request, **kwargs):
if transaction.is_dirty():
transaction.rollback()
return sentry_exception_handler(request, **kwargs)
exception_handler = wrap_sentry
else:
exception_handler = sentry_exception_handler
# Connect to Django's internal signal handler
got_request_exception.connect(exception_handler, weak=False)
# If Celery is installed, register a signal handler
if 'djcelery' in settings.INSTALLED_APPS:
try:
# Celery < 2.5? is not supported
from raven.contrib.celery import (
register_signal, register_logger_signal)
except ImportError:
logger.exception('Failed to install Celery error handler')
else:
try:
register_signal(client)
except Exception:
logger.exception('Failed to install Celery error handler')
try:
ga = lambda x, d=None: getattr(settings, 'SENTRY_%s' % x, d)
options = getattr(settings, 'RAVEN_CONFIG', {})
loglevel = options.get('celery_loglevel',
ga('CELERY_LOGLEVEL', logging.ERROR))
register_logger_signal(client, loglevel=loglevel)
except Exception:
logger.exception('Failed to install Celery error handler')
def register_serializers():
# force import so serializers can call register
import raven.contrib.django.serializers # NOQA
if ('raven.contrib.django' in settings.INSTALLED_APPS
or 'raven.contrib.django.raven_compat' in settings.INSTALLED_APPS):
register_handlers()
register_serializers()
patch_cli_runner()

View File

@ -1,11 +0,0 @@
"""
raven.contrib.django.raven_compat
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
:copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
from __future__ import absolute_import
from raven.contrib.django import * # NOQA

View File

@ -1,11 +0,0 @@
"""
raven.contrib.django.raven_compat.handlers
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
:copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
from __future__ import absolute_import
from raven.contrib.django.handlers import * # NOQA

View File

@ -1,10 +0,0 @@
"""
raven.contrib.django.raven_compat.management
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
:copyright: (c) 2010-2013 by the Sentry Team, see AUTHORS for more details
:license: BSD, see LICENSE for more details.
"""
from __future__ import absolute_import, print_function
from raven.contrib.django.management import * # NOQA

View File

@ -1,10 +0,0 @@
"""
raven.contrib.django.raven_compat.management.commands
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
:copyright: (c) 2010-2013 by the Sentry Team, see AUTHORS for more details
:license: BSD, see LICENSE for more details.
"""
from __future__ import absolute_import, print_function
from raven.contrib.django.management.commands import * # NOQA

View File

@ -1,10 +0,0 @@
"""
raven.contrib.django.raven_compat.management.commands.raven
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
:copyright: (c) 2010-2013 by the Sentry Team, see AUTHORS for more details
:license: BSD, see LICENSE for more details.
"""
from __future__ import absolute_import, print_function
from raven.contrib.django.management.commands.raven import * # NOQA

View File

@ -1,11 +0,0 @@
"""
raven.contrib.django.raven_compat.middleware
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
:copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
from __future__ import absolute_import
from raven.contrib.django.middleware import * # NOQA

View File

@ -1,11 +0,0 @@
"""
raven.contrib.django.raven_compat.middleware.wsgi
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
:copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
from __future__ import absolute_import
from raven.contrib.django.middleware.wsgi import * # NOQA

View File

@ -1,11 +0,0 @@
"""
raven.contrib.django.raven_compat.models
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
:copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
from __future__ import absolute_import
from raven.contrib.django.models import * # NOQA

View File

@ -1,11 +0,0 @@
"""
raven.contrib.django.raven_compat.templatetags
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
:copyright: (c) 2010-2013 by the Sentry Team, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
from __future__ import absolute_import
from raven.contrib.django.templatetags import * # NOQA

View File

@ -1,11 +0,0 @@
"""
raven.contrib.django.raven_compat.templatetags.raven
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
:copyright: (c) 2010-2013 by the Sentry Team, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
from __future__ import absolute_import
from raven.contrib.django.templatetags.raven import * # NOQA

View File

@ -1,72 +0,0 @@
"""
raven.contrib.django.serializers
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
:copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
from __future__ import absolute_import
from __future__ import unicode_literals
from django.conf import settings
from django.http import HttpRequest
from django.utils.functional import Promise
from raven.utils.serializer import Serializer, register
from raven._compat import text_type
__all__ = ('PromiseSerializer',)
class PromiseSerializer(Serializer):
types = (Promise,)
def can(self, value):
if not super(PromiseSerializer, self).can(value):
return False
pre = value.__class__.__name__[1:]
if not (hasattr(value, '%s__func' % pre) or
hasattr(value, '%s__unicode_cast' % pre) or
hasattr(value, '%s__text_cast' % pre)):
return False
return True
def serialize(self, value, **kwargs):
# EPIC HACK
# handles lazy model instances (which are proxy values that don't
# easily give you the actual function)
pre = value.__class__.__name__[1:]
if hasattr(value, '%s__func' % pre):
value = getattr(value, '%s__func' % pre)(
*getattr(value, '%s__args' % pre),
**getattr(value, '%s__kw' % pre))
else:
return self.recurse(text_type(value))
return self.recurse(value, **kwargs)
register(PromiseSerializer)
class HttpRequestSerializer(Serializer):
types = (HttpRequest,)
def serialize(self, value, **kwargs):
return '<%s at 0x%s>' % (type(value).__name__, id(value))
register(HttpRequestSerializer)
if getattr(settings, 'DATABASES', None):
from django.db.models.query import QuerySet
class QuerySetSerializer(Serializer):
types = (QuerySet,)
def serialize(self, value, **kwargs):
qs_name = type(value).__name__
if value.model:
return '<%s: model=%s>' % (qs_name, value.model.__name__)
return '<%s: (Unbound)>' % (qs_name,)
register(QuerySetSerializer)

View File

@ -1,8 +0,0 @@
"""
raven.contrib.django.templatetags
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
:copyright: (c) 2010-2013 by the Sentry Team, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
from __future__ import absolute_import

View File

@ -1,19 +0,0 @@
"""
raven.contrib.django.templatetags.raven
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
:copyright: (c) 2010-2013 by the Sentry Team, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
from __future__ import absolute_import
from django import template
register = template.Library()
@register.simple_tag
def sentry_public_dsn(scheme=None):
from raven.contrib.django.models import client
return client.get_public_dsn(scheme) or ''

View File

@ -1,19 +0,0 @@
"""
raven.contrib.django.urls
~~~~~~~~~~~~~~~~~~~~~~~~~
:copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
from __future__ import absolute_import
try:
from django.conf.urls import patterns, url
except ImportError:
# for Django version less than 1.4
from django.conf.urls.defaults import patterns, url # NOQA
urlpatterns = patterns('',
url(r'^api/(?:(?P<project_id>[\w_-]+)/)?store/$', 'raven.contrib.django.views.report', name='raven-report'),
url(r'^report/', 'raven.contrib.django.views.report'),
)

View File

@ -1,84 +0,0 @@
"""
raven.contrib.django.utils
~~~~~~~~~~~~~~~~~~~~~~~~~~
:copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
from __future__ import absolute_import
import os
from django.conf import settings
def linebreak_iter(template_source):
yield 0
p = template_source.find('\n')
while p >= 0:
yield p + 1
p = template_source.find('\n', p + 1)
yield len(template_source) + 1
def get_data_from_template(source, debug=None):
if debug is not None:
start = debug['start']
end = debug['end']
source_lines = debug['source_lines']
lineno = debug['line']
filename = debug['name']
culprit = filename.split('/templates/')[-1]
elif source:
origin, (start, end) = source
filename = culprit = getattr(origin, 'loadname', None)
template_source = origin.reload()
lineno = None
upto = 0
source_lines = []
for num, next in enumerate(linebreak_iter(template_source)):
if start >= upto and end <= next:
lineno = num
source_lines.append(template_source[upto:next])
upto = next
if not source_lines or lineno is None:
return {}
else:
raise TypeError('Source or debug needed')
pre_context = source_lines[max(lineno - 3, 0):lineno]
post_context = source_lines[(lineno + 1):(lineno + 4)]
context_line = source_lines[lineno]
return {
'template': {
'filename': os.path.basename(filename),
'abs_path': filename,
'pre_context': pre_context,
'context_line': context_line,
'lineno': lineno,
'post_context': post_context,
},
'culprit': culprit,
}
def get_host(request):
"""
A reimplementation of Django's get_host, without the
SuspiciousOperation check.
"""
# We try three options, in order of decreasing preference.
if settings.USE_X_FORWARDED_HOST and (
'HTTP_X_FORWARDED_HOST' in request.META):
host = request.META['HTTP_X_FORWARDED_HOST']
elif 'HTTP_HOST' in request.META:
host = request.META['HTTP_HOST']
else:
# Reconstruct the host using the algorithm from PEP 333.
host = request.META['SERVER_NAME']
server_port = str(request.META['SERVER_PORT'])
if server_port != (request.is_secure() and '443' or '80'):
host = '%s:%s' % (host, server_port)
return host

View File

@ -1,108 +0,0 @@
"""
raven.contrib.django.views
~~~~~~~~~~~~~~~~~~~~~~~~~~
:copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
from __future__ import absolute_import
from functools import wraps
from django.conf import settings
from django.http import HttpResponse, HttpResponseForbidden, HttpResponseBadRequest
from django.views.decorators.cache import never_cache
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_http_methods
from raven._compat import string_types
from raven.contrib.django.models import client
from raven.utils import json
def is_valid_origin(origin):
if not settings.SENTRY_ALLOW_ORIGIN:
return False
if settings.SENTRY_ALLOW_ORIGIN == '*':
return True
if not origin:
return False
origin = origin.lower()
for value in settings.SENTRY_ALLOW_ORIGIN:
if isinstance(value, string_types):
if value.lower() == origin:
return True
else:
if value.match(origin):
return True
return False
def with_origin(func):
@wraps(func)
def wrapped(request, *args, **kwargs):
origin = request.META.get('HTTP_ORIGIN')
if not is_valid_origin(origin):
return HttpResponseForbidden()
response = func(request, *args, **kwargs)
response['Access-Control-Allow-Origin'] = origin
response['Access-Control-Allow-Methods'] = 'GET, POST, OPTIONS'
return response
return wrapped
def extract_auth_vars(request):
"""
raven-js will pass both Authorization and X-Sentry-Auth depending on the browser
and server configurations.
"""
if request.META.get('HTTP_X_SENTRY_AUTH', '').startswith('Sentry'):
return request.META['HTTP_X_SENTRY_AUTH']
elif request.META.get('HTTP_AUTHORIZATION', '').startswith('Sentry'):
return request.META['HTTP_AUTHORIZATION']
else:
# Try to construct from GET request
args = [
'%s=%s' % i
for i in request.GET.items()
if i[0].startswith('sentry_') and i[0] != 'sentry_data'
]
if args:
return 'Sentry %s' % ', '.join(args)
return None
@csrf_exempt
@require_http_methods(['GET', 'POST', 'OPTIONS'])
@never_cache
@with_origin
def report(request, project_id=None):
if request.method == 'OPTIONS':
return HttpResponse()
if request.method == 'POST':
if hasattr(request, 'body'):
data = request.body
else:
data = request.raw_post_data
else:
data = request.GET.get('sentry_data')
if not data:
return HttpResponseBadRequest()
try:
decoded = json.loads(data.decode('utf8'))
except json.JSONDecodeError:
return HttpResponseBadRequest()
client.send(auth_header=extract_auth_vars(request), **decoded)
return HttpResponse()

View File

@ -1,321 +0,0 @@
"""
raven.contrib.flask
~~~~~~~~~~~~~~~~~~~
:copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
from __future__ import absolute_import
try:
from flask_login import current_user
except ImportError:
has_flask_login = False
else:
has_flask_login = True
import sys
import os
import logging
from flask import request, current_app, g
from flask.signals import got_request_exception, request_finished
from werkzeug.exceptions import ClientDisconnected
from raven._compat import string_types
from raven.conf import setup_logging
from raven.base import Client
from raven.middleware import Sentry as SentryMiddleware
from raven.handlers.logging import SentryHandler
from raven.utils.compat import _urlparse
from raven.utils.encoding import to_unicode
from raven.utils.imports import import_string
from raven.utils.wsgi import get_headers, get_environ
def make_client(client_cls, app, dsn=None):
# TODO(dcramer): django and Flask share very similar concepts here, and
# should be refactored
transport = app.config.get('SENTRY_TRANSPORT')
if isinstance(transport, string_types):
transport = import_string(transport)
return client_cls(
dsn=dsn or app.config.get('SENTRY_DSN') or os.environ.get('SENTRY_DSN'),
transport=transport,
include_paths=set(app.config.get(
'SENTRY_INCLUDE_PATHS', [])) | set([app.import_name]),
exclude_paths=app.config.get('SENTRY_EXCLUDE_PATHS'),
name=app.config.get('SENTRY_NAME'),
site=app.config.get('SENTRY_SITE_NAME'),
processors=app.config.get('SENTRY_PROCESSORS'),
string_max_length=app.config.get('SENTRY_MAX_LENGTH_STRING'),
list_max_length=app.config.get('SENTRY_MAX_LENGTH_LIST'),
auto_log_stacks=app.config.get('SENTRY_AUTO_LOG_STACKS'),
tags=app.config.get('SENTRY_TAGS'),
release=app.config.get('SENTRY_RELEASE'),
extra={
'app': app,
},
)
class Sentry(object):
"""
Flask application for Sentry.
Look up configuration from ``os.environ['SENTRY_DSN']``::
>>> sentry = Sentry(app)
Pass an arbitrary DSN::
>>> sentry = Sentry(app, dsn='http://public:secret@example.com/1')
Pass an explicit client::
>>> sentry = Sentry(app, client=client)
Automatically configure logging::
>>> sentry = Sentry(app, logging=True, level=logging.ERROR)
Capture an exception::
>>> try:
>>> 1 / 0
>>> except ZeroDivisionError:
>>> sentry.captureException()
Capture a message::
>>> sentry.captureMessage('hello, world!')
By default, the Flask integration will do the following:
- Hook into the `got_request_exception` signal. This can be disabled by
passing `register_signal=False`.
- Wrap the WSGI application. This can be disabled by passing
`wrap_wsgi=False`.
- Capture information from Flask-Login (if available).
"""
# TODO(dcramer): the client isn't using local context and therefore
# gets shared by every app that does init on it
def __init__(self, app=None, client=None, client_cls=Client, dsn=None,
logging=False, logging_exclusions=None, level=logging.NOTSET,
wrap_wsgi=None, register_signal=True):
self.dsn = dsn
self.logging = logging
self.logging_exclusions = logging_exclusions
self.client_cls = client_cls
self.client = client
self.level = level
self.wrap_wsgi = wrap_wsgi
self.register_signal = register_signal
if app:
self.init_app(app)
@property
def last_event_id(self):
return getattr(self, '_last_event_id', None)
@last_event_id.setter
def last_event_id(self, value):
self._last_event_id = value
try:
g.sentry_event_id = value
except Exception:
pass
def handle_exception(self, *args, **kwargs):
if not self.client:
return
ignored_exc_type_list = current_app.config.get(
'RAVEN_IGNORE_EXCEPTIONS', [])
exc = sys.exc_info()[1]
if any((isinstance(exc, ignored_exc_type)
for ignored_exc_type in ignored_exc_type_list)):
return
self.captureException(exc_info=kwargs.get('exc_info'))
def get_user_info(self, request):
"""
Requires Flask-Login (https://pypi.python.org/pypi/Flask-Login/)
to be installed
and setup
"""
if not has_flask_login:
return
if not hasattr(current_app, 'login_manager'):
return
try:
is_authenticated = current_user.is_authenticated
except AttributeError:
# HACK: catch the attribute error thrown by flask-login is not attached
# > current_user = LocalProxy(lambda: _request_ctx_stack.top.user)
# E AttributeError: 'RequestContext' object has no attribute 'user'
return {}
if callable(is_authenticated):
is_authenticated = is_authenticated()
if not is_authenticated:
return {}
user_info = {
'id': current_user.get_id(),
}
if 'SENTRY_USER_ATTRS' in current_app.config:
for attr in current_app.config['SENTRY_USER_ATTRS']:
if hasattr(current_user, attr):
user_info[attr] = getattr(current_user, attr)
return user_info
def get_http_info(self, request):
"""
Determine how to retrieve actual data by using request.mimetype.
"""
if self.is_json_type(request.mimetype):
retriever = self.get_json_data
else:
retriever = self.get_form_data
return self.get_http_info_with_retriever(request, retriever)
def is_json_type(self, content_type):
return content_type == 'application/json'
def get_form_data(self, request):
return request.form
def get_json_data(self, request):
return request.data
def get_http_info_with_retriever(self, request, retriever=None):
"""
Exact method for getting http_info but with form data work around.
"""
if retriever is None:
retriever = self.get_form_data
urlparts = _urlparse.urlsplit(request.url)
try:
data = retriever(request)
except ClientDisconnected:
data = {}
return {
'url': '%s://%s%s' % (urlparts.scheme, urlparts.netloc, urlparts.path),
'query_string': urlparts.query,
'method': request.method,
'data': data,
'headers': dict(get_headers(request.environ)),
'env': dict(get_environ(request.environ)),
}
def before_request(self, *args, **kwargs):
self.last_event_id = None
try:
self.client.http_context(self.get_http_info(request))
except Exception as e:
self.client.logger.exception(to_unicode(e))
try:
self.client.user_context(self.get_user_info(request))
except Exception as e:
self.client.logger.exception(to_unicode(e))
def after_request(self, sender, response, *args, **kwargs):
if self.last_event_id:
response.headers['X-Sentry-ID'] = self.last_event_id
self.client.context.clear()
return response
def init_app(self, app, dsn=None, logging=None, level=None,
logging_exclusions=None, wrap_wsgi=None,
register_signal=None):
if dsn is not None:
self.dsn = dsn
if level is not None:
self.level = level
if wrap_wsgi is not None:
self.wrap_wsgi = wrap_wsgi
elif self.wrap_wsgi is None:
# Fix https://github.com/getsentry/raven-python/issues/412
# the gist is that we get errors twice in debug mode if we don't do this
if app and app.debug:
self.wrap_wsgi = False
else:
self.wrap_wsgi = True
if register_signal is not None:
self.register_signal = register_signal
if logging is not None:
self.logging = logging
if logging_exclusions is not None:
self.logging_exclusions = logging_exclusions
if not self.client:
self.client = make_client(self.client_cls, app, self.dsn)
if self.logging:
kwargs = {}
if self.logging_exclusions is not None:
kwargs['exclude'] = self.logging_exclusions
setup_logging(SentryHandler(self.client, level=self.level), **kwargs)
if self.wrap_wsgi:
app.wsgi_app = SentryMiddleware(app.wsgi_app, self.client)
app.before_request(self.before_request)
if self.register_signal:
got_request_exception.connect(self.handle_exception, sender=app)
request_finished.connect(self.after_request, sender=app)
if not hasattr(app, 'extensions'):
app.extensions = {}
app.extensions['sentry'] = self
def captureException(self, *args, **kwargs):
assert self.client, 'captureException called before application configured'
result = self.client.captureException(*args, **kwargs)
if result:
self.last_event_id = self.client.get_ident(result)
else:
self.last_event_id = None
return result
def captureMessage(self, *args, **kwargs):
assert self.client, 'captureMessage called before application configured'
result = self.client.captureMessage(*args, **kwargs)
if result:
self.last_event_id = self.client.get_ident(result)
else:
self.last_event_id = None
return result
def user_context(self, *args, **kwargs):
assert self.client, 'user_context called before application configured'
return self.client.user_context(*args, **kwargs)
def tags_context(self, *args, **kwargs):
assert self.client, 'tags_context called before application configured'
return self.client.tags_context(*args, **kwargs)
def extra_context(self, *args, **kwargs):
assert self.client, 'extra_context called before application configured'
return self.client.extra_context(*args, **kwargs)

View File

@ -1,16 +0,0 @@
"""
raven.contrib.paste
~~~~~~~~~~~~~~~~~~~
:copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
from __future__ import absolute_import
from raven.middleware import Sentry
from raven.base import Client
def sentry_filter_factory(app, global_conf, **kwargs):
client = Client(**kwargs)
return Sentry(app, client)

View File

@ -1,30 +0,0 @@
"""
raven.contrib.pylons
~~~~~~~~~~~~~~~~~~~~
:copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
from __future__ import absolute_import
from raven.middleware import Sentry as Middleware
from raven.base import Client
def list_from_setting(config, setting):
value = config.get(setting)
if not value:
return None
return value.split()
class Sentry(Middleware):
def __init__(self, app, config, client_cls=Client):
client = client_cls(
dsn=config.get('sentry.dsn'),
name=config.get('sentry.name'),
site=config.get('sentry.site'),
include_paths=list_from_setting(config, 'sentry.include_paths'),
exclude_paths=list_from_setting(config, 'sentry.exclude_paths'),
)
super(Sentry, self).__init__(app, client)

View File

@ -1,247 +0,0 @@
"""
raven.contrib.tornado
~~~~~~~~~~~~~~~~~~~~~
:copyright: (c) 2012 by the Sentry Team, see AUTHORS for more details
:license: BSD, see LICENSE for more details
"""
from __future__ import absolute_import
from functools import partial
from tornado import ioloop
from tornado.httpclient import AsyncHTTPClient, HTTPError
from tornado.web import HTTPError as WebHTTPError
from raven.base import Client
class AsyncSentryClient(Client):
"""A mixin class that could be used along with request handlers to
asynchronously send errors to sentry. The client also captures the
information from the request handlers
"""
def __init__(self, *args, **kwargs):
self.validate_cert = kwargs.pop('validate_cert', True)
super(AsyncSentryClient, self).__init__(*args, **kwargs)
def capture(self, *args, **kwargs):
"""
Takes the same arguments as the super function in :py:class:`Client`
and extracts the keyword argument callback which will be called on
asynchronous sending of the request
:return: a 32-length string identifying this event
"""
if not self.is_enabled():
return
data = self.build_msg(*args, **kwargs)
self.send(callback=kwargs.get('callback', None), **data)
return (data['event_id'],)
def send(self, auth_header=None, callback=None, **data):
"""
Serializes the message and passes the payload onto ``send_encoded``.
"""
message = self.encode(data)
return self.send_encoded(message, auth_header=auth_header, callback=callback)
def send_remote(self, url, data, headers=None, callback=None):
if headers is None:
headers = {}
if not self.state.should_try():
data = self.decode(data)
self._log_failed_submission(data)
return
future = self._send_remote(
url=url, data=data, headers=headers, callback=callback
)
ioloop.IOLoop.current().add_future(future, partial(self._handle_result, url, data))
return future
def _handle_result(self, url, data, future):
try:
future.result()
except HTTPError as e:
data = self.decode(data)
self._failed_send(e, url, data)
except Exception as e:
data = self.decode(data)
self._failed_send(e, url, data)
else:
self.state.set_success()
def _send_remote(self, url, data, headers=None, callback=None):
"""
Initialise a Tornado AsyncClient and send the reuqest to the sentry
server. If the callback is a callable, it will be called with the
response.
"""
if headers is None:
headers = {}
return AsyncHTTPClient().fetch(
url, callback, method="POST", body=data, headers=headers,
validate_cert=self.validate_cert
)
class SentryMixin(object):
"""
A mixin class that extracts information from the Request in a Request
Handler to capture and send to sentry. This mixin class is designed to be
used along with `tornado.web.RequestHandler`
.. code-block:: python
:emphasize-lines: 6
class MyRequestHandler(SentryMixin, tornado.web.RequestHandler):
def get(self):
try:
fail()
except Exception as e:
self.captureException()
While the above example would result in sequential execution, an example
for asynchronous use would be
.. code-block:: python
:emphasize-lines: 6
class MyRequestHandler(SentryMixin, tornado.web.RequestHandler):
@tornado.web.asynchronous
@tornado.gen.engine
def get(self):
# Do something and record a message in sentry
response = yield tornado.gen.Task(
self.captureMessage, "Did something really important"
)
self.write("Your request to do something important is done")
self.finish()
The mixin assumes that the application will have an attribute called
`sentry_client`, which should be an instance of
:py:class:`AsyncSentryClient`. This can be changed by implementing your
own get_sentry_client method on your request handler.
"""
def get_sentry_client(self):
"""
Returns the sentry client configured in the application. If you need
to change the behaviour to do something else to get the client, then
subclass this method
"""
return self.application.sentry_client
def get_sentry_data_from_request(self):
"""
Extracts the data required for 'sentry.interfaces.Http' from the
current request being handled by the request handler
:param return: A dictionary.
"""
return {
'request': {
'url': self.request.full_url(),
'method': self.request.method,
'data': self.request.body,
'query_string': self.request.query,
'cookies': self.request.headers.get('Cookie', None),
'headers': dict(self.request.headers),
}
}
def get_sentry_user_info(self):
"""
Data for sentry.interfaces.User
Default implementation only sends `is_authenticated` by checking if
`tornado.web.RequestHandler.get_current_user` tests postitively for on
Truth calue testing
"""
try:
user = self.get_current_user()
except Exception:
return {}
return {
'user': {
'is_authenticated': True if user else False
}
}
def get_sentry_extra_info(self):
"""
Subclass and implement this method if you need to send any extra
information
"""
return {
'extra': {
}
}
def get_default_context(self):
data = {}
# Update request data
data.update(self.get_sentry_data_from_request())
# update user data
data.update(self.get_sentry_user_info())
# Update extra data
data.update(self.get_sentry_extra_info())
return data
def _capture(self, call_name, data=None, **kwargs):
if data is None:
data = self.get_default_context()
else:
default_context = self.get_default_context()
if isinstance(data, dict):
default_context.update(data)
else:
default_context['extra']['extra_data'] = data
data = default_context
client = self.get_sentry_client()
return getattr(client, call_name)(data=data, **kwargs)
def captureException(self, exc_info=None, **kwargs):
return self._capture('captureException', exc_info=exc_info, **kwargs)
def captureMessage(self, message, **kwargs):
return self._capture('captureMessage', message=message, **kwargs)
def log_exception(self, typ, value, tb):
"""Override implementation to report all exceptions to sentry.
log_exception() is added in Tornado v3.1.
"""
rv = super(SentryMixin, self).log_exception(typ, value, tb)
# Do not capture tornado.web.HTTPErrors outside the 500 range.
if isinstance(value, WebHTTPError) and (value.status_code < 500 or value.status_code > 599):
return rv
self.captureException(exc_info=(typ, value, tb))
return rv
def send_error(self, status_code=500, **kwargs):
"""Override implementation to report all exceptions to sentry, even
after self.flush() or self.finish() is called, for pre-v3.1 Tornado.
"""
if hasattr(super(SentryMixin, self), 'log_exception'):
return super(SentryMixin, self).send_error(status_code, **kwargs)
else:
rv = super(SentryMixin, self).send_error(status_code, **kwargs)
if 500 <= status_code <= 599:
self.captureException(exc_info=kwargs.get('exc_info'))
return rv

View File

@ -1,77 +0,0 @@
"""
raven.contrib.webpy
~~~~~~~~~~~~~~~~~~~
:copyright: (c) 2013 by the Sentry Team, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
from __future__ import absolute_import
import sys
import web
from raven.conf import setup_logging
from raven.handlers.logging import SentryHandler
from raven.contrib.webpy.utils import get_data_from_request
class SentryApplication(web.application):
"""
Web.py application for Sentry.
>>> sentry = Sentry(client, mapping=urls, fvars=globals())
Automatically configure logging::
>>> sentry = Sentry(client, logging=True, mapping=urls, fvars=globals())
Capture an exception::
>>> try:
>>> 1 / 0
>>> except ZeroDivisionError:
>>> sentry.captureException()
Capture a message::
>>> sentry.captureMessage('hello, world!')
"""
def __init__(self, client, logging=False, **kwargs):
self.client = client
self.logging = logging
if self.logging:
setup_logging(SentryHandler(self.client))
web.application.__init__(self, **kwargs)
def handle_exception(self, *args, **kwargs):
self.client.captureException(
exc_info=kwargs.get('exc_info'),
data=get_data_from_request(),
extra={
'app': self,
},
)
def handle(self):
try:
return web.application.handle(self)
except:
self.handle_exception(exc_info=sys.exc_info())
raise
def captureException(self, *args, **kwargs):
assert self.client, 'captureException called before application configured'
data = kwargs.get('data')
if data is None:
kwargs['data'] = get_data_from_request()
return self.client.captureException(*args, **kwargs)
def captureMessage(self, *args, **kwargs):
assert self.client, 'captureMessage called before application configured'
data = kwargs.get('data')
if data is None:
kwargs['data'] = get_data_from_request()
return self.client.captureMessage(*args, **kwargs)

View File

@ -1,26 +0,0 @@
"""
raven.contrib.webpy.utils
~~~~~~~~~~~~~~~~~~~~~~~~~~
:copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
from __future__ import absolute_import
import web
from raven.utils.wsgi import get_headers, get_environ
def get_data_from_request():
"""Returns request data extracted from web.ctx."""
return {
'request': {
'url': '%s://%s%s' % (web.ctx['protocol'], web.ctx['host'], web.ctx['path']),
'query_string': web.ctx.query,
'method': web.ctx.method,
'data': web.data(),
'headers': dict(get_headers(web.ctx.environ)),
'env': dict(get_environ(web.ctx.environ)),
}
}

View File

@ -1,70 +0,0 @@
"""
raven.contrib.zerorpc
~~~~~~~~~~~~~~~~~~~~~
:copyright: (c) 2010-2013 by the Sentry Team, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
from __future__ import absolute_import
import inspect
from raven.base import Client
class SentryMiddleware(object):
"""Sentry/Raven middleware for ZeroRPC.
>>> import zerorpc
>>> from raven.contrib.zerorpc import SentryMiddleware
>>> sentry = SentryMiddleware(dsn='udp://..../')
>>> zerorpc.Context.get_instance().register_middleware(sentry)
Exceptions detected server-side in ZeroRPC will be submitted to Sentry (and
propagated to the client as well).
"""
def __init__(self, hide_zerorpc_frames=True, client=None, **kwargs):
"""Create a middleware object that can be injected in a ZeroRPC server.
- hide_zerorpc_frames: modify the exception stacktrace to remove the
internal zerorpc frames (True by default to make
the stacktrace as readable as possible);
- client: use an existing raven.Client object, otherwise one will be
instantiated from the keyword arguments.
"""
self._sentry_client = client or Client(**kwargs)
self._hide_zerorpc_frames = hide_zerorpc_frames
def server_inspect_exception(self, req_event, rep_event, task_ctx, exc_info):
"""Called when an exception has been raised in the code run by ZeroRPC"""
# Hide the zerorpc internal frames for readability, for a REQ/REP or
# REQ/STREAM server the frames to hide are:
# - core.ServerBase._async_task
# - core.Pattern*.process_call
# - core.DecoratorBase.__call__
#
# For a PUSH/PULL or PUB/SUB server the frame to hide is:
# - core.Puller._receiver
if self._hide_zerorpc_frames:
traceback = exc_info[2]
while traceback:
zerorpc_frame = traceback.tb_frame
zerorpc_frame.f_locals['__traceback_hide__'] = True
frame_info = inspect.getframeinfo(zerorpc_frame)
# Is there a better way than this (or looking up the filenames
# or hardcoding the number of frames to skip) to know when we
# are out of zerorpc?
if frame_info.function == '__call__' \
or frame_info.function == '_receiver':
break
traceback = traceback.tb_next
self._sentry_client.captureException(
exc_info,
extra=task_ctx
)

View File

@ -1,107 +0,0 @@
# -*- coding: utf-8 -*-
"""
raven.contrib.zope
~~~~~~~~~~~~~~~~~~
:copyright: (c) 2010-2013 by the Sentry Team, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
from __future__ import absolute_import
from inspect import getouterframes, currentframe, getinnerframes
from raven.handlers.logging import SentryHandler
from ZConfig.components.logger.factory import Factory
import logging
from AccessControl.users import nobody
from raven.utils.stacks import iter_stack_frames
logger = logging.getLogger(__name__)
class ZopeSentryHandlerFactory(Factory):
def getLevel(self):
return self.section.level
def create(self):
return ZopeSentryHandler(**self.section.__dict__)
def __init__(self, section):
Factory.__init__(self)
self.section = section
class ZopeSentryHandler(SentryHandler):
'''
Zope unfortunately eats the stack trace information.
To get the stack trace information and other useful information
from the request object, this class looks into the different stack
frames when the emit method is invoked.
'''
def __init__(self, *args, **kw):
super(ZopeSentryHandler, self).__init__(*args, **kw)
level = kw.get('level', logging.ERROR)
self.setLevel(level)
def can_record(self, record):
return not (
record.name == 'raven' or
record.name.startswith(('sentry.errors', 'raven.'))
)
def emit(self, record):
if record.levelno <= logging.ERROR and self.can_record(record):
request = None
exc_info = None
for frame_info in getouterframes(currentframe()):
frame = frame_info[0]
if not request:
request = frame.f_locals.get('request', None)
if not request:
view = frame.f_locals.get('self', None)
try:
request = getattr(view, 'request', None)
except RuntimeError:
request = None
if not exc_info:
exc_info = frame.f_locals.get('exc_info', None)
if not hasattr(exc_info, '__getitem__'):
exc_info = None
if request and exc_info:
break
if exc_info:
record.exc_info = exc_info
record.stack = \
iter_stack_frames(getinnerframes(exc_info[2]))
if request:
try:
body_pos = request.stdin.tell()
request.stdin.seek(0)
body = request.stdin.read()
request.stdin.seek(body_pos)
http = dict(headers=request.environ,
url=request.getURL(),
method=request.method,
host=request.environ.get('REMOTE_ADDR',
''), data=body)
if 'HTTP_USER_AGENT' in http['headers']:
if 'User-Agent' not in http['headers']:
http['headers']['User-Agent'] = \
http['headers']['HTTP_USER_AGENT']
if 'QUERY_STRING' in http['headers']:
http['query_string'] = http['headers']['QUERY_STRING']
setattr(record, 'request', http)
user = request.get('AUTHENTICATED_USER', None)
if user is not None and user != nobody:
user_dict = {
'id': user.getId(),
'email': user.getProperty('email') or '',
}
else:
user_dict = {}
setattr(record, 'user', user_dict)
except (AttributeError, KeyError):
logger.warning('Could not extract data from request', exc_info=True)
return super(ZopeSentryHandler, self).emit(record)

View File

@ -1,23 +0,0 @@
<component>
<description>
</description>
<import package="ZConfig.components.logger" file="abstract.xml"/>
<sectiontype name="sentry"
datatype="raven.contrib.zope.ZopeSentryHandlerFactory"
implements="ZConfig.logger.handler"
extends="ZConfig.logger.base-log-handler">
<key name="include_paths" required="no" datatype="string-list"/>
<key name="exclude_paths" required="no" datatype="string-list"/>
<key name="timeout" required="no" datatype="integer"/>
<key name="name" required="no"/>
<key name="auto_log_stacks" required="no" datatype="boolean"/>
<key name="string_max_length" required="no" datatype="integer"/>
<key name="list_max_length" required="no" datatype="integer"/>
<key name="site" required="no"/>
<key name="processors" required="no" datatype="string-list"/>
<key name="project" required="no"/>
<key name="dsn" required="no"/>
</sectiontype>
</component>

File diff suppressed because it is too large Load Diff

View File

@ -1,135 +0,0 @@
"""
raven.events
~~~~~~~~~~~~
:copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
from __future__ import absolute_import
import logging
import sys
from raven.utils.encoding import to_unicode
from raven.utils.stacks import get_stack_info, iter_traceback_frames
__all__ = ('BaseEvent', 'Exception', 'Message', 'Query')
class BaseEvent(object):
def __init__(self, client):
self.client = client
self.logger = logging.getLogger(__name__)
def to_string(self, data):
raise NotImplementedError
def capture(self, **kwargs):
return {
}
def transform(self, value):
return self.client.transform(value)
class Exception(BaseEvent):
"""
Exceptions store the following metadata:
- value: 'My exception value'
- type: 'ClassName'
- module '__builtin__' (i.e. __builtin__.TypeError)
- frames: a list of serialized frames (see _get_traceback_frames)
"""
name = 'exception'
def to_string(self, data):
exc = data[self.name]['values'][0]
if exc['value']:
return '%s: %s' % (exc['type'], exc['value'])
return exc['type']
def capture(self, exc_info=None, **kwargs):
if not exc_info or exc_info is True:
exc_info = sys.exc_info()
if not exc_info:
raise ValueError('No exception found')
exc_type, exc_value, exc_traceback = exc_info
try:
stack_info = get_stack_info(
iter_traceback_frames(exc_traceback),
transformer=self.transform,
capture_locals=self.client.capture_locals,
)
exc_module = getattr(exc_type, '__module__', None)
if exc_module:
exc_module = str(exc_module)
exc_type = getattr(exc_type, '__name__', '<unknown>')
return {
'level': kwargs.get('level', logging.ERROR),
self.name: {
'values': [{
'value': to_unicode(exc_value),
'type': str(exc_type),
'module': to_unicode(exc_module),
'stacktrace': stack_info,
}],
},
}
finally:
try:
del exc_type, exc_value, exc_traceback
except Exception as e:
self.logger.exception(e)
class Message(BaseEvent):
"""
Messages store the following metadata:
- message: 'My message from %s about %s'
- params: ('foo', 'bar')
"""
name = 'sentry.interfaces.Message'
def to_string(self, data):
return data[self.name]['message']
def capture(self, message, params=(), formatted=None, **kwargs):
message = to_unicode(message)
data = {
self.name: {
'message': message,
'params': self.transform(params),
},
}
if 'message' not in data:
data['message'] = formatted or message
return data
class Query(BaseEvent):
"""
Messages store the following metadata:
- query: 'SELECT * FROM table'
- engine: 'postgesql_psycopg2'
"""
name = 'sentry.interfaces.Query'
def to_string(self, data):
sql = data[self.name]
return sql['query']
def capture(self, query, engine, **kwargs):
return {
self.name: {
'query': to_unicode(query),
'engine': str(engine),
}
}

View File

@ -1,30 +0,0 @@
from __future__ import absolute_import
from raven._compat import text_type
class APIError(Exception):
def __init__(self, message, code=0):
self.code = code
self.message = message
def __unicode__(self):
return text_type("%s: %s" % (self.message, self.code))
class RateLimited(APIError):
def __init__(self, message, retry_after=0):
self.retry_after = retry_after
super(RateLimited, self).__init__(message, 429)
class InvalidGitRepository(Exception):
pass
class ConfigurationError(ValueError):
pass
class InvalidDsn(ConfigurationError):
pass

Some files were not shown because too many files have changed in this diff Show More