8
8
9
9
from sentry_sdk .utils import logger
10
10
from sentry_sdk .tracing_utils import (
11
- SENTRY_TRACE_REGEX ,
12
11
EnvironHeaders ,
12
+ compute_tracestate_entry ,
13
+ extract_sentrytrace_data ,
14
+ extract_tracestate_data ,
13
15
has_tracing_enabled ,
14
16
is_valid_sample_rate ,
15
17
maybe_create_breadcrumbs_from_span ,
@@ -210,11 +212,12 @@ def continue_from_environ(
210
212
# type: (...) -> Transaction
211
213
"""
212
214
Create a Transaction with the given params, then add in data pulled from
213
- the 'sentry-trace' header in the environ (if any) before returning the
214
- Transaction.
215
+ the 'sentry-trace' and 'tracestate' headers from the environ (if any)
216
+ before returning the Transaction.
215
217
216
- If the 'sentry-trace' header is malformed or missing, just create and
217
- return a Transaction instance with the given params.
218
+ This is different from `continue_from_headers` in that it assumes header
219
+ names in the form "HTTP_HEADER_NAME" - such as you would get from a wsgi
220
+ environ - rather than the form "header-name".
218
221
"""
219
222
if cls is Span :
220
223
logger .warning (
@@ -231,28 +234,30 @@ def continue_from_headers(
231
234
):
232
235
# type: (...) -> Transaction
233
236
"""
234
- Create a Transaction with the given params, then add in data pulled from
235
- the 'sentry-trace' header (if any) before returning the Transaction.
236
-
237
- If the 'sentry-trace' header is malformed or missing, just create and
238
- return a Transaction instance with the given params.
237
+ Create a transaction with the given params (including any data pulled from
238
+ the 'sentry-trace' and 'tracestate' headers).
239
239
"""
240
+ # TODO move this to the Transaction class
240
241
if cls is Span :
241
242
logger .warning (
242
243
"Deprecated: use Transaction.continue_from_headers "
243
244
"instead of Span.continue_from_headers."
244
245
)
245
- transaction = Transaction . from_traceparent (
246
- headers .get ("sentry-trace" ), ** kwargs
247
- )
248
- if transaction is None :
249
- transaction = Transaction (** kwargs )
246
+
247
+ kwargs . update ( extract_sentrytrace_data ( headers .get ("sentry-trace" )))
248
+ kwargs . update ( extract_tracestate_data ( headers . get ( "tracestate" )) )
249
+
250
+ transaction = Transaction (** kwargs )
250
251
transaction .same_process_as_parent = False
252
+
251
253
return transaction
252
254
253
255
def iter_headers (self ):
254
256
# type: () -> Generator[Tuple[str, str], None, None]
255
257
yield "sentry-trace" , self .to_traceparent ()
258
+ tracestate = self .to_tracestate ()
259
+ if tracestate :
260
+ yield "tracestate" , tracestate
256
261
257
262
@classmethod
258
263
def from_traceparent (
@@ -262,46 +267,21 @@ def from_traceparent(
262
267
):
263
268
# type: (...) -> Optional[Transaction]
264
269
"""
270
+ DEPRECATED: Use Transaction.continue_from_headers(headers, **kwargs)
271
+
265
272
Create a Transaction with the given params, then add in data pulled from
266
273
the given 'sentry-trace' header value before returning the Transaction.
267
274
268
- If the header value is malformed or missing, just create and return a
269
- Transaction instance with the given params.
270
275
"""
271
- if cls is Span :
272
- logger .warning (
273
- "Deprecated: use Transaction.from_traceparent "
274
- "instead of Span.from_traceparent."
275
- )
276
+ logger .warning (
277
+ "Deprecated: Use Transaction.continue_from_headers(headers, **kwargs) "
278
+ "instead of from_traceparent(traceparent, **kwargs)"
279
+ )
276
280
277
281
if not traceparent :
278
282
return None
279
283
280
- if traceparent .startswith ("00-" ) and traceparent .endswith ("-00" ):
281
- traceparent = traceparent [3 :- 3 ]
282
-
283
- match = SENTRY_TRACE_REGEX .match (str (traceparent ))
284
- if match is None :
285
- return None
286
-
287
- trace_id , parent_span_id , sampled_str = match .groups ()
288
-
289
- if trace_id is not None :
290
- trace_id = "{:032x}" .format (int (trace_id , 16 ))
291
- if parent_span_id is not None :
292
- parent_span_id = "{:016x}" .format (int (parent_span_id , 16 ))
293
-
294
- if sampled_str :
295
- parent_sampled = sampled_str != "0" # type: Optional[bool]
296
- else :
297
- parent_sampled = None
298
-
299
- return Transaction (
300
- trace_id = trace_id ,
301
- parent_span_id = parent_span_id ,
302
- parent_sampled = parent_sampled ,
303
- ** kwargs
304
- )
284
+ return cls .continue_from_headers ({"sentry-trace" : traceparent }, ** kwargs )
305
285
306
286
def to_traceparent (self ):
307
287
# type: () -> str
@@ -312,6 +292,34 @@ def to_traceparent(self):
312
292
sampled = "0"
313
293
return "%s-%s-%s" % (self .trace_id , self .span_id , sampled )
314
294
295
+ def to_tracestate (self ):
296
+ # type: () -> Optional[str]
297
+ """
298
+ Generates the `tracestate` header value to attach to outgoing requests.
299
+ """
300
+ header_value = None
301
+
302
+ if isinstance (self , Transaction ):
303
+ transaction = self # type: Optional[Transaction]
304
+ else :
305
+ transaction = self ._containing_transaction
306
+
307
+ # we should have the relevant values stored on the transaction, but if
308
+ # this is an orphan span, make a new value
309
+ if transaction :
310
+ sentry_tracestate = transaction ._sentry_tracestate
311
+ third_party_tracestate = transaction ._third_party_tracestate
312
+ else :
313
+ sentry_tracestate = compute_tracestate_entry (self )
314
+ third_party_tracestate = None
315
+
316
+ header_value = sentry_tracestate
317
+
318
+ if third_party_tracestate :
319
+ header_value = header_value + "," + third_party_tracestate
320
+
321
+ return header_value
322
+
315
323
def set_tag (self , key , value ):
316
324
# type: (str, Any) -> None
317
325
self ._tags [key ] = value
@@ -418,16 +426,36 @@ def get_trace_context(self):
418
426
if self .status :
419
427
rv ["status" ] = self .status
420
428
429
+ if isinstance (self , Transaction ):
430
+ transaction = self # type: Optional[Transaction]
431
+ else :
432
+ transaction = self ._containing_transaction
433
+
434
+ if transaction :
435
+ rv ["tracestate" ] = transaction ._sentry_tracestate
436
+
421
437
return rv
422
438
423
439
424
440
class Transaction (Span ):
425
- __slots__ = ("name" , "parent_sampled" )
441
+ __slots__ = (
442
+ "name" ,
443
+ "parent_sampled" ,
444
+ # the sentry portion of the `tracestate` header used to transmit
445
+ # correlation context for server-side dynamic sampling, of the form
446
+ # `sentry=xxxxx`, where `xxxxx` is the base64-encoded json of the
447
+ # correlation context data, missing trailing any =
448
+ "_sentry_tracestate" ,
449
+ # tracestate data from other vendors, of the form `dogs=yes,cats=maybe`
450
+ "_third_party_tracestate" ,
451
+ )
426
452
427
453
def __init__ (
428
454
self ,
429
455
name = "" , # type: str
430
456
parent_sampled = None , # type: Optional[bool]
457
+ sentry_tracestate = None , # type: Optional[str]
458
+ third_party_tracestate = None , # type: Optional[str]
431
459
** kwargs # type: Any
432
460
):
433
461
# type: (...) -> None
@@ -443,6 +471,8 @@ def __init__(
443
471
Span .__init__ (self , ** kwargs )
444
472
self .name = name
445
473
self .parent_sampled = parent_sampled
474
+ self ._sentry_tracestate = sentry_tracestate or compute_tracestate_entry (self )
475
+ self ._third_party_tracestate = third_party_tracestate
446
476
447
477
def __repr__ (self ):
448
478
# type: () -> str
0 commit comments