Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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 @@ -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
110 changes: 77 additions & 33 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,22 +626,73 @@ def _drop(cause, ty):
"replay_id": replay_id,
}

def _run_error_processors(self, event, hint, options):
# type: (Event, Hint, Optional[Dict[str, Any]]) -> None
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")
logger.info("error processor (%s) dropped event", error_processor)
return None

event = new_event

def _run_event_processors(self, event, hint, options):
# type: (Event, Hint, Optional[Dict[str, Any]]) -> None
for event_processor in chain(global_event_processors, self._event_processors):
new_event = event
new_event = event # type: Optional[Event]
with capture_internal_exceptions():
new_event = event_processor(event, hint)
if new_event is None:
return _drop(event_processor, "event processor")
logger.info("event processor (%s) dropped event", event_processor)
return None
event = new_event

@_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_level_to_event(event, hint, options)
self._apply_fingerprint_to_event(event, hint, options)
self._apply_user_to_event(event, hint, options)
self._apply_tags_to_event(event, hint, options)
self._apply_contexts_to_event(event, hint, options)
self._apply_transaction_name_to_event(event, hint, options)

if not is_check_in:
self._apply_transaction_info_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)

event = self._run_error_processors(event, hint, options)
if event is None:
return None

event = self._run_event_processors(event, hint, options)
if event is None:
return None

return event

def update_from_scope(self, scope):
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",
# TODO: These fields need to be checked if valid for checkin:
"level",
"tags",
"extra",
"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
), f"Unexpected keys found in checkin: {invalid_keys}"