2
2
from logging import getLogger
3
3
from typing import Callable , Dict , List , Optional
4
4
5
+ from kafka import KafkaConsumer
6
+
5
7
from opentelemetry import trace
6
- from opentelemetry .context import attach
8
+ from opentelemetry .context import attach , detach
9
+ from opentelemetry .context .context import Context
7
10
from opentelemetry .propagate import extract , inject
8
11
from opentelemetry .propagators import textmap
9
12
from opentelemetry .semconv .trace import SpanAttributes
13
16
_LOG = getLogger (__name__ )
14
17
15
18
19
+ class KafkaInstrumentorContextManager :
20
+ def __init__ (self ):
21
+ self .spans = dict ()
22
+ self .tokens = dict ()
23
+
24
+ def set_consumer_context (
25
+ self , consumer : KafkaConsumer , context : Context , span : Span
26
+ ):
27
+ self .set_span (consumer , span )
28
+ self .attach_context (consumer , context )
29
+
30
+ def set_span (self , consumer : KafkaConsumer , span : Span ):
31
+ self .close_span (consumer )
32
+ self .spans [consumer ] = span
33
+
34
+ def close_span (self , consumer : KafkaConsumer ):
35
+ if consumer in self .spans :
36
+ self .spans .get (consumer ).close ()
37
+ del self .spans [consumer ]
38
+
39
+ def attach_context (self , consumer : KafkaConsumer , context : Context ):
40
+ self .detach_context (consumer )
41
+ self .tokens [consumer ] = attach (context )
42
+
43
+ def detach_context (self , consumer : KafkaConsumer ):
44
+ if consumer in self .tokens :
45
+ detach (self .tokens .get (consumer ))
46
+ del self .tokens [consumer ]
47
+
48
+ def close (self , kafka_consumer : KafkaConsumer = None ):
49
+ if kafka_consumer :
50
+ self .close_span (kafka_consumer )
51
+ self .detach_context (kafka_consumer )
52
+ else :
53
+ for consumer in self .spans :
54
+ self .close_span (consumer )
55
+ for consumer in self .tokens :
56
+ self .detach_context (consumer )
57
+
58
+
16
59
class KafkaPropertiesExtractor :
17
60
@staticmethod
18
61
def extract_bootstrap_servers (instance ):
@@ -167,26 +210,30 @@ def _traced_send(func, instance, args, kwargs):
167
210
168
211
169
212
def _start_consume_span_with_extracted_context (
170
- tracer : Tracer , headers : List , topic : str
213
+ tracer : Tracer ,
214
+ context_manager : KafkaInstrumentorContextManager ,
215
+ instance : KafkaConsumer ,
216
+ headers : List ,
217
+ topic : str ,
171
218
) -> Span :
172
219
extracted_context = extract (headers , getter = _kafka_getter )
173
220
span_name = _get_span_name ("receive" , topic )
174
221
span = tracer .start_span (
175
222
span_name , context = extracted_context , kind = trace .SpanKind .CONSUMER
176
223
)
177
224
new_context = set_span_in_context (span , extracted_context )
178
- attach ( new_context )
225
+ context_manager . set_consumer_context ( instance , new_context , span )
179
226
return span
180
227
181
228
182
- def _wrap_next (tracer : Tracer , consume_hook : HookT ) -> Callable :
229
+ def _wrap_next (
230
+ tracer : Tracer ,
231
+ context_manager : KafkaInstrumentorContextManager ,
232
+ consume_hook : HookT ,
233
+ ) -> Callable :
183
234
def _traced_next (func , instance , args , kwargs ):
184
235
# End the current span if exists before processing the next record
185
- current_span = trace .get_current_span ()
186
- if current_span .is_recording () and current_span .name .startswith (
187
- "receive"
188
- ):
189
- current_span .end ()
236
+ context_manager .close (instance )
190
237
191
238
record = func (* args , ** kwargs )
192
239
@@ -198,7 +245,7 @@ def _traced_next(func, instance, args, kwargs):
198
245
)
199
246
partition = record .partition
200
247
span = _start_consume_span_with_extracted_context (
201
- tracer , headers , topic
248
+ tracer , context_manager , instance , headers , topic
202
249
)
203
250
with trace .use_span (span ):
204
251
_enrich_span (span , bootstrap_servers , topic , partition )
0 commit comments