Skip to content

Commit 4267ac9

Browse files
authored
[SR] GA Session replay (#4017)
1 parent 90d524d commit 4267ac9

File tree

29 files changed

+141
-117
lines changed

29 files changed

+141
-117
lines changed

CHANGELOG.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,30 @@
22

33
## Unreleased
44

5+
### Features
6+
7+
- Session Replay GA ([#4017](https://github.com/getsentry/sentry-java/pull/4017))
8+
9+
To enable Replay use the `sessionReplay.sessionSampleRate` or `sessionReplay.onErrorSampleRate` options.
10+
11+
```kotlin
12+
import io.sentry.SentryReplayOptions
13+
import io.sentry.android.core.SentryAndroid
14+
15+
SentryAndroid.init(context) { options ->
16+
17+
options.sessionReplay.sessionSampleRate = 1.0
18+
options.sessionReplay.onErrorSampleRate = 1.0
19+
20+
// To change default redaction behavior (defaults to true)
21+
options.sessionReplay.redactAllImages = true
22+
options.sessionReplay.redactAllText = true
23+
24+
// To change quality of the recording (defaults to MEDIUM)
25+
options.sessionReplay.quality = SentryReplayOptions.SentryReplayQuality.MEDIUM // (LOW|MEDIUM|HIGH)
26+
}
27+
```
28+
529
### Fixes
630

731
- Fix warm start detection ([#3937](https://github.com/getsentry/sentry-java/pull/3937))
@@ -13,6 +37,10 @@
1337
- Session Replay: Allow overriding `SdkVersion` for replay events ([#4014](https://github.com/getsentry/sentry-java/pull/4014))
1438
- Session Replay: Send replay options as tags ([#4015](https://github.com/getsentry/sentry-java/pull/4015))
1539

40+
### Breaking changes
41+
42+
- Session Replay options were moved from under `experimental` to the main `options` object ([#4017](https://github.com/getsentry/sentry-java/pull/4017))
43+
1644
## 7.19.1
1745

1846
### Fixes

sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -399,28 +399,26 @@ static void applyMetadata(
399399
options.setEnableMetrics(
400400
readBool(metadata, logger, ENABLE_METRICS, options.isEnableMetrics()));
401401

402-
if (options.getExperimental().getSessionReplay().getSessionSampleRate() == null) {
402+
if (options.getSessionReplay().getSessionSampleRate() == null) {
403403
final Double sessionSampleRate =
404404
readDouble(metadata, logger, REPLAYS_SESSION_SAMPLE_RATE);
405405
if (sessionSampleRate != -1) {
406-
options.getExperimental().getSessionReplay().setSessionSampleRate(sessionSampleRate);
406+
options.getSessionReplay().setSessionSampleRate(sessionSampleRate);
407407
}
408408
}
409409

410-
if (options.getExperimental().getSessionReplay().getOnErrorSampleRate() == null) {
410+
if (options.getSessionReplay().getOnErrorSampleRate() == null) {
411411
final Double onErrorSampleRate = readDouble(metadata, logger, REPLAYS_ERROR_SAMPLE_RATE);
412412
if (onErrorSampleRate != -1) {
413-
options.getExperimental().getSessionReplay().setOnErrorSampleRate(onErrorSampleRate);
413+
options.getSessionReplay().setOnErrorSampleRate(onErrorSampleRate);
414414
}
415415
}
416416

417417
options
418-
.getExperimental()
419418
.getSessionReplay()
420419
.setMaskAllText(readBool(metadata, logger, REPLAYS_MASK_ALL_TEXT, true));
421420

422421
options
423-
.getExperimental()
424422
.getSessionReplay()
425423
.setMaskAllImages(readBool(metadata, logger, REPLAYS_MASK_ALL_IMAGES, true));
426424
}

sentry-android-core/src/test/java/io/sentry/android/core/ManifestMetadataReaderTest.kt

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1459,22 +1459,22 @@ class ManifestMetadataReaderTest {
14591459
ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider)
14601460

14611461
// Assert
1462-
assertEquals(expectedSampleRate.toDouble(), fixture.options.experimental.sessionReplay.onErrorSampleRate)
1462+
assertEquals(expectedSampleRate.toDouble(), fixture.options.sessionReplay.onErrorSampleRate)
14631463
}
14641464

14651465
@Test
14661466
fun `applyMetadata does not override replays onErrorSampleRate from options`() {
14671467
// Arrange
14681468
val expectedSampleRate = 0.99f
1469-
fixture.options.experimental.sessionReplay.onErrorSampleRate = expectedSampleRate.toDouble()
1469+
fixture.options.sessionReplay.onErrorSampleRate = expectedSampleRate.toDouble()
14701470
val bundle = bundleOf(ManifestMetadataReader.REPLAYS_ERROR_SAMPLE_RATE to 0.1f)
14711471
val context = fixture.getContext(metaData = bundle)
14721472

14731473
// Act
14741474
ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider)
14751475

14761476
// Assert
1477-
assertEquals(expectedSampleRate.toDouble(), fixture.options.experimental.sessionReplay.onErrorSampleRate)
1477+
assertEquals(expectedSampleRate.toDouble(), fixture.options.sessionReplay.onErrorSampleRate)
14781478
}
14791479

14801480
@Test
@@ -1486,7 +1486,7 @@ class ManifestMetadataReaderTest {
14861486
ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider)
14871487

14881488
// Assert
1489-
assertNull(fixture.options.experimental.sessionReplay.onErrorSampleRate)
1489+
assertNull(fixture.options.sessionReplay.onErrorSampleRate)
14901490
}
14911491

14921492
@Test
@@ -1499,8 +1499,8 @@ class ManifestMetadataReaderTest {
14991499
ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider)
15001500

15011501
// Assert
1502-
assertTrue(fixture.options.experimental.sessionReplay.unmaskViewClasses.contains(SentryReplayOptions.IMAGE_VIEW_CLASS_NAME))
1503-
assertTrue(fixture.options.experimental.sessionReplay.unmaskViewClasses.contains(SentryReplayOptions.TEXT_VIEW_CLASS_NAME))
1502+
assertTrue(fixture.options.sessionReplay.unmaskViewClasses.contains(SentryReplayOptions.IMAGE_VIEW_CLASS_NAME))
1503+
assertTrue(fixture.options.sessionReplay.unmaskViewClasses.contains(SentryReplayOptions.TEXT_VIEW_CLASS_NAME))
15041504
}
15051505

15061506
@Test
@@ -1512,8 +1512,8 @@ class ManifestMetadataReaderTest {
15121512
ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider)
15131513

15141514
// Assert
1515-
assertTrue(fixture.options.experimental.sessionReplay.maskViewClasses.contains(SentryReplayOptions.IMAGE_VIEW_CLASS_NAME))
1516-
assertTrue(fixture.options.experimental.sessionReplay.maskViewClasses.contains(SentryReplayOptions.TEXT_VIEW_CLASS_NAME))
1515+
assertTrue(fixture.options.sessionReplay.maskViewClasses.contains(SentryReplayOptions.IMAGE_VIEW_CLASS_NAME))
1516+
assertTrue(fixture.options.sessionReplay.maskViewClasses.contains(SentryReplayOptions.TEXT_VIEW_CLASS_NAME))
15171517
}
15181518

15191519
@Test
@@ -1562,7 +1562,7 @@ class ManifestMetadataReaderTest {
15621562
assertEquals(expectedSampleRate.toDouble(), fixture.options.sampleRate)
15631563
assertEquals(expectedSampleRate.toDouble(), fixture.options.tracesSampleRate)
15641564
assertEquals(expectedSampleRate.toDouble(), fixture.options.profilesSampleRate)
1565-
assertEquals(expectedSampleRate.toDouble(), fixture.options.experimental.sessionReplay.sessionSampleRate)
1566-
assertEquals(expectedSampleRate.toDouble(), fixture.options.experimental.sessionReplay.onErrorSampleRate)
1565+
assertEquals(expectedSampleRate.toDouble(), fixture.options.sessionReplay.sessionSampleRate)
1566+
assertEquals(expectedSampleRate.toDouble(), fixture.options.sessionReplay.onErrorSampleRate)
15671567
}
15681568
}

sentry-android-core/src/test/java/io/sentry/android/core/SentryAndroidTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,7 @@ class SentryAndroidTest {
371371
options.release = "prod"
372372
options.dsn = "https://key@sentry.io/123"
373373
options.isEnableAutoSessionTracking = true
374-
options.experimental.sessionReplay.onErrorSampleRate = 1.0
374+
options.sessionReplay.onErrorSampleRate = 1.0
375375
optionsConfig(options)
376376
}
377377

sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/ReplayTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ class ReplayTest : BaseUiTest() {
6565
activityScenario.moveToState(Lifecycle.State.RESUMED)
6666

6767
initSentry {
68-
it.experimental.sessionReplay.sessionSampleRate = 1.0
68+
it.sessionReplay.sessionSampleRate = 1.0
6969

7070
it.beforeSendReplay =
7171
SentryOptions.BeforeSendReplayCallback { event, _ ->

sentry-android-replay/src/main/java/io/sentry/android/replay/ReplayCache.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ public class ReplayCache(
8585
it.createNewFile()
8686
}
8787
screenshot.outputStream().use {
88-
bitmap.compress(JPEG, options.experimental.sessionReplay.quality.screenshotQuality, it)
88+
bitmap.compress(JPEG, options.sessionReplay.quality.screenshotQuality, it)
8989
it.flush()
9090
}
9191

sentry-android-replay/src/main/java/io/sentry/android/replay/ReplayIntegration.kt

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,8 @@ public class ReplayIntegration(
118118
return
119119
}
120120

121-
if (!options.experimental.sessionReplay.isSessionReplayEnabled &&
122-
!options.experimental.sessionReplay.isSessionReplayForErrorsEnabled
121+
if (!options.sessionReplay.isSessionReplayEnabled &&
122+
!options.sessionReplay.isSessionReplayForErrorsEnabled
123123
) {
124124
options.logger.log(INFO, "Session replay is disabled, no sample rate specified")
125125
return
@@ -132,7 +132,7 @@ public class ReplayIntegration(
132132

133133
options.connectionStatusProvider.addConnectionStatusObserver(this)
134134
hub.rateLimiter?.addRateLimitObserver(this)
135-
if (options.experimental.sessionReplay.isTrackOrientationChange) {
135+
if (options.sessionReplay.isTrackOrientationChange) {
136136
try {
137137
context.registerComponentCallbacks(this)
138138
} catch (e: Throwable) {
@@ -167,13 +167,13 @@ public class ReplayIntegration(
167167
return
168168
}
169169

170-
val isFullSession = random.sample(options.experimental.sessionReplay.sessionSampleRate)
171-
if (!isFullSession && !options.experimental.sessionReplay.isSessionReplayForErrorsEnabled) {
170+
val isFullSession = random.sample(options.sessionReplay.sessionSampleRate)
171+
if (!isFullSession && !options.sessionReplay.isSessionReplayForErrorsEnabled) {
172172
options.logger.log(INFO, "Session replay is not started, full session was not sampled and onErrorSampleRate is not specified")
173173
return
174174
}
175175

176-
val recorderConfig = recorderConfigProvider?.invoke(false) ?: ScreenshotRecorderConfig.from(context, options.experimental.sessionReplay)
176+
val recorderConfig = recorderConfigProvider?.invoke(false) ?: ScreenshotRecorderConfig.from(context, options.sessionReplay)
177177
captureStrategy = replayCaptureStrategyProvider?.invoke(isFullSession) ?: if (isFullSession) {
178178
SessionCaptureStrategy(options, hub, dateProvider, replayExecutor, replayCacheProvider)
179179
} else {
@@ -264,7 +264,7 @@ public class ReplayIntegration(
264264

265265
options.connectionStatusProvider.removeConnectionStatusObserver(this)
266266
hub?.rateLimiter?.removeRateLimitObserver(this)
267-
if (options.experimental.sessionReplay.isTrackOrientationChange) {
267+
if (options.sessionReplay.isTrackOrientationChange) {
268268
try {
269269
context.unregisterComponentCallbacks(this)
270270
} catch (ignored: Throwable) {
@@ -285,7 +285,7 @@ public class ReplayIntegration(
285285
recorder?.stop()
286286

287287
// refresh config based on new device configuration
288-
val recorderConfig = recorderConfigProvider?.invoke(true) ?: ScreenshotRecorderConfig.from(context, options.experimental.sessionReplay)
288+
val recorderConfig = recorderConfigProvider?.invoke(true) ?: ScreenshotRecorderConfig.from(context, options.sessionReplay)
289289
captureStrategy?.onConfigurationChanged(recorderConfig)
290290

291291
recorder?.start(recorderConfig)

sentry-android-replay/src/main/java/io/sentry/android/replay/capture/BufferCaptureStrategy.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ internal class BufferCaptureStrategy(
6565
isTerminating: Boolean,
6666
onSegmentSent: (Date) -> Unit
6767
) {
68-
val sampled = random.sample(options.experimental.sessionReplay.onErrorSampleRate)
68+
val sampled = random.sample(options.sessionReplay.onErrorSampleRate)
6969

7070
if (!sampled) {
7171
options.logger.log(INFO, "Replay wasn't sampled by onErrorSampleRate, not capturing for event")
@@ -107,7 +107,7 @@ internal class BufferCaptureStrategy(
107107
cache?.store(frameTimestamp)
108108

109109
val now = dateProvider.currentTimeMillis
110-
val bufferLimit = now - options.experimental.sessionReplay.errorReplayDuration
110+
val bufferLimit = now - options.sessionReplay.errorReplayDuration
111111
screenAtStart = cache?.rotate(bufferLimit)
112112
bufferedSegments.rotate(bufferLimit)
113113
}
@@ -137,7 +137,7 @@ internal class BufferCaptureStrategy(
137137

138138
override fun onTouchEvent(event: MotionEvent) {
139139
super.onTouchEvent(event)
140-
val bufferLimit = dateProvider.currentTimeMillis - options.experimental.sessionReplay.errorReplayDuration
140+
val bufferLimit = dateProvider.currentTimeMillis - options.sessionReplay.errorReplayDuration
141141
rotateEvents(currentEvents, bufferLimit)
142142
}
143143

@@ -189,7 +189,7 @@ internal class BufferCaptureStrategy(
189189
}
190190

191191
private fun createCurrentSegment(taskName: String, onSegmentCreated: (ReplaySegment) -> Unit) {
192-
val errorReplayDuration = options.experimental.sessionReplay.errorReplayDuration
192+
val errorReplayDuration = options.sessionReplay.errorReplayDuration
193193
val now = dateProvider.currentTimeMillis
194194
val currentSegmentTimestamp = if (cache?.frames?.isNotEmpty() == true) {
195195
// in buffer mode we have to set the timestamp of the first frame as the actual start

sentry-android-replay/src/main/java/io/sentry/android/replay/capture/SessionCaptureStrategy.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -92,10 +92,10 @@ internal class SessionCaptureStrategy(
9292
}
9393

9494
val now = dateProvider.currentTimeMillis
95-
if ((now - currentSegmentTimestamp.time >= options.experimental.sessionReplay.sessionSegmentDuration)) {
95+
if ((now - currentSegmentTimestamp.time >= options.sessionReplay.sessionSegmentDuration)) {
9696
val segment =
9797
createSegmentInternal(
98-
options.experimental.sessionReplay.sessionSegmentDuration,
98+
options.sessionReplay.sessionSegmentDuration,
9999
currentSegmentTimestamp,
100100
currentReplayId,
101101
currentSegment,
@@ -110,7 +110,7 @@ internal class SessionCaptureStrategy(
110110
}
111111
}
112112

113-
if ((now - replayStartTimestamp.get() >= options.experimental.sessionReplay.sessionDuration)) {
113+
if ((now - replayStartTimestamp.get() >= options.sessionReplay.sessionDuration)) {
114114
options.replayController.stop()
115115
options.logger.log(INFO, "Session replay deadline exceeded (1h), stopping recording")
116116
}

sentry-android-replay/src/main/java/io/sentry/android/replay/viewhierarchy/ComposeViewHierarchyNode.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,11 @@ internal object ComposeViewHierarchyNode {
5656
}
5757

5858
val className = getProxyClassName(isImage)
59-
if (options.experimental.sessionReplay.unmaskViewClasses.contains(className)) {
59+
if (options.sessionReplay.unmaskViewClasses.contains(className)) {
6060
return false
6161
}
6262

63-
return options.experimental.sessionReplay.maskViewClasses.contains(className)
63+
return options.sessionReplay.maskViewClasses.contains(className)
6464
}
6565

6666
private var _rootCoordinates: WeakReference<LayoutCoordinates>? = null

0 commit comments

Comments
 (0)