15
15
16
16
error_logger = logging .getLogger ("elasticapm.errors" )
17
17
18
- thread_local = threading .local ()
19
- thread_local .transaction = None
20
-
21
-
22
18
_time_func = timeit .default_timer
23
19
24
20
25
21
TAG_RE = re .compile ('^[^.*"]+$' )
26
22
27
23
28
- DROPPED_SPAN = object ()
29
- IGNORED_SPAN = object ()
30
-
31
-
32
- def get_transaction (clear = False ):
33
- """
34
- Get the transaction registered for the current thread.
35
-
36
- :return:
37
- :rtype: Transaction
38
- """
39
- transaction = getattr (thread_local , "transaction" , None )
40
- if clear :
41
- thread_local .transaction = None
42
- return transaction
24
+ try :
25
+ from elasticapm .context .contextvars import get_transaction , set_transaction , get_span , set_span
26
+ except ImportError :
27
+ from elasticapm .context .threadlocal import get_transaction , set_transaction , get_span , set_span
43
28
44
29
45
30
class Transaction (object ):
46
- def __init__ (
47
- self ,
48
- frames_collector_func ,
49
- queue_func ,
50
- transaction_type = "custom" ,
51
- is_sampled = True ,
52
- max_spans = None ,
53
- span_frames_min_duration = None ,
54
- ):
31
+ def __init__ (self , store , transaction_type = "custom" , is_sampled = True ):
55
32
self .id = str (uuid .uuid4 ())
56
33
self .trace_id = None # for later use in distributed tracing
57
34
self .timestamp = datetime .datetime .utcnow ()
@@ -60,15 +37,10 @@ def __init__(
60
37
self .duration = None
61
38
self .result = None
62
39
self .transaction_type = transaction_type
63
- self ._frames_collector_func = frames_collector_func
64
- self ._queue_func = queue_func
40
+ self ._store = store
65
41
66
42
self .spans = []
67
- self .span_stack = []
68
- self .max_spans = max_spans
69
- self .span_frames_min_duration = span_frames_min_duration
70
43
self .dropped_spans = 0
71
- self .ignore_subtree = False
72
44
self .context = {}
73
45
self .tags = {}
74
46
@@ -79,45 +51,40 @@ def end_transaction(self, skip_frames=8):
79
51
self .duration = _time_func () - self .start_time
80
52
81
53
def begin_span (self , name , span_type , context = None , leaf = False ):
82
- # If we were already called with `leaf=True`, we'll just push
83
- # a placeholder on the stack.
84
- if self .ignore_subtree :
85
- self .span_stack .append (IGNORED_SPAN )
86
- return None
87
-
88
- if leaf :
89
- self .ignore_subtree = True
90
-
91
- self ._span_counter += 1
92
-
93
- if self .max_spans and self ._span_counter > self .max_spans :
54
+ parent_span = get_span ()
55
+ store = self ._store
56
+ if parent_span and parent_span .leaf :
57
+ span = DroppedSpan (parent_span , leaf = True )
58
+ elif store .max_spans and self ._span_counter > store .max_spans - 1 :
94
59
self .dropped_spans += 1
95
- self .span_stack .append (DROPPED_SPAN )
96
- return None
97
-
98
- start = _time_func () - self .start_time
99
- span = Span (self ._span_counter - 1 , self .id , self .trace_id , name , span_type , start , context )
100
- 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 , self .id , self .trace_id , name , span_type , start , context , leaf )
65
+ span .frames = store .frames_collector_func ()
66
+ span .parent = parent_span
67
+ self ._span_counter += 1
68
+ set_span (span )
101
69
return span
102
70
103
71
def end_span (self , skip_frames ):
104
- span = self .span_stack .pop ()
105
- if span is IGNORED_SPAN :
106
- return None
107
-
108
- self .ignore_subtree = False
109
-
110
- if span is DROPPED_SPAN :
72
+ span = get_span ()
73
+ if span is None :
74
+ raise LookupError ()
75
+ if isinstance (span , DroppedSpan ):
76
+ set_span (span .parent )
111
77
return
112
78
113
79
span .duration = _time_func () - span .start_time - self .start_time
114
80
115
- if self .span_stack :
116
- span .parent = self .span_stack [- 1 ].idx
117
-
118
- if not self .span_frames_min_duration or span .duration >= self .span_frames_min_duration :
119
- span .frames = self ._frames_collector_func ()[skip_frames :]
120
- self ._queue_func (SPAN , span .to_dict ())
81
+ if not self ._store .span_frames_min_duration or span .duration >= self ._store .span_frames_min_duration :
82
+ span .frames = self ._store .frames_processing_func (span .frames )[skip_frames :]
83
+ else :
84
+ span .frames = None
85
+ self .spans .append (span )
86
+ set_span (span .parent )
87
+ self ._store .queue_func (SPAN , span .to_dict ())
121
88
return span
122
89
123
90
def to_dict (self ):
@@ -185,30 +152,38 @@ def to_dict(self):
185
152
"type" : encoding .keyword_field (self .type ),
186
153
"start" : self .start_time * 1000 , # milliseconds
187
154
"duration" : self .duration * 1000 , # milliseconds
188
- "parent" : self .parent ,
155
+ "parent" : self .parent . idx if self . parent else None ,
189
156
"context" : self .context ,
190
157
}
191
158
if self .frames :
192
159
result ["stacktrace" ] = self .frames
193
160
return result
194
161
195
162
163
+ class DroppedSpan (object ):
164
+ __slots__ = ("leaf" , "parent" )
165
+
166
+ def __init__ (self , parent , leaf = False ):
167
+ self .parent = parent
168
+ self .leaf = leaf
169
+
170
+
196
171
class TransactionsStore (object ):
197
172
def __init__ (
198
173
self ,
199
174
frames_collector_func ,
175
+ frames_processing_func ,
200
176
queue_func ,
201
- collect_frequency ,
202
177
sample_rate = 1.0 ,
203
178
max_spans = 0 ,
204
179
span_frames_min_duration = None ,
205
180
ignore_patterns = None ,
206
181
):
207
182
self .cond = threading .Condition ()
208
- self .collect_frequency = collect_frequency
209
183
self .max_spans = max_spans
210
- self ._queue_func = queue_func
211
- self ._frames_collector_func = frames_collector_func
184
+ self .queue_func = queue_func
185
+ self .frames_processing_func = frames_processing_func
186
+ self .frames_collector_func = frames_collector_func
212
187
self ._transactions = []
213
188
self ._last_collect = _time_func ()
214
189
self ._ignore_patterns = [re .compile (p ) for p in ignore_patterns or []]
@@ -244,15 +219,8 @@ def begin_transaction(self, transaction_type):
244
219
:returns the Transaction object
245
220
"""
246
221
is_sampled = self ._sample_rate == 1.0 or self ._sample_rate > random .random ()
247
- transaction = Transaction (
248
- self ._frames_collector_func ,
249
- self ._queue_func ,
250
- transaction_type ,
251
- max_spans = self .max_spans ,
252
- span_frames_min_duration = self .span_frames_min_duration ,
253
- is_sampled = is_sampled ,
254
- )
255
- thread_local .transaction = transaction
222
+ transaction = Transaction (self , transaction_type , is_sampled = is_sampled )
223
+ set_transaction (transaction )
256
224
return transaction
257
225
258
226
def _should_ignore (self , transaction_name ):
@@ -271,7 +239,7 @@ def end_transaction(self, result=None, transaction_name=None):
271
239
return
272
240
if transaction .result is None :
273
241
transaction .result = result
274
- self ._queue_func (TRANSACTION , transaction .to_dict ())
242
+ self .queue_func (TRANSACTION , transaction .to_dict ())
275
243
return transaction
276
244
277
245
@@ -303,7 +271,7 @@ def __exit__(self, exc_type, exc_val, exc_tb):
303
271
if transaction and transaction .is_sampled :
304
272
try :
305
273
transaction .end_span (self .skip_frames )
306
- except IndexError :
274
+ except LookupError :
307
275
error_logger .info ("ended non-existing span %s of type %s" , self .name , self .type )
308
276
309
277
0 commit comments