-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(sdk-crash-detection): Store events to project (#50796)
Store all unhandled Cocoa SDK events to a dedicated project configurable via the settings. Before enabling this feature in production we must check if the unhandled event stems from the Cocoa SDK and we need to strip most of the event data to prevent collecting PII. This is the second PR of splitting up the POC for SDK crash detection (#49928) into multiple PRs and it is based on #50794. --------- Co-authored-by: Joris Bayer <joris.bayer@sentry.io>
- Loading branch information
1 parent
f61ff85
commit 7a57134
Showing
7 changed files
with
483 additions
and
9 deletions.
There are no files selected for viewing
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,269 @@ | ||
from typing import Any, Collection, Dict, Mapping, Sequence | ||
|
||
IN_APP_FRAME = { | ||
"function": "LoginViewController.viewDidAppear", | ||
"raw_function": "LoginViewController.viewDidAppear(Bool)", | ||
"symbol": "$s8Sentry9LoginViewControllerC13viewDidAppearyySbF", | ||
"package": "SentryApp", | ||
"filename": "LoginViewController.swift", | ||
"abs_path": "/Users/sentry/git/iOS/Sentry/LoggedOut/LoginViewController.swift", | ||
"lineno": 196, | ||
"in_app": True, | ||
"image_addr": "0x100260000", | ||
"instruction_addr": "0x102b16630", | ||
"symbol_addr": "0x100260000", | ||
} | ||
|
||
|
||
def get_sentry_frame(function: str, in_app: bool = False) -> Mapping[str, Any]: | ||
return { | ||
"function": function, | ||
"package": "Sentry", | ||
"in_app": in_app, | ||
"image_addr": "0x100304000", | ||
} | ||
|
||
|
||
def get_frames(function: str, sentry_frame_in_app: bool = False) -> Sequence[Mapping[str, Any]]: | ||
frames = [ | ||
get_sentry_frame(function, sentry_frame_in_app), | ||
{ | ||
"function": "LoginViewController.viewDidAppear", | ||
"symbol": "$s8Sentry9LoginViewControllerC13viewDidAppearyySbF", | ||
"package": "SentryApp", | ||
"in_app": True, | ||
"filename": "LoginViewController.swift", | ||
"image_addr": "0x100260000", | ||
}, | ||
IN_APP_FRAME, | ||
{ | ||
"function": "-[UIViewController _setViewAppearState:isAnimating:]", | ||
"symbol": "-[UIViewController _setViewAppearState:isAnimating:]", | ||
"package": "UIKitCore", | ||
"in_app": False, | ||
"image_addr": "0x1a4e8f000", | ||
}, | ||
{ | ||
"function": "-[UIViewController __viewDidAppear:]", | ||
"symbol": "-[UIViewController __viewDidAppear:]", | ||
"package": "UIKitCore", | ||
"in_app": False, | ||
"image_addr": "0x1a4e8f000", | ||
}, | ||
{ | ||
"function": "-[UIViewController _endAppearanceTransition:]", | ||
"symbol": "-[UIViewController _endAppearanceTransition:]", | ||
"package": "UIKitCore", | ||
"in_app": False, | ||
"image_addr": "0x1a4e8f000", | ||
}, | ||
{ | ||
"function": "-[UINavigationController navigationTransitionView:didEndTransition:fromView:toView:]", | ||
"symbol": "-[UINavigationController navigationTransitionView:didEndTransition:fromView:toView:]", | ||
"package": "UIKitCore", | ||
"in_app": False, | ||
"image_addr": "0x1a4e8f000", | ||
}, | ||
{ | ||
"function": "__49-[UINavigationController _startCustomTransition:]_block_invoke", | ||
"symbol": "__49-[UINavigationController _startCustomTransition:]_block_invoke", | ||
"package": "UIKitCore", | ||
"in_app": False, | ||
"image_addr": "0x1a4e8f000", | ||
}, | ||
] | ||
|
||
# The frames have to be ordered from caller to callee, or oldest to youngest. | ||
# The last frame is the one creating the exception. | ||
# As we usually look at stacktraces from youngest to oldest, we reverse the order. | ||
return frames[::-1] | ||
|
||
|
||
def get_crash_event(handled=False, function="-[Sentry]", **kwargs) -> Dict[str, Collection[str]]: | ||
return get_crash_event_with_frames(get_frames(function), handled=handled, **kwargs) | ||
|
||
|
||
def get_crash_event_with_frames( | ||
frames: Sequence[Mapping[str, Any]], handled=False, **kwargs | ||
) -> Dict[str, Collection[str]]: | ||
result = { | ||
"event_id": "80e3496eff734ab0ac993167aaa0d1cd", | ||
"release": "5.222.5", | ||
"type": "error", | ||
"level": "fatal", | ||
"platform": "cocoa", | ||
"tags": {"level": "fatal"}, | ||
"exception": { | ||
"values": [ | ||
{ | ||
"stacktrace": { | ||
"frames": frames, | ||
}, | ||
"type": "SIGABRT", | ||
"mechanism": {"handled": handled}, | ||
} | ||
] | ||
}, | ||
"breadcrumbs": { | ||
"values": [ | ||
{ | ||
"timestamp": 1675900265.0, | ||
"type": "debug", | ||
"category": "started", | ||
"level": "info", | ||
"message": "Breadcrumb Tracking", | ||
}, | ||
] | ||
}, | ||
"contexts": { | ||
"app": { | ||
"app_start_time": "2023-02-08T23:51:05Z", | ||
"device_app_hash": "8854fe9e3d4e4a66493baee798bfae0228efabf1", | ||
"build_type": "app store", | ||
"app_identifier": "com.some.company.io", | ||
"app_name": "SomeCompany", | ||
"app_version": "5.222.5", | ||
"app_build": "21036", | ||
"app_id": "397D4F75-6C01-32D1-BF46-62098979E470", | ||
"type": "app", | ||
}, | ||
"device": { | ||
"family": "iOS", | ||
"model": "iPhone14,8", | ||
"model_id": "D28AP", | ||
"arch": "arm64e", | ||
"memory_size": 5944508416, | ||
"free_memory": 102154240, | ||
"usable_memory": 4125687808, | ||
"storage_size": 127854202880, | ||
"boot_time": "2023-02-01T05:21:23Z", | ||
"timezone": "PST", | ||
"type": "device", | ||
}, | ||
"os": { | ||
"name": "iOS", | ||
"version": "16.3", | ||
"build": "20D47", | ||
"kernel_version": "Darwin Kernel Version 22.3.0: Wed Jan 4 21:25:19 PST 2023; root:xnu-8792.82.2~1/RELEASE_ARM64_T8110", | ||
"rooted": False, | ||
"type": "os", | ||
}, | ||
}, | ||
"debug_meta": { | ||
"images": [ | ||
{ | ||
"code_file": "/private/var/containers/Bundle/Application/895DA2DE-5FE3-44A0-8C0F-900519EA5516/iOS-Swift.app/iOS-Swift", | ||
"debug_id": "aa8a3697-c88a-36f9-a687-3d3596568c8d", | ||
"arch": "arm64", | ||
"image_addr": "0x100260000", | ||
"image_size": 180224, | ||
"image_vmaddr": "0x100000000", | ||
"type": "macho", | ||
}, | ||
{ | ||
"code_file": "/private/var/containers/Bundle/Application/9EB557CD-D653-4F51-BFCE-AECE691D4347/iOS-Swift.app/Frameworks/Sentry.framework/Sentry", | ||
"debug_id": "e2623c4d-79c5-3cdf-90ab-2cf44e026bdd", | ||
"arch": "arm64", | ||
"image_addr": "0x100304000", | ||
"image_size": 802816, | ||
"type": "macho", | ||
}, | ||
{ | ||
"code_file": "/System/Library/PrivateFrameworks/UIKitCore.framework/UIKitCore", | ||
"debug_id": "b0858d8e-7220-37bf-873f-ecc2b0a358c3", | ||
"arch": "arm64e", | ||
"image_addr": "0x1a4e8f000", | ||
"image_size": 25309184, | ||
"image_vmaddr": "0x188ff7000", | ||
"type": "macho", | ||
}, | ||
{ | ||
"code_file": "/System/Library/Frameworks/CFNetwork.framework/CFNetwork", | ||
"debug_id": "b2273be9-538a-3f56-b9c7-801f39550f58", | ||
"arch": "arm64e", | ||
"image_addr": "0x1a3e32000", | ||
"image_size": 3977216, | ||
"image_vmaddr": "0x187f9a000", | ||
"in_app": False, | ||
"type": "macho", | ||
}, | ||
] | ||
}, | ||
"environment": "test-app", | ||
"sdk": { | ||
"name": "sentry.cocoa", | ||
"version": "8.1.0", | ||
"integrations": [ | ||
"Crash", | ||
"PerformanceTracking", | ||
"MetricKit", | ||
"WatchdogTerminationTracking", | ||
"ViewHierarchy", | ||
"NetworkTracking", | ||
"ANRTracking", | ||
"AutoBreadcrumbTracking", | ||
"FramesTracking", | ||
"AppStartTracking", | ||
"Screenshot", | ||
"FileIOTracking", | ||
"UIEventTracking", | ||
"AutoSessionTracking", | ||
"CoreDataTracking", | ||
"PreWarmedAppStartTracing", | ||
], | ||
}, | ||
"threads": { | ||
"values": [ | ||
{ | ||
"id": 0, | ||
"stacktrace": { | ||
"frames": [ | ||
{ | ||
"function": "<redacted>", | ||
"in_app": False, | ||
"data": {"symbolicator_status": "unknown_image"}, | ||
"image_addr": "0x0", | ||
"instruction_addr": "0x1129be52e", | ||
"symbol_addr": "0x0", | ||
}, | ||
{ | ||
"function": "<redacted>", | ||
"in_app": False, | ||
"data": {"symbolicator_status": "unknown_image"}, | ||
"image_addr": "0x0", | ||
"instruction_addr": "0x104405f21", | ||
"symbol_addr": "0x0", | ||
}, | ||
], | ||
}, | ||
"raw_stacktrace": { | ||
"frames": [ | ||
{ | ||
"function": "<redacted>", | ||
"in_app": False, | ||
"image_addr": "0x0", | ||
"instruction_addr": "0x1129be52e", | ||
"symbol_addr": "0x0", | ||
}, | ||
{ | ||
"function": "<redacted>", | ||
"in_app": False, | ||
"image_addr": "0x0", | ||
"instruction_addr": "0x104405f21", | ||
"symbol_addr": "0x0", | ||
}, | ||
], | ||
}, | ||
"crashed": True, | ||
} | ||
] | ||
}, | ||
"user": { | ||
"id": "803F5C87-0F8B-41C7-8499-27BD71A92738", | ||
"ip_address": "192.168.0.1", | ||
"geo": {"country_code": "US", "region": "United States"}, | ||
}, | ||
"logger": "my.logger.name", | ||
} | ||
result.update(kwargs) | ||
return result |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
from sentry.utils.sdk_crashes.sdk_crash_detector import SDKCrashDetector | ||
|
||
|
||
class CocoaSDKCrashDetector(SDKCrashDetector): | ||
def is_sdk_crash(self) -> bool: | ||
return True | ||
|
||
def is_sdk_frame(self) -> bool: | ||
return True |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,73 @@ | ||
from __future__ import annotations | ||
|
||
from typing import Any, Mapping, Optional | ||
|
||
from sentry.eventstore.models import Event | ||
from sentry.issues.grouptype import GroupCategory | ||
from sentry.utils.safe import get_path, set_path | ||
from sentry.utils.sdk_crashes.cocoa_sdk_crash_detector import CocoaSDKCrashDetector | ||
from sentry.utils.sdk_crashes.sdk_crash_detector import SDKCrashDetector | ||
|
||
|
||
class SDKCrashReporter: | ||
def report(self, event_data: Mapping[str, Any], event_project_id: int) -> Event: | ||
from sentry.event_manager import EventManager | ||
|
||
manager = EventManager(dict(event_data)) | ||
manager.normalize() | ||
return manager.save(project_id=event_project_id) | ||
|
||
|
||
class SDKCrashDetection: | ||
""" | ||
Placeholder class for SDK crash detection. | ||
""" | ||
def __init__( | ||
self, | ||
sdk_crash_reporter: SDKCrashReporter, | ||
sdk_crash_detector: SDKCrashDetector, | ||
): | ||
self.sdk_crash_reporter = sdk_crash_reporter | ||
self.cocoa_sdk_crash_detector = sdk_crash_detector | ||
|
||
def detect_sdk_crash(self, event: Event, event_project_id: int) -> Optional[Event]: | ||
should_detect_sdk_crash = ( | ||
event.group | ||
and event.group.issue_category == GroupCategory.ERROR | ||
and event.group.platform == "cocoa" | ||
) | ||
if not should_detect_sdk_crash: | ||
return None | ||
|
||
context = get_path(event.data, "contexts", "sdk_crash_detection") | ||
if context is not None and context.get("detected", False): | ||
return None | ||
|
||
# Getting the frames and checking if the event is unhandled might different per platform. | ||
# We will change this once we implement this for more platforms. | ||
is_unhandled = ( | ||
get_path(event.data, "exception", "values", -1, "mechanism", "data", "handled") is False | ||
) | ||
if is_unhandled is False: | ||
return None | ||
|
||
frames = get_path(event.data, "exception", "values", -1, "stacktrace", "frames") | ||
if not frames: | ||
return None | ||
|
||
# We still need to pass in the frames to validate it's an unhandled event coming from the Cocoa SDK. | ||
# We will do this in a separate PR. | ||
if self.cocoa_sdk_crash_detector.is_sdk_crash(): | ||
# We still need to strip event data for to avoid collecting PII. We will do this in a separate PR. | ||
sdk_crash_event_data = event.data | ||
|
||
set_path( | ||
sdk_crash_event_data, "contexts", "sdk_crash_detection", value={"detected": True} | ||
) | ||
|
||
return self.sdk_crash_reporter.report(sdk_crash_event_data, event_project_id) | ||
|
||
def detect_sdk_crash(self): | ||
return None | ||
|
||
|
||
sdk_crash_detection = SDKCrashDetection() | ||
_crash_reporter = SDKCrashReporter() | ||
_cocoa_sdk_crash_detector = CocoaSDKCrashDetector() | ||
|
||
sdk_crash_detection = SDKCrashDetection(_crash_reporter, _cocoa_sdk_crash_detector) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
from abc import ABC, abstractmethod | ||
|
||
|
||
class SDKCrashDetector(ABC): | ||
@abstractmethod | ||
def is_sdk_crash(self) -> bool: | ||
""" | ||
Returns true if the stacktrace stems from an SDK crash. | ||
:param frames: The stacktrace frames ordered from newest to oldest. | ||
""" | ||
raise NotImplementedError | ||
|
||
@abstractmethod | ||
def is_sdk_frame(self) -> bool: | ||
raise NotImplementedError |
Oops, something went wrong.