diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c1b0807c3..4336a3aa82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ - Fix warm start detection ([#3937](https://github.com/getsentry/sentry-java/pull/3937)) +### Internal + +- Session Replay: Allow overriding `SdkVersion` for replay events ([#4014](https://github.com/getsentry/sentry-java/pull/4014)) + ## 7.19.1 ### Fixes diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index ef922d4f59..92a8e90976 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -315,7 +315,7 @@ public abstract interface class io/sentry/EventProcessor { } public final class io/sentry/ExperimentalOptions { - public fun (Z)V + public fun (ZLio/sentry/protocol/SdkVersion;)V public fun getSessionReplay ()Lio/sentry/SentryReplayOptions; public fun setSessionReplay (Lio/sentry/SentryReplayOptions;)V } @@ -2724,8 +2724,8 @@ public final class io/sentry/SentryReplayOptions { public static final field TEXT_VIEW_CLASS_NAME Ljava/lang/String; public static final field VIDEO_VIEW_CLASS_NAME Ljava/lang/String; public static final field WEB_VIEW_CLASS_NAME Ljava/lang/String; - public fun (Ljava/lang/Double;Ljava/lang/Double;)V - public fun (Z)V + public fun (Ljava/lang/Double;Ljava/lang/Double;Lio/sentry/protocol/SdkVersion;)V + public fun (ZLio/sentry/protocol/SdkVersion;)V public fun addMaskViewClass (Ljava/lang/String;)V public fun addUnmaskViewClass (Ljava/lang/String;)V public fun getErrorReplayDuration ()J @@ -2734,6 +2734,7 @@ public final class io/sentry/SentryReplayOptions { public fun getMaskViewContainerClass ()Ljava/lang/String; public fun getOnErrorSampleRate ()Ljava/lang/Double; public fun getQuality ()Lio/sentry/SentryReplayOptions$SentryReplayQuality; + public fun getSdkVersion ()Lio/sentry/protocol/SdkVersion; public fun getSessionDuration ()J public fun getSessionSampleRate ()Ljava/lang/Double; public fun getSessionSegmentDuration ()J @@ -2747,6 +2748,7 @@ public final class io/sentry/SentryReplayOptions { public fun setMaskViewContainerClass (Ljava/lang/String;)V public fun setOnErrorSampleRate (Ljava/lang/Double;)V public fun setQuality (Lio/sentry/SentryReplayOptions$SentryReplayQuality;)V + public fun setSdkVersion (Lio/sentry/protocol/SdkVersion;)V public fun setSessionSampleRate (Ljava/lang/Double;)V public fun setTrackOrientationChange (Z)V public fun setUnmaskViewContainerClass (Ljava/lang/String;)V diff --git a/sentry/src/main/java/io/sentry/ExperimentalOptions.java b/sentry/src/main/java/io/sentry/ExperimentalOptions.java index 4a0e7de78d..f1bf9a8bc7 100644 --- a/sentry/src/main/java/io/sentry/ExperimentalOptions.java +++ b/sentry/src/main/java/io/sentry/ExperimentalOptions.java @@ -1,6 +1,8 @@ package io.sentry; +import io.sentry.protocol.SdkVersion; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; /** * Experimental options for new features, these options are going to be promoted to SentryOptions @@ -11,8 +13,8 @@ public final class ExperimentalOptions { private @NotNull SentryReplayOptions sessionReplay; - public ExperimentalOptions(final boolean empty) { - this.sessionReplay = new SentryReplayOptions(empty); + public ExperimentalOptions(final boolean empty, final @Nullable SdkVersion sdkVersion) { + this.sessionReplay = new SentryReplayOptions(empty, sdkVersion); } @NotNull diff --git a/sentry/src/main/java/io/sentry/MainEventProcessor.java b/sentry/src/main/java/io/sentry/MainEventProcessor.java index d6445e3a56..30f95b8b8f 100644 --- a/sentry/src/main/java/io/sentry/MainEventProcessor.java +++ b/sentry/src/main/java/io/sentry/MainEventProcessor.java @@ -4,6 +4,7 @@ import io.sentry.hints.Cached; import io.sentry.protocol.DebugImage; import io.sentry.protocol.DebugMeta; +import io.sentry.protocol.SdkVersion; import io.sentry.protocol.SentryException; import io.sentry.protocol.SentryTransaction; import io.sentry.protocol.User; @@ -159,6 +160,12 @@ private void processNonCachedEvent(final @NotNull SentryBaseEvent event) { if (shouldApplyScopeData(event, hint)) { processNonCachedEvent(event); + final @Nullable SdkVersion replaySdkVersion = + options.getExperimental().getSessionReplay().getSdkVersion(); + if (replaySdkVersion != null) { + // we override the SdkVersion only for replay events as those may come from Hybrid SDKs + event.setSdk(replaySdkVersion); + } } return event; } diff --git a/sentry/src/main/java/io/sentry/SentryClient.java b/sentry/src/main/java/io/sentry/SentryClient.java index 31a5d6f780..a94d6b5ec3 100644 --- a/sentry/src/main/java/io/sentry/SentryClient.java +++ b/sentry/src/main/java/io/sentry/SentryClient.java @@ -640,8 +640,11 @@ public void captureUserFeedback(final @NotNull UserFeedback userFeedback) { envelopeItems.add(replayItem); final SentryId sentryId = event.getEventId(); + // SdkVersion from ReplayOptions defaults to SdkVersion from SentryOptions and can be + // overwritten by the hybrid SDKs final SentryEnvelopeHeader envelopeHeader = - new SentryEnvelopeHeader(sentryId, options.getSdkVersion(), traceContext); + new SentryEnvelopeHeader( + sentryId, options.getExperimental().getSessionReplay().getSdkVersion(), traceContext); return new SentryEnvelope(envelopeHeader, envelopeItems); } diff --git a/sentry/src/main/java/io/sentry/SentryOptions.java b/sentry/src/main/java/io/sentry/SentryOptions.java index 22738b4ba1..23f195ba8e 100644 --- a/sentry/src/main/java/io/sentry/SentryOptions.java +++ b/sentry/src/main/java/io/sentry/SentryOptions.java @@ -1421,6 +1421,13 @@ public void setSslSocketFactory(final @Nullable SSLSocketFactory sslSocketFactor */ @ApiStatus.Internal public void setSdkVersion(final @Nullable SdkVersion sdkVersion) { + final @Nullable SdkVersion replaySdkVersion = experimental.getSessionReplay().getSdkVersion(); + if (this.sdkVersion != null + && replaySdkVersion != null + && this.sdkVersion.equals(replaySdkVersion)) { + // if sdkVersion = sessionReplay.sdkVersion we override it, as it means no one else set it + experimental.getSessionReplay().setSdkVersion(sdkVersion); + } this.sdkVersion = sdkVersion; } @@ -2626,7 +2633,8 @@ public SentryOptions() { * @param empty if options should be empty. */ private SentryOptions(final boolean empty) { - experimental = new ExperimentalOptions(empty); + final @NotNull SdkVersion sdkVersion = createSdkVersion(); + experimental = new ExperimentalOptions(empty, sdkVersion); if (!empty) { // SentryExecutorService should be initialized before any // SendCachedEventFireAndForgetIntegration @@ -2647,7 +2655,7 @@ private SentryOptions(final boolean empty) { } setSentryClientName(BuildConfig.SENTRY_JAVA_SDK_NAME + "/" + BuildConfig.VERSION_NAME); - setSdkVersion(createSdkVersion()); + setSdkVersion(sdkVersion); addPackageInfo(); } } diff --git a/sentry/src/main/java/io/sentry/SentryReplayOptions.java b/sentry/src/main/java/io/sentry/SentryReplayOptions.java index f9e82c8600..73eb7a33e9 100644 --- a/sentry/src/main/java/io/sentry/SentryReplayOptions.java +++ b/sentry/src/main/java/io/sentry/SentryReplayOptions.java @@ -1,5 +1,6 @@ package io.sentry; +import io.sentry.protocol.SdkVersion; import io.sentry.util.SampleRateUtils; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; @@ -114,7 +115,13 @@ public enum SentryReplayQuality { */ private boolean trackOrientationChange = true; - public SentryReplayOptions(final boolean empty) { + /** + * SdkVersion object that contains the Sentry Client Name and its version. This object is only + * applied to {@link SentryReplayEvent}s. + */ + private @Nullable SdkVersion sdkVersion; + + public SentryReplayOptions(final boolean empty, final @Nullable SdkVersion sdkVersion) { if (!empty) { setMaskAllText(true); setMaskAllImages(true); @@ -123,14 +130,18 @@ public SentryReplayOptions(final boolean empty) { maskViewClasses.add(ANDROIDX_MEDIA_VIEW_CLASS_NAME); maskViewClasses.add(EXOPLAYER_CLASS_NAME); maskViewClasses.add(EXOPLAYER_STYLED_CLASS_NAME); + this.sdkVersion = sdkVersion; } } public SentryReplayOptions( - final @Nullable Double sessionSampleRate, final @Nullable Double onErrorSampleRate) { - this(false); + final @Nullable Double sessionSampleRate, + final @Nullable Double onErrorSampleRate, + final @Nullable SdkVersion sdkVersion) { + this(false, sdkVersion); this.sessionSampleRate = sessionSampleRate; this.onErrorSampleRate = onErrorSampleRate; + this.sdkVersion = sdkVersion; } @Nullable @@ -282,4 +293,14 @@ public boolean isTrackOrientationChange() { public void setTrackOrientationChange(final boolean trackOrientationChange) { this.trackOrientationChange = trackOrientationChange; } + + @ApiStatus.Internal + public @Nullable SdkVersion getSdkVersion() { + return sdkVersion; + } + + @ApiStatus.Internal + public void setSdkVersion(final @Nullable SdkVersion sdkVersion) { + this.sdkVersion = sdkVersion; + } } diff --git a/sentry/src/test/java/io/sentry/MainEventProcessorTest.kt b/sentry/src/test/java/io/sentry/MainEventProcessorTest.kt index 00214e92c5..e82b184c27 100644 --- a/sentry/src/test/java/io/sentry/MainEventProcessorTest.kt +++ b/sentry/src/test/java/io/sentry/MainEventProcessorTest.kt @@ -27,7 +27,7 @@ import kotlin.test.assertTrue class MainEventProcessorTest { class Fixture { - private val sentryOptions: SentryOptions = SentryOptions().apply { + val sentryOptions: SentryOptions = SentryOptions().apply { dsn = dsnString release = "release" dist = "dist" @@ -619,6 +619,18 @@ class MainEventProcessorTest { assertEquals("value1", replayEvent.tags!!["tag1"]) } + @Test + fun `uses SdkVersion from replay options for replay events`() { + val sut = fixture.getSut(tags = mapOf("tag1" to "value1")) + + fixture.sentryOptions.experimental.sessionReplay.sdkVersion = SdkVersion("dart", "3.2.1") + var replayEvent = SentryReplayEvent() + replayEvent = sut.process(replayEvent, Hint()) + + assertEquals("3.2.1", replayEvent.sdk!!.version) + assertEquals("dart", replayEvent.sdk!!.name) + } + private fun generateCrashedEvent(crashedThread: Thread = Thread.currentThread()) = SentryEvent().apply { val mockThrowable = mock() diff --git a/sentry/src/test/java/io/sentry/SentryReplayOptionsTest.kt b/sentry/src/test/java/io/sentry/SentryReplayOptionsTest.kt index 794a3dac09..48d9d71ac4 100644 --- a/sentry/src/test/java/io/sentry/SentryReplayOptionsTest.kt +++ b/sentry/src/test/java/io/sentry/SentryReplayOptionsTest.kt @@ -7,7 +7,7 @@ class SentryReplayOptionsTest { @Test fun `uses medium quality as default`() { - val replayOptions = SentryReplayOptions(true) + val replayOptions = SentryReplayOptions(true, null) assertEquals(SentryReplayOptions.SentryReplayQuality.MEDIUM, replayOptions.quality) assertEquals(75_000, replayOptions.quality.bitRate) @@ -16,7 +16,7 @@ class SentryReplayOptionsTest { @Test fun `low quality`() { - val replayOptions = SentryReplayOptions(true).apply { quality = SentryReplayOptions.SentryReplayQuality.LOW } + val replayOptions = SentryReplayOptions(true, null).apply { quality = SentryReplayOptions.SentryReplayQuality.LOW } assertEquals(50_000, replayOptions.quality.bitRate) assertEquals(0.8f, replayOptions.quality.sizeScale) @@ -24,7 +24,7 @@ class SentryReplayOptionsTest { @Test fun `high quality`() { - val replayOptions = SentryReplayOptions(true).apply { quality = SentryReplayOptions.SentryReplayQuality.HIGH } + val replayOptions = SentryReplayOptions(true, null).apply { quality = SentryReplayOptions.SentryReplayQuality.HIGH } assertEquals(100_000, replayOptions.quality.bitRate) assertEquals(1.0f, replayOptions.quality.sizeScale)