Skip to content

Commit

Permalink
Merge 0a08f99 into 83acf3e
Browse files Browse the repository at this point in the history
  • Loading branch information
brustolin committed Mar 5, 2024
2 parents 83acf3e + 0a08f99 commit 4b92944
Show file tree
Hide file tree
Showing 45 changed files with 1,111 additions and 21 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

### Features

- Add Session Replay (#3625)
- Add support for Sentry [Spotlight](https://spotlightjs.com/) (#3642), which is basically Sentry
for development. Read our [blog post](https://blog.sentry.io/sentry-for-development/) to find out more.
- Add field `SentrySDK.detectedStartUpCrash` (#3644)
Expand Down
70 changes: 70 additions & 0 deletions Sentry.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Sources/Sentry/Public/Sentry.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ FOUNDATION_EXPORT const unsigned char SentryVersionString[];
#import "SentryMessage.h"
#import "SentryNSError.h"
#import "SentryOptions.h"
#import "SentryReplayOptions.h"
#import "SentryRequest.h"
#import "SentrySDK.h"
#import "SentrySampleDecision.h"
Expand Down
12 changes: 11 additions & 1 deletion Sources/Sentry/Public/SentryOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@

NS_ASSUME_NONNULL_BEGIN

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

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

/**
* @warning This is an experimental feature and may still have bugs.
* Settings to configure the session replay.
* @node Default value is @c nil .
*/
@property (nonatomic, strong)
SentryReplayOptions *sessionReplayOptions API_AVAILABLE(ios(16.0), tvos(16.0));

#endif // SENTRY_UIKIT_AVAILABLE

/**
Expand Down
38 changes: 38 additions & 0 deletions Sources/Sentry/Public/SentryReplayOptions.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface SentryReplayOptions : NSObject

/**
* Indicates the percentage in which the replay for the session will be created.
* @discussion Specifying @c 0 means never, @c 1.0 means always.
* @note The value needs to be >= 0.0 and \<= 1.0. When setting a value out of range the SDK sets it
* to the default.
* @note The default is @c 0.
*/
@property (nonatomic) float replaysSessionSampleRate;

/**
* Indicates the percentage in which a 30 seconds replay will be send with error events.
* @discussion Specifying @c 0 means never, @c 1.0 means always.
* @note The value needs to be >= 0.0 and \<= 1.0. When setting a value out of range the SDK sets it
* to the default.
* @note The default is @c 0.
*/
@property (nonatomic) float replaysOnErrorSampleRate;

/**
* Inittialize the settings of session replay
*
* @param sessionSampleRate Indicates the percentage in which the replay for the session will be
* created.
* @param errorSampleRate Indicates the percentage in which a 30 seconds replay will be send with
* error events.
*/
- (instancetype)initWithReplaySessionSampleRate:(float)sessionSampleRate
replaysOnErrorSampleRate:(float)errorSampleRate;

@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 "SentryReplayOptions.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.sessionReplayOptions.replaysOnErrorSampleRate == 0
&& options.sessionReplayOptions.replaysSessionSampleRate == 0) {
[self logWithOptionName:@"sessionReplaySettings"];
return NO;
}
} else {
[self logWithReason:@"Session replay requires iOS 16 or above"];
return NO;
}
}
#endif

if ((integrationOptions & kIntegrationOptionEnableCrashHandler)
Expand Down
47 changes: 41 additions & 6 deletions Sources/Sentry/SentryClient.m
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,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 @@ -30,12 +30,15 @@
#import "SentryMechanismMeta.h"
#import "SentryMessage.h"
#import "SentryMeta.h"
#import "SentryMsgPackSerializer.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,43 @@ - (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 empty]
items:@[ videoEnvelopeItem ]];

[self captureEnvelope:envelope];
}

- (void)captureEnvelope:(SentryEnvelope *)envelope
{
if ([self isDisabled]) {
Expand Down Expand Up @@ -553,9 +586,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
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 +618,7 @@ - (SentryEvent *_Nullable)prepareEvent:(SentryEvent *)event
[self setSdk:event];

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

Expand Down
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 kSentryDataCategoryNameStatsd = @"statsd";
NSString *const kSentryDataCategoryNameUnknown = @"unknown";

Expand All @@ -34,6 +35,9 @@
if ([itemType isEqualToString:SentryEnvelopeItemTypeProfile]) {
return kSentryDataCategoryProfile;
}
if ([itemType isEqualToString:SentryEnvelopeItemTypeReplayVideo]) {
return kSentryDataCategoryReplay;
}
if ([itemType isEqualToString:SentryEnvelopeItemTypeStatsd]) {
return kSentryDataCategoryStatsd;
}
Expand Down Expand Up @@ -77,6 +81,9 @@
if ([value isEqualToString:kSentryDataCategoryNameProfile]) {
return kSentryDataCategoryProfile;
}
if ([value isEqualToString:kSentryDataCategoryNameReplay]) {
return kSentryDataCategoryReplay;
}
if ([value isEqualToString:kSentryDataCategoryNameStatsd]) {
return kSentryDataCategoryStatsd;
}
Expand Down Expand Up @@ -112,6 +119,8 @@
return kSentryDataCategoryNameStatsd;
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 @@ -38,6 +38,11 @@ + (NSDate *_Nullable)getMaximumDate:(NSDate *_Nullable)first andOther:(NSDate *_
}
}

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

@end

NS_ASSUME_NONNULL_END
35 changes: 35 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,33 @@ - (_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.");
return nil;
}

NSData *envelopeItemContent = [NSData dataWithContentsOfURL:envelopeContentUrl];
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 @@ -297,6 +297,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

0 comments on commit 4b92944

Please sign in to comment.