Skip to content

Commit 9aa5279

Browse files
authored
[SR] Send replay recording options (#4015)
1 parent 75ebe50 commit 9aa5279

File tree

6 files changed

+350
-0
lines changed

6 files changed

+350
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
### Internal
1010

1111
- Session Replay: Allow overriding `SdkVersion` for replay events ([#4014](https://github.com/getsentry/sentry-java/pull/4014))
12+
- Session Replay: Send replay options as tags ([#4015](https://github.com/getsentry/sentry-java/pull/4015))
1213

1314
## 7.19.1
1415

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import io.sentry.protocol.SentryId
1616
import io.sentry.rrweb.RRWebBreadcrumbEvent
1717
import io.sentry.rrweb.RRWebEvent
1818
import io.sentry.rrweb.RRWebMetaEvent
19+
import io.sentry.rrweb.RRWebOptionsEvent
1920
import io.sentry.rrweb.RRWebVideoEvent
2021
import java.io.File
2122
import java.util.Date
@@ -195,6 +196,10 @@ internal interface CaptureStrategy {
195196
}
196197
}
197198

199+
if (segmentId == 0) {
200+
recordingPayload += RRWebOptionsEvent(options)
201+
}
202+
198203
val recording = ReplayRecording().apply {
199204
this.segmentId = segmentId
200205
this.payload = recordingPayload.sortedBy { it.timestamp }

sentry-android-replay/src/test/java/io/sentry/android/replay/capture/SessionCaptureStrategyTest.kt

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import io.sentry.ScopeCallback
99
import io.sentry.SentryOptions
1010
import io.sentry.SentryReplayEvent
1111
import io.sentry.SentryReplayEvent.ReplayType
12+
import io.sentry.SentryReplayOptions.SentryReplayQuality.HIGH
13+
import io.sentry.android.replay.BuildConfig
1214
import io.sentry.android.replay.DefaultReplayBreadcrumbConverter
1315
import io.sentry.android.replay.GeneratedVideo
1416
import io.sentry.android.replay.ReplayCache
@@ -22,9 +24,11 @@ import io.sentry.android.replay.ReplayCache.Companion.SEGMENT_KEY_TIMESTAMP
2224
import io.sentry.android.replay.ReplayCache.Companion.SEGMENT_KEY_WIDTH
2325
import io.sentry.android.replay.ReplayFrame
2426
import io.sentry.android.replay.ScreenshotRecorderConfig
27+
import io.sentry.android.replay.maskAllImages
2528
import io.sentry.protocol.SentryId
2629
import io.sentry.rrweb.RRWebBreadcrumbEvent
2730
import io.sentry.rrweb.RRWebMetaEvent
31+
import io.sentry.rrweb.RRWebOptionsEvent
2832
import io.sentry.transport.CurrentDateProvider
2933
import io.sentry.transport.ICurrentDateProvider
3034
import org.junit.Rule
@@ -43,8 +47,10 @@ import org.mockito.kotlin.whenever
4347
import java.io.File
4448
import java.util.Date
4549
import kotlin.test.Test
50+
import kotlin.test.assertContentEquals
4651
import kotlin.test.assertEquals
4752
import kotlin.test.assertFalse
53+
import kotlin.test.assertNull
4854
import kotlin.test.assertTrue
4955

5056
class SessionCaptureStrategyTest {
@@ -367,4 +373,81 @@ class SessionCaptureStrategyTest {
367373
"the current replay cache folder is not being deleted."
368374
)
369375
}
376+
377+
@Test
378+
fun `records replay options event for segment 0`() {
379+
fixture.options.experimental.sessionReplay.sessionSampleRate = 1.0
380+
fixture.options.experimental.sessionReplay.maskAllImages = false
381+
fixture.options.experimental.sessionReplay.quality = HIGH
382+
fixture.options.experimental.sessionReplay.addMaskViewClass("my.custom.View")
383+
384+
val now =
385+
System.currentTimeMillis() + (fixture.options.experimental.sessionReplay.sessionSegmentDuration * 5)
386+
val strategy = fixture.getSut(dateProvider = { now })
387+
strategy.start(fixture.recorderConfig)
388+
389+
strategy.onScreenshotRecorded(mock<Bitmap>()) {}
390+
391+
verify(fixture.hub).captureReplay(
392+
argThat { event ->
393+
event is SentryReplayEvent && event.segmentId == 0
394+
},
395+
check {
396+
val optionsEvent =
397+
it.replayRecording?.payload?.filterIsInstance<RRWebOptionsEvent>()!!
398+
assertEquals("sentry.java", optionsEvent[0].optionsPayload["nativeSdkName"])
399+
assertEquals(BuildConfig.VERSION_NAME, optionsEvent[0].optionsPayload["nativeSdkVersion"])
400+
401+
assertEquals(null, optionsEvent[0].optionsPayload["errorSampleRate"])
402+
assertEquals(1.0, optionsEvent[0].optionsPayload["sessionSampleRate"])
403+
assertEquals(true, optionsEvent[0].optionsPayload["maskAllText"])
404+
assertEquals(false, optionsEvent[0].optionsPayload["maskAllImages"])
405+
assertEquals("high", optionsEvent[0].optionsPayload["quality"])
406+
assertContentEquals(
407+
listOf(
408+
"android.widget.TextView",
409+
"android.webkit.WebView",
410+
"android.widget.VideoView",
411+
"androidx.media3.ui.PlayerView",
412+
"com.google.android.exoplayer2.ui.PlayerView",
413+
"com.google.android.exoplayer2.ui.StyledPlayerView",
414+
"my.custom.View"
415+
),
416+
optionsEvent[0].optionsPayload["maskedViewClasses"] as Collection<*>
417+
)
418+
assertContentEquals(
419+
listOf("android.widget.ImageView"),
420+
optionsEvent[0].optionsPayload["unmaskedViewClasses"] as Collection<*>
421+
)
422+
}
423+
)
424+
}
425+
426+
@Test
427+
fun `does not record replay options event for segment above 0`() {
428+
val now =
429+
System.currentTimeMillis() + (fixture.options.experimental.sessionReplay.sessionSegmentDuration * 5)
430+
val strategy = fixture.getSut(dateProvider = { now })
431+
strategy.start(fixture.recorderConfig)
432+
433+
strategy.onScreenshotRecorded(mock<Bitmap>()) {}
434+
verify(fixture.hub).captureReplay(
435+
argThat { event ->
436+
event is SentryReplayEvent && event.segmentId == 0
437+
},
438+
any()
439+
)
440+
441+
strategy.onScreenshotRecorded(mock<Bitmap>()) {}
442+
verify(fixture.hub).captureReplay(
443+
argThat { event ->
444+
event is SentryReplayEvent && event.segmentId == 1
445+
},
446+
check {
447+
val optionsEvent =
448+
it.replayRecording?.payload?.find { it is RRWebOptionsEvent }
449+
assertNull(optionsEvent)
450+
}
451+
)
452+
}
370453
}

sentry/api/sentry.api

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2760,6 +2760,7 @@ public final class io/sentry/SentryReplayOptions$SentryReplayQuality : java/lang
27602760
public static final field MEDIUM Lio/sentry/SentryReplayOptions$SentryReplayQuality;
27612761
public final field bitRate I
27622762
public final field sizeScale F
2763+
public fun serializedName ()Ljava/lang/String;
27632764
public static fun valueOf (Ljava/lang/String;)Lio/sentry/SentryReplayOptions$SentryReplayQuality;
27642765
public static fun values ()[Lio/sentry/SentryReplayOptions$SentryReplayQuality;
27652766
}
@@ -5415,6 +5416,33 @@ public final class io/sentry/rrweb/RRWebMetaEvent$JsonKeys {
54155416
public fun <init> ()V
54165417
}
54175418

5419+
public final class io/sentry/rrweb/RRWebOptionsEvent : io/sentry/rrweb/RRWebEvent, io/sentry/JsonSerializable, io/sentry/JsonUnknown {
5420+
public static final field EVENT_TAG Ljava/lang/String;
5421+
public fun <init> ()V
5422+
public fun <init> (Lio/sentry/SentryOptions;)V
5423+
public fun getDataUnknown ()Ljava/util/Map;
5424+
public fun getOptionsPayload ()Ljava/util/Map;
5425+
public fun getTag ()Ljava/lang/String;
5426+
public fun getUnknown ()Ljava/util/Map;
5427+
public fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V
5428+
public fun setDataUnknown (Ljava/util/Map;)V
5429+
public fun setOptionsPayload (Ljava/util/Map;)V
5430+
public fun setTag (Ljava/lang/String;)V
5431+
public fun setUnknown (Ljava/util/Map;)V
5432+
}
5433+
5434+
public final class io/sentry/rrweb/RRWebOptionsEvent$Deserializer : io/sentry/JsonDeserializer {
5435+
public fun <init> ()V
5436+
public fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Lio/sentry/rrweb/RRWebOptionsEvent;
5437+
public synthetic fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object;
5438+
}
5439+
5440+
public final class io/sentry/rrweb/RRWebOptionsEvent$JsonKeys {
5441+
public static final field DATA Ljava/lang/String;
5442+
public static final field PAYLOAD Ljava/lang/String;
5443+
public fun <init> ()V
5444+
}
5445+
54185446
public final class io/sentry/rrweb/RRWebSpanEvent : io/sentry/rrweb/RRWebEvent, io/sentry/JsonSerializable, io/sentry/JsonUnknown {
54195447
public static final field EVENT_TAG Ljava/lang/String;
54205448
public fun <init> ()V

sentry/src/main/java/io/sentry/SentryReplayOptions.java

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

33
import io.sentry.protocol.SdkVersion;
44
import io.sentry.util.SampleRateUtils;
5+
import java.util.Locale;
56
import java.util.Set;
67
import java.util.concurrent.CopyOnWriteArraySet;
78
import org.jetbrains.annotations.ApiStatus;
@@ -42,6 +43,10 @@ public enum SentryReplayQuality {
4243
this.sizeScale = sizeScale;
4344
this.bitRate = bitRate;
4445
}
46+
47+
public @NotNull String serializedName() {
48+
return name().toLowerCase(Locale.ROOT);
49+
}
4550
}
4651

4752
/**

0 commit comments

Comments
 (0)