Skip to content

Commit 0bae457

Browse files
authored
[core] Add WSGI http header support to HTTP propagator (#522)
* [core] add WSGI compatible http header support to propagator * [core] clean up old TODOs, unused imports * [core] use string literals in tests instead of constants
1 parent 7a3d0f6 commit 0bae457

File tree

4 files changed

+95
-16
lines changed

4 files changed

+95
-16
lines changed

ddtrace/propagation/http.py

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,28 @@
22

33
from ..context import Context
44

5+
from .utils import get_wsgi_header
6+
57
log = logging.getLogger(__name__)
68

79
# HTTP headers one should set for distributed tracing.
810
# These are cross-language (eg: Python, Go and other implementations should honor these)
9-
HTTP_HEADER_TRACE_ID = 'x-datadog-trace-id'
10-
HTTP_HEADER_PARENT_ID = 'x-datadog-parent-id'
11-
HTTP_HEADER_SAMPLING_PRIORITY = 'x-datadog-sampling-priority'
11+
HTTP_HEADER_TRACE_ID = "x-datadog-trace-id"
12+
HTTP_HEADER_PARENT_ID = "x-datadog-parent-id"
13+
HTTP_HEADER_SAMPLING_PRIORITY = "x-datadog-sampling-priority"
14+
15+
16+
# Note that due to WSGI spec we have to also check for uppercased and prefixed
17+
# versions of these headers
18+
POSSIBLE_HTTP_HEADER_TRACE_IDS = frozenset(
19+
[HTTP_HEADER_TRACE_ID, get_wsgi_header(HTTP_HEADER_TRACE_ID)]
20+
)
21+
POSSIBLE_HTTP_HEADER_PARENT_IDS = frozenset(
22+
[HTTP_HEADER_PARENT_ID, get_wsgi_header(HTTP_HEADER_PARENT_ID)]
23+
)
24+
POSSIBLE_HTTP_HEADER_SAMPLING_PRIORITIES = frozenset(
25+
[HTTP_HEADER_SAMPLING_PRIORITY, get_wsgi_header(HTTP_HEADER_SAMPLING_PRIORITY)]
26+
)
1227

1328

1429
class HTTPPropagator(object):
@@ -39,6 +54,36 @@ def parent_call():
3954
if sampling_priority is not None:
4055
headers[HTTP_HEADER_SAMPLING_PRIORITY] = str(span_context.sampling_priority)
4156

57+
@staticmethod
58+
def extract_trace_id(headers):
59+
trace_id = 0
60+
61+
for key in POSSIBLE_HTTP_HEADER_TRACE_IDS:
62+
if key in headers:
63+
trace_id = headers.get(key)
64+
65+
return int(trace_id)
66+
67+
@staticmethod
68+
def extract_parent_span_id(headers):
69+
parent_span_id = 0
70+
71+
for key in POSSIBLE_HTTP_HEADER_PARENT_IDS:
72+
if key in headers:
73+
parent_span_id = headers.get(key)
74+
75+
return int(parent_span_id)
76+
77+
@staticmethod
78+
def extract_sampling_priority(headers):
79+
sampling_priority = None
80+
81+
for key in POSSIBLE_HTTP_HEADER_SAMPLING_PRIORITIES:
82+
if key in headers:
83+
sampling_priority = headers.get(key)
84+
85+
return sampling_priority
86+
4287
def extract(self, headers):
4388
"""Extract a Context from HTTP headers into a new Context.
4489
@@ -60,9 +105,10 @@ def my_controller(url, headers):
60105
return Context()
61106

62107
try:
63-
trace_id = int(headers.get(HTTP_HEADER_TRACE_ID, 0))
64-
parent_span_id = int(headers.get(HTTP_HEADER_PARENT_ID, 0))
65-
sampling_priority = headers.get(HTTP_HEADER_SAMPLING_PRIORITY)
108+
trace_id = HTTPPropagator.extract_trace_id(headers)
109+
parent_span_id = HTTPPropagator.extract_parent_span_id(headers)
110+
sampling_priority = HTTPPropagator.extract_sampling_priority(headers)
111+
66112
if sampling_priority is not None:
67113
sampling_priority = int(sampling_priority)
68114

ddtrace/propagation/utils.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
def get_wsgi_header(header):
2+
"""Returns a WSGI compliant HTTP header.
3+
See https://www.python.org/dev/peps/pep-3333/#environ-variables for
4+
information from the spec.
5+
"""
6+
return "HTTP_{}".format(header.upper().replace("-", "_"))

tests/propagation/test_http.py

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,63 @@
11
from unittest import TestCase
2-
from nose.tools import eq_, ok_
2+
from nose.tools import eq_
33
from tests.test_tracer import get_dummy_tracer
44

5-
from ddtrace.span import Span
6-
from ddtrace.context import Context, ThreadLocalContext
7-
85
from ddtrace.propagation.http import (
96
HTTPPropagator,
107
HTTP_HEADER_TRACE_ID,
118
HTTP_HEADER_PARENT_ID,
129
HTTP_HEADER_SAMPLING_PRIORITY,
1310
)
1411

12+
1513
class TestHttpPropagation(TestCase):
1614
"""
1715
Tests related to the ``Context`` class that hosts the trace for the
1816
current execution flow.
1917
"""
18+
2019
def test_inject(self):
2120
tracer = get_dummy_tracer()
2221

2322
with tracer.trace("global_root_span") as span:
23+
span.context.sampling_priority = 2
2424
headers = {}
2525
propagator = HTTPPropagator()
2626
propagator.inject(span.context, headers)
2727

2828
eq_(int(headers[HTTP_HEADER_TRACE_ID]), span.trace_id)
2929
eq_(int(headers[HTTP_HEADER_PARENT_ID]), span.span_id)
30-
# TODO: do it for priority too
31-
30+
eq_(
31+
int(headers[HTTP_HEADER_SAMPLING_PRIORITY]),
32+
span.context.sampling_priority,
33+
)
3234

3335
def test_extract(self):
3436
tracer = get_dummy_tracer()
3537

3638
headers = {
37-
HTTP_HEADER_TRACE_ID: '1234',
38-
HTTP_HEADER_PARENT_ID: '5678',
39-
HTTP_HEADER_SAMPLING_PRIORITY: '1',
39+
"x-datadog-trace-id": "1234",
40+
"x-datadog-parent-id": "5678",
41+
"x-datadog-sampling-priority": "1",
42+
}
43+
44+
propagator = HTTPPropagator()
45+
context = propagator.extract(headers)
46+
tracer.context_provider.activate(context)
47+
48+
with tracer.trace("local_root_span") as span:
49+
eq_(span.trace_id, 1234)
50+
eq_(span.parent_id, 5678)
51+
eq_(span.context.sampling_priority, 1)
52+
53+
def test_WSGI_extract(self):
54+
"""Ensure we support the WSGI formatted headers as well."""
55+
tracer = get_dummy_tracer()
56+
57+
headers = {
58+
"HTTP_X_DATADOG_TRACE_ID": "1234",
59+
"HTTP_X_DATADOG_PARENT_ID": "5678",
60+
"HTTP_X_DATADOG_SAMPLING_PRIORITY": "1",
4061
}
4162

4263
propagator = HTTPPropagator()
@@ -46,4 +67,4 @@ def test_extract(self):
4667
with tracer.trace("local_root_span") as span:
4768
eq_(span.trace_id, 1234)
4869
eq_(span.parent_id, 5678)
49-
# TODO: do it for priority too
70+
eq_(span.context.sampling_priority, 1)

tests/propagation/test_utils.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from ddtrace.propagation.utils import get_wsgi_header
2+
3+
4+
class TestPropagationUtils(object):
5+
def test_get_wsgi_header(self):
6+
assert get_wsgi_header("x-datadog-trace-id") == "HTTP_X_DATADOG_TRACE_ID"

0 commit comments

Comments
 (0)