Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(breadcrumbs): add _meta information for truncation of breadcrumbs #4007

Merged
merged 25 commits into from
Apr 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
faf1d22
feat: add _meta annotations for breadcrumb truncataion
shellmayr Jan 29, 2025
f6f9dc7
rename var
shellmayr Jan 29, 2025
07b72d4
add AnnotatedDeque for better handling
shellmayr Jan 31, 2025
bd8e3fa
remove special case for test
shellmayr Jan 31, 2025
06216d8
handle scope merging for breadcrumb annotations
shellmayr Mar 31, 2025
c45aa01
make everything work together and remove annotateddeque
shellmayr Mar 31, 2025
ba6634d
add some details to tests
shellmayr Mar 31, 2025
28ca7e7
remove breakpoints
shellmayr Mar 31, 2025
9456348
remove logging
shellmayr Mar 31, 2025
b066c3d
wip
shellmayr Mar 31, 2025
0869df7
whitespace
shellmayr Mar 31, 2025
9e0aead
remove logging
shellmayr Mar 31, 2025
019a166
remove logging
shellmayr Mar 31, 2025
142e88e
remove logging
shellmayr Mar 31, 2025
168fc55
fix error for older versions without breadcrumbs
shellmayr Apr 2, 2025
a05707f
fix linter errors
shellmayr Apr 2, 2025
2c8dafb
Merge branch 'master' into shellmayr/feat/add-breadcrumb-annotationvalue
antonpirker Apr 3, 2025
a75d5bc
fix check for annotatedvalue in scrubber
shellmayr Apr 3, 2025
5b04e4f
fix check for annotatedvalue in scrubber
shellmayr Apr 3, 2025
12cf7da
fix check for annotatedvalue in scrubber
shellmayr Apr 3, 2025
de33f6e
simplify scrubbing of breadcrumbs - annotatedvalue is added afterwards
shellmayr Apr 3, 2025
d041ad5
Merge branch 'master' into shellmayr/feat/add-breadcrumb-annotationvalue
antonpirker Apr 3, 2025
24a4270
fix type cohesion
shellmayr Apr 3, 2025
b2d297d
trying something
antonpirker Apr 3, 2025
e9a5e51
make mypy happy
shellmayr Apr 3, 2025
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
15 changes: 13 additions & 2 deletions sentry_sdk/_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,17 @@ def __eq__(self, other):

return self.value == other.value and self.metadata == other.metadata

def __str__(self):
# type: (AnnotatedValue) -> str
return str({"value": str(self.value), "metadata": str(self.metadata)})

def __len__(self):
# type: (AnnotatedValue) -> int
if self.value is not None:
return len(self.value)
else:
return 0

@classmethod
def removed_because_raw_data(cls):
# type: () -> AnnotatedValue
Expand Down Expand Up @@ -152,8 +163,8 @@ class SDKInfo(TypedDict):
Event = TypedDict(
"Event",
{
"breadcrumbs": dict[
Literal["values"], list[dict[str, Any]]
"breadcrumbs": Annotated[
dict[Literal["values"], list[dict[str, Any]]]
], # TODO: We can expand on this type
"check_in_id": str,
"contexts": dict[str, dict[str, object]],
Expand Down
16 changes: 15 additions & 1 deletion sentry_sdk/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,7 @@ def _prepare_event(
# type: (...) -> Optional[Event]

previous_total_spans = None # type: Optional[int]
previous_total_breadcrumbs = None # type: Optional[int]

if event.get("timestamp") is None:
event["timestamp"] = datetime.now(timezone.utc)
Expand Down Expand Up @@ -534,6 +535,16 @@ def _prepare_event(
dropped_spans = event.pop("_dropped_spans", 0) + spans_delta # type: int
if dropped_spans > 0:
previous_total_spans = spans_before + dropped_spans
if scope._n_breadcrumbs_truncated > 0:
breadcrumbs = event.get("breadcrumbs", {})
values = (
breadcrumbs.get("values", [])
if not isinstance(breadcrumbs, AnnotatedValue)
else []
)
previous_total_breadcrumbs = (
len(values) + scope._n_breadcrumbs_truncated
)

if (
self.options["attach_stacktrace"]
Expand Down Expand Up @@ -586,7 +597,10 @@ def _prepare_event(
event["spans"] = AnnotatedValue(
event.get("spans", []), {"len": previous_total_spans}
)

if previous_total_breadcrumbs is not None:
event["breadcrumbs"] = AnnotatedValue(
event.get("breadcrumbs", []), {"len": previous_total_breadcrumbs}
)
# Postprocess the event here so that annotated types do
# generally not surface in before_send
if event is not None:
Expand Down
30 changes: 23 additions & 7 deletions sentry_sdk/scope.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from functools import wraps
from itertools import chain

from sentry_sdk._types import AnnotatedValue
from sentry_sdk.attachments import Attachment
from sentry_sdk.consts import DEFAULT_MAX_BREADCRUMBS, FALSE_VALUES, INSTRUMENTER
from sentry_sdk.feature_flags import FlagBuffer, DEFAULT_FLAG_CAPACITY
Expand Down Expand Up @@ -186,6 +187,7 @@ class Scope:
"_contexts",
"_extras",
"_breadcrumbs",
"_n_breadcrumbs_truncated",
"_event_processors",
"_error_processors",
"_should_capture",
Expand All @@ -210,6 +212,7 @@ def __init__(self, ty=None, client=None):

self._name = None # type: Optional[str]
self._propagation_context = None # type: Optional[PropagationContext]
self._n_breadcrumbs_truncated = 0 # type: int

self.client = NonRecordingClient() # type: sentry_sdk.client.BaseClient

Expand Down Expand Up @@ -243,6 +246,7 @@ def __copy__(self):
rv._extras = dict(self._extras)

rv._breadcrumbs = copy(self._breadcrumbs)
rv._n_breadcrumbs_truncated = copy(self._n_breadcrumbs_truncated)
rv._event_processors = list(self._event_processors)
rv._error_processors = list(self._error_processors)
rv._propagation_context = self._propagation_context
Expand Down Expand Up @@ -916,6 +920,7 @@ def clear_breadcrumbs(self):
# type: () -> None
"""Clears breadcrumb buffer."""
self._breadcrumbs = deque() # type: Deque[Breadcrumb]
self._n_breadcrumbs_truncated = 0

def add_attachment(
self,
Expand Down Expand Up @@ -983,6 +988,7 @@ def add_breadcrumb(self, crumb=None, hint=None, **kwargs):

while len(self._breadcrumbs) > max_breadcrumbs:
self._breadcrumbs.popleft()
self._n_breadcrumbs_truncated += 1

def start_transaction(
self,
Expand Down Expand Up @@ -1366,17 +1372,23 @@ def _apply_level_to_event(self, event, hint, options):

def _apply_breadcrumbs_to_event(self, event, hint, options):
# type: (Event, Hint, Optional[Dict[str, Any]]) -> None
event.setdefault("breadcrumbs", {}).setdefault("values", []).extend(
self._breadcrumbs
)
event.setdefault("breadcrumbs", {})

# This check is just for mypy -
if not isinstance(event["breadcrumbs"], AnnotatedValue):
event["breadcrumbs"].setdefault("values", [])
event["breadcrumbs"]["values"].extend(self._breadcrumbs)

# Attempt to sort timestamps
try:
for crumb in event["breadcrumbs"]["values"]:
if isinstance(crumb["timestamp"], str):
crumb["timestamp"] = datetime_from_isoformat(crumb["timestamp"])
if not isinstance(event["breadcrumbs"], AnnotatedValue):
for crumb in event["breadcrumbs"]["values"]:
if isinstance(crumb["timestamp"], str):
crumb["timestamp"] = datetime_from_isoformat(crumb["timestamp"])

event["breadcrumbs"]["values"].sort(key=lambda crumb: crumb["timestamp"])
event["breadcrumbs"]["values"].sort(
key=lambda crumb: crumb["timestamp"]
)
except Exception as err:
logger.debug("Error when sorting breadcrumbs", exc_info=err)
pass
Expand Down Expand Up @@ -1564,6 +1576,10 @@ def update_from_scope(self, scope):
self._extras.update(scope._extras)
if scope._breadcrumbs:
self._breadcrumbs.extend(scope._breadcrumbs)
if scope._n_breadcrumbs_truncated:
self._n_breadcrumbs_truncated = (
self._n_breadcrumbs_truncated + scope._n_breadcrumbs_truncated
)
if scope._span:
self._span = scope._span
if scope._attachments:
Expand Down
5 changes: 4 additions & 1 deletion sentry_sdk/scrubber.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,10 @@ def scrub_breadcrumbs(self, event):
# type: (Event) -> None
with capture_internal_exceptions():
if "breadcrumbs" in event:
if "values" in event["breadcrumbs"]:
if (
not isinstance(event["breadcrumbs"], AnnotatedValue)
and "values" in event["breadcrumbs"]
):
for value in event["breadcrumbs"]["values"]:
if "data" in value:
self.scrub_dict(value["data"])
Expand Down
20 changes: 14 additions & 6 deletions tests/test_scrubber.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,25 +119,33 @@ def test_stack_var_scrubbing(sentry_init, capture_events):


def test_breadcrumb_extra_scrubbing(sentry_init, capture_events):
sentry_init()
sentry_init(max_breadcrumbs=2)
events = capture_events()

logger.info("bread", extra=dict(foo=42, password="secret"))
logger.info("breadcrumb 1", extra=dict(foo=1, password="secret"))
logger.info("breadcrumb 2", extra=dict(bar=2, auth="secret"))
logger.info("breadcrumb 3", extra=dict(foobar=3, password="secret"))
logger.critical("whoops", extra=dict(bar=69, auth="secret"))

(event,) = events

assert event["extra"]["bar"] == 69
assert event["extra"]["auth"] == "[Filtered]"

assert event["breadcrumbs"]["values"][0]["data"] == {
"foo": 42,
"bar": 2,
"auth": "[Filtered]",
}
assert event["breadcrumbs"]["values"][1]["data"] == {
"foobar": 3,
"password": "[Filtered]",
}

assert event["_meta"]["extra"]["auth"] == {"": {"rem": [["!config", "s"]]}}
assert event["_meta"]["breadcrumbs"] == {
"values": {"0": {"data": {"password": {"": {"rem": [["!config", "s"]]}}}}}
"": {"len": 3},
"values": {
"0": {"data": {"auth": {"": {"rem": [["!config", "s"]]}}}},
"1": {"data": {"password": {"": {"rem": [["!config", "s"]]}}}},
},
}


Expand Down
Loading