Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,22 @@
> make sure you follow our [migration guide](https://docs.sentry.io/platforms/react-native/migration/) first.
<!-- prettier-ignore-end -->

## Unreleased

### Features

- Adds `replaysSessionQuality` Session Replay option to control replay quality and performance overhead on mobile ([#5001](https://github.com/getsentry/sentry-react-native/pull/5001))

```js
import * as Sentry from '@sentry/react-native';

Sentry.init({
replaysSessionSampleRate: 1.0,
replaysSessionQuality: 'low', // possible values: low, medium (default), high
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: maybe worth mentioning it affects only mobile replays

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the quick review and the feedback @romtsn 🙇
Updated with b91669b. Also updated the doc getsentry/sentry-docs@b47cb87

integrations: [Sentry.mobileReplayIntegration()],
});
```

## 6.18.0

### Fixes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ final class RNSentryReplayOptions: XCTestCase {
}

func assertAllDefaultReplayOptionsAreNotNil(replayOptions: [String: Any]) {
XCTAssertEqual(replayOptions.count, 8)
XCTAssertEqual(replayOptions.count, 9)
XCTAssertNotNil(replayOptions["sessionSampleRate"])
XCTAssertNotNil(replayOptions["errorSampleRate"])
XCTAssertNotNil(replayOptions["maskAllImages"])
Expand All @@ -57,6 +57,7 @@ final class RNSentryReplayOptions: XCTestCase {
XCTAssertNotNil(replayOptions["sdkInfo"])
XCTAssertNotNil(replayOptions["enableViewRendererV2"])
XCTAssertNotNil(replayOptions["enableFastViewRendering"])
XCTAssertNotNil(replayOptions["quality"])
}

func testSessionSampleRate() {
Expand Down Expand Up @@ -248,5 +249,73 @@ final class RNSentryReplayOptions: XCTestCase {

XCTAssertFalse(actualOptions.sessionReplay.enableFastViewRendering)
}

func testReplayQualityDefault() {
let optionsDict = ([
"dsn": "https://abc@def.ingest.sentry.io/1234567",
"replaysOnErrorSampleRate": 0.75
] as NSDictionary).mutableCopy() as! NSMutableDictionary

RNSentryReplay.updateOptions(optionsDict)

let actualOptions = try! Options(dict: optionsDict as! [String: Any])

XCTAssertEqual(actualOptions.sessionReplay.quality, SentryReplayOptions.SentryReplayQuality.medium)
}

func testReplayQualityLow() {
let optionsDict = ([
"dsn": "https://abc@def.ingest.sentry.io/1234567",
"replaysOnErrorSampleRate": 0.75,
"replaysSessionQuality": "low"
] as NSDictionary).mutableCopy() as! NSMutableDictionary

RNSentryReplay.updateOptions(optionsDict)

let actualOptions = try! Options(dict: optionsDict as! [String: Any])

XCTAssertEqual(actualOptions.sessionReplay.quality, SentryReplayOptions.SentryReplayQuality.low)
}

func testReplayQualityMedium() {
let optionsDict = ([
"dsn": "https://abc@def.ingest.sentry.io/1234567",
"replaysOnErrorSampleRate": 0.75,
"replaysSessionQuality": "medium"
] as NSDictionary).mutableCopy() as! NSMutableDictionary

RNSentryReplay.updateOptions(optionsDict)

let actualOptions = try! Options(dict: optionsDict as! [String: Any])

XCTAssertEqual(actualOptions.sessionReplay.quality, SentryReplayOptions.SentryReplayQuality.medium)
}

func testReplayQualityHigh() {
let optionsDict = ([
"dsn": "https://abc@def.ingest.sentry.io/1234567",
"replaysOnErrorSampleRate": 0.75,
"replaysSessionQuality": "high"
] as NSDictionary).mutableCopy() as! NSMutableDictionary

RNSentryReplay.updateOptions(optionsDict)

let actualOptions = try! Options(dict: optionsDict as! [String: Any])

XCTAssertEqual(actualOptions.sessionReplay.quality, SentryReplayOptions.SentryReplayQuality.high)
}

func testReplayQualityInvalidFallsBackToMedium() {
let optionsDict = ([
"dsn": "https://abc@def.ingest.sentry.io/1234567",
"replaysOnErrorSampleRate": 0.75,
"replaysSessionQuality": "invalid"
] as NSDictionary).mutableCopy() as! NSMutableDictionary

RNSentryReplay.updateOptions(optionsDict)

let actualOptions = try! Options(dict: optionsDict as! [String: Any])

XCTAssertEqual(actualOptions.sessionReplay.quality, SentryReplayOptions.SentryReplayQuality.medium)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
import io.sentry.SentryLevel;
import io.sentry.SentryOptions;
import io.sentry.SentryReplayOptions;
import io.sentry.SentryReplayOptions.SentryReplayQuality;
import io.sentry.UncaughtExceptionHandlerIntegration;
import io.sentry.android.core.AndroidLogger;
import io.sentry.android.core.AndroidProfiler;
Expand Down Expand Up @@ -86,6 +87,7 @@
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.CountDownLatch;
Expand Down Expand Up @@ -370,6 +372,12 @@ private SentryReplayOptions getReplayOptions(@NotNull ReadableMap rnOptions) {
? rnOptions.getDouble("replaysOnErrorSampleRate")
: null);

if (rnOptions.hasKey("replaysSessionQuality")) {
final String qualityString = rnOptions.getString("replaysSessionQuality");
final SentryReplayQuality quality = parseReplayQuality(qualityString);
androidReplayOptions.setQuality(quality);
}

if (!rnOptions.hasKey("mobileReplayOptions")) {
return androidReplayOptions;
}
Expand Down Expand Up @@ -398,6 +406,27 @@ private SentryReplayOptions getReplayOptions(@NotNull ReadableMap rnOptions) {
return androidReplayOptions;
}

private SentryReplayQuality parseReplayQuality(@Nullable String qualityString) {
if (qualityString == null) {
return SentryReplayQuality.MEDIUM;
}

try {
switch (qualityString.toLowerCase(Locale.ROOT)) {
case "low":
return SentryReplayQuality.LOW;
case "medium":
return SentryReplayQuality.MEDIUM;
case "high":
return SentryReplayQuality.HIGH;
default:
return SentryReplayQuality.MEDIUM;
}
} catch (Exception e) {
return SentryReplayQuality.MEDIUM;
}
}

public void crash() {
throw new RuntimeException("TEST - Sentry Client Crash (only works in release mode)");
}
Expand Down
4 changes: 4 additions & 0 deletions packages/core/ios/RNSentryReplay.mm
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#import "RNSentryReplay.h"
#import "RNSentryReplayBreadcrumbConverterHelper.h"
#import "RNSentryReplayQuality.h"
#import "RNSentryVersion.h"
#import "React/RCTTextView.h"
#import "Replay/RNSentryReplayMask.h"
Expand All @@ -22,9 +23,12 @@ + (void)updateOptions:(NSMutableDictionary *)options
NSLog(@"Setting up session replay");
NSDictionary *replayOptions = options[@"mobileReplayOptions"] ?: @{};

NSString *qualityString = options[@"replaysSessionQuality"];

[options setValue:@{
@"sessionSampleRate" : options[@"replaysSessionSampleRate"] ?: [NSNull null],
@"errorSampleRate" : options[@"replaysOnErrorSampleRate"] ?: [NSNull null],
@"quality" : @([RNSentryReplayQuality parseReplayQuality:qualityString]),
@"maskAllImages" : replayOptions[@"maskAllImages"] ?: [NSNull null],
@"maskAllText" : replayOptions[@"maskAllText"] ?: [NSNull null],
@"enableViewRendererV2" : replayOptions[@"enableViewRendererV2"] ?: [NSNull null],
Expand Down
13 changes: 13 additions & 0 deletions packages/core/ios/RNSentryReplayQuality.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

typedef NS_ENUM(NSInteger, SentryReplayQuality);

@interface RNSentryReplayQuality : NSObject

+ (SentryReplayQuality)parseReplayQuality:(NSString *_Nullable)qualityString;

@end

NS_ASSUME_NONNULL_END
25 changes: 25 additions & 0 deletions packages/core/ios/RNSentryReplayQuality.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#import "RNSentryReplayQuality.h"
@import Sentry;

@implementation RNSentryReplayQuality

+ (SentryReplayQuality)parseReplayQuality:(NSString *_Nullable)qualityString
{
if (qualityString == nil) {
return SentryReplayQualityMedium;
}

NSString *lowercaseQuality = [qualityString lowercaseString];

if ([lowercaseQuality isEqualToString:@"low"]) {
return SentryReplayQualityLow;
} else if ([lowercaseQuality isEqualToString:@"medium"]) {
return SentryReplayQualityMedium;
} else if ([lowercaseQuality isEqualToString:@"high"]) {
return SentryReplayQualityHigh;
} else {
return SentryReplayQualityMedium;
}
}

@end
10 changes: 10 additions & 0 deletions packages/core/src/js/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,14 @@ export interface BaseReactNativeOptions {
*/
replaysOnErrorSampleRate?: number;

/**
* Defines the quality of the session replay. The higher the quality, the more accurate the replay
* will be, but also more data to transfer and more CPU load.
*
* @default 'medium'
*/
replaysSessionQuality?: SentryReplayQuality;

/**
* Options which are in beta, or otherwise not guaranteed to be stable.
*/
Expand Down Expand Up @@ -275,6 +283,8 @@ export interface BaseReactNativeOptions {
useThreadsForMessageStack?: boolean;
}

export type SentryReplayQuality = 'low' | 'medium' | 'high';

export interface ReactNativeTransportOptions extends BrowserTransportOptions {
/**
* @deprecated use `maxQueueSize` in the root of the SDK options.
Expand Down
1 change: 1 addition & 0 deletions samples/react-native/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ Sentry.init({
profilesSampleRate: 1.0,
replaysSessionSampleRate: 1.0,
replaysOnErrorSampleRate: 1.0,
replaysSessionQuality: 'medium', // default
spotlight: true,
// This should be disabled when manually initializing the native SDK
// Note that options from JS are not passed to the native SDKs when initialized manually
Expand Down
Loading