Skip to content

Commit 2f5ac8c

Browse files
authored
Merge pull request #366 from DataDog/benjamin/distributed-django
Add distributed sampling support to Django
2 parents 1159808 + 46db440 commit 2f5ac8c

File tree

6 files changed

+80
-7
lines changed

6 files changed

+80
-7
lines changed

ddtrace/contrib/django/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@
4444
are sent to the trace agent. This setting cannot be changed at runtime
4545
and a restart is required. By default the tracer is disabled when in ``DEBUG``
4646
mode, enabled otherwise.
47+
* ``DISTRIBUTED_TRACING`` (default: ``False``): defines if the tracer should
48+
use incoming X-DATADOG-* HTTP headers to extend a trace created remotely. It is
49+
required for distributed tracing if this application is called remotely from another
50+
instrumented application.
51+
We suggest to enable it only for internal services where headers are under your control.
4752
* ``AGENT_HOSTNAME`` (default: ``localhost``): define the hostname of the trace agent.
4853
* ``AGENT_PORT`` (default: ``8126``): define the port of the trace agent.
4954
* ``AUTO_INSTRUMENT`` (default: ``True``): if set to false the code will not be

ddtrace/contrib/django/conf.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
'DEFAULT_DATABASE_PREFIX': '',
3535
'DEFAULT_SERVICE': 'django',
3636
'ENABLED': True,
37+
'DISTRIBUTED_TRACING': False,
3738
'TAGS': {},
3839
'TRACER': 'ddtrace.tracer',
3940
}

ddtrace/contrib/django/middleware.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
from ...ext import http
77
from ...contrib import func_name
8+
from ...propagation.http import HTTPPropagator
89

910
# 3p
1011
from django.core.exceptions import MiddlewareNotUsed
@@ -80,6 +81,12 @@ class TraceMiddleware(InstrumentationMixin):
8081
"""
8182
def process_request(self, request):
8283
tracer = settings.TRACER
84+
if settings.DISTRIBUTED_TRACING:
85+
propagator = HTTPPropagator()
86+
context = propagator.extract(request.META)
87+
# Only need to active the new context if something was propagated
88+
if context.trace_id:
89+
tracer.context_provider.activate(context)
8390
try:
8491
span = tracer.trace(
8592
'django.request',

ddtrace/propagation/http.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,11 @@ def extract(self, headers):
3939
4040
from ddtrace.propagation.http import HTTPPropagator
4141
42-
def child_call(url, headers):
42+
def my_controller(url, headers):
4343
context = HTTPPropagator.extract(headers)
4444
tracer.context_provider.activate(context)
4545
46-
with tracer.trace("child_span") as span:
46+
with tracer.trace("my_controller") as span:
4747
span.set_meta('http.url', url)
4848
4949
:param dict headers: HTTP headers to extract tracing attributes.

docs/index.rst

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -272,13 +272,25 @@ Distributed Tracing
272272

273273
To trace requests across hosts, the spans on the secondary hosts must be linked together by setting `trace_id`, `parent_id` and `sampling_priority`.
274274

275+
- On the server side, it means to read propagated attributes and set them to the active tracing context.
276+
- On the client side, it means to propagate the attributes, commonly as a header/metadata.
277+
275278
`ddtrace` already provides default propagators but you can also implement your own.
276279

277-
HTTP
278-
~~~~
280+
Web frameworks
281+
~~~~~~~~~~~~~~
282+
283+
Some web framework integrations support the distributed tracing out of the box, you just have to enable it.
284+
For that, refer to the configuration of the given integration.
285+
Supported web frameworks:
286+
287+
- Django
288+
289+
290+
HTTP client/server
291+
~~~~~~~~~~~~~~~~~~
279292

280-
The `HTTPPropagator` is already automatically used in our `aiohttp` integration. For the others, you can use
281-
it manually.
293+
You can use the `HTTPPropagator` manually when used with an HTTP client or if your web framework isn't supported.
282294

283295
.. autoclass:: ddtrace.propagation.http.HTTPPropagator
284296
:members:

tests/contrib/django/test_middleware.py

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@
55
from django.core.urlresolvers import reverse
66

77
# project
8+
from ddtrace.constants import SAMPLING_PRIORITY_KEY
89
from ddtrace.contrib.django.conf import settings
910
from ddtrace.contrib.django import TraceMiddleware
1011

1112
# testing
12-
from .utils import DjangoTraceTestCase
13+
from .utils import DjangoTraceTestCase, override_ddtrace_settings
1314

1415

1516
class DjangoMiddlewareTest(DjangoTraceTestCase):
@@ -143,3 +144,50 @@ def test_middleware_without_user(self):
143144
sp_database = spans[2]
144145
eq_(sp_request.get_tag('http.status_code'), '200')
145146
eq_(sp_request.get_tag('django.user.is_authenticated'), None)
147+
148+
@override_ddtrace_settings(DISTRIBUTED_TRACING=True)
149+
def test_middleware_propagation(self):
150+
# ensures that we properly propagate http context
151+
url = reverse('users-list')
152+
headers = {
153+
'x-datadog-trace-id': '100',
154+
'x-datadog-parent-id': '42',
155+
'x-datadog-sampling-priority': '2',
156+
}
157+
response = self.client.get(url, **headers)
158+
eq_(response.status_code, 200)
159+
160+
# check for spans
161+
spans = self.tracer.writer.pop()
162+
eq_(len(spans), 3)
163+
sp_request = spans[0]
164+
sp_template = spans[1]
165+
sp_database = spans[2]
166+
167+
# Check for proper propagated attributes
168+
eq_(sp_request.trace_id, 100)
169+
eq_(sp_request.parent_id, 42)
170+
eq_(sp_request.get_metric(SAMPLING_PRIORITY_KEY), 2)
171+
172+
def test_middleware_no_propagation(self):
173+
# ensures that we properly propagate http context
174+
url = reverse('users-list')
175+
headers = {
176+
'x-datadog-trace-id': '100',
177+
'x-datadog-parent-id': '42',
178+
'x-datadog-sampling-priority': '2',
179+
}
180+
response = self.client.get(url, **headers)
181+
eq_(response.status_code, 200)
182+
183+
# check for spans
184+
spans = self.tracer.writer.pop()
185+
eq_(len(spans), 3)
186+
sp_request = spans[0]
187+
sp_template = spans[1]
188+
sp_database = spans[2]
189+
190+
# Check that propagation didn't happen
191+
assert sp_request.trace_id != 100
192+
assert sp_request.parent_id != 42
193+
assert sp_request.get_metric(SAMPLING_PRIORITY_KEY) != 2

0 commit comments

Comments
 (0)