Skip to content

Commit

Permalink
Add support for B3 parentspanid
Browse files Browse the repository at this point in the history
Fixes #236
  • Loading branch information
ocelotl committed Dec 7, 2019
1 parent 3d441b1 commit db91e97
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 78 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class B3Format(HTTPTextFormat):
SINGLE_HEADER_KEY = "b3"
TRACE_ID_KEY = "x-b3-traceid"
SPAN_ID_KEY = "x-b3-spanid"
PARENT_SPAN_ID_KEY = "x-b3-parentspanid"
SAMPLED_KEY = "x-b3-sampled"
FLAGS_KEY = "x-b3-flags"
_SAMPLE_PROPAGATE_VALUES = set(["1", "True", "true", "d"])
Expand Down Expand Up @@ -55,7 +56,7 @@ def extract(cls, get_from_carrier, carrier):
elif len(fields) == 3:
trace_id, span_id, sampled = fields
elif len(fields) == 4:
trace_id, span_id, sampled, _parent_span_id = fields
trace_id, span_id, sampled, _ = fields
else:
return trace.INVALID_SPAN_CONTEXT
else:
Expand Down Expand Up @@ -100,14 +101,24 @@ def extract(cls, get_from_carrier, carrier):
)

@classmethod
def inject(cls, context, set_in_carrier, carrier):
sampled = (trace.TraceOptions.SAMPLED & context.trace_options) != 0
def inject(
cls, span, set_in_carrier, carrier
): # pylint: disable=arguments-differ
sampled = (
trace.TraceOptions.SAMPLED & span.context.trace_options
) != 0
set_in_carrier(
carrier, cls.TRACE_ID_KEY, format_trace_id(context.trace_id)
carrier, cls.TRACE_ID_KEY, format_trace_id(span.context.trace_id)
)
set_in_carrier(
carrier, cls.SPAN_ID_KEY, format_span_id(context.span_id)
carrier, cls.SPAN_ID_KEY, format_span_id(span.context.span_id)
)
if span.parent is not None:
set_in_carrier(
carrier,
cls.PARENT_SPAN_ID_KEY,
format_span_id(span.parent.context.span_id),
)
set_in_carrier(carrier, cls.SAMPLED_KEY, "1" if sampled else "0")


Expand Down
5 changes: 4 additions & 1 deletion opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,10 @@ def __repr__(self):
)

def __str__(self):
return '{}(name="{}", context={}, kind={}, parent={}, start_time={}, end_time={})'.format(
return (
'{}(name="{}", context={}, kind={}, parent={}, '
"start_time={}, end_time={})"
).format(
type(self).__name__,
self.name,
self.context,
Expand Down
201 changes: 130 additions & 71 deletions opentelemetry-sdk/tests/context/propagation/test_b3_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,40 +35,97 @@ def setUpClass(cls):
cls.serialized_span_id = b3_format.format_span_id(
trace.generate_span_id()
)
cls.serialized_parent_id = b3_format.format_span_id(
trace.generate_span_id()
)

def get_child_parent_new_carrier(self, old_carrier):

parent_context = FORMAT.extract(get_as_list, old_carrier)

parent = trace.Span("parent", parent_context)
child = trace.Span(
"child",
trace_api.SpanContext(
parent_context.trace_id,
trace.generate_span_id(),
trace_options=parent_context.trace_options,
trace_state=parent_context.trace_state,
),
parent=parent
)

new_carrier = {}
FORMAT.inject(child, dict.__setitem__, new_carrier)

return child, parent, new_carrier

def test_extract_multi_header(self):
"""Test the extraction of B3 headers."""
carrier = {
FORMAT.TRACE_ID_KEY: self.serialized_trace_id,
FORMAT.SPAN_ID_KEY: self.serialized_span_id,
FORMAT.SAMPLED_KEY: "1",
}
span_context = FORMAT.extract(get_as_list, carrier)
new_carrier = {}
FORMAT.inject(span_context, dict.__setitem__, new_carrier)
child, parent, new_carrier = self.get_child_parent_new_carrier(
{
FORMAT.TRACE_ID_KEY: self.serialized_trace_id,
FORMAT.SPAN_ID_KEY: self.serialized_span_id,
FORMAT.PARENT_SPAN_ID_KEY: self.serialized_parent_id,
FORMAT.SAMPLED_KEY: "1",
}
)

self.assertEqual(
new_carrier[FORMAT.TRACE_ID_KEY],
b3_format.format_trace_id(child.context.trace_id),
)
self.assertEqual(
new_carrier[FORMAT.TRACE_ID_KEY], self.serialized_trace_id
new_carrier[FORMAT.SPAN_ID_KEY],
b3_format.format_span_id(child.context.span_id),
)
self.assertEqual(
new_carrier[FORMAT.SPAN_ID_KEY], self.serialized_span_id
new_carrier[FORMAT.PARENT_SPAN_ID_KEY],
b3_format.format_span_id(parent.context.span_id),
)
self.assertEqual(new_carrier[FORMAT.SAMPLED_KEY], "1")

def test_extract_single_header(self):
"""Test the extraction from a single b3 header."""
carrier = {
FORMAT.SINGLE_HEADER_KEY: "{}-{}".format(
self.serialized_trace_id, self.serialized_span_id
)
}
span_context = FORMAT.extract(get_as_list, carrier)
new_carrier = {}
FORMAT.inject(span_context, dict.__setitem__, new_carrier)
child, parent, new_carrier = self.get_child_parent_new_carrier(
{
FORMAT.SINGLE_HEADER_KEY: "{}-{}".format(
self.serialized_trace_id, self.serialized_span_id
)
}
)

self.assertEqual(
new_carrier[FORMAT.TRACE_ID_KEY], self.serialized_trace_id
new_carrier[FORMAT.TRACE_ID_KEY],
b3_format.format_trace_id(child.context.trace_id),
)
self.assertEqual(
new_carrier[FORMAT.SPAN_ID_KEY], self.serialized_span_id
new_carrier[FORMAT.SPAN_ID_KEY],
b3_format.format_span_id(child.context.span_id),
)
self.assertEqual(new_carrier[FORMAT.SAMPLED_KEY], "1")

child, parent, new_carrier = self.get_child_parent_new_carrier(
{
FORMAT.SINGLE_HEADER_KEY: "{}-{}-1-{}".format(
self.serialized_trace_id,
self.serialized_span_id,
self.serialized_parent_id,
)
}
)

self.assertEqual(
new_carrier[FORMAT.TRACE_ID_KEY],
b3_format.format_trace_id(child.context.trace_id),
)
self.assertEqual(
new_carrier[FORMAT.SPAN_ID_KEY],
b3_format.format_span_id(child.context.span_id),
)
self.assertEqual(
new_carrier[FORMAT.PARENT_SPAN_ID_KEY],
b3_format.format_span_id(parent.context.span_id),
)
self.assertEqual(new_carrier[FORMAT.SAMPLED_KEY], "1")

Expand All @@ -77,82 +134,84 @@ def test_extract_header_precedence(self):
headers.
"""
single_header_trace_id = self.serialized_trace_id[:-3] + "123"
carrier = {
FORMAT.SINGLE_HEADER_KEY: "{}-{}".format(
single_header_trace_id, self.serialized_span_id
),
FORMAT.TRACE_ID_KEY: self.serialized_trace_id,
FORMAT.SPAN_ID_KEY: self.serialized_span_id,
FORMAT.SAMPLED_KEY: "1",
}
span_context = FORMAT.extract(get_as_list, carrier)
new_carrier = {}
FORMAT.inject(span_context, dict.__setitem__, new_carrier)

child, parent, new_carrier = self.get_child_parent_new_carrier(
{
FORMAT.SINGLE_HEADER_KEY: "{}-{}".format(
single_header_trace_id, self.serialized_span_id
),
FORMAT.TRACE_ID_KEY: self.serialized_trace_id,
FORMAT.SPAN_ID_KEY: self.serialized_span_id,
FORMAT.SAMPLED_KEY: "1",
}
)

self.assertEqual(
new_carrier[FORMAT.TRACE_ID_KEY], single_header_trace_id
)

def test_enabled_sampling(self):
"""Test b3 sample key variants that turn on sampling."""
for variant in ["1", "True", "true", "d"]:
carrier = {
FORMAT.TRACE_ID_KEY: self.serialized_trace_id,
FORMAT.SPAN_ID_KEY: self.serialized_span_id,
FORMAT.SAMPLED_KEY: variant,
}
span_context = FORMAT.extract(get_as_list, carrier)
new_carrier = {}
FORMAT.inject(span_context, dict.__setitem__, new_carrier)
child, parent, new_carrier = self.get_child_parent_new_carrier(
{
FORMAT.TRACE_ID_KEY: self.serialized_trace_id,
FORMAT.SPAN_ID_KEY: self.serialized_span_id,
FORMAT.SAMPLED_KEY: variant,
}
)

self.assertEqual(new_carrier[FORMAT.SAMPLED_KEY], "1")

def test_disabled_sampling(self):
"""Test b3 sample key variants that turn off sampling."""
for variant in ["0", "False", "false", None]:
carrier = {
FORMAT.TRACE_ID_KEY: self.serialized_trace_id,
FORMAT.SPAN_ID_KEY: self.serialized_span_id,
FORMAT.SAMPLED_KEY: variant,
}
span_context = FORMAT.extract(get_as_list, carrier)
new_carrier = {}
FORMAT.inject(span_context, dict.__setitem__, new_carrier)
child, parent, new_carrier = self.get_child_parent_new_carrier(
{
FORMAT.TRACE_ID_KEY: self.serialized_trace_id,
FORMAT.SPAN_ID_KEY: self.serialized_span_id,
FORMAT.SAMPLED_KEY: variant,
}
)

self.assertEqual(new_carrier[FORMAT.SAMPLED_KEY], "0")

def test_flags(self):
"""x-b3-flags set to "1" should result in propagation."""
carrier = {
FORMAT.TRACE_ID_KEY: self.serialized_trace_id,
FORMAT.SPAN_ID_KEY: self.serialized_span_id,
FORMAT.FLAGS_KEY: "1",
}
span_context = FORMAT.extract(get_as_list, carrier)
new_carrier = {}
FORMAT.inject(span_context, dict.__setitem__, new_carrier)
child, parent, new_carrier = self.get_child_parent_new_carrier(
{
FORMAT.TRACE_ID_KEY: self.serialized_trace_id,
FORMAT.SPAN_ID_KEY: self.serialized_span_id,
FORMAT.FLAGS_KEY: "1",
}
)

self.assertEqual(new_carrier[FORMAT.SAMPLED_KEY], "1")

def test_flags_and_sampling(self):
"""Propagate if b3 flags and sampling are set."""
carrier = {
FORMAT.TRACE_ID_KEY: self.serialized_trace_id,
FORMAT.SPAN_ID_KEY: self.serialized_span_id,
FORMAT.FLAGS_KEY: "1",
}
span_context = FORMAT.extract(get_as_list, carrier)
new_carrier = {}
FORMAT.inject(span_context, dict.__setitem__, new_carrier)
child, parent, new_carrier = self.get_child_parent_new_carrier(
{
FORMAT.TRACE_ID_KEY: self.serialized_trace_id,
FORMAT.SPAN_ID_KEY: self.serialized_span_id,
FORMAT.FLAGS_KEY: "1",
}
)

self.assertEqual(new_carrier[FORMAT.SAMPLED_KEY], "1")

def test_64bit_trace_id(self):
"""64 bit trace ids should be padded to 128 bit trace ids."""
trace_id_64_bit = self.serialized_trace_id[:16]
carrier = {
FORMAT.TRACE_ID_KEY: trace_id_64_bit,
FORMAT.SPAN_ID_KEY: self.serialized_span_id,
FORMAT.FLAGS_KEY: "1",
}
span_context = FORMAT.extract(get_as_list, carrier)
new_carrier = {}
FORMAT.inject(span_context, dict.__setitem__, new_carrier)

child, parent, new_carrier = self.get_child_parent_new_carrier(
{
FORMAT.TRACE_ID_KEY: trace_id_64_bit,
FORMAT.SPAN_ID_KEY: self.serialized_span_id,
FORMAT.FLAGS_KEY: "1",
}
)

self.assertEqual(
new_carrier[FORMAT.TRACE_ID_KEY], "0" * 16 + trace_id_64_bit
)
Expand Down
2 changes: 1 addition & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ commands_pre =
mypyinstalled: pip install file://{toxinidir}/opentelemetry-api/

commands =
test: pytest
test: pytest {posargs}
coverage: {toxinidir}/scripts/coverage.sh

mypy: mypy --namespace-packages opentelemetry-api/src/opentelemetry/
Expand Down

0 comments on commit db91e97

Please sign in to comment.