Skip to content

Commit d87a8bf

Browse files
Bastian Krolbasti1302
authored andcommitted
fix(w3c trace context): do not pass down unknown flags.
The W3C trace context specification mandates that a participant must only send known flags downstream. See https://www.w3.org/TR/trace-context/#other-flags: "The behavior of other flags, such as (00000100) is not defined and is reserved for future use. Vendors MUST set those to zero." In particular, this commit changes the way traceparent.py handles the flags. Instead of persisting the complete flags field from traceparent, we specifically parse the flag(s) that we understand and keep them as individual boolean attributes. This will also make it easier to handle the random trace ID flag correctly when updating support to W3C trace context level 2. In addition we now correctly pass down the version field as 00, since that is the traceparent version we support (instead of passing down the incoming version value). Signed-off-by: Bastian Krol <[email protected]>
1 parent ebc4103 commit d87a8bf

File tree

4 files changed

+38
-20
lines changed

4 files changed

+38
-20
lines changed

instana/w3c_trace_context/traceparent.py

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
from ..log import logger
55
import re
66

7+
# See https://www.w3.org/TR/trace-context-2/#trace-flags for details on the bitmasks.
8+
SAMPLED_BITMASK = 0b1;
79

810
class Traceparent:
911
SPECIFICATION_VERSION = "00"
@@ -27,15 +29,16 @@ def get_traceparent_fields(traceparent):
2729
"""
2830
Parses the validated traceparent header into its fields and returns the fields
2931
:param traceparent: the original validated traceparent header
30-
:return: version, trace_id, parent_id, trace_flags
32+
:return: version, trace_id, parent_id, sampled_flag
3133
"""
3234
try:
3335
traceparent_properties = traceparent.split("-")
3436
version = traceparent_properties[0]
3537
trace_id = traceparent_properties[1]
3638
parent_id = traceparent_properties[2]
37-
trace_flags = traceparent_properties[3]
38-
return version, trace_id, parent_id, trace_flags
39+
flags = int(traceparent_properties[3])
40+
sampled_flag = (flags & SAMPLED_BITMASK) == SAMPLED_BITMASK
41+
return version, trace_id, parent_id, sampled_flag
3942
except Exception: # This method is intended to be called with a version 00 validated traceparent
4043
# This exception handling is added just for making sure we do not throw any unhandled exception
4144
# if somebody calls the method in the future without a validated traceparent
@@ -51,21 +54,24 @@ def update_traceparent(self, traceparent, in_trace_id, in_span_id, level):
5154
:param level: instana level, used to determine the value of sampled flag of the traceparent header
5255
:return: the updated traceparent header
5356
"""
54-
mask = 1 << 0
55-
trace_flags = 0
5657
if traceparent is None: # modify the trace_id part only when it was not present at all
5758
trace_id = in_trace_id.zfill(32)
58-
version = self.SPECIFICATION_VERSION
5959
else:
60-
version, trace_id, _, trace_flags = self.get_traceparent_fields(traceparent)
61-
trace_flags = int(trace_flags, 16)
60+
# - We do not need the incoming upstream parent span ID for the header we sent downstream.
61+
# - We also do not care about the incoming version: The version field we sent downstream needs to match the
62+
# format of the traceparent header we produce here, so we always send the version _we_ support downstream,
63+
# even if the header coming from upstream supported a different version.
64+
# - Finally, we also do not care about the incoming sampled flag , we only need to communicate our own
65+
# sampling decision downstream. The sampling decisions from our upstream is irrelevant for what we send
66+
# downstream.
67+
_, trace_id, _, _ = self.get_traceparent_fields(traceparent)
6268

6369
parent_id = in_span_id.zfill(16)
64-
trace_flags = (trace_flags & ~mask) | ((level << 0) & mask)
65-
trace_flags = format(trace_flags, '0>2x')
70+
flags = level & SAMPLED_BITMASK
71+
flags = format(flags, '0>2x')
6672

67-
traceparent = "{version}-{traceid}-{parentid}-{trace_flags}".format(version=version,
73+
traceparent = "{version}-{traceid}-{parentid}-{flags}".format(version=self.SPECIFICATION_VERSION,
6874
traceid=trace_id,
6975
parentid=parent_id,
70-
trace_flags=trace_flags)
76+
flags=flags)
7177
return traceparent

tests/frameworks/test_django.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,9 @@ def test_with_incoming_context(self):
338338
self.assertEqual('1', response.headers['X-INSTANA-L'])
339339

340340
assert ('traceparent' in response.headers)
341-
self.assertEqual('01-4bf92f3577b34da6a3ce929d0e0e4736-{}-01'.format(django_span.s),
341+
# The incoming traceparent header had version 01 (which does not exist at the time of writing), but since we
342+
# support version 00, we also need to pass down 00 for the version field.
343+
self.assertEqual('00-4bf92f3577b34da6a3ce929d0e0e4736-{}-01'.format(django_span.s),
342344
response.headers['traceparent'])
343345

344346
assert ('tracestate' in response.headers)

tests/propagators/test_http_propagator.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ def test_extract_carrier_dict(self, mock_validate, mock_get_traceparent_fields):
2828
'X-INSTANA-L': '1, correlationType=web; correlationId=1234567890abcdef'
2929
}
3030
mock_validate.return_value = '00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01'
31-
mock_get_traceparent_fields.return_value = ["00", "4bf92f3577b34da6a3ce929d0e0e4736", "00f067aa0ba902b7", "01"]
31+
mock_get_traceparent_fields.return_value = ["00", "4bf92f3577b34da6a3ce929d0e0e4736", "00f067aa0ba902b7", True]
3232
ctx = self.hptc.extract(carrier)
3333
self.assertEqual(ctx.correlation_id, '1234567890abcdef')
3434
self.assertEqual(ctx.correlation_type, "web")
@@ -54,7 +54,7 @@ def test_extract_carrier_list(self, mock_validate, mock_get_traceparent_fields):
5454
('X-INSTANA-L', '1')]
5555

5656
mock_validate.return_value = '00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01'
57-
mock_get_traceparent_fields.return_value = ["00", "4bf92f3577b34da6a3ce929d0e0e4736", "00f067aa0ba902b7", "01"]
57+
mock_get_traceparent_fields.return_value = ["00", "4bf92f3577b34da6a3ce929d0e0e4736", "00f067aa0ba902b7", True]
5858
ctx = self.hptc.extract(carrier)
5959
self.assertIsNone(ctx.correlation_id)
6060
self.assertIsNone(ctx.correlation_type)
@@ -124,7 +124,7 @@ def test_extract_carrier_dict_corrupted_level_header(self, mock_validate, mock_g
124124
'X-INSTANA-L': '1, correlationTypeweb; correlationId1234567890abcdef'
125125
}
126126
mock_validate.return_value = '00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01'
127-
mock_get_traceparent_fields.return_value = ["00", "4bf92f3577b34da6a3ce929d0e0e4736", "00f067aa0ba902b7", "01"]
127+
mock_get_traceparent_fields.return_value = ["00", "4bf92f3577b34da6a3ce929d0e0e4736", "00f067aa0ba902b7", True]
128128
ctx = self.hptc.extract(carrier)
129129
self.assertIsNone(ctx.correlation_id)
130130
self.assertIsNone(ctx.correlation_type)
@@ -156,7 +156,7 @@ def test_extract_carrier_dict_level_header_not_splitable(self, mock_validate, mo
156156
'X-INSTANA-L': ['1']
157157
}
158158
mock_validate.return_value = '00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01'
159-
mock_get_traceparent_fields.return_value = ["00", "4bf92f3577b34da6a3ce929d0e0e4736", "00f067aa0ba902b7", "01"]
159+
mock_get_traceparent_fields.return_value = ["00", "4bf92f3577b34da6a3ce929d0e0e4736", "00f067aa0ba902b7", True]
160160
ctx = self.hptc.extract(carrier)
161161
self.assertIsNone(ctx.correlation_id)
162162
self.assertIsNone(ctx.correlation_type)

tests/w3c_trace_context/test_traceparent.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,21 +23,31 @@ def test_validate_traceparent_None(self):
2323

2424
def test_get_traceparent_fields(self):
2525
traceparent = "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
26-
version, trace_id, parent_id, trace_flags = self.tp.get_traceparent_fields(traceparent)
26+
version, trace_id, parent_id, sampled_flag = self.tp.get_traceparent_fields(traceparent)
2727
self.assertEqual(trace_id, "4bf92f3577b34da6a3ce929d0e0e4736")
2828
self.assertEqual(parent_id, "00f067aa0ba902b7")
29+
self.assertTrue(sampled_flag)
30+
31+
def test_get_traceparent_fields_unsampled(self):
32+
traceparent = "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-00"
33+
version, trace_id, parent_id, sampled_flag = self.tp.get_traceparent_fields(traceparent)
34+
self.assertEqual(trace_id, "4bf92f3577b34da6a3ce929d0e0e4736")
35+
self.assertEqual(parent_id, "00f067aa0ba902b7")
36+
self.assertFalse(sampled_flag)
2937

3038
def test_get_traceparent_fields_None_input(self):
3139
traceparent = None
32-
version, trace_id, parent_id, trace_flags = self.tp.get_traceparent_fields(traceparent)
40+
version, trace_id, parent_id, sampled_flag = self.tp.get_traceparent_fields(traceparent)
3341
self.assertIsNone(trace_id)
3442
self.assertIsNone(parent_id)
43+
self.assertFalse(sampled_flag)
3544

3645
def test_get_traceparent_fields_string_input_no_dash(self):
3746
traceparent = "invalid"
38-
version, trace_id, parent_id, trace_flags = self.tp.get_traceparent_fields(traceparent)
47+
version, trace_id, parent_id, sampled_flag = self.tp.get_traceparent_fields(traceparent)
3948
self.assertIsNone(trace_id)
4049
self.assertIsNone(parent_id)
50+
self.assertFalse(sampled_flag)
4151

4252
def test_update_traceparent(self):
4353
traceparent = "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"

0 commit comments

Comments
 (0)