Skip to content
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: add Sentry screenName tracking #4646

Merged
merged 21 commits into from
Jan 13, 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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## Unreleased

### Features

- Add protocol for custom screenName for UIViewControllers (#4646)

## 8.43.1-beta.0

### Fixes
Expand Down
4 changes: 4 additions & 0 deletions Sentry.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,7 @@
63FE722220DA66EC00CDBAE8 /* SentryCrashJSONCodec_Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 63FE71F920DA66EB00CDBAE8 /* SentryCrashJSONCodec_Tests.m */; };
63FE722420DA66EC00CDBAE8 /* SentryCrashMonitor_NSException_Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 63FE71FB20DA66EB00CDBAE8 /* SentryCrashMonitor_NSException_Tests.m */; };
63FE722520DA66EC00CDBAE8 /* SentryCrashFileUtils_Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 63FE71FC20DA66EB00CDBAE8 /* SentryCrashFileUtils_Tests.m */; };
64F9571D2D12DA1A00324652 /* SentryViewControllerBreadcrumbTracking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64F9571C2D12DA1800324652 /* SentryViewControllerBreadcrumbTracking.swift */; };
69BEE6F72620729E006DF9DF /* UrlSessionDelegateSpy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69BEE6F62620729E006DF9DF /* UrlSessionDelegateSpy.swift */; };
71F116E8F40D530BB68A2987 /* SentryCrashUUIDConversion.h in Headers */ = {isa = PBXBuildFile; fileRef = 71F11CDEF5952DF5CC69AC74 /* SentryCrashUUIDConversion.h */; };
7B0002322477F0520035FEF1 /* SentrySessionTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 7B0002312477F0520035FEF1 /* SentrySessionTests.m */; };
Expand Down Expand Up @@ -1348,6 +1349,7 @@
63FE71F920DA66EB00CDBAE8 /* SentryCrashJSONCodec_Tests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SentryCrashJSONCodec_Tests.m; sourceTree = "<group>"; };
63FE71FB20DA66EB00CDBAE8 /* SentryCrashMonitor_NSException_Tests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SentryCrashMonitor_NSException_Tests.m; sourceTree = "<group>"; };
63FE71FC20DA66EB00CDBAE8 /* SentryCrashFileUtils_Tests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SentryCrashFileUtils_Tests.m; sourceTree = "<group>"; };
64F9571C2D12DA1800324652 /* SentryViewControllerBreadcrumbTracking.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryViewControllerBreadcrumbTracking.swift; sourceTree = "<group>"; };
69BEE6F62620729E006DF9DF /* UrlSessionDelegateSpy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UrlSessionDelegateSpy.swift; sourceTree = "<group>"; };
71F11CDEF5952DF5CC69AC74 /* SentryCrashUUIDConversion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SentryCrashUUIDConversion.h; sourceTree = "<group>"; };
7B0002312477F0520035FEF1 /* SentrySessionTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentrySessionTests.m; sourceTree = "<group>"; };
Expand Down Expand Up @@ -3994,6 +3996,7 @@
D8F016B12B9622B7007B9AFB /* Protocol */ = {
isa = PBXGroup;
children = (
64F9571C2D12DA1800324652 /* SentryViewControllerBreadcrumbTracking.swift */,
D8F016B22B9622D6007B9AFB /* SentryId.swift */,
D8CAC0402BA0984500E38F34 /* SentryIntegrationProtocol.swift */,
D87C89022BC43C9C0086C7DF /* SentryRedactOptions.swift */,
Expand Down Expand Up @@ -4787,6 +4790,7 @@
15360CCF2432777500112302 /* SentrySessionTracker.m in Sources */,
6334314320AD9AE40077E581 /* SentryMechanism.m in Sources */,
849B8F9C2C6E906900148E1F /* SentryUserFeedbackThemeConfiguration.swift in Sources */,
64F9571D2D12DA1A00324652 /* SentryViewControllerBreadcrumbTracking.swift in Sources */,
63FE70D320DA4C1000CDBAE8 /* SentryCrashMonitor_AppState.c in Sources */,
849B8F9D2C6E906900148E1F /* SentryUserFeedbackWidgetConfiguration.swift in Sources */,
639FCFA51EBC809A00778193 /* SentryStacktrace.m in Sources */,
Expand Down
7 changes: 4 additions & 3 deletions Sources/Sentry/SentryBreadcrumbTracker.m
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@
{
NSMutableDictionary *info = @{}.mutableCopy;

info[@"screen"] = [SwiftDescriptor getObjectClassName:controller];
info[@"screen"] = [SwiftDescriptor getViewControllerClassName:controller];

if ([controller.navigationItem.title length] != 0) {
info[@"title"] = controller.navigationItem.title;
Expand All @@ -316,12 +316,12 @@

if (controller.presentingViewController != nil) {
info[@"presentingViewController"] =
[SwiftDescriptor getObjectClassName:controller.presentingViewController];
[SwiftDescriptor getViewControllerClassName:controller.presentingViewController];

Check warning on line 319 in Sources/Sentry/SentryBreadcrumbTracker.m

View check run for this annotation

Codecov / codecov/patch

Sources/Sentry/SentryBreadcrumbTracker.m#L319

Added line #L319 was not covered by tests
}

if (controller.parentViewController != nil) {
info[@"parentViewController"] =
[SwiftDescriptor getObjectClassName:controller.parentViewController];
[SwiftDescriptor getViewControllerClassName:controller.parentViewController];
}

if (controller.view.window != nil) {
Expand All @@ -335,6 +335,7 @@

return info;
}

#endif // SENTRY_HAS_UIKIT

@end
Expand Down
2 changes: 1 addition & 1 deletion Sources/Sentry/SentryTimeToDisplayTracker.m
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ - (instancetype)initForController:(UIViewController *)controller
dispatchQueueWrapper:(SentryDispatchQueueWrapper *)dispatchQueueWrapper
{
if (self = [super init]) {
_controllerName = [SwiftDescriptor getObjectClassName:controller];
_controllerName = [SwiftDescriptor getViewControllerClassName:controller];
_waitForFullDisplay = waitForFullDisplay;
_dispatchQueueWrapper = dispatchQueueWrapper;
_initialDisplayReported = NO;
Expand Down
6 changes: 3 additions & 3 deletions Sources/Sentry/SentryUIApplication.m
Original file line number Diff line number Diff line change
Expand Up @@ -122,12 +122,12 @@ - (UIApplication *)sharedApplication

[SentryDependencyContainer.sharedInstance.dispatchQueueWrapper
dispatchSyncOnMainQueue:^{
NSArray *viewControllers
NSArray<UIViewController *> *viewControllers
= SentryDependencyContainer.sharedInstance.application.relevantViewControllers;
NSMutableArray *vcsNames =
[[NSMutableArray alloc] initWithCapacity:viewControllers.count];
for (id vc in viewControllers) {
[vcsNames addObject:[SwiftDescriptor getObjectClassName:vc]];
for (UIViewController *vc in viewControllers) {
[vcsNames addObject:[SwiftDescriptor getViewControllerClassName:vc]];
}
result = [NSArray arrayWithArray:vcsNames];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ - (void)startRootSpanFor:(UIViewController *)controller
[self.currentTTDTracker finishSpansIfNotFinished];
}

NSString *name = [SwiftDescriptor getObjectClassName:controller];
NSString *name = [SwiftDescriptor getViewControllerClassName:controller];
spanId = [self.tracker startSpanWithName:name
nameSource:kSentryTransactionNameSourceComponent
operation:SentrySpanOperationUILoad
Expand Down
2 changes: 1 addition & 1 deletion Sources/Sentry/SentryViewHierarchy.m
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ - (int)viewHierarchyFromView:(UIView *)view intoContext:(SentryCrashJSONEncodeCo
UIViewController *vc = (UIViewController *)view.nextResponder;
if (vc.view == view) {
const char *viewControllerClassName =
[[SwiftDescriptor getObjectClassName:vc] UTF8String];
[[SwiftDescriptor getViewControllerClassName:vc] UTF8String];
tryJson(sentrycrashjson_addStringElement(context, "view_controller",
viewControllerClassName, SentryCrashJSON_SIZE_AUTOMATIC));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import Foundation

///
/// Use this protocol to customize the name used in the automatic
/// UIViewController performance tracker, view hierarchy, and breadcrumbs.
///
@objc
public protocol SentryUIViewControllerDescriptor: NSObjectProtocol {

/// The custom name of the UIViewController
/// that the Sentry SDK uses for transaction names, breadcrumbs, and
/// view hierarchy.
var sentryName: String { get }
}
17 changes: 16 additions & 1 deletion Sources/Swift/SwiftDescriptor.swift
Original file line number Diff line number Diff line change
@@ -1,13 +1,28 @@
import Foundation

#if canImport(UIKit) && !SENTRY_NO_UIKIT
import UIKit
#endif

@objc
class SwiftDescriptor: NSObject {

@objc
static func getObjectClassName(_ object: AnyObject) -> String {
return String(describing: type(of: object))
}


/// UIViewControllers aren't available on watchOS
#if canImport(UIKit) && !os(watchOS) && !SENTRY_NO_UIKIT
@objc
static func getViewControllerClassName(_ object: UIViewController) -> String {
if let object = object as? SentryUIViewControllerDescriptor {
return object.sentryName
}
return getObjectClassName(object)
}
#endif

@objc
static func getSwiftErrorDescription(_ error: Error) -> String? {
return String(describing: error)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,45 @@

XCTAssertEqual(crumbData["accessibilityIdentifier"] as? String, "TestAccessibilityIdentifier")
}


func testBreadcrumbViewControllerCustomScreenName() throws {
let testReachability = TestSentryReachability()
SentryDependencyContainer.sharedInstance().reachability = testReachability

let scope = Scope()
let client = TestClient(options: Options())
let hub = TestHub(client: client, andScope: scope)
SentrySDK.setCurrentHub(hub)

let sut = SentryBreadcrumbTracker()
sut.start(with: delegate)
sut.startSwizzle()

let parentScreenName = UUID().uuidString
let screenName = UUID().uuidString
let title = UUID().uuidString

let parentController = CustomScreenNameViewController(sentryName: parentScreenName)
let viewController = CustomScreenNameViewController(sentryName: screenName)
parentController.addChild(viewController)
viewController.title = title

viewController.viewDidAppear(false)

let crumbs = delegate.addCrumbInvocations.invocations

// one breadcrumb for starting the tracker, one for the first reachability breadcrumb and one final one for the swizzled viewDidAppear
guard crumbs.count == 2 else {
XCTFail("Expected exactly 2 breadcrumbs, got: \(crumbs)")
return

Check warning on line 320 in Tests/SentryTests/Integrations/Breadcrumbs/SentryBreadcrumbTrackerTests.swift

View check run for this annotation

Codecov / codecov/patch

Tests/SentryTests/Integrations/Breadcrumbs/SentryBreadcrumbTrackerTests.swift#L319-L320

Added lines #L319 - L320 were not covered by tests
}

let lifeCycleCrumb = try XCTUnwrap(crumbs.element(at: 1))
XCTAssertEqual(screenName, lifeCycleCrumb.data?["screen"] as? String)
XCTAssertEqual(title, lifeCycleCrumb.data?["title"] as? String)
XCTAssertEqual(parentScreenName, lifeCycleCrumb.data?["parentViewController"] as? String)
}

private class TestEvent: UIEvent {
let touchedView: UIView?
class TestEndTouch: UITouch {
Expand All @@ -307,7 +345,21 @@
TestEndTouch(touchedView: touchedView)
] }
}


fileprivate final class CustomScreenNameViewController: UIViewController, SentryUIViewControllerDescriptor {

fileprivate required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

Check warning on line 353 in Tests/SentryTests/Integrations/Breadcrumbs/SentryBreadcrumbTrackerTests.swift

View check run for this annotation

Codecov / codecov/patch

Tests/SentryTests/Integrations/Breadcrumbs/SentryBreadcrumbTrackerTests.swift#L353

Added line #L353 was not covered by tests

fileprivate var sentryName: String

fileprivate init(sentryName: String) {
self.sentryName = sentryName
super.init(nibName: nil, bundle: nil)
}
}

#endif

}
Loading