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

Set correct data in check_ins #2500

Merged
merged 18 commits into from Nov 13, 2023
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions sentry_sdk/_types.py
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
Expand Up @@ -262,6 +262,8 @@
return "profile"
elif ty == "statsd":
return "statsd"
elif ty == "check_in":
return "check_in"

Check warning on line 266 in sentry_sdk/envelope.py

View check run for this annotation

Codecov / codecov/patch

sentry_sdk/envelope.py#L266

Added line #L266 was not covered by tests
else:
return "default"

Expand Down
2 changes: 1 addition & 1 deletion sentry_sdk/integrations/rq.py
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
Expand Up @@ -560,69 +560,62 @@

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 @@
"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)

Check warning on line 648 in sentry_sdk/scope.py

View check run for this annotation

Codecov / codecov/patch

sentry_sdk/scope.py#L648

Added line #L648 was not covered by tests
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
Expand Up @@ -586,7 +586,7 @@
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)

Check warning on line 589 in sentry_sdk/transport.py

View check run for this annotation

Codecov / codecov/patch

sentry_sdk/transport.py#L589

Added line #L589 was not covered by tests

# 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
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",
antonpirker marked this conversation as resolved.
Show resolved Hide resolved
# 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
)