Skip to content

Commit ab0012c

Browse files
ref: Add UIEventTracker Mode (#3406)
Carrier transactions are going to replace UI event transactions in the future. When users enable carrier transactions, UI event transactions must be disabled because both would interfere. Therefore, prepare the SentryUIEventTracker to be able to handle two different modes—one for the existing functionality of UI event transactions and one for adding spans to carrier transactions.
1 parent 44ce888 commit ab0012c

File tree

9 files changed

+197
-100
lines changed

9 files changed

+197
-100
lines changed

Sentry.xcodeproj/project.pbxproj

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,9 @@
8484
62885DA729E946B100554F38 /* TestConncurrentModifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62885DA629E946B100554F38 /* TestConncurrentModifications.swift */; };
8585
62950F1029E7FE0100A42624 /* SentryTransactionContextTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62950F0F29E7FE0100A42624 /* SentryTransactionContextTests.swift */; };
8686
629690532AD3E060000185FA /* SentryReachabilitySwiftTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 629690522AD3E060000185FA /* SentryReachabilitySwiftTests.swift */; };
87+
62A456E12B03704A003F19A1 /* SentryUIEventTrackerMode.h in Headers */ = {isa = PBXBuildFile; fileRef = 62A456E02B03704A003F19A1 /* SentryUIEventTrackerMode.h */; };
88+
62A456E32B0370AA003F19A1 /* SentryUIEventTrackerTransactionMode.h in Headers */ = {isa = PBXBuildFile; fileRef = 62A456E22B0370AA003F19A1 /* SentryUIEventTrackerTransactionMode.h */; };
89+
62A456E52B0370E0003F19A1 /* SentryUIEventTrackerTransactionMode.m in Sources */ = {isa = PBXBuildFile; fileRef = 62A456E42B0370E0003F19A1 /* SentryUIEventTrackerTransactionMode.m */; };
8790
62B86CFC29F052BB008F3947 /* SentryTestLogConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = 62B86CFB29F052BB008F3947 /* SentryTestLogConfig.m */; };
8891
62E081A929ED4260000F69FC /* SentryBreadcrumbDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 62E081A829ED4260000F69FC /* SentryBreadcrumbDelegate.h */; };
8992
62E081AB29ED4322000F69FC /* SentryBreadcrumbTestDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E081AA29ED4322000F69FC /* SentryBreadcrumbTestDelegate.swift */; };
@@ -965,6 +968,9 @@
965968
62885DA629E946B100554F38 /* TestConncurrentModifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestConncurrentModifications.swift; sourceTree = "<group>"; };
966969
62950F0F29E7FE0100A42624 /* SentryTransactionContextTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryTransactionContextTests.swift; sourceTree = "<group>"; };
967970
629690522AD3E060000185FA /* SentryReachabilitySwiftTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryReachabilitySwiftTests.swift; sourceTree = "<group>"; };
971+
62A456E02B03704A003F19A1 /* SentryUIEventTrackerMode.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryUIEventTrackerMode.h; path = include/SentryUIEventTrackerMode.h; sourceTree = "<group>"; };
972+
62A456E22B0370AA003F19A1 /* SentryUIEventTrackerTransactionMode.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryUIEventTrackerTransactionMode.h; path = include/SentryUIEventTrackerTransactionMode.h; sourceTree = "<group>"; };
973+
62A456E42B0370E0003F19A1 /* SentryUIEventTrackerTransactionMode.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryUIEventTrackerTransactionMode.m; sourceTree = "<group>"; };
968974
62B86CFB29F052BB008F3947 /* SentryTestLogConfig.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryTestLogConfig.m; sourceTree = "<group>"; };
969975
62E081A829ED4260000F69FC /* SentryBreadcrumbDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryBreadcrumbDelegate.h; path = include/SentryBreadcrumbDelegate.h; sourceTree = "<group>"; };
970976
62E081AA29ED4322000F69FC /* SentryBreadcrumbTestDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryBreadcrumbTestDelegate.swift; sourceTree = "<group>"; };
@@ -2643,6 +2649,9 @@
26432649
7B63459A280EB9E200CFA05A /* SentryUIEventTrackingIntegration.m */,
26442650
7B63459C280EBA6300CFA05A /* SentryUIEventTracker.h */,
26452651
7B63459E280EBA7200CFA05A /* SentryUIEventTracker.m */,
2652+
62A456E02B03704A003F19A1 /* SentryUIEventTrackerMode.h */,
2653+
62A456E22B0370AA003F19A1 /* SentryUIEventTrackerTransactionMode.h */,
2654+
62A456E42B0370E0003F19A1 /* SentryUIEventTrackerTransactionMode.m */,
26462655
);
26472656
name = UIEvents;
26482657
sourceTree = "<group>";
@@ -3579,6 +3588,7 @@
35793588
7B31C291277B04A000337126 /* SentryCrashPlatformSpecificDefines.h in Headers */,
35803589
7B77BE3527EC8445003C9020 /* SentryDiscardReasonMapper.h in Headers */,
35813590
7B610D602512390E00B0B5D9 /* SentrySDK+Private.h in Headers */,
3591+
62A456E12B03704A003F19A1 /* SentryUIEventTrackerMode.h in Headers */,
35823592
03F84D2327DD414C008FE43F /* SentryThreadHandle.hpp in Headers */,
35833593
7B6C5EE0264E8E050010D138 /* SentryFramesTracker.h in Headers */,
35843594
63FE715720DA4C1100CDBAE8 /* SentryCrashThread.h in Headers */,
@@ -3698,6 +3708,7 @@
36983708
7BA61CB9247BC57B00C130A8 /* SentryCrashDefaultBinaryImageProvider.h in Headers */,
36993709
8E4E7C7D25DAB287006AB9E2 /* SentryTracer.h in Headers */,
37003710
7BC8523724588115005A70F0 /* SentryDataCategory.h in Headers */,
3711+
62A456E32B0370AA003F19A1 /* SentryUIEventTrackerTransactionMode.h in Headers */,
37013712
63FE714B20DA4C1100CDBAE8 /* SentryCrashString.h in Headers */,
37023713
7BCFBD6D2681D0A900BC27D8 /* SentryCrashScopeObserver.h in Headers */,
37033714
63FE715320DA4C1100CDBAE8 /* SentryCrashObjCApple.h in Headers */,
@@ -4135,6 +4146,7 @@
41354146
7D65260E237F649E00113EA2 /* SentryScope.m in Sources */,
41364147
84281C472A57905700EE88F2 /* SentrySample.m in Sources */,
41374148
84AC61D329F7541E009EEF61 /* SentryDispatchSourceWrapper.m in Sources */,
4149+
62A456E52B0370E0003F19A1 /* SentryUIEventTrackerTransactionMode.m in Sources */,
41384150
63FE712D20DA4C1100CDBAE8 /* SentryCrashJSONCodecObjC.m in Sources */,
41394151
7BBD18932449BEDD00427C76 /* SentryDefaultRateLimits.m in Sources */,
41404152
7BD729982463E93500EA3610 /* SentryDateUtil.m in Sources */,

Sources/Sentry/SentryUIEventTracker.m

Lines changed: 10 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,9 @@
44

55
# import "SentrySwizzleWrapper.h"
66
# import <SentryDependencyContainer.h>
7-
# import <SentryHub+Private.h>
87
# import <SentryLog.h>
9-
# import <SentrySDK+Private.h>
10-
# import <SentrySDK.h>
11-
# import <SentryScope.h>
12-
# import <SentrySpanId.h>
138
# import <SentrySpanOperations.h>
14-
# import <SentrySpanProtocol.h>
15-
# import <SentryTraceOrigins.h>
16-
# import <SentryTracer.h>
17-
# import <SentryTransactionContext+Private.h>
9+
# import <SentryUIEventTrackerMode.h>
1810

1911
NS_ASSUME_NONNULL_BEGIN
2012

@@ -24,21 +16,16 @@
2416
@interface
2517
SentryUIEventTracker ()
2618

27-
@property (nonatomic, strong) SentryDispatchQueueWrapper *dispatchQueueWrapper;
28-
@property (nonatomic, assign) NSTimeInterval idleTimeout;
29-
@property (nullable, nonatomic, strong) NSMutableArray<SentryTracer *> *activeTransactions;
19+
@property (nonatomic, strong) id<SentryUIEventTrackerMode> uiEventTrackerMode;
3020

3121
@end
3222

3323
@implementation SentryUIEventTracker
3424

35-
- (instancetype)initWithDispatchQueueWrapper:(SentryDispatchQueueWrapper *)dispatchQueueWrapper
36-
idleTimeout:(NSTimeInterval)idleTimeout
25+
- (instancetype)initWithMode:(id<SentryUIEventTrackerMode>)mode
3726
{
3827
if (self = [super init]) {
39-
self.dispatchQueueWrapper = dispatchQueueWrapper;
40-
self.idleTimeout = idleTimeout;
41-
self.activeTransactions = [NSMutableArray new];
28+
self.uiEventTrackerMode = mode;
4229
}
4330
return self;
4431
}
@@ -83,87 +70,18 @@ - (void)sendActionCallback:(NSString *)action
8370
return;
8471
}
8572

86-
NSString *transactionName = [self getTransactionName:action target:targetClass];
87-
88-
// There might be more active transactions stored, but only the last one might still be
89-
// active with a timeout. The others are already waiting for their children to finish
90-
// without a timeout.
91-
SentryTracer *currentActiveTransaction;
92-
@synchronized(self.activeTransactions) {
93-
currentActiveTransaction = self.activeTransactions.lastObject;
94-
}
95-
96-
BOOL sameAction =
97-
[currentActiveTransaction.transactionContext.name isEqualToString:transactionName];
98-
if (sameAction) {
99-
SENTRY_LOG_DEBUG(@"Dispatching idle timeout for transaction with span id %@",
100-
currentActiveTransaction.spanId.sentrySpanIdString);
101-
[currentActiveTransaction dispatchIdleTimeout];
102-
return;
103-
}
104-
105-
[currentActiveTransaction finish];
106-
107-
if (currentActiveTransaction) {
108-
SENTRY_LOG_DEBUG(@"SentryUIEventTracker finished transaction %@ (span ID %@)",
109-
currentActiveTransaction.transactionContext.name,
110-
currentActiveTransaction.spanId.sentrySpanIdString);
111-
}
112-
73+
NSString *actionName = [self getTransactionName:action target:targetClass];
11374
NSString *operation = [self getOperation:sender];
11475

115-
SentryTransactionContext *context =
116-
[[SentryTransactionContext alloc] initWithName:transactionName
117-
nameSource:kSentryTransactionNameSourceComponent
118-
operation:operation
119-
origin:SentryTraceOriginUIEventTracker];
120-
121-
__block SentryTracer *transaction;
122-
[SentrySDK.currentHub.scope useSpan:^(id<SentrySpan> _Nullable span) {
123-
BOOL ongoingScreenLoadTransaction
124-
= span != nil && [span.operation isEqualToString:SentrySpanOperationUILoad];
125-
BOOL ongoingManualTransaction = span != nil
126-
&& ![span.operation isEqualToString:SentrySpanOperationUILoad]
127-
&& ![span.operation containsString:SentrySpanOperationUIAction];
128-
129-
BOOL bindToScope = !ongoingScreenLoadTransaction && !ongoingManualTransaction;
130-
131-
transaction = [SentrySDK.currentHub
132-
startTransactionWithContext:context
133-
bindToScope:bindToScope
134-
customSamplingContext:@{}
135-
configuration:[SentryTracerConfiguration configurationWithBlock:^(
136-
SentryTracerConfiguration *config) {
137-
config.idleTimeout = self.idleTimeout;
138-
config.waitForChildren = YES;
139-
config.dispatchQueueWrapper = self.dispatchQueueWrapper;
140-
}]];
141-
142-
SENTRY_LOG_DEBUG(@"SentryUIEventTracker automatically started a new transaction with name: "
143-
@"%@, bindToScope: %@",
144-
transactionName, bindToScope ? @"YES" : @"NO");
145-
}];
146-
76+
NSString *accessibilityIdentifier = nil;
14777
if ([[sender class] isSubclassOfClass:[UIView class]]) {
14878
UIView *view = sender;
149-
if (view.accessibilityIdentifier) {
150-
[transaction setTagValue:view.accessibilityIdentifier
151-
forKey:@"accessibilityIdentifier"];
152-
}
79+
accessibilityIdentifier = view.accessibilityIdentifier;
15380
}
15481

155-
transaction.finishCallback = ^(SentryTracer *tracer) {
156-
@synchronized(self.activeTransactions) {
157-
[self.activeTransactions removeObject:tracer];
158-
SENTRY_LOG_DEBUG(@"Active transactions after removing tracer for span ID %@: %@",
159-
tracer.spanId.sentrySpanIdString, self.activeTransactions);
160-
}
161-
};
162-
@synchronized(self.activeTransactions) {
163-
SENTRY_LOG_DEBUG(@"Adding transaction %@ to list of active transactions (currently %@)",
164-
transaction.spanId.sentrySpanIdString, self.activeTransactions);
165-
[self.activeTransactions addObject:transaction];
166-
}
82+
[self.uiEventTrackerMode handleUIEvent:actionName
83+
operation:operation
84+
accessibilityIdentifier:accessibilityIdentifier];
16785
}
16886

16987
- (void)stop
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
#import <SentryUIEventTrackerTransactionMode.h>
2+
3+
#if SENTRY_HAS_UIKIT
4+
5+
# import <SentryDependencyContainer.h>
6+
# import <SentryHub+Private.h>
7+
# import <SentryLog.h>
8+
# import <SentrySDK+Private.h>
9+
# import <SentrySDK.h>
10+
# import <SentryScope.h>
11+
# import <SentrySpanId.h>
12+
# import <SentrySpanOperations.h>
13+
# import <SentryTraceOrigins.h>
14+
# import <SentryTracer.h>
15+
# import <SentryTransactionContext+Private.h>
16+
17+
NS_ASSUME_NONNULL_BEGIN
18+
19+
@interface
20+
SentryUIEventTrackerTransactionMode ()
21+
22+
@property (nonatomic, strong) SentryDispatchQueueWrapper *dispatchQueueWrapper;
23+
@property (nonatomic, assign) NSTimeInterval idleTimeout;
24+
@property (nullable, nonatomic, strong) NSMutableArray<SentryTracer *> *activeTransactions;
25+
26+
@end
27+
28+
@implementation SentryUIEventTrackerTransactionMode
29+
30+
- (instancetype)initWithDispatchQueueWrapper:(SentryDispatchQueueWrapper *)dispatchQueueWrapper
31+
idleTimeout:(NSTimeInterval)idleTimeout
32+
{
33+
if (self = [super init]) {
34+
self.dispatchQueueWrapper = dispatchQueueWrapper;
35+
self.idleTimeout = idleTimeout;
36+
self.activeTransactions = [NSMutableArray new];
37+
}
38+
return self;
39+
}
40+
41+
- (void)handleUIEvent:(NSString *)action
42+
operation:(NSString *)operation
43+
accessibilityIdentifier:(NSString *)accessibilityIdentifier
44+
{
45+
46+
// There might be more active transactions stored, but only the last one might still be
47+
// active with a timeout. The others are already waiting for their children to finish
48+
// without a timeout.
49+
SentryTracer *currentActiveTransaction;
50+
@synchronized(self.activeTransactions) {
51+
currentActiveTransaction = self.activeTransactions.lastObject;
52+
}
53+
54+
BOOL sameAction = [currentActiveTransaction.transactionContext.name isEqualToString:action];
55+
if (sameAction) {
56+
SENTRY_LOG_DEBUG(@"Dispatching idle timeout for transaction with span id %@",
57+
currentActiveTransaction.spanId.sentrySpanIdString);
58+
[currentActiveTransaction dispatchIdleTimeout];
59+
return;
60+
}
61+
62+
[currentActiveTransaction finish];
63+
64+
if (currentActiveTransaction) {
65+
SENTRY_LOG_DEBUG(@"Finished transaction %@ (span ID %@)",
66+
currentActiveTransaction.transactionContext.name,
67+
currentActiveTransaction.spanId.sentrySpanIdString);
68+
}
69+
70+
SentryTransactionContext *context =
71+
[[SentryTransactionContext alloc] initWithName:action
72+
nameSource:kSentryTransactionNameSourceComponent
73+
operation:operation
74+
origin:SentryTraceOriginUIEventTracker];
75+
76+
__block SentryTracer *transaction;
77+
[SentrySDK.currentHub.scope useSpan:^(id<SentrySpan> _Nullable span) {
78+
BOOL ongoingScreenLoadTransaction
79+
= span != nil && [span.operation isEqualToString:SentrySpanOperationUILoad];
80+
BOOL ongoingManualTransaction = span != nil
81+
&& ![span.operation isEqualToString:SentrySpanOperationUILoad]
82+
&& ![span.operation containsString:SentrySpanOperationUIAction];
83+
84+
BOOL bindToScope = !ongoingScreenLoadTransaction && !ongoingManualTransaction;
85+
86+
transaction = [SentrySDK.currentHub
87+
startTransactionWithContext:context
88+
bindToScope:bindToScope
89+
customSamplingContext:@{}
90+
configuration:[SentryTracerConfiguration configurationWithBlock:^(
91+
SentryTracerConfiguration *config) {
92+
config.idleTimeout = self.idleTimeout;
93+
config.waitForChildren = YES;
94+
config.dispatchQueueWrapper = self.dispatchQueueWrapper;
95+
}]];
96+
97+
SENTRY_LOG_DEBUG(@"Automatically started a new transaction with name: "
98+
@"%@, bindToScope: %@",
99+
action, bindToScope ? @"YES" : @"NO");
100+
}];
101+
102+
if (accessibilityIdentifier) {
103+
[transaction setTagValue:accessibilityIdentifier forKey:@"accessibilityIdentifier"];
104+
}
105+
106+
transaction.finishCallback = ^(SentryTracer *tracer) {
107+
@synchronized(self.activeTransactions) {
108+
[self.activeTransactions removeObject:tracer];
109+
SENTRY_LOG_DEBUG(@"Active transactions after removing tracer for span ID %@: %@",
110+
tracer.spanId.sentrySpanIdString, self.activeTransactions);
111+
}
112+
};
113+
@synchronized(self.activeTransactions) {
114+
SENTRY_LOG_DEBUG(@"Adding transaction %@ to list of active transactions (currently %@)",
115+
transaction.spanId.sentrySpanIdString, self.activeTransactions);
116+
[self.activeTransactions addObject:transaction];
117+
}
118+
}
119+
120+
@end
121+
122+
NS_ASSUME_NONNULL_END
123+
124+
#endif // SENTRY_HAS_UIKIT

Sources/Sentry/SentryUIEventTrackingIntegration.m

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
# import <SentryOptions+Private.h>
99
# import <SentryOptions.h>
1010
# import <SentryUIEventTracker.h>
11+
# import <SentryUIEventTrackerTransactionMode.h>
1112

1213
@interface
1314
SentryUIEventTrackingIntegration ()
@@ -25,9 +26,11 @@ - (BOOL)installWithOptions:(SentryOptions *)options
2526
}
2627

2728
SentryDependencyContainer *dependencies = [SentryDependencyContainer sharedInstance];
28-
self.uiEventTracker =
29-
[[SentryUIEventTracker alloc] initWithDispatchQueueWrapper:dependencies.dispatchQueueWrapper
30-
idleTimeout:options.idleTimeout];
29+
SentryUIEventTrackerTransactionMode *mode = [[SentryUIEventTrackerTransactionMode alloc]
30+
initWithDispatchQueueWrapper:dependencies.dispatchQueueWrapper
31+
idleTimeout:options.idleTimeout];
32+
33+
self.uiEventTracker = [[SentryUIEventTracker alloc] initWithMode:mode];
3134

3235
[self.uiEventTracker start];
3336

Sources/Sentry/include/SentryUIEventTracker.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@
55
NS_ASSUME_NONNULL_BEGIN
66

77
@class SentryDispatchQueueWrapper;
8+
@protocol SentryUIEventTrackerMode;
89

910
@interface SentryUIEventTracker : NSObject
1011
SENTRY_NO_INIT
1112

12-
- (instancetype)initWithDispatchQueueWrapper:(SentryDispatchQueueWrapper *)dispatchQueueWrapper
13-
idleTimeout:(NSTimeInterval)idleTimeout;
13+
- (instancetype)initWithMode:(id<SentryUIEventTrackerMode>)mode;
1414

1515
- (void)start;
1616
- (void)stop;
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#import "SentryDefines.h"
2+
3+
#if SENTRY_HAS_UIKIT
4+
5+
NS_ASSUME_NONNULL_BEGIN
6+
7+
@protocol SentryUIEventTrackerMode <NSObject>
8+
9+
- (void)handleUIEvent:(NSString *)action
10+
operation:(NSString *)operation
11+
accessibilityIdentifier:(NSString *)accessibilityIdentifier;
12+
13+
@end
14+
15+
NS_ASSUME_NONNULL_END
16+
17+
#endif // SENTRY_HAS_UIKIT
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#import "SentryUIEventTrackerMode.h"
2+
3+
#if SENTRY_HAS_UIKIT
4+
5+
@class SentryDispatchQueueWrapper;
6+
7+
NS_ASSUME_NONNULL_BEGIN
8+
9+
@interface SentryUIEventTrackerTransactionMode : NSObject <SentryUIEventTrackerMode>
10+
SENTRY_NO_INIT
11+
12+
- (instancetype)initWithDispatchQueueWrapper:(SentryDispatchQueueWrapper *)dispatchQueueWrapper
13+
idleTimeout:(NSTimeInterval)idleTimeout;
14+
15+
@end
16+
17+
NS_ASSUME_NONNULL_END
18+
19+
#endif // SENTRY_HAS_UIKIT

0 commit comments

Comments
 (0)