Skip to content

Commit

Permalink
Refactor Android to wrap core SDK
Browse files Browse the repository at this point in the history
  • Loading branch information
louiszawadzki committed Jan 23, 2024
1 parent b6ce681 commit de6a7d5
Show file tree
Hide file tree
Showing 6 changed files with 192 additions and 85 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@ package com.datadog.reactnative
import android.content.Context
import com.datadog.android.Datadog
import com.datadog.android._InternalProxy
import com.datadog.android.api.SdkCore
import com.datadog.android.api.context.DatadogContext
import com.datadog.android.api.feature.FeatureScope
import com.datadog.android.api.feature.FeatureSdkCore
import com.datadog.android.core.InternalSdkCore
import com.datadog.android.core.configuration.Configuration
import com.datadog.android.log.Logs
import com.datadog.android.log.LogsConfiguration
Expand All @@ -27,37 +30,15 @@ import com.datadog.android.webview.WebViewTracking
* Internal object used to add internal testing.
*/
object DatadogSDKWrapperStorage {
internal val onFeatureEnabledListeners: MutableList<(FeatureScope, featureName: String) -> Unit> = mutableListOf()
internal val onInitializedListeners: MutableList<(FeatureSdkCore?) -> Unit> = mutableListOf()
internal val onInitializedListeners: MutableList<(InternalSdkCore?) -> Unit> = mutableListOf()

/**
* Adds a Listener called whenever a feature is enabled on the core.
* Adds a Listener called when the core is initialized.
*/
fun addOnFeatureEnabledListener(listener: (FeatureScope, featureName: String) -> Unit) {
onFeatureEnabledListeners.add(listener)
}

/**
* Adds a Listener called when the core is completely initialized.
* This should be removed once setting features of the core does not break RUM Views.
*/
fun addOnInitializedListener(listener: (FeatureSdkCore?) -> Unit) {
fun addOnInitializedListener(listener: (InternalSdkCore?) -> Unit) {
onInitializedListeners.add(listener)
}

/**
* To be called in RN SDKs after enabling a feature (e.g. Session Replay).
*/
@Suppress("FunctionMaxLength")
fun notifyOnFeatureEnabledListeners(featureName: String) {
val feature = core?.getFeature(featureName)
if (feature !== null) {
onFeatureEnabledListeners.forEach {
it(feature, featureName)
}
}
}

/**
* Exposed for testing purposes only.
*/
Expand All @@ -67,20 +48,20 @@ object DatadogSDKWrapperStorage {
}
}

private var core: FeatureSdkCore? = null
private var core: InternalSdkCore? = null

/**
* Exposed for testing purposes only.
* Sets instance of core SDK to be used to initialize features.
*/
fun setSdkCore(core: FeatureSdkCore?) {
fun setSdkCore(core: InternalSdkCore?) {
this.core = core
}

/**
* Returns the core used for registering RN features.
*/
fun getSdkCore(): FeatureSdkCore? {
return core
fun getSdkCore(): SdkCore {
return core ?: Datadog.getInstance()
}
}

Expand All @@ -102,7 +83,7 @@ internal class DatadogSDKWrapper : DatadogWrapper {
private var webViewProxy: WebViewTracking._InternalWebViewProxy? = null
get() {
if (field == null && isInitialized()) {
field = WebViewTracking._InternalWebViewProxy(Datadog.getInstance())
field = WebViewTracking._InternalWebViewProxy(DatadogSDKWrapperStorage.getSdkCore())
}

return field
Expand All @@ -118,22 +99,26 @@ internal class DatadogSDKWrapper : DatadogWrapper {
consent: TrackingConsent
) {
val core = Datadog.initialize(context, configuration, consent)
DatadogSDKWrapperStorage.setSdkCore(core as FeatureSdkCore)
DatadogSDKWrapperStorage.setSdkCore(core as InternalSdkCore)
DatadogSDKWrapperStorage.notifyOnInitializedListeners()
}

override fun enableRum(configuration: RumConfiguration) {
Rum.enable(configuration)
DatadogSDKWrapperStorage.notifyOnFeatureEnabledListeners("rum")
DatadogSDKWrapperStorage.getSdkCore()?.let {
Rum.enable(configuration, it)
}
}

override fun enableLogs(configuration: LogsConfiguration) {
Logs.enable(configuration)
DatadogSDKWrapperStorage.notifyOnFeatureEnabledListeners("logs")
DatadogSDKWrapperStorage.getSdkCore()?.let {
Logs.enable(configuration, it)
}
}

override fun enableTrace(configuration: TraceConfiguration) {
Trace.enable(configuration)
DatadogSDKWrapperStorage.notifyOnFeatureEnabledListeners("tracing")
DatadogSDKWrapperStorage.getSdkCore()?.let {
Trace.enable(configuration, it)
}
}

override fun setUserInfo(
Expand Down Expand Up @@ -177,6 +162,6 @@ internal class DatadogSDKWrapper : DatadogWrapper {
}

override fun getRumMonitor(): RumMonitor {
return GlobalRumMonitor.get()
return GlobalRumMonitor.get(DatadogSDKWrapperStorage.getSdkCore())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class DdLogsImplementation(
private val datadog: DatadogWrapper = DatadogSDKWrapper()
) {
private val reactNativeLogger: Logger by lazy {
logger ?: Logger.Builder()
logger ?: Logger.Builder(DatadogSDKWrapperStorage.getSdkCore())
.setLogcatLogsEnabled(true)
.setName("DdLogs")
.build()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,6 @@ class DdSdkImplementation(

initialized.set(true)

DatadogSDKWrapperStorage.notifyOnInitializedListeners()

promise.resolve(null)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,6 @@ internal class DdSdkTest {

DatadogSDKWrapperStorage.setSdkCore(null)
DatadogSDKWrapperStorage.onInitializedListeners.clear()
DatadogSDKWrapperStorage.onFeatureEnabledListeners.clear()
}

@AfterEach
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,39 +6,42 @@

package com.datadog.reactnative.internaltesting

import com.datadog.android.api.InternalLogger
import com.datadog.android.api.context.DatadogContext
import com.datadog.android.api.context.NetworkInfo
import com.datadog.android.api.context.TimeInfo
import com.datadog.android.api.feature.Feature
import com.datadog.android.api.feature.FeatureScope
import com.datadog.android.api.storage.EventBatchWriter
import com.datadog.android.api.storage.RawBatchEvent
import com.datadog.android.core.InternalSdkCore
import com.datadog.android.core.internal.net.FirstPartyHostHeaderTypeResolver
import com.datadog.android.trace.TracingHeaderType
import com.datadog.reactnative.DatadogSDKWrapperStorage
import com.facebook.react.bridge.Promise
import com.google.gson.Gson
import okhttp3.HttpUrl
import kotlin.concurrent.thread

/**
* The entry point to use Datadog's internal testing feature.
*/
class DdInternalTestingImplementation() {
internal val featureScopes = mutableMapOf<String, FeatureScopeInterceptor>()
private var wrappedCore: StubSDKCore? = null

/**
* Clears all data for all features.
*/
fun clearData(promise: Promise) {
featureScopes["logs"]?.clearData()
featureScopes["rum"]?.clearData()
featureScopes["tracing"]?.clearData()
featureScopes["session-replay"]?.clearData()

wrappedCore?.clearData()
promise.resolve(null)
}

/**
* Retrieves the list of events for a given feature.
*/
fun getAllEvents(feature: String, promise: Promise) {
val events = featureScopes[feature]?.eventsWritten()?.toList() ?: emptyList<Any>()
val events = wrappedCore?.eventsWritten(feature)
val eventsJson = Gson().toJson(events)
promise.resolve(eventsJson)
}
Expand All @@ -47,45 +50,139 @@ class DdInternalTestingImplementation() {
* Enable native testing module.
*/
fun enable(promise: Promise) {
DatadogSDKWrapperStorage.addOnFeatureEnabledListener { featureScope, featureName ->
val core = DatadogSDKWrapperStorage.getSdkCore()
registerFeature(featureScope, featureName, core as InternalSdkCore)
}
DatadogSDKWrapperStorage.addOnInitializedListener { core ->
core?.let { ddCore ->
/**
* There's a bug when trying a new RUM View after the features have been reset.
* When debugging we stop in DatadogCore::updateFeatureContext on the first line trying to get the feature.
* It's because we expect a SDKFeature and we give FeatureScopeInterceptor.
*
* By waiting for 3 seconds on a separate thread we give enough time for the "Application Start" View and first
* RUM view to be created.
*
* If we switch from `features[featureName]` to `getFeature(featureName)` in DatadogCore::updateFeatureContext
* we can remove the sleep here.
*/
thread {
Thread.sleep(3000)
ddCore.javaClass.declaredFields.firstOrNull { it.name == "features" }?.let {
it.isAccessible = true
it.set(core, featureScopes)
}
}
DatadogSDKWrapperStorage.addOnInitializedListener { ddCore ->
ddCore?.let { core ->
this.wrappedCore = StubSDKCore(core)
DatadogSDKWrapperStorage.setSdkCore(this.wrappedCore)
}
}
promise.resolve(null)
}

private fun registerFeature(featureScope: FeatureScope, featureName: String, core: InternalSdkCore) {
val instrumentedScope = FeatureScopeInterceptor(featureScope, core)
featureScopes[featureName] = instrumentedScope
}

companion object {
internal const val NAME = "DdInternalTesting"
}
}

internal class StubSDKCore(
private val core: InternalSdkCore
) : InternalSdkCore by core {
internal val featureScopes = mutableMapOf<String, FeatureScopeInterceptor>()

// region Stub

/**
* Lists all the events written by a given feature.
* @param featureName the name of the feature
* @return a list of [StubEvent]
*/
fun eventsWritten(featureName: String): List<String> {
return featureScopes[featureName]?.eventsWritten()?.toList() ?: emptyList<String>()
}

fun clearData() {
featureScopes.forEach { entry ->
featureScopes[entry.key]?.clearData()
}
}

// endregion

// region InternalSdkCore

override val firstPartyHostResolver: FirstPartyHostHeaderTypeResolver =
StubFirstPartyHostHeaderTypeResolver()

override fun getDatadogContext(): DatadogContext? {
return core.getDatadogContext()
}

override val networkInfo: NetworkInfo
get() = core.networkInfo

// endregion

// region FeatureSdkCore

override val internalLogger: InternalLogger = core.internalLogger

override fun registerFeature(feature: Feature) {
core.registerFeature(feature)
core.getFeature(feature.name)?.let {
featureScopes[feature.name] = FeatureScopeInterceptor(it, core)
}
}

override fun getFeature(featureName: String): FeatureScope? {
return featureScopes[featureName]
}

override fun updateFeatureContext(
featureName: String,
updateCallback: (context: MutableMap<String, Any?>) -> Unit
) {
core.updateFeatureContext(featureName, updateCallback)
}

override fun getFeatureContext(featureName: String): Map<String, Any?> {
return core.getFeatureContext(featureName)
}

// endregion

// region SdkCore

override val service: String
get() {
return core.service
}

override val time: TimeInfo
get() {
val nanos = System.nanoTime()
return TimeInfo(
deviceTimeNs = nanos,
serverTimeNs = nanos,
serverTimeOffsetMs = 0L,
serverTimeOffsetNs = 0L
)
}

override fun setUserInfo(
id: String?,
name: String?,
email: String?,
extraInfo: Map<String, Any?>
) {
core.setUserInfo(id, name, email, extraInfo)
}

// endregion
}

class StubFirstPartyHostHeaderTypeResolver :
FirstPartyHostHeaderTypeResolver {

// region FirstPartyHostHeaderTypeResolver

override fun isEmpty(): Boolean = false

override fun isFirstPartyUrl(url: HttpUrl): Boolean = true

override fun isFirstPartyUrl(url: String): Boolean = true

override fun headerTypesForUrl(url: String): Set<TracingHeaderType> = setOf(TracingHeaderType.TRACECONTEXT)

override fun headerTypesForUrl(url: HttpUrl): Set<TracingHeaderType> = setOf(TracingHeaderType.TRACECONTEXT)

override fun getAllHeaderTypes(): Set<TracingHeaderType> = setOf(TracingHeaderType.TRACECONTEXT)

// endregion
}



internal class FeatureScopeInterceptor(
private val featureScope: FeatureScope,
private val core: InternalSdkCore,
Expand All @@ -108,8 +205,9 @@ internal class FeatureScopeInterceptor(
) {
featureScope.withWriteContext(forceNewBatch, callback)

val context = core.getDatadogContext()!!
callback(context, eventsBatchInterceptor)
core.getDatadogContext()?.let {
callback(it, eventsBatchInterceptor)
}
}

// endregion
Expand Down
Loading

0 comments on commit de6a7d5

Please sign in to comment.