Skip to content

Commit 7b972eb

Browse files
committed
Baggage creation for head of trace
1 parent 83917db commit 7b972eb

File tree

3 files changed

+75
-15
lines changed

3 files changed

+75
-15
lines changed

sentry_sdk/tracing.py

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@
3535
TRANSACTION_SOURCE_COMPONENT = "component"
3636
TRANSACTION_SOURCE_TASK = "task"
3737

38+
# These are typically high cardinality and the server hates them
39+
LOW_QUALITY_TRANSACTION_SOURCES = [
40+
TRANSACTION_SOURCE_URL,
41+
]
42+
3843
SOURCE_FOR_STYLE = {
3944
"endpoint": TRANSACTION_SOURCE_COMPONENT,
4045
"function_name": TRANSACTION_SOURCE_COMPONENT,
@@ -281,6 +286,10 @@ def continue_from_headers(
281286

282287
if sentrytrace_kwargs is not None:
283288
kwargs.update(sentrytrace_kwargs)
289+
290+
# If there's an incoming sentry-trace but no incoming baggage header,
291+
# for instance in traces coming from older SDKs,
292+
# baggage will be empty and immutable and won't be populated as head SDK.
284293
baggage.freeze()
285294

286295
kwargs.update(extract_tracestate_data(headers.get("tracestate")))
@@ -309,8 +318,8 @@ def iter_headers(self):
309318
if tracestate:
310319
yield "tracestate", tracestate
311320

312-
if self.containing_transaction and self.containing_transaction._baggage:
313-
baggage = self.containing_transaction._baggage.serialize()
321+
if self.containing_transaction:
322+
baggage = self.containing_transaction.get_baggage().serialize()
314323
if baggage:
315324
yield "baggage", baggage
316325

@@ -513,11 +522,10 @@ def get_trace_context(self):
513522
if sentry_tracestate:
514523
rv["tracestate"] = sentry_tracestate
515524

516-
# TODO-neel populate fresh if head SDK
517-
if self.containing_transaction and self.containing_transaction._baggage:
525+
if self.containing_transaction:
518526
rv[
519527
"dynamic_sampling_context"
520-
] = self.containing_transaction._baggage.dynamic_sampling_context()
528+
] = self.containing_transaction.get_baggage().dynamic_sampling_context()
521529

522530
return rv
523531

@@ -527,6 +535,7 @@ class Transaction(Span):
527535
"name",
528536
"source",
529537
"parent_sampled",
538+
"sample_rate",
530539
# the sentry portion of the `tracestate` header used to transmit
531540
# correlation context for server-side dynamic sampling, of the form
532541
# `sentry=xxxxx`, where `xxxxx` is the base64-encoded json of the
@@ -562,6 +571,7 @@ def __init__(
562571
Span.__init__(self, **kwargs)
563572
self.name = name
564573
self.source = source
574+
self.sample_rate = None # type: Optional[float]
565575
self.parent_sampled = parent_sampled
566576
# if tracestate isn't inherited and set here, it will get set lazily,
567577
# either the first time an outgoing request needs it for a header or the
@@ -570,7 +580,7 @@ def __init__(
570580
self._third_party_tracestate = third_party_tracestate
571581
self._measurements = {} # type: Dict[str, Any]
572582
self._profile = None # type: Optional[Sampler]
573-
self._baggage = baggage
583+
self._baggage = baggage # type: Optional[Baggage]
574584

575585
def __repr__(self):
576586
# type: () -> str
@@ -708,6 +718,17 @@ def to_json(self):
708718

709719
return rv
710720

721+
def get_baggage(self):
722+
# type: () -> Baggage
723+
"""
724+
The first time a new baggage with sentry items is made,
725+
it will be frozen.
726+
"""
727+
if not self._baggage or self._baggage.mutable:
728+
self._baggage = Baggage.populate_from_transaction(self)
729+
730+
return self._baggage
731+
711732
def _set_initial_sampling_decision(self, sampling_context):
712733
# type: (SamplingContext) -> None
713734
"""
@@ -773,6 +794,9 @@ def _set_initial_sampling_decision(self, sampling_context):
773794
self.sampled = False
774795
return
775796

797+
# used to create baggage value for head SDKs in dynamic sampling
798+
self.sample_rate = float(sample_rate)
799+
776800
# if the function returned 0 (or false), or if `traces_sample_rate` is
777801
# 0, it's a sign the transaction should be dropped
778802
if not sample_rate:

sentry_sdk/tracing_utils.py

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,49 @@ def from_incoming_header(cls, header):
470470

471471
return Baggage(sentry_items, third_party_items, mutable)
472472

473+
@classmethod
474+
def populate_from_transaction(cls, transaction):
475+
# type: (Transaction) -> Baggage
476+
"""
477+
Populate fresh baggage entry with sentry_items and make it immutable
478+
if this is the head SDK which originates traces.
479+
"""
480+
481+
hub = transaction.hub or sentry_sdk.Hub.current
482+
client = hub.client
483+
sentry_items = {} # type: Dict[str, str]
484+
485+
if not client:
486+
return Baggage(sentry_items)
487+
488+
options = client.options or {}
489+
user = (hub.scope and hub.scope._user) or {}
490+
491+
sentry_items["trace_id"] = transaction.trace_id
492+
493+
if options.get("environment"):
494+
sentry_items["environment"] = options["environment"]
495+
496+
if options.get("release"):
497+
sentry_items["release"] = options["release"]
498+
499+
if options.get("dsn"):
500+
sentry_items["public_key"] = Dsn(options["dsn"]).public_key
501+
502+
if (
503+
transaction.name
504+
and transaction.source not in LOW_QUALITY_TRANSACTION_SOURCES
505+
):
506+
sentry_items["transaction"] = transaction.name
507+
508+
if user.get("segment"):
509+
sentry_items["user_segment"] = user["segment"]
510+
511+
if transaction.sample_rate is not None:
512+
sentry_items["sample_rate"] = str(transaction.sample_rate)
513+
514+
return Baggage(sentry_items, mutable=False)
515+
473516
def freeze(self):
474517
# type: () -> None
475518
self.mutable = False
@@ -500,6 +543,7 @@ def serialize(self, include_third_party=False):
500543

501544

502545
# Circular imports
546+
from sentry_sdk.tracing import LOW_QUALITY_TRANSACTION_SOURCES
503547

504548
if MYPY:
505-
from sentry_sdk.tracing import Span
549+
from sentry_sdk.tracing import Span, Transaction

tests/integrations/sqlalchemy/test_sqlalchemy.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -191,14 +191,6 @@ def processor(event, hint):
191191
# Some spans are discarded.
192192
assert len(event["spans"]) == 1000
193193

194-
# Some spans have their descriptions truncated. Because the test always
195-
# generates the same amount of descriptions and truncation is deterministic,
196-
# the number here should never change across test runs.
197-
#
198-
# Which exact span descriptions are truncated depends on the span durations
199-
# of each SQL query and is non-deterministic.
200-
assert len(event["_meta"]["spans"]) == 537
201-
202194
for i, span in enumerate(event["spans"]):
203195
description = span["description"]
204196

0 commit comments

Comments
 (0)