Skip to content

Commit b2e9349

Browse files
author
Emanuele Palazzetti
authored
Merge pull request #314 from mcanaves/django_flexible_configuration
[django] disable db or cache instrumentation
2 parents d248309 + dbb01ff commit b2e9349

File tree

15 files changed

+263
-94
lines changed

15 files changed

+263
-94
lines changed

ddtrace/contrib/django/__init__.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,6 @@
1313
'ddtrace.contrib.django',
1414
]
1515
16-
# or MIDDLEWARE_CLASSES for Django pre 1.10
17-
MIDDLEWARE = [
18-
# the tracer must be the first middleware
19-
'ddtrace.contrib.django.TraceMiddleware',
20-
21-
# your middlewares...
22-
]
23-
2416
The configuration for this integration is namespaced under the ``DATADOG_TRACE``
2517
Django setting. For example, your ``settings.py`` may contain::
2618
@@ -54,10 +46,18 @@
5446
and a restart is required. By default the tracer is disabled when in ``DEBUG``
5547
mode, enabled otherwise.
5648
* ``AUTO_INSTRUMENT`` (default: ``True``): if set to false the code will not be
57-
instrumented, while the tracer may be active for your internal usage. This could
58-
be useful if you want to use the Django integration, but you want to trace only
59-
particular functions or views. If set to False, the request middleware will be
60-
disabled even if present.
49+
instrumented (even if ``INSTRUMENT_DATABASE``, ``INSTRUMENT_CACHE`` or
50+
``INSTRUMENT_TEMPLATE`` are set to ``True``), while the tracer may be active
51+
for your internal usage. This could be useful if you want to use the Django
52+
integration, but you want to trace only particular functions or views. If set
53+
to False, the request middleware will be disabled even if present.
54+
* ``INSTRUMENT_DATABASE`` (default: ``True``): if set to ``False`` database will not
55+
be instrumented. Only configurable when ``AUTO_INSTRUMENT`` is set to ``True``.
56+
* ``INSTRUMENT_CACHE`` (default: ``True``): if set to ``False`` cache will not
57+
be instrumented. Only configurable when ``AUTO_INSTRUMENT`` is set to ``True``.
58+
* ``INSTRUMENT_TEMPLATE`` (default: ``True``): if set to ``False`` template
59+
rendering will not be instrumented. Only configurable when ``AUTO_INSTRUMENT``
60+
is set to ``True``.
6161
* ``AGENT_HOSTNAME`` (default: ``localhost``): define the hostname of the trace agent.
6262
* ``AGENT_PORT`` (default: ``8126``): define the port of the trace agent.
6363
"""

ddtrace/contrib/django/apps.py

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from .conf import settings
99
from .cache import patch_cache
1010
from .templates import patch_template
11-
from .middleware import insert_exception_middleware
11+
from .middleware import insert_exception_middleware, insert_trace_middleware
1212

1313
from ...ext import AppTypes
1414

@@ -48,18 +48,23 @@ def ready(self):
4848

4949
if settings.AUTO_INSTRUMENT:
5050
# trace Django internals
51+
insert_trace_middleware()
5152
insert_exception_middleware()
52-
try:
53-
patch_db(tracer)
54-
except Exception:
55-
log.exception('error patching Django database connections')
5653

57-
try:
58-
patch_template(tracer)
59-
except Exception:
60-
log.exception('error patching Django template rendering')
54+
if settings.INSTRUMENT_TEMPLATE:
55+
try:
56+
patch_template(tracer)
57+
except Exception:
58+
log.exception('error patching Django template rendering')
6159

62-
try:
63-
patch_cache(tracer)
64-
except Exception:
65-
log.exception('error patching Django cache')
60+
if settings.INSTRUMENT_DATABASE:
61+
try:
62+
patch_db(tracer)
63+
except Exception:
64+
log.exception('error patching Django database connections')
65+
66+
if settings.INSTRUMENT_CACHE:
67+
try:
68+
patch_cache(tracer)
69+
except Exception:
70+
log.exception('error patching Django cache')

ddtrace/contrib/django/cache.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,3 +89,19 @@ def _wrap_method(cls, method_name):
8989

9090
for method in TRACED_METHODS:
9191
_wrap_method(cache, method)
92+
93+
def unpatch_method(cls, method_name):
94+
method = getattr(cls, DATADOG_NAMESPACE.format(method=method_name), None)
95+
if method is None:
96+
log.debug('nothing to do, the class is not patched')
97+
return
98+
setattr(cls, method_name, method)
99+
delattr(cls, DATADOG_NAMESPACE.format(method=method_name))
100+
101+
def unpatch_cache():
102+
cache_backends = [cache['BACKEND'] for cache in django_settings.CACHES.values()]
103+
for cache_module in cache_backends:
104+
cache = import_from_string(cache_module, cache_module)
105+
106+
for method in TRACED_METHODS:
107+
unpatch_method(cache, method)

ddtrace/contrib/django/conf.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@
2525
'AGENT_HOSTNAME': 'localhost',
2626
'AGENT_PORT': 8126,
2727
'AUTO_INSTRUMENT': True,
28+
'INSTRUMENT_CACHE': True,
29+
'INSTRUMENT_DATABASE': True,
30+
'INSTRUMENT_TEMPLATE': True,
2831
'DEFAULT_DATABASE_PREFIX': '',
2932
'DEFAULT_SERVICE': 'django',
3033
'ENABLED': True,

ddtrace/contrib/django/db.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,25 +12,37 @@
1212

1313
log = logging.getLogger(__name__)
1414

15+
CURSOR_ATTR = '_datadog_original_cursor'
16+
1517

1618
def patch_db(tracer):
1719
for c in connections.all():
1820
patch_conn(tracer, c)
1921

22+
def unpatch_db():
23+
for c in connections.all():
24+
unpatch_conn(c)
25+
2026

2127
def patch_conn(tracer, conn):
22-
attr = '_datadog_original_cursor'
23-
if hasattr(conn, attr):
28+
if hasattr(conn, CURSOR_ATTR):
2429
log.debug("already patched")
2530
return
2631

27-
conn._datadog_original_cursor = conn.cursor
32+
setattr(conn, CURSOR_ATTR, conn.cursor)
2833

2934
def cursor():
3035
return TracedCursor(tracer, conn, conn._datadog_original_cursor())
3136

3237
conn.cursor = cursor
3338

39+
def unpatch_conn(conn):
40+
cursor = getattr(conn, CURSOR_ATTR, None)
41+
if cursor is None:
42+
log.debug('nothing to do, the connection is not patched')
43+
return
44+
conn.cursor = cursor
45+
delattr(conn, CURSOR_ATTR)
3446

3547
class TracedCursor(object):
3648

ddtrace/contrib/django/middleware.py

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,35 @@
1818

1919
log = logging.getLogger(__name__)
2020

21+
EXCEPTION_MIDDLEWARE = 'ddtrace.contrib.django.TraceExceptionMiddleware'
22+
TRACE_MIDDLEWARE = 'ddtrace.contrib.django.TraceMiddleware'
23+
MIDDLEWARE_ATTRIBUTES = ['MIDDLEWARE', 'MIDDLEWARE_CLASSES']
24+
25+
def insert_trace_middleware():
26+
for middleware_attribute in MIDDLEWARE_ATTRIBUTES:
27+
middleware = getattr(django_settings, middleware_attribute, None)
28+
if middleware is not None and TRACE_MIDDLEWARE not in set(middleware):
29+
setattr(django_settings, middleware_attribute, type(middleware)((TRACE_MIDDLEWARE,)) + middleware)
30+
break
31+
32+
def remove_trace_middleware():
33+
for middleware_attribute in MIDDLEWARE_ATTRIBUTES:
34+
middleware = getattr(django_settings, middleware_attribute, None)
35+
if middleware and TRACE_MIDDLEWARE in set(middleware):
36+
middleware.remove(TRACE_MIDDLEWARE)
37+
2138
def insert_exception_middleware():
22-
exception_middleware = 'ddtrace.contrib.django.TraceExceptionMiddleware'
23-
middleware_attributes = ['MIDDLEWARE', 'MIDDLEWARE_CLASSES']
24-
for middleware_attribute in middleware_attributes:
39+
for middleware_attribute in MIDDLEWARE_ATTRIBUTES:
2540
middleware = getattr(django_settings, middleware_attribute, None)
26-
if middleware and exception_middleware not in set(middleware):
27-
setattr(django_settings, middleware_attribute, middleware + type(middleware)((exception_middleware,)))
41+
if middleware is not None and EXCEPTION_MIDDLEWARE not in set(middleware):
42+
setattr(django_settings, middleware_attribute, middleware + type(middleware)((EXCEPTION_MIDDLEWARE,)))
43+
break
2844

45+
def remove_exception_middleware():
46+
for middleware_attribute in MIDDLEWARE_ATTRIBUTES:
47+
middleware = getattr(django_settings, middleware_attribute, None)
48+
if middleware and EXCEPTION_MIDDLEWARE in set(middleware):
49+
middleware.remove(EXCEPTION_MIDDLEWARE)
2950

3051
class InstrumentationMixin(MiddlewareClass):
3152
"""

ddtrace/contrib/django/patch.py

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,4 @@ def traced_setup(wrapped, instance, args, kwargs):
2222
else:
2323
settings.INSTALLED_APPS.append('ddtrace.contrib.django')
2424

25-
if hasattr(settings, 'MIDDLEWARE_CLASSES'):
26-
if 'ddtrace.contrib.django.TraceMiddleware' not in settings.MIDDLEWARE_CLASSES:
27-
if isinstance(settings.MIDDLEWARE_CLASSES, tuple):
28-
# MIDDLEWARE_CLASSES is a tuple < 1.9
29-
settings.MIDDLEWARE_CLASSES = ('ddtrace.contrib.django.TraceMiddleware', ) + settings.MIDDLEWARE_CLASSES
30-
else:
31-
settings.MIDDLEWARE_CLASSES.insert(0, 'ddtrace.contrib.django.TraceMiddleware')
32-
33-
if hasattr(settings, 'MIDDLEWARE'):
34-
if 'ddtrace.contrib.django.TraceMiddleware' not in settings.MIDDLEWARE:
35-
settings.MIDDLEWARE.insert(0, 'ddtrace.contrib.django.TraceMiddleware')
36-
3725
wrapped(*args, **kwargs)

ddtrace/contrib/django/templates.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
log = logging.getLogger(__name__)
1717

18+
RENDER_ATTR = '_datadog_original_render'
1819

1920
def patch_template(tracer):
2021
""" will patch django's template rendering function to include timing
@@ -24,12 +25,11 @@ def patch_template(tracer):
2425
# FIXME[matt] we're patching the template class here. ideally we'd only
2526
# patch so we can use multiple tracers at once, but i suspect this is fine
2627
# in practice.
27-
attr = '_datadog_original_render'
28-
if getattr(Template, attr, None):
28+
if getattr(Template, RENDER_ATTR, None):
2929
log.debug("already patched")
3030
return
3131

32-
setattr(Template, attr, Template.render)
32+
setattr(Template, RENDER_ATTR, Template.render)
3333

3434
def traced_render(self, context):
3535
with tracer.trace('django.template', span_type=http.TEMPLATE) as span:
@@ -41,3 +41,11 @@ def traced_render(self, context):
4141
span.set_tag('django.template_name', template_name)
4242

4343
Template.render = traced_render
44+
45+
def unpatch_template():
46+
render = getattr(Template, RENDER_ATTR, None)
47+
if render is None:
48+
log.debug('nothing to do Template is already patched')
49+
return
50+
Template.render = render
51+
delattr(Template, RENDER_ATTR)

tests/contrib/django/app/settings.py

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
Django during tests
55
"""
66
import os
7+
import django
78

89

910
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
@@ -68,28 +69,22 @@
6869
},
6970
]
7071

71-
# 1.10+ style
72-
MIDDLEWARE = [
73-
# tracer middleware
74-
'ddtrace.contrib.django.TraceMiddleware',
75-
76-
'django.contrib.sessions.middleware.SessionMiddleware',
77-
'django.middleware.common.CommonMiddleware',
78-
'django.middleware.csrf.CsrfViewMiddleware',
79-
'django.contrib.auth.middleware.AuthenticationMiddleware',
80-
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
81-
'django.contrib.messages.middleware.MessageMiddleware',
82-
'django.middleware.clickjacking.XFrameOptionsMiddleware',
83-
'django.middleware.security.SecurityMiddleware',
84-
85-
'tests.contrib.django.app.middlewares.CatchExceptionMiddleware',
86-
]
72+
if django.VERSION >= (1, 10):
73+
MIDDLEWARE = [
74+
'django.contrib.sessions.middleware.SessionMiddleware',
75+
'django.middleware.common.CommonMiddleware',
76+
'django.middleware.csrf.CsrfViewMiddleware',
77+
'django.contrib.auth.middleware.AuthenticationMiddleware',
78+
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
79+
'django.contrib.messages.middleware.MessageMiddleware',
80+
'django.middleware.clickjacking.XFrameOptionsMiddleware',
81+
'django.middleware.security.SecurityMiddleware',
8782

83+
'tests.contrib.django.app.middlewares.CatchExceptionMiddleware',
84+
]
85+
# Always add the legacy conf to make sure we handle it properly
8886
# Pre 1.10 style
8987
MIDDLEWARE_CLASSES = [
90-
# tracer middleware
91-
'ddtrace.contrib.django.TraceMiddleware',
92-
9388
'django.contrib.sessions.middleware.SessionMiddleware',
9489
'django.middleware.common.CommonMiddleware',
9590
'django.middleware.csrf.CsrfViewMiddleware',
Lines changed: 48 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,69 @@
1+
import django
2+
13
from ddtrace.monkey import patch
24
from .utils import DjangoTraceTestCase
35
from nose.tools import eq_, ok_
6+
from django.conf import settings
7+
from unittest import skipIf
8+
49

510
class DjangoAutopatchTest(DjangoTraceTestCase):
6-
def test_autopatching(self):
11+
def setUp(self):
12+
super(DjangoAutopatchTest, self).setUp()
713
patch(django=True)
8-
9-
import django
10-
ok_(django._datadog_patch)
1114
django.setup()
1215

13-
from django.conf import settings
16+
@skipIf(django.VERSION >= (1, 10), 'skip if version above 1.10')
17+
def test_autopatching_middleware_classes(self):
18+
ok_(django._datadog_patch)
1419
ok_('ddtrace.contrib.django' in settings.INSTALLED_APPS)
1520
eq_(settings.MIDDLEWARE_CLASSES[0], 'ddtrace.contrib.django.TraceMiddleware')
21+
eq_(settings.MIDDLEWARE_CLASSES[-1], 'ddtrace.contrib.django.TraceExceptionMiddleware')
1622

1723

18-
def test_autopatching_twice(self):
19-
patch(django=True)
20-
24+
@skipIf(django.VERSION >= (1, 10), 'skip if version above 1.10')
25+
def test_autopatching_twice_middleware_classes(self):
26+
ok_(django._datadog_patch)
2127
# Call django.setup() twice and ensure we don't add a duplicate tracer
22-
import django
23-
django.setup()
2428
django.setup()
2529

26-
from django.conf import settings
27-
found_app = 0
30+
found_app = settings.INSTALLED_APPS.count('ddtrace.contrib.django')
31+
eq_(found_app, 1)
2832

29-
for app in settings.INSTALLED_APPS:
30-
if app == 'ddtrace.contrib.django':
31-
found_app += 1
33+
eq_(settings.MIDDLEWARE_CLASSES[0], 'ddtrace.contrib.django.TraceMiddleware')
34+
eq_(settings.MIDDLEWARE_CLASSES[-1], 'ddtrace.contrib.django.TraceExceptionMiddleware')
3235

36+
found_mw = settings.MIDDLEWARE_CLASSES.count('ddtrace.contrib.django.TraceMiddleware')
37+
eq_(found_mw, 1)
38+
found_mw = settings.MIDDLEWARE_CLASSES.count('ddtrace.contrib.django.TraceExceptionMiddleware')
39+
eq_(found_mw, 1)
40+
41+
@skipIf(django.VERSION < (1, 10), 'skip if version is below 1.10')
42+
def test_autopatching_middleware(self):
43+
ok_(django._datadog_patch)
44+
ok_('ddtrace.contrib.django' in settings.INSTALLED_APPS)
45+
eq_(settings.MIDDLEWARE[0], 'ddtrace.contrib.django.TraceMiddleware')
46+
ok_('ddtrace.contrib.django.TraceMiddleware' not in settings.MIDDLEWARE_CLASSES)
47+
eq_(settings.MIDDLEWARE[-1], 'ddtrace.contrib.django.TraceExceptionMiddleware')
48+
ok_('ddtrace.contrib.django.TraceExceptionMiddleware' not in settings.MIDDLEWARE_CLASSES)
49+
50+
51+
@skipIf(django.VERSION < (1, 10), 'skip if version is below 1.10')
52+
def test_autopatching_twice_middleware(self):
53+
ok_(django._datadog_patch)
54+
# Call django.setup() twice and ensure we don't add a duplicate tracer
55+
django.setup()
56+
57+
found_app = settings.INSTALLED_APPS.count('ddtrace.contrib.django')
3358
eq_(found_app, 1)
34-
eq_(settings.MIDDLEWARE_CLASSES[0], 'ddtrace.contrib.django.TraceMiddleware')
3559

36-
found_mw = 0
37-
for mw in settings.MIDDLEWARE_CLASSES:
38-
if mw == 'ddtrace.contrib.django.TraceMiddleware':
39-
found_mw += 1
60+
eq_(settings.MIDDLEWARE[0], 'ddtrace.contrib.django.TraceMiddleware')
61+
ok_('ddtrace.contrib.django.TraceMiddleware' not in settings.MIDDLEWARE_CLASSES)
62+
eq_(settings.MIDDLEWARE[-1], 'ddtrace.contrib.django.TraceExceptionMiddleware')
63+
ok_('ddtrace.contrib.django.TraceExceptionMiddleware' not in settings.MIDDLEWARE_CLASSES)
64+
65+
found_mw = settings.MIDDLEWARE.count('ddtrace.contrib.django.TraceMiddleware')
66+
eq_(found_mw, 1)
4067

68+
found_mw = settings.MIDDLEWARE.count('ddtrace.contrib.django.TraceExceptionMiddleware')
4169
eq_(found_mw, 1)

0 commit comments

Comments
 (0)