Skip to content

feat: Added cycle detection #236

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

Merged
merged 6 commits into from
Jan 17, 2019
Merged
Show file tree
Hide file tree
Changes from all 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
17 changes: 10 additions & 7 deletions sentry_sdk/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
get_type_name,
capture_internal_exceptions,
current_stacktrace,
break_cycles,
logger,
)
from sentry_sdk.transport import make_transport
Expand Down Expand Up @@ -118,21 +119,23 @@ def _prepare_event(self, event, hint, scope):
event, self.options["in_app_exclude"], self.options["in_app_include"]
)

# Postprocess the event here so that annotated types do
# generally not surface in before_send
if event is not None:
event = break_cycles(event)
strip_event_mut(event)
event = flatten_metadata(event)
event = convert_types(event)

before_send = self.options["before_send"]
if before_send is not None:
new_event = None
with capture_internal_exceptions():
new_event = before_send(event, hint)
if new_event is None:
logger.info("before send dropped event (%s)", event)
event = new_event

# Postprocess the event in the very end so that annotated types do
# generally not surface in before_send
if event is not None:
strip_event_mut(event)
event = flatten_metadata(event)
event = convert_types(event)

return event

def _is_ignored_error(self, event, hint=None):
Expand Down
27 changes: 26 additions & 1 deletion sentry_sdk/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
# The logger is created here but initialized in the debug support module
logger = logging.getLogger("sentry_sdk.errors")

CYCLE_MARKER = object()


def _get_debug_hub():
# This function is replaced by debug.py
Expand Down Expand Up @@ -312,9 +314,13 @@ def _walk(obj, depth):
return [_walk(x, depth + 1) for x in obj]
if isinstance(obj, Mapping):
return {safe_str(k): _walk(v, depth + 1) for k, v in obj.items()}

if obj is CYCLE_MARKER:
return obj

return safe_repr(obj)

return _walk(obj, 0)
return _walk(break_cycles(obj), 0)


def extract_locals(frame):
Expand Down Expand Up @@ -615,7 +621,26 @@ def strip_frame_mut(frame):
frame["vars"] = strip_databag(frame["vars"])


def break_cycles(obj, memo=None):
if memo is None:
memo = {}
if id(obj) in memo:
return CYCLE_MARKER
memo[id(obj)] = obj

try:
if isinstance(obj, Mapping):
return {k: break_cycles(v, memo) for k, v in obj.items()}
if isinstance(obj, Sequence) and not isinstance(obj, (text_type, bytes)):
return [break_cycles(v, memo) for v in obj]
return obj
finally:
del memo[id(obj)]


def convert_types(obj):
if obj is CYCLE_MARKER:
return u"<cyclic>"
if isinstance(obj, datetime):
return obj.strftime("%Y-%m-%dT%H:%M:%SZ")
if isinstance(obj, Mapping):
Expand Down
52 changes: 52 additions & 0 deletions tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -313,3 +313,55 @@ def test_nan(sentry_init, capture_events):
frames = event["exception"]["values"][0]["stacktrace"]["frames"]
frame, = frames
assert frame["vars"]["nan"] == "nan"


def test_cyclic_frame_vars(sentry_init, capture_events):
sentry_init()
events = capture_events()

try:
a = {}
a["a"] = a
1 / 0
except Exception:
capture_exception()

event, = events
assert event["exception"]["values"][0]["stacktrace"]["frames"][0]["vars"]["a"] == {
"a": "<cyclic>"
}


def test_cyclic_data(sentry_init, capture_events):
sentry_init()
events = capture_events()

with configure_scope() as scope:
data = {}
data["is_cyclic"] = data

other_data = ""
data["not_cyclic"] = other_data
data["not_cyclic2"] = other_data
scope.set_extra("foo", data)

capture_message("hi")
event, = events

data = event["extra"]["foo"]
assert data == {"not_cyclic2": "", "not_cyclic": "", "is_cyclic": "<cyclic>"}


def test_databag_stripping(sentry_init, capture_events):
sentry_init()
events = capture_events()

try:
a = "A" * 16000 # noqa
1 / 0
except Exception:
capture_exception()

event, = events

assert len(json.dumps(event)) < 10000