Browse Source

Make os.distribution searchable (#69865)

Users of the Native SDK also want to search for the Linux distributions
their events came from:
https://github.com/getsentry/sentry-native/issues/943

The corresponding PRs to

* develop docs: https://github.com/getsentry/develop/pull/1227
* relay: https://github.com/getsentry/relay/pull/3443
* Native SDK: https://github.com/getsentry/sentry-native/pull/963
Mischan Toosarani-Hausberger 9 months ago
parent
commit
1ec1cafae4

+ 21 - 2
src/sentry/rules/conditions/event_attribute.py

@@ -39,6 +39,8 @@ ATTR_CHOICES = {
     "stacktrace.package": Columns.STACK_PACKAGE,
     "unreal.crashtype": Columns.UNREAL_CRASH_TYPE,
     "app.in_foreground": Columns.APP_IN_FOREGROUND,
+    "os.distribution.name": Columns.OS_DISTRIBUTION_NAME,
+    "os.distribution.version": Columns.OS_DISTRIBUTION_VERSION,
 }
 
 
@@ -112,8 +114,8 @@ class EventAttributeCondition(EventCondition):
                 return value
             return [value]
 
-        elif len(path) != 2:
-            return []
+        elif len(path) < 2:
+            return []  # all attribute paths below have at least 2 elements
 
         elif path[0] == "exception":
             if path[1] not in ("type", "value"):
@@ -211,6 +213,23 @@ class EventAttributeCondition(EventCondition):
                     response = {}
                 return [response.get(path[1])]
 
+        elif len(path) < 3:
+            return []  # all attribute paths below have at least 3 elements
+
+        elif path[0] == "os":
+            if path[1] in ("distribution"):
+                if path[2] in ("name", "version"):
+                    contexts = event.data["contexts"]
+                    os_context = contexts.get("os")
+                    if os_context is None:
+                        os_context = {}
+
+                    distribution = os_context.get(path[1])
+                    if distribution is None:
+                        distribution = {}
+
+                    return [distribution.get(path[2])]
+                return []
             return []
 
         return []

+ 16 - 0
src/sentry/snuba/events.py

@@ -600,6 +600,22 @@ class Columns(Enum):
         issue_platform_name="contexts[app.in_foreground]",
         alias="app.in_foreground",
     )
+    OS_DISTRIBUTION_NAME = Column(
+        group_name="events.contexts[os.distribution.name]",
+        event_name="contexts[os.distribution.name]",
+        transaction_name="contexts[os.distribution.name]",
+        discover_name="contexts[os.distribution.name]",
+        issue_platform_name="contexts[os.distribution.name]",
+        alias="os.distribution.name",
+    )
+    OS_DISTRIBUTION_VERSION = Column(
+        group_name="events.contexts[os.distribution.version]",
+        event_name="contexts[os.distribution.version]",
+        transaction_name="contexts[os.distribution.version]",
+        discover_name="contexts[os.distribution.version]",
+        issue_platform_name="contexts[os.distribution.version]",
+        alias="os.distribution.version",
+    )
     # Transactions specific columns
     TRANSACTION_OP = Column(
         group_name=None,

+ 47 - 0
tests/sentry/rules/conditions/test_event_attribute.py

@@ -59,6 +59,12 @@ class EventAttributeConditionTest(RuleTestCase):
                 "unreal": {
                     "crash_type": "crash",
                 },
+                "os": {
+                    "distribution": {
+                        "name": "ubuntu",
+                        "version": "22.04",
+                    }
+                },
             },
             "threads": {
                 "values": [
@@ -757,6 +763,47 @@ class EventAttributeConditionTest(RuleTestCase):
         )
         self.assertDoesNotPass(rule, event)
 
+    def test_os_distribution_only(self):
+        event = self.get_event()
+        rule = self.get_rule(
+            data={"match": MatchType.EQUAL, "attribute": "os.distribution", "value": "irrelevant"}
+        )
+        self.assertDoesNotPass(rule, event)
+
+    def test_os_distribution_name_and_version(self):
+        event = self.get_event()
+        rule = self.get_rule(
+            data={"match": MatchType.EQUAL, "attribute": "os.distribution.name", "value": "ubuntu"}
+        )
+        self.assertPasses(rule, event)
+
+        rule = self.get_rule(
+            data={
+                "match": MatchType.EQUAL,
+                "attribute": "os.distribution.version",
+                "value": "22.04",
+            }
+        )
+        self.assertPasses(rule, event)
+
+        rule = self.get_rule(
+            data={
+                "match": MatchType.EQUAL,
+                "attribute": "os.distribution.name",
+                "value": "slackware",
+            }
+        )
+        self.assertDoesNotPass(rule, event)
+
+        rule = self.get_rule(
+            data={
+                "match": MatchType.EQUAL,
+                "attribute": "os.distribution.version",
+                "value": "20.04",
+            }
+        )
+        self.assertDoesNotPass(rule, event)
+
     def test_unreal_crash_type(self):
         event = self.get_event()
         rule = self.get_rule(