Compare commits
No commits in common. "master" and "debian" have entirely different histories.
|
@ -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
|
|
@ -1,3 +0,0 @@
|
|||
[submodule "docs/_sentryext"]
|
||||
path = docs/_sentryext
|
||||
url = https://github.com/getsentry/sentry-doc-support.git
|
75
.travis.yml
75
.travis.yml
|
@ -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
|
500
CHANGES
500
CHANGES
|
@ -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
12
LICENSE
|
@ -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.
|
|
@ -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 *~
|
27
Makefile
27
Makefile
|
@ -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
|
24
README.rst
24
README.rst
|
@ -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>`_
|
||||
|
13
ci/setup
13
ci/setup
|
@ -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
|
76
conftest.py
76
conftest.py
|
@ -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=['*'],
|
||||
)
|
130
docs/Makefile
130
docs/Makefile
|
@ -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
|
Binary file not shown.
Before Width: | Height: | Size: 10 KiB |
|
@ -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">
|
||||
© Copyright {{ copyright }}.
|
||||
</div>
|
||||
{%- endblock %}
|
|
@ -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>
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
[theme]
|
||||
inherit = basic
|
||||
stylesheet = flasky.css
|
||||
pygments_style = flask_theme_support.FlaskyStyle
|
||||
|
||||
[options]
|
||||
touch_icon =
|
|
@ -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.
|
154
docs/api.rst
154
docs/api.rst
|
@ -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.
|
231
docs/conf.py
231
docs/conf.py
|
@ -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()
|
|
@ -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
|
131
docs/index.rst
131
docs/index.rst
|
@ -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)
|
|
@ -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!')
|
|
@ -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')
|
|
@ -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
|
|
@ -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.
|
|
@ -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
|
|
@ -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.
|
||||
...
|
|
@ -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", ...))
|
|
@ -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.
|
|
@ -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 = ()``.
|
|
@ -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/
|
|
@ -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")
|
|
@ -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.
|
|
@ -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.
|
|
@ -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>
|
155
docs/make.bat
155
docs/make.bat
|
@ -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
|
|
@ -1,9 +0,0 @@
|
|||
Supported Platforms
|
||||
===================
|
||||
|
||||
- Python 2.6
|
||||
- Python 2.7
|
||||
- Python 3.2
|
||||
- Python 3.3
|
||||
- PyPy
|
||||
- Google App Engine
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
|
@ -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!
|
|
@ -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
|
|
@ -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())
|
|
@ -1 +0,0 @@
|
|||
../docs/_sentryext/verify-docs.py
|
|
@ -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'
|
177
raven/_compat.py
177
raven/_compat.py
|
@ -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,), {})
|
789
raven/base.py
789
raven/base.py
|
@ -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
|
|
@ -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
|
|
@ -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')
|
|
@ -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,
|
||||
)
|
|
@ -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()
|
|
@ -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
|
|
@ -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)
|
|
@ -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)
|
|
@ -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
|
|
@ -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)
|
|
@ -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
|
|
@ -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'
|
|
@ -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)
|
|
@ -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.")
|
|
@ -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
|
|
@ -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
|
|
@ -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)
|
|
@ -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',)
|
|
@ -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
|
|
@ -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
|
|
@ -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)
|
|
@ -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
|
|
@ -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
|
|
@ -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()
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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)
|
|
@ -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
|
|
@ -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 ''
|
|
@ -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'),
|
||||
)
|
|
@ -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
|
|
@ -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()
|
|
@ -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)
|
|
@ -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)
|
|
@ -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)
|
|
@ -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
|
|
@ -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)
|
|
@ -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)),
|
||||
}
|
||||
}
|
|
@ -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
|
||||
)
|
|
@ -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)
|
|
@ -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
135
raven/events.py
135
raven/events.py
|
@ -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),
|
||||
}
|
||||
}
|
|
@ -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
Loading…
Reference in New Issue