Skip to content

Commit bfb4bcf

Browse files
m1entusbrustolinphilipphofmann
authored
feat: add Sentry screenName tracking (#4646)
Added UIViewController custom screenName tracking ref: #4642 Co-authored-by: Dhiogo Brustolin <dhiogorb@gmail.com> Co-authored-by: Philipp Hofmann <ph.hofmann@pm.me>
1 parent e2484ac commit bfb4bcf

File tree

10 files changed

+104
-12
lines changed

10 files changed

+104
-12
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Changelog
22

3+
## Unreleased
4+
5+
### Features
6+
7+
- Add protocol for custom screenName for UIViewControllers (#4646)
8+
39
## 8.43.1-beta.0
410

511
### Fixes

Sentry.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,7 @@
324324
63FE722220DA66EC00CDBAE8 /* SentryCrashJSONCodec_Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 63FE71F920DA66EB00CDBAE8 /* SentryCrashJSONCodec_Tests.m */; };
325325
63FE722420DA66EC00CDBAE8 /* SentryCrashMonitor_NSException_Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 63FE71FB20DA66EB00CDBAE8 /* SentryCrashMonitor_NSException_Tests.m */; };
326326
63FE722520DA66EC00CDBAE8 /* SentryCrashFileUtils_Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 63FE71FC20DA66EB00CDBAE8 /* SentryCrashFileUtils_Tests.m */; };
327+
64F9571D2D12DA1A00324652 /* SentryViewControllerBreadcrumbTracking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64F9571C2D12DA1800324652 /* SentryViewControllerBreadcrumbTracking.swift */; };
327328
69BEE6F72620729E006DF9DF /* UrlSessionDelegateSpy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69BEE6F62620729E006DF9DF /* UrlSessionDelegateSpy.swift */; };
328329
71F116E8F40D530BB68A2987 /* SentryCrashUUIDConversion.h in Headers */ = {isa = PBXBuildFile; fileRef = 71F11CDEF5952DF5CC69AC74 /* SentryCrashUUIDConversion.h */; };
329330
7B0002322477F0520035FEF1 /* SentrySessionTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 7B0002312477F0520035FEF1 /* SentrySessionTests.m */; };
@@ -1348,6 +1349,7 @@
13481349
63FE71F920DA66EB00CDBAE8 /* SentryCrashJSONCodec_Tests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SentryCrashJSONCodec_Tests.m; sourceTree = "<group>"; };
13491350
63FE71FB20DA66EB00CDBAE8 /* SentryCrashMonitor_NSException_Tests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SentryCrashMonitor_NSException_Tests.m; sourceTree = "<group>"; };
13501351
63FE71FC20DA66EB00CDBAE8 /* SentryCrashFileUtils_Tests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SentryCrashFileUtils_Tests.m; sourceTree = "<group>"; };
1352+
64F9571C2D12DA1800324652 /* SentryViewControllerBreadcrumbTracking.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryViewControllerBreadcrumbTracking.swift; sourceTree = "<group>"; };
13511353
69BEE6F62620729E006DF9DF /* UrlSessionDelegateSpy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UrlSessionDelegateSpy.swift; sourceTree = "<group>"; };
13521354
71F11CDEF5952DF5CC69AC74 /* SentryCrashUUIDConversion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SentryCrashUUIDConversion.h; sourceTree = "<group>"; };
13531355
7B0002312477F0520035FEF1 /* SentrySessionTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentrySessionTests.m; sourceTree = "<group>"; };
@@ -3994,6 +3996,7 @@
39943996
D8F016B12B9622B7007B9AFB /* Protocol */ = {
39953997
isa = PBXGroup;
39963998
children = (
3999+
64F9571C2D12DA1800324652 /* SentryViewControllerBreadcrumbTracking.swift */,
39974000
D8F016B22B9622D6007B9AFB /* SentryId.swift */,
39984001
D8CAC0402BA0984500E38F34 /* SentryIntegrationProtocol.swift */,
39994002
D87C89022BC43C9C0086C7DF /* SentryRedactOptions.swift */,
@@ -4787,6 +4790,7 @@
47874790
15360CCF2432777500112302 /* SentrySessionTracker.m in Sources */,
47884791
6334314320AD9AE40077E581 /* SentryMechanism.m in Sources */,
47894792
849B8F9C2C6E906900148E1F /* SentryUserFeedbackThemeConfiguration.swift in Sources */,
4793+
64F9571D2D12DA1A00324652 /* SentryViewControllerBreadcrumbTracking.swift in Sources */,
47904794
63FE70D320DA4C1000CDBAE8 /* SentryCrashMonitor_AppState.c in Sources */,
47914795
849B8F9D2C6E906900148E1F /* SentryUserFeedbackWidgetConfiguration.swift in Sources */,
47924796
639FCFA51EBC809A00778193 /* SentryStacktrace.m in Sources */,

Sources/Sentry/SentryBreadcrumbTracker.m

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,7 @@ + (NSDictionary *)fetchInfoAboutViewController:(UIViewController *)controller
304304
{
305305
NSMutableDictionary *info = @{}.mutableCopy;
306306

307-
info[@"screen"] = [SwiftDescriptor getObjectClassName:controller];
307+
info[@"screen"] = [SwiftDescriptor getViewControllerClassName:controller];
308308

309309
if ([controller.navigationItem.title length] != 0) {
310310
info[@"title"] = controller.navigationItem.title;
@@ -316,12 +316,12 @@ + (NSDictionary *)fetchInfoAboutViewController:(UIViewController *)controller
316316

317317
if (controller.presentingViewController != nil) {
318318
info[@"presentingViewController"] =
319-
[SwiftDescriptor getObjectClassName:controller.presentingViewController];
319+
[SwiftDescriptor getViewControllerClassName:controller.presentingViewController];
320320
}
321321

322322
if (controller.parentViewController != nil) {
323323
info[@"parentViewController"] =
324-
[SwiftDescriptor getObjectClassName:controller.parentViewController];
324+
[SwiftDescriptor getViewControllerClassName:controller.parentViewController];
325325
}
326326

327327
if (controller.view.window != nil) {
@@ -335,6 +335,7 @@ + (NSDictionary *)fetchInfoAboutViewController:(UIViewController *)controller
335335

336336
return info;
337337
}
338+
338339
#endif // SENTRY_HAS_UIKIT
339340

340341
@end

Sources/Sentry/SentryTimeToDisplayTracker.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ - (instancetype)initForController:(UIViewController *)controller
4444
dispatchQueueWrapper:(SentryDispatchQueueWrapper *)dispatchQueueWrapper
4545
{
4646
if (self = [super init]) {
47-
_controllerName = [SwiftDescriptor getObjectClassName:controller];
47+
_controllerName = [SwiftDescriptor getViewControllerClassName:controller];
4848
_waitForFullDisplay = waitForFullDisplay;
4949
_dispatchQueueWrapper = dispatchQueueWrapper;
5050
_initialDisplayReported = NO;

Sources/Sentry/SentryUIApplication.m

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -122,12 +122,12 @@ - (UIApplication *)sharedApplication
122122

123123
[SentryDependencyContainer.sharedInstance.dispatchQueueWrapper
124124
dispatchSyncOnMainQueue:^{
125-
NSArray *viewControllers
125+
NSArray<UIViewController *> *viewControllers
126126
= SentryDependencyContainer.sharedInstance.application.relevantViewControllers;
127127
NSMutableArray *vcsNames =
128128
[[NSMutableArray alloc] initWithCapacity:viewControllers.count];
129-
for (id vc in viewControllers) {
130-
[vcsNames addObject:[SwiftDescriptor getObjectClassName:vc]];
129+
for (UIViewController *vc in viewControllers) {
130+
[vcsNames addObject:[SwiftDescriptor getViewControllerClassName:vc]];
131131
}
132132
result = [NSArray arrayWithArray:vcsNames];
133133
}

Sources/Sentry/SentryUIViewControllerPerformanceTracker.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ - (void)startRootSpanFor:(UIViewController *)controller
126126
[self.currentTTDTracker finishSpansIfNotFinished];
127127
}
128128

129-
NSString *name = [SwiftDescriptor getObjectClassName:controller];
129+
NSString *name = [SwiftDescriptor getViewControllerClassName:controller];
130130
spanId = [self.tracker startSpanWithName:name
131131
nameSource:kSentryTransactionNameSourceComponent
132132
operation:SentrySpanOperationUILoad

Sources/Sentry/SentryViewHierarchy.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ - (int)viewHierarchyFromView:(UIView *)view intoContext:(SentryCrashJSONEncodeCo
144144
UIViewController *vc = (UIViewController *)view.nextResponder;
145145
if (vc.view == view) {
146146
const char *viewControllerClassName =
147-
[[SwiftDescriptor getObjectClassName:vc] UTF8String];
147+
[[SwiftDescriptor getViewControllerClassName:vc] UTF8String];
148148
tryJson(sentrycrashjson_addStringElement(context, "view_controller",
149149
viewControllerClassName, SentryCrashJSON_SIZE_AUTOMATIC));
150150
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import Foundation
2+
3+
///
4+
/// Use this protocol to customize the name used in the automatic
5+
/// UIViewController performance tracker, view hierarchy, and breadcrumbs.
6+
///
7+
@objc
8+
public protocol SentryUIViewControllerDescriptor: NSObjectProtocol {
9+
10+
/// The custom name of the UIViewController
11+
/// that the Sentry SDK uses for transaction names, breadcrumbs, and
12+
/// view hierarchy.
13+
var sentryName: String { get }
14+
}

Sources/Swift/SwiftDescriptor.swift

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,28 @@
11
import Foundation
22

3+
#if canImport(UIKit) && !SENTRY_NO_UIKIT
4+
import UIKit
5+
#endif
6+
37
@objc
48
class SwiftDescriptor: NSObject {
59

610
@objc
711
static func getObjectClassName(_ object: AnyObject) -> String {
812
return String(describing: type(of: object))
913
}
10-
14+
15+
/// UIViewControllers aren't available on watchOS
16+
#if canImport(UIKit) && !os(watchOS) && !SENTRY_NO_UIKIT
17+
@objc
18+
static func getViewControllerClassName(_ object: UIViewController) -> String {
19+
if let object = object as? SentryUIViewControllerDescriptor {
20+
return object.sentryName
21+
}
22+
return getObjectClassName(object)
23+
}
24+
#endif
25+
1126
@objc
1227
static func getSwiftErrorDescription(_ error: Error) -> String? {
1328
return String(describing: error)

Tests/SentryTests/Integrations/Breadcrumbs/SentryBreadcrumbTrackerTests.swift

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,45 @@ class SentryBreadcrumbTrackerTests: XCTestCase {
287287

288288
XCTAssertEqual(crumbData["accessibilityIdentifier"] as? String, "TestAccessibilityIdentifier")
289289
}
290-
290+
291+
func testBreadcrumbViewControllerCustomScreenName() throws {
292+
let testReachability = TestSentryReachability()
293+
SentryDependencyContainer.sharedInstance().reachability = testReachability
294+
295+
let scope = Scope()
296+
let client = TestClient(options: Options())
297+
let hub = TestHub(client: client, andScope: scope)
298+
SentrySDK.setCurrentHub(hub)
299+
300+
let sut = SentryBreadcrumbTracker()
301+
sut.start(with: delegate)
302+
sut.startSwizzle()
303+
304+
let parentScreenName = UUID().uuidString
305+
let screenName = UUID().uuidString
306+
let title = UUID().uuidString
307+
308+
let parentController = CustomScreenNameViewController(sentryName: parentScreenName)
309+
let viewController = CustomScreenNameViewController(sentryName: screenName)
310+
parentController.addChild(viewController)
311+
viewController.title = title
312+
313+
viewController.viewDidAppear(false)
314+
315+
let crumbs = delegate.addCrumbInvocations.invocations
316+
317+
// one breadcrumb for starting the tracker, one for the first reachability breadcrumb and one final one for the swizzled viewDidAppear
318+
guard crumbs.count == 2 else {
319+
XCTFail("Expected exactly 2 breadcrumbs, got: \(crumbs)")
320+
return
321+
}
322+
323+
let lifeCycleCrumb = try XCTUnwrap(crumbs.element(at: 1))
324+
XCTAssertEqual(screenName, lifeCycleCrumb.data?["screen"] as? String)
325+
XCTAssertEqual(title, lifeCycleCrumb.data?["title"] as? String)
326+
XCTAssertEqual(parentScreenName, lifeCycleCrumb.data?["parentViewController"] as? String)
327+
}
328+
291329
private class TestEvent: UIEvent {
292330
let touchedView: UIView?
293331
class TestEndTouch: UITouch {
@@ -307,7 +345,21 @@ class SentryBreadcrumbTrackerTests: XCTestCase {
307345
TestEndTouch(touchedView: touchedView)
308346
] }
309347
}
310-
348+
349+
fileprivate final class CustomScreenNameViewController: UIViewController, SentryUIViewControllerDescriptor {
350+
351+
fileprivate required init?(coder: NSCoder) {
352+
fatalError("init(coder:) has not been implemented")
353+
}
354+
355+
fileprivate var sentryName: String
356+
357+
fileprivate init(sentryName: String) {
358+
self.sentryName = sentryName
359+
super.init(nibName: nil, bundle: nil)
360+
}
361+
}
362+
311363
#endif
312364

313365
}

0 commit comments

Comments
 (0)