Skip to content

Commit 6c90baa

Browse files
authored
fix(taskworker) Remove pickle usage from sentryapp hooks (#91435)
Redo of #91372 which was reverted because of rate limiting. This time around, use an option to control task variants, and only generate a payload when a hook is going to be sent. For projects with multiple service hooks, this should result in fewer snuba calls as events are only serialized once.
1 parent d0f7b32 commit 6c90baa

File tree

5 files changed

+104
-11
lines changed

5 files changed

+104
-11
lines changed

src/sentry/options/defaults.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3343,3 +3343,5 @@
33433343
# Orgs for which compression should be disabled in the chunk upload endpoint.
33443344
# This is intended to circumvent sporadic 503 errors reported by some customers.
33453345
register("chunk-upload.no-compression", default=[], flags=FLAG_AUTOMATOR_MODIFIABLE)
3346+
3347+
register("process_service_hook.payload.rollout", default=0.0, flags=FLAG_AUTOMATOR_MODIFIABLE)

src/sentry/sentry_apps/tasks/service_hooks.py

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
import logging
22
from time import time
3+
from typing import Any
34

5+
from sentry import nodestore
46
from sentry.api.serializers import serialize
7+
from sentry.eventstore.models import Event, GroupEvent
58
from sentry.http import safe_urlopen
9+
from sentry.models.group import Group
610
from sentry.sentry_apps.models.servicehook import ServiceHook
711
from sentry.silo.base import SiloMode
812
from sentry.tasks.base import instrumented_task, retry
@@ -46,14 +50,36 @@ def get_payload_v0(event):
4650
),
4751
)
4852
@retry
49-
def process_service_hook(servicehook_id, event, **kwargs):
53+
def process_service_hook(
54+
servicehook_id: int,
55+
event: Any | None = None,
56+
project_id: int | None = None,
57+
group_id: int | None = None,
58+
event_id: str | None = None,
59+
**kwargs,
60+
):
5061
try:
5162
servicehook = ServiceHook.objects.get(id=servicehook_id)
5263
except ServiceHook.DoesNotExist:
5364
return
5465

66+
if not project_id and event:
67+
project_id = event.project_id
68+
69+
if project_id and group_id and event_id:
70+
node_id = Event.generate_node_id(project_id, event_id)
71+
group = Group.objects.get_from_cache(id=group_id)
72+
nodedata = nodestore.backend.get(node_id)
73+
event = GroupEvent(
74+
project_id=project_id,
75+
event_id=event_id,
76+
group=group,
77+
data=nodedata,
78+
)
79+
80+
assert event, "Event must exist at this point"
5581
if servicehook.version == 0:
56-
payload = get_payload_v0(event)
82+
payload = json.dumps(get_payload_v0(event))
5783
else:
5884
raise NotImplementedError
5985

@@ -65,10 +91,8 @@ def process_service_hook(servicehook_id, event, **kwargs):
6591
"Content-Type": "application/json",
6692
"X-ServiceHook-Timestamp": str(int(time())),
6793
"X-ServiceHook-GUID": servicehook.guid,
68-
"X-ServiceHook-Signature": servicehook.build_signature(json.dumps(payload)),
94+
"X-ServiceHook-Signature": servicehook.build_signature(payload),
6995
}
7096

71-
safe_urlopen(
72-
url=servicehook.url, data=json.dumps(payload), headers=headers, timeout=5, verify_ssl=False
73-
)
74-
logger.info("service_hook.success", extra={"project_id": event.project_id})
97+
safe_urlopen(url=servicehook.url, data=payload, headers=headers, timeout=5, verify_ssl=False)
98+
logger.info("service_hook.success", extra={"project_id": project_id})

src/sentry/tasks/post_process.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from sentry.issues.grouptype import GroupCategory
1919
from sentry.issues.issue_occurrence import IssueOccurrence
2020
from sentry.killswitches import killswitch_matches_context
21+
from sentry.options.rollout import in_random_rollout
2122
from sentry.replays.lib.event_linking import transform_event_for_linking_payload
2223
from sentry.replays.lib.kafka import initialize_replays_publisher
2324
from sentry.sentry_metrics.client import generic_metrics_backend
@@ -72,7 +73,7 @@ class PostProcessJob(TypedDict, total=False):
7273
has_escalated: bool
7374

7475

75-
def _get_service_hooks(project_id):
76+
def _get_service_hooks(project_id: int) -> list[tuple[int, list[str]]]:
7677
from sentry.sentry_apps.models.servicehook import ServiceHook
7778

7879
cache_key = f"servicehooks:1:{project_id}"
@@ -1193,7 +1194,16 @@ def process_service_hooks(job: PostProcessJob) -> None:
11931194
if has_alert:
11941195
allowed_events.add("event.alert")
11951196

1196-
if allowed_events:
1197+
if in_random_rollout("process_service_hook.payload.rollout"):
1198+
for servicehook_id, events in _get_service_hooks(project_id=event.project_id):
1199+
if any(e in allowed_events for e in events):
1200+
process_service_hook.delay(
1201+
servicehook_id=servicehook_id,
1202+
project_id=event.project_id,
1203+
group_id=event.group_id,
1204+
event_id=event.event_id,
1205+
)
1206+
else:
11971207
for servicehook_id, events in _get_service_hooks(project_id=event.project_id):
11981208
if any(e in allowed_events for e in events):
11991209
process_service_hook.delay(servicehook_id=servicehook_id, event=event)

tests/sentry/sentry_apps/tasks/test_servicehooks.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,35 @@ def test_event_created_sends_service_hook(self, safe_urlopen):
5959
"X-ServiceHook-Signature",
6060
}
6161

62+
@patch("sentry.sentry_apps.tasks.service_hooks.safe_urlopen")
63+
@responses.activate
64+
def test_event_created_sends_service_hook_with_event_id(self, safe_urlopen):
65+
self.hook.update(events=["event.created", "event.alert"])
66+
67+
event = self.store_event(
68+
data={"timestamp": before_now(minutes=1).isoformat()}, project_id=self.project.id
69+
)
70+
assert event.group
71+
72+
process_service_hook(
73+
self.hook.id,
74+
project_id=event.project_id,
75+
group_id=event.group.id,
76+
event_id=event.event_id,
77+
)
78+
79+
((_, kwargs),) = safe_urlopen.call_args_list
80+
data = json.loads(kwargs["data"])
81+
82+
assert kwargs["url"] == self.hook.url
83+
assert data == json.loads(json.dumps(get_payload_v0(event)))
84+
assert kwargs["headers"].keys() <= {
85+
"Content-Type",
86+
"X-ServiceHook-Timestamp",
87+
"X-ServiceHook-GUID",
88+
"X-ServiceHook-Signature",
89+
}
90+
6291
@responses.activate
6392
def test_v0_payload(self):
6493
responses.add(responses.POST, "https://example.com/sentry/webhook")

tests/sentry/tasks/test_post_process.py

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -499,6 +499,32 @@ def test_group_last_seen_buffer(self, mock_processor):
499499

500500

501501
class ServiceHooksTestMixin(BasePostProgressGroupMixin):
502+
@override_options({"process_service_hook.payload.rollout": 1.0})
503+
@patch("sentry.sentry_apps.tasks.service_hooks.process_service_hook")
504+
def test_service_hook_fires_on_new_event_payload_param(self, mock_process_service_hook):
505+
event = self.create_event(data={}, project_id=self.project.id)
506+
hook = self.create_service_hook(
507+
project=self.project,
508+
organization=self.project.organization,
509+
actor=self.user,
510+
events=["event.created"],
511+
)
512+
513+
with self.feature("projects:servicehooks"):
514+
self.call_post_process_group(
515+
is_new=False,
516+
is_regression=False,
517+
is_new_group_environment=False,
518+
event=event,
519+
)
520+
521+
mock_process_service_hook.delay.assert_called_once_with(
522+
servicehook_id=hook.id,
523+
project_id=self.project.id,
524+
group_id=event.group_id,
525+
event_id=event.event_id,
526+
)
527+
502528
@patch("sentry.sentry_apps.tasks.service_hooks.process_service_hook")
503529
def test_service_hook_fires_on_new_event(self, mock_process_service_hook):
504530
event = self.create_event(data={}, project_id=self.project.id)
@@ -518,7 +544,8 @@ def test_service_hook_fires_on_new_event(self, mock_process_service_hook):
518544
)
519545

520546
mock_process_service_hook.delay.assert_called_once_with(
521-
servicehook_id=hook.id, event=EventMatcher(event)
547+
servicehook_id=hook.id,
548+
event=EventMatcher(event),
522549
)
523550

524551
@patch("sentry.sentry_apps.tasks.service_hooks.process_service_hook")
@@ -547,7 +574,8 @@ def test_service_hook_fires_on_alert(self, mock_processor, mock_process_service_
547574
)
548575

549576
mock_process_service_hook.delay.assert_called_once_with(
550-
servicehook_id=hook.id, event=EventMatcher(event)
577+
servicehook_id=hook.id,
578+
event=EventMatcher(event),
551579
)
552580

553581
@patch("sentry.sentry_apps.tasks.service_hooks.process_service_hook")

0 commit comments

Comments
 (0)