Skip to content

Commit 975b000

Browse files
cvxluolobsterkatie
andauthored
feat(issues): infer project platform if not set (#95402)
If a project ends up with no `platform` (e.g. by API creation), we attempt to infer it from its next event by setting the project's `platform` to the event's `platform`. Related: - https://linear.app/getsentry/issue/ID-442/investigate-and-fix-projects-with-no-platform - #82176 --------- Co-authored-by: Katie Byers <lobsterkatie@gmail.com>
1 parent 168cf45 commit 975b000

File tree

3 files changed

+65
-1
lines changed

3 files changed

+65
-1
lines changed

src/sentry/event_manager.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,14 @@
1414
from django.core.cache import cache
1515
from django.core.exceptions import ValidationError
1616
from django.db import IntegrityError, OperationalError, connection, router, transaction
17-
from django.db.models import Max
17+
from django.db.models import Max, Q
1818
from django.db.models.signals import post_save
1919
from django.utils.encoding import force_str
2020
from urllib3.exceptions import MaxRetryError, TimeoutError
2121
from usageaccountant import UsageUnit
2222

2323
from sentry import (
24+
audit_log,
2425
eventstore,
2526
eventstream,
2627
eventtypes,
@@ -37,6 +38,7 @@
3738
LOG_LEVELS_MAP,
3839
MAX_TAG_VALUE_LENGTH,
3940
PLACEHOLDER_EVENT_TITLES,
41+
VALID_PLATFORMS,
4042
DataCategory,
4143
InsightModules,
4244
)
@@ -127,6 +129,7 @@
127129
from sentry.types.group import GroupSubStatus, PriorityLevel
128130
from sentry.usage_accountant import record
129131
from sentry.utils import metrics
132+
from sentry.utils.audit import create_system_audit_entry
130133
from sentry.utils.cache import cache_key_for_event
131134
from sentry.utils.circuit_breaker import (
132135
ERROR_COUNT_CACHE_KEY,
@@ -137,6 +140,7 @@
137140
from sentry.utils.event import has_event_minified_stack_trace, has_stacktrace, is_handled
138141
from sentry.utils.eventuser import EventUser
139142
from sentry.utils.metrics import MutableTags
143+
from sentry.utils.options import sample_modulo
140144
from sentry.utils.outcomes import Outcome, track_outcome
141145
from sentry.utils.projectflags import set_project_flag_and_signal
142146
from sentry.utils.safe import get_path, safe_execute, setdefault_path, trim
@@ -464,6 +468,11 @@ def save(
464468
# After calling _pull_out_data we get some keys in the job like the platform
465469
_pull_out_data([job], projects)
466470

471+
# Sometimes projects get created without a platform (e.g. through the API), in which case we
472+
# attempt to set it based on the first event
473+
if sample_modulo("sentry:infer_project_platform", project.id):
474+
_set_project_platform_if_needed(project, job["event"])
475+
467476
event_type = self._data.get("type")
468477
if event_type == "transaction":
469478
job["data"]["project"] = project.id
@@ -690,6 +699,30 @@ def _pull_out_data(jobs: Sequence[Job], projects: ProjectsMapping) -> None:
690699
job["groups"] = []
691700

692701

702+
def _set_project_platform_if_needed(project: Project, event: Event) -> None:
703+
if project.platform:
704+
return
705+
706+
if event.platform not in VALID_PLATFORMS or event.get_tag("sample_event") == "yes":
707+
return
708+
709+
try:
710+
updated = Project.objects.filter(
711+
Q(id=project.id) & (Q(platform__isnull=True) | Q(platform=""))
712+
).update(platform=event.platform)
713+
714+
if updated:
715+
create_system_audit_entry(
716+
organization=project.organization,
717+
target_object=project.id,
718+
event=audit_log.get_event_id("PROJECT_EDIT"),
719+
data={**project.get_audit_log_data(), "platform": event.platform},
720+
)
721+
722+
except Exception:
723+
logger.exception("Failed to infer and set project platform")
724+
725+
693726
@sentry_sdk.tracing.trace
694727
def _get_or_create_release_many(jobs: Sequence[Job], projects: ProjectsMapping) -> None:
695728
for job in jobs:

src/sentry/options/defaults.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3480,3 +3480,11 @@
34803480
default=False,
34813481
flags=FLAG_AUTOMATOR_MODIFIABLE,
34823482
)
3483+
3484+
# Rollout for inferring project platform from events received
3485+
register(
3486+
"sentry:infer_project_platform",
3487+
type=Float,
3488+
default=0.0,
3489+
flags=FLAG_AUTOMATOR_MODIFIABLE,
3490+
)
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
from sentry.testutils.cases import TestCase
2+
from sentry.testutils.helpers import override_options
3+
from sentry.testutils.helpers.eventprocessing import save_new_event
4+
5+
6+
class ProjectPlatformInferTest(TestCase):
7+
@override_options({"sentry:infer_project_platform": 1.0})
8+
def test_platform_inferred_on_event(self):
9+
project = self.create_project()
10+
11+
save_new_event({"message": "test", "platform": "javascript"}, project)
12+
13+
project.refresh_from_db()
14+
assert project.platform == "javascript"
15+
16+
@override_options({"sentry:infer_project_platform": 1.0})
17+
def test_platform_does_not_override_existing_platform(self):
18+
project = self.create_project(platform="python")
19+
20+
save_new_event({"message": "test", "platform": "javascript"}, project)
21+
22+
project.refresh_from_db()
23+
assert project.platform == "python"

0 commit comments

Comments
 (0)