Skip to content

Commit

Permalink
Set correct data in check_ins (#2500)
Browse files Browse the repository at this point in the history
Made sure that only relevant data is added to check_ins and breadcrumbs, and other things are not sent with checkins, because checkins have a strict size limit.
  • Loading branch information
antonpirker authored Nov 13, 2023
1 parent 35d86b6 commit 338acda
Show file tree
Hide file tree
Showing 6 changed files with 133 additions and 32 deletions.
1 change: 1 addition & 0 deletions sentry_sdk/_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
"internal",
"profile",
"statsd",
"check_in",
]
SessionStatus = Literal["ok", "exited", "crashed", "abnormal"]
EndpointType = Literal["store", "envelope"]
Expand Down
2 changes: 2 additions & 0 deletions sentry_sdk/envelope.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,8 @@ def data_category(self):
return "profile"
elif ty == "statsd":
return "statsd"
elif ty == "check_in":
return "check_in"
else:
return "default"

Expand Down
2 changes: 1 addition & 1 deletion sentry_sdk/integrations/rq.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ def sentry_patched_handle_exception(self, job, *exc_info, **kwargs):
# Note, the order of the `or` here is important,
# because calling `job.is_failed` will change `_status`.
if job._status == JobStatus.FAILED or job.is_failed:
_capture_exception(exc_info) # type: ignore
_capture_exception(exc_info)

return old_handle_exception(self, job, *exc_info, **kwargs)

Expand Down
97 changes: 67 additions & 30 deletions sentry_sdk/scope.py
Original file line number Diff line number Diff line change
Expand Up @@ -560,69 +560,62 @@ def func(event, exc_info):

self._error_processors.append(func)

@_disable_capture
def apply_to_event(
self,
event, # type: Event
hint, # type: Hint
options=None, # type: Optional[Dict[str, Any]]
):
# type: (...) -> Optional[Event]
"""Applies the information contained on the scope to the given event."""

def _drop(cause, ty):
# type: (Any, str) -> Optional[Any]
logger.info("%s (%s) dropped event", ty, cause)
return None

is_transaction = event.get("type") == "transaction"

# put all attachments into the hint. This lets callbacks play around
# with attachments. We also later pull this out of the hint when we
# create the envelope.
attachments_to_send = hint.get("attachments") or []
for attachment in self._attachments:
if not is_transaction or attachment.add_to_transactions:
attachments_to_send.append(attachment)
hint["attachments"] = attachments_to_send

def _apply_level_to_event(self, event, hint, options):
# type: (Event, Hint, Optional[Dict[str, Any]]) -> None
if self._level is not None:
event["level"] = self._level

if not is_transaction:
event.setdefault("breadcrumbs", {}).setdefault("values", []).extend(
self._breadcrumbs
)
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
)

def _apply_user_to_event(self, event, hint, options):
# type: (Event, Hint, Optional[Dict[str, Any]]) -> None
if event.get("user") is None and self._user is not None:
event["user"] = self._user

def _apply_transaction_name_to_event(self, event, hint, options):
# type: (Event, Hint, Optional[Dict[str, Any]]) -> None
if event.get("transaction") is None and self._transaction is not None:
event["transaction"] = self._transaction

def _apply_transaction_info_to_event(self, event, hint, options):
# type: (Event, Hint, Optional[Dict[str, Any]]) -> None
if event.get("transaction_info") is None and self._transaction_info is not None:
event["transaction_info"] = self._transaction_info

def _apply_fingerprint_to_event(self, event, hint, options):
# type: (Event, Hint, Optional[Dict[str, Any]]) -> None
if event.get("fingerprint") is None and self._fingerprint is not None:
event["fingerprint"] = self._fingerprint

def _apply_extra_to_event(self, event, hint, options):
# type: (Event, Hint, Optional[Dict[str, Any]]) -> None
if self._extras:
event.setdefault("extra", {}).update(self._extras)

def _apply_tags_to_event(self, event, hint, options):
# type: (Event, Hint, Optional[Dict[str, Any]]) -> None
if self._tags:
event.setdefault("tags", {}).update(self._tags)

def _apply_contexts_to_event(self, event, hint, options):
# type: (Event, Hint, Optional[Dict[str, Any]]) -> None
if self._contexts:
event.setdefault("contexts", {}).update(self._contexts)

contexts = event.setdefault("contexts", {})

# Add "trace" context
if contexts.get("trace") is None:
if has_tracing_enabled(options) and self._span is not None:
contexts["trace"] = self._span.get_trace_context()
else:
contexts["trace"] = self.get_trace_context()

# Add "reply_id" context
try:
replay_id = contexts["trace"]["dynamic_sampling_context"]["replay_id"]
except (KeyError, TypeError):
Expand All @@ -633,14 +626,58 @@ def _drop(cause, ty):
"replay_id": replay_id,
}

@_disable_capture
def apply_to_event(
self,
event, # type: Event
hint, # type: Hint
options=None, # type: Optional[Dict[str, Any]]
):
# type: (...) -> Optional[Event]
"""Applies the information contained on the scope to the given event."""
ty = event.get("type")
is_transaction = ty == "transaction"
is_check_in = ty == "check_in"

# put all attachments into the hint. This lets callbacks play around
# with attachments. We also later pull this out of the hint when we
# create the envelope.
attachments_to_send = hint.get("attachments") or []
for attachment in self._attachments:
if not is_transaction or attachment.add_to_transactions:
attachments_to_send.append(attachment)
hint["attachments"] = attachments_to_send

self._apply_contexts_to_event(event, hint, options)

if not is_check_in:
self._apply_level_to_event(event, hint, options)
self._apply_fingerprint_to_event(event, hint, options)
self._apply_user_to_event(event, hint, options)
self._apply_transaction_name_to_event(event, hint, options)
self._apply_transaction_info_to_event(event, hint, options)
self._apply_tags_to_event(event, hint, options)
self._apply_extra_to_event(event, hint, options)

if not is_transaction and not is_check_in:
self._apply_breadcrumbs_to_event(event, hint, options)

def _drop(cause, ty):
# type: (Any, str) -> Optional[Any]
logger.info("%s (%s) dropped event", ty, cause)
return None

# run error processors
exc_info = hint.get("exc_info")
if exc_info is not None:
for error_processor in self._error_processors:
new_event = error_processor(event, exc_info)
if new_event is None:
return _drop(error_processor, "error processor")

event = new_event

# run event processors
for event_processor in chain(global_event_processors, self._event_processors):
new_event = event
with capture_internal_exceptions():
Expand Down
2 changes: 1 addition & 1 deletion sentry_sdk/transport.py
Original file line number Diff line number Diff line change
Expand Up @@ -586,7 +586,7 @@ def make_transport(options):
elif isinstance(ref_transport, type) and issubclass(ref_transport, Transport):
transport_cls = ref_transport
elif callable(ref_transport):
return _FunctionTransport(ref_transport) # type: ignore
return _FunctionTransport(ref_transport)

# if a transport class is given only instantiate it if the dsn is not
# empty or None
Expand Down
61 changes: 61 additions & 0 deletions tests/test_crons.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import sentry_sdk
from sentry_sdk.crons import capture_checkin

from sentry_sdk import Hub, configure_scope, set_level

try:
from unittest import mock # python 3.3 and above
except ImportError:
Expand Down Expand Up @@ -220,3 +222,62 @@ def test_capture_checkin_sdk_not_initialized():
duration=None,
)
assert check_in_id == "112233"


def test_scope_data_in_checkin(sentry_init, capture_envelopes):
sentry_init()
envelopes = capture_envelopes()

valid_keys = [
# Mandatory event keys
"type",
"event_id",
"timestamp",
"platform",
# Optional event keys
"release",
"environment",
# Mandatory check-in specific keys
"check_in_id",
"monitor_slug",
"status",
# Optional check-in specific keys
"duration",
"monitor_config",
"contexts", # an event processor adds this
# TODO: These fields need to be checked if valid for checkin:
"_meta",
"tags",
"extra", # an event processor adds this
"modules",
"server_name",
"sdk",
]

hub = Hub.current
with configure_scope() as scope:
# Add some data to the scope
set_level("warning")
hub.add_breadcrumb(message="test breadcrumb")
scope.set_tag("test_tag", "test_value")
scope.set_extra("test_extra", "test_value")
scope.set_context("test_context", {"test_key": "test_value"})

capture_checkin(
monitor_slug="abc123",
check_in_id="112233",
status="ok",
duration=123,
)

(envelope,) = envelopes
check_in_event = envelope.items[0].payload.json

invalid_keys = []
for key in check_in_event.keys():
if key not in valid_keys:
invalid_keys.append(key)

assert len(invalid_keys) == 0, "Unexpected keys found in checkin: {}".format(
invalid_keys
)

0 comments on commit 338acda

Please sign in to comment.