Skip to content

Commit 0ce0523

Browse files
feat(tracing): Record lost spans in client reports
Resolves #3229
1 parent 854b46c commit 0ce0523

File tree

4 files changed

+60
-13
lines changed

4 files changed

+60
-13
lines changed

sentry_sdk/_types.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@
155155
"profile_chunk",
156156
"metric_bucket",
157157
"monitor",
158+
"span",
158159
]
159160
SessionStatus = Literal["ok", "exited", "crashed", "abnormal"]
160161

sentry_sdk/client.py

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -61,12 +61,11 @@
6161
from sentry_sdk.session import Session
6262
from sentry_sdk.transport import Transport
6363

64-
6564
_client_init_debug = ContextVar("client_init_debug")
6665

67-
6866
SDK_INFO = {
69-
"name": "sentry.python", # SDK name will be overridden after integrations have been loaded with sentry_sdk.integrations.setup_integrations()
67+
"name": "sentry.python",
68+
# SDK name will be overridden after integrations have been loaded with sentry_sdk.integrations.setup_integrations()
7069
"version": VERSION,
7170
"packages": [{"name": "pypi:sentry-sdk", "version": VERSION}],
7271
}
@@ -453,10 +452,15 @@ def _prepare_event(
453452
# one of the event/error processors returned None
454453
if event_ is None:
455454
if self.transport:
456-
self.transport.record_lost_event(
457-
"event_processor",
458-
data_category=("transaction" if is_transaction else "error"),
459-
)
455+
if is_transaction:
456+
self.transport.record_lost_transaction(
457+
"event_processor", len(event.get("spans", []))
458+
)
459+
else:
460+
self.transport.record_lost_event(
461+
"event_processor",
462+
data_category="error",
463+
)
460464
return None
461465

462466
event = event_
@@ -546,8 +550,8 @@ def _prepare_event(
546550
if new_event is None:
547551
logger.info("before send transaction dropped event")
548552
if self.transport:
549-
self.transport.record_lost_event(
550-
"before_send", data_category="transaction"
553+
self.transport.record_lost_transaction(
554+
"before_send", len(event.get("spans", []))
551555
)
552556
event = new_event # type: ignore
553557

sentry_sdk/tracing.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -119,11 +119,9 @@ class TransactionKwargs(SpanKwargs, total=False):
119119
},
120120
)
121121

122-
123122
BAGGAGE_HEADER_NAME = "baggage"
124123
SENTRY_TRACE_HEADER_NAME = "sentry-trace"
125124

126-
127125
# Transaction source
128126
# see https://develop.sentry.dev/sdk/event-payloads/transaction/#transaction-annotations
129127
TRANSACTION_SOURCE_CUSTOM = "custom"
@@ -856,7 +854,8 @@ def finish(self, hub=None, end_timestamp=None):
856854
else:
857855
reason = "sample_rate"
858856

859-
client.transport.record_lost_event(reason, data_category="transaction")
857+
# No spans are discarded here because they were never recorded in the first place
858+
client.transport.record_lost_transaction(reason, span_count=0)
860859

861860
return None
862861

sentry_sdk/transport.py

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,22 @@ def record_lost_event(
137137
# type: (...) -> None
138138
"""This increments a counter for event loss by reason and
139139
data category.
140+
141+
data_category="transaction" should use record_lost_transaction,
142+
instead.
143+
"""
144+
return None
145+
146+
def record_lost_transaction(
147+
self,
148+
reason, # type: str
149+
span_count, # type: int
150+
):
151+
# type: (...) -> None
152+
"""This increments a counter for loss of a transaction and the transaction's spans.
153+
154+
The span_count should only include the number of spans contained in the transaction, EXCLUDING the transaction
155+
itself.
140156
"""
141157
return None
142158

@@ -229,10 +245,24 @@ def record_lost_event(
229245
if not self.options["send_client_reports"]:
230246
return
231247

248+
if data_category == "transaction":
249+
warnings.warn(
250+
"Use record_lost_transaction for transactions to ensure lost spans are counted.",
251+
stacklevel=2,
252+
)
253+
232254
quantity = 1
233255
if item is not None:
234256
data_category = item.data_category
235-
if data_category == "attachment":
257+
258+
if data_category == "transaction":
259+
# Handle transactions through record_lost_transaction.
260+
event = item.get_event() or {}
261+
span_count = len(event.get("spans") or [])
262+
self.record_lost_transaction(reason, span_count)
263+
return
264+
265+
elif data_category == "attachment":
236266
# quantity of 0 is actually 1 as we do not want to count
237267
# empty attachments as actually empty.
238268
quantity = len(item.get_bytes()) or 1
@@ -242,6 +272,19 @@ def record_lost_event(
242272

243273
self._discarded_events[data_category, reason] += quantity
244274

275+
def record_lost_transaction(
276+
self,
277+
reason, # type: str
278+
span_count, # type: int
279+
): # type: (...) -> None
280+
if not self.options["send_client_reports"]:
281+
return
282+
283+
self._discarded_events["transaction", reason] += 1
284+
self._discarded_events["span", reason] += (
285+
span_count + 1
286+
) # Also count the transaction itself
287+
245288
def _update_rate_limits(self, response):
246289
# type: (urllib3.BaseHTTPResponse) -> None
247290

0 commit comments

Comments
 (0)