Skip to content

Commit d079998

Browse files
authored
feat(session-replay): Adds replaysSessionQuality option (#5001)
* feat(session-replay): Adds replaysSessionQuality option * Adds changelog * Move RNSentryReplayQuality to the correct folder * Update changelog * Mention mobile in the changelog
1 parent 94f0c2b commit d079998

File tree

8 files changed

+168
-1
lines changed

8 files changed

+168
-1
lines changed

CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,22 @@
66
> make sure you follow our [migration guide](https://docs.sentry.io/platforms/react-native/migration/) first.
77
<!-- prettier-ignore-end -->
88
9+
## Unreleased
10+
11+
### Features
12+
13+
- Adds `replaysSessionQuality` Session Replay option to control replay quality and performance overhead on mobile ([#5001](https://github.com/getsentry/sentry-react-native/pull/5001))
14+
15+
```js
16+
import * as Sentry from '@sentry/react-native';
17+
18+
Sentry.init({
19+
replaysSessionSampleRate: 1.0,
20+
replaysSessionQuality: 'low', // possible values: low, medium (default), high
21+
integrations: [Sentry.mobileReplayIntegration()],
22+
});
23+
```
24+
925
## 6.18.0
1026

1127
### Fixes

packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryReplayOptionsTests.swift

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ final class RNSentryReplayOptions: XCTestCase {
4848
}
4949

5050
func assertAllDefaultReplayOptionsAreNotNil(replayOptions: [String: Any]) {
51-
XCTAssertEqual(replayOptions.count, 8)
51+
XCTAssertEqual(replayOptions.count, 9)
5252
XCTAssertNotNil(replayOptions["sessionSampleRate"])
5353
XCTAssertNotNil(replayOptions["errorSampleRate"])
5454
XCTAssertNotNil(replayOptions["maskAllImages"])
@@ -57,6 +57,7 @@ final class RNSentryReplayOptions: XCTestCase {
5757
XCTAssertNotNil(replayOptions["sdkInfo"])
5858
XCTAssertNotNil(replayOptions["enableViewRendererV2"])
5959
XCTAssertNotNil(replayOptions["enableFastViewRendering"])
60+
XCTAssertNotNil(replayOptions["quality"])
6061
}
6162

6263
func testSessionSampleRate() {
@@ -248,5 +249,73 @@ final class RNSentryReplayOptions: XCTestCase {
248249

249250
XCTAssertFalse(actualOptions.sessionReplay.enableFastViewRendering)
250251
}
252+
253+
func testReplayQualityDefault() {
254+
let optionsDict = ([
255+
"dsn": "https://abc@def.ingest.sentry.io/1234567",
256+
"replaysOnErrorSampleRate": 0.75
257+
] as NSDictionary).mutableCopy() as! NSMutableDictionary
258+
259+
RNSentryReplay.updateOptions(optionsDict)
260+
261+
let actualOptions = try! Options(dict: optionsDict as! [String: Any])
262+
263+
XCTAssertEqual(actualOptions.sessionReplay.quality, SentryReplayOptions.SentryReplayQuality.medium)
264+
}
251265

266+
func testReplayQualityLow() {
267+
let optionsDict = ([
268+
"dsn": "https://abc@def.ingest.sentry.io/1234567",
269+
"replaysOnErrorSampleRate": 0.75,
270+
"replaysSessionQuality": "low"
271+
] as NSDictionary).mutableCopy() as! NSMutableDictionary
272+
273+
RNSentryReplay.updateOptions(optionsDict)
274+
275+
let actualOptions = try! Options(dict: optionsDict as! [String: Any])
276+
277+
XCTAssertEqual(actualOptions.sessionReplay.quality, SentryReplayOptions.SentryReplayQuality.low)
278+
}
279+
280+
func testReplayQualityMedium() {
281+
let optionsDict = ([
282+
"dsn": "https://abc@def.ingest.sentry.io/1234567",
283+
"replaysOnErrorSampleRate": 0.75,
284+
"replaysSessionQuality": "medium"
285+
] as NSDictionary).mutableCopy() as! NSMutableDictionary
286+
287+
RNSentryReplay.updateOptions(optionsDict)
288+
289+
let actualOptions = try! Options(dict: optionsDict as! [String: Any])
290+
291+
XCTAssertEqual(actualOptions.sessionReplay.quality, SentryReplayOptions.SentryReplayQuality.medium)
292+
}
293+
294+
func testReplayQualityHigh() {
295+
let optionsDict = ([
296+
"dsn": "https://abc@def.ingest.sentry.io/1234567",
297+
"replaysOnErrorSampleRate": 0.75,
298+
"replaysSessionQuality": "high"
299+
] as NSDictionary).mutableCopy() as! NSMutableDictionary
300+
301+
RNSentryReplay.updateOptions(optionsDict)
302+
303+
let actualOptions = try! Options(dict: optionsDict as! [String: Any])
304+
305+
XCTAssertEqual(actualOptions.sessionReplay.quality, SentryReplayOptions.SentryReplayQuality.high)
306+
}
307+
308+
func testReplayQualityInvalidFallsBackToMedium() {
309+
let optionsDict = ([
310+
"dsn": "https://abc@def.ingest.sentry.io/1234567",
311+
"replaysOnErrorSampleRate": 0.75,
312+
"replaysSessionQuality": "invalid"
313+
] as NSDictionary).mutableCopy() as! NSMutableDictionary
314+
315+
RNSentryReplay.updateOptions(optionsDict)
316+
317+
let actualOptions = try! Options(dict: optionsDict as! [String: Any])
318+
319+
XCTAssertEqual(actualOptions.sessionReplay.quality, SentryReplayOptions.SentryReplayQuality.medium)
320+
}
252321
}

packages/core/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
import io.sentry.SentryLevel;
4646
import io.sentry.SentryOptions;
4747
import io.sentry.SentryReplayOptions;
48+
import io.sentry.SentryReplayOptions.SentryReplayQuality;
4849
import io.sentry.UncaughtExceptionHandlerIntegration;
4950
import io.sentry.android.core.AndroidLogger;
5051
import io.sentry.android.core.AndroidProfiler;
@@ -86,6 +87,7 @@
8687
import java.util.HashMap;
8788
import java.util.Iterator;
8889
import java.util.List;
90+
import java.util.Locale;
8991
import java.util.Map;
9092
import java.util.Properties;
9193
import java.util.concurrent.CountDownLatch;
@@ -370,6 +372,12 @@ private SentryReplayOptions getReplayOptions(@NotNull ReadableMap rnOptions) {
370372
? rnOptions.getDouble("replaysOnErrorSampleRate")
371373
: null);
372374

375+
if (rnOptions.hasKey("replaysSessionQuality")) {
376+
final String qualityString = rnOptions.getString("replaysSessionQuality");
377+
final SentryReplayQuality quality = parseReplayQuality(qualityString);
378+
androidReplayOptions.setQuality(quality);
379+
}
380+
373381
if (!rnOptions.hasKey("mobileReplayOptions")) {
374382
return androidReplayOptions;
375383
}
@@ -398,6 +406,27 @@ private SentryReplayOptions getReplayOptions(@NotNull ReadableMap rnOptions) {
398406
return androidReplayOptions;
399407
}
400408

409+
private SentryReplayQuality parseReplayQuality(@Nullable String qualityString) {
410+
if (qualityString == null) {
411+
return SentryReplayQuality.MEDIUM;
412+
}
413+
414+
try {
415+
switch (qualityString.toLowerCase(Locale.ROOT)) {
416+
case "low":
417+
return SentryReplayQuality.LOW;
418+
case "medium":
419+
return SentryReplayQuality.MEDIUM;
420+
case "high":
421+
return SentryReplayQuality.HIGH;
422+
default:
423+
return SentryReplayQuality.MEDIUM;
424+
}
425+
} catch (Exception e) {
426+
return SentryReplayQuality.MEDIUM;
427+
}
428+
}
429+
401430
public void crash() {
402431
throw new RuntimeException("TEST - Sentry Client Crash (only works in release mode)");
403432
}

packages/core/ios/RNSentryReplay.mm

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#import "RNSentryReplay.h"
22
#import "RNSentryReplayBreadcrumbConverterHelper.h"
3+
#import "RNSentryReplayQuality.h"
34
#import "RNSentryVersion.h"
45
#import "React/RCTTextView.h"
56
#import "Replay/RNSentryReplayMask.h"
@@ -22,9 +23,12 @@ + (void)updateOptions:(NSMutableDictionary *)options
2223
NSLog(@"Setting up session replay");
2324
NSDictionary *replayOptions = options[@"mobileReplayOptions"] ?: @{};
2425

26+
NSString *qualityString = options[@"replaysSessionQuality"];
27+
2528
[options setValue:@{
2629
@"sessionSampleRate" : options[@"replaysSessionSampleRate"] ?: [NSNull null],
2730
@"errorSampleRate" : options[@"replaysOnErrorSampleRate"] ?: [NSNull null],
31+
@"quality" : @([RNSentryReplayQuality parseReplayQuality:qualityString]),
2832
@"maskAllImages" : replayOptions[@"maskAllImages"] ?: [NSNull null],
2933
@"maskAllText" : replayOptions[@"maskAllText"] ?: [NSNull null],
3034
@"enableViewRendererV2" : replayOptions[@"enableViewRendererV2"] ?: [NSNull null],
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#import <Foundation/Foundation.h>
2+
3+
NS_ASSUME_NONNULL_BEGIN
4+
5+
typedef NS_ENUM(NSInteger, SentryReplayQuality);
6+
7+
@interface RNSentryReplayQuality : NSObject
8+
9+
+ (SentryReplayQuality)parseReplayQuality:(NSString *_Nullable)qualityString;
10+
11+
@end
12+
13+
NS_ASSUME_NONNULL_END
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#import "RNSentryReplayQuality.h"
2+
@import Sentry;
3+
4+
@implementation RNSentryReplayQuality
5+
6+
+ (SentryReplayQuality)parseReplayQuality:(NSString *_Nullable)qualityString
7+
{
8+
if (qualityString == nil) {
9+
return SentryReplayQualityMedium;
10+
}
11+
12+
NSString *lowercaseQuality = [qualityString lowercaseString];
13+
14+
if ([lowercaseQuality isEqualToString:@"low"]) {
15+
return SentryReplayQualityLow;
16+
} else if ([lowercaseQuality isEqualToString:@"medium"]) {
17+
return SentryReplayQualityMedium;
18+
} else if ([lowercaseQuality isEqualToString:@"high"]) {
19+
return SentryReplayQualityHigh;
20+
} else {
21+
return SentryReplayQualityMedium;
22+
}
23+
}
24+
25+
@end

packages/core/src/js/options.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,14 @@ export interface BaseReactNativeOptions {
234234
*/
235235
replaysOnErrorSampleRate?: number;
236236

237+
/**
238+
* Defines the quality of the session replay. The higher the quality, the more accurate the replay
239+
* will be, but also more data to transfer and more CPU load.
240+
*
241+
* @default 'medium'
242+
*/
243+
replaysSessionQuality?: SentryReplayQuality;
244+
237245
/**
238246
* Options which are in beta, or otherwise not guaranteed to be stable.
239247
*/
@@ -275,6 +283,8 @@ export interface BaseReactNativeOptions {
275283
useThreadsForMessageStack?: boolean;
276284
}
277285

286+
export type SentryReplayQuality = 'low' | 'medium' | 'high';
287+
278288
export interface ReactNativeTransportOptions extends BrowserTransportOptions {
279289
/**
280290
* @deprecated use `maxQueueSize` in the root of the SDK options.

samples/react-native/src/App.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ Sentry.init({
167167
profilesSampleRate: 1.0,
168168
replaysSessionSampleRate: 1.0,
169169
replaysOnErrorSampleRate: 1.0,
170+
replaysSessionQuality: 'medium', // default
170171
spotlight: true,
171172
// This should be disabled when manually initializing the native SDK
172173
// Note that options from JS are not passed to the native SDKs when initialized manually

0 commit comments

Comments
 (0)