Просмотр исходного кода

feat(metrics): audit log entries for metrics extraction rules (#74058)

Simon Hellmayr 8 месяцев назад
Родитель
Сommit
c0a6769ed0

+ 23 - 1
src/sentry/api/endpoints/project_metrics_extraction_rules.py

@@ -6,7 +6,7 @@ from django.db import IntegrityError, router, transaction
 from rest_framework.request import Request
 from rest_framework.response import Response
 
-from sentry import features, options
+from sentry import audit_log, features, options
 from sentry.api.api_owners import ApiOwner
 from sentry.api.api_publish_status import ApiPublishStatus
 from sentry.api.base import region_silo_endpoint
@@ -62,6 +62,18 @@ class ProjectMetricsExtractionRulesEndpoint(ProjectEndpoint):
             "organizations:custom-metrics-extraction-rule", organization, actor=request.user
         )
 
+    def _create_audit_entry(self, event: str, project: Project, span_attribute: str):
+        self.create_audit_entry(
+            request=self.request,
+            organization=project.organization,
+            target_object=project.id,
+            event=audit_log.get_event_id(event),
+            data={
+                "span_attribute": span_attribute,
+                "project_slug": project.slug,
+            },
+        )
+
     def delete(self, request: Request, project: Project) -> Response:
         """DELETE an extraction rule in a project. Returns 204 No Data on success."""
         if not self.has_feature(project.organization, request):
@@ -77,6 +89,10 @@ class ProjectMetricsExtractionRulesEndpoint(ProjectEndpoint):
                 schedule_invalidate_project_config(
                     project_id=project.id, trigger="span_attribute_extraction_configs"
                 )
+                for config in config_update:
+                    self._create_audit_entry(
+                        "SPAN_BASED_METRIC_DELETE", project, config["spanAttribute"]
+                    )
 
             return Response(status=204)
 
@@ -124,6 +140,10 @@ class ProjectMetricsExtractionRulesEndpoint(ProjectEndpoint):
                 schedule_invalidate_project_config(
                     project_id=project.id, trigger="span_attribute_extraction_configs"
                 )
+                for config in configs:
+                    self._create_audit_entry(
+                        "SPAN_BASED_METRIC_CREATE", project, config.span_attribute
+                    )
 
             persisted_config = serialize(
                 configs, request.user, SpanAttributeExtractionRuleConfigSerializer()
@@ -146,6 +166,8 @@ class ProjectMetricsExtractionRulesEndpoint(ProjectEndpoint):
                 schedule_invalidate_project_config(
                     project_id=project.id, trigger="span_attribute_extraction_configs"
                 )
+            for config in configs:
+                self._create_audit_entry("SPAN_BASED_METRIC_UPDATE", project, config.span_attribute)
 
             persisted_config = serialize(
                 configs,

+ 26 - 0
src/sentry/audit_log/register.py

@@ -509,3 +509,29 @@ default_manager.add(
         template="Deleted issue {issue_id} for project {project_slug}",
     )
 )
+default_manager.add(
+    AuditLogEvent(
+        event_id=187,
+        name="SPAN_BASED_METRIC_CREATE",
+        api_name="span_extraction_rule_config.create",
+        template="Created span-based metric for span attribute {span_attribute} for project {project_slug}",
+    )
+)
+
+default_manager.add(
+    AuditLogEvent(
+        event_id=188,
+        name="SPAN_BASED_METRIC_UPDATE",
+        api_name="span_extraction_rule_config.update",
+        template="Updated span-based metric for span attribute {span_attribute} for project {project_slug}",
+    )
+)
+
+default_manager.add(
+    AuditLogEvent(
+        event_id=189,
+        name="SPAN_BASED_METRIC_DELETE",
+        api_name="span_extraction_rule_config.delete",
+        template="Deleted span-based metric for span attribute {span_attribute} for project {project_slug}",
+    )
+)

+ 63 - 0
tests/sentry/api/endpoints/test_project_metrics_extraction_rules.py

@@ -1,4 +1,5 @@
 import uuid
+from unittest.mock import patch
 
 from django.urls import reverse
 
@@ -80,6 +81,68 @@ class ProjectMetricsExtractionEndpointTestCase(APITestCase):
             assert conditions[0]["value"] == "foo:bar"
             assert conditions[1]["value"] == "baz:faz"
 
+    @django_db_all
+    @with_feature("organizations:custom-metrics-extraction-rule")
+    @patch(
+        "sentry.api.endpoints.project_metrics_extraction_rules.ProjectMetricsExtractionRulesEndpoint.create_audit_entry"
+    )
+    def test_audit_log_entry_emitted(self, create_audit_entry):
+        new_rule = {
+            "metricsExtractionRules": [
+                {
+                    "spanAttribute": "count_clicks",
+                    "aggregates": ["count"],
+                    "unit": "none",
+                    "tags": ["tag1", "tag2", "tag3"],
+                    "conditions": [
+                        {"value": "foo:bar"},
+                        {"value": "baz:faz"},
+                    ],
+                }
+            ]
+        }
+
+        self.get_success_response(
+            self.organization.slug,
+            self.project.slug,
+            method="post",
+            **new_rule,
+        )
+        create_audit_entry.assert_called()
+        create_audit_entry.reset_mock()
+
+        updated_rule = {
+            "metricsExtractionRules": [
+                {
+                    "spanAttribute": "count_clicks",
+                    "aggregates": ["count"],
+                    "unit": "none",
+                    "tags": ["tag1", "tag2", "tag3"],
+                    "conditions": [
+                        {"id": 1, "value": "other:condition"},
+                    ],
+                }
+            ]
+        }
+
+        self.get_success_response(
+            self.organization.slug,
+            self.project.slug,
+            method="put",
+            **updated_rule,
+        )
+        create_audit_entry.assert_called()
+        create_audit_entry.reset_mock()
+
+        self.get_success_response(
+            self.organization.slug,
+            self.project.slug,
+            method="delete",
+            **updated_rule,
+        )
+        create_audit_entry.assert_called()
+        create_audit_entry.reset_mock()
+
     @django_db_all
     @with_feature("organizations:custom-metrics-extraction-rule")
     def test_create_new_extraction_rule_hardcoded_units(self):

+ 3 - 0
tests/sentry/audit_log/test_register.py

@@ -66,6 +66,9 @@ class AuditLogEventRegisterTest(TestCase):
             "integration.rotate-client-secret",
             "sampling_priority.enabled",
             "sampling_priority.disabled",
+            "span_extraction_rule_config.create",
+            "span_extraction_rule_config.update",
+            "span_extraction_rule_config.delete",
             "monitor.add",
             "monitor.edit",
             "monitor.environment.edit",