14
14
15
15
error_logger = logging .getLogger ('elasticapm.errors' )
16
16
17
- thread_local = threading .local ()
18
- thread_local .transaction = None
19
-
20
-
21
17
_time_func = timeit .default_timer
22
18
23
19
24
20
TAG_RE = re .compile ('^[^.*\" ]+$' )
25
21
26
22
27
- DROPPED_SPAN = object ()
28
- IGNORED_SPAN = object ()
29
-
30
-
31
- def get_transaction (clear = False ):
32
- """
33
- Get the transaction registered for the current thread.
34
-
35
- :return:
36
- :rtype: Transaction
37
- """
38
- transaction = getattr (thread_local , "transaction" , None )
39
- if clear :
40
- thread_local .transaction = None
41
- return transaction
23
+ try :
24
+ from elasticapm .context .contextvars import get_transaction , set_transaction , get_span , set_span
25
+ except ImportError :
26
+ from elasticapm .context .threadlocal import get_transaction , set_transaction , get_span , set_span
42
27
43
28
44
29
class Transaction (object ):
@@ -54,11 +39,9 @@ def __init__(self, frames_collector_func, transaction_type="custom", is_sampled=
54
39
self ._frames_collector_func = frames_collector_func
55
40
56
41
self .spans = []
57
- self .span_stack = []
58
42
self .max_spans = max_spans
59
43
self .span_frames_min_duration = span_frames_min_duration
60
44
self .dropped_spans = 0
61
- self .ignore_subtree = False
62
45
self .context = {}
63
46
self .tags = {}
64
47
@@ -69,46 +52,35 @@ def end_transaction(self, skip_frames=8):
69
52
self .duration = _time_func () - self .start_time
70
53
71
54
def begin_span (self , name , span_type , context = None , leaf = False ):
72
- # If we were already called with `leaf=True`, we'll just push
73
- # a placeholder on the stack.
74
- if self .ignore_subtree :
75
- self .span_stack .append (IGNORED_SPAN )
76
- return None
77
-
78
- if leaf :
79
- self .ignore_subtree = True
80
-
81
- self ._span_counter += 1
82
-
83
- if self .max_spans and self ._span_counter > self .max_spans :
55
+ parent_span = get_span ()
56
+ if parent_span and parent_span .leaf :
57
+ span = DroppedSpan (parent_span , leaf = True )
58
+ elif self .max_spans and self ._span_counter > self .max_spans - 1 :
84
59
self .dropped_spans += 1
85
- self .span_stack .append (DROPPED_SPAN )
86
- return None
87
-
88
- start = _time_func () - self .start_time
89
- span = Span (self ._span_counter - 1 , name , span_type , start , context )
90
- self .span_stack .append (span )
60
+ span = DroppedSpan (parent_span )
61
+ self ._span_counter += 1
62
+ else :
63
+ start = _time_func () - self .start_time
64
+ span = Span (self ._span_counter , name , span_type , start , context , leaf )
65
+ span .parent = parent_span
66
+ self ._span_counter += 1
67
+ set_span (span )
91
68
return span
92
69
93
70
def end_span (self , skip_frames ):
94
- span = self .span_stack .pop ()
95
- if span is IGNORED_SPAN :
96
- return None
97
-
98
- self .ignore_subtree = False
99
-
100
- if span is DROPPED_SPAN :
71
+ span = get_span ()
72
+ if span is None :
73
+ raise LookupError ()
74
+ if isinstance (span , DroppedSpan ):
75
+ set_span (span .parent )
101
76
return
102
77
103
78
span .duration = _time_func () - span .start_time - self .start_time
104
79
105
- if self .span_stack :
106
- span .parent = self .span_stack [- 1 ].idx
107
-
108
80
if not self .span_frames_min_duration or span .duration >= self .span_frames_min_duration :
109
81
span .frames = self ._frames_collector_func ()[skip_frames :]
110
82
self .spans .append (span )
111
-
83
+ set_span ( span . parent )
112
84
return span
113
85
114
86
def to_dict (self ):
@@ -162,14 +134,22 @@ def to_dict(self):
162
134
'type' : encoding .keyword_field (self .type ),
163
135
'start' : self .start_time * 1000 , # milliseconds
164
136
'duration' : self .duration * 1000 , # milliseconds
165
- 'parent' : self .parent ,
137
+ 'parent' : self .parent . idx if self . parent else None ,
166
138
'context' : self .context
167
139
}
168
140
if self .frames :
169
141
result ['stacktrace' ] = self .frames
170
142
return result
171
143
172
144
145
+ class DroppedSpan (object ):
146
+ __slots__ = ('leaf' , 'parent' )
147
+
148
+ def __init__ (self , parent , leaf = False ):
149
+ self .parent = parent
150
+ self .leaf = leaf
151
+
152
+
173
153
class TransactionsStore (object ):
174
154
def __init__ (self , frames_collector_func , collect_frequency , sample_rate = 1.0 , max_spans = 0 , max_queue_size = None ,
175
155
span_frames_min_duration = None , ignore_patterns = None ):
@@ -219,7 +199,8 @@ def begin_transaction(self, transaction_type):
219
199
is_sampled = self ._sample_rate == 1.0 or self ._sample_rate > random .random ()
220
200
transaction = Transaction (self ._frames_collector_func , transaction_type , max_spans = self .max_spans ,
221
201
span_frames_min_duration = self .span_frames_min_duration , is_sampled = is_sampled )
222
- thread_local .transaction = transaction
202
+
203
+ set_transaction (transaction )
223
204
return transaction
224
205
225
206
def _should_ignore (self , transaction_name ):
@@ -270,7 +251,7 @@ def __exit__(self, exc_type, exc_val, exc_tb):
270
251
if transaction and transaction .is_sampled :
271
252
try :
272
253
transaction .end_span (self .skip_frames )
273
- except IndexError :
254
+ except LookupError :
274
255
error_logger .info ('ended non-existing span %s of type %s' , self .name , self .type )
275
256
276
257
0 commit comments