Skip to content

Commit a8dc9b3

Browse files
committed
Changed to the propagator API
Adding a UnifiedContext, composing DistributedContext and SpanContext. This will enable propagators to extract and inject values from either system, enabling more sophisticated schemes and standards to propagate data. This also removes the need for generics and propagators that only consume one or the other, requiring integrators to do extra work to wire propagators appropriately. Modifying the API of the propagators to consume the context as a mutable argument. By passing in the context rather than returning, this enables the chained use of propagators, allowing for situations such as supporting multiple trace propagation standards simulatenously.
1 parent 4aea780 commit a8dc9b3

File tree

7 files changed

+154
-78
lines changed

7 files changed

+154
-78
lines changed

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

+7-10
Original file line numberDiff line numberDiff line change
@@ -11,25 +11,22 @@
1111
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
14-
15-
1614
"""
1715
The OpenTelemetry context module provides abstraction layer on top of
1816
thread-local storage and contextvars. The long term direction is to switch to
1917
contextvars provided by the Python runtime library.
2018
2119
A global object ``Context`` is provided to access all the context related
22-
functionalities::
20+
functionalities:
2321
2422
>>> from opentelemetry.context import Context
2523
>>> Context.foo = 1
2624
>>> Context.foo = 2
2725
>>> Context.foo
2826
2
2927
30-
When explicit thread is used, a helper function
31-
``Context.with_current_context`` can be used to carry the context across
32-
threads::
28+
When explicit thread is used, a helper function `Context.with_current_context`
29+
can be used to carry the context across threads:
3330
3431
from threading import Thread
3532
from opentelemetry.context import Context
@@ -62,7 +59,7 @@ def work(name):
6259
6360
print('Main thread:', Context)
6461
65-
Here goes another example using thread pool::
62+
Here goes another example using thread pool:
6663
6764
import time
6865
import threading
@@ -97,7 +94,7 @@ def work(name):
9794
pool.join()
9895
println('Main thread: {}'.format(Context))
9996
100-
Here goes a simple demo of how async could work in Python 3.7+::
97+
Here goes a simple demo of how async could work in Python 3.7+:
10198
10299
import asyncio
103100
@@ -141,9 +138,9 @@ async def main():
141138
import typing
142139

143140
from .base_context import BaseRuntimeContext
141+
from .unified_context import UnifiedContext
144142

145-
__all__ = ["Context"]
146-
143+
__all__ = ["Context", "UnifiedContext"]
147144

148145
Context = None # type: typing.Optional[BaseRuntimeContext]
149146

opentelemetry-api/src/opentelemetry/context/base_context.py

+2-4
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ def set(self, value: "object") -> None:
3737
raise NotImplementedError
3838

3939
_lock = threading.Lock()
40-
_slots = {} # type: typing.Dict[str, 'BaseRuntimeContext.Slot']
40+
_slots: typing.Dict[str, Slot] = {}
4141

4242
@classmethod
4343
def clear(cls) -> None:
@@ -48,9 +48,7 @@ def clear(cls) -> None:
4848
slot.clear()
4949

5050
@classmethod
51-
def register_slot(
52-
cls, name: str, default: "object" = None
53-
) -> "BaseRuntimeContext.Slot":
51+
def register_slot(cls, name: str, default: "object" = None) -> "Slot":
5452
"""Register a context slot with an optional default value.
5553
5654
:type name: str

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

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

1515
import abc
16-
import typing
1716

18-
from opentelemetry.trace import SpanContext
17+
from opentelemetry.context import UnifiedContext
1918

2019

2120
class BinaryFormat(abc.ABC):
@@ -27,14 +26,14 @@ class BinaryFormat(abc.ABC):
2726

2827
@staticmethod
2928
@abc.abstractmethod
30-
def to_bytes(context: SpanContext) -> bytes:
29+
def to_bytes(context: UnifiedContext) -> bytes:
3130
"""Creates a byte representation of a SpanContext.
3231
3332
to_bytes should read values from a SpanContext and return a data
3433
format to represent it, in bytes.
3534
3635
Args:
37-
context: the SpanContext to serialize
36+
context: the SpanContext to serialize.
3837
3938
Returns:
4039
A bytes representation of the SpanContext.
@@ -43,15 +42,17 @@ def to_bytes(context: SpanContext) -> bytes:
4342

4443
@staticmethod
4544
@abc.abstractmethod
46-
def from_bytes(byte_representation: bytes) -> typing.Optional[SpanContext]:
47-
"""Return a SpanContext that was represented by bytes.
45+
def from_bytes(context: UnifiedContext,
46+
byte_representation: bytes) -> None:
47+
"""Populate UnifiedContext that was represented by bytes.
4848
49-
from_bytes should return back a SpanContext that was constructed from
50-
the data serialized in the byte_representation passed. If it is not
49+
from_bytes should populated UnifiedContext with data that was
50+
serialized in the byte_representation passed. If it is not
5151
possible to read in a proper SpanContext, return None.
5252
5353
Args:
54-
byte_representation: the bytes to deserialize
54+
context: The UnifiedContext to populate.
55+
byte_representation: the bytes to deserialize.
5556
5657
Returns:
5758
A bytes representation of the SpanContext if it is valid.

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

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

18+
from opentelemetry.context import UnifiedContext
1819
from opentelemetry.trace import SpanContext
1920

2021
Setter = typing.Callable[[object, str, str], None]
@@ -35,11 +36,12 @@ class HTTPTextFormat(abc.ABC):
3536
import flask
3637
import requests
3738
from opentelemetry.context.propagation import HTTPTextFormat
39+
from opentelemetry.trace import tracer
40+
from opentelemetry.context import UnifiedContext
3841
3942
PROPAGATOR = HTTPTextFormat()
4043
4144
42-
4345
def get_header_from_flask_request(request, key):
4446
return request.headers.get_all(key)
4547
@@ -48,15 +50,17 @@ def set_header_into_requests_request(request: requests.Request,
4850
request.headers[key] = value
4951
5052
def example_route():
51-
span_context = PROPAGATOR.extract(
52-
get_header_from_flask_request,
53+
span = tracer().create_span("")
54+
context = UnifiedContext.create(span)
55+
PROPAGATOR.extract(
56+
context, get_header_from_flask_request,
5357
flask.request
5458
)
5559
request_to_downstream = requests.Request(
5660
"GET", "http://httpbin.org/get"
5761
)
5862
PROPAGATOR.inject(
59-
span_context,
63+
context,
6064
set_header_into_requests_request,
6165
request_to_downstream
6266
)
@@ -70,15 +74,20 @@ def example_route():
7074

7175
@abc.abstractmethod
7276
def extract(
73-
self, get_from_carrier: Getter, carrier: object
74-
) -> SpanContext:
75-
"""Create a SpanContext from values in the carrier.
77+
self,
78+
context: UnifiedContext,
79+
get_from_carrier: Getter,
80+
carrier: object,
81+
) -> None:
82+
"""Extract values from the carrier into the context.
7683
7784
The extract function should retrieve values from the carrier
78-
object using get_from_carrier, and use values to populate a
79-
SpanContext value and return it.
85+
object using get_from_carrier, and use values to populate
86+
attributes of the UnifiedContext passed in.
8087
8188
Args:
89+
context: A UnifiedContext instance that will be
90+
populated with values from the carrier.
8291
get_from_carrier: a function that can retrieve zero
8392
or more values from the carrier. In the case that
8493
the value does not exist, return an empty list.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
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.distributedcontext import DistributedContext
16+
from opentelemetry.trace import SpanContext
17+
18+
19+
class UnifiedContext:
20+
"""A unified context object that contains all context relevant to
21+
telemetry.
22+
23+
The UnifiedContext is a single object that composes all contexts that
24+
are needed by the various forms of telemetry. It is intended to be an
25+
object that can be passed as the argument to any component that needs
26+
to read or modify content values (such as propagators). By unifying
27+
all context in a composed data structure, it expands the flexibility
28+
of the APIs that modify it.
29+
30+
As it is designed to carry context specific to all telemetry use
31+
cases, it's schema is explicit. Note that this is not intended to
32+
be an object that acts as a singleton that returns different results
33+
based on the thread or coroutine of execution. For that, see `Context`.
34+
35+
36+
Args:
37+
distributed: The DistributedContext for this instance.
38+
span: The SpanContext for this instance.
39+
"""
40+
__slots__ = ["distributed", "span"]
41+
42+
def __init__(self, distributed: DistributedContext, span: SpanContext):
43+
self.distributed = distributed
44+
self.span = span
45+
46+
@staticmethod
47+
def create(span: SpanContext) -> "UnifiedContext":
48+
"""Create an unpopulated UnifiedContext object.
49+
50+
Example:
51+
52+
from opentelemetry.trace import tracer
53+
span = tracer.create_span("")
54+
context = UnifiedContext.create(span)
55+
56+
57+
Args:
58+
parent_span: the parent SpanContext that will be the
59+
parent of the span in the UnifiedContext.
60+
"""
61+
return UnifiedContext(DistributedContext(), span)
62+
63+
def __repr__(self) -> str:
64+
return "{}(distributed={}, span={})".format(
65+
type(self).__name__, repr(self.distributed), repr(self.span))

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

+3-3
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ class B3Format(HTTPTextFormat):
3232
_SAMPLE_PROPAGATE_VALUES = set(["1", "True", "true", "d"])
3333

3434
@classmethod
35-
def extract(cls, get_from_carrier, carrier):
35+
def extract(cls, context, get_from_carrier, carrier):
3636
trace_id = format_trace_id(trace.INVALID_TRACE_ID)
3737
span_id = format_span_id(trace.INVALID_SPAN_ID)
3838
sampled = 0
@@ -57,7 +57,7 @@ def extract(cls, get_from_carrier, carrier):
5757
elif len(fields) == 4:
5858
trace_id, span_id, sampled, _parent_span_id = fields
5959
else:
60-
return trace.INVALID_SPAN_CONTEXT
60+
return
6161
else:
6262
trace_id = (
6363
_extract_first_element(
@@ -92,7 +92,7 @@ def extract(cls, get_from_carrier, carrier):
9292
if sampled in cls._SAMPLE_PROPAGATE_VALUES or flags == "1":
9393
options |= trace.TraceOptions.RECORDED
9494

95-
return trace.SpanContext(
95+
context.span = trace.SpanContext(
9696
# trace an span ids are encoded in hex, so must be converted
9797
trace_id=int(trace_id, 16),
9898
span_id=int(span_id, 16),

0 commit comments

Comments
 (0)