Skip to content

Commit 58796b6

Browse files
author
Alex Boten
committed
Context API
This change implements the Context API portion of OTEP open-telemetry#66. The CorrelationContext API and Propagation API changes will come in future PRs. We're leveraging entrypoints to support other implementations of the Context API if/when necessary. For backwards compatibility, this change uses aiocontextvars for Python versions older than 3.7. Signed-off-by: Alex Boten <[email protected]>
1 parent 6aa69ae commit 58796b6

File tree

17 files changed

+425
-393
lines changed

17 files changed

+425
-393
lines changed

ext/opentelemetry-ext-http-requests/src/opentelemetry/ext/http_requests/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
from requests.sessions import Session
2424

2525
from opentelemetry import propagators
26-
from opentelemetry.context import Context
26+
from opentelemetry.context import get_value
2727
from opentelemetry.ext.http_requests.version import __version__
2828
from opentelemetry.trace import SpanKind
2929

@@ -54,7 +54,7 @@ def enable(tracer_source):
5454

5555
@functools.wraps(wrapped)
5656
def instrumented_request(self, method, url, *args, **kwargs):
57-
if Context.suppress_instrumentation:
57+
if get_value("suppress_instrumentation"):
5858
return wrapped(self, method, url, *args, **kwargs)
5959

6060
# See

opentelemetry-api/setup.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,10 @@
5656
"/tree/master/opentelemetry-api"
5757
),
5858
zip_safe=False,
59+
entry_points={
60+
"opentelemetry_context": [
61+
"default_context = "
62+
"opentelemetry.context.default_context:DefaultContext",
63+
]
64+
},
5965
)

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

Lines changed: 64 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -12,141 +12,68 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
import typing
16+
from os import environ
1517

16-
"""
17-
The OpenTelemetry context module provides abstraction layer on top of
18-
thread-local storage and contextvars. The long term direction is to switch to
19-
contextvars provided by the Python runtime library.
20-
21-
A global object ``Context`` is provided to access all the context related
22-
functionalities::
23-
24-
>>> from opentelemetry.context import Context
25-
>>> Context.foo = 1
26-
>>> Context.foo = 2
27-
>>> Context.foo
28-
2
29-
30-
When explicit thread is used, a helper function
31-
``Context.with_current_context`` can be used to carry the context across
32-
threads::
33-
34-
from threading import Thread
35-
from opentelemetry.context import Context
36-
37-
def work(name):
38-
print('Entering worker:', Context)
39-
Context.operation_id = name
40-
print('Exiting worker:', Context)
41-
42-
if __name__ == '__main__':
43-
print('Main thread:', Context)
44-
Context.operation_id = 'main'
45-
46-
print('Main thread:', Context)
47-
48-
# by default context is not propagated to worker thread
49-
thread = Thread(target=work, args=('foo',))
50-
thread.start()
51-
thread.join()
52-
53-
print('Main thread:', Context)
54-
55-
# user can propagate context explicitly
56-
thread = Thread(
57-
target=Context.with_current_context(work),
58-
args=('bar',),
59-
)
60-
thread.start()
61-
thread.join()
62-
63-
print('Main thread:', Context)
64-
65-
Here goes another example using thread pool::
66-
67-
import time
68-
import threading
69-
70-
from multiprocessing.dummy import Pool as ThreadPool
71-
from opentelemetry.context import Context
72-
73-
_console_lock = threading.Lock()
74-
75-
def println(msg):
76-
with _console_lock:
77-
print(msg)
78-
79-
def work(name):
80-
println('Entering worker[{}]: {}'.format(name, Context))
81-
Context.operation_id = name
82-
time.sleep(0.01)
83-
println('Exiting worker[{}]: {}'.format(name, Context))
84-
85-
if __name__ == "__main__":
86-
println('Main thread: {}'.format(Context))
87-
Context.operation_id = 'main'
88-
pool = ThreadPool(2) # create a thread pool with 2 threads
89-
pool.map(Context.with_current_context(work), [
90-
'bear',
91-
'cat',
92-
'dog',
93-
'horse',
94-
'rabbit',
95-
])
96-
pool.close()
97-
pool.join()
98-
println('Main thread: {}'.format(Context))
99-
100-
Here goes a simple demo of how async could work in Python 3.7+::
101-
102-
import asyncio
103-
104-
from opentelemetry.context import Context
105-
106-
class Span(object):
107-
def __init__(self, name):
108-
self.name = name
109-
self.parent = Context.current_span
110-
111-
def __repr__(self):
112-
return ('{}(name={}, parent={})'
113-
.format(
114-
type(self).__name__,
115-
self.name,
116-
self.parent,
117-
))
118-
119-
async def __aenter__(self):
120-
Context.current_span = self
121-
122-
async def __aexit__(self, exc_type, exc, tb):
123-
Context.current_span = self.parent
124-
125-
async def main():
126-
print(Context)
127-
async with Span('foo'):
128-
print(Context)
129-
await asyncio.sleep(0.1)
130-
async with Span('bar'):
131-
print(Context)
132-
await asyncio.sleep(0.1)
133-
print(Context)
134-
await asyncio.sleep(0.1)
135-
print(Context)
136-
137-
if __name__ == '__main__':
138-
asyncio.run(main())
139-
"""
140-
141-
from .base_context import BaseRuntimeContext
142-
143-
__all__ = ["Context"]
144-
145-
try:
146-
from .async_context import AsyncRuntimeContext
147-
148-
Context = AsyncRuntimeContext() # type: BaseRuntimeContext
149-
except ImportError:
150-
from .thread_local_context import ThreadLocalRuntimeContext
151-
152-
Context = ThreadLocalRuntimeContext()
18+
from pkg_resources import iter_entry_points
19+
20+
from opentelemetry.context.context import Context
21+
22+
# FIXME use a better implementation of a configuration manager to avoid having
23+
# to get configuration values straight from environment variables
24+
_CONTEXT = {
25+
entry_point.name: entry_point.load()
26+
for entry_point in (iter_entry_points("opentelemetry_context"))
27+
}[
28+
environ.get("OPENTELEMETRY_CONTEXT", "default_context")
29+
]() # type: Context
30+
31+
32+
def _copy_context(context: typing.Optional[Context]) -> Context:
33+
if context:
34+
return context.copy()
35+
return get_current().copy()
36+
37+
38+
def create_key(key: str) -> "object":
39+
# FIXME Implement this
40+
raise NotImplementedError
41+
42+
43+
def get_value(key: str, context: typing.Optional[Context] = None) -> "object":
44+
if context:
45+
return context.get_value(key)
46+
return get_current().get_value(key)
47+
48+
49+
def set_value(
50+
key: str, value: "object", context: typing.Optional[Context] = None
51+
) -> Context:
52+
new_context = _copy_context(context)
53+
new_context.set_value(key, value)
54+
return new_context
55+
56+
57+
def remove_value(context: Context, key: str) -> Context:
58+
new_context = _copy_context(context)
59+
new_context.remove_value(key)
60+
return new_context
61+
62+
63+
def get_current() -> Context:
64+
return _CONTEXT
65+
66+
67+
def set_current(context: Context) -> None:
68+
global _CONTEXT
69+
_CONTEXT = context
70+
71+
72+
__all__ = [
73+
"get_value",
74+
"set_value",
75+
"remove_value",
76+
"get_current",
77+
"set_current",
78+
"Context",
79+
]

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

Lines changed: 0 additions & 45 deletions
This file was deleted.

0 commit comments

Comments
 (0)