forked from DataDog/dd-trace-py
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathstack_context.py
145 lines (121 loc) · 5.95 KB
/
stack_context.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
import tornado
from tornado.ioloop import IOLoop
from ddtrace._trace.provider import BaseContextProvider
from ddtrace._trace.provider import DefaultContextProvider
from ...span import Span
# tornado.stack_context deprecated in Tornado 5 removed in Tornado 6
# instead use DefaultContextProvider with ContextVarContextManager for asyncio
_USE_STACK_CONTEXT = not tornado.version_info >= (5, 0)
if _USE_STACK_CONTEXT:
from tornado.stack_context import StackContextInconsistentError
from tornado.stack_context import _state
class TracerStackContext(DefaultContextProvider):
"""
A context manager that manages ``Context`` instances in a thread-local state.
It must be used every time 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.
Every time 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):
# type: (...) -> None
# HACK(jd): this should be using super(), but calling DefaultContextProvider.__init__
# sets the context to `None` which breaks this code.
# We therefore skip DefaultContextProvider.__init__ and call only BaseContextProvider.__init__.
BaseContextProvider.__init__(self)
self._context = None
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 _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 super(TracerStackContext, self)._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__):
ctx = stack._context
if isinstance(ctx, Span):
return self._update_active(ctx)
return ctx
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 super(TracerStackContext, self).active()
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
super(TracerStackContext, self).activate(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__):
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)