Skip to content

Commit f62eeac

Browse files
committed
1 parent 42415ba commit f62eeac

File tree

9 files changed

+241
-0
lines changed

9 files changed

+241
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
from .httptextformat import HTTPTextFormat
2+
from .binaryformat import BinaryFormat
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Copyright 2019, OpenTelemetry Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import abc
16+
import typing
17+
from opentelemetry.trace import SpanContext
18+
19+
20+
class BinaryFormat(abc.ABC):
21+
@staticmethod
22+
@abc.abstractmethod
23+
def to_bytes(context: SpanContext) -> bytes:
24+
pass
25+
26+
@staticmethod
27+
@abc.abstractmethod
28+
def from_bytes(byte_representation: bytes) -> typing.Optional[SpanContext]:
29+
pass
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# Copyright 2019, OpenTelemetry Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import abc
16+
import typing
17+
from opentelemetry.trace import SpanContext
18+
19+
Setter = typing.Callable[[object, str, str], None]
20+
Getter = typing.Callable[[object, str], str]
21+
22+
23+
class HTTPTextFormat(abc.ABC):
24+
"""API for propagation of spans via headers.
25+
26+
This class provides an interface that enables extracting and injecting
27+
trace state into headers of HTTP requests. Http frameworks and client
28+
can integrate with HTTPTextFormat by providing the object containing the
29+
headers, and a getter and setter function for the extraction and
30+
injection of values, respectively.
31+
32+
Example::
33+
34+
import flask
35+
import requests
36+
from opentelemetry.context.propagation import HTTPTextFormat
37+
38+
PROPAGATOR = HTTPTextFormat()
39+
40+
41+
42+
def get_header_from_flask_request(request, key):
43+
return request.headers[key]
44+
45+
def set_header_into_requests_request(request: requests.Request,
46+
key: str, value: str):
47+
request.headers[key] = value
48+
49+
def example_route():
50+
span_context = PROPAGATOR.extract(
51+
get_header_from_flask_request,
52+
flask.request
53+
)
54+
request_to_downstream = requests.Request(
55+
"GET", "http://httpbin.org/get"
56+
)
57+
PROPAGATOR.inject(
58+
span_context,
59+
set_header_into_requests_request,
60+
request_to_downstream
61+
)
62+
session = requests.Session()
63+
session.send(request_to_downstream.prepare())
64+
65+
66+
.. _Propagation API Specification:
67+
https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/api-propagators.md
68+
69+
Enabling this flexi
70+
"""
71+
@abc.abstractmethod
72+
def extract(self, get_from_carrier: Getter,
73+
carrier: object) -> SpanContext:
74+
pass
75+
76+
@abc.abstractmethod
77+
def inject(self, context: SpanContext, set_in_carrier: Setter,
78+
carrier: object):
79+
pass

opentelemetry-sdk/src/opentelemetry/sdk/context/__init__.py

Whitespace-only changes.

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

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# Copyright 2019, OpenTelemetry Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from opentelemetry.context.propagation.httptextformat import HTTPTextFormat
16+
import opentelemetry.trace as trace
17+
18+
19+
class B3Format(HTTPTextFormat):
20+
"""Propagator for the B3 HTTP header format.
21+
22+
See: https://github.com/openzipkin/b3-propagation
23+
"""
24+
25+
SINGLE_HEADER_KEY = "b3"
26+
TRACE_ID_KEY = "x-b3-traceid"
27+
SPAN_ID_KEY = "x-b3-spanid"
28+
SAMPLED_KEY = "x-b3-sampled"
29+
30+
@classmethod
31+
def extract(cls, get_from_carrier, carrier):
32+
trace_id = trace.INVALID_TRACE_ID
33+
span_id = trace.INVALID_SPAN_ID
34+
sampled = 1
35+
36+
single_header = get_from_carrier(carrier, cls.SINGLE_HEADER_KEY)
37+
if single_header:
38+
# b3-propagation spec calls for the sampling state to be
39+
# "deferred", which is unspecified. This concept does not
40+
# translate to SpanContext, so we set it as recorded.
41+
sampled = "1"
42+
fields = single_header.split("-", 4)
43+
44+
if len(fields) == 1:
45+
sampled = fields[0]
46+
elif len(fields) == 2:
47+
trace_id, span_id = fields
48+
elif len(fields) == 3:
49+
trace_id, span_id, sampled = fields
50+
elif len(fields) == 4:
51+
trace_id, span_id, sampled, _parent_span_id = fields
52+
else:
53+
return trace.INVALID_SPAN_CONTEXT
54+
else:
55+
trace_id = get_from_carrier(carrier, cls.TRACE_ID_KEY)
56+
span_id = get_from_carrier(carrier, cls.SPAN_ID_KEY)
57+
sampled = get_from_carrier(carrier, cls.SAMPLED_KEY)
58+
59+
options = 0
60+
if sampled == "1":
61+
options |= trace.TraceOptions.RECORDED
62+
return trace.SpanContext(
63+
trace_id=int(trace_id),
64+
span_id=int(span_id),
65+
trace_options=options,
66+
trace_state={},
67+
)
68+
69+
@classmethod
70+
def inject(cls, context, set_in_carrier, carrier):
71+
sampled = (trace.TraceOptions.RECORDED & context.trace_options) != 0
72+
set_in_carrier(carrier, cls.TRACE_ID_KEY, str(context.trace_id))
73+
set_in_carrier(carrier, cls.SPAN_ID_KEY, str(context.span_id))
74+
set_in_carrier(carrier, cls.SAMPLED_KEY, "1" if sampled else "0")

opentelemetry-sdk/tests/context/__init__.py

Whitespace-only changes.

opentelemetry-sdk/tests/context/propagation/__init__.py

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# Copyright 2019, OpenTelemetry Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import unittest
16+
import opentelemetry.sdk.context.propagation.b3_format as b3_format
17+
import opentelemetry.sdk.trace as trace
18+
19+
FORMAT = b3_format.B3Format()
20+
21+
22+
def _get_from_dict(carrier: dict, key: str) -> str:
23+
return carrier.get(key)
24+
25+
26+
def _set_into_dict(carrier: dict, key: str, value: str):
27+
carrier[key] = value
28+
29+
30+
class TestB3Format(unittest.TestCase):
31+
def test_extract_multi_header(self):
32+
"""Test the extraction of B3 headers """
33+
trace_id = str(trace.generate_trace_id())
34+
span_id = str(trace.generate_span_id())
35+
carrier = {
36+
FORMAT.TRACE_ID_KEY: trace_id,
37+
FORMAT.SPAN_ID_KEY: span_id,
38+
FORMAT.SAMPLED_KEY: "1",
39+
}
40+
span_context = FORMAT.extract(_get_from_dict, carrier)
41+
new_carrier = {}
42+
FORMAT.inject(span_context, _set_into_dict, new_carrier)
43+
self.assertEqual(new_carrier[FORMAT.TRACE_ID_KEY], trace_id)
44+
self.assertEqual(new_carrier[FORMAT.SPAN_ID_KEY], span_id)
45+
self.assertEqual(new_carrier[FORMAT.SAMPLED_KEY], "1")
46+
47+
def test_extract_single_headder(self):
48+
"""Test the extraction from a single b3 header"""
49+
trace_id = str(trace.generate_trace_id())
50+
span_id = str(trace.generate_span_id())
51+
carrier = {FORMAT.SINGLE_HEADER_KEY: "{}-{}".format(trace_id, span_id)}
52+
span_context = FORMAT.extract(_get_from_dict, carrier)
53+
new_carrier = {}
54+
FORMAT.inject(span_context, _set_into_dict, new_carrier)
55+
self.assertEqual(new_carrier[FORMAT.TRACE_ID_KEY], trace_id)
56+
self.assertEqual(new_carrier[FORMAT.SPAN_ID_KEY], span_id)
57+
self.assertEqual(new_carrier[FORMAT.SAMPLED_KEY], "1")

0 commit comments

Comments
 (0)