Skip to content

Commit 9b772d1

Browse files
authored
Merge pull request #371 from DataDog/benjamin/distributed-tornado
Implement distributed tracing for Tornado web
2 parents 8ed3b4b + c91995e commit 9b772d1

File tree

5 files changed

+97
-5
lines changed

5 files changed

+97
-5
lines changed

ddtrace/contrib/tornado/__init__.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ def notify(self):
5454
'datadog_trace': {
5555
'default_service': 'my-tornado-app',
5656
'tags': {'env': 'production'},
57+
'distributed_tracing': True,
5758
},
5859
}
5960
@@ -66,8 +67,11 @@ def notify(self):
6667
* ``default_service`` (default: `tornado-web`): set the service name used by the tracer. Usually
6768
this configuration must be updated with a meaningful name.
6869
* ``tags`` (default: `{}`): set global tags that should be applied to all spans.
69-
* ``enabled`` (default: `true`): define if the tracer is enabled or not. If set to `false`, the
70+
* ``enabled`` (default: `True`): define if the tracer is enabled or not. If set to `false`, the
7071
code is still instrumented but no spans are sent to the APM agent.
72+
* ``distributed_tracing`` (default: `False`): enable distributed tracing if this is called
73+
remotely from an instrumented application.
74+
We suggest to enable it only for internal services where headers are under your control.
7175
* ``agent_hostname`` (default: `localhost`): define the hostname of the APM agent.
7276
* ``agent_port`` (default: `8126`): define the port of the APM agent.
7377
"""

ddtrace/contrib/tornado/application.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ def tracer_config(__init__, app, args, kwargs):
2020
settings = {
2121
'tracer': ddtrace.tracer,
2222
'default_service': 'tornado-web',
23+
'distributed_tracing': False,
2324
}
2425

2526
# update defaults with users settings

ddtrace/contrib/tornado/handlers.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from .constants import CONFIG_KEY, REQUEST_CONTEXT_KEY, REQUEST_SPAN_KEY
44
from .stack_context import TracerStackContext
55
from ...ext import http
6+
from ...propagation.http import HTTPPropagator
67

78

89
def execute(func, handler, args, kwargs):
@@ -15,11 +16,19 @@ def execute(func, handler, args, kwargs):
1516
settings = handler.settings[CONFIG_KEY]
1617
tracer = settings['tracer']
1718
service = settings['default_service']
19+
distributed_tracing = settings['distributed_tracing']
1820

1921
with TracerStackContext():
2022
# attach the context to the request
2123
setattr(handler.request, REQUEST_CONTEXT_KEY, tracer.get_call_context())
2224

25+
# Read and use propagated context from HTTP headers
26+
if distributed_tracing:
27+
propagator = HTTPPropagator()
28+
context = propagator.extract(handler.request.headers)
29+
if context.trace_id:
30+
tracer.context_provider.activate(context)
31+
2332
# store the request span in the request so that it can be used later
2433
request_span = tracer.trace(
2534
'tornado.request',

docs/index.rst

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -286,15 +286,21 @@ Supported web frameworks:
286286

287287
- Django
288288
- Flask
289+
- Tornado
289290

291+
For web servers not supported, you can extract the HTTP context from the headers using the `HTTPPropagator`.
290292

291-
HTTP client/server
292-
~~~~~~~~~~~~~~~~~~
293+
.. autoclass:: ddtrace.propagation.http.HTTPPropagator
294+
:members: extract
295+
296+
HTTP client
297+
~~~~~~~~~~~
293298

294-
You can use the `HTTPPropagator` manually when used with an HTTP client or if your web framework isn't supported.
299+
When calling a remote HTTP server part of the distributed trace, you have to propagate the HTTP headers.
300+
This is not done automatically to prevent your system from leaking tracing information to external services.
295301

296302
.. autoclass:: ddtrace.propagation.http.HTTPPropagator
297-
:members:
303+
:members: inject
298304

299305
Custom
300306
~~~~~~

tests/contrib/tornado/test_tornado_web.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,20 @@
33
from .web.app import CustomDefaultHandler
44
from .utils import TornadoTestCase
55

6+
from ddtrace.constants import SAMPLING_PRIORITY_KEY
7+
68

79
class TestTornadoWeb(TornadoTestCase):
810
"""
911
Ensure that Tornado web handlers are properly traced.
1012
"""
13+
def get_settings(self):
14+
return {
15+
'datadog_trace': {
16+
'distributed_tracing': True,
17+
}
18+
}
19+
1120
def test_success_handler(self):
1221
# it should trace a handler that returns 200
1322
response = self.fetch('/success/')
@@ -227,6 +236,69 @@ def test_static_handler(self):
227236
eq_('/statics/empty.txt', request_span.get_tag('http.url'))
228237
eq_(0, request_span.error)
229238

239+
def test_propagation(self):
240+
# it should trace a handler that returns 200 with a propagated context
241+
headers = {
242+
'x-datadog-trace-id': '1234',
243+
'x-datadog-parent-id': '4567',
244+
'x-datadog-sampling-priority': '2'
245+
}
246+
response = self.fetch('/success/', headers=headers)
247+
eq_(200, response.code)
248+
249+
traces = self.tracer.writer.pop_traces()
250+
eq_(1, len(traces))
251+
eq_(1, len(traces[0]))
252+
253+
request_span = traces[0][0]
254+
255+
# simple sanity check on the span
256+
eq_('tornado.request', request_span.name)
257+
eq_('200', request_span.get_tag('http.status_code'))
258+
eq_('/success/', request_span.get_tag('http.url'))
259+
eq_(0, request_span.error)
260+
261+
# check propagation
262+
eq_(1234, request_span.trace_id)
263+
eq_(4567, request_span.parent_id)
264+
eq_(2, request_span.get_metric(SAMPLING_PRIORITY_KEY))
265+
266+
267+
class TestNoPropagationTornadoWeb(TornadoTestCase):
268+
"""
269+
Ensure that Tornado web handlers are properly traced and are ignoring propagated HTTP headers when disabled.
270+
"""
271+
def get_settings(self):
272+
# distributed_tracing should be disabled by default
273+
return {}
274+
275+
def test_no_propagation(self):
276+
# it should not propagate the HTTP context
277+
headers = {
278+
'x-datadog-trace-id': '1234',
279+
'x-datadog-parent-id': '4567',
280+
'x-datadog-sampling-priority': '2'
281+
}
282+
response = self.fetch('/success/', headers=headers)
283+
eq_(200, response.code)
284+
285+
traces = self.tracer.writer.pop_traces()
286+
eq_(1, len(traces))
287+
eq_(1, len(traces[0]))
288+
289+
request_span = traces[0][0]
290+
291+
# simple sanity check on the span
292+
eq_('tornado.request', request_span.name)
293+
eq_('200', request_span.get_tag('http.status_code'))
294+
eq_('/success/', request_span.get_tag('http.url'))
295+
eq_(0, request_span.error)
296+
297+
# check non-propagation
298+
assert request_span.trace_id != 1234
299+
assert request_span.parent_id != 4567
300+
assert request_span.get_metric(SAMPLING_PRIORITY_KEY) != 2
301+
230302

231303
class TestCustomTornadoWeb(TornadoTestCase):
232304
"""

0 commit comments

Comments
 (0)