Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
25 changes: 17 additions & 8 deletions src/sentry/grouping/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
from sentry.models.grouphash import GroupHash
from sentry.utils.cache import cache
from sentry.utils.hashlib import md5_text
from sentry.utils.safe import get_path

if TYPE_CHECKING:
from sentry.grouping.fingerprinting import FingerprintingConfig
Expand Down Expand Up @@ -282,16 +283,9 @@ def apply_server_side_fingerprinting(

fingerprint_match = fingerprinting_config.get_fingerprint_values_for_event(event)
if fingerprint_match is not None:
# TODO: We don't need to return attributes as part of the fingerprint match anymore
matched_rule, new_fingerprint, attributes = fingerprint_match

# A custom title attribute is stored in the event to override the
# default title.
if "title" in attributes:
event["title"] = expand_title_template(attributes["title"], event)
event["fingerprint"] = new_fingerprint

# Persist the rule that matched with the fingerprint in the event
# dictionary for later debugging.
fingerprint_info["matched_rule"] = matched_rule.to_json()

if fingerprint_info:
Expand Down Expand Up @@ -405,6 +399,9 @@ def get_grouping_variants_for_event(
else resolve_fingerprint_values(raw_fingerprint, event.data)
)

# Check if the fingerprint includes a custom title, and if so, set the event's title accordingly.
_apply_custom_title_if_needed(fingerprint_info, event)

# Run all of the event-data-based grouping strategies. Any which apply will create grouping
# components, which will then be grouped into variants by variant type (system, app, default).
context = GroupingContext(config or _load_default_grouping_config(), event)
Expand Down Expand Up @@ -458,6 +455,18 @@ def get_grouping_variants_for_event(
return final_variants


def _apply_custom_title_if_needed(fingerprint_info: FingerprintInfo, event: Event) -> None:
"""
If the given event has a custom fingerprint which includes a title template, apply the custom
title to the event.
"""
custom_title_template = get_path(fingerprint_info, "matched_rule", "attributes", "title")

if custom_title_template:
resolved_title = expand_title_template(custom_title_template, event.data)
event.data["title"] = resolved_title


def get_contributing_variant_and_component(
variants: dict[str, BaseVariant],
) -> tuple[BaseVariant, ContributingComponent | None]:
Expand Down
25 changes: 24 additions & 1 deletion tests/sentry/grouping/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
GROUPING_CONFIG_CLASSES,
register_grouping_config,
)
from sentry.grouping.utils import expand_title_template
from sentry.grouping.variants import BaseVariant
from sentry.models.project import Project
from sentry.services import eventstore
Expand All @@ -35,6 +36,7 @@
from sentry.testutils.helpers.eventprocessing import save_new_event
from sentry.testutils.pytest.fixtures import InstaSnapshotter, django_db_all
from sentry.utils import json
from sentry.utils.safe import get_path

GROUPING_TESTS_DIR = path.dirname(__file__)
GROUPING_INPUTS_DIR = path.join(GROUPING_TESTS_DIR, "grouping_inputs")
Expand Down Expand Up @@ -82,11 +84,22 @@ def _manually_save_event(
mgr.normalize()
data = mgr.get_data()

# Normalize the stacktrace for grouping. This normally happens in `EventManager.save`.
# Before creating the event, manually run the parts of `EventManager.save` which are
# necessary for grouping.

normalize_stacktraces_for_grouping(data, load_grouping_config(grouping_config))

data.setdefault("fingerprint", ["{{ default }}"])
apply_server_side_fingerprinting(data, fingerprinting_config)
fingerprint_info = data.get("_fingerprint_info", {})
custom_title_template = get_path(fingerprint_info, "matched_rule", "attributes", "title")

# Technically handling custom titles happens during grouping, not before it, but we're not
# running grouping until later, and the title needs to be set before we get metadata below.
if custom_title_template:
resolved_title = expand_title_template(custom_title_template, data)
data["title"] = resolved_title

event_type = get_event_type(data)
event_metadata = event_type.get_metadata(data)
data.update(materialize_metadata(data, event_type, event_metadata))
Expand Down Expand Up @@ -311,8 +324,18 @@ def create_event(self) -> tuple[FingerprintingConfig, Event]:
mgr.normalize()
data = mgr.get_data()

# Before creating the event, manually run the parts of `EventManager.save` which are
# necessary for fingerprinting.

data.setdefault("fingerprint", ["{{ default }}"])
apply_server_side_fingerprinting(data, config)
fingerprint_info = data.get("_fingerprint_info", {})
custom_title_template = get_path(fingerprint_info, "matched_rule", "attributes", "title")

if custom_title_template:
resolved_title = expand_title_template(custom_title_template, data)
data["title"] = resolved_title

event_type = get_event_type(data)
event_metadata = event_type.get_metadata(data)
data.update(materialize_metadata(data, event_type, event_metadata))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"_fingerprinting_rules": [
{
"matchers": [["module", "do_dog_stuff"]],
"fingerprint": ["{{ function }}"],
"attributes": {
"title": "Dogs are great ({{ function }})"
}
}
],
"exception": {
"values": [
{
"stacktrace": {
"frames": [
{
"function": "fetch",
"module": "do_dog_stuff",
"in_app": true
},
{
"function": "throw_ball",
"module": "do_dog_stuff",
"in_app": true
},
{
"function": "compute_ball_arc",
"module": "physics",
"in_app": false
}
]
},
"type": "MathError",
"value": "Missing necessary formulas"
}
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
---
source: tests/sentry/grouping/test_fingerprinting.py::test_event_hash_variant
---
config:
rules:
- attributes:
title: Dogs are great ({{ function }})
fingerprint:
- '{{ function }}'
matchers:
- - module
- do_dog_stuff
text: module:"do_dog_stuff" -> "{{ function }}" title="Dogs are great ({{ function
}})"
version: 1
fingerprint:
- '{{ function }}'
title: Dogs are great (throw_ball)
variants:
app:
component:
contributes: false
hint: ignored because custom server fingerprint takes precedence
contributes: false
hint: ignored because custom server fingerprint takes precedence
key: app_exception_stacktrace
type: component
custom_fingerprint:
contributes: true
hint: null
key: custom_fingerprint
matched_rule: module:"do_dog_stuff" -> "{{ function }}" title="Dogs are great
({{ function }})"
type: custom_fingerprint
values:
- throw_ball
system:
component:
contributes: false
hint: ignored because custom server fingerprint takes precedence
contributes: false
hint: ignored because custom server fingerprint takes precedence
key: system_exception_stacktrace
type: component