Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add internal testing tools package #587

Merged
merged 13 commits into from
Jan 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@
package com.datadog.reactnative

import android.content.Context
import android.util.Log
import com.datadog.android.Datadog
import com.datadog.android._InternalProxy
import com.datadog.android.api.SdkCore
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 @@ -21,6 +24,51 @@ import com.datadog.android.trace.Trace
import com.datadog.android.trace.TraceConfiguration
import com.datadog.android.webview.WebViewTracking

/**
* Internal object used to add internal testing.
*/
object DatadogSDKWrapperStorage {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we don't want to make it available to the customers, we need to make this class internal.

Suggested change
object DatadogSDKWrapperStorage {
internal object DatadogSDKWrapperStorage {

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I make it internal, I cannot import it in the internal testing module.
Is it possible to still import it?

And I think it can be ok to leave it available to customers. Most of them won't do anything, and maybe that can make things a little easier for some customers with hybrid applications.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's keep it public then, but if it used only for the tests, nothing in the name reflects that the intended usage is for the tests only.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will also be used in Session Replay to make sure we use the same core for all features - it's one of the next tasks on the topic.
When we'll split the RN SDK in core/RUM/Logs/Trace this will also be used to get the core in the feature packages.

Let me know if you think we should still change the name to something more explicit here.

internal val onInitializedListeners: MutableList<(InternalSdkCore) -> Unit> = mutableListOf()
private var core: InternalSdkCore? = null

/**
* Adds a Listener called when the core is initialized.
*/
fun addOnInitializedListener(listener: (InternalSdkCore) -> Unit) {
onInitializedListeners.add(listener)
}

/**
* Exposed for testing purposes only.
*/
fun notifyOnInitializedListeners(ddCore: InternalSdkCore) {
onInitializedListeners.forEach {
it(ddCore)
}
}

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

/**
* Returns the core set by setSdkCore or the default core instance by default.
*/
fun getSdkCore(): SdkCore {
core?.let {
return it
}
Log.d(
DatadogSDKWrapperStorage::class.java.canonicalName,
"SdkCore was not set in DatadogSDKWrapperStorage, using default instance."
)
return Datadog.getInstance()
}
}

internal class DatadogSDKWrapper : DatadogWrapper {

// We use Kotlin backing field here to initialize once the telemetry proxy
Expand All @@ -39,7 +87,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 @@ -54,19 +102,21 @@ internal class DatadogSDKWrapper : DatadogWrapper {
configuration: Configuration,
consent: TrackingConsent
) {
Datadog.initialize(context, configuration, consent)
val core = Datadog.initialize(context, configuration, consent)
DatadogSDKWrapperStorage.setSdkCore(core as InternalSdkCore)
DatadogSDKWrapperStorage.notifyOnInitializedListeners(core)
}

override fun enableRum(configuration: RumConfiguration) {
Rum.enable(configuration)
Rum.enable(configuration, DatadogSDKWrapperStorage.getSdkCore())
}

override fun enableLogs(configuration: LogsConfiguration) {
Logs.enable(configuration)
Logs.enable(configuration, DatadogSDKWrapperStorage.getSdkCore())
}

override fun enableTrace(configuration: TraceConfiguration) {
Trace.enable(configuration)
Trace.enable(configuration, DatadogSDKWrapperStorage.getSdkCore())
}

override fun setUserInfo(
Expand Down Expand Up @@ -110,6 +160,7 @@ 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 @@ -149,6 +149,9 @@ internal class DdSdkTest {
true
}
testedBridgeSdk = DdSdkImplementation(mockReactContext, mockDatadog)

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

@AfterEach
Expand Down
154 changes: 154 additions & 0 deletions packages/core/ios/Sources/DatadogSDKWrapper.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
/*
* 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.
*/


import DatadogCore
import DatadogRUM
import DatadogLogs
import DatadogTrace
import DatadogCrashReporting
import DatadogWebViewTracking
import DatadogInternal
import Foundation

public typealias OnCoreInitializedListener = (DatadogCoreProtocol) -> Void

/// Wrapper around the Datadog SDK. Use DatadogSDKWrapper.shared to access the instance.
public class DatadogSDKWrapper {
// Singleton
public static var shared = DatadogSDKWrapper()

private init() {}

// Initialization callbacks
internal var onCoreInitializedListeners: [OnCoreInitializedListener] = []

public func addOnCoreInitializedListener(listener:@escaping OnCoreInitializedListener) {
onCoreInitializedListeners.append(listener)
}

// Core instance
private var coreInstance: DatadogCoreProtocol? = nil

/// This is intended for internal testing only.
public func setCoreInstance(core: DatadogCoreProtocol?) {
self.coreInstance = core
}

/// This is not supposed to be used in the SDK itself, rather by other SDKs like Session Replay.
public func getCoreInstance() -> DatadogCoreProtocol? {
return coreInstance
}

// SDK Wrapper
internal func initialize(
with configuration: Datadog.Configuration,
trackingConsent: TrackingConsent
) -> Void {
let core = Datadog.initialize(with: configuration, trackingConsent: trackingConsent)
setCoreInstance(core: core)
for listener in onCoreInitializedListeners {
listener(core)
}
}

internal func isInitialized() -> Bool {
return Datadog.isInitialized()
}

internal func enableRUM(with configuration: RUM.Configuration) {
if let core = coreInstance {
RUM.enable(with: configuration, in: core)
} else {
consolePrint("Core instance was not found when initializing RUM.")
}
}

internal func enableLogs(with configuration: Logs.Configuration) {
if let core = coreInstance {
Logs.enable(with: configuration, in: core)
} else {
consolePrint("Core instance was not found when initializing Logs.")
}
}

internal func enableTrace(with configuration: Trace.Configuration) {
if let core = coreInstance {
Trace.enable(with: configuration, in: core)
} else {
consolePrint("Core instance was not found when initializing Trace.")
}
}

internal func enableCrashReporting() {
if let core = coreInstance {
CrashReporting.enable(in: core)
} else {
consolePrint("Core instance was not found when initializing CrashReporting.")
}
}

internal func createLogger() -> LoggerProtocol {
if let core = coreInstance {
return Logger.create(with: Logger.Configuration(networkInfoEnabled: true, consoleLogFormat: .short), in: core)
} else {
consolePrint("Core instance was not found when creating Logger.")
return Logger.create(with: Logger.Configuration(networkInfoEnabled: true, consoleLogFormat: .short))
}
}

// Telemetry
internal func telemetryDebug(id: String, message: String) {
return Datadog._internal.telemetry.debug(id: id, message: message)
}

internal func telemetryError(id: String, message: String, kind: String?, stack: String?) {
return Datadog._internal.telemetry.error(id: id, message: message, kind: kind, stack: stack)
}

internal func overrideTelemetryConfiguration(
initializationType: String? = nil,
reactNativeVersion: String? = nil,
reactVersion: String? = nil,
trackCrossPlatformLongTasks: Bool? = nil,
trackErrors: Bool? = nil,
trackInteractions: Bool? = nil,
trackLongTask: Bool? = nil,
trackNativeErrors: Bool? = nil,
trackNativeLongTasks: Bool? = nil,
trackNetworkRequests: Bool? = nil
) {
coreInstance?.telemetry.configuration(
initializationType: initializationType,
reactNativeVersion: reactNativeVersion,
reactVersion: reactVersion,
trackCrossPlatformLongTasks: trackCrossPlatformLongTasks,
trackErrors: trackErrors,
trackInteractions: trackInteractions,
trackLongTask: trackLongTask,
trackNativeErrors: trackNativeErrors,
trackNativeLongTasks: trackNativeLongTasks,
trackNetworkRequests: trackNetworkRequests
)
}

// Webview
private var webviewMessageEmitter: InternalExtension<WebViewTracking>.AbstractMessageEmitter?

internal func enableWebviewTracking() {
if let core = coreInstance {
webviewMessageEmitter = WebViewTracking._internal.messageEmitter(in: core)
} else {
consolePrint("Core instance was not found when initializing Webview tracking.")
}
}

internal func sendWebviewMessage(body: NSString) throws {
try self.webviewMessageEmitter?.send(body: body)
}
}


3 changes: 1 addition & 2 deletions packages/core/ios/Sources/DdLogsImplementation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

import Foundation
import DatadogLogs
import DatadogCore

@objc
public class DdLogsImplementation: NSObject {
Expand All @@ -21,7 +20,7 @@ public class DdLogsImplementation: NSObject {

@objc
public override convenience init() {
self.init({ Logger.create(with: Logger.Configuration(networkInfoEnabled: true, consoleLogFormat: .short)) }, { Datadog.isInitialized() })
self.init({ DatadogSDKWrapper.shared.createLogger() }, { DatadogSDKWrapper.shared.isInitialized() })
}

@objc
Expand Down
34 changes: 17 additions & 17 deletions packages/core/ios/Sources/DdSdkImplementation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,11 @@ public class DdSdkImplementation: NSObject {
let sdkConfiguration = configuration.asDdSdkConfiguration()

// TODO: see if this `if` is still needed
if Datadog.isInitialized() {
if DatadogSDKWrapper.shared.isInitialized() {
// Initializing the SDK twice results in Global.rum and
// Global.sharedTracer to be set to no-op instances
consolePrint("Datadog SDK is already initialized, skipping initialization.")
Datadog._internal.telemetry.debug(id: "datadog_react_native: RN SDK was already initialized in native", message: "RN SDK was already initialized in native")
DatadogSDKWrapper.shared.telemetryDebug(id: "datadog_react_native: RN SDK was already initialized in native", message: "RN SDK was already initialized in native")

// This block is called when SDK is reinitialized and the javascript has been wiped out.
// In this case, we need to restart the refresh rate monitor, as the javascript thread
Expand All @@ -82,30 +82,30 @@ public class DdSdkImplementation: NSObject {

let sdkConfig = self.buildSDKConfiguration(configuration: sdkConfiguration)
let consent = self.buildTrackingConsent(consent: sdkConfiguration.trackingConsent)
let core = Datadog.initialize(with: sdkConfig, trackingConsent: consent)
DatadogSDKWrapper.shared.initialize(with: sdkConfig, trackingConsent: consent)

self.enableFeatures(sdkConfiguration: sdkConfiguration, core: core)
self.enableFeatures(sdkConfiguration: sdkConfiguration)
self.startJSRefreshRateMonitoring(sdkConfiguration: sdkConfiguration)

resolve(nil)
}
}

func enableFeatures(sdkConfiguration: DdSdkConfiguration, core: DatadogCoreProtocol) {
func enableFeatures(sdkConfiguration: DdSdkConfiguration) {
let rumConfig = buildRUMConfiguration(configuration: sdkConfiguration)
RUM.enable(with: rumConfig, in: core)
DatadogSDKWrapper.shared.enableRUM(with: rumConfig)

Logs.enable(with: Logs.Configuration(), in: core)
DatadogSDKWrapper.shared.enableLogs(with: Logs.Configuration())

Trace.enable(with: Trace.Configuration(), in: core)
DatadogSDKWrapper.shared.enableTrace(with: Trace.Configuration())

if sdkConfiguration.nativeCrashReportEnabled ?? false {
CrashReporting.enable(in: core)
DatadogSDKWrapper.shared.enableCrashReporting()
}

self.webviewMessageEmitter = WebViewTracking._internal.messageEmitter(in: core)
DatadogSDKWrapper.shared.enableWebviewTracking()

overrideReactNativeTelemetry(rnConfiguration: sdkConfiguration, core: core)
overrideReactNativeTelemetry(rnConfiguration: sdkConfiguration)
}

@objc
Expand Down Expand Up @@ -139,28 +139,28 @@ public class DdSdkImplementation: NSObject {

@objc
public func telemetryDebug(message: NSString, resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void {
Datadog._internal.telemetry.debug(id: "datadog_react_native:\(message)", message: message as String)
DatadogSDKWrapper.shared.telemetryDebug(id: "datadog_react_native:\(message)", message: message as String)
resolve(nil)
}

@objc
public func telemetryError(message: NSString, stack: NSString, kind: NSString, resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void {
Datadog._internal.telemetry.error(id: "datadog_react_native:\(String(describing: kind)):\(message)", message: message as String, kind: kind as? String, stack: stack as? String)
DatadogSDKWrapper.shared.telemetryError(id: "datadog_react_native:\(String(describing: kind)):\(message)", message: message as String, kind: kind as? String, stack: stack as? String)
resolve(nil)
}

@objc
public func consumeWebviewEvent(message: NSString, resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void {
do{
try self.webviewMessageEmitter?.send(body: message)
try DatadogSDKWrapper.shared.sendWebviewMessage(body: message)
} catch {
Datadog._internal.telemetry.error(id: "datadog_react_native:\(error.localizedDescription)", message: "The message being sent was:\(message)" as String, kind: "WebViewEventBridgeError" as String, stack: String(describing: error) as String)
DatadogSDKWrapper.shared.telemetryError(id: "datadog_react_native:\(error.localizedDescription)", message: "The message being sent was:\(message)" as String, kind: "WebViewEventBridgeError" as String, stack: String(describing: error) as String)
}
resolve(nil)
}

func overrideReactNativeTelemetry(rnConfiguration: DdSdkConfiguration, core: DatadogCoreProtocol) -> Void {
core.telemetry.configuration(
func overrideReactNativeTelemetry(rnConfiguration: DdSdkConfiguration) -> Void {
DatadogSDKWrapper.shared.overrideTelemetryConfiguration(
initializationType: rnConfiguration.configurationForTelemetry?.initializationType as? String,
reactNativeVersion: rnConfiguration.configurationForTelemetry?.reactNativeVersion as? String,
reactVersion: rnConfiguration.configurationForTelemetry?.reactVersion as? String,
Expand Down
Loading
Loading