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: Session Replay #3625

Merged
merged 37 commits into from
Apr 11, 2024
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
0f3280e
Update project.pbxproj
brustolin Feb 9, 2024
0e524b5
feat: Add Session Replay
brustolin Feb 12, 2024
cd816d5
Update CHANGELOG.md
brustolin Feb 12, 2024
3f6f035
Merge branch 'main' into feat/session-replay
brustolin Feb 14, 2024
5780b82
Merge branch 'main' into feat/session-replay
brustolin Feb 21, 2024
88c7547
feat(Session Replay): ReplayEvent, ReplayRecording and Envelope handl…
brustolin Feb 29, 2024
2c6fff0
Feat(Session Replay): Replay Options (#3674)
brustolin Feb 29, 2024
90e6582
ref(Session Replay):Replay recording serialization (#3677)
brustolin Mar 1, 2024
9bcf1e7
feat(Session Replay): Prepare Session event (#3693)
brustolin Mar 5, 2024
48bf981
Merge branch 'main' into feat/session-replay
brustolin Mar 5, 2024
0a08f99
Fixing Merge
brustolin Mar 5, 2024
639e9fc
Format code
getsentry-bot Mar 5, 2024
283b2d6
Merge branch 'main' into feat/session-replay
brustolin Mar 12, 2024
73cbdd9
format
brustolin Mar 12, 2024
8a4584b
Format code
getsentry-bot Mar 12, 2024
d23eefa
Update SentryReplayEvent.m
brustolin Mar 12, 2024
e79bb50
Merge branch 'feat/session-replay' of github.com:getsentry/sentry-coc…
brustolin Mar 14, 2024
87d0133
merge
brustolin Mar 14, 2024
b5ee0f3
feat(Session Replay): Session Replay Integration (#3671)
brustolin Apr 10, 2024
9d2607f
merge
brustolin Apr 10, 2024
0e07787
Update CHANGELOG.md
brustolin Apr 10, 2024
952c50e
Update project.pbxproj
brustolin Apr 10, 2024
5b26000
Merge branch 'main' into feat/session-replay
brustolin Apr 11, 2024
82381df
test fix
brustolin Apr 11, 2024
14347e8
Apply suggestions from code review
brustolin Apr 11, 2024
29754df
Merge branch 'feat/session-replay' of github.com:getsentry/sentry-coc…
brustolin Apr 11, 2024
0e14417
Format code
getsentry-bot Apr 11, 2024
bee6391
Fix tests
brustolin Apr 11, 2024
3bef548
Update SentryClientTests.swift
brustolin Apr 11, 2024
1c8eff4
Merge branch 'feat/session-replay' of github.com:getsentry/sentry-coc…
brustolin Apr 11, 2024
0e6e6b2
Update SentryPrivate.h
brustolin Apr 11, 2024
fe454d1
Update CHANGELOG.md
brustolin Apr 11, 2024
913958d
Update CHANGELOG.md
brustolin Apr 11, 2024
ef3b147
comments
brustolin Apr 11, 2024
ecaba29
Merge branch 'feat/session-replay' of github.com:getsentry/sentry-coc…
brustolin Apr 11, 2024
b46380f
Merge branch 'main' into feat/session-replay
brustolin Apr 11, 2024
35e59ae
Update CHANGELOG.md
brustolin Apr 11, 2024
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- Add timing API for Metrics (#3812):
- Add [rate limiting](https://develop.sentry.dev/sdk/rate-limiting/) for Metrics (#3838)
- Data normalization for Metrics (#3843)
- Add Session Replay (#3625)

## 8.23.0

Expand Down
10 changes: 7 additions & 3 deletions Samples/iOS-Swift/iOS-Swift/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,17 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
let dsn = DSNStorage.shared.getDSN() ?? AppDelegate.defaultDSN
DSNStorage.shared.saveDSN(dsn: dsn)

SentrySDK.start { options in
SentrySDK.start(configureOptions: { options in
options.dsn = dsn
options.beforeSend = { event in
return event
}
options.debug = true

if #available(iOS 16.0, *) {
options.experimental.sessionReplay = SentryReplayOptions(sessionSampleRate: 1, errorSampleRate: 1, redactAllText: false, redactAllImages: true)
}

if #available(iOS 15.0, *) {
options.enableMetricKit = true
}
Expand Down Expand Up @@ -60,7 +64,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
options.sessionTrackingIntervalMillis = 5_000
options.attachScreenshot = true
options.attachViewHierarchy = true

#if targetEnvironment(simulator)
options.enableSpotlight = true
options.environment = "test-app"
Expand Down Expand Up @@ -130,7 +134,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
}
return scope
}
}
})

SentrySDK.metrics.increment(key: "app.start", value: 1.0, tags: ["view": "app-delegate"])

Expand Down
6 changes: 4 additions & 2 deletions Sentry.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ Pod::Spec.new do |s|

s.subspec 'Core' do |sp|
sp.source_files = "Sources/Sentry/**/*.{h,hpp,m,mm,c,cpp}",
"Sources/SentryCrash/**/*.{h,hpp,m,mm,c,cpp}", "Sources/Swift/**/*.{swift,h,hpp,m,mm,c,cpp}",
"Sources/SentryCrash/**/*.{h,hpp,m,mm,c,cpp}", "Sources/Swift/**/*.{swift,h,hpp,m,mm,c,cpp}"
sp.preserve_path = "Sources/Sentry/include/module.modulemap"
brustolin marked this conversation as resolved.
Show resolved Hide resolved
sp.public_header_files =
"Sources/Sentry/Public/*.h"

Expand All @@ -43,7 +44,8 @@ Pod::Spec.new do |s|
s.subspec 'HybridSDK' do |sp|
sp.source_files = "Sources/Sentry/**/*.{h,hpp,m,mm,c,cpp}",
"Sources/SentryCrash/**/*.{h,hpp,m,mm,c,cpp}", "Sources/Swift/**/*.{swift,h,hpp,m,mm,c,cpp}"


sp.preserve_path = "Sources/Sentry/include/module.modulemap"
sp.public_header_files =
"Sources/Sentry/Public/*.h", "Sources/Sentry/include/HybridPublic/*.h"

Expand Down
158 changes: 150 additions & 8 deletions Sentry.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions SentryTestUtils/TestCurrentDateProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ public class TestCurrentDateProvider: SentryCurrentDateProvider {
setDate(date: date().addingTimeInterval(TimeInterval(nanoseconds) / 1e9))
internalSystemTime += nanoseconds
}

public func advanceBy(interval: TimeInterval) {
setDate(date: date().addingTimeInterval(interval))
}

public var timezoneOffsetValue = 0
public override func timezoneOffset() -> Int {
Expand Down
1 change: 1 addition & 0 deletions SentryTestUtils/TestTransport.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import _SentryPrivate
import Foundation

@objc
Expand Down
3 changes: 3 additions & 0 deletions Sources/Configuration/SentryNoUI.xcconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#include "Sentry.xcconfig"

OTHER_SWIFT_FLAGS = -DSENTRY_NO_UIKIT
brustolin marked this conversation as resolved.
Show resolved Hide resolved
11 changes: 10 additions & 1 deletion Sources/Sentry/Public/SentryOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@

NS_ASSUME_NONNULL_BEGIN

@class SentryDsn, SentryMeasurementValue, SentryHttpStatusCodeRange, SentryScope;
@class SentryDsn, SentryMeasurementValue, SentryHttpStatusCodeRange, SentryScope,
SentryReplayOptions;
@class SentryExperimentalOptions;

NS_SWIFT_NAME(Options)
@interface SentryOptions : NSObject
Expand Down Expand Up @@ -269,6 +271,7 @@ NS_SWIFT_NAME(Options)
* @note Default value is @c NO .
*/
@property (nonatomic, assign) BOOL enablePreWarmedAppStartTracing;

#endif // SENTRY_UIKIT_AVAILABLE

/**
Expand Down Expand Up @@ -604,6 +607,12 @@ NS_SWIFT_NAME(Options)
*/
@property (nullable, nonatomic, copy) SentryBeforeEmitMetricCallback beforeEmitMetric;

/**
* This will agreggate options for all experimental features.
brustolin marked this conversation as resolved.
Show resolved Hide resolved
* Be aware that the options available for experimental can change at any time.
*/
@property (nonatomic, readonly) SentryExperimentalOptions *experimental;

@end

NS_ASSUME_NONNULL_END
14 changes: 14 additions & 0 deletions Sources/Sentry/SentryBaseIntegration.m
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#import "SentryBaseIntegration.h"
#import "SentryCrashWrapper.h"
#import "SentryLog.h"
#import "SentrySwift.h"
#import <Foundation/Foundation.h>
#import <SentryDependencyContainer.h>
#import <SentryOptions+Private.h>
Expand Down Expand Up @@ -140,6 +141,19 @@ - (BOOL)shouldBeEnabledWithOptions:(SentryOptions *)options
[self logWithOptionName:@"attachViewHierarchy"];
return NO;
}

if (integrationOptions & kIntegrationOptionEnableReplay) {
if (@available(iOS 16.0, tvOS 16.0, *)) {
if (options.experimental.sessionReplay.errorSampleRate == 0
&& options.experimental.sessionReplay.sessionSampleRate == 0) {
[self logWithOptionName:@"sessionReplaySettings"];
return NO;
}
} else {
[self logWithReason:@"Session replay requires iOS 16 or above"];
return NO;
}
}
#endif

if ((integrationOptions & kIntegrationOptionEnableCrashHandler)
Expand Down
48 changes: 42 additions & 6 deletions Sources/Sentry/SentryClient.m
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
#import "SentryDependencyContainer.h"
#import "SentryDispatchQueueWrapper.h"
#import "SentryDsn.h"
#import "SentryEnvelope.h"
#import "SentryEnvelope+Private.h"
#import "SentryEnvelopeItemType.h"
#import "SentryEvent.h"
#import "SentryException.h"
Expand All @@ -27,13 +27,16 @@
#import "SentryMechanismMeta.h"
#import "SentryMessage.h"
#import "SentryMeta.h"
#import "SentryMsgPackSerializer.h"
#import "SentryNSDictionarySanitize.h"
#import "SentryNSError.h"
#import "SentryOptions+Private.h"
#import "SentryPropagationContext.h"
#import "SentryRandom.h"
#import "SentryReplayEvent.h"
#import "SentrySDK+Private.h"
#import "SentryScope+Private.h"
#import "SentrySerialization.h"
#import "SentrySession.h"
#import "SentryStacktraceBuilder.h"
#import "SentrySwift.h"
Expand Down Expand Up @@ -472,13 +475,44 @@ - (void)captureSession:(SentrySession *)session
}

SentryEnvelopeItem *item = [[SentryEnvelopeItem alloc] initWithSession:session];
SentryEnvelopeHeader *envelopeHeader = [[SentryEnvelopeHeader alloc] initWithId:nil
traceContext:nil];
SentryEnvelope *envelope = [[SentryEnvelope alloc] initWithHeader:envelopeHeader
SentryEnvelope *envelope = [[SentryEnvelope alloc] initWithHeader:[SentryEnvelopeHeader empty]
singleItem:item];
[self captureEnvelope:envelope];
}

- (void)captureReplayEvent:(SentryReplayEvent *)replayEvent
replayRecording:(SentryReplayRecording *)replayRecording
video:(NSURL *)videoURL
withScope:(SentryScope *)scope
{
replayEvent = (SentryReplayEvent *)[self prepareEvent:replayEvent
withScope:scope
alwaysAttachStacktrace:NO];

if (![replayEvent isKindOfClass:SentryReplayEvent.class]) {
SENTRY_LOG_DEBUG(@"The event preprocessor didn't update the replay event in place. The "
@"replay was discarded.");
return;
}

SentryEnvelopeItem *videoEnvelopeItem =
[[SentryEnvelopeItem alloc] initWithReplayEvent:replayEvent
replayRecording:replayRecording
video:videoURL];

if (videoEnvelopeItem == nil) {
SENTRY_LOG_DEBUG(@"The Session Replay segment will not be sent to Sentry because an "
@"Envelope Item could not be created.");
return;
}

SentryEnvelope *envelope = [[SentryEnvelope alloc]
initWithHeader:[[SentryEnvelopeHeader alloc] initWithId:replayEvent.eventId]
items:@[ videoEnvelopeItem ]];

[self captureEnvelope:envelope];
}

- (void)captureEnvelope:(SentryEnvelope *)envelope
{
if ([self isDisabled]) {
Expand Down Expand Up @@ -553,9 +587,11 @@ - (SentryEvent *_Nullable)prepareEvent:(SentryEvent *)event

BOOL eventIsNotATransaction
= event.type == nil || ![event.type isEqualToString:SentryEnvelopeItemTypeTransaction];
BOOL eventIsNotReplay
= event.type == nil || ![event.type isEqualToString:SentryEnvelopeItemTypeReplayVideo];

// Transactions have their own sampleRate
philipphofmann marked this conversation as resolved.
Show resolved Hide resolved
if (eventIsNotATransaction && [self isSampled:self.options.sampleRate]) {
if (eventIsNotATransaction && eventIsNotReplay && [self isSampled:self.options.sampleRate]) {
SENTRY_LOG_DEBUG(@"Event got sampled, will not send the event");
[self recordLostEvent:kSentryDataCategoryError reason:kSentryDiscardReasonSampleRate];
return nil;
Expand Down Expand Up @@ -583,7 +619,7 @@ - (SentryEvent *_Nullable)prepareEvent:(SentryEvent *)event
[self setSdk:event];

// We don't want to attach debug meta and stacktraces for transactions;
brustolin marked this conversation as resolved.
Show resolved Hide resolved
if (eventIsNotATransaction) {
if (eventIsNotATransaction && eventIsNotReplay) {
BOOL shouldAttachStacktrace = alwaysAttachStacktrace || self.options.attachStacktrace
|| (nil != event.exceptions && [event.exceptions count] > 0);

Expand Down
18 changes: 18 additions & 0 deletions Sources/Sentry/SentryCoreGraphicsHelper.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#import "SentryCoreGraphicsHelper.h"
#if SENTRY_HAS_UIKIT
@implementation SentryCoreGraphicsHelper
+ (CGMutablePathRef)excludeRect:(CGRect)rectangle fromPath:(CGMutablePathRef)path
{
# if (TARGET_OS_IOS || TARGET_OS_TV)
# ifdef __IPHONE_16_0
if (@available(iOS 16.0, tvOS 16.0, *)) {
CGPathRef exclude = CGPathCreateWithRect(rectangle, nil);
CGPathRef newPath = CGPathCreateCopyBySubtractingPath(path, exclude, YES);
return CGPathCreateMutableCopy(newPath);
}
# endif // defined(__IPHONE_16_0)
# endif // (TARGET_OS_IOS || TARGET_OS_TV)
return path;
}
@end
#endif // SENTRY_HAS_UIKIT
9 changes: 9 additions & 0 deletions Sources/Sentry/SentryDataCategoryMapper.m
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
NSString *const kSentryDataCategoryNameAttachment = @"attachment";
NSString *const kSentryDataCategoryNameUserFeedback = @"user_report";
NSString *const kSentryDataCategoryNameProfile = @"profile";
NSString *const kSentryDataCategoryNameReplay = @"replay";
NSString *const kSentryDataCategoryNameMetricBucket = @"metric_bucket";
NSString *const kSentryDataCategoryNameUnknown = @"unknown";

Expand All @@ -34,6 +35,9 @@
if ([itemType isEqualToString:SentryEnvelopeItemTypeProfile]) {
return kSentryDataCategoryProfile;
}
if ([itemType isEqualToString:SentryEnvelopeItemTypeReplayVideo]) {
return kSentryDataCategoryReplay;
}
// The envelope item type used for metrics is statsd whereas the client report category for
// discarded events is metric_bucket.
if ([itemType isEqualToString:SentryEnvelopeItemTypeStatsd]) {
Expand Down Expand Up @@ -79,6 +83,9 @@
if ([value isEqualToString:kSentryDataCategoryNameProfile]) {
return kSentryDataCategoryProfile;
}
if ([value isEqualToString:kSentryDataCategoryNameReplay]) {
return kSentryDataCategoryReplay;
}
if ([value isEqualToString:kSentryDataCategoryNameMetricBucket]) {
return kSentryDataCategoryMetricBucket;
}
Expand Down Expand Up @@ -114,6 +121,8 @@
return kSentryDataCategoryNameMetricBucket;
case kSentryDataCategoryUnknown:
return kSentryDataCategoryNameUnknown;
case kSentryDataCategoryReplay:
return kSentryDataCategoryNameReplay;
}
}

Expand Down
5 changes: 5 additions & 0 deletions Sources/Sentry/SentryDateUtil.m
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ + (NSDate *_Nullable)getMaximumDate:(NSDate *_Nullable)first andOther:(NSDate *_
}
}

+ (long)millisecondsSince1970:(NSDate *)date
{
return (long)([date timeIntervalSince1970] * 1000);
}

@end

NS_ASSUME_NONNULL_END
40 changes: 40 additions & 0 deletions Sources/Sentry/SentryEnvelope.m
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
#import "SentryLog.h"
#import "SentryMessage.h"
#import "SentryMeta.h"
#import "SentryMsgPackSerializer.h"
#import "SentryReplayEvent.h"
#import "SentryReplayRecording.h"
#import "SentrySdkInfo.h"
#import "SentrySerialization.h"
#import "SentrySession.h"
Expand Down Expand Up @@ -48,6 +51,11 @@ - (instancetype)initWithId:(nullable SentryId *)eventId
return self;
}

+ (instancetype)empty
{
return [[SentryEnvelopeHeader alloc] initWithId:nil traceContext:nil];
}

@end

@implementation SentryEnvelopeItem
Expand Down Expand Up @@ -198,6 +206,38 @@ - (_Nullable instancetype)initWithAttachment:(SentryAttachment *)attachment
return [self initWithHeader:itemHeader data:data];
}

- (nullable instancetype)initWithReplayEvent:(SentryReplayEvent *)replayEvent
replayRecording:(SentryReplayRecording *)replayRecording
video:(NSURL *)videoURL
{
NSData *replayEventData = [SentrySerialization dataWithJSONObject:[replayEvent serialize]];
NSData *recording = [SentrySerialization dataWithReplayRecording:replayRecording];
NSURL *envelopeContentUrl =
[[videoURL URLByDeletingPathExtension] URLByAppendingPathExtension:@"dat"];

BOOL success = [SentryMsgPackSerializer serializeDictionaryToMessagePack:@{
@"replay_event" : replayEventData,
@"replay_recording" : recording,
@"replay_video" : videoURL
}
intoFile:envelopeContentUrl];
if (success == NO) {
SENTRY_LOG_DEBUG(@"Could not create MessagePack for session replay envelope item.");
brustolin marked this conversation as resolved.
Show resolved Hide resolved
return nil;
}

NSData *envelopeItemContent = [NSData dataWithContentsOfURL:envelopeContentUrl];

NSError *error;
if (![NSFileManager.defaultManager removeItemAtURL:envelopeContentUrl error:&error]) {
SENTRY_LOG_ERROR(@"Cound not delete temporary replay content from disk: %@", error);
}
return [self initWithHeader:[[SentryEnvelopeItemHeader alloc]
initWithType:SentryEnvelopeItemTypeReplayVideo
length:envelopeItemContent.length]
data:envelopeItemContent];
}

@end

@implementation SentryEnvelope
Expand Down
10 changes: 10 additions & 0 deletions Sources/Sentry/SentryHub.m
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,16 @@ - (SentryId *)captureEvent:(SentryEvent *)event
return SentryId.empty;
}

- (void)captureReplayEvent:(SentryReplayEvent *)replayEvent
replayRecording:(SentryReplayRecording *)replayRecording
video:(NSURL *)videoURL
{
[_client captureReplayEvent:replayEvent
replayRecording:replayRecording
video:videoURL
withScope:self.scope];
}

- (id<SentrySpan>)startTransactionWithName:(NSString *)name operation:(NSString *)operation
{
return [self startTransactionWithContext:[[SentryTransactionContext alloc]
Expand Down
Loading
Loading