Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions sentry_sdk/_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ class SDKInfo(TypedDict):
"check_in_id": str,
"contexts": dict[str, dict[str, object]],
"dist": str,
"dropped_spans": int,
"duration": Optional[float],
"environment": str,
"errors": list[dict[str, Any]], # TODO: We can expand on this type
Expand Down
11 changes: 10 additions & 1 deletion sentry_sdk/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

from sentry_sdk._compat import PY37, check_uwsgi_thread_support
from sentry_sdk.utils import (
AnnotatedValue,
ContextVar,
capture_internal_exceptions,
current_stacktrace,
Expand Down Expand Up @@ -483,6 +484,8 @@ def _prepare_event(
):
# type: (...) -> Optional[Event]

previous_total_spans = None # type: Optional[int]

if event.get("timestamp") is None:
event["timestamp"] = datetime.now(timezone.utc)

Expand All @@ -507,13 +510,16 @@ def _prepare_event(
return None

event = event_

spans_delta = spans_before - len(event.get("spans", []))
if is_transaction and spans_delta > 0 and self.transport is not None:
self.transport.record_lost_event(
"event_processor", data_category="span", quantity=spans_delta
)

dropped_spans = event.pop("dropped_spans", 0) + spans_delta # type: int
if dropped_spans > 0 or spans_delta > 0:
previous_total_spans = spans_before + dropped_spans

if (
self.options["attach_stacktrace"]
and "exception" not in event
Expand Down Expand Up @@ -561,6 +567,9 @@ def _prepare_event(
if event_scrubber:
event_scrubber.scrub_event(event)

if previous_total_spans is not None:
event["spans"] = AnnotatedValue(event.get("spans", []), {"len": previous_total_spans})

# Postprocess the event here so that annotated types do
# generally not surface in before_send
if event is not None:
Expand Down
10 changes: 9 additions & 1 deletion sentry_sdk/tracing.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ def get_span_status_from_http_code(http_status_code):
class _SpanRecorder:
"""Limits the number of spans recorded in a transaction."""

__slots__ = ("maxlen", "spans")
__slots__ = ("maxlen", "spans", "dropped_spans")

def __init__(self, maxlen):
# type: (int) -> None
Expand All @@ -204,11 +204,13 @@ def __init__(self, maxlen):
# limits: either transaction+spans or only child spans.
self.maxlen = maxlen - 1
self.spans = [] # type: List[Span]
self.dropped_spans = 0 # type: int

def add(self, span):
# type: (Span) -> None
if len(self.spans) > self.maxlen:
span._span_recorder = None
self.dropped_spans += 1
else:
self.spans.append(span)

Expand Down Expand Up @@ -972,6 +974,9 @@ def finish(
if span.timestamp is not None
]

len_diff = len(self._span_recorder.spans) - len(finished_spans)
dropped_spans = len_diff + self._span_recorder.dropped_spans

# we do this to break the circular reference of transaction -> span
# recorder -> span -> containing transaction (which is where we started)
# before either the spans or the transaction goes out of scope and has
Expand All @@ -996,6 +1001,9 @@ def finish(
"spans": finished_spans,
} # type: Event

if dropped_spans > 0:
event["dropped_spans"] = dropped_spans

if self._profile is not None and self._profile.valid():
event["profile"] = self._profile
self._profile = None
Expand Down
27 changes: 26 additions & 1 deletion tests/tracing/test_misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from sentry_sdk.tracing import Span, Transaction
from sentry_sdk.tracing_utils import should_propagate_trace
from sentry_sdk.utils import Dsn

from tests.conftest import ApproxDict

def test_span_trimming(sentry_init, capture_events):
sentry_init(traces_sample_rate=1.0, _experiments={"max_spans": 3})
Expand All @@ -31,6 +31,31 @@ def test_span_trimming(sentry_init, capture_events):
assert span2["op"] == "foo1"
assert span3["op"] == "foo2"

assert event["_meta"]["spans"][""]["len"] == 10


def test_span_data_scrubbing_and_trimming(sentry_init, capture_events):
sentry_init(traces_sample_rate=1.0, _experiments={"max_spans": 3})
events = capture_events()

with start_transaction(name="hi"):
with start_span(op="foo", name="bar") as span:
span.set_data("password", "secret")
span.set_data("datafoo", "databar")

for i in range(10):
with start_span(op="foo{}".format(i)):
pass

(event,) = events
assert event["spans"][0]["data"] == ApproxDict(
{"password": "[Filtered]", "datafoo": "databar"}
)
assert event["_meta"]["spans"] == {
"0": {"data": {"password": {"": {"rem": [["!config", "s"]]}}}},
"": {"len": 11},
}


def test_transaction_naming(sentry_init, capture_events):
sentry_init(traces_sample_rate=1.0)
Expand Down
Loading