Skip to content
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

Merged
merged 20 commits into from
Jul 27, 2019
125 changes: 125 additions & 0 deletions opentelemetry-api/src/opentelemetry/context/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Copy link
Member

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.

Copy link
Member Author

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 :)

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):
Copy link
Member Author

Choose a reason for hiding this comment

The 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):
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This example only works on Python3.7+ due to the new async/await syntax.
We can explore more in #62.

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())
"""
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about adding this module to the sphinx docs?

Copy link
Member Author

Choose a reason for hiding this comment

The 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
Expand Down