Skip to content

feat(crash-detection): allow matching on frame.module #93690

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

Merged
merged 6 commits into from
Jun 23, 2025
Merged
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
59 changes: 47 additions & 12 deletions src/sentry/utils/sdk_crashes/sdk_crash_detection_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,21 @@ class FunctionAndPathPattern:
path_pattern: str


@dataclass(frozen=True)
class FunctionAndModulePattern:
"""Pattern for matching function and module to ignore SDK crashes.

Use "*" as a wildcard to match any value.
Examples:
- FunctionAndModulePattern("specific.module", "invoke") - matches only "invoke" in "specific.module"
- FunctionAndModulePattern("*", "invoke") - matches "invoke" in any module
- FunctionAndModulePattern("specific.module", "*") - matches any function in "specific.module"
"""

module_pattern: str
function_pattern: str


@dataclass
class SDKFrameConfig:
function_patterns: set[str]
Expand Down Expand Up @@ -65,8 +80,8 @@ class SDKCrashDetectionConfig:
system_library_path_patterns: set[str]
"""The configuration for detecting SDK frames."""
sdk_frame_config: SDKFrameConfig
"""The functions to ignore when detecting SDK crashes. For example, `**SentrySDK crash**`"""
sdk_crash_ignore_functions_matchers: set[str]
"""The function and module patterns to ignore when detecting SDK crashes. For example, FunctionAndModulePattern("*", "**SentrySDK crash**") for any module with that function"""
sdk_crash_ignore_matchers: set[FunctionAndModulePattern]


class SDKCrashDetectionOptions(TypedDict):
Expand Down Expand Up @@ -119,7 +134,12 @@ def build_sdk_crash_detection_configs() -> Sequence[SDKCrashDetectionConfig]:
),
# [SentrySDK crash] is a testing function causing a crash.
# Therefore, we don't want to mark it a as a SDK crash.
sdk_crash_ignore_functions_matchers={"**SentrySDK crash**"},
sdk_crash_ignore_matchers={
FunctionAndModulePattern(
module_pattern="*",
function_pattern="**SentrySDK crash**",
),
},
)
configs.append(cocoa_config)

Expand Down Expand Up @@ -172,10 +192,13 @@ def build_sdk_crash_detection_configs() -> Sequence[SDKCrashDetectionConfig]:
fallback_path="sentry-react-native",
),
),
sdk_crash_ignore_functions_matchers={
sdk_crash_ignore_matchers={
# sentryWrapped rethrows the original error
# https://github.com/getsentry/sentry-javascript/blob/a67ebc4f56fd20259bffbe194e8e92e968589c12/packages/browser/src/helpers.ts#L107
"sentryWrapped",
FunctionAndModulePattern(
module_pattern="*",
function_pattern="sentryWrapped",
),
},
)
configs.append(react_native_config)
Expand Down Expand Up @@ -256,7 +279,7 @@ def build_sdk_crash_detection_configs() -> Sequence[SDKCrashDetectionConfig]:
],
path_replacer=KeepFieldPathReplacer(fields={"module", "filename", "package"}),
),
sdk_crash_ignore_functions_matchers=set(),
sdk_crash_ignore_matchers=set(),
)
configs.append(java_config)

Expand Down Expand Up @@ -312,7 +335,7 @@ def build_sdk_crash_detection_configs() -> Sequence[SDKCrashDetectionConfig]:
fallback_path="sentry",
),
),
sdk_crash_ignore_functions_matchers=set(),
sdk_crash_ignore_matchers=set(),
)
configs.append(native_config)

Expand Down Expand Up @@ -359,19 +382,31 @@ def build_sdk_crash_detection_configs() -> Sequence[SDKCrashDetectionConfig]:
},
path_replacer=KeepFieldPathReplacer(fields={"package", "filename", "abs_path"}),
),
sdk_crash_ignore_functions_matchers={
sdk_crash_ignore_matchers={
# getCurrentStackTrace is always part of the stacktrace when the SDK captures the stacktrace,
# and would cause false positives. Therefore, we ignore it.
"getCurrentStackTrace",
FunctionAndModulePattern(
module_pattern="*",
function_pattern="getCurrentStackTrace",
),
# Ignore handleDrawFrame and handleBeginFrame to avoid false positives.
# In the Sentry Flutter SDK, we override the handleDrawFrame and handleBeginFrame methods,
# add our custom implementation on top to instrument frame tracking and then forward the calls to Flutter.
# However every custom implementation is try/catch guarded so no exception can be thrown.
"SentryWidgetsBindingMixin.handleDrawFrame",
"SentryWidgetsBindingMixin.handleBeginFrame",
FunctionAndModulePattern(
module_pattern="*",
function_pattern="SentryWidgetsBindingMixin.handleDrawFrame",
),
FunctionAndModulePattern(
module_pattern="*",
function_pattern="SentryWidgetsBindingMixin.handleBeginFrame",
),
# This is the integration responsible for reporting unhandled errors.
# For certain errors the frame is sometimes included in the stacktrace which leads to false positives.
"FlutterErrorIntegration.call.<fn>",
FunctionAndModulePattern(
module_pattern="*",
function_pattern="FlutterErrorIntegration.call.<fn>",
),
},
)
configs.append(dart_config)
Expand Down
11 changes: 9 additions & 2 deletions src/sentry/utils/sdk_crashes/sdk_crash_detector.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,16 @@ def is_sdk_crash(self, frames: Sequence[Mapping[str, Any]]) -> bool:
iter_frames = [f for f in reversed(frames) if f is not None]
for frame in iter_frames:
function = frame.get("function")
module = frame.get("module")

if function:
for matcher in self.config.sdk_crash_ignore_functions_matchers:
if glob_match(function, matcher, ignorecase=True):
for matcher in self.config.sdk_crash_ignore_matchers:
function_matches = glob_match(
function, matcher.function_pattern, ignorecase=True
)
module_matches = glob_match(module, matcher.module_pattern, ignorecase=True)

if function_matches and module_matches:
return False

if self.is_sdk_frame(frame):
Expand Down
2 changes: 1 addition & 1 deletion tests/sentry/utils/sdk_crashes/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,5 @@ def empty_cocoa_config() -> SDKCrashDetectionConfig:
path_patterns=set(),
path_replacer=FixedPathReplacer(path=""),
),
sdk_crash_ignore_functions_matchers=set(),
sdk_crash_ignore_matchers=set(),
)
63 changes: 63 additions & 0 deletions tests/sentry/utils/sdk_crashes/test_sdk_crash_detector.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import pytest

from sentry.utils.sdk_crashes.sdk_crash_detection_config import FunctionAndModulePattern
from sentry.utils.sdk_crashes.sdk_crash_detector import SDKCrashDetector


Expand All @@ -15,3 +16,65 @@ def test_build_sdk_crash_detection_configs(empty_cocoa_config, field_containing_
}

assert detector.is_sdk_frame(frame) is True


@pytest.mark.parametrize(
"test_id,ignore_matchers,frames,is_crash,description",
[
(
"function_module_match",
[
FunctionAndModulePattern(
module_pattern="kotlin.coroutines", function_pattern="invoke"
)
],
[{"function": "invoke", "module": "kotlin.coroutines", "package": "MyApp"}],
False,
"Should not report a crash when both module and function match pattern exactly",
),
(
"function_match_module_wildcard",
[FunctionAndModulePattern(module_pattern="*", function_pattern="getCurrentStackTrace")],
[{"function": "getCurrentStackTrace", "module": "some.module", "package": "MyApp"}],
False,
"Should not report a crash when function matches and module pattern is wildcard",
),
(
"function_match_module_wildcard_module_is_none",
[FunctionAndModulePattern(module_pattern="*", function_pattern="getCurrentStackTrace")],
[{"function": "getCurrentStackTrace", "package": "MyApp"}],
False,
"Should not report a crash when function matches and module pattern is wildcard, even when frames[0].module is None",
),
(
"function_mismatch_module_wildcard",
[FunctionAndModulePattern(module_pattern="*", function_pattern="getCurrentStackTrace")],
[{"function": "someOtherFunction", "module": "some.module", "package": "MyApp"}],
True,
"Should report a crash when module pattern is wildcard but function doesn't match",
),
(
"function_wildcard_module_match",
[FunctionAndModulePattern(module_pattern="test.module", function_pattern="*")],
[{"function": "anyFunction", "module": "test.module", "package": "MyApp"}],
False,
"Should not report a crash when function pattern is wildcard and module matches",
),
(
"function_wildcard_module_mismatch",
[FunctionAndModulePattern(module_pattern="test.module", function_pattern="*")],
[{"function": "anyFunction", "module": "other.module", "package": "MyApp"}],
True,
"Should report a crash when function pattern is wildcard but module doesn't match",
),
],
)
def test_sdk_crash_ignore_matchers(
empty_cocoa_config, test_id, ignore_matchers, frames, is_crash, description
):
empty_cocoa_config.sdk_crash_ignore_matchers = set(ignore_matchers)
empty_cocoa_config.sdk_frame_config.path_patterns = {"**"}

detector = SDKCrashDetector(empty_cocoa_config)

assert detector.is_sdk_crash(frames) is is_crash, description
Loading