-
Notifications
You must be signed in to change notification settings - Fork 623
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Initial implementation of context #57
Changes from 1 commit
989c3e7
dd979b2
e6fbe91
44e5897
beaa000
9f71780
90fde07
b8ca3e4
00a256c
199054a
023eac9
f83cf89
62517f0
937240e
2b2f553
533fd2a
123887e
1f93a36
0b114e4
e557afd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,6 +12,131 @@ | |
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
|
||
""" | ||
The OpenTelemetry context module provides abstraction layer on top of | ||
thread-local storage and contextvars. The long term direction is to switch to | ||
contextvars provided by the Python runtime library. | ||
|
||
A global object ``Context`` is provided to access all the context related | ||
functionalities: | ||
|
||
>>> from opentelemetry.context import Context | ||
>>> Context.foo = 1 | ||
>>> Context.foo = 2 | ||
>>> Context.foo | ||
2 | ||
|
||
When explicit thread is used, a helper function `Context.with_current_context` | ||
can be used to carry the context across threads: | ||
|
||
from threading import Thread | ||
from opentelemetry.context import Context | ||
|
||
def work(name): | ||
print('Entering worker:', Context) | ||
Context.operation_id = name | ||
print('Exiting worker:', Context) | ||
|
||
if __name__ == '__main__': | ||
print('Main thread:', Context) | ||
Context.operation_id = 'main' | ||
|
||
print('Main thread:', Context) | ||
|
||
# by default context is not propagated to worker thread | ||
thread = Thread(target=work, args=('foo',)) | ||
thread.start() | ||
thread.join() | ||
|
||
print('Main thread:', Context) | ||
|
||
# user can propagate context explicitly | ||
thread = Thread( | ||
target=Context.with_current_context(work), | ||
args=('bar',), | ||
) | ||
thread.start() | ||
thread.join() | ||
|
||
print('Main thread:', Context) | ||
|
||
Here goes another example using thread pool: | ||
|
||
import time | ||
import threading | ||
|
||
from multiprocessing.dummy import Pool as ThreadPool | ||
from opentelemetry.context import Context | ||
|
||
_console_lock = threading.Lock() | ||
|
||
def println(msg): | ||
with _console_lock: | ||
print(msg) | ||
|
||
def work(name): | ||
println('Entering worker[{}]: {}'.format(name, Context)) | ||
Context.operation_id = name | ||
time.sleep(0.01) | ||
println('Exiting worker[{}]: {}'.format(name, Context)) | ||
|
||
if __name__ == "__main__": | ||
println('Main thread: {}'.format(Context)) | ||
Context.operation_id = 'main' | ||
pool = ThreadPool(2) # create a thread pool with 2 threads | ||
pool.map(Context.with_current_context(work), [ | ||
'bear', | ||
'cat', | ||
'dog', | ||
'horse', | ||
'rabbit', | ||
]) | ||
pool.close() | ||
pool.join() | ||
println('Main thread: {}'.format(Context)) | ||
|
||
Here goes a simple demo of how async could work in Python 3.7+: | ||
|
||
import asyncio | ||
|
||
from opentelemetry.context import Context | ||
|
||
class Span(object): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @c24t this should unblock your work on the actual span creation and tracer context update. |
||
def __init__(self, name): | ||
self.name = name | ||
self.parent = Context.current_span | ||
|
||
def __repr__(self): | ||
return ('{}(name={}, parent={})' | ||
.format( | ||
type(self).__name__, | ||
self.name, | ||
self.parent, | ||
)) | ||
|
||
async def __aenter__(self): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This example only works on Python3.7+ due to the new |
||
Context.current_span = self | ||
|
||
async def __aexit__(self, exc_type, exc, tb): | ||
Context.current_span = self.parent | ||
|
||
async def main(): | ||
print(Context) | ||
async with Span('foo'): | ||
print(Context) | ||
await asyncio.sleep(0.1) | ||
async with Span('bar'): | ||
print(Context) | ||
await asyncio.sleep(0.1) | ||
print(Context) | ||
await asyncio.sleep(0.1) | ||
print(Context) | ||
|
||
if __name__ == '__main__': | ||
asyncio.run(main()) | ||
""" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How about adding this module to the sphinx docs? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure, I'll send a follow up PR to add test cases and docs. |
||
|
||
import typing | ||
|
||
from .base_context import BaseRuntimeContext | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this helps clear up the value of with_current_context. Nice!
I feel like the need to add with_current_context will probably be a gotcha in many cases. It's too bad there isn't a way to make this something that is implicitly shared.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When the day comes that contextvars or some built-in Python lib support such scenario, we can say goodbye to this
Context
class and never need to reinvent wheels again :)