From 3f6d1d251c8c414e2cfea5893fb5a025a9a0e11c Mon Sep 17 00:00:00 2001 From: Carlos Nogueira Date: Wed, 23 Jul 2025 11:38:01 +0100 Subject: [PATCH] [RUM-10972] Expose to JS method to send telemetry logs with custom attributes --- .../datadog/reactnative/DatadogSDKWrapper.kt | 19 +++++++++++++++++++ .../com/datadog/reactnative/DatadogWrapper.kt | 8 ++++++++ .../reactnative/DdSdkImplementation.kt | 13 ++++++++++++- .../kotlin/com/datadog/reactnative/DdSdk.kt | 11 +++++++++++ .../kotlin/com/datadog/reactnative/DdSdk.kt | 16 ++++++++++++++++ .../core/ios/Sources/DatadogSDKWrapper.swift | 11 ++++++++++- packages/core/ios/Sources/DdSdk.mm | 4 ++++ .../ios/Sources/DdSdkImplementation.swift | 9 +++++++++ packages/core/src/specs/NativeDdSdk.ts | 11 +++++++++++ 9 files changed, 100 insertions(+), 2 deletions(-) diff --git a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DatadogSDKWrapper.kt b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DatadogSDKWrapper.kt index 577f14096..7452c40ef 100644 --- a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DatadogSDKWrapper.kt +++ b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DatadogSDKWrapper.kt @@ -10,7 +10,9 @@ import android.content.Context import android.util.Log import com.datadog.android.Datadog import com.datadog.android._InternalProxy +import com.datadog.android.api.InternalLogger import com.datadog.android.api.SdkCore +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 @@ -23,6 +25,7 @@ import com.datadog.android.rum.RumMonitor import com.datadog.android.trace.Trace import com.datadog.android.trace.TraceConfiguration import com.datadog.android.webview.WebViewTracking +import com.facebook.react.bridge.ReadableMap /** * Internal object used to add internal testing. @@ -157,6 +160,22 @@ internal class DatadogSDKWrapper : DatadogWrapper { Datadog.setTrackingConsent(trackingConsent) } + override fun sendTelemetryLog(message: String, attributes: ReadableMap, config: ReadableMap) { + val core = DatadogSDKWrapperStorage.getSdkCore() as FeatureSdkCore? + val logger = core?.internalLogger; + + val additionalProperties = attributes.toMap() + val telemetryConfig = config.toMap() + + logger?.log( + level = InternalLogger.Level.INFO, + target = InternalLogger.Target.TELEMETRY, + messageBuilder = { message }, + onlyOnce = (telemetryConfig["onlyOnce"] as? Boolean) ?: true, + additionalProperties = additionalProperties + ) + } + override fun telemetryDebug(message: String) { telemetryProxy?.debug(message) } diff --git a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DatadogWrapper.kt b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DatadogWrapper.kt index 410de40bb..41e86f4d5 100644 --- a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DatadogWrapper.kt +++ b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DatadogWrapper.kt @@ -13,6 +13,8 @@ import com.datadog.android.privacy.TrackingConsent import com.datadog.android.rum.RumConfiguration import com.datadog.android.rum.RumMonitor import com.datadog.android.trace.TraceConfiguration +import com.facebook.react.bridge.Promise +import com.facebook.react.bridge.ReadableMap import java.lang.IllegalArgumentException /** @@ -142,6 +144,12 @@ interface DatadogWrapper { */ fun setTrackingConsent(trackingConsent: TrackingConsent) + + /** + * Sends telemetry event with attributes. + */ + fun sendTelemetryLog(message: String, attributes: ReadableMap, config: ReadableMap) + /** * Sends telemetry debug event. */ diff --git a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkImplementation.kt b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkImplementation.kt index e964054f8..7705483b2 100644 --- a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkImplementation.kt +++ b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkImplementation.kt @@ -20,6 +20,7 @@ import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicBoolean /** The entry point to initialize Datadog's features. */ +@Suppress("TooManyFunctions") class DdSdkImplementation( private val reactContext: ReactApplicationContext, private val datadog: DatadogWrapper = DatadogSDKWrapper(), @@ -60,7 +61,6 @@ class DdSdkImplementation( configureSynthetics() initialized.set(true) - promise.resolve(null) } @@ -138,6 +138,17 @@ class DdSdkImplementation( promise.resolve(null) } + + /** + * Convert React Native's ReadableMap to Map. + * @param attributes Custom attributes we want to send along with the Telemetry Log + * @param config Configuration object, can take 'onlyOnce: Boolean' + */ + fun sendTelemetryLog(message: String, attributes: ReadableMap, config: ReadableMap, promise: Promise) { + datadog.sendTelemetryLog(message, attributes, config) + promise.resolve(null) + } + /** * Sends a telemetry debug event. * @param message Debug message. diff --git a/packages/core/android/src/newarch/kotlin/com/datadog/reactnative/DdSdk.kt b/packages/core/android/src/newarch/kotlin/com/datadog/reactnative/DdSdk.kt index f1a9dffe2..d46e53ade 100644 --- a/packages/core/android/src/newarch/kotlin/com/datadog/reactnative/DdSdk.kt +++ b/packages/core/android/src/newarch/kotlin/com/datadog/reactnative/DdSdk.kt @@ -88,6 +88,17 @@ class DdSdk( implementation.setTrackingConsent(trackingConsent, promise) } + /** + * Sends a telemetry event with attributes. + * @param message message. + * @param attributes telemetry attributes. + * @param config telemetry configuration. + */ + @ReactMethod + override fun sendTelemetryLog(message: String, attributes: ReadableMap, config: ReadableMap, promise: Promise) { + implementation.sendTelemetryLog(message, attributes, config, promise) + } + /** * Sends a telemetry debug event. * @param message Debug message. diff --git a/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdSdk.kt b/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdSdk.kt index 80b26691e..d356055cd 100644 --- a/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdSdk.kt +++ b/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdSdk.kt @@ -109,6 +109,22 @@ class DdSdk( implementation.setTrackingConsent(trackingConsent, promise) } + /** + * Sends a telemetry event with attributes. + * @param message message. + * @param attributes telemetry attributes. + * @param config telemetry configuration. + */ + @ReactMethod + fun sendTelemetryLog( + message: String, + attributes: ReadableMap, + config: ReadableMap, + promise: Promise + ) { + implementation.sendTelemetryLog(message, attributes, config, promise) + } + /** * Sends a telemetry debug event. * @param message Debug message. diff --git a/packages/core/ios/Sources/DatadogSDKWrapper.swift b/packages/core/ios/Sources/DatadogSDKWrapper.swift index 4cabca237..842ee5d89 100644 --- a/packages/core/ios/Sources/DatadogSDKWrapper.swift +++ b/packages/core/ios/Sources/DatadogSDKWrapper.swift @@ -111,8 +111,17 @@ public class DatadogSDKWrapper { 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) + } + } + internal func telemetryDebug(id: String, message: String) { return Datadog._internal.telemetry.debug(id: id, message: message) } diff --git a/packages/core/ios/Sources/DdSdk.mm b/packages/core/ios/Sources/DdSdk.mm index 065097464..54ff1434c 100644 --- a/packages/core/ios/Sources/DdSdk.mm +++ b/packages/core/ios/Sources/DdSdk.mm @@ -147,6 +147,10 @@ -(void)addUserExtraInfo:(NSDictionary *)extraInfo resolve:(RCTPromiseResolveBloc [self.ddSdkImplementation addUserExtraInfoWithExtraInfo:extraInfo resolve:resolve reject:reject]; } +- (void)sendTelemetryLog:(NSString *)message attributes:(NSDictionary *)attributes config:(NSDictionary *)config resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject { + [self.ddSdkImplementation sendTelemetryLogWithMessage:message attributes:attributes config:config resolve:resolve reject:reject]; +} + - (void)telemetryDebug:(NSString *)message resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject { [self.ddSdkImplementation telemetryDebugWithMessage:message resolve:resolve reject:reject]; } diff --git a/packages/core/ios/Sources/DdSdkImplementation.swift b/packages/core/ios/Sources/DdSdkImplementation.swift index 843ccaf5d..6d060dc31 100644 --- a/packages/core/ios/Sources/DdSdkImplementation.swift +++ b/packages/core/ios/Sources/DdSdkImplementation.swift @@ -128,6 +128,15 @@ public class DdSdkImplementation: NSObject { resolve(nil) } + + @objc + 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) + 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) diff --git a/packages/core/src/specs/NativeDdSdk.ts b/packages/core/src/specs/NativeDdSdk.ts index 27ffd14fc..6f1ce82a5 100644 --- a/packages/core/src/specs/NativeDdSdk.ts +++ b/packages/core/src/specs/NativeDdSdk.ts @@ -56,6 +56,17 @@ export interface Spec extends TurboModule { */ setTrackingConsent(trackingConsent: string): Promise; + /** + * Sends internal telemetry message with attributes + * @param message message + * @param attributes attributes + */ + sendTelemetryLog( + message: string, + attributes: Object, + config: Object + ): Promise; + /** * Sends internal telemetry debug message * @param message debug message