Skip to content

Commit 74a9534

Browse files
authored
1 parent 2edc07f commit 74a9534

File tree

9 files changed

+457
-0
lines changed

9 files changed

+457
-0
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from .binaryformat import BinaryFormat
2+
from .httptextformat import HTTPTextFormat
3+
4+
__all__ = ["BinaryFormat", "HTTPTextFormat"]
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
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+
18+
from opentelemetry.trace import SpanContext
19+
20+
21+
class BinaryFormat(abc.ABC):
22+
"""API for serialization of span context into binary formats.
23+
24+
This class provides an interface that enables converting span contexts
25+
to and from a binary format.
26+
"""
27+
@staticmethod
28+
@abc.abstractmethod
29+
def to_bytes(context: SpanContext) -> bytes:
30+
"""Creates a byte representation of a SpanContext.
31+
32+
to_bytes should read values from a SpanContext and return a data
33+
format to represent it, in bytes.
34+
35+
Args:
36+
context: the SpanContext to serialize
37+
38+
Returns:
39+
A bytes representation of the SpanContext.
40+
41+
"""
42+
@staticmethod
43+
@abc.abstractmethod
44+
def from_bytes(byte_representation: bytes) -> typing.Optional[SpanContext]:
45+
"""Return a SpanContext that was represented by bytes.
46+
47+
from_bytes should return back a SpanContext that was constructed from
48+
the data serialized in the byte_representation passed. If it is not
49+
possible to read in a proper SpanContext, return None.
50+
51+
Args:
52+
byte_representation: the bytes to deserialize
53+
54+
Returns:
55+
A bytes representation of the SpanContext if it is valid.
56+
Otherwise return None.
57+
58+
"""
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
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+
18+
from opentelemetry.trace import SpanContext
19+
20+
Setter = typing.Callable[[object, str, str], None]
21+
Getter = typing.Callable[[object, str], typing.List[str]]
22+
23+
24+
class HTTPTextFormat(abc.ABC):
25+
"""API for propagation of span context via headers.
26+
27+
This class provides an interface that enables extracting and injecting
28+
span context into headers of HTTP requests. HTTP frameworks and clients
29+
can integrate with HTTPTextFormat by providing the object containing the
30+
headers, and a getter and setter function for the extraction and
31+
injection of values, respectively.
32+
33+
Example::
34+
35+
import flask
36+
import requests
37+
from opentelemetry.context.propagation import HTTPTextFormat
38+
39+
PROPAGATOR = HTTPTextFormat()
40+
41+
42+
43+
def get_header_from_flask_request(request, key):
44+
return request.headers.get_all(key)
45+
46+
def set_header_into_requests_request(request: requests.Request,
47+
key: str, value: str):
48+
request.headers[key] = value
49+
50+
def example_route():
51+
span_context = PROPAGATOR.extract(
52+
get_header_from_flask_request,
53+
flask.request
54+
)
55+
request_to_downstream = requests.Request(
56+
"GET", "http://httpbin.org/get"
57+
)
58+
PROPAGATOR.inject(
59+
span_context,
60+
set_header_into_requests_request,
61+
request_to_downstream
62+
)
63+
session = requests.Session()
64+
session.send(request_to_downstream.prepare())
65+
66+
67+
.. _Propagation API Specification:
68+
https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/api-propagators.md
69+
"""
70+
@abc.abstractmethod
71+
def extract(self, get_from_carrier: Getter,
72+
carrier: object) -> SpanContext:
73+
"""Create a SpanContext from values in the carrier.
74+
75+
The extract function should retrieve values from the carrier
76+
object using get_from_carrier, and use values to populate a
77+
SpanContext value and return it.
78+
79+
Args:
80+
get_from_carrier: a function that can retrieve zero
81+
or more values from the carrier. In the case that
82+
the value does not exist, return an empty list.
83+
carrier: and object which contains values that are
84+
used to construct a SpanContext. This object
85+
must be paired with an appropriate get_from_carrier
86+
which understands how to extract a value from it.
87+
Returns:
88+
A SpanContext with configuration found in the carrier.
89+
90+
"""
91+
@abc.abstractmethod
92+
def inject(self, context: SpanContext, set_in_carrier: Setter,
93+
carrier: object) -> None:
94+
"""Inject values from a SpanContext into a carrier.
95+
96+
inject enables the propagation of values into HTTP clients or
97+
other objects which perform an HTTP request. Implementations
98+
should use the set_in_carrier method to set values on the
99+
carrier.
100+
101+
Args:
102+
context: The SpanContext to read values from.
103+
set_in_carrier: A setter function that can set values
104+
on the carrier.
105+
carrier: An object that a place to define HTTP headers.
106+
Should be paired with set_in_carrier, which should
107+
know how to set header values on the carrier.
108+
109+
"""

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

Whitespace-only changes.

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

Whitespace-only changes.
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
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 typing
16+
17+
from opentelemetry.context.propagation.httptextformat import HTTPTextFormat
18+
import opentelemetry.trace as trace
19+
20+
21+
class B3Format(HTTPTextFormat):
22+
"""Propagator for the B3 HTTP header format.
23+
24+
See: https://github.com/openzipkin/b3-propagation
25+
"""
26+
27+
SINGLE_HEADER_KEY = "b3"
28+
TRACE_ID_KEY = "x-b3-traceid"
29+
SPAN_ID_KEY = "x-b3-spanid"
30+
SAMPLED_KEY = "x-b3-sampled"
31+
FLAGS_KEY = "x-b3-flags"
32+
_SAMPLE_PROPAGATE_VALUES = set(["1", "True", "true", "d"])
33+
34+
@classmethod
35+
def extract(cls, get_from_carrier, carrier):
36+
trace_id = format_trace_id(trace.INVALID_TRACE_ID)
37+
span_id = format_span_id(trace.INVALID_SPAN_ID)
38+
sampled = 0
39+
flags = None
40+
41+
single_header = _extract_first_element(
42+
get_from_carrier(carrier, cls.SINGLE_HEADER_KEY))
43+
if single_header:
44+
# The b3 spec calls for the sampling state to be
45+
# "deferred", which is unspecified. This concept does not
46+
# translate to SpanContext, so we set it as recorded.
47+
sampled = "1"
48+
fields = single_header.split("-", 4)
49+
50+
if len(fields) == 1:
51+
sampled = fields[0]
52+
elif len(fields) == 2:
53+
trace_id, span_id = fields
54+
elif len(fields) == 3:
55+
trace_id, span_id, sampled = fields
56+
elif len(fields) == 4:
57+
trace_id, span_id, sampled, _parent_span_id = fields
58+
else:
59+
return trace.INVALID_SPAN_CONTEXT
60+
else:
61+
trace_id = _extract_first_element(
62+
get_from_carrier(carrier, cls.TRACE_ID_KEY)) or trace_id
63+
span_id = _extract_first_element(
64+
get_from_carrier(carrier, cls.SPAN_ID_KEY)) or span_id
65+
sampled = _extract_first_element(
66+
get_from_carrier(carrier, cls.SAMPLED_KEY)) or sampled
67+
flags = _extract_first_element(
68+
get_from_carrier(carrier, cls.FLAGS_KEY)) or flags
69+
70+
options = 0
71+
# The b3 spec provides no defined behavior for both sample and
72+
# flag values set. Since the setting of at least one implies
73+
# the desire for some form of sampling, propagate if either
74+
# header is set to allow.
75+
if sampled in cls._SAMPLE_PROPAGATE_VALUES or flags == "1":
76+
options |= trace.TraceOptions.RECORDED
77+
78+
return trace.SpanContext(
79+
# trace an span ids are encoded in hex, so must be converted
80+
trace_id=int(trace_id, 16),
81+
span_id=int(span_id, 16),
82+
trace_options=options,
83+
trace_state={},
84+
)
85+
86+
@classmethod
87+
def inject(cls, context, set_in_carrier, carrier):
88+
sampled = (trace.TraceOptions.RECORDED & context.trace_options) != 0
89+
set_in_carrier(carrier, cls.TRACE_ID_KEY,
90+
format_trace_id(context.trace_id))
91+
set_in_carrier(carrier, cls.SPAN_ID_KEY,
92+
format_span_id(context.span_id))
93+
set_in_carrier(carrier, cls.SAMPLED_KEY, "1" if sampled else "0")
94+
95+
96+
def format_trace_id(trace_id: int):
97+
"""Format the trace id according to b3 specification."""
98+
return format(trace_id, "032x")
99+
100+
101+
def format_span_id(span_id: int):
102+
"""Format the span id according to b3 specification."""
103+
return format(span_id, "016x")
104+
105+
106+
def _extract_first_element(list_object: list) -> typing.Optional[object]:
107+
if list_object:
108+
return list_object[0]
109+
return None

opentelemetry-sdk/tests/context/__init__.py

Whitespace-only changes.

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

Whitespace-only changes.

0 commit comments

Comments
 (0)