mirror of
https://github.com/open-telemetry/opentelemetry-python-contrib.git
synced 2025-08-01 17:34:38 +08:00
143 lines
5.8 KiB
Python
143 lines
5.8 KiB
Python
import tornado
|
|
from tornado.ioloop import IOLoop
|
|
import sys
|
|
|
|
from ...context import Context
|
|
from ...provider import DefaultContextProvider
|
|
|
|
# tornado.stack_context deprecated in Tornado 5 removed in Tornado 6
|
|
# instead use DefaultContextProvider with ContextVarContextManager for asyncio
|
|
_USE_STACK_CONTEXT = not (
|
|
sys.version_info >= (3, 7) and tornado.version_info >= (5, 0)
|
|
)
|
|
|
|
if _USE_STACK_CONTEXT:
|
|
from tornado.stack_context import StackContextInconsistentError, _state
|
|
|
|
class TracerStackContext(DefaultContextProvider):
|
|
"""
|
|
A context manager that manages ``Context`` instances in a thread-local state.
|
|
It must be used everytime a Tornado's handler or coroutine is used within a
|
|
tracing Context. It is meant to work like a traditional ``StackContext``,
|
|
preserving the state across asynchronous calls.
|
|
|
|
Everytime a new manager is initialized, a new ``Context()`` is created for
|
|
this execution flow. A context created in a ``TracerStackContext`` is not
|
|
shared between different threads.
|
|
|
|
This implementation follows some suggestions provided here:
|
|
https://github.com/tornadoweb/tornado/issues/1063
|
|
"""
|
|
def __init__(self):
|
|
# DEV: skip resetting context manager since TracerStackContext is used
|
|
# as a with-statement context where we do not want to be clearing the
|
|
# current context for a thread or task
|
|
super(TracerStackContext, self).__init__(reset_context_manager=False)
|
|
self._active = True
|
|
self._context = Context()
|
|
|
|
def enter(self):
|
|
"""
|
|
Required to preserve the ``StackContext`` protocol.
|
|
"""
|
|
pass
|
|
|
|
def exit(self, type, value, traceback): # noqa: A002
|
|
"""
|
|
Required to preserve the ``StackContext`` protocol.
|
|
"""
|
|
pass
|
|
|
|
def __enter__(self):
|
|
self.old_contexts = _state.contexts
|
|
self.new_contexts = (self.old_contexts[0] + (self,), self)
|
|
_state.contexts = self.new_contexts
|
|
return self
|
|
|
|
def __exit__(self, type, value, traceback): # noqa: A002
|
|
final_contexts = _state.contexts
|
|
_state.contexts = self.old_contexts
|
|
|
|
if final_contexts is not self.new_contexts:
|
|
raise StackContextInconsistentError(
|
|
'stack_context inconsistency (may be caused by yield '
|
|
'within a "with TracerStackContext" block)')
|
|
|
|
# break the reference to allow faster GC on CPython
|
|
self.new_contexts = None
|
|
|
|
def deactivate(self):
|
|
self._active = False
|
|
|
|
def _has_io_loop(self):
|
|
"""Helper to determine if we are currently in an IO loop"""
|
|
return getattr(IOLoop._current, 'instance', None) is not None
|
|
|
|
def _has_active_context(self):
|
|
"""Helper to determine if we have an active context or not"""
|
|
if not self._has_io_loop():
|
|
return self._local._has_active_context()
|
|
else:
|
|
# we're inside a Tornado loop so the TracerStackContext is used
|
|
return self._get_state_active_context() is not None
|
|
|
|
def _get_state_active_context(self):
|
|
"""Helper to get the currently active context from the TracerStackContext"""
|
|
# we're inside a Tornado loop so the TracerStackContext is used
|
|
for stack in reversed(_state.contexts[0]):
|
|
if isinstance(stack, self.__class__) and stack._active:
|
|
return stack._context
|
|
return None
|
|
|
|
def active(self):
|
|
"""
|
|
Return the ``Context`` from the current execution flow. This method can be
|
|
used inside a Tornado coroutine to retrieve and use the current tracing context.
|
|
If used in a separated Thread, the `_state` thread-local storage is used to
|
|
propagate the current Active context from the `MainThread`.
|
|
"""
|
|
if not self._has_io_loop():
|
|
# if a Tornado loop is not available, it means that this method
|
|
# has been called from a synchronous code, so we can rely in a
|
|
# thread-local storage
|
|
return self._local.get()
|
|
else:
|
|
# we're inside a Tornado loop so the TracerStackContext is used
|
|
return self._get_state_active_context()
|
|
|
|
def activate(self, ctx):
|
|
"""
|
|
Set the active ``Context`` for this async execution. If a ``TracerStackContext``
|
|
is not found, the context is discarded.
|
|
If used in a separated Thread, the `_state` thread-local storage is used to
|
|
propagate the current Active context from the `MainThread`.
|
|
"""
|
|
if not self._has_io_loop():
|
|
# because we're outside of an asynchronous execution, we store
|
|
# the current context in a thread-local storage
|
|
self._local.set(ctx)
|
|
else:
|
|
# we're inside a Tornado loop so the TracerStackContext is used
|
|
for stack_ctx in reversed(_state.contexts[0]):
|
|
if isinstance(stack_ctx, self.__class__) and stack_ctx._active:
|
|
stack_ctx._context = ctx
|
|
return ctx
|
|
else:
|
|
# no-op when not using stack_context
|
|
class TracerStackContext(DefaultContextProvider):
|
|
def __enter__(self):
|
|
pass
|
|
|
|
def __exit__(self, *exc):
|
|
pass
|
|
|
|
|
|
def run_with_trace_context(func, *args, **kwargs):
|
|
"""
|
|
Run the given function within a traced StackContext. This function is used to
|
|
trace Tornado web handlers, but can be used in your code to trace coroutines
|
|
execution.
|
|
"""
|
|
with TracerStackContext():
|
|
return func(*args, **kwargs)
|