From 1dfdc069a8d36ff09cedc0f25c5e6b0763571537 Mon Sep 17 00:00:00 2001 From: Marius Constantin Date: Thu, 7 Jul 2022 12:35:22 +0200 Subject: [PATCH] Add the Session Replay Public API --- .../com/datadog/android/DatadogEndpoint.kt | 32 +++++++++ .../core/configuration/Configuration.kt | 19 +++++- .../com/datadog/android/plugin/Feature.kt | 3 +- .../internal/SessionReplayFeature.kt | 43 ++++++++++++ .../internal/domain/RecordSerializer.kt | 18 +++++ .../SessionReplayRecordPersistenceStrategy.kt | 41 ++++++++++++ .../datadog/android/v2/core/DatadogCore.kt | 13 ++++ .../configuration/ConfigurationBuilderTest.kt | 66 ++++++++++++------- .../dd-sdk-android-session-replay/apiSurface | 0 .../transitiveDependencies | 8 +++ 10 files changed, 219 insertions(+), 24 deletions(-) create mode 100644 dd-sdk-android/src/main/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayFeature.kt create mode 100644 dd-sdk-android/src/main/kotlin/com/datadog/android/sessionreplay/internal/domain/RecordSerializer.kt create mode 100644 dd-sdk-android/src/main/kotlin/com/datadog/android/sessionreplay/internal/domain/SessionReplayRecordPersistenceStrategy.kt create mode 100644 library/dd-sdk-android-session-replay/apiSurface create mode 100644 library/dd-sdk-android-session-replay/transitiveDependencies diff --git a/dd-sdk-android/src/main/kotlin/com/datadog/android/DatadogEndpoint.kt b/dd-sdk-android/src/main/kotlin/com/datadog/android/DatadogEndpoint.kt index a257b29554..ad6ab8a4f7 100644 --- a/dd-sdk-android/src/main/kotlin/com/datadog/android/DatadogEndpoint.kt +++ b/dd-sdk-android/src/main/kotlin/com/datadog/android/DatadogEndpoint.kt @@ -241,4 +241,36 @@ object DatadogEndpoint { const val NTP_3: String = "3.datadog.pool.ntp.org" // endregion + + // region Session Replay + + /** + * The US1 endpoint for Session Replay (US based servers). + * Use this in your [Configuration] if you log on [app.datadoghq.com](https://app.datadoghq.com) + * @see [Configuration] + */ + const val SESSION_REPLAY_US1 = "https://session-replay.browser-intake-datadoghq.com" + + /** + * The US3 endpoint for Session Replay (US based servers). + * Use this in your [Configuration] if you log on [us3.datadoghq.com](https://us3.datadoghq.com) + * @see [Configuration] + */ + const val SESSION_REPLAY_US3 = "https://session-replay.browser-intake-us3-datadoghq.com" + + /** + * The US5 endpoint for Session replay (US based servers). + * Use this in your [Configuration] if you log on [us5.datadoghq.com](https://us5.datadoghq.com) + * @see [Configuration] + */ + const val SESSION_REPLAY_US5 = "https://session-replay.browser-intake-us5-datadoghq.com" + + /** + * The EU1 endpoint for Session Replay (EU based servers). + * Use this in your [Configuration] if you log on [app.datadoghq.eu](https://app.datadoghq.eu) + * @see [Configuration] + */ + const val SESSION_REPLAY_EU1 = "https://session-replay.browser-intake-datadoghq.eu" + + // endregion } diff --git a/dd-sdk-android/src/main/kotlin/com/datadog/android/core/configuration/Configuration.kt b/dd-sdk-android/src/main/kotlin/com/datadog/android/core/configuration/Configuration.kt index b96dea5bc1..6bba944a96 100644 --- a/dd-sdk-android/src/main/kotlin/com/datadog/android/core/configuration/Configuration.kt +++ b/dd-sdk-android/src/main/kotlin/com/datadog/android/core/configuration/Configuration.kt @@ -60,6 +60,7 @@ internal constructor( internal val tracesConfig: Feature.Tracing?, internal val crashReportConfig: Feature.CrashReport?, internal val rumConfig: Feature.RUM?, + internal val sessionReplayConfig: Feature.SessionReplay?, internal val additionalConfig: Map ) { @@ -107,6 +108,11 @@ internal constructor( val rumEventMapper: EventMapper, val backgroundEventTracking: Boolean ) : Feature() + + internal data class SessionReplay( + override val endpointUrl: String, + override val plugins: List + ) : Feature() } // region Builder @@ -123,12 +129,14 @@ internal constructor( val logsEnabled: Boolean, val tracesEnabled: Boolean, val crashReportsEnabled: Boolean, - val rumEnabled: Boolean + val rumEnabled: Boolean, + val sessionReplayEnabled: Boolean ) { private var logsConfig: Feature.Logs = DEFAULT_LOGS_CONFIG private var tracesConfig: Feature.Tracing = DEFAULT_TRACING_CONFIG private var crashReportConfig: Feature.CrashReport = DEFAULT_CRASH_CONFIG private var rumConfig: Feature.RUM = DEFAULT_RUM_CONFIG + private var sessionReplayConfig: Feature.SessionReplay = DEFAULT_SESSION_REPLAY_CONFIG private var additionalConfig: Map = emptyMap() private var coreConfig = DEFAULT_CORE_CONFIG @@ -145,6 +153,7 @@ internal constructor( tracesConfig = if (tracesEnabled) tracesConfig else null, crashReportConfig = if (crashReportsEnabled) crashReportConfig else null, rumConfig = if (rumEnabled) rumConfig else null, + sessionReplayConfig = if (sessionReplayEnabled) sessionReplayConfig else null, additionalConfig = additionalConfig ) } @@ -431,6 +440,9 @@ internal constructor( PluginFeature.CRASH -> crashReportConfig = crashReportConfig.copy( plugins = crashReportConfig.plugins + plugin ) + PluginFeature.SESSION_REPLAY -> + sessionReplayConfig = + sessionReplayConfig.copy(plugins = sessionReplayConfig.plugins + plugin) } } return this @@ -684,6 +696,7 @@ internal constructor( PluginFeature.TRACE -> tracesEnabled PluginFeature.CRASH -> crashReportsEnabled PluginFeature.RUM -> rumEnabled + PluginFeature.SESSION_REPLAY -> sessionReplayEnabled } if (featureEnabled) { @Suppress("UnsafeThirdPartyFunctionCall") // internal safe call @@ -740,6 +753,10 @@ internal constructor( rumEventMapper = NoOpEventMapper(), backgroundEventTracking = false ) + internal val DEFAULT_SESSION_REPLAY_CONFIG = Feature.SessionReplay( + endpointUrl = DatadogEndpoint.SESSION_REPLAY_US1, + plugins = emptyList() + ) internal const val ERROR_FEATURE_DISABLED = "The %s feature has been disabled in your " + "Configuration.Builder, but you're trying to edit the RUM configuration with the " + diff --git a/dd-sdk-android/src/main/kotlin/com/datadog/android/plugin/Feature.kt b/dd-sdk-android/src/main/kotlin/com/datadog/android/plugin/Feature.kt index 23f0863c90..5160089a91 100644 --- a/dd-sdk-android/src/main/kotlin/com/datadog/android/plugin/Feature.kt +++ b/dd-sdk-android/src/main/kotlin/com/datadog/android/plugin/Feature.kt @@ -13,5 +13,6 @@ enum class Feature(internal val featureName: String) { LOG("Logging"), CRASH("Crash Reporting"), TRACE("Tracing"), - RUM("RUM") + RUM("RUM"), + SESSION_REPLAY(featureName = "Session Replay") } diff --git a/dd-sdk-android/src/main/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayFeature.kt b/dd-sdk-android/src/main/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayFeature.kt new file mode 100644 index 0000000000..a4f82613f5 --- /dev/null +++ b/dd-sdk-android/src/main/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayFeature.kt @@ -0,0 +1,43 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.sessionreplay.internal + +import android.content.Context +import com.datadog.android.core.configuration.Configuration +import com.datadog.android.core.internal.CoreFeature +import com.datadog.android.core.internal.SdkFeature +import com.datadog.android.core.internal.net.DataUploader +import com.datadog.android.core.internal.net.NoOpDataUploader +import com.datadog.android.core.internal.persistence.PersistenceStrategy +import com.datadog.android.core.internal.utils.sdkLogger +import com.datadog.android.sessionreplay.internal.domain.SessionReplayRecordPersistenceStrategy + +internal class SessionReplayFeature(coreFeature: CoreFeature) : + SdkFeature(coreFeature) { + + override fun createPersistenceStrategy( + context: Context, + configuration: Configuration.Feature.SessionReplay + ): PersistenceStrategy { + return SessionReplayRecordPersistenceStrategy( + coreFeature.trackingConsentProvider, + context, + coreFeature.persistenceExecutorService, + sdkLogger, + coreFeature.localDataEncryption + ) + } + + override fun createUploader(configuration: Configuration.Feature.SessionReplay): DataUploader { + // TODO: This will be added later in RUMM-2273 + return NoOpDataUploader() + } + + companion object { + const val SESSION_REPLAY_FEATURE_NAME = "session-replay" + } +} diff --git a/dd-sdk-android/src/main/kotlin/com/datadog/android/sessionreplay/internal/domain/RecordSerializer.kt b/dd-sdk-android/src/main/kotlin/com/datadog/android/sessionreplay/internal/domain/RecordSerializer.kt new file mode 100644 index 0000000000..4e6cd8de0e --- /dev/null +++ b/dd-sdk-android/src/main/kotlin/com/datadog/android/sessionreplay/internal/domain/RecordSerializer.kt @@ -0,0 +1,18 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.sessionreplay.internal.domain + +import com.datadog.android.core.internal.persistence.Serializer + +internal class RecordSerializer : Serializer { + override fun serialize(model: Any): String? { + TODO( + "Not yet implemented. This will be switched to a Serializer once the" + + "models will be in place." + ) + } +} diff --git a/dd-sdk-android/src/main/kotlin/com/datadog/android/sessionreplay/internal/domain/SessionReplayRecordPersistenceStrategy.kt b/dd-sdk-android/src/main/kotlin/com/datadog/android/sessionreplay/internal/domain/SessionReplayRecordPersistenceStrategy.kt new file mode 100644 index 0000000000..5b3fce5ca1 --- /dev/null +++ b/dd-sdk-android/src/main/kotlin/com/datadog/android/sessionreplay/internal/domain/SessionReplayRecordPersistenceStrategy.kt @@ -0,0 +1,41 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.sessionreplay.internal.domain + +import android.content.Context +import com.datadog.android.core.internal.persistence.PayloadDecoration +import com.datadog.android.core.internal.persistence.file.FileMover +import com.datadog.android.core.internal.persistence.file.advanced.FeatureFileOrchestrator +import com.datadog.android.core.internal.persistence.file.batch.BatchFilePersistenceStrategy +import com.datadog.android.core.internal.persistence.file.batch.BatchFileReaderWriter +import com.datadog.android.core.internal.privacy.ConsentProvider +import com.datadog.android.log.Logger +import com.datadog.android.security.Encryption +import com.datadog.android.sessionreplay.internal.SessionReplayFeature +import java.util.concurrent.ExecutorService + +internal class SessionReplayRecordPersistenceStrategy( + consentProvider: ConsentProvider, + context: Context, + executorService: ExecutorService, + internalLogger: Logger, + localDataEncryption: Encryption? +) : BatchFilePersistenceStrategy( + FeatureFileOrchestrator( + consentProvider, + context, + SessionReplayFeature.SESSION_REPLAY_FEATURE_NAME, + executorService, + internalLogger + ), + executorService, + RecordSerializer(), + PayloadDecoration.NEW_LINE_DECORATION, + internalLogger, + BatchFileReaderWriter.create(internalLogger, localDataEncryption), + FileMover(internalLogger) +) diff --git a/dd-sdk-android/src/main/kotlin/com/datadog/android/v2/core/DatadogCore.kt b/dd-sdk-android/src/main/kotlin/com/datadog/android/v2/core/DatadogCore.kt index e0ec00e3f6..3116dfface 100644 --- a/dd-sdk-android/src/main/kotlin/com/datadog/android/v2/core/DatadogCore.kt +++ b/dd-sdk-android/src/main/kotlin/com/datadog/android/v2/core/DatadogCore.kt @@ -28,6 +28,7 @@ import com.datadog.android.log.internal.LogsFeature import com.datadog.android.log.model.LogEvent import com.datadog.android.privacy.TrackingConsent import com.datadog.android.rum.internal.RumFeature +import com.datadog.android.sessionreplay.internal.SessionReplayFeature import com.datadog.android.tracing.internal.TracingFeature import com.datadog.android.v2.api.FeatureScope import com.datadog.android.v2.api.FeatureStorageConfiguration @@ -60,6 +61,7 @@ class DatadogCore( null internal var webViewLogsFeature: SdkFeature? = null internal var webViewRumFeature: SdkFeature? = null + internal var sessionReplayFeature: SdkFeature? = null init { val isDebug = isAppDebuggable(context) @@ -176,6 +178,7 @@ class DatadogCore( initializeTracingFeature(mutableConfig.tracesConfig, appContext) initializeRumFeature(mutableConfig.rumConfig, appContext) initializeCrashReportFeature(mutableConfig.crashReportConfig, appContext) + initializeSessionReplayFeature(mutableConfig.sessionReplayConfig, appContext) coreFeature.ndkCrashHandler.handleNdkCrash( logsFeature?.persistenceStrategy?.getWriter() ?: NoOpDataWriter(), @@ -252,6 +255,16 @@ class DatadogCore( } } + private fun initializeSessionReplayFeature( + configuration: Configuration.Feature.SessionReplay?, + appContext: Context + ) { + if (configuration != null) { + sessionReplayFeature = SessionReplayFeature(coreFeature) + sessionReplayFeature?.initialize(appContext, configuration) + } + } + @Suppress("FunctionMaxLength") private fun modifyConfigurationForDeveloperDebug(configuration: Configuration): Configuration { return configuration.copy( diff --git a/dd-sdk-android/src/test/kotlin/com/datadog/android/core/configuration/ConfigurationBuilderTest.kt b/dd-sdk-android/src/test/kotlin/com/datadog/android/core/configuration/ConfigurationBuilderTest.kt index dcca3ffc90..273ffdae6b 100644 --- a/dd-sdk-android/src/test/kotlin/com/datadog/android/core/configuration/ConfigurationBuilderTest.kt +++ b/dd-sdk-android/src/test/kotlin/com/datadog/android/core/configuration/ConfigurationBuilderTest.kt @@ -85,7 +85,8 @@ internal class ConfigurationBuilderTest { logsEnabled = true, tracesEnabled = true, crashReportsEnabled = true, - rumEnabled = true + rumEnabled = true, + sessionReplayEnabled = true ) } @@ -161,7 +162,8 @@ internal class ConfigurationBuilderTest { logsEnabled = false, tracesEnabled = true, crashReportsEnabled = true, - rumEnabled = true + rumEnabled = true, + sessionReplayEnabled = true ) // When @@ -182,7 +184,8 @@ internal class ConfigurationBuilderTest { logsEnabled = true, tracesEnabled = false, crashReportsEnabled = true, - rumEnabled = true + rumEnabled = true, + sessionReplayEnabled = true ) // When @@ -203,7 +206,8 @@ internal class ConfigurationBuilderTest { logsEnabled = true, tracesEnabled = true, crashReportsEnabled = false, - rumEnabled = true + rumEnabled = true, + sessionReplayEnabled = true ) // When @@ -224,7 +228,8 @@ internal class ConfigurationBuilderTest { logsEnabled = true, tracesEnabled = true, crashReportsEnabled = true, - rumEnabled = false + rumEnabled = false, + sessionReplayEnabled = true ) // When @@ -814,7 +819,8 @@ internal class ConfigurationBuilderTest { logsEnabled = true, tracesEnabled = true, crashReportsEnabled = true, - rumEnabled = false + rumEnabled = false, + sessionReplayEnabled = true ) // When @@ -840,7 +846,8 @@ internal class ConfigurationBuilderTest { logsEnabled = true, tracesEnabled = true, crashReportsEnabled = true, - rumEnabled = false + rumEnabled = false, + sessionReplayEnabled = true ) // When @@ -864,7 +871,8 @@ internal class ConfigurationBuilderTest { logsEnabled = true, tracesEnabled = true, crashReportsEnabled = true, - rumEnabled = false + rumEnabled = false, + sessionReplayEnabled = true ) val viewStrategy: ViewTrackingStrategy = mock() @@ -891,7 +899,8 @@ internal class ConfigurationBuilderTest { logsEnabled = true, tracesEnabled = true, crashReportsEnabled = true, - rumEnabled = false + rumEnabled = false, + sessionReplayEnabled = true ) // When @@ -915,7 +924,8 @@ internal class ConfigurationBuilderTest { logsEnabled = true, tracesEnabled = true, crashReportsEnabled = true, - rumEnabled = false + rumEnabled = false, + sessionReplayEnabled = true ) val eventMapper: ViewEventMapper = mock() @@ -940,7 +950,8 @@ internal class ConfigurationBuilderTest { logsEnabled = true, tracesEnabled = true, crashReportsEnabled = true, - rumEnabled = false + rumEnabled = false, + sessionReplayEnabled = true ) val eventMapper: EventMapper = mock() @@ -965,7 +976,8 @@ internal class ConfigurationBuilderTest { logsEnabled = true, tracesEnabled = true, crashReportsEnabled = true, - rumEnabled = false + rumEnabled = false, + sessionReplayEnabled = true ) val eventMapper: EventMapper = mock() @@ -990,7 +1002,8 @@ internal class ConfigurationBuilderTest { logsEnabled = true, tracesEnabled = true, crashReportsEnabled = true, - rumEnabled = false + rumEnabled = false, + sessionReplayEnabled = true ) val eventMapper: EventMapper = mock() @@ -1015,7 +1028,8 @@ internal class ConfigurationBuilderTest { logsEnabled = true, tracesEnabled = true, crashReportsEnabled = true, - rumEnabled = false + rumEnabled = false, + sessionReplayEnabled = true ) val eventMapper: EventMapper = mock() @@ -1040,7 +1054,8 @@ internal class ConfigurationBuilderTest { logsEnabled = false, tracesEnabled = true, crashReportsEnabled = true, - rumEnabled = true + rumEnabled = true, + sessionReplayEnabled = true ) val logsPlugin: DatadogPlugin = mock() @@ -1065,7 +1080,8 @@ internal class ConfigurationBuilderTest { logsEnabled = true, tracesEnabled = false, crashReportsEnabled = true, - rumEnabled = true + rumEnabled = true, + sessionReplayEnabled = true ) val tracesPlugin: DatadogPlugin = mock() @@ -1090,7 +1106,8 @@ internal class ConfigurationBuilderTest { logsEnabled = true, tracesEnabled = true, crashReportsEnabled = false, - rumEnabled = true + rumEnabled = true, + sessionReplayEnabled = true ) val crashPlugin: DatadogPlugin = mock() @@ -1115,7 +1132,8 @@ internal class ConfigurationBuilderTest { logsEnabled = true, tracesEnabled = true, crashReportsEnabled = true, - rumEnabled = false + rumEnabled = false, + sessionReplayEnabled = true ) val rumPlugin: DatadogPlugin = mock() @@ -1142,7 +1160,8 @@ internal class ConfigurationBuilderTest { logsEnabled = false, tracesEnabled = true, crashReportsEnabled = true, - rumEnabled = true + rumEnabled = true, + sessionReplayEnabled = true ) // When @@ -1168,7 +1187,8 @@ internal class ConfigurationBuilderTest { logsEnabled = true, tracesEnabled = false, crashReportsEnabled = true, - rumEnabled = true + rumEnabled = true, + sessionReplayEnabled = true ) // When @@ -1194,7 +1214,8 @@ internal class ConfigurationBuilderTest { logsEnabled = true, tracesEnabled = true, crashReportsEnabled = false, - rumEnabled = true + rumEnabled = true, + sessionReplayEnabled = true ) // When @@ -1220,7 +1241,8 @@ internal class ConfigurationBuilderTest { logsEnabled = true, tracesEnabled = true, crashReportsEnabled = true, - rumEnabled = false + rumEnabled = false, + sessionReplayEnabled = true ) // When diff --git a/library/dd-sdk-android-session-replay/apiSurface b/library/dd-sdk-android-session-replay/apiSurface new file mode 100644 index 0000000000..e69de29bb2 diff --git a/library/dd-sdk-android-session-replay/transitiveDependencies b/library/dd-sdk-android-session-replay/transitiveDependencies new file mode 100644 index 0000000000..a8e93c8d38 --- /dev/null +++ b/library/dd-sdk-android-session-replay/transitiveDependencies @@ -0,0 +1,8 @@ +Dependencies List + +org.jetbrains.kotlin:kotlin-stdlib-common:1.6.10 : 195 Kb +org.jetbrains.kotlin:kotlin-stdlib:1.6.10 : 1472 Kb +org.jetbrains:annotations:13.0 : 17 Kb + +Total transitive dependencies size : 1685 Kb +