Skip to content

Commit

Permalink
RUM-6375: Force single core for session replay
Browse files Browse the repository at this point in the history
  • Loading branch information
jonathanmos committed Oct 16, 2024
1 parent 774c798 commit 4fbbd96
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 1 deletion.
1 change: 1 addition & 0 deletions detekt_custom.yml
Original file line number Diff line number Diff line change
Expand Up @@ -717,6 +717,7 @@ datadog:
- "java.lang.ref.WeakReference.constructor(android.content.Context?)"
- "java.lang.ref.WeakReference.constructor(android.view.View?)"
- "java.lang.ref.WeakReference.constructor(android.view.Window?)"
- "java.lang.ref.WeakReference.constructor(com.datadog.android.api.SdkCore?)"
- "java.lang.ref.WeakReference.constructor(kotlin.Any?)"
- "java.lang.ref.WeakReference.constructor(kotlin.Nothing?)"
- "java.lang.ref.WeakReference.constructor(kotlin.String?)"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,27 @@

package com.datadog.android.sessionreplay

import androidx.annotation.VisibleForTesting
import com.datadog.android.Datadog
import com.datadog.android.api.InternalLogger
import com.datadog.android.api.SdkCore
import com.datadog.android.api.feature.Feature.Companion.SESSION_REPLAY_FEATURE_NAME
import com.datadog.android.api.feature.FeatureSdkCore
import com.datadog.android.sessionreplay.internal.SessionReplayFeature
import java.lang.ref.WeakReference

/**
* An entry point to Datadog Session Replay feature.
*/
object SessionReplay {

// core on which session replay was last enabled
@VisibleForTesting internal var activeSdkCore: WeakReference<SdkCore>? = null

internal const val FEATURE_ALREADY_REGISTERED_WARNING =
"Session Replay is already enabled and does not support multiple instances. " +
"The existing instance will continue to be used."

/**
* Enables a SessionReplay feature based on the configuration provided.
* It is recommended to invoke this function as early as possible in the app's lifecycle,
Expand Down Expand Up @@ -48,7 +58,18 @@ object SessionReplay {
startRecordingImmediately = sessionReplayConfiguration.startRecordingImmediately,
dynamicOptimizationEnabled = sessionReplayConfiguration.dynamicOptimizationEnabled
)
sdkCore.registerFeature(sessionReplayFeature)

val previousCore = activeSdkCore?.get()
if (previousCore == null) {
activeSdkCore = WeakReference(sdkCore)
sdkCore.registerFeature(sessionReplayFeature)
} else {
sdkCore.internalLogger.log(
level = InternalLogger.Level.WARN,
targets = listOf(InternalLogger.Target.USER, InternalLogger.Target.TELEMETRY),
messageBuilder = { FEATURE_ALREADY_REGISTERED_WARNING }
)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@

package com.datadog.android.sessionreplay

import com.datadog.android.api.InternalLogger
import com.datadog.android.api.feature.Feature.Companion.SESSION_REPLAY_FEATURE_NAME
import com.datadog.android.api.feature.FeatureScope
import com.datadog.android.api.feature.FeatureSdkCore
import com.datadog.android.sessionreplay.SessionReplay.FEATURE_ALREADY_REGISTERED_WARNING
import com.datadog.android.sessionreplay.forge.ForgeConfigurator
import com.datadog.android.sessionreplay.internal.SessionReplayFeature
import com.datadog.android.sessionreplay.internal.net.SegmentRequestFactory
import com.datadog.android.sessionreplay.utils.verifyLog
import fr.xgouchet.elmyr.annotation.Forgery
import fr.xgouchet.elmyr.annotation.StringForgery
import fr.xgouchet.elmyr.junit5.ForgeConfiguration
Expand Down Expand Up @@ -50,6 +53,7 @@ internal class SessionReplayTest {
@BeforeEach
fun `set up`() {
whenever(mockSdkCore.internalLogger) doReturn mock()
SessionReplay.activeSdkCore = null
}

@Test
Expand Down Expand Up @@ -119,4 +123,41 @@ internal class SessionReplayTest {
// Then
verify(mockSessionReplayFeature).manuallyStopRecording()
}

@Test
fun `M warn and send telemetry W enable { session replay feature already registered with another core }`(
@Forgery fakeSessionReplayConfiguration: SessionReplayConfiguration,
@Mock mockCore1: FeatureSdkCore,
@Mock mockCore2: FeatureSdkCore,
@Mock mockInternalLogger: InternalLogger
) {
// Given
whenever(mockCore1.internalLogger).thenReturn(mockInternalLogger)
whenever(mockCore2.internalLogger).thenReturn(mockInternalLogger)
val fakeSessionReplayConfigurationWithMockRequirement = fakeSessionReplayConfiguration.copy(
systemRequirementsConfiguration = mockSystemRequirementsConfiguration
)
whenever(
mockSystemRequirementsConfiguration.runIfRequirementsMet(any(), any())
) doAnswer {
it.getArgument<() -> Unit>(1).invoke()
}
SessionReplay.enable(
sessionReplayConfiguration = fakeSessionReplayConfigurationWithMockRequirement,
sdkCore = mockCore1
)

// When
SessionReplay.enable(
sessionReplayConfiguration = fakeSessionReplayConfigurationWithMockRequirement,
sdkCore = mockCore2
)

// Then
mockInternalLogger.verifyLog(
level = InternalLogger.Level.WARN,
targets = listOf(InternalLogger.Target.USER, InternalLogger.Target.TELEMETRY),
message = FEATURE_ALREADY_REGISTERED_WARNING
)
}
}

0 comments on commit 4fbbd96

Please sign in to comment.