├── tests ├── utils │ ├── __init__.py │ ├── json │ │ ├── __init__.py │ │ └── tests.py │ ├── wsgi │ │ ├── __init__.py │ │ └── tests.py │ ├── encoding │ │ └── __init__.py │ ├── stacks │ │ ├── __init__.py │ │ └── tests.py │ ├── compat.py │ ├── tests.py │ └── lru_tests.py ├── client │ └── __init__.py ├── config │ ├── __init__.py │ └── tests.py ├── contrib │ ├── __init__.py │ ├── async │ │ ├── tests.py │ │ └── __init__.py │ ├── celery │ │ ├── __init__.py │ │ └── tests.py │ ├── django │ │ ├── __init__.py │ │ ├── testapp │ │ │ ├── templates │ │ │ │ ├── 404.html │ │ │ │ ├── error.html │ │ │ │ ├── jinja2 │ │ │ │ │ └── jinja2_template.html │ │ │ │ └── list_users.html │ │ │ ├── __init__.py │ │ │ ├── models.py │ │ │ ├── celery.py │ │ │ ├── middleware.py │ │ │ ├── urls.py │ │ │ └── views.py │ │ ├── fake2 │ │ │ └── __init__.py │ │ └── fake1 │ │ │ └── __init__.py │ ├── flask │ │ ├── __init__.py │ │ └── templates │ │ │ └── users.html │ ├── pylons │ │ ├── __init__.py │ │ └── tests.py │ ├── twisted │ │ ├── __init__.py │ │ └── tests.py │ └── zerorpc │ │ ├── __init__.py │ │ └── zeropc_tests.py ├── events │ ├── __init__.py │ └── tests.py ├── handlers │ ├── __init__.py │ ├── logbook │ │ ├── __init__.py │ │ └── logbook_tests.py │ └── logging │ │ └── __init__.py ├── middleware │ ├── __init__.py │ └── tests.py ├── processors │ └── __init__.py ├── transports │ ├── __init__.py │ ├── test_http.py │ └── test_urllib3.py ├── instrumentation │ ├── __init__.py │ ├── django_tests │ │ └── __init__.py │ ├── jinja2_tests │ │ ├── __init__.py │ │ ├── mytemplate.html │ │ └── jinja2_tests.py │ ├── base_tests.py │ ├── botocore_tests.py │ ├── dbapi2_tests.py │ ├── python_memcached_tests.py │ ├── sqlite_tests.py │ ├── urllib3_tests.py │ ├── requests_tests.py │ └── mysql_tests.py ├── __init__.py ├── helpers.py └── asyncio │ ├── test_asyncio_http.py │ └── test_asyncio_client.py ├── opbeat ├── contrib │ ├── __init__.py │ ├── django │ │ ├── management │ │ │ ├── __init__.py │ │ │ └── commands │ │ │ │ └── __init__.py │ │ ├── apps.py │ │ ├── __init__.py │ │ ├── celery │ │ │ ├── __init__.py │ │ │ └── models.py │ │ ├── middleware │ │ │ └── wsgi.py │ │ ├── handlers.py │ │ └── utils.py │ ├── asyncio │ │ ├── __init__.py │ │ └── client.py │ ├── paste.py │ ├── flask │ │ └── utils.py │ ├── rq │ │ └── __init__.py │ ├── twisted │ │ └── __init__.py │ ├── pylons │ │ └── __init__.py │ ├── celery │ │ └── __init__.py │ └── zerorpc │ │ └── __init__.py ├── instrumentation │ ├── __init__.py │ ├── packages │ │ ├── __init__.py │ │ ├── django │ │ │ ├── __init__.py │ │ │ └── template.py │ │ ├── jinja2.py │ │ ├── zlib.py │ │ ├── mysql.py │ │ ├── botocore.py │ │ ├── urllib3.py │ │ ├── pylibmc.py │ │ ├── redis.py │ │ ├── sqlite.py │ │ ├── requests.py │ │ ├── python_memcached.py │ │ ├── psycopg2.py │ │ └── pymongo.py │ ├── control.py │ └── register.py ├── version.py ├── transport │ ├── __init__.py │ ├── exceptions.py │ ├── base.py │ ├── asyncio.py │ ├── http_urllib3.py │ └── http.py ├── handlers │ ├── __init__.py │ ├── logbook.py │ └── logging.py ├── __init__.py ├── utils │ ├── wrapt │ │ ├── __init__.py │ │ ├── LICENSE │ │ └── arguments.py │ ├── module_import.py │ ├── compat.py │ ├── deprecation.py │ ├── opbeat_json.py │ ├── __init__.py │ ├── lru.py │ └── wsgi.py ├── conf │ ├── __init__.py │ └── defaults.py ├── middleware.py ├── processors.py └── events.py ├── test_requirements ├── requirements-cpython.txt ├── requirements-pypy.txt ├── requirements-python-3.txt ├── requirements-python-2.txt ├── requirements-asyncio.txt ├── requirements-flask-0.10.txt ├── requirements-flask-0.11.txt ├── requirements-flask-0.12.txt ├── requirements-zerorpc.txt ├── requirements-django-2.0.txt ├── requirements-django-1.10.txt ├── requirements-django-1.11.txt ├── requirements-django-1.4.txt ├── requirements-django-1.5.txt ├── requirements-django-1.6.txt ├── requirements-django-1.7.txt ├── requirements-django-1.8.txt ├── requirements-django-1.9.txt ├── requirements-django-master.txt └── requirements-base.txt ├── AUTHORS ├── MANIFEST.in ├── docs ├── config │ ├── index.rst │ ├── wsgi.rst │ ├── logbook.rst │ ├── zerorpc.rst │ ├── pylons.rst │ ├── pyramid.rst │ ├── flask.rst │ ├── logging.rst │ └── other.rst ├── install │ └── index.rst ├── index.rst ├── contributing │ └── index.rst ├── make.bat └── Makefile ├── travis ├── run_docker.sh ├── build_manylinux_wheels.sh └── run_tests.sh ├── Makefile ├── .gitignore ├── tox.ini ├── setup.cfg ├── README.rst ├── LICENSE ├── .travis.yml └── conftest.py /tests/utils/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /opbeat/contrib/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/client/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/config/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/contrib/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/contrib/async/tests.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/events/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/handlers/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/middleware/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/processors/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/transports/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/utils/json/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/utils/wsgi/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/contrib/async/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/contrib/celery/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/contrib/django/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/contrib/flask/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/contrib/pylons/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/contrib/twisted/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/contrib/zerorpc/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/instrumentation/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/utils/encoding/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/utils/stacks/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /opbeat/instrumentation/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | VERSION = 1.0 2 | -------------------------------------------------------------------------------- /tests/handlers/logbook/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/handlers/logging/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /opbeat/instrumentation/packages/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /opbeat/contrib/django/management/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/contrib/django/testapp/templates/404.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/instrumentation/django_tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/instrumentation/jinja2_tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /opbeat/contrib/django/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /opbeat/instrumentation/packages/django/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test_requirements/requirements-cpython.txt: -------------------------------------------------------------------------------- 1 | psycopg2 -------------------------------------------------------------------------------- /test_requirements/requirements-pypy.txt: -------------------------------------------------------------------------------- 1 | psycopg2cffi -------------------------------------------------------------------------------- /test_requirements/requirements-python-3.txt: -------------------------------------------------------------------------------- 1 | python3-memcached -------------------------------------------------------------------------------- /opbeat/contrib/asyncio/__init__.py: -------------------------------------------------------------------------------- 1 | from .client import Client 2 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | http://github.com/dcramer/raven/contributors 2 | 3 | and Opbeat -------------------------------------------------------------------------------- /test_requirements/requirements-python-2.txt: -------------------------------------------------------------------------------- 1 | unittest2 2 | python-memcached -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include setup.py README.rst MANIFEST.in LICENSE 2 | global-exclude *~ 3 | -------------------------------------------------------------------------------- /opbeat/version.py: -------------------------------------------------------------------------------- 1 | __version__ = (3, 6, 1) 2 | VERSION = '.'.join(map(str, __version__)) 3 | -------------------------------------------------------------------------------- /test_requirements/requirements-asyncio.txt: -------------------------------------------------------------------------------- 1 | aiohttp 2 | pytest-asyncio 3 | pytest-mock 4 | -------------------------------------------------------------------------------- /test_requirements/requirements-flask-0.10.txt: -------------------------------------------------------------------------------- 1 | Flask>=0.10,<0.11 2 | -r requirements-base.txt 3 | -------------------------------------------------------------------------------- /test_requirements/requirements-flask-0.11.txt: -------------------------------------------------------------------------------- 1 | Flask>=0.11,<0.12 2 | -r requirements-base.txt 3 | -------------------------------------------------------------------------------- /test_requirements/requirements-flask-0.12.txt: -------------------------------------------------------------------------------- 1 | Flask>=0.12,<0.13 2 | -r requirements-base.txt 3 | -------------------------------------------------------------------------------- /test_requirements/requirements-zerorpc.txt: -------------------------------------------------------------------------------- 1 | pyzmq==13.1.0 2 | gevent==1.1b1 3 | zerorpc>=0.4.0,<0.5 -------------------------------------------------------------------------------- /tests/contrib/django/fake2/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | class FakeException(BaseException): 4 | pass 5 | -------------------------------------------------------------------------------- /test_requirements/requirements-django-2.0.txt: -------------------------------------------------------------------------------- 1 | Django>=2.0,<2.1 2 | django-celery 3 | -r requirements-base.txt 4 | -------------------------------------------------------------------------------- /tests/contrib/django/testapp/templates/error.html: -------------------------------------------------------------------------------- 1 | Foo Bar 2 | Baz 3 | {% invalid template tag %} 4 | 42 5 | 4711 -------------------------------------------------------------------------------- /test_requirements/requirements-django-1.10.txt: -------------------------------------------------------------------------------- 1 | Django>=1.10,<1.11 2 | django-celery 3 | -r requirements-base.txt 4 | -------------------------------------------------------------------------------- /test_requirements/requirements-django-1.11.txt: -------------------------------------------------------------------------------- 1 | Django>=1.11b1,<1.12 2 | django-celery 3 | -r requirements-base.txt 4 | -------------------------------------------------------------------------------- /test_requirements/requirements-django-1.4.txt: -------------------------------------------------------------------------------- 1 | Django>=1.4.21,<1.5 2 | django-celery 3 | -r requirements-base.txt 4 | -------------------------------------------------------------------------------- /test_requirements/requirements-django-1.5.txt: -------------------------------------------------------------------------------- 1 | Django>=1.5.12,<1.6 2 | django-celery 3 | -r requirements-base.txt 4 | -------------------------------------------------------------------------------- /test_requirements/requirements-django-1.6.txt: -------------------------------------------------------------------------------- 1 | Django>=1.6.11,<1.7 2 | django-celery 3 | -r requirements-base.txt 4 | -------------------------------------------------------------------------------- /test_requirements/requirements-django-1.7.txt: -------------------------------------------------------------------------------- 1 | Django>=1.7.9,<1.8 2 | django-celery 3 | -r requirements-base.txt 4 | -------------------------------------------------------------------------------- /test_requirements/requirements-django-1.8.txt: -------------------------------------------------------------------------------- 1 | Django>=1.8.3,<1.9 2 | django-celery 3 | -r requirements-base.txt 4 | -------------------------------------------------------------------------------- /test_requirements/requirements-django-1.9.txt: -------------------------------------------------------------------------------- 1 | Django>=1.9,<1.10 2 | django-celery 3 | -r requirements-base.txt 4 | -------------------------------------------------------------------------------- /tests/contrib/django/testapp/templates/jinja2/jinja2_template.html: -------------------------------------------------------------------------------- 1 | 2 | {% macro foo() %}42{% endmacro %}23 3 | -------------------------------------------------------------------------------- /tests/instrumentation/jinja2_tests/mytemplate.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
Hello
4 |
5 | 6 | 7 | -------------------------------------------------------------------------------- /docs/config/index.rst: -------------------------------------------------------------------------------- 1 | Configuration 2 | ============= 3 | 4 | .. csv-table:: 5 | :class: page-info 6 | 7 | "Page updated: 23rd July 2013", "" -------------------------------------------------------------------------------- /test_requirements/requirements-django-master.txt: -------------------------------------------------------------------------------- 1 | -e git://github.com/django/django.git@master#egg=Django 2 | django-celery 3 | -r requirements-base.txt 4 | -------------------------------------------------------------------------------- /tests/contrib/django/testapp/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from __future__ import absolute_import 4 | 5 | from .celery import app as celery_app 6 | -------------------------------------------------------------------------------- /tests/contrib/flask/templates/users.html: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /tests/contrib/django/fake1/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | class FakeException(BaseException): 4 | pass 5 | 6 | 7 | class OtherFakeException(BaseException): 8 | pass 9 | -------------------------------------------------------------------------------- /opbeat/transport/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from opbeat.transport.http import AsyncHTTPTransport, HTTPTransport 4 | 5 | 6 | default = [HTTPTransport, AsyncHTTPTransport] 7 | -------------------------------------------------------------------------------- /opbeat/instrumentation/control.py: -------------------------------------------------------------------------------- 1 | from opbeat.instrumentation import register 2 | 3 | 4 | def instrument(): 5 | for obj in register.get_instrumentation_objects(): 6 | obj.instrument() 7 | -------------------------------------------------------------------------------- /tests/utils/compat.py: -------------------------------------------------------------------------------- 1 | 2 | try: 3 | from unittest2 import TestCase 4 | from unittest2 import skipIf 5 | except ImportError: 6 | from unittest import TestCase 7 | from unittest import skipIf 8 | -------------------------------------------------------------------------------- /opbeat/contrib/django/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class OpbeatConfig(AppConfig): 5 | name = 'opbeat.contrib.django' 6 | label = 'opbeat.contrib.django' 7 | verbose_name = 'Opbeat' 8 | -------------------------------------------------------------------------------- /tests/contrib/django/testapp/templates/list_users.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {% for user in users %} 5 | 6 | {% endfor %} 7 |
Users
{{ user }}
8 | 9 | -------------------------------------------------------------------------------- /opbeat/contrib/paste.py: -------------------------------------------------------------------------------- 1 | from opbeat.base import Client 2 | from opbeat.middleware import Opbeat 3 | 4 | 5 | def opbeat_filter_factory(app, global_conf, **kwargs): 6 | client = Client(**kwargs) 7 | return Opbeat(app, client) 8 | -------------------------------------------------------------------------------- /tests/utils/tests.py: -------------------------------------------------------------------------------- 1 | from opbeat.utils.deprecation import deprecated 2 | 3 | 4 | @deprecated("alternative") 5 | def deprecated_function(): 6 | pass 7 | 8 | 9 | def test_deprecation(): 10 | deprecated_function() 11 | -------------------------------------------------------------------------------- /opbeat/handlers/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | opbeat.handlers 3 | ~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2011-2012 Opbeat 6 | 7 | Large portions are 8 | :copyright: (c) 2010 by the Sentry Team, see AUTHORS for more details. 9 | :license: BSD, see LICENSE for more details. 10 | """ 11 | -------------------------------------------------------------------------------- /travis/run_docker.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | if [ -z ${TRAVIS_TAG+x} ]; then 3 | echo "Not a tagged build, skipping building wheels"; 4 | else 5 | mkdir -p wheelhouse; 6 | docker run --rm -v `pwd`:/io $DOCKER_IMAGE $PRE_CMD /io/travis/build_manylinux_wheels.sh; 7 | ls wheelhouse/; 8 | fi -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | isort: 2 | isort -rc -vb . 3 | 4 | test: 5 | if [ "$$TRAVIS_PYTHON_VERSION" != "3.5" ]; then \ 6 | py.test --isort --ignore=tests/asyncio; \ 7 | else py.test --isort; fi 8 | 9 | coverage: 10 | coverage run runtests.py --include=opbeat/* && \ 11 | coverage html --omit=*/migrations/* -d cover 12 | 13 | .PHONY: isort test coverage 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.log 3 | *.egg 4 | *.db 5 | *.pid 6 | .coverage 7 | .DS_Store 8 | .idea 9 | pip-log.txt 10 | /*.egg-info 11 | /build 12 | /cover 13 | /dist 14 | /example_project/local_settings.py 15 | /docs/html 16 | /docs/doctrees 17 | /example_project/*.db 18 | opbeat/utils/wrapt/_wrappers.so 19 | coverage 20 | .tox 21 | .eggs 22 | .cache 23 | /testdb.sql 24 | venv -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | # Tox (http://codespeak.net/~hpk/tox/) is a tool for running tests 2 | # in multiple virtualenvs. This configuration file will run the 3 | # test suite on all supported python versions. To use it, "pip install tox" 4 | # and then run "tox" from this directory. 5 | 6 | [tox] 7 | envlist = py{26,27,33,34,35,py} 8 | 9 | [testenv] 10 | commands = python setup.py test -a "{posargs}" 11 | -------------------------------------------------------------------------------- /opbeat/contrib/django/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | opbeat.contrib.django 3 | ~~~~~~~~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2011-2012 Opbeat 6 | 7 | Large portions are 8 | :copyright: (c) 2010 by the Sentry Team, see AUTHORS for more details. 9 | :license: BSD, see LICENSE for more details. 10 | """ 11 | 12 | default_app_config = 'opbeat.contrib.django.apps.OpbeatConfig' 13 | 14 | from opbeat.contrib.django.client import * 15 | -------------------------------------------------------------------------------- /opbeat/transport/exceptions.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | 4 | class InvalidScheme(ValueError): 5 | """ 6 | Raised when a transport is constructed using a URI which is not 7 | handled by the transport 8 | """ 9 | 10 | 11 | class DuplicateScheme(Exception): 12 | """ 13 | Raised when registering a handler for a particular scheme which 14 | is already registered 15 | """ 16 | pass 17 | -------------------------------------------------------------------------------- /tests/utils/lru_tests.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | from opbeat.utils.lru import LRUCache 4 | from tests.utils.compat import TestCase 5 | 6 | 7 | class LRUTest(TestCase): 8 | def test_insert_overflow(self): 9 | 10 | lru = LRUCache(4) 11 | 12 | for x in range(6): 13 | lru.set(x) 14 | 15 | self.assertFalse(lru.has_key(1)) 16 | for x in range(2, 6): 17 | self.assertTrue(lru.has_key(x)) 18 | -------------------------------------------------------------------------------- /docs/install/index.rst: -------------------------------------------------------------------------------- 1 | Install 2 | ======= 3 | 4 | If you haven't already, start by downloading opbeat. The easiest way is with **pip** 5 | 6 | .. code-block:: bash 7 | 8 | pip install opbeat 9 | 10 | Requirements 11 | ------------ 12 | 13 | If you installed using pip or setuptools you shouldn't need to worry about requirements. 14 | Otherwise you will need to install the following packages in your environment, 15 | if you are using Python 2.6. 16 | 17 | - ``simplejson`` 18 | -------------------------------------------------------------------------------- /opbeat/contrib/django/celery/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | opbeat.contrib.django.celery 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2011-2012 Opbeat 6 | 7 | Large portions are 8 | :copyright: (c) 2010 by the Sentry Team, see AUTHORS for more details. 9 | :license: BSD, see LICENSE for more details. 10 | """ 11 | 12 | from opbeat.contrib.celery import CeleryMixin 13 | from opbeat.contrib.django import DjangoClient 14 | 15 | 16 | class CeleryClient(CeleryMixin, DjangoClient): 17 | pass 18 | -------------------------------------------------------------------------------- /opbeat/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | opbeat 3 | ~~~~~ 4 | 5 | :copyright: (c) 2011-2012 Opbeat 6 | 7 | Large portions are 8 | :copyright: (c) 2010 by the Sentry Team, see AUTHORS for more details. 9 | :license: BSD, see LICENSE for more details. 10 | """ 11 | 12 | __all__ = ('VERSION', 'Client') 13 | 14 | try: 15 | VERSION = __import__('pkg_resources') \ 16 | .get_distribution('opbeat').version 17 | except Exception as e: 18 | VERSION = 'unknown' 19 | 20 | from opbeat.base import * 21 | from opbeat.conf import * 22 | from opbeat.traces import * 23 | -------------------------------------------------------------------------------- /tests/contrib/django/testapp/models.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import 3 | 4 | from django import VERSION as DJANGO_VERSION 5 | from django.db import models 6 | 7 | if DJANGO_VERSION >= (1, 5): 8 | from django.contrib.auth.models import AbstractBaseUser, BaseUserManager 9 | 10 | class MyUser(AbstractBaseUser): 11 | USERNAME_FIELD = 'my_username' 12 | my_username = models.CharField(max_length=30) 13 | 14 | objects = BaseUserManager() 15 | 16 | class Meta: 17 | abstract = False 18 | -------------------------------------------------------------------------------- /opbeat/instrumentation/packages/jinja2.py: -------------------------------------------------------------------------------- 1 | from opbeat.instrumentation.packages.base import AbstractInstrumentedModule 2 | from opbeat.traces import trace 3 | 4 | 5 | class Jinja2Instrumentation(AbstractInstrumentedModule): 6 | name = 'jinja2' 7 | 8 | instrument_list = [ 9 | ("jinja2", "Template.render"), 10 | ] 11 | 12 | def call(self, module, method, wrapped, instance, args, kwargs): 13 | signature = instance.name or instance.filename 14 | with trace(signature, "template.jinja2"): 15 | return wrapped(*args, **kwargs) 16 | -------------------------------------------------------------------------------- /opbeat/instrumentation/packages/zlib.py: -------------------------------------------------------------------------------- 1 | from opbeat.instrumentation.packages.base import AbstractInstrumentedModule 2 | from opbeat.traces import trace 3 | 4 | 5 | class ZLibInstrumentation(AbstractInstrumentedModule): 6 | name = 'zlib' 7 | instrument_list = [ 8 | ('zlib', 'compress'), 9 | ('zlib', 'decompress'), 10 | ] 11 | 12 | def call(self, module, method, wrapped, instance, args, kwargs): 13 | wrapped_name = module + "." + method 14 | with trace(wrapped_name, "compression.zlib"): 15 | return wrapped(*args, **kwargs) 16 | -------------------------------------------------------------------------------- /test_requirements/requirements-base.txt: -------------------------------------------------------------------------------- 1 | py==1.4.30 2 | pytest==3.0.6 3 | pytest-capturelog==0.7 4 | pytest-django==2.8.0 5 | pytest-benchmark==2.5.0 6 | apipkg==1.4 7 | execnet==1.4.1 8 | isort==4.2.2 9 | pytest-cache==1.0 10 | pytest-isort==0.1.0 11 | 12 | urllib3 13 | certifi 14 | Jinja2 15 | Logbook 16 | MarkupSafe 17 | WebOb 18 | Werkzeug 19 | amqp==1.4.9 20 | anyjson 21 | argparse 22 | billiard 23 | blinker>=1.1 24 | boto3 25 | celery<4 26 | greenlet 27 | itsdangerous 28 | kombu<4 29 | mock 30 | msgpack-python 31 | pep8 32 | redis 33 | requests 34 | urllib3-mock 35 | pymongo 36 | Twisted 37 | 38 | pytz 39 | -------------------------------------------------------------------------------- /opbeat/contrib/django/celery/models.py: -------------------------------------------------------------------------------- 1 | """ 2 | opbeat.contrib.django.celery.models 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2011-2012 Opbeat 6 | 7 | Large portions are 8 | :copyright: (c) 2010 by the Sentry Team, see AUTHORS for more details. 9 | :license: BSD, see LICENSE for more details. 10 | """ 11 | 12 | from django.conf import settings 13 | from django.core.exceptions import ImproperlyConfigured 14 | 15 | if 'djcelery' not in settings.INSTALLED_APPS: 16 | raise ImproperlyConfigured("Put 'djcelery' in your " 17 | "INSTALLED_APPS setting in order to use the sentry celery client.") 18 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [nosetests] 2 | exclude=^(start|stop)_test_server 3 | verbose=2 4 | 5 | [bdist_wheel] 6 | universal=0 7 | 8 | [pytest] 9 | python_files=tests.py test_*.py *_tests.py 10 | isort_ignore= 11 | opbeat/transport/asyncio.py 12 | opbeat/contrib/asyncio/client.py 13 | 14 | [isort] 15 | line_length=80 16 | indent=' ' 17 | not_skip=__init__.py 18 | skip=wrapt,setup.py,six.py 19 | multi_line_output=0 20 | known_standard_library=importlib,types 21 | known_django=django 22 | known_first_party=opbeat,tests 23 | known_third_party=pytest,flask 24 | default_section=FIRSTPARTY 25 | sections=FUTURE,STDLIB,DJANGO,THIRDPARTY,FIRSTPARTY,LOCALFOLDER 26 | -------------------------------------------------------------------------------- /docs/config/wsgi.rst: -------------------------------------------------------------------------------- 1 | Configuring ``WSGI`` Middleware 2 | =============================== 3 | 4 | .. csv-table:: 5 | :class: page-info 6 | 7 | "Page updated: 23rd July 2013", "" 8 | 9 | opbeat includes a simple to use WSGI middleware. 10 | 11 | :: 12 | 13 | from opbeat import Client 14 | from opbeat.middleware import Opbeat 15 | 16 | application = Opbeat( 17 | application, 18 | Client(organization_id='', app_id='', secret_token='') 19 | ) 20 | 21 | .. container:: note 22 | 23 | Many frameworks will not propagate exceptions to the underlying WSGI middleware by default. 24 | -------------------------------------------------------------------------------- /opbeat/contrib/flask/utils.py: -------------------------------------------------------------------------------- 1 | try: 2 | import urlparse 3 | except ImportError: 4 | import urllib.parse as urlparse 5 | 6 | from opbeat.utils.wsgi import get_environ, get_headers 7 | 8 | 9 | def get_data_from_request(request): 10 | urlparts = urlparse.urlsplit(request.url) 11 | 12 | return { 13 | 'http': { 14 | 'url': '%s://%s%s' % (urlparts.scheme, urlparts.netloc, urlparts.path), 15 | 'query_string': urlparts.query, 16 | 'method': request.method, 17 | 'data': request.form, 18 | 'headers': dict(get_headers(request.environ)), 19 | 'env': dict(get_environ(request.environ)), 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tests/contrib/pylons/tests.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | from opbeat.contrib.pylons import Opbeat 4 | from tests.utils.compat import TestCase 5 | 6 | 7 | def example_app(environ, start_response): 8 | raise ValueError('hello world') 9 | 10 | 11 | class MiddlewareTest(TestCase): 12 | def setUp(self): 13 | self.app = example_app 14 | 15 | def test_init(self): 16 | config = { 17 | 'opbeat.servers': 'http://localhost/api/store', 18 | 'opbeat.organization_id': 'p' * 32, 19 | 'opbeat.app_id': 'p' * 32, 20 | 'opbeat.secret_token': 'a' * 32, 21 | } 22 | middleware = Opbeat(self.app, config) 23 | -------------------------------------------------------------------------------- /tests/contrib/django/testapp/celery.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | from django.conf import settings 4 | 5 | from celery import Celery 6 | 7 | from opbeat.contrib.celery import register_signal 8 | from opbeat.contrib.django.models import client, logger, register_handlers 9 | 10 | app = Celery('testapp') 11 | 12 | app.config_from_object('django.conf:settings') 13 | app.autodiscover_tasks(lambda: settings.INSTALLED_APPS) 14 | 15 | 16 | # hook up Opbeat 17 | 18 | 19 | try: 20 | register_signal(client) 21 | except Exception as e: 22 | logger.exception('Failed installing celery hook: %s' % e) 23 | 24 | if 'opbeat.contrib.django' in settings.INSTALLED_APPS: 25 | register_handlers() 26 | -------------------------------------------------------------------------------- /opbeat/utils/wrapt/__init__.py: -------------------------------------------------------------------------------- 1 | __version_info__ = ('1', '10', '2') 2 | __version__ = '.'.join(__version_info__) 3 | 4 | from .wrappers import (ObjectProxy, CallableObjectProxy, FunctionWrapper, 5 | BoundFunctionWrapper, WeakFunctionProxy, resolve_path, apply_patch, 6 | wrap_object, wrap_object_attribute, function_wrapper, 7 | wrap_function_wrapper, patch_function_wrapper, 8 | transient_function_wrapper) 9 | 10 | from .decorators import (adapter_factory, AdapterFactory, decorator, 11 | synchronized) 12 | 13 | from .importer import (register_post_import_hook, when_imported, 14 | discover_post_import_hooks) 15 | 16 | try: 17 | from inspect import getcallargs 18 | except ImportError: 19 | from .arguments import getcallargs 20 | -------------------------------------------------------------------------------- /tests/helpers.py: -------------------------------------------------------------------------------- 1 | from opbeat.base import Client 2 | 3 | 4 | def get_tempstoreclient(organization_id="1", app_id="2", 5 | secret_token="test_key", **kwargs): 6 | return TempStoreClient(organization_id=organization_id, app_id=app_id, 7 | secret_token=secret_token, **kwargs) 8 | 9 | 10 | class TempStoreClient(Client): 11 | def __init__(self, 12 | servers=None, organization_id=None, app_id=None, 13 | secret_token=None, **kwargs): 14 | self.events = [] 15 | super(TempStoreClient, self).__init__( 16 | servers=servers, organization_id=organization_id, app_id=app_id, 17 | secret_token=secret_token, **kwargs) 18 | 19 | def send(self, **kwargs): 20 | self.events.append(kwargs) 21 | -------------------------------------------------------------------------------- /docs/config/logbook.rst: -------------------------------------------------------------------------------- 1 | Configuring ``logbook`` 2 | ======================= 3 | 4 | .. csv-table:: 5 | :class: page-info 6 | 7 | "Page updated: 23rd July 2013", "" 8 | 9 | opbeat provides a `logbook `_ handler which will pipe 10 | messages to Opbeat. 11 | 12 | First you'll need to configure a handler:: 13 | 14 | from opbeat.handlers.logbook import OpbeatHandler 15 | 16 | # Manually specify a client 17 | client = Client(...) 18 | handler = OpbeatHandler(client) 19 | 20 | Finally, bind your handler to your context:: 21 | 22 | from opbeat.handlers.logbook import OpbeatHandler 23 | 24 | client = Client(...) 25 | opbeat_handler = OpbeatHandler(client) 26 | with opbeat_handler.applicationbound(): 27 | # everything logged here will go to sentry. 28 | ... 29 | -------------------------------------------------------------------------------- /tests/events/tests.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import 3 | 4 | from mock import Mock 5 | 6 | from opbeat.events import Message 7 | from tests.utils.compat import TestCase 8 | 9 | 10 | class MessageTest(TestCase): 11 | def test_to_string(self): 12 | unformatted_message = 'My message from %s about %s' 13 | client = Mock() 14 | message = Message(client) 15 | message.logger = Mock() 16 | data = { 17 | 'param_message': { 18 | 'message': unformatted_message, 19 | } 20 | } 21 | 22 | self.assertEqual(message.to_string(data), unformatted_message) 23 | 24 | data['param_message']['params'] = (1, 2) 25 | self.assertEqual(message.to_string(data), 26 | unformatted_message % (1, 2)) 27 | -------------------------------------------------------------------------------- /tests/instrumentation/base_tests.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from opbeat.instrumentation.packages.base import AbstractInstrumentedModule 4 | 5 | 6 | class _TestInstrumentNonExistingFunctionOnModule(AbstractInstrumentedModule): 7 | name = "test_non_existing_function_instrumentation" 8 | instrument_list = [ 9 | ("os.path", "non_existing_function") 10 | ] 11 | 12 | 13 | class _TestInstrumentNonExistingMethod(AbstractInstrumentedModule): 14 | name = "test_non_existing_method_instrumentation" 15 | instrument_list = [ 16 | ("dict", "non_existing_method") 17 | ] 18 | 19 | 20 | def test_instrument_nonexisting_method_on_module(): 21 | _TestInstrumentNonExistingFunctionOnModule().instrument() 22 | 23 | 24 | def test_instrument_nonexisting_method(): 25 | _TestInstrumentNonExistingMethod().instrument() 26 | -------------------------------------------------------------------------------- /opbeat/contrib/asyncio/client.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import urllib.parse 3 | 4 | from opbeat.base import Client 5 | 6 | 7 | class Client(Client): 8 | 9 | def handle_transport_response(self, task): 10 | try: 11 | url = task.result() 12 | except Exception as exc: 13 | self.handle_transport_fail(exception=exc) 14 | else: 15 | self.handle_transport_success(url=url) 16 | 17 | def _send_remote(self, url, data, headers=None): 18 | if headers is None: 19 | headers = {} 20 | parsed = urllib.parse.urlparse(url) 21 | transport = self._get_transport(parsed) 22 | loop = asyncio.get_event_loop() 23 | task = loop.create_task( 24 | transport.send(data, headers, timeout=self.timeout)) 25 | task.add_done_callback(self.handle_transport_response) 26 | -------------------------------------------------------------------------------- /opbeat/contrib/django/middleware/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | opbeat.contrib.django.middleware.wsgi 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2011-2012 Opbeat 6 | 7 | Large portions are 8 | :copyright: (c) 2010 by the Sentry Team, see AUTHORS for more details. 9 | :license: BSD, see LICENSE for more details. 10 | """ 11 | 12 | from opbeat.middleware import Opbeat 13 | 14 | 15 | class Opbeat(Opbeat): 16 | """ 17 | Identical to the default WSGI middleware except that 18 | the client comes dynamically via ``get_client 19 | 20 | >>> from opbeat.contrib.django.middleware.wsgi import Opbeat 21 | >>> application = Opbeat(application) 22 | """ 23 | def __init__(self, application): 24 | self.application = application 25 | 26 | @property 27 | def client(self): 28 | from opbeat.contrib.django.models import client 29 | return client 30 | -------------------------------------------------------------------------------- /tests/contrib/django/testapp/middleware.py: -------------------------------------------------------------------------------- 1 | try: 2 | from django.utils.deprecation import MiddlewareMixin 3 | except ImportError: 4 | # no-op class for Django < 1.10 5 | class MiddlewareMixin(object): 6 | pass 7 | 8 | 9 | class BrokenRequestMiddleware(MiddlewareMixin): 10 | def process_request(self, request): 11 | raise ImportError('request') 12 | 13 | 14 | class BrokenResponseMiddleware(MiddlewareMixin): 15 | def process_response(self, request, response): 16 | raise ImportError('response') 17 | 18 | 19 | class BrokenViewMiddleware(MiddlewareMixin): 20 | def process_view(self, request, func, args, kwargs): 21 | raise ImportError('view') 22 | 23 | 24 | class MetricsNameOverrideMiddleware(MiddlewareMixin): 25 | def process_response(self, request, response): 26 | request._opbeat_transaction_name = 'foobar' 27 | return response 28 | -------------------------------------------------------------------------------- /tests/contrib/twisted/tests.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | from twisted.python.failure import Failure 4 | 5 | from opbeat.contrib.twisted import OpbeatLogObserver 6 | from tests.helpers import get_tempstoreclient 7 | from tests.utils.compat import TestCase 8 | 9 | 10 | class TwistedLogObserverTest(TestCase): 11 | def setUp(self): 12 | self.client = get_tempstoreclient() 13 | 14 | def test_observer(self): 15 | observer = OpbeatLogObserver(client=self.client) 16 | try: 17 | 1 / 0 18 | except ZeroDivisionError: 19 | failure = Failure() 20 | event = dict(log_failure=failure) 21 | observer(event) 22 | 23 | cli_event = self.client.events.pop(0) 24 | self.assertEquals(cli_event['exception']['type'], 'ZeroDivisionError') 25 | self.assertTrue('zero' in cli_event['exception']['value']) 26 | -------------------------------------------------------------------------------- /opbeat/contrib/rq/__init__.py: -------------------------------------------------------------------------------- 1 | def register_opbeat(client, worker): 2 | """Given a Opbeat client and an RQ worker, registers exception handlers 3 | with the worker so exceptions are logged to Opbeat. 4 | 5 | E.g.: 6 | 7 | from opbeat.contrib.django.models import client 8 | from opbeat.contrib.rq import register_opbeat 9 | 10 | worker = Worker(map(Queue, listen)) 11 | register_opbeat(client, worker) 12 | worker.work() 13 | 14 | """ 15 | def send_to_opbeat(job, *exc_info): 16 | client.capture_exception( 17 | exc_info=exc_info, 18 | extra={ 19 | 'job_id': job.id, 20 | 'func': job.func_name, 21 | 'args': job.args, 22 | 'kwargs': job.kwargs, 23 | 'description': job.description, 24 | } 25 | ) 26 | 27 | worker.push_exc_handler(send_to_opbeat) 28 | -------------------------------------------------------------------------------- /opbeat/instrumentation/packages/mysql.py: -------------------------------------------------------------------------------- 1 | from opbeat.instrumentation.packages.dbapi2 import (ConnectionProxy, 2 | CursorProxy, 3 | DbApi2Instrumentation, 4 | extract_signature) 5 | 6 | 7 | class MySQLCursorProxy(CursorProxy): 8 | provider_name = 'mysql' 9 | 10 | def extract_signature(self, sql): 11 | return extract_signature(sql) 12 | 13 | 14 | class MySQLConnectionProxy(ConnectionProxy): 15 | cursor_proxy = MySQLCursorProxy 16 | 17 | 18 | class MySQLInstrumentation(DbApi2Instrumentation): 19 | name = 'mysql' 20 | 21 | instrument_list = [ 22 | ("MySQLdb", "connect"), 23 | ] 24 | 25 | def call(self, module, method, wrapped, instance, args, kwargs): 26 | return MySQLConnectionProxy(wrapped(*args, **kwargs)) 27 | -------------------------------------------------------------------------------- /tests/config/tests.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | import logging 4 | 5 | import mock 6 | 7 | from opbeat.conf import setup_logging 8 | from tests.utils.compat import TestCase 9 | 10 | 11 | class SetupLoggingTest(TestCase): 12 | def test_basic_not_configured(self): 13 | with mock.patch('logging.getLogger', spec=logging.getLogger) as getLogger: 14 | logger = getLogger() 15 | logger.handlers = [] 16 | handler = mock.Mock() 17 | result = setup_logging(handler) 18 | self.assertTrue(result) 19 | 20 | def test_basic_already_configured(self): 21 | with mock.patch('logging.getLogger', spec=logging.getLogger) as getLogger: 22 | handler = mock.Mock() 23 | logger = getLogger() 24 | logger.handlers = [handler] 25 | result = setup_logging(handler) 26 | self.assertFalse(result) 27 | -------------------------------------------------------------------------------- /travis/build_manylinux_wheels.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e -x 4 | 5 | export SKIP_ZERORPC=1 6 | export OPBEAT_WRAPT_EXTENSIONS="true" 7 | 8 | # Compile wheels 9 | for PYBIN in /opt/python/*/bin; do 10 | if [[ $PYBIN == *cp26* ]]; then 11 | continue 12 | fi 13 | ${PYBIN}/pip install -r /io/test_requirements/requirements-base.txt 14 | ${PYBIN}/pip install -r /io/test_requirements/requirements-python-$($PYBIN/python -c "import sys; print(sys.version_info[0])").txt 15 | ${PYBIN}/pip wheel /io/ -w wheelhouse/ 16 | done 17 | 18 | # Bundle external shared libraries into the wheels 19 | for whl in wheelhouse/opbeat*.whl; do 20 | auditwheel repair $whl -w /io/wheelhouse/ 21 | done 22 | 23 | # Install packages and test 24 | for PYBIN in /opt/python/*/bin/; do 25 | ${PYBIN}/pip install opbeat --no-index -f /io/wheelhouse 26 | (cd $HOME; ${PYBIN}/py.test) 27 | done 28 | 29 | chmod 0777 /io/wheelhouse/*.whl 30 | -------------------------------------------------------------------------------- /opbeat/contrib/twisted/__init__.py: -------------------------------------------------------------------------------- 1 | from twisted.logger import ILogObserver 2 | from zope.interface import implementer 3 | 4 | from opbeat.base import Client 5 | 6 | 7 | @implementer(ILogObserver) 8 | class OpbeatLogObserver(object): 9 | """ 10 | A twisted log observer for Opbeat. 11 | Eg.: 12 | 13 | from opbeat.base import Client 14 | from twisted.logger import Logger 15 | 16 | client = Client(...) 17 | observer = OpbeatLogObserver(client=client) 18 | log = Logger(observer=observer) 19 | 20 | try: 21 | 1 / 0 22 | except: 23 | log.failure("Math is hard!") 24 | """ 25 | 26 | def __init__(self, client=None, **kwargs): 27 | self.client = client or Client(**kwargs) 28 | 29 | def __call__(self, event): 30 | failure = event.get('log_failure') 31 | if failure is not None: 32 | self.client.capture_exception( 33 | (failure.type, failure.value, failure.getTracebackObject()), 34 | extra=event) 35 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Error logging: Python 2 | ===================== 3 | 4 | .. csv-table:: 5 | :class: page-info 6 | 7 | "Page updated: 23rd July 2013", "" 8 | 9 | Introduction 10 | ------------ 11 | To send error logs to Opbeat, you must install an agent. 12 | 13 | This is the official Opbeat standalone Python agent. It is forked from `Raven `_. 14 | 15 | Requirements 16 | ------------ 17 | - pip 18 | - simplejson (Only if you're using < Python 2.7) 19 | 20 | 21 | Installation 22 | ------------ 23 | 24 | .. code:: 25 | :class: language-bash 26 | 27 | # Install Opbeat 28 | $ pip install opbeat 29 | 30 | Configuration 31 | ------------- 32 | 33 | 34 | .. toctree:: 35 | :maxdepth: 1 36 | 37 | Django 38 | Flask 39 | Pylons 40 | Pyramid 41 | Logging 42 | Logbook 43 | WSGI Middle 44 | ZeroRPC 45 | Other 46 | -------------------------------------------------------------------------------- /opbeat/utils/module_import.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from importlib import import_module 3 | 4 | from opbeat.utils import six 5 | 6 | 7 | # From Django 8 | # https://github.com/django/django/blob/master/django/utils/module_loading.py 9 | 10 | 11 | def import_string(dotted_path): 12 | """ 13 | Import a dotted module path and return the attribute/class designated by the 14 | last name in the path. Raise ImportError if the import failed. 15 | """ 16 | try: 17 | module_path, class_name = dotted_path.rsplit('.', 1) 18 | except ValueError: 19 | msg = "%s doesn't look like a module path" % dotted_path 20 | six.reraise(ImportError, ImportError(msg), sys.exc_info()[2]) 21 | 22 | module = import_module(module_path) 23 | 24 | try: 25 | return getattr(module, class_name) 26 | except AttributeError: 27 | msg = 'Module "%s" does not define a "%s" attribute/class' % ( 28 | module_path, class_name) 29 | six.reraise(ImportError, ImportError(msg), sys.exc_info()[2]) 30 | -------------------------------------------------------------------------------- /travis/run_tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -ex 4 | 5 | PYTHON_MAJOR_VERSION=$(python -c "import sys; print(sys.version_info[0])"); 6 | mkdir -p "$PIP_CACHE" 7 | mkdir -p wheelhouse 8 | psql -c 'create database opbeat_test;' -U postgres 9 | pip install -U pip 10 | pip install -r "test_requirements/requirements-${WEBFRAMEWORK}.txt" --cache-dir "${PIP_CACHE}" 11 | pip install -r "test_requirements/requirements-python-${PYTHON_MAJOR_VERSION}.txt" --cache-dir "${PIP_CACHE}" 12 | if [[ $TRAVIS_PYTHON_VERSION == '3.5' ]]; then 13 | pip install -r test_requirements/requirements-asyncio.txt --cache-dir "${PIP_CACHE}" 14 | fi 15 | if [[ $TRAVIS_PYTHON_VERSION == 'pypy' ]]; then 16 | pip install -r test_requirements/requirements-pypy.txt --cache-dir "${PIP_CACHE}" 17 | else 18 | pip install -r test_requirements/requirements-cpython.txt --cache-dir "${PIP_CACHE}" 19 | if [[ $PYTHON_MAJOR_VERSION == '2' ]]; then 20 | pip install -r test_requirements/requirements-zerorpc.txt --cache-dir "${PIP_CACHE}" 21 | fi 22 | fi 23 | 24 | make test 25 | -------------------------------------------------------------------------------- /opbeat/utils/compat.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import atexit 3 | import functools 4 | 5 | try: 6 | import urlparse 7 | except ImportError: 8 | from urllib import parse as urlparse 9 | 10 | try: 11 | from urllib2 import HTTPError 12 | except ImportError: 13 | from urllib.error import HTTPError 14 | 15 | 16 | def noop_decorator(func): 17 | @functools.wraps(func) 18 | def wrapped(*args, **kwargs): 19 | return func(*args, **kwargs) 20 | return wrapped 21 | 22 | 23 | def atexit_register(func): 24 | """ 25 | Uses either uwsgi's atexit mechanism, or atexit from the stdlib. 26 | 27 | When running under uwsgi, using their atexit handler is more reliable, 28 | especially when using gevent 29 | :param func: the function to call at exit 30 | """ 31 | try: 32 | import uwsgi 33 | orig = getattr(uwsgi, 'atexit', None) 34 | 35 | def uwsgi_atexit(): 36 | if callable(orig): 37 | orig() 38 | func() 39 | 40 | uwsgi.atexit = uwsgi_atexit 41 | except ImportError: 42 | atexit.register(func) 43 | -------------------------------------------------------------------------------- /opbeat/utils/deprecation.py: -------------------------------------------------------------------------------- 1 | import functools 2 | import warnings 3 | 4 | # https://wiki.python.org/moin/PythonDecoratorLibrary#Smart_deprecation_warnings_.28with_valid_filenames.2C_line_numbers.2C_etc..29 5 | # Updated to work with 2.6 and 3+. 6 | from opbeat.utils import six 7 | 8 | 9 | def deprecated(alternative=None): 10 | """This is a decorator which can be used to mark functions 11 | as deprecated. It will result in a warning being emitted 12 | when the function is used.""" 13 | def real_decorator(func): 14 | @functools.wraps(func) 15 | def new_func(*args, **kwargs): 16 | msg = "Call to deprecated function {0}.".format(func.__name__) 17 | if alternative: 18 | msg += " Use {0} instead".format(alternative) 19 | warnings.warn_explicit( 20 | msg, 21 | category=DeprecationWarning, 22 | filename=six.get_function_code(func).co_filename, 23 | lineno=six.get_function_code(func).co_firstlineno + 1 24 | ) 25 | return func(*args, **kwargs) 26 | return new_func 27 | return real_decorator 28 | -------------------------------------------------------------------------------- /opbeat/instrumentation/packages/botocore.py: -------------------------------------------------------------------------------- 1 | from opbeat.instrumentation.packages.base import AbstractInstrumentedModule 2 | from opbeat.traces import trace 3 | from opbeat.utils.compat import urlparse 4 | 5 | 6 | class BotocoreInstrumentation(AbstractInstrumentedModule): 7 | name = 'botocore' 8 | 9 | instrument_list = [ 10 | ('botocore.client', 'BaseClient._make_api_call'), 11 | ] 12 | 13 | def call(self, module, method, wrapped, instance, args, kwargs): 14 | if 'operation_name' in kwargs: 15 | operation_name = kwargs['operation_name'] 16 | else: 17 | operation_name = args[0] 18 | 19 | target_endpoint = instance._endpoint.host 20 | parsed_url = urlparse.urlparse(target_endpoint) 21 | service, region, _ = parsed_url.hostname.split('.', 2) 22 | 23 | signature = '{}:{}'.format(service, operation_name) 24 | extra_data = { 25 | 'service': service, 26 | 'region': region, 27 | 'operation': operation_name, 28 | } 29 | 30 | with trace(signature, 'ext.http.aws', extra_data, leaf=True): 31 | return wrapped(*args, **kwargs) 32 | -------------------------------------------------------------------------------- /tests/utils/json/tests.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import 3 | 4 | import datetime 5 | import uuid 6 | 7 | from opbeat.utils import opbeat_json as json 8 | from opbeat.utils import six 9 | from tests.utils.compat import TestCase 10 | 11 | 12 | class JSONTest(TestCase): 13 | def test_uuid(self): 14 | res = uuid.uuid4() 15 | self.assertEquals(json.dumps(res), '"%s"' % res.hex) 16 | 17 | def test_datetime(self): 18 | res = datetime.datetime(day=1, month=1, year=2011, hour=1, minute=1, second=1) 19 | self.assertEquals(json.dumps(res), '"2011-01-01T01:01:01.000000Z"') 20 | 21 | def test_set(self): 22 | res = set(['foo', 'bar']) 23 | self.assertIn(json.dumps(res), ('["foo", "bar"]', '["bar", "foo"]')) 24 | 25 | def test_frozenset(self): 26 | res = frozenset(['foo', 'bar']) 27 | self.assertIn(json.dumps(res), ('["foo", "bar"]', '["bar", "foo"]')) 28 | 29 | def test_bytes(self): 30 | if six.PY2: 31 | res = bytes('foobar') 32 | else: 33 | res = bytes('foobar', encoding='ascii') 34 | self.assertEqual(json.dumps(res), '"foobar"') 35 | -------------------------------------------------------------------------------- /opbeat/instrumentation/packages/urllib3.py: -------------------------------------------------------------------------------- 1 | from opbeat.instrumentation.packages.base import AbstractInstrumentedModule 2 | from opbeat.traces import trace 3 | from opbeat.utils import default_ports 4 | 5 | 6 | class Urllib3Instrumentation(AbstractInstrumentedModule): 7 | name = 'urllib3' 8 | 9 | instrument_list = [ 10 | ("urllib3.connectionpool", "HTTPConnectionPool.urlopen"), 11 | ] 12 | 13 | def call(self, module, method, wrapped, instance, args, kwargs): 14 | if 'method' in kwargs: 15 | method = kwargs['method'] 16 | else: 17 | method = args[0] 18 | 19 | host = instance.host 20 | 21 | if instance.port != default_ports.get(instance.scheme): 22 | host += ":" + str(instance.port) 23 | 24 | if 'url' in kwargs: 25 | url = kwargs['url'] 26 | else: 27 | url = args[1] 28 | 29 | signature = method.upper() + " " + host 30 | 31 | url = instance.scheme + "://" + host + url 32 | 33 | with trace(signature, "ext.http.urllib3", 34 | {'url': url}, leaf=True): 35 | return wrapped(*args, **kwargs) 36 | -------------------------------------------------------------------------------- /opbeat/contrib/django/handlers.py: -------------------------------------------------------------------------------- 1 | """ 2 | opbeat.contrib.django.handlers 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2011-2012 Opbeat 6 | 7 | Large portions are 8 | :copyright: (c) 2010 by the Sentry Team, see AUTHORS for more details. 9 | :license: BSD, see LICENSE for more details. 10 | """ 11 | 12 | from __future__ import absolute_import 13 | 14 | import logging 15 | 16 | from opbeat.handlers.logging import OpbeatHandler as BaseOpbeatHandler 17 | 18 | 19 | class OpbeatHandler(BaseOpbeatHandler): 20 | def __init__(self, level=logging.NOTSET): 21 | logging.Handler.__init__(self, level=level) 22 | 23 | def _get_client(self): 24 | from opbeat.contrib.django.models import client 25 | 26 | return client 27 | 28 | client = property(_get_client) 29 | 30 | def _emit(self, record): 31 | from opbeat.contrib.django.middleware import OpbeatLogMiddleware 32 | 33 | # Fetch the request from a threadlocal variable, if available 34 | request = getattr(OpbeatLogMiddleware.thread, 'request', None) 35 | request = getattr(record, 'request', request) 36 | 37 | return super(OpbeatHandler, self)._emit(record, request=request) 38 | -------------------------------------------------------------------------------- /opbeat/instrumentation/packages/pylibmc.py: -------------------------------------------------------------------------------- 1 | from opbeat.instrumentation.packages.base import AbstractInstrumentedModule 2 | from opbeat.traces import trace 3 | 4 | 5 | class PyLibMcInstrumentation(AbstractInstrumentedModule): 6 | name = 'pylibmc' 7 | 8 | instrument_list = [ 9 | ("pylibmc", "Client.get"), 10 | ("pylibmc", "Client.get_multi"), 11 | ("pylibmc", "Client.set"), 12 | ("pylibmc", "Client.set_multi"), 13 | ("pylibmc", "Client.add"), 14 | ("pylibmc", "Client.replace"), 15 | ("pylibmc", "Client.append"), 16 | ("pylibmc", "Client.prepend"), 17 | ("pylibmc", "Client.incr"), 18 | ("pylibmc", "Client.decr"), 19 | ("pylibmc", "Client.gets"), 20 | ("pylibmc", "Client.cas"), 21 | ("pylibmc", "Client.delete"), 22 | ("pylibmc", "Client.delete_multi"), 23 | ("pylibmc", "Client.touch"), 24 | ("pylibmc", "Client.get_stats"), 25 | ] 26 | 27 | def call(self, module, method, wrapped, instance, args, kwargs): 28 | wrapped_name = self.get_wrapped_name(wrapped, instance, method) 29 | with trace(wrapped_name, "cache.memcached"): 30 | return wrapped(*args, **kwargs) 31 | -------------------------------------------------------------------------------- /opbeat/instrumentation/packages/redis.py: -------------------------------------------------------------------------------- 1 | from opbeat.instrumentation.packages.base import AbstractInstrumentedModule 2 | from opbeat.traces import trace 3 | 4 | 5 | class RedisInstrumentation(AbstractInstrumentedModule): 6 | name = 'redis' 7 | 8 | instrument_list = [ 9 | ("redis.client", "Redis.execute_command"), 10 | ("redis.client", "StrictRedis.execute_command"), 11 | ] 12 | 13 | def call(self, module, method, wrapped, instance, args, kwargs): 14 | if len(args) > 0: 15 | wrapped_name = str(args[0]) 16 | else: 17 | wrapped_name = self.get_wrapped_name(wrapped, instance, method) 18 | 19 | with trace(wrapped_name, "cache.redis", leaf=True): 20 | return wrapped(*args, **kwargs) 21 | 22 | 23 | class RedisPipelineInstrumentation(AbstractInstrumentedModule): 24 | name = 'redis' 25 | 26 | instrument_list = [ 27 | ("redis.client", "BasePipeline.execute"), 28 | ] 29 | 30 | def call(self, module, method, wrapped, instance, args, kwargs): 31 | wrapped_name = self.get_wrapped_name(wrapped, instance, method) 32 | with trace(wrapped_name, "cache.redis", leaf=True): 33 | return wrapped(*args, **kwargs) 34 | -------------------------------------------------------------------------------- /opbeat/utils/opbeat_json.py: -------------------------------------------------------------------------------- 1 | """ 2 | opbeat.utils.json 3 | ~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2011-2012 Opbeat 6 | 7 | Large portions are 8 | :copyright: (c) 2010 by the Sentry Team, see AUTHORS for more details. 9 | :license: BSD, see LICENSE for more details. 10 | """ 11 | 12 | import datetime 13 | import uuid 14 | 15 | try: 16 | import json 17 | except ImportError: 18 | import simplejson as json 19 | 20 | 21 | class BetterJSONEncoder(json.JSONEncoder): 22 | ENCODERS = { 23 | set: list, 24 | frozenset: list, 25 | datetime.datetime: lambda obj: obj.strftime('%Y-%m-%dT%H:%M:%S.%fZ'), 26 | uuid.UUID: lambda obj: obj.hex, 27 | bytes: lambda obj: obj.decode('utf-8', errors='replace') 28 | } 29 | 30 | def default(self, obj): 31 | if type(obj) in self.ENCODERS: 32 | return self.ENCODERS[type(obj)](obj) 33 | return super(BetterJSONEncoder, self).default(obj) 34 | 35 | 36 | def better_decoder(data): 37 | return data 38 | 39 | 40 | def dumps(value, **kwargs): 41 | return json.dumps(value, cls=BetterJSONEncoder, **kwargs) 42 | 43 | 44 | def loads(value, **kwargs): 45 | return json.loads(value, object_hook=better_decoder) 46 | -------------------------------------------------------------------------------- /opbeat/instrumentation/packages/sqlite.py: -------------------------------------------------------------------------------- 1 | from opbeat.instrumentation.packages.dbapi2 import (ConnectionProxy, 2 | CursorProxy, 3 | DbApi2Instrumentation, 4 | extract_signature) 5 | from opbeat.traces import trace 6 | 7 | 8 | class SQLiteCursorProxy(CursorProxy): 9 | provider_name = 'sqlite' 10 | 11 | def extract_signature(self, sql): 12 | return extract_signature(sql) 13 | 14 | 15 | class SQLiteConnectionProxy(ConnectionProxy): 16 | cursor_proxy = SQLiteCursorProxy 17 | 18 | 19 | class SQLiteInstrumentation(DbApi2Instrumentation): 20 | name = 'sqlite' 21 | 22 | instrument_list = [ 23 | ("sqlite3", "connect"), 24 | ("sqlite3.dbapi2", "connect"), 25 | ("pysqlite2.dbapi2", "connect"), 26 | ] 27 | 28 | def call(self, module, method, wrapped, instance, args, kwargs): 29 | signature = ".".join([module, method]) 30 | 31 | if len(args) == 1: 32 | signature += " " + str(args[0]) 33 | 34 | with trace(signature, "db.sqlite.connect"): 35 | return SQLiteConnectionProxy(wrapped(*args, **kwargs)) 36 | -------------------------------------------------------------------------------- /opbeat/instrumentation/packages/requests.py: -------------------------------------------------------------------------------- 1 | from opbeat.instrumentation.packages.base import AbstractInstrumentedModule 2 | from opbeat.traces import trace 3 | from opbeat.utils import default_ports 4 | from opbeat.utils.compat import urlparse 5 | 6 | 7 | def get_host_from_url(url): 8 | parsed_url = urlparse.urlparse(url) 9 | host = parsed_url.hostname or " " 10 | 11 | if ( 12 | parsed_url.port and 13 | default_ports.get(parsed_url.scheme) != parsed_url.port 14 | ): 15 | host += ":" + str(parsed_url.port) 16 | 17 | return host 18 | 19 | 20 | class RequestsInstrumentation(AbstractInstrumentedModule): 21 | name = 'requests' 22 | 23 | instrument_list = [ 24 | ("requests.sessions", "Session.send"), 25 | ] 26 | 27 | def call(self, module, method, wrapped, instance, args, kwargs): 28 | if 'request' in kwargs: 29 | request = kwargs['request'] 30 | else: 31 | request = args[0] 32 | 33 | signature = request.method.upper() 34 | signature += " " + get_host_from_url(request.url) 35 | 36 | with trace(signature, "ext.http.requests", 37 | {'url': request.url}, leaf=True): 38 | return wrapped(*args, **kwargs) 39 | -------------------------------------------------------------------------------- /opbeat/contrib/pylons/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | opbeat.contrib.pylons 3 | ~~~~~~~~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2011-2012 Opbeat 6 | 7 | Large portions are 8 | :copyright: (c) 2010 by the Sentry Team, see AUTHORS for more details. 9 | :license: BSD, see LICENSE for more details. 10 | """ 11 | from opbeat.base import Client 12 | from opbeat.middleware import Opbeat as Middleware 13 | 14 | 15 | def list_from_setting(config, setting): 16 | value = config.get(setting) 17 | if not value: 18 | return None 19 | return value.split() 20 | 21 | 22 | class Opbeat(Middleware): 23 | def __init__(self, app, config, client_cls=Client): 24 | client = client_cls( 25 | servers=list_from_setting(config, 'opbeat.servers'), 26 | timeout=config.get('opbeat.timeout'), 27 | name=config.get('opbeat.name'), 28 | organization_id=config.get('opbeat.organization_id'), 29 | app_id=config.get('opbeat.app_id'), 30 | secret_token=config.get('opbeat.secret_token'), 31 | include_paths=list_from_setting(config, 'opbeat.include_paths'), 32 | exclude_paths=list_from_setting(config, 'opbeat.exclude_paths'), 33 | ) 34 | super(Opbeat, self).__init__(app, client) 35 | -------------------------------------------------------------------------------- /opbeat/instrumentation/packages/python_memcached.py: -------------------------------------------------------------------------------- 1 | from opbeat.instrumentation.packages.base import AbstractInstrumentedModule 2 | from opbeat.traces import trace 3 | 4 | 5 | class PythonMemcachedInstrumentation(AbstractInstrumentedModule): 6 | name = 'python_memcached' 7 | 8 | method_list = [ 9 | 'add', 10 | 'append', 11 | 'cas', 12 | 'decr', 13 | 'delete', 14 | 'delete_multi', 15 | 'disconnect_all', 16 | 'flush_all', 17 | 'get', 18 | 'get_multi', 19 | 'get_slabs', 20 | 'get_stats', 21 | 'gets', 22 | 'incr', 23 | 'prepend', 24 | 'replace', 25 | 'set', 26 | 'set_multi', 27 | 'touch' 28 | ] 29 | # Took out 'set_servers', 'reset_cas', 'debuglog', 'check_key' and 30 | # 'forget_dead_hosts' because they involve no communication. 31 | 32 | def get_instrument_list(self): 33 | return [("memcache", "Client." + method) for method in self.method_list] 34 | 35 | def call(self, module, method, wrapped, instance, args, kwargs): 36 | name = self.get_wrapped_name(wrapped, instance, method) 37 | 38 | with trace(name, "cache.memcached"): 39 | return wrapped(*args, **kwargs) 40 | -------------------------------------------------------------------------------- /docs/config/zerorpc.rst: -------------------------------------------------------------------------------- 1 | Configuring ZeroRPC 2 | =================== 3 | 4 | .. csv-table:: 5 | :class: page-info 6 | 7 | "Page updated: 23rd July 2013", "" 8 | 9 | Setup 10 | ----- 11 | 12 | The ZeroRPC integration comes as middleware for ZeroRPC. The middleware can be 13 | configured like the original opbeat client (using keyword arguments) and 14 | registered into ZeroRPC's context manager 15 | 16 | .. code:: 17 | :class: language-python 18 | 19 | import zerorpc 20 | 21 | from opbeat.contrib.zerorpc import OpbeatMiddleware 22 | 23 | opbeat = OpbeatMiddleware(organization_id='', ...) 24 | zerorpc.Context.get_instance().register_middleware(opbeat) 25 | 26 | By default, the middleware will hide internal frames from ZeroRPC when it 27 | submits exceptions to Opbeat. This behavior can be disabled by passing the 28 | ``hide_zerorpc_frames`` parameter to the middleware 29 | 30 | .. code:: 31 | :class: language-python 32 | 33 | opbeat = OpbeatMiddleware(hide_zerorpc_frames=False, organization_id='', ...) 34 | 35 | Caveats 36 | ------- 37 | 38 | Sending an exception to Opbeat will basically block your RPC call. 39 | In any cases, a clean and long term solution would be to make opbeat requests 40 | to the Opbeat server asynchronous. 41 | -------------------------------------------------------------------------------- /opbeat/transport/base.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from opbeat.transport.exceptions import InvalidScheme 4 | 5 | 6 | class TransportException(Exception): 7 | def __init__(self, message, data=None, print_trace=True): 8 | super(TransportException, self).__init__(message) 9 | self.data = data 10 | self.print_trace = print_trace 11 | 12 | 13 | class Transport(object): 14 | """ 15 | All transport implementations need to subclass this class 16 | 17 | You must implement a send method.. 18 | """ 19 | async_mode = False 20 | scheme = [] 21 | 22 | def check_scheme(self, url): 23 | if url.scheme not in self.scheme: 24 | raise InvalidScheme() 25 | 26 | def send(self, data, headers): 27 | """ 28 | You need to override this to do something with the actual 29 | data. Usually - this is sending to a server 30 | """ 31 | raise NotImplementedError 32 | 33 | def close(self): 34 | """ 35 | Cleans up resources and closes connection 36 | :return: 37 | """ 38 | pass 39 | 40 | 41 | class AsyncTransport(Transport): 42 | async_mode = True 43 | 44 | def send_async(self, data, headers, success_callback=None, fail_callback=None): 45 | raise NotImplementedError 46 | -------------------------------------------------------------------------------- /tests/instrumentation/botocore_tests.py: -------------------------------------------------------------------------------- 1 | import boto3 2 | import mock 3 | 4 | import opbeat 5 | import opbeat.instrumentation.control 6 | from opbeat.traces import trace 7 | from tests.helpers import get_tempstoreclient 8 | from tests.utils.compat import TestCase 9 | 10 | 11 | class InstrumentBotocoreTest(TestCase): 12 | def setUp(self): 13 | self.client = get_tempstoreclient() 14 | opbeat.instrumentation.control.instrument() 15 | 16 | @mock.patch("botocore.endpoint.Endpoint.make_request") 17 | def test_botocore_instrumentation(self, mock_make_request): 18 | mock_response = mock.Mock() 19 | mock_response.status_code = 200 20 | mock_make_request.return_value = (mock_response, {}) 21 | 22 | self.client.begin_transaction("transaction.test") 23 | with trace("test_pipeline", "test"): 24 | session = boto3.Session(aws_access_key_id='foo', 25 | aws_secret_access_key='bar', 26 | region_name='us-west-2') 27 | ec2 = session.client('ec2') 28 | ec2.describe_instances() 29 | self.client.end_transaction("MyView") 30 | 31 | _, traces = self.client.instrumentation_store.get_all() 32 | self.assertIn('ec2:DescribeInstances', map(lambda x: x['signature'], traces)) 33 | -------------------------------------------------------------------------------- /opbeat/utils/wrapt/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, Graham Dumpleton 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Opbeat for Python 2 | ================= 3 | 4 | .. image:: https://api.travis-ci.org/opbeat/opbeat_python.svg?branch=master 5 | :target: https://travis-ci.org/opbeat/opbeat_python 6 | :alt: Build Status 7 | 8 | .. image:: https://img.shields.io/pypi/v/opbeat.svg?style=flat 9 | :target: https://pypi.python.org/pypi/opbeat/ 10 | :alt: Latest Version 11 | 12 | .. image:: https://img.shields.io/pypi/pyversions/opbeat.svg?style=flat 13 | :target: https://pypi.python.org/pypi/opbeat/ 14 | :alt: Supported Python versions 15 | 16 | 17 | This is the official Python module for `Opbeat `_. 18 | 19 | It provides full out-of-the-box support for many of the popular frameworks, 20 | including Django, and Flask. Opbeat also includes drop-in support for any 21 | WSGI-compatible web application. 22 | 23 | Your application doesn't live on the web? No problem! Opbeat is easy to use in 24 | any Python application. 25 | 26 | 27 | Documentation 28 | ------------- 29 | 30 | * `Documentation overview `_ 31 | * `Get started with Django `_ 32 | * `Get started with Flask `_ 33 | * `Get started with a custom Python stack `_ 34 | 35 | 36 | License 37 | ------- 38 | 39 | BSD-3-Clause 40 | 41 | 42 | Made with ♥️ and ☕️ by Opbeat and our community. 43 | -------------------------------------------------------------------------------- /opbeat/instrumentation/packages/django/template.py: -------------------------------------------------------------------------------- 1 | from opbeat.instrumentation.packages.base import AbstractInstrumentedModule 2 | from opbeat.traces import trace 3 | 4 | 5 | class DjangoTemplateInstrumentation(AbstractInstrumentedModule): 6 | name = 'django_template' 7 | 8 | instrument_list = [ 9 | ("django.template", "Template.render"), 10 | ] 11 | 12 | def call(self, module, method, wrapped, instance, args, kwargs): 13 | name = getattr(instance, 'name', None) 14 | 15 | if not name: 16 | name = '