8000 Set correct data in `check_in`s by antonpirker · Pull Request #2500 · getsentry/sentry-python · GitHub
[go: up one dir, main page]

Skip to content

Set correct data in check_ins #2500

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 18 commits into from
Nov 13, 2023
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"
< EDBE /td>
# 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}"
0