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: Restart replay session with mobile session #4085

Merged
merged 14 commits into from
Jun 20, 2024
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

### Features

- Restart replay session with mobile session (#4085)

### Fixes

- Fix potential deadlock in app hang detection (#4063)
Expand Down
5 changes: 4 additions & 1 deletion Sentry.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -902,6 +902,7 @@
D8C66A372A77B1F70015696A /* SentryPropagationContext.m in Sources */ = {isa = PBXBuildFile; fileRef = D8C66A352A77B1F70015696A /* SentryPropagationContext.m */; };
D8C67E9B28000E24007E326E /* SentryUIApplication.h in Headers */ = {isa = PBXBuildFile; fileRef = D8C67E9928000E23007E326E /* SentryUIApplication.h */; };
D8C67E9C28000E24007E326E /* SentryScreenshot.h in Headers */ = {isa = PBXBuildFile; fileRef = D8C67E9A28000E23007E326E /* SentryScreenshot.h */; };
D8CA12952C203E71005894F4 /* SentrySessionListener.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8CA12942C203E71005894F4 /* SentrySessionListener.swift */; };
D8CAC02E2BA0663E00E38F34 /* SentryReplayOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8CAC02A2BA0663E00E38F34 /* SentryReplayOptions.swift */; };
D8CAC02F2BA0663E00E38F34 /* SentryVideoInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8CAC02B2BA0663E00E38F34 /* SentryVideoInfo.swift */; };
D8CAC0412BA0984500E38F34 /* SentryIntegrationProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8CAC0402BA0984500E38F34 /* SentryIntegrationProtocol.swift */; };
Expand Down Expand Up @@ -1972,6 +1973,7 @@
D8C66A352A77B1F70015696A /* SentryPropagationContext.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryPropagationContext.m; sourceTree = "<group>"; };
D8C67E9928000E23007E326E /* SentryUIApplication.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SentryUIApplication.h; path = include/SentryUIApplication.h; sourceTree = "<group>"; };
D8C67E9A28000E23007E326E /* SentryScreenshot.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SentryScreenshot.h; path = include/SentryScreenshot.h; sourceTree = "<group>"; };
D8CA12942C203E71005894F4 /* SentrySessionListener.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentrySessionListener.swift; sourceTree = "<group>"; };
D8CAC02A2BA0663E00E38F34 /* SentryReplayOptions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SentryReplayOptions.swift; sourceTree = "<group>"; };
D8CAC02B2BA0663E00E38F34 /* SentryVideoInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SentryVideoInfo.swift; sourceTree = "<group>"; };
D8CAC0402BA0984500E38F34 /* SentryIntegrationProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryIntegrationProtocol.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -3601,7 +3603,6 @@
D861301B2BB5A267004C0F5E /* SentrySessionReplayTests.swift */,
D8AFC0002BD252B900118BE1 /* SentryOnDemandReplayTests.swift */,
D82DD1CC2BEEB1A0001AB556 /* SentrySRDefaultBreadcrumbConverterTests.swift */,
D82DD1CC2BEEB1A0001AB556 /* SentryBreadcrumbReplayConverterTests.swift */,
D8DBE0C92C0E093000FAB1FD /* SentryTouchTrackerTests.swift */,
D8DBE0D12C0EFFC300FAB1FD /* SentryReplayOptionsTests.swift */,
);
Expand Down Expand Up @@ -3896,6 +3897,7 @@
D8F016B22B9622D6007B9AFB /* SentryId.swift */,
D8CAC0402BA0984500E38F34 /* SentryIntegrationProtocol.swift */,
D87C89022BC43C9C0086C7DF /* SentryRedactOptions.swift */,
D8CA12942C203E71005894F4 /* SentrySessionListener.swift */,
);
path = Protocol;
sourceTree = "<group>";
Expand Down Expand Up @@ -4536,6 +4538,7 @@
63FE713B20DA4C1100CDBAE8 /* SentryCrashFileUtils.c in Sources */,
63FE716920DA4C1100CDBAE8 /* SentryCrashStackCursor.c in Sources */,
7BA61CCA247D128B00C130A8 /* SentryThreadInspector.m in Sources */,
D8CA12952C203E71005894F4 /* SentrySessionListener.swift in Sources */,
63FE718D20DA4C1100CDBAE8 /* SentryCrashReportStore.c in Sources */,
62A2F43E2BA9AC10000C9FDD /* DistributionMetric.swift in Sources */,
7BA0C0482805600A003E0326 /* SentryTransportAdapter.m in Sources */,
Expand Down
2 changes: 1 addition & 1 deletion SentryTestUtils/SentryTestUtils-ObjC-BridgingHeader.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
#import "SentrySDK+Private.h"
#import "SentrySDK+Tests.h"
#import "SentryScopeSyncC.h"
#import "SentrySession.h"

#import "SentrySwizzleWrapper.h"
#import "SentrySystemWrapper.h"
#import "SentryThreadInspector.h"
Expand Down
1 change: 1 addition & 0 deletions SentryTestUtils/TestTransportAdapter.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Foundation
import _SentryPrivate

public class TestTransportAdapter: SentryTransportAdapter {
public override func send(_ event: Event, session: SentrySession, attachments: [Attachment]) {
Expand Down
26 changes: 26 additions & 0 deletions Sources/Sentry/SentryHub.m
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
@property (nonatomic, strong) SentryCrashWrapper *crashWrapper;
@property (nonatomic, strong) NSMutableSet<NSString *> *installedIntegrationNames;
@property (nonatomic) NSUInteger errorsBeforeSession;
@property (nonatomic) NSMutableArray<id<SentrySessionListener>> * sessionListeners;
brustolin marked this conversation as resolved.
Show resolved Hide resolved

@end

Expand Down Expand Up @@ -77,6 +78,7 @@ - (instancetype)initWithClient:(nullable SentryClient *)client
_installedIntegrationNames = [[NSMutableSet alloc] init];
_crashWrapper = [SentryCrashWrapper sharedInstance];
_errorsBeforeSession = 0;
_sessionListeners = [NSMutableArray array];

[SentryDependencyContainer.sharedInstance.crashWrapper enrichScope:scope];
}
Expand Down Expand Up @@ -107,6 +109,7 @@ - (void)startSession
andLevel:kSentryLevelError];
return;
}
NSArray<id<SentrySessionListener>>* listeners;
@synchronized(_sessionLock) {
if (_session != nil) {
lastSession = _session;
Expand All @@ -129,10 +132,15 @@ - (void)startSession

[self storeCurrentSession:_session];
[self captureSession:_session];
listeners = [_sessionListeners copy];
brustolin marked this conversation as resolved.
Show resolved Hide resolved
}
[lastSession
endSessionExitedWithTimestamp:[SentryDependencyContainer.sharedInstance.dateProvider date]];
[self captureSession:lastSession];

for (id<SentrySessionListener> listener in listeners) {
[listener sentrySessionStarted:_session];
}
}

- (void)endSession
Expand All @@ -143,11 +151,13 @@ - (void)endSession
- (void)endSessionWithTimestamp:(NSDate *)timestamp
{
SentrySession *currentSession = nil;
NSArray<id<SentrySessionListener>>* listeners;
@synchronized(_sessionLock) {
currentSession = _session;
_session = nil;
_errorsBeforeSession = 0;
[self deleteCurrentSession];
listeners = [_sessionListeners copy];
}

if (currentSession == nil) {
Expand All @@ -156,6 +166,10 @@ - (void)endSessionWithTimestamp:(NSDate *)timestamp
}
[currentSession endSessionExitedWithTimestamp:timestamp];
[self captureSession:currentSession];

for (id<SentrySessionListener> listener in listeners) {
[listener sentrySessionEnded:currentSession];
}
}

- (void)storeCurrentSession:(SentrySession *)session
Expand Down Expand Up @@ -755,6 +769,18 @@ - (LocalMetricsAggregator *_Nullable)getLocalMetricsAggregatorWithSpan:(id<Sentr
return nil;
}

- (void)registerSessionListener:(id<SentrySessionListener>)listener {
@synchronized (_sessionLock) {
[_sessionListeners addObject:listener];
}
}

- (void)unregisterSessionListener:(id<SentrySessionListener>)listener {
@synchronized (_sessionLock) {
[_sessionListeners removeObject:listener];
}
}

#pragma mark - Protected

- (NSMutableArray<NSString *> *)trimmedInstalledIntegrationNames
Expand Down
3 changes: 3 additions & 0 deletions Sources/Sentry/SentrySessionReplay.m
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,9 @@ - (void)startFullReplay
- (void)stop
{
@synchronized(self) {
if (_isRunning == NO) {
return;
}
[_displayLink invalidate];
_isRunning = NO;
[self prepareSegmentUntil:_dateProvider.date];
Expand Down
171 changes: 92 additions & 79 deletions Sources/Sentry/SentrySessionReplayIntegration.m
Original file line number Diff line number Diff line change
Expand Up @@ -44,38 +44,48 @@ - (BOOL)installWithOptions:(nonnull SentryOptions *)options
if ([super installWithOptions:options] == NO) {
return NO;
}

_replayOptions = options.experimental.sessionReplay;

if (options.enableSwizzling) {
_touchTracker = [[SentryTouchTracker alloc]
initWithDateProvider:SentryDependencyContainer.sharedInstance.dateProvider
scale:options.experimental.sessionReplay.sizeScale];
[self swizzleApplicationTouch];
}

[SentrySDK.currentHub registerSessionListener:self];

[SentryGlobalEventProcessor.shared
addEventProcessor:^SentryEvent *_Nullable(SentryEvent *_Nonnull event) {
[self.sessionReplay captureReplayForEvent:event];
return event;
}];

return YES;
}

if (@available(iOS 16.0, tvOS 16.0, *)) {
_replayOptions = options.experimental.sessionReplay;

_startedAsFullSession = [self shouldReplayFullSession:_replayOptions.sessionSampleRate];

if (!_startedAsFullSession && _replayOptions.errorSampleRate == 0) {
return NO;
}

if (options.enableSwizzling) {
_touchTracker = [[SentryTouchTracker alloc]
initWithDateProvider:SentryDependencyContainer.sharedInstance.dateProvider
scale:options.experimental.sessionReplay.sizeScale];
[self swizzleApplicationTouch];
}

if (SentryDependencyContainer.sharedInstance.application.windows.count > 0) {
// If a window its already available start replay right away
[self startWithOptions:_replayOptions fullSession:_startedAsFullSession];
} else {
// Wait for a scene to be available to started the replay
- (void)startSession {
_startedAsFullSession = [self shouldReplayFullSession:_replayOptions.sessionSampleRate];

if (!_startedAsFullSession && _replayOptions.errorSampleRate == 0) {
return;
}

if (SentryDependencyContainer.sharedInstance.application.windows.count > 0) {
// If a window its already available start replay right away
[self startWithOptions:_replayOptions fullSession:_startedAsFullSession];
} else {
// Wait for a scene to be available to started the replay
if (@available(iOS 13.0, *)) {
[SentryDependencyContainer.sharedInstance.notificationCenterWrapper
addObserver:self
selector:@selector(newSceneActivate)
name:UISceneDidActivateNotification];
addObserver:self
selector:@selector(newSceneActivate)
name:UISceneDidActivateNotification];
}

return YES;
} else {
return NO;
}

return;
brustolin marked this conversation as resolved.
Show resolved Hide resolved
}

- (void)newSceneActivate
Expand All @@ -98,58 +108,49 @@ - (void)startWithOptions:(SentryReplayOptions *)replayOptions
breadcrumbConverter:(id<SentryReplayBreadcrumbConverter>)breadcrumbConverter
fullSession:(BOOL)shouldReplayFullSession
{
if (@available(iOS 16.0, tvOS 16.0, *)) {
NSURL *docs = [NSURL
fileURLWithPath:[SentryDependencyContainer.sharedInstance.fileManager sentryPath]];
docs = [docs URLByAppendingPathComponent:SENTRY_REPLAY_FOLDER];
NSString *currentSession = [NSUUID UUID].UUIDString;
docs = [docs URLByAppendingPathComponent:currentSession];

if (![NSFileManager.defaultManager fileExistsAtPath:docs.path]) {
[NSFileManager.defaultManager createDirectoryAtURL:docs
withIntermediateDirectories:YES
attributes:nil
error:nil];
}

SentryOnDemandReplay *replayMaker =
[[SentryOnDemandReplay alloc] initWithOutputPath:docs.path];
replayMaker.bitRate = replayOptions.replayBitRate;
replayMaker.cacheMaxSize
= (NSInteger)(shouldReplayFullSession ? replayOptions.sessionSegmentDuration
: replayOptions.errorReplayDuration);

self.sessionReplay = [[SentrySessionReplay alloc]
initWithSettings:replayOptions
replayFolderPath:docs
screenshotProvider:screenshotProvider
replayMaker:replayMaker
breadcrumbConverter:breadcrumbConverter
touchTracker:_touchTracker
dateProvider:SentryDependencyContainer.sharedInstance.dateProvider
random:SentryDependencyContainer.sharedInstance.random
displayLinkWrapper:[[SentryDisplayLinkWrapper alloc] init]];

[self.sessionReplay
start:SentryDependencyContainer.sharedInstance.application.windows.firstObject
fullSession:[self shouldReplayFullSession:replayOptions.sessionSampleRate]];

[NSNotificationCenter.defaultCenter addObserver:self
selector:@selector(stop)
name:UIApplicationDidEnterBackgroundNotification
object:nil];

[NSNotificationCenter.defaultCenter addObserver:self
selector:@selector(resume)
name:UIApplicationWillEnterForegroundNotification
object:nil];

[SentryGlobalEventProcessor.shared
addEventProcessor:^SentryEvent *_Nullable(SentryEvent *_Nonnull event) {
[self.sessionReplay captureReplayForEvent:event];
return event;
}];
NSURL *docs =
[NSURL fileURLWithPath:[SentryDependencyContainer.sharedInstance.fileManager sentryPath]];
docs = [docs URLByAppendingPathComponent:SENTRY_REPLAY_FOLDER];
NSString *currentSession = [NSUUID UUID].UUIDString;
docs = [docs URLByAppendingPathComponent:currentSession];

if (![NSFileManager.defaultManager fileExistsAtPath:docs.path]) {
[NSFileManager.defaultManager createDirectoryAtURL:docs
withIntermediateDirectories:YES
attributes:nil
error:nil];
}

SentryOnDemandReplay *replayMaker = [[SentryOnDemandReplay alloc] initWithOutputPath:docs.path];
replayMaker.bitRate = replayOptions.replayBitRate;
replayMaker.cacheMaxSize
= (NSInteger)(shouldReplayFullSession ? replayOptions.sessionSegmentDuration
: replayOptions.errorReplayDuration);

self.sessionReplay = [[SentrySessionReplay alloc]
initWithSettings:replayOptions
replayFolderPath:docs
screenshotProvider:screenshotProvider
replayMaker:replayMaker
breadcrumbConverter:breadcrumbConverter
touchTracker:_touchTracker
dateProvider:SentryDependencyContainer.sharedInstance.dateProvider
random:SentryDependencyContainer.sharedInstance.random
displayLinkWrapper:[[SentryDisplayLinkWrapper alloc] init]];

[self.sessionReplay
start:SentryDependencyContainer.sharedInstance.application.windows.firstObject
fullSession:[self shouldReplayFullSession:replayOptions.sessionSampleRate]];

[NSNotificationCenter.defaultCenter addObserver:self
brustolin marked this conversation as resolved.
Show resolved Hide resolved
selector:@selector(stop)
name:UIApplicationDidEnterBackgroundNotification
object:nil];

[NSNotificationCenter.defaultCenter addObserver:self
selector:@selector(resume)
name:UIApplicationWillEnterForegroundNotification
object:nil];
}

- (void)stop
Expand All @@ -162,6 +163,17 @@ - (void)resume
[self.sessionReplay resume];
}

- (void)sentrySessionEnded:(SentrySession *)session {
[self stop];
[NSNotificationCenter.defaultCenter removeObserver:self];
brustolin marked this conversation as resolved.
Show resolved Hide resolved
_sessionReplay = nil;
}

- (void)sentrySessionStarted:(SentrySession *)session {
if (_sessionReplay) { return; }
[self startSession];
}

- (void)captureReplay
{
[self.sessionReplay captureReplay];
Expand All @@ -186,6 +198,7 @@ - (SentryIntegrationOption)integrationOptions

- (void)uninstall
{
[SentrySDK.currentHub unregisterSessionListener:self];
brustolin marked this conversation as resolved.
Show resolved Hide resolved
_touchTracker = nil;
[self stop];
}
Expand Down
4 changes: 4 additions & 0 deletions Sources/Sentry/include/SentryHub+Private.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
@class SentryReplayEvent;
@class SentryReplayRecording;
@protocol SentryIntegrationProtocol;
@protocol SentrySessionListener;

NS_ASSUME_NONNULL_BEGIN

Expand Down Expand Up @@ -62,6 +63,9 @@ SentryHub ()

- (void)captureEnvelope:(SentryEnvelope *)envelope;

- (void)registerSessionListener:(id<SentrySessionListener>)listener;
- (void)unregisterSessionListener:(id<SentrySessionListener>)listener;

@end

NS_ASSUME_NONNULL_END
1 change: 1 addition & 0 deletions Sources/Sentry/include/SentryPrivate.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@
#import "SentryDateUtil.h"
#import "SentryLevelHelper.h"
#import "SentrySdkInfo.h"
#import "SentrySession.h"
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
@class SentrySessionReplay;

@interface
SentrySessionReplayIntegration () <SentryIntegrationProtocol>
SentrySessionReplayIntegration () <SentryIntegrationProtocol, SentrySessionListener>

@property (nonatomic, strong) SentrySessionReplay *sessionReplay;

Expand Down
Loading