-
-
Notifications
You must be signed in to change notification settings - Fork 4.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(auto_source_config): Derive in-app stack trace rules #87842
feat(auto_source_config): Derive in-app stack trace rules #87842
Conversation
Codecov ReportAttention: Patch coverage is ✅ All tests successful. No failed tests found.
Additional details and impacted files@@ Coverage Diff @@
## master #87842 +/- ##
===========================================
+ Coverage 42.12% 87.74% +45.61%
===========================================
Files 9892 9924 +32
Lines 561937 563914 +1977
Branches 22190 22190
===========================================
+ Hits 236735 494815 +258080
+ Misses 324771 68668 -256103
Partials 431 431 |
expected_stack_root: str, | ||
expected_source_root: str, | ||
expected_stack_root: str | None = None, | ||
expected_source_root: str | None = None, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Once this merges, this won't be necessary.
@@ -100,12 +102,13 @@ def _process_and_assert_configuration_changes( | |||
) | |||
code_mappings = RepositoryProjectPathConfig.objects.all() | |||
assert len(code_mappings) == expected_num_code_mappings | |||
code_mapping = code_mappings.filter( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This block won't be necessary when #87776 merges.
@@ -133,7 +146,8 @@ def _process_and_assert_no_configuration_changes( | |||
repo_trees: Mapping[str, Sequence[str]], | |||
frames: Sequence[Mapping[str, str | bool]], | |||
platform: str, | |||
) -> tuple[list[CodeMapping], list[str]]: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tests/sentry/issues/auto_source_code_config/test_process_event.py
Outdated
Show resolved
Hide resolved
frames=[ | ||
{"module": "com.example.foo.Bar", "abs_path": "Bar.kt"}, | ||
{"module": "com.example.foo.Baz", "abs_path": "Baz.kt"}, | ||
{"module": "com.example.utils.Helper", "abs_path": "Helper.kt"}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's the utils
app and the foo
app under the same domain, thus, we will have one in-app stack trace rule but two code mappings.
2564abb
to
d519a3b
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This LGTM! Can't wait to see the effects once it's turned on!
src/sentry/grouping/api.py
Outdated
enhancements_string = ( | ||
f"{automatic_enhancements}\n{enhancements_string}" | ||
if enhancements_string | ||
else automatic_enhancements | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: This can be simpler. If enhancements_string
is falsy, the worst that happens is we add an extra newline to the auto enhancements.
enhancements_string = ( | |
f"{automatic_enhancements}\n{enhancements_string}" | |
if enhancements_string | |
else automatic_enhancements | |
) | |
enhancements_string = f"{automatic_enhancements}\n{enhancements_string}" |
|
||
logger = logging.getLogger(__name__) | ||
|
||
OPTION_KEY = "sentry:automatic_grouping_enhancements" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What would you think of derived_grouping_enhancements
as an option name rather than automatic_grouping_enhancements
?
If I didn't know anything about how this worked, my first thought seeing the description "auto enhancements" would be that they were enhancements applied automatically. OTOH, that's not a lie - we do apply them automatically. OTOH, we also apply the default and custom enhancements automatically, so under my gut-reaction interpretation "automatic" isn't a great differentiator.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Works for me 👍🏻
src/sentry/issues/auto_source_code_config/in_app_stack_trace_rules.py
Outdated
Show resolved
Hide resolved
src/sentry/issues/auto_source_code_config/in_app_stack_trace_rules.py
Outdated
Show resolved
Hide resolved
src/sentry/issues/auto_source_code_config/in_app_stack_trace_rules.py
Outdated
Show resolved
Hide resolved
# For packages including the domain name, we want to have less granularity to create less rules, thus, | ||
# reducing the number of rules applied to the stack trace. | ||
# com/example/foo/bar -> com.example | ||
if stacktrace_root.find("/") > 2: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doesn't this want to be >= 2
? You've already stripped the trailing slash, but you still want to catch examples like com/example/foo
, right?
if len(parts) > 2: | ||
stacktrace_root = "/".join(parts[:2]) | ||
|
||
return f"stack.module:{stacktrace_root.replace('/', '.')}.** +app" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will this rule match something at the base level of the stacktrace root (module = com.example
)? IOW, can .**
match with nothing, or does the .
mean that it'd have to be com.example.
(trailing period) to match? (IDK if that's even a reasonable scenario, though.)
Also, does our glob matching treat .
the same way it treats /
? Put another way, does it recognize a dot-separated string as a path, or does it fall back to literal string matching?
assert event.data["metadata"]["in_app_frame_mix"] == "system-only" | ||
assert len(event.data["hashes"]) == 1 # Only system hash | ||
system_only_hash = event.data["hashes"][0] | ||
first_enhancements_hash = event.data["grouping_config"]["enhancements"] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I get why you're referring to this as a "hash", but that means something different than the "hash" on the line before. Maybe first_enhancements_base64_string
or something?
tests/sentry/issues/auto_source_code_config/test_process_event.py
Outdated
Show resolved
Hide resolved
This is the logic to mark frames as in-app when the files are found in the developers' GitHub repositories. This is very important for languages where the SDK or our internal rules cannot determine which frames are in-app vs not. The initial language to get support for this is Java. For now, this will run as dry-run to discover any errors or escaling problems.
Co-authored-by: Katie Byers <katie.byers@sentry.io>
2c93f6e
to
f52e04a
Compare
@@ -593,9 +593,12 @@ def get_path_from_module(module: str, abs_path: str) -> tuple[str, str]: | |||
if "." not in module: | |||
raise DoesNotFollowJavaPackageNamingConvention | |||
|
|||
# If module has a dot, take everything before the last dot |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These changes simplify this code block.
It also fixes a bug where we were not restricting the length of the stack root, thus, we would end up with com/example/
and many more levels deep (not intended behaviour).
There's a test down below that ensures we get the right number of levels.
Inputs:
- repo_file="src/com/example/foo/bar/Baz.kt"
- self.frame(module="com.example.foo.bar.Baz", abs_path="Baz.kt", in_app=False)
Expected:
- stack_root="com/example/", source_root="src/com/example/"
- in_app_rule="stack.module:com.example.** +app"
@@ -87,6 +88,7 @@ def get_config_dict(self, project: Project) -> GroupingConfig: | |||
} | |||
|
|||
def _get_enhancements(self, project: Project) -> str: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just curious - where/how is this enhancement string used?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Primarily, it is used as part of save_event
to determine which frames should be considered for app or system hash calculations.
if stacktrace_root == "": | ||
raise ValueError("Stacktrace root is empty") | ||
|
||
parts = stacktrace_root.rstrip("/").split("/", 2) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to account for backslash paths too?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not for Java.
When we generate the code mappings, we replace periods (e.g. com.foo
) with forward slashes (e.g. com/foo
).
platform=self.platform, | ||
expected_new_code_mappings=[self.code_mapping("com/example/", "src/com/example/")], | ||
expected_in_app_stack_trace_rules=["stack.module:com.example.** +app"], | ||
expected_num_code_mappings=0, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This API is a bit confusing to me — the expected_num_code_mappings
seems to conflict with the expected_new_code_mappings
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is. I intend to fix it. Since we're in dry_run mode, it won't create them.
if not platform_config.is_dry_run_platform(): | ||
project.update_option(DERIVED_ENHANCEMENTS_OPTION_KEY, "\n".join(rules)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we be checking for duplicates here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Locally, I have a test and a fix for this. I will push it later.
This is the logic to mark frames as in-app when the files are found in the developers' GitHub repositories. This is very important for languages where the SDK or our internal rules cannot determine which frames are in-app and which are not. The initial language to get support for this is Java. This will run in dry run mode to discover errors or scaling problems. --------- Co-authored-by: Katie Byers <katie.byers@sentry.io>
This is the logic to mark frames as in-app when the files are found in the developers' GitHub repositories.
This is very important for languages where the SDK or our internal rules cannot determine which frames are in-app and which are not.
The initial language to get support for this is Java.
This will run in dry run mode to discover errors or scaling problems.