Skip to content

Commit 1d47828

Browse files
committed
Add support for B3 parentspanid
Fixes open-telemetry#236
1 parent a89bbc8 commit 1d47828

File tree

8 files changed

+209
-112
lines changed

8 files changed

+209
-112
lines changed

ext/opentelemetry-ext-opentracing-shim/src/opentelemetry/ext/opentracing_shim/__init__.py

+8-5
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@
8989
from opentelemetry import propagators
9090
from opentelemetry.ext.opentracing_shim import util
9191
from opentelemetry.ext.opentracing_shim.version import __version__
92+
from opentelemetry.sdk.trace import Span
9293

9394
logger = logging.getLogger(__name__)
9495

@@ -101,10 +102,10 @@ def create_tracer(otel_tracer_source):
101102
:class:`opentracing.Tracer` using OpenTelemetry under the hood.
102103
103104
Args:
104-
otel_tracer_source: A :class:`opentelemetry.trace.TracerSource` to be used for
105-
constructing the :class:`TracerShim`. A tracer from this source will be used
106-
to perform the actual tracing when user code is instrumented using
107-
the OpenTracing API.
105+
otel_tracer_source: A :class:`opentelemetry.trace.TracerSource` to be
106+
used for constructing the :class:`TracerShim`. A tracer from this
107+
source will be used to perform the actual tracing when user code is
108+
instrumented using the OpenTracing API.
108109
109110
Returns:
110111
The created :class:`TracerShim`.
@@ -667,12 +668,14 @@ def inject(self, span_context, format, carrier):
667668
# uses the configured propagators in opentelemetry.propagators.
668669
# TODO: Support Format.BINARY once it is supported in
669670
# opentelemetry-python.
671+
670672
if format not in self._supported_formats:
671673
raise opentracing.UnsupportedFormatException
672674

673675
propagator = propagators.get_global_httptextformat()
676+
674677
propagator.inject(
675-
span_context.unwrap(), type(carrier).__setitem__, carrier
678+
Span("", span_context.unwrap()), type(carrier).__setitem__, carrier
676679
)
677680

678681
def extract(self, format, carrier):

ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py

+10-5
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
# limitations under the License.
1414

1515
import time
16-
import unittest
16+
from unittest import TestCase
1717

1818
import opentracing
1919

@@ -24,7 +24,7 @@
2424
from opentelemetry.sdk.trace import TracerSource
2525

2626

27-
class TestShim(unittest.TestCase):
27+
class TestShim(TestCase):
2828
# pylint: disable=too-many-public-methods
2929

3030
def setUp(self):
@@ -486,6 +486,7 @@ def test_inject_text_map(self):
486486

487487
# Verify Format.TEXT_MAP
488488
text_map = {}
489+
489490
self.shim.inject(context, opentracing.Format.TEXT_MAP, text_map)
490491
self.assertEqual(text_map[MockHTTPTextFormat.TRACE_ID_KEY], str(1220))
491492
self.assertEqual(text_map[MockHTTPTextFormat.SPAN_ID_KEY], str(7478))
@@ -551,6 +552,10 @@ def extract(cls, get_from_carrier, carrier):
551552
)
552553

553554
@classmethod
554-
def inject(cls, context, set_in_carrier, carrier):
555-
set_in_carrier(carrier, cls.TRACE_ID_KEY, str(context.trace_id))
556-
set_in_carrier(carrier, cls.SPAN_ID_KEY, str(context.span_id))
555+
def inject(cls, span, set_in_carrier, carrier):
556+
set_in_carrier(
557+
carrier, cls.TRACE_ID_KEY, str(span.get_context().trace_id)
558+
)
559+
set_in_carrier(
560+
carrier, cls.SPAN_ID_KEY, str(span.get_context().span_id)
561+
)

opentelemetry-api/src/opentelemetry/context/propagation/httptextformat.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
import abc
1616
import typing
1717

18-
from opentelemetry.trace import SpanContext
18+
from opentelemetry.trace import Span, SpanContext
1919

2020
_T = typing.TypeVar("_T")
2121

@@ -95,7 +95,7 @@ def extract(
9595

9696
@abc.abstractmethod
9797
def inject(
98-
self, context: SpanContext, set_in_carrier: Setter[_T], carrier: _T
98+
self, span: Span, set_in_carrier: Setter[_T], carrier: _T
9999
) -> None:
100100
"""Inject values from a SpanContext into a carrier.
101101

opentelemetry-api/src/opentelemetry/context/propagation/tracecontexthttptextformat.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -105,10 +105,13 @@ def extract(
105105
@classmethod
106106
def inject(
107107
cls,
108-
context: trace.SpanContext,
108+
span: trace.Span,
109109
set_in_carrier: httptextformat.Setter[_T],
110110
carrier: _T,
111111
) -> None:
112+
113+
context = span.get_context()
114+
112115
if context == trace.INVALID_SPAN_CONTEXT:
113116
return
114117
traceparent_string = "00-{:032x}-{:016x}-{:02x}".format(

opentelemetry-api/src/opentelemetry/propagators/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ def inject(
6464
should know how to set header values on the carrier.
6565
"""
6666
get_global_httptextformat().inject(
67-
tracer.get_current_span().get_context(), set_in_carrier, carrier
67+
tracer.get_current_span(), set_in_carrier, carrier
6868
)
6969

7070

opentelemetry-api/tests/context/propagation/test_tracecontexthttptextformat.py

+37-22
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
import typing
1616
import unittest
17+
from unittest.mock import Mock
1718

1819
from opentelemetry import trace
1920
from opentelemetry.context.propagation import tracecontexthttptextformat
@@ -38,7 +39,8 @@ def test_no_traceparent_header(self):
3839
3940
RFC 4.2.2:
4041
41-
If no traceparent header is received, the vendor creates a new trace-id and parent-id that represents the current request.
42+
If no traceparent header is received, the vendor creates a new
43+
trace-id and parent-id that represents the current request.
4244
"""
4345
output = {} # type:typing.Dict[str, typing.List[str]]
4446
span_context = FORMAT.extract(get_as_list, output)
@@ -66,8 +68,10 @@ def test_headers_with_tracestate(self):
6668
span_context.trace_state, {"foo": "1", "bar": "2", "baz": "3"}
6769
)
6870

71+
mock_span = Mock()
72+
mock_span.configure_mock(**{"get_context.return_value": span_context})
6973
output = {} # type:typing.Dict[str, str]
70-
FORMAT.inject(span_context, dict.__setitem__, output)
74+
FORMAT.inject(mock_span, dict.__setitem__, output)
7175
self.assertEqual(output["traceparent"], traceparent_value)
7276
for pair in ["foo=1", "bar=2", "baz=3"]:
7377
self.assertIn(pair, output["tracestate"])
@@ -81,13 +85,16 @@ def test_invalid_trace_id(self):
8185
8286
RFC 3.2.2.3
8387
84-
If the trace-id value is invalid (for example if it contains non-allowed characters or all
85-
zeros), vendors MUST ignore the traceparent.
88+
If the trace-id value is invalid (for example if it contains
89+
non-allowed characters or all zeros), vendors MUST ignore the
90+
traceparent.
8691
8792
RFC 3.3
8893
89-
If the vendor failed to parse traceparent, it MUST NOT attempt to parse tracestate.
90-
Note that the opposite is not true: failure to parse tracestate MUST NOT affect the parsing of traceparent.
94+
If the vendor failed to parse traceparent, it MUST NOT attempt to
95+
parse tracestate.
96+
Note that the opposite is not true: failure to parse tracestate MUST
97+
NOT affect the parsing of traceparent.
9198
"""
9299
span_context = FORMAT.extract(
93100
get_as_list,
@@ -101,19 +108,22 @@ def test_invalid_trace_id(self):
101108
self.assertEqual(span_context, trace.INVALID_SPAN_CONTEXT)
102109

103110
def test_invalid_parent_id(self):
104-
"""If the parent id is invalid, we must ignore the full traceparent header.
111+
"""If the parent id is invalid, we must ignore the full traceparent
112+
header.
105113
106114
Also ignore any tracestate.
107115
108116
RFC 3.2.2.3
109117
110-
Vendors MUST ignore the traceparent when the parent-id is invalid (for example,
111-
if it contains non-lowercase hex characters).
118+
Vendors MUST ignore the traceparent when the parent-id is invalid (for
119+
example, if it contains non-lowercase hex characters).
112120
113121
RFC 3.3
114122
115-
If the vendor failed to parse traceparent, it MUST NOT attempt to parse tracestate.
116-
Note that the opposite is not true: failure to parse tracestate MUST NOT affect the parsing of traceparent.
123+
If the vendor failed to parse traceparent, it MUST NOT attempt to parse
124+
tracestate.
125+
Note that the opposite is not true: failure to parse tracestate MUST
126+
NOT affect the parsing of traceparent.
117127
"""
118128
span_context = FORMAT.extract(
119129
get_as_list,
@@ -131,15 +141,19 @@ def test_no_send_empty_tracestate(self):
131141
132142
RFC 3.3.1.1
133143
134-
Empty and whitespace-only list members are allowed. Vendors MUST accept empty
135-
tracestate headers but SHOULD avoid sending them.
144+
Empty and whitespace-only list members are allowed. Vendors MUST accept
145+
empty tracestate headers but SHOULD avoid sending them.
136146
"""
137147
output = {} # type:typing.Dict[str, str]
138-
FORMAT.inject(
139-
trace.SpanContext(self.TRACE_ID, self.SPAN_ID),
140-
dict.__setitem__,
141-
output,
148+
mock_span = Mock()
149+
mock_span.configure_mock(
150+
**{
151+
"get_context.return_value": trace.SpanContext(
152+
self.TRACE_ID, self.SPAN_ID
153+
)
154+
}
142155
)
156+
FORMAT.inject(mock_span, dict.__setitem__, output)
143157
self.assertTrue("traceparent" in output)
144158
self.assertFalse("tracestate" in output)
145159

@@ -155,22 +169,23 @@ def test_format_not_supported(self):
155169
get_as_list,
156170
{
157171
"traceparent": [
158-
"00-12345678901234567890123456789012-1234567890123456-00-residue"
172+
"00-12345678901234567890123456789012-"
173+
"1234567890123456-00-residue"
159174
],
160175
"tracestate": ["foo=1,bar=2,foo=3"],
161176
},
162177
)
163178
self.assertEqual(span_context, trace.INVALID_SPAN_CONTEXT)
164179

165180
def test_propagate_invalid_context(self):
166-
"""Do not propagate invalid trace context.
167-
"""
181+
"""Do not propagate invalid trace context."""
168182
output = {} # type:typing.Dict[str, str]
169-
FORMAT.inject(trace.INVALID_SPAN_CONTEXT, dict.__setitem__, output)
183+
FORMAT.inject(trace.Span(), dict.__setitem__, output)
170184
self.assertFalse("traceparent" in output)
171185

172186
def test_tracestate_empty_header(self):
173-
"""Test tracestate with an additional empty header (should be ignored)"""
187+
"""Test tracestate with an additional empty header (should be ignored)
188+
"""
174189
span_context = FORMAT.extract(
175190
get_as_list,
176191
{

opentelemetry-sdk/src/opentelemetry/sdk/context/propagation/b3_format.py

+16-5
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ class B3Format(HTTPTextFormat):
2727
SINGLE_HEADER_KEY = "b3"
2828
TRACE_ID_KEY = "x-b3-traceid"
2929
SPAN_ID_KEY = "x-b3-spanid"
30+
PARENT_SPAN_ID_KEY = "x-b3-parentspanid"
3031
SAMPLED_KEY = "x-b3-sampled"
3132
FLAGS_KEY = "x-b3-flags"
3233
_SAMPLE_PROPAGATE_VALUES = set(["1", "True", "true", "d"])
@@ -55,7 +56,7 @@ def extract(cls, get_from_carrier, carrier):
5556
elif len(fields) == 3:
5657
trace_id, span_id, sampled = fields
5758
elif len(fields) == 4:
58-
trace_id, span_id, sampled, _parent_span_id = fields
59+
trace_id, span_id, sampled, _ = fields
5960
else:
6061
return trace.INVALID_SPAN_CONTEXT
6162
else:
@@ -100,14 +101,24 @@ def extract(cls, get_from_carrier, carrier):
100101
)
101102

102103
@classmethod
103-
def inject(cls, context, set_in_carrier, carrier):
104-
sampled = (trace.TraceOptions.SAMPLED & context.trace_options) != 0
104+
def inject(
105+
cls, span, set_in_carrier, carrier
106+
): # pylint: disable=arguments-differ
107+
sampled = (
108+
trace.TraceOptions.SAMPLED & span.context.trace_options
109+
) != 0
105110
set_in_carrier(
106-
carrier, cls.TRACE_ID_KEY, format_trace_id(context.trace_id)
111+
carrier, cls.TRACE_ID_KEY, format_trace_id(span.context.trace_id)
107112
)
108113
set_in_carrier(
109-
carrier, cls.SPAN_ID_KEY, format_span_id(context.span_id)
114+
carrier, cls.SPAN_ID_KEY, format_span_id(span.context.span_id)
110115
)
116+
if span.parent is not None:
117+
set_in_carrier(
118+
carrier,
119+
cls.PARENT_SPAN_ID_KEY,
120+
format_span_id(span.parent.context.span_id),
121+
)
111122
set_in_carrier(carrier, cls.SAMPLED_KEY, "1" if sampled else "0")
112123

113124

0 commit comments

Comments
 (0)