Skip to content
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
137 changes: 12 additions & 125 deletions packages/core/ios/Sources/DatadogSDKWrapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,33 +14,22 @@ import DatadogWebViewTracking
import DatadogInternal
import Foundation

public typealias OnCoreInitializedListener = (DatadogCoreProtocol) -> Void
public typealias OnSdkInitializedListener = () -> Void

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

// Initialization callbacks
internal var onCoreInitializedListeners: [OnCoreInitializedListener] = []
internal var loggerConfiguration = DatadogLogs.Logger.Configuration()
// Core instance
private var coreInstance: DatadogCoreProtocol? = nil
internal var onSdkInitializedListeners: [OnSdkInitializedListener] = []

private init() { }

public func addOnCoreInitializedListener(listener:@escaping OnCoreInitializedListener) {
onCoreInitializedListeners.append(listener)
}
internal private(set) var loggerConfiguration = DatadogLogs.Logger.Configuration()

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

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

// SDK Wrapper
Expand All @@ -49,124 +38,22 @@ public class DatadogSDKWrapper {
loggerConfiguration: DatadogLogs.Logger.Configuration,
trackingConsent: TrackingConsent
) -> Void {
let core = Datadog.initialize(with: coreConfiguration, trackingConsent: trackingConsent)
setCoreInstance(core: core)
for listener in onCoreInitializedListeners {
listener(core)
}

self.loggerConfiguration = loggerConfiguration
}

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

internal func clearAllData() -> Void {
if let core = coreInstance {
Datadog.clearAllData(in: core)
} else {
Datadog.clearAllData()
}
}

// Features
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.", .critical)
}
}

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.", .critical)
}
}

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.", .critical)
}
}

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

internal func createLogger() -> LoggerProtocol {
let core = coreInstance ?? {
consolePrint("Core instance was not found when creating Logger.", .critical)
return CoreRegistry.default
}()
Datadog.initialize(with: coreConfiguration, trackingConsent: trackingConsent)

return DatadogLogs.Logger.create(with: loggerConfiguration, in: core)
}

// Telemetry
internal func sendTelemetryLog(message: String, attributes: [String: any Encodable], config: [String: any Encodable]) {
if let core = coreInstance {
let id = (config["onlyOnce"] as? Bool) == true ? message : UUID().uuidString
core.telemetry.debug(id: id, message: message, attributes: attributes)
} else {
consolePrint("Core instance was not found when calling sendTelemetryLog.", .warn)
for listener in onSdkInitializedListeners {
listener()
}
}

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,
trackLongTask: trackLongTask,
trackNativeErrors: trackNativeErrors,
trackNativeLongTasks: trackNativeLongTasks,
trackNetworkRequests: trackNetworkRequests,
trackUserInteractions: trackInteractions
)
self.loggerConfiguration = loggerConfiguration
}

// 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.", .critical)
}
webviewMessageEmitter = WebViewTracking._internal.messageEmitter(in: CoreRegistry.default)
}

internal func sendWebviewMessage(body: NSString) throws {
try self.webviewMessageEmitter?.send(body: body)
}
Expand Down
7 changes: 6 additions & 1 deletion packages/core/ios/Sources/DdLogsImplementation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
*/

import Foundation
import DatadogInternal
import DatadogLogs
import DatadogCore

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

@objc
public override convenience init() {
self.init({ DatadogSDKWrapper.shared.createLogger() }, { DatadogSDKWrapper.shared.isInitialized() })
self.init(
{ DatadogLogs.Logger.create(with: DatadogSDKWrapper.shared.loggerConfiguration) },
{ Datadog.isInitialized() }
)
}

@objc
Expand Down
15 changes: 8 additions & 7 deletions packages/core/ios/Sources/DdSdkImplementation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -133,40 +133,41 @@ public class DdSdkImplementation: NSObject {
public func sendTelemetryLog(message: NSString, attributes: NSDictionary, config: NSDictionary, resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void {
let castedAttributes = castAttributesToSwift(attributes)
let castedConfig = castAttributesToSwift(config)
DatadogSDKWrapper.shared.sendTelemetryLog(message: message as String, attributes: castedAttributes, config: castedConfig)
DdTelemetry.sendTelemetryLog(message: message as String, attributes: castedAttributes, config: castedConfig)
resolve(nil)
}

@objc
public func telemetryDebug(message: NSString, resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void {
DatadogSDKWrapper.shared.telemetryDebug(id: "datadog_react_native:\(message)", message: message as String)
DdTelemetry.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 {
DatadogSDKWrapper.shared.telemetryError(id: "datadog_react_native:\(String(describing: kind)):\(message)", message: message as String, kind: kind as String, stack: stack as String)
DdTelemetry.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 DatadogSDKWrapper.shared.sendWebviewMessage(body: message)
} catch {
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)
DdTelemetry.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)
}

@objc
public func clearAllData(resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void {
DatadogSDKWrapper.shared.clearAllData()
Datadog.clearAllData()
resolve(nil)
}

func overrideReactNativeTelemetry(rnConfiguration: DdSdkConfiguration) -> Void {
DatadogSDKWrapper.shared.overrideTelemetryConfiguration(
DdTelemetry.overrideTelemetryConfiguration(
initializationType: rnConfiguration.configurationForTelemetry?.initializationType as? String,
reactNativeVersion: rnConfiguration.configurationForTelemetry?.reactNativeVersion as? String,
reactVersion: rnConfiguration.configurationForTelemetry?.reactVersion as? String,
Expand Down
21 changes: 10 additions & 11 deletions packages/core/ios/Sources/DdSdkNativeInitialization.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,13 @@ public class DdSdkNativeInitialization: NSObject {
}

internal func initialize(sdkConfiguration: DdSdkConfiguration) {
// TODO: see if this `if` is still needed
if DatadogSDKWrapper.shared.isInitialized() {
// Initializing the SDK twice results in Global.rum and
// Global.sharedTracer to be set to no-op instances
if Datadog.isInitialized(instanceName: CoreRegistry.defaultInstanceName) {
// 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.", .debug)
DatadogSDKWrapper.shared.telemetryDebug(id: "datadog_react_native: RN SDK was already initialized in native", message: "RN SDK was already initialized in native")
DdTelemetry.telemetryDebug(
id: "datadog_react_native: RN SDK was already initialized in native",
message: "RN SDK was already initialized in native"
)

RUMMonitor.shared().currentSessionID { sessionId in
guard let id = sessionId else { return }
Expand Down Expand Up @@ -78,19 +79,17 @@ public class DdSdkNativeInitialization: NSObject {

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

let logsConfig = buildLogsConfiguration(configuration: sdkConfiguration)
DatadogSDKWrapper.shared.enableLogs(with: logsConfig)
Logs.enable(with: logsConfig)

let traceConfig = buildTraceConfiguration(configuration: sdkConfiguration)
DatadogSDKWrapper.shared.enableTrace(with: traceConfig)
Trace.enable(with: traceConfig)

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

DatadogSDKWrapper.shared.enableWebviewTracking()
}

func buildSDKConfiguration(configuration: DdSdkConfiguration, defaultAppVersion: String = getDefaultAppVersion()) -> Datadog.Configuration {
Expand Down
49 changes: 49 additions & 0 deletions packages/core/ios/Sources/DdTelemetry.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@

/*
* 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 2019-2025 Datadog, Inc.
*/
import DatadogCore
import DatadogInternal

public class DdTelemetry {
public static func sendTelemetryLog(message: String, attributes: [String: any Encodable], config: [String: any Encodable]) {
let id = (config["onlyOnce"] as? Bool) == true ? message : UUID().uuidString
CoreRegistry.default.telemetry.debug(id: id, message: message, attributes: attributes)
}

public static func telemetryDebug(id: String, message: String) {
return Datadog._internal.telemetry.debug(id: id, message: message)
}

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

public static 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
) {
CoreRegistry.default.telemetry.configuration(
initializationType: initializationType,
reactNativeVersion: reactNativeVersion,
reactVersion: reactVersion,
trackCrossPlatformLongTasks: trackCrossPlatformLongTasks,
trackErrors: trackErrors,
trackLongTask: trackLongTask,
trackNativeErrors: trackNativeErrors,
trackNativeLongTasks: trackNativeLongTasks,
trackNetworkRequests: trackNetworkRequests,
trackUserInteractions: trackInteractions
)
}
}
17 changes: 9 additions & 8 deletions packages/core/ios/Tests/DatadogSdkWrapperTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,23 @@ import XCTest
@testable import DatadogSDKReactNative
import DatadogTrace
import DatadogInternal

import DatadogRUM
import DatadogLogs

internal class DatadogSdkWrapperTests: XCTestCase {
override func setUp() {
super.setUp()
DatadogSDKWrapper.shared.setCoreInstance(core: nil)
DatadogSDKWrapper.shared.onCoreInitializedListeners = []
DatadogSDKWrapper.shared.onSdkInitializedListeners = []
}

func testItSetsCoreUsedForFeatures() {
func testOverrideCoreRegistryDefault() {
let coreMock = MockDatadogCore()
DatadogSDKWrapper.shared.setCoreInstance(core: coreMock)
CoreRegistry.register(default: coreMock)
defer { CoreRegistry.unregisterDefault() }

DatadogSDKWrapper.shared.enableTrace(with: .init())
DatadogSDKWrapper.shared.enableRUM(with: .init(applicationID: "app-id"))
DatadogSDKWrapper.shared.enableLogs(with: .init())
Trace.enable(with: .init())
RUM.enable(with: .init(applicationID: "app-id"))
Logs.enable(with: .init())

XCTAssertNotNil(coreMock.features["tracing"])
XCTAssertNotNil(coreMock.features["rum"])
Expand Down
Loading