From 35019395a9acda6aff557b55f280ff4a121f33a7 Mon Sep 17 00:00:00 2001 From: Nikita Ogorodnikov Date: Thu, 14 Dec 2023 16:53:58 +0100 Subject: [PATCH] Better handling of event write errors in RUM --- detekt_custom.yml | 1 + .../internal/domain/scope/RumActionScope.kt | 161 ++-- .../internal/domain/scope/RumResourceScope.kt | 317 ++++---- .../rum/internal/domain/scope/RumViewScope.kt | 708 +++++++++--------- .../datadog/android/rum/utils/SdkCoreExt.kt | 84 +++ 5 files changed, 680 insertions(+), 591 deletions(-) create mode 100644 features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/utils/SdkCoreExt.kt diff --git a/detekt_custom.yml b/detekt_custom.yml index 15fb7e4595..5764c72d23 100644 --- a/detekt_custom.yml +++ b/detekt_custom.yml @@ -845,6 +845,7 @@ datadog: - "kotlin.collections.MutableCollection.forEach(kotlin.Function1)" - "kotlin.collections.MutableCollection.toList()" - "kotlin.collections.MutableIterator.hasNext()" + - "kotlin.collections.MutableList.add(com.datadog.android.api.InternalLogger.Target)" - "kotlin.collections.MutableList.add(com.datadog.android.core.internal.persistence.Batch)" - "kotlin.collections.MutableList.add(com.datadog.android.plugin.DatadogPlugin)" - "kotlin.collections.MutableList.add(com.datadog.android.rum.internal.domain.scope.RumScope)" diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumActionScope.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumActionScope.kt index 417a0265d9..34df3fa67d 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumActionScope.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumActionScope.kt @@ -7,7 +7,6 @@ package com.datadog.android.rum.internal.domain.scope import androidx.annotation.WorkerThread -import com.datadog.android.api.feature.Feature import com.datadog.android.api.storage.DataWriter import com.datadog.android.core.InternalSdkCore import com.datadog.android.rum.GlobalRumMonitor @@ -15,7 +14,9 @@ import com.datadog.android.rum.RumActionType import com.datadog.android.rum.internal.FeaturesContextResolver import com.datadog.android.rum.internal.domain.RumContext import com.datadog.android.rum.internal.domain.Time +import com.datadog.android.rum.internal.monitor.StorageEvent import com.datadog.android.rum.model.ActionEvent +import com.datadog.android.rum.utils.createRumEventWriteOperation import com.datadog.android.rum.utils.hasUserData import java.lang.ref.WeakReference import java.util.UUID @@ -220,90 +221,90 @@ internal class RumActionScope( } else { ActionEvent.ActionEventSessionType.SYNTHETICS } + val frustrations = mutableListOf() + if (trackFrustrations && eventErrorCount > 0 && actualType == RumActionType.TAP) { + frustrations.add(ActionEvent.Type.ERROR_TAP) + } - sdkCore.getFeature(Feature.RUM_FEATURE_NAME) - ?.withWriteContext { datadogContext, eventBatchWriter -> - val user = datadogContext.userInfo - val hasReplay = featuresContextResolver.resolveViewHasReplay( - datadogContext, - rumContext.viewId.orEmpty() - ) - val frustrations = mutableListOf() - if (trackFrustrations && eventErrorCount > 0 && actualType == RumActionType.TAP) { - frustrations.add(ActionEvent.Type.ERROR_TAP) - } - - val actionEvent = ActionEvent( - date = eventTimestamp, - action = ActionEvent.ActionEventAction( - type = actualType.toSchemaType(), - id = actionId, - target = ActionEvent.ActionEventActionTarget(eventName), - error = ActionEvent.Error(eventErrorCount), - crash = ActionEvent.Crash(eventCrashCount), - longTask = ActionEvent.LongTask(eventLongTaskCount), - resource = ActionEvent.Resource(eventResourceCount), - loadingTime = max(endNanos - startedNanos, 1L), - frustration = if (frustrations.isNotEmpty()) { - ActionEvent.Frustration(frustrations) - } else { - null - } - ), - view = ActionEvent.View( - id = rumContext.viewId.orEmpty(), - name = rumContext.viewName, - url = rumContext.viewUrl.orEmpty() - ), - application = ActionEvent.Application(rumContext.applicationId), - session = ActionEvent.ActionEventSession( - id = rumContext.sessionId, - type = sessionType, - hasReplay = hasReplay - ), - synthetics = syntheticsAttribute, - source = ActionEvent.ActionEventSource.tryFromSource( - datadogContext.source, - sdkCore.internalLogger - ), - usr = if (user.hasUserData()) { - ActionEvent.Usr( - id = user.id, - name = user.name, - email = user.email, - additionalProperties = user.additionalProperties.toMutableMap() - ) + sdkCore.createRumEventWriteOperation(writer) { datadogContext -> + val user = datadogContext.userInfo + val hasReplay = featuresContextResolver.resolveViewHasReplay( + datadogContext, + rumContext.viewId.orEmpty() + ) + + ActionEvent( + date = eventTimestamp, + action = ActionEvent.ActionEventAction( + type = actualType.toSchemaType(), + id = actionId, + target = ActionEvent.ActionEventActionTarget(eventName), + error = ActionEvent.Error(eventErrorCount), + crash = ActionEvent.Crash(eventCrashCount), + longTask = ActionEvent.LongTask(eventLongTaskCount), + resource = ActionEvent.Resource(eventResourceCount), + loadingTime = max(endNanos - startedNanos, 1L), + frustration = if (frustrations.isNotEmpty()) { + ActionEvent.Frustration(frustrations) } else { null - }, - os = ActionEvent.Os( - name = datadogContext.deviceInfo.osName, - version = datadogContext.deviceInfo.osVersion, - versionMajor = datadogContext.deviceInfo.osMajorVersion - ), - device = ActionEvent.Device( - type = datadogContext.deviceInfo.deviceType.toActionSchemaType(), - name = datadogContext.deviceInfo.deviceName, - model = datadogContext.deviceInfo.deviceModel, - brand = datadogContext.deviceInfo.deviceBrand, - architecture = datadogContext.deviceInfo.architecture - ), - context = ActionEvent.Context(additionalProperties = attributes), - dd = ActionEvent.Dd( - session = ActionEvent.DdSession( - plan = ActionEvent.Plan.PLAN_1, - sessionPrecondition = rumContext.sessionStartReason.toActionSessionPrecondition() - ), - configuration = ActionEvent.Configuration(sessionSampleRate = sampleRate) + } + ), + view = ActionEvent.View( + id = rumContext.viewId.orEmpty(), + name = rumContext.viewName, + url = rumContext.viewUrl.orEmpty() + ), + application = ActionEvent.Application(rumContext.applicationId), + session = ActionEvent.ActionEventSession( + id = rumContext.sessionId, + type = sessionType, + hasReplay = hasReplay + ), + synthetics = syntheticsAttribute, + source = ActionEvent.ActionEventSource.tryFromSource( + datadogContext.source, + sdkCore.internalLogger + ), + usr = if (user.hasUserData()) { + ActionEvent.Usr( + id = user.id, + name = user.name, + email = user.email, + additionalProperties = user.additionalProperties.toMutableMap() + ) + } else { + null + }, + os = ActionEvent.Os( + name = datadogContext.deviceInfo.osName, + version = datadogContext.deviceInfo.osVersion, + versionMajor = datadogContext.deviceInfo.osMajorVersion + ), + device = ActionEvent.Device( + type = datadogContext.deviceInfo.deviceType.toActionSchemaType(), + name = datadogContext.deviceInfo.deviceName, + model = datadogContext.deviceInfo.deviceModel, + brand = datadogContext.deviceInfo.deviceBrand, + architecture = datadogContext.deviceInfo.architecture + ), + context = ActionEvent.Context(additionalProperties = attributes), + dd = ActionEvent.Dd( + session = ActionEvent.DdSession( + plan = ActionEvent.Plan.PLAN_1, + sessionPrecondition = rumContext.sessionStartReason.toActionSessionPrecondition() ), - connectivity = networkInfo.toActionConnectivity(), - service = datadogContext.service, - version = datadogContext.version - ) - - @Suppress("ThreadSafety") // called in a worker thread context - writer.write(eventBatchWriter, actionEvent) + configuration = ActionEvent.Configuration(sessionSampleRate = sampleRate) + ), + connectivity = networkInfo.toActionConnectivity(), + service = datadogContext.service, + version = datadogContext.version + ) + } + .onError { + it.eventDropped(rumContext.viewId.orEmpty(), StorageEvent.Action(frustrations.size)) } + .submit() sent = true } diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumResourceScope.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumResourceScope.kt index 928eeeae91..874e694427 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumResourceScope.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumResourceScope.kt @@ -8,7 +8,6 @@ package com.datadog.android.rum.internal.domain.scope import androidx.annotation.WorkerThread import com.datadog.android.api.InternalLogger -import com.datadog.android.api.feature.Feature import com.datadog.android.api.storage.DataWriter import com.datadog.android.core.InternalSdkCore import com.datadog.android.core.internal.net.FirstPartyHostHeaderTypeResolver @@ -22,8 +21,10 @@ import com.datadog.android.rum.internal.FeaturesContextResolver import com.datadog.android.rum.internal.domain.RumContext import com.datadog.android.rum.internal.domain.Time import com.datadog.android.rum.internal.domain.event.ResourceTiming +import com.datadog.android.rum.internal.monitor.StorageEvent import com.datadog.android.rum.model.ErrorEvent import com.datadog.android.rum.model.ResourceEvent +import com.datadog.android.rum.utils.createRumEventWriteOperation import com.datadog.android.rum.utils.hasUserData import java.net.MalformedURLException import java.net.URL @@ -203,90 +204,90 @@ internal class RumResourceScope( attributes.remove(RumAttributes.GRAPHQL_PAYLOAD) as? String?, attributes.remove(RumAttributes.GRAPHQL_VARIABLES) as? String? ) - sdkCore.getFeature(Feature.RUM_FEATURE_NAME) - ?.withWriteContext { datadogContext, eventBatchWriter -> - val user = datadogContext.userInfo - val hasReplay = featuresContextResolver.resolveViewHasReplay( - datadogContext, - rumContext.viewId.orEmpty() - ) - val duration = resolveResourceDuration(eventTime) - val resourceEvent = ResourceEvent( - date = eventTimestamp, - resource = ResourceEvent.Resource( - id = resourceId, - type = kind.toSchemaType(), - url = url, - duration = duration, - method = method.toResourceMethod(), - statusCode = statusCode, - size = size, - dns = finalTiming?.dns(), - connect = finalTiming?.connect(), - ssl = finalTiming?.ssl(), - firstByte = finalTiming?.firstByte(), - download = finalTiming?.download(), - provider = resolveResourceProvider(), - graphql = graphql - ), - action = rumContext.actionId?.let { ResourceEvent.Action(listOf(it)) }, - view = ResourceEvent.View( - id = rumContext.viewId.orEmpty(), - name = rumContext.viewName, - url = rumContext.viewUrl.orEmpty() - ), - usr = if (user.hasUserData()) { - ResourceEvent.Usr( - id = user.id, - name = user.name, - email = user.email, - additionalProperties = user.additionalProperties.toMutableMap() - ) - } else { - null - }, - connectivity = networkInfo.toResourceConnectivity(), - application = ResourceEvent.Application(rumContext.applicationId), - session = ResourceEvent.ResourceEventSession( - id = rumContext.sessionId, - type = sessionType, - hasReplay = hasReplay - ), - synthetics = syntheticsAttribute, - source = ResourceEvent.ResourceEventSource.tryFromSource( - datadogContext.source, - sdkCore.internalLogger - ), - os = ResourceEvent.Os( - name = datadogContext.deviceInfo.osName, - version = datadogContext.deviceInfo.osVersion, - versionMajor = datadogContext.deviceInfo.osMajorVersion - ), - device = ResourceEvent.Device( - type = datadogContext.deviceInfo.deviceType.toResourceSchemaType(), - name = datadogContext.deviceInfo.deviceName, - model = datadogContext.deviceInfo.deviceModel, - brand = datadogContext.deviceInfo.deviceBrand, - architecture = datadogContext.deviceInfo.architecture - ), - context = ResourceEvent.Context(additionalProperties = attributes), - dd = ResourceEvent.Dd( - traceId = traceId, - spanId = spanId, - rulePsr = rulePsr, - session = ResourceEvent.DdSession( - plan = ResourceEvent.Plan.PLAN_1, - sessionPrecondition = rumContext.sessionStartReason.toResourceSessionPrecondition() - ), - configuration = ResourceEvent.Configuration(sessionSampleRate = sampleRate) + sdkCore.createRumEventWriteOperation(writer) { datadogContext -> + val user = datadogContext.userInfo + val hasReplay = featuresContextResolver.resolveViewHasReplay( + datadogContext, + rumContext.viewId.orEmpty() + ) + val duration = resolveResourceDuration(eventTime) + ResourceEvent( + date = eventTimestamp, + resource = ResourceEvent.Resource( + id = resourceId, + type = kind.toSchemaType(), + url = url, + duration = duration, + method = method.toResourceMethod(), + statusCode = statusCode, + size = size, + dns = finalTiming?.dns(), + connect = finalTiming?.connect(), + ssl = finalTiming?.ssl(), + firstByte = finalTiming?.firstByte(), + download = finalTiming?.download(), + provider = resolveResourceProvider(), + graphql = graphql + ), + action = rumContext.actionId?.let { ResourceEvent.Action(listOf(it)) }, + view = ResourceEvent.View( + id = rumContext.viewId.orEmpty(), + name = rumContext.viewName, + url = rumContext.viewUrl.orEmpty() + ), + usr = if (user.hasUserData()) { + ResourceEvent.Usr( + id = user.id, + name = user.name, + email = user.email, + additionalProperties = user.additionalProperties.toMutableMap() + ) + } else { + null + }, + connectivity = networkInfo.toResourceConnectivity(), + application = ResourceEvent.Application(rumContext.applicationId), + session = ResourceEvent.ResourceEventSession( + id = rumContext.sessionId, + type = sessionType, + hasReplay = hasReplay + ), + synthetics = syntheticsAttribute, + source = ResourceEvent.ResourceEventSource.tryFromSource( + datadogContext.source, + sdkCore.internalLogger + ), + os = ResourceEvent.Os( + name = datadogContext.deviceInfo.osName, + version = datadogContext.deviceInfo.osVersion, + versionMajor = datadogContext.deviceInfo.osMajorVersion + ), + device = ResourceEvent.Device( + type = datadogContext.deviceInfo.deviceType.toResourceSchemaType(), + name = datadogContext.deviceInfo.deviceName, + model = datadogContext.deviceInfo.deviceModel, + brand = datadogContext.deviceInfo.deviceBrand, + architecture = datadogContext.deviceInfo.architecture + ), + context = ResourceEvent.Context(additionalProperties = attributes), + dd = ResourceEvent.Dd( + traceId = traceId, + spanId = spanId, + rulePsr = rulePsr, + session = ResourceEvent.DdSession( + plan = ResourceEvent.Plan.PLAN_1, + sessionPrecondition = rumContext.sessionStartReason.toResourceSessionPrecondition() ), - service = datadogContext.service, - version = datadogContext.version - ) - - @Suppress("ThreadSafety") // called in a worker thread context - writer.write(eventBatchWriter, resourceEvent) + configuration = ResourceEvent.Configuration(sessionSampleRate = sampleRate) + ), + service = datadogContext.service, + version = datadogContext.version + ) + } + .onError { + it.eventDropped(rumContext.viewId.orEmpty(), StorageEvent.Resource) } + .submit() sent = true } @@ -345,84 +346,84 @@ internal class RumResourceScope( } else { ErrorEvent.ErrorEventSessionType.SYNTHETICS } - sdkCore.getFeature(Feature.RUM_FEATURE_NAME) - ?.withWriteContext { datadogContext, eventBatchWriter -> - val user = datadogContext.userInfo - val hasReplay = featuresContextResolver.resolveViewHasReplay( - datadogContext, - rumContext.viewId.orEmpty() - ) - val errorEvent = ErrorEvent( - date = eventTimestamp, - error = ErrorEvent.Error( - message = message, - source = source.toSchemaSource(), - stack = stackTrace, - isCrash = false, - resource = ErrorEvent.Resource( - url = url, - method = method.toErrorMethod(), - statusCode = statusCode ?: 0, - provider = resolveErrorProvider() - ), - type = errorType, - sourceType = ErrorEvent.SourceType.ANDROID - ), - action = rumContext.actionId?.let { ErrorEvent.Action(listOf(it)) }, - view = ErrorEvent.View( - id = rumContext.viewId.orEmpty(), - name = rumContext.viewName, - url = rumContext.viewUrl.orEmpty() - ), - usr = if (user.hasUserData()) { - ErrorEvent.Usr( - id = user.id, - name = user.name, - email = user.email, - additionalProperties = user.additionalProperties.toMutableMap() - ) - } else { - null - }, - connectivity = networkInfo.toErrorConnectivity(), - application = ErrorEvent.Application(rumContext.applicationId), - session = ErrorEvent.ErrorEventSession( - id = rumContext.sessionId, - type = sessionType, - hasReplay = hasReplay - ), - synthetics = syntheticsAttribute, - source = ErrorEvent.ErrorEventSource.tryFromSource( - datadogContext.source, - sdkCore.internalLogger - ), - os = ErrorEvent.Os( - name = datadogContext.deviceInfo.osName, - version = datadogContext.deviceInfo.osVersion, - versionMajor = datadogContext.deviceInfo.osMajorVersion - ), - device = ErrorEvent.Device( - type = datadogContext.deviceInfo.deviceType.toErrorSchemaType(), - name = datadogContext.deviceInfo.deviceName, - model = datadogContext.deviceInfo.deviceModel, - brand = datadogContext.deviceInfo.deviceBrand, - architecture = datadogContext.deviceInfo.architecture + sdkCore.createRumEventWriteOperation(writer) { datadogContext -> + val user = datadogContext.userInfo + val hasReplay = featuresContextResolver.resolveViewHasReplay( + datadogContext, + rumContext.viewId.orEmpty() + ) + ErrorEvent( + date = eventTimestamp, + error = ErrorEvent.Error( + message = message, + source = source.toSchemaSource(), + stack = stackTrace, + isCrash = false, + resource = ErrorEvent.Resource( + url = url, + method = method.toErrorMethod(), + statusCode = statusCode ?: 0, + provider = resolveErrorProvider() ), - context = ErrorEvent.Context(additionalProperties = attributes), - dd = ErrorEvent.Dd( - session = ErrorEvent.DdSession( - plan = ErrorEvent.Plan.PLAN_1, - sessionPrecondition = rumContext.sessionStartReason.toErrorSessionPrecondition() - ), - configuration = ErrorEvent.Configuration(sessionSampleRate = sampleRate) + type = errorType, + sourceType = ErrorEvent.SourceType.ANDROID + ), + action = rumContext.actionId?.let { ErrorEvent.Action(listOf(it)) }, + view = ErrorEvent.View( + id = rumContext.viewId.orEmpty(), + name = rumContext.viewName, + url = rumContext.viewUrl.orEmpty() + ), + usr = if (user.hasUserData()) { + ErrorEvent.Usr( + id = user.id, + name = user.name, + email = user.email, + additionalProperties = user.additionalProperties.toMutableMap() + ) + } else { + null + }, + connectivity = networkInfo.toErrorConnectivity(), + application = ErrorEvent.Application(rumContext.applicationId), + session = ErrorEvent.ErrorEventSession( + id = rumContext.sessionId, + type = sessionType, + hasReplay = hasReplay + ), + synthetics = syntheticsAttribute, + source = ErrorEvent.ErrorEventSource.tryFromSource( + datadogContext.source, + sdkCore.internalLogger + ), + os = ErrorEvent.Os( + name = datadogContext.deviceInfo.osName, + version = datadogContext.deviceInfo.osVersion, + versionMajor = datadogContext.deviceInfo.osMajorVersion + ), + device = ErrorEvent.Device( + type = datadogContext.deviceInfo.deviceType.toErrorSchemaType(), + name = datadogContext.deviceInfo.deviceName, + model = datadogContext.deviceInfo.deviceModel, + brand = datadogContext.deviceInfo.deviceBrand, + architecture = datadogContext.deviceInfo.architecture + ), + context = ErrorEvent.Context(additionalProperties = attributes), + dd = ErrorEvent.Dd( + session = ErrorEvent.DdSession( + plan = ErrorEvent.Plan.PLAN_1, + sessionPrecondition = rumContext.sessionStartReason.toErrorSessionPrecondition() ), - service = datadogContext.service, - version = datadogContext.version - ) - - @Suppress("ThreadSafety") // called in a worker thread context - writer.write(eventBatchWriter, errorEvent) + configuration = ErrorEvent.Configuration(sessionSampleRate = sampleRate) + ), + service = datadogContext.service, + version = datadogContext.version + ) + } + .onError { + it.eventDropped(rumContext.viewId.orEmpty(), StorageEvent.Error) } + .submit() sent = true } diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScope.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScope.kt index 2b3ff12d28..e32c20fa95 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScope.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScope.kt @@ -22,6 +22,7 @@ import com.datadog.android.rum.internal.FeaturesContextResolver import com.datadog.android.rum.internal.RumFeature import com.datadog.android.rum.internal.domain.RumContext import com.datadog.android.rum.internal.domain.Time +import com.datadog.android.rum.internal.monitor.StorageEvent import com.datadog.android.rum.internal.vitals.VitalInfo import com.datadog.android.rum.internal.vitals.VitalListener import com.datadog.android.rum.internal.vitals.VitalMonitor @@ -29,6 +30,7 @@ import com.datadog.android.rum.model.ActionEvent import com.datadog.android.rum.model.ErrorEvent import com.datadog.android.rum.model.LongTaskEvent import com.datadog.android.rum.model.ViewEvent +import com.datadog.android.rum.utils.createRumEventWriteOperation import com.datadog.android.rum.utils.hasUserData import com.datadog.android.rum.utils.resolveViewUrl import java.lang.ref.Reference @@ -380,96 +382,96 @@ internal open class RumViewScope( event.message } - sdkCore.getFeature(Feature.RUM_FEATURE_NAME) - ?.withWriteContext { datadogContext, eventBatchWriter -> + sdkCore.createRumEventWriteOperation(writer) { datadogContext -> - val user = datadogContext.userInfo - val hasReplay = featuresContextResolver.resolveViewHasReplay( - datadogContext, - rumContext.viewId.orEmpty() + val user = datadogContext.userInfo + val hasReplay = featuresContextResolver.resolveViewHasReplay( + datadogContext, + rumContext.viewId.orEmpty() + ) + val syntheticsAttribute = if ( + rumContext.syntheticsTestId.isNullOrBlank() || + rumContext.syntheticsResultId.isNullOrBlank() + ) { + null + } else { + ErrorEvent.Synthetics( + testId = rumContext.syntheticsTestId, + resultId = rumContext.syntheticsResultId ) - val syntheticsAttribute = if ( - rumContext.syntheticsTestId.isNullOrBlank() || - rumContext.syntheticsResultId.isNullOrBlank() - ) { - null - } else { - ErrorEvent.Synthetics( - testId = rumContext.syntheticsTestId, - resultId = rumContext.syntheticsResultId + } + val sessionType = if (syntheticsAttribute == null) { + ErrorEvent.ErrorEventSessionType.USER + } else { + ErrorEvent.ErrorEventSessionType.SYNTHETICS + } + ErrorEvent( + date = event.eventTime.timestamp + serverTimeOffsetInMs, + featureFlags = ErrorEvent.Context(featureFlags), + error = ErrorEvent.Error( + message = message, + source = event.source.toSchemaSource(), + stack = event.stacktrace ?: event.throwable?.loggableStackTrace(), + isCrash = isFatal, + type = errorType, + sourceType = event.sourceType.toSchemaSourceType() + ), + action = rumContext.actionId?.let { ErrorEvent.Action(listOf(it)) }, + view = ErrorEvent.View( + id = rumContext.viewId.orEmpty(), + name = rumContext.viewName, + url = rumContext.viewUrl.orEmpty() + ), + usr = if (user.hasUserData()) { + ErrorEvent.Usr( + id = user.id, + name = user.name, + email = user.email, + additionalProperties = user.additionalProperties.toMutableMap() ) - } - val sessionType = if (syntheticsAttribute == null) { - ErrorEvent.ErrorEventSessionType.USER } else { - ErrorEvent.ErrorEventSessionType.SYNTHETICS - } - val errorEvent = ErrorEvent( - date = event.eventTime.timestamp + serverTimeOffsetInMs, - featureFlags = ErrorEvent.Context(featureFlags), - error = ErrorEvent.Error( - message = message, - source = event.source.toSchemaSource(), - stack = event.stacktrace ?: event.throwable?.loggableStackTrace(), - isCrash = isFatal, - type = errorType, - sourceType = event.sourceType.toSchemaSourceType() - ), - action = rumContext.actionId?.let { ErrorEvent.Action(listOf(it)) }, - view = ErrorEvent.View( - id = rumContext.viewId.orEmpty(), - name = rumContext.viewName, - url = rumContext.viewUrl.orEmpty() - ), - usr = if (user.hasUserData()) { - ErrorEvent.Usr( - id = user.id, - name = user.name, - email = user.email, - additionalProperties = user.additionalProperties.toMutableMap() - ) - } else { - null - }, - connectivity = datadogContext.networkInfo.toErrorConnectivity(), - application = ErrorEvent.Application(rumContext.applicationId), - session = ErrorEvent.ErrorEventSession( - id = rumContext.sessionId, - type = sessionType, - hasReplay = hasReplay - ), - synthetics = syntheticsAttribute, - source = ErrorEvent.ErrorEventSource.tryFromSource( - source = datadogContext.source, - internalLogger = sdkCore.internalLogger - ), - os = ErrorEvent.Os( - name = datadogContext.deviceInfo.osName, - version = datadogContext.deviceInfo.osVersion, - versionMajor = datadogContext.deviceInfo.osMajorVersion - ), - device = ErrorEvent.Device( - type = datadogContext.deviceInfo.deviceType.toErrorSchemaType(), - name = datadogContext.deviceInfo.deviceName, - model = datadogContext.deviceInfo.deviceModel, - brand = datadogContext.deviceInfo.deviceBrand, - architecture = datadogContext.deviceInfo.architecture - ), - context = ErrorEvent.Context(additionalProperties = updatedAttributes), - dd = ErrorEvent.Dd( - session = ErrorEvent.DdSession( - plan = ErrorEvent.Plan.PLAN_1, - sessionPrecondition = rumContext.sessionStartReason.toErrorSessionPrecondition() - ), - configuration = ErrorEvent.Configuration(sessionSampleRate = sampleRate) + null + }, + connectivity = datadogContext.networkInfo.toErrorConnectivity(), + application = ErrorEvent.Application(rumContext.applicationId), + session = ErrorEvent.ErrorEventSession( + id = rumContext.sessionId, + type = sessionType, + hasReplay = hasReplay + ), + synthetics = syntheticsAttribute, + source = ErrorEvent.ErrorEventSource.tryFromSource( + source = datadogContext.source, + internalLogger = sdkCore.internalLogger + ), + os = ErrorEvent.Os( + name = datadogContext.deviceInfo.osName, + version = datadogContext.deviceInfo.osVersion, + versionMajor = datadogContext.deviceInfo.osMajorVersion + ), + device = ErrorEvent.Device( + type = datadogContext.deviceInfo.deviceType.toErrorSchemaType(), + name = datadogContext.deviceInfo.deviceName, + model = datadogContext.deviceInfo.deviceModel, + brand = datadogContext.deviceInfo.deviceBrand, + architecture = datadogContext.deviceInfo.architecture + ), + context = ErrorEvent.Context(additionalProperties = updatedAttributes), + dd = ErrorEvent.Dd( + session = ErrorEvent.DdSession( + plan = ErrorEvent.Plan.PLAN_1, + sessionPrecondition = rumContext.sessionStartReason.toErrorSessionPrecondition() ), - service = datadogContext.service, - version = datadogContext.version - ) - - @Suppress("ThreadSafety") // called in a worker thread context - writer.write(eventBatchWriter, errorEvent) + configuration = ErrorEvent.Configuration(sessionSampleRate = sampleRate) + ), + service = datadogContext.service, + version = datadogContext.version + ) + } + .onError { + it.eventDropped(viewId, StorageEvent.Error) } + .submit() if (isFatal) { errorCount++ @@ -711,120 +713,117 @@ internal open class RumViewScope( val refreshRateInfo = lastFrameRateInfo val isSlowRendered = resolveRefreshRateInfo(refreshRateInfo) ?: false - sdkCore.getFeature(Feature.RUM_FEATURE_NAME) - ?.withWriteContext { datadogContext, eventBatchWriter -> - val currentViewId = rumContext.viewId.orEmpty() - val user = datadogContext.userInfo - val hasReplay = featuresContextResolver.resolveViewHasReplay( - datadogContext, - currentViewId - ) - val sessionReplayRecordsCount = featuresContextResolver.resolveViewRecordsCount( - datadogContext, - currentViewId + sdkCore.createRumEventWriteOperation(writer) { datadogContext -> + val currentViewId = rumContext.viewId.orEmpty() + val user = datadogContext.userInfo + val hasReplay = featuresContextResolver.resolveViewHasReplay( + datadogContext, + currentViewId + ) + val sessionReplayRecordsCount = featuresContextResolver.resolveViewRecordsCount( + datadogContext, + currentViewId + ) + val replayStats = ViewEvent.ReplayStats(recordsCount = sessionReplayRecordsCount) + val syntheticsAttribute = if ( + rumContext.syntheticsTestId.isNullOrBlank() || + rumContext.syntheticsResultId.isNullOrBlank() + ) { + null + } else { + ViewEvent.Synthetics( + testId = rumContext.syntheticsTestId, + resultId = rumContext.syntheticsResultId ) - val replayStats = ViewEvent.ReplayStats(recordsCount = sessionReplayRecordsCount) - val syntheticsAttribute = if ( - rumContext.syntheticsTestId.isNullOrBlank() || - rumContext.syntheticsResultId.isNullOrBlank() - ) { - null - } else { - ViewEvent.Synthetics( - testId = rumContext.syntheticsTestId, - resultId = rumContext.syntheticsResultId - ) - } - val sessionType = if (syntheticsAttribute == null) { - ViewEvent.ViewEventSessionType.USER - } else { - ViewEvent.ViewEventSessionType.SYNTHETICS - } + } + val sessionType = if (syntheticsAttribute == null) { + ViewEvent.ViewEventSessionType.USER + } else { + ViewEvent.ViewEventSessionType.SYNTHETICS + } - val viewEvent = ViewEvent( - date = eventTimestamp, - featureFlags = ViewEvent.Context(additionalProperties = featureFlags), - view = ViewEvent.View( - id = currentViewId, - name = rumContext.viewName, - url = rumContext.viewUrl.orEmpty(), - timeSpent = updatedDurationNs, - action = ViewEvent.Action(eventActionCount), - resource = ViewEvent.Resource(eventResourceCount), - error = ViewEvent.Error(eventErrorCount), - crash = ViewEvent.Crash(eventCrashCount), - longTask = ViewEvent.LongTask(eventLongTaskCount), - frozenFrame = ViewEvent.FrozenFrame(eventFrozenFramesCount), - customTimings = timings, - isActive = !viewComplete, - cpuTicksCount = eventCpuTicks, - cpuTicksPerSecond = if (updatedDurationNs >= ONE_SECOND_NS) { - eventCpuTicks?.let { (it * ONE_SECOND_NS) / updatedDurationNs } - } else { - null - }, - memoryAverage = memoryInfo?.meanValue, - memoryMax = memoryInfo?.maxValue, - refreshRateAverage = refreshRateInfo?.meanValue, - refreshRateMin = refreshRateInfo?.minValue, - isSlowRendered = isSlowRendered, - frustration = ViewEvent.Frustration(eventFrustrationCount.toLong()), - flutterBuildTime = eventFlutterBuildTime, - flutterRasterTime = eventFlutterRasterTime, - jsRefreshRate = eventJsRefreshRate - ), - usr = if (user.hasUserData()) { - ViewEvent.Usr( - id = user.id, - name = user.name, - email = user.email, - additionalProperties = user.additionalProperties.toMutableMap() - ) + ViewEvent( + date = eventTimestamp, + featureFlags = ViewEvent.Context(additionalProperties = featureFlags), + view = ViewEvent.View( + id = currentViewId, + name = rumContext.viewName, + url = rumContext.viewUrl.orEmpty(), + timeSpent = updatedDurationNs, + action = ViewEvent.Action(eventActionCount), + resource = ViewEvent.Resource(eventResourceCount), + error = ViewEvent.Error(eventErrorCount), + crash = ViewEvent.Crash(eventCrashCount), + longTask = ViewEvent.LongTask(eventLongTaskCount), + frozenFrame = ViewEvent.FrozenFrame(eventFrozenFramesCount), + customTimings = timings, + isActive = !viewComplete, + cpuTicksCount = eventCpuTicks, + cpuTicksPerSecond = if (updatedDurationNs >= ONE_SECOND_NS) { + eventCpuTicks?.let { (it * ONE_SECOND_NS) / updatedDurationNs } } else { null }, - application = ViewEvent.Application(rumContext.applicationId), - session = ViewEvent.ViewEventSession( - id = rumContext.sessionId, - type = sessionType, - hasReplay = hasReplay, - isActive = rumContext.isSessionActive - ), - synthetics = syntheticsAttribute, - source = ViewEvent.ViewEventSource.tryFromSource( - datadogContext.source, - sdkCore.internalLogger - ), - os = ViewEvent.Os( - name = datadogContext.deviceInfo.osName, - version = datadogContext.deviceInfo.osVersion, - versionMajor = datadogContext.deviceInfo.osMajorVersion - ), - device = ViewEvent.Device( - type = datadogContext.deviceInfo.deviceType.toViewSchemaType(), - name = datadogContext.deviceInfo.deviceName, - model = datadogContext.deviceInfo.deviceModel, - brand = datadogContext.deviceInfo.deviceBrand, - architecture = datadogContext.deviceInfo.architecture - ), - context = ViewEvent.Context(additionalProperties = attributes), - dd = ViewEvent.Dd( - documentVersion = eventVersion, - session = ViewEvent.DdSession( - plan = ViewEvent.Plan.PLAN_1, - sessionPrecondition = rumContext.sessionStartReason.toViewSessionPrecondition() - ), - replayStats = replayStats, - configuration = ViewEvent.Configuration(sessionSampleRate = sampleRate) + memoryAverage = memoryInfo?.meanValue, + memoryMax = memoryInfo?.maxValue, + refreshRateAverage = refreshRateInfo?.meanValue, + refreshRateMin = refreshRateInfo?.minValue, + isSlowRendered = isSlowRendered, + frustration = ViewEvent.Frustration(eventFrustrationCount.toLong()), + flutterBuildTime = eventFlutterBuildTime, + flutterRasterTime = eventFlutterRasterTime, + jsRefreshRate = eventJsRefreshRate + ), + usr = if (user.hasUserData()) { + ViewEvent.Usr( + id = user.id, + name = user.name, + email = user.email, + additionalProperties = user.additionalProperties.toMutableMap() + ) + } else { + null + }, + application = ViewEvent.Application(rumContext.applicationId), + session = ViewEvent.ViewEventSession( + id = rumContext.sessionId, + type = sessionType, + hasReplay = hasReplay, + isActive = rumContext.isSessionActive + ), + synthetics = syntheticsAttribute, + source = ViewEvent.ViewEventSource.tryFromSource( + datadogContext.source, + sdkCore.internalLogger + ), + os = ViewEvent.Os( + name = datadogContext.deviceInfo.osName, + version = datadogContext.deviceInfo.osVersion, + versionMajor = datadogContext.deviceInfo.osMajorVersion + ), + device = ViewEvent.Device( + type = datadogContext.deviceInfo.deviceType.toViewSchemaType(), + name = datadogContext.deviceInfo.deviceName, + model = datadogContext.deviceInfo.deviceModel, + brand = datadogContext.deviceInfo.deviceBrand, + architecture = datadogContext.deviceInfo.architecture + ), + context = ViewEvent.Context(additionalProperties = attributes), + dd = ViewEvent.Dd( + documentVersion = eventVersion, + session = ViewEvent.DdSession( + plan = ViewEvent.Plan.PLAN_1, + sessionPrecondition = rumContext.sessionStartReason.toViewSessionPrecondition() ), - connectivity = datadogContext.networkInfo.toViewConnectivity(), - service = datadogContext.service, - version = datadogContext.version - ) - - @Suppress("ThreadSafety") // called in a worker thread context - writer.write(eventBatchWriter, viewEvent) - } + replayStats = replayStats, + configuration = ViewEvent.Configuration(sessionSampleRate = sampleRate) + ), + connectivity = datadogContext.networkInfo.toViewConnectivity(), + service = datadogContext.service, + version = datadogContext.version + ) + } + .submit() } private fun resolveViewDuration(event: RumRawEvent): Long { @@ -875,93 +874,93 @@ internal open class RumViewScope( val globalAttributes = GlobalRumMonitor.get(sdkCore).getAttributes().toMutableMap() - sdkCore.getFeature(Feature.RUM_FEATURE_NAME) - ?.withWriteContext { datadogContext, eventBatchWriter -> - val user = datadogContext.userInfo - val syntheticsAttribute = if ( - rumContext.syntheticsTestId.isNullOrBlank() || - rumContext.syntheticsResultId.isNullOrBlank() - ) { - null - } else { - ActionEvent.Synthetics( - testId = rumContext.syntheticsTestId, - resultId = rumContext.syntheticsResultId + sdkCore.createRumEventWriteOperation(writer) { datadogContext -> + val user = datadogContext.userInfo + val syntheticsAttribute = if ( + rumContext.syntheticsTestId.isNullOrBlank() || + rumContext.syntheticsResultId.isNullOrBlank() + ) { + null + } else { + ActionEvent.Synthetics( + testId = rumContext.syntheticsTestId, + resultId = rumContext.syntheticsResultId + ) + } + val sessionType = if (syntheticsAttribute == null) { + ActionEvent.ActionEventSessionType.USER + } else { + ActionEvent.ActionEventSessionType.SYNTHETICS + } + + ActionEvent( + date = eventTimestamp, + action = ActionEvent.ActionEventAction( + type = ActionEvent.ActionEventActionType.APPLICATION_START, + id = UUID.randomUUID().toString(), + error = ActionEvent.Error(0), + crash = ActionEvent.Crash(0), + longTask = ActionEvent.LongTask(0), + resource = ActionEvent.Resource(0), + loadingTime = event.applicationStartupNanos + ), + view = ActionEvent.View( + id = rumContext.viewId.orEmpty(), + name = rumContext.viewName, + url = rumContext.viewUrl.orEmpty() + ), + usr = if (user.hasUserData()) { + ActionEvent.Usr( + id = user.id, + name = user.name, + email = user.email, + additionalProperties = user.additionalProperties.toMutableMap() ) - } - val sessionType = if (syntheticsAttribute == null) { - ActionEvent.ActionEventSessionType.USER } else { - ActionEvent.ActionEventSessionType.SYNTHETICS - } - - val actionEvent = ActionEvent( - date = eventTimestamp, - action = ActionEvent.ActionEventAction( - type = ActionEvent.ActionEventActionType.APPLICATION_START, - id = UUID.randomUUID().toString(), - error = ActionEvent.Error(0), - crash = ActionEvent.Crash(0), - longTask = ActionEvent.LongTask(0), - resource = ActionEvent.Resource(0), - loadingTime = event.applicationStartupNanos - ), - view = ActionEvent.View( - id = rumContext.viewId.orEmpty(), - name = rumContext.viewName, - url = rumContext.viewUrl.orEmpty() - ), - usr = if (user.hasUserData()) { - ActionEvent.Usr( - id = user.id, - name = user.name, - email = user.email, - additionalProperties = user.additionalProperties.toMutableMap() - ) - } else { - null - }, - application = ActionEvent.Application(rumContext.applicationId), - session = ActionEvent.ActionEventSession( - id = rumContext.sessionId, - type = sessionType, - hasReplay = false - ), - synthetics = syntheticsAttribute, - source = ActionEvent.ActionEventSource.tryFromSource( - datadogContext.source, - sdkCore.internalLogger - ), - os = ActionEvent.Os( - name = datadogContext.deviceInfo.osName, - version = datadogContext.deviceInfo.osVersion, - versionMajor = datadogContext.deviceInfo.osMajorVersion - ), - device = ActionEvent.Device( - type = datadogContext.deviceInfo.deviceType.toActionSchemaType(), - name = datadogContext.deviceInfo.deviceName, - model = datadogContext.deviceInfo.deviceModel, - brand = datadogContext.deviceInfo.deviceBrand, - architecture = datadogContext.deviceInfo.architecture - ), - context = ActionEvent.Context( - additionalProperties = globalAttributes - ), - dd = ActionEvent.Dd( - session = ActionEvent.DdSession( - plan = ActionEvent.Plan.PLAN_1, - sessionPrecondition = rumContext.sessionStartReason.toActionSessionPrecondition() - ), - configuration = ActionEvent.Configuration(sessionSampleRate = sampleRate) + null + }, + application = ActionEvent.Application(rumContext.applicationId), + session = ActionEvent.ActionEventSession( + id = rumContext.sessionId, + type = sessionType, + hasReplay = false + ), + synthetics = syntheticsAttribute, + source = ActionEvent.ActionEventSource.tryFromSource( + datadogContext.source, + sdkCore.internalLogger + ), + os = ActionEvent.Os( + name = datadogContext.deviceInfo.osName, + version = datadogContext.deviceInfo.osVersion, + versionMajor = datadogContext.deviceInfo.osMajorVersion + ), + device = ActionEvent.Device( + type = datadogContext.deviceInfo.deviceType.toActionSchemaType(), + name = datadogContext.deviceInfo.deviceName, + model = datadogContext.deviceInfo.deviceModel, + brand = datadogContext.deviceInfo.deviceBrand, + architecture = datadogContext.deviceInfo.architecture + ), + context = ActionEvent.Context( + additionalProperties = globalAttributes + ), + dd = ActionEvent.Dd( + session = ActionEvent.DdSession( + plan = ActionEvent.Plan.PLAN_1, + sessionPrecondition = rumContext.sessionStartReason.toActionSessionPrecondition() ), - connectivity = datadogContext.networkInfo.toActionConnectivity(), - service = datadogContext.service, - version = datadogContext.version - ) - - @Suppress("ThreadSafety") // called in a worker thread context - writer.write(eventBatchWriter, actionEvent) + configuration = ActionEvent.Configuration(sessionSampleRate = sampleRate) + ), + connectivity = datadogContext.networkInfo.toActionConnectivity(), + service = datadogContext.service, + version = datadogContext.version + ) + } + .onError { + it.eventDropped(viewId, StorageEvent.Action(0)) } + .submit() } @Suppress("LongMethod") @@ -976,91 +975,94 @@ internal open class RumViewScope( ) val timestamp = event.eventTime.timestamp + serverTimeOffsetInMs val isFrozenFrame = event.durationNs > FROZEN_FRAME_THRESHOLD_NS - sdkCore.getFeature(Feature.RUM_FEATURE_NAME) - ?.withWriteContext { datadogContext, eventBatchWriter -> + sdkCore.createRumEventWriteOperation(writer) { datadogContext -> - val user = datadogContext.userInfo - val hasReplay = featuresContextResolver.resolveViewHasReplay( - datadogContext, - rumContext.viewId.orEmpty() + val user = datadogContext.userInfo + val hasReplay = featuresContextResolver.resolveViewHasReplay( + datadogContext, + rumContext.viewId.orEmpty() + ) + val syntheticsAttribute = if ( + rumContext.syntheticsTestId.isNullOrBlank() || + rumContext.syntheticsResultId.isNullOrBlank() + ) { + null + } else { + LongTaskEvent.Synthetics( + testId = rumContext.syntheticsTestId, + resultId = rumContext.syntheticsResultId ) - val syntheticsAttribute = if ( - rumContext.syntheticsTestId.isNullOrBlank() || - rumContext.syntheticsResultId.isNullOrBlank() - ) { - null - } else { - LongTaskEvent.Synthetics( - testId = rumContext.syntheticsTestId, - resultId = rumContext.syntheticsResultId + } + val sessionType = if (syntheticsAttribute == null) { + LongTaskEvent.LongTaskEventSessionType.USER + } else { + LongTaskEvent.LongTaskEventSessionType.SYNTHETICS + } + LongTaskEvent( + date = timestamp - TimeUnit.NANOSECONDS.toMillis(event.durationNs), + longTask = LongTaskEvent.LongTask( + duration = event.durationNs, + isFrozenFrame = isFrozenFrame + ), + action = rumContext.actionId?.let { LongTaskEvent.Action(listOf(it)) }, + view = LongTaskEvent.View( + id = rumContext.viewId.orEmpty(), + name = rumContext.viewName, + url = rumContext.viewUrl.orEmpty() + ), + usr = if (user.hasUserData()) { + LongTaskEvent.Usr( + id = user.id, + name = user.name, + email = user.email, + additionalProperties = user.additionalProperties.toMutableMap() ) - } - val sessionType = if (syntheticsAttribute == null) { - LongTaskEvent.LongTaskEventSessionType.USER } else { - LongTaskEvent.LongTaskEventSessionType.SYNTHETICS - } - val longTaskEvent = LongTaskEvent( - date = timestamp - TimeUnit.NANOSECONDS.toMillis(event.durationNs), - longTask = LongTaskEvent.LongTask( - duration = event.durationNs, - isFrozenFrame = isFrozenFrame - ), - action = rumContext.actionId?.let { LongTaskEvent.Action(listOf(it)) }, - view = LongTaskEvent.View( - id = rumContext.viewId.orEmpty(), - name = rumContext.viewName, - url = rumContext.viewUrl.orEmpty() - ), - usr = if (user.hasUserData()) { - LongTaskEvent.Usr( - id = user.id, - name = user.name, - email = user.email, - additionalProperties = user.additionalProperties.toMutableMap() - ) - } else { - null - }, - connectivity = datadogContext.networkInfo.toLongTaskConnectivity(), - application = LongTaskEvent.Application(rumContext.applicationId), - session = LongTaskEvent.LongTaskEventSession( - id = rumContext.sessionId, - type = sessionType, - hasReplay = hasReplay - ), - synthetics = syntheticsAttribute, - source = LongTaskEvent.LongTaskEventSource.tryFromSource( - datadogContext.source, - sdkCore.internalLogger - ), - os = LongTaskEvent.Os( - name = datadogContext.deviceInfo.osName, - version = datadogContext.deviceInfo.osVersion, - versionMajor = datadogContext.deviceInfo.osMajorVersion - ), - device = LongTaskEvent.Device( - type = datadogContext.deviceInfo.deviceType.toLongTaskSchemaType(), - name = datadogContext.deviceInfo.deviceName, - model = datadogContext.deviceInfo.deviceModel, - brand = datadogContext.deviceInfo.deviceBrand, - architecture = datadogContext.deviceInfo.architecture - ), - context = LongTaskEvent.Context(additionalProperties = updatedAttributes), - dd = LongTaskEvent.Dd( - session = LongTaskEvent.DdSession( - plan = LongTaskEvent.Plan.PLAN_1, - sessionPrecondition = rumContext.sessionStartReason.toLongTaskSessionPrecondition() - ), - configuration = LongTaskEvent.Configuration(sessionSampleRate = sampleRate) + null + }, + connectivity = datadogContext.networkInfo.toLongTaskConnectivity(), + application = LongTaskEvent.Application(rumContext.applicationId), + session = LongTaskEvent.LongTaskEventSession( + id = rumContext.sessionId, + type = sessionType, + hasReplay = hasReplay + ), + synthetics = syntheticsAttribute, + source = LongTaskEvent.LongTaskEventSource.tryFromSource( + datadogContext.source, + sdkCore.internalLogger + ), + os = LongTaskEvent.Os( + name = datadogContext.deviceInfo.osName, + version = datadogContext.deviceInfo.osVersion, + versionMajor = datadogContext.deviceInfo.osMajorVersion + ), + device = LongTaskEvent.Device( + type = datadogContext.deviceInfo.deviceType.toLongTaskSchemaType(), + name = datadogContext.deviceInfo.deviceName, + model = datadogContext.deviceInfo.deviceModel, + brand = datadogContext.deviceInfo.deviceBrand, + architecture = datadogContext.deviceInfo.architecture + ), + context = LongTaskEvent.Context(additionalProperties = updatedAttributes), + dd = LongTaskEvent.Dd( + session = LongTaskEvent.DdSession( + plan = LongTaskEvent.Plan.PLAN_1, + sessionPrecondition = rumContext.sessionStartReason.toLongTaskSessionPrecondition() ), - service = datadogContext.service, - version = datadogContext.version + configuration = LongTaskEvent.Configuration(sessionSampleRate = sampleRate) + ), + service = datadogContext.service, + version = datadogContext.version + ) + } + .onError { + it.eventDropped( + viewId, + if (isFrozenFrame) StorageEvent.FrozenFrame else StorageEvent.LongTask ) - - @Suppress("ThreadSafety") // called in a worker thread context - writer.write(eventBatchWriter, longTaskEvent) } + .submit() pendingLongTaskCount++ if (isFrozenFrame) pendingFrozenFrameCount++ diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/utils/SdkCoreExt.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/utils/SdkCoreExt.kt new file mode 100644 index 0000000000..0b4b343782 --- /dev/null +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/utils/SdkCoreExt.kt @@ -0,0 +1,84 @@ +/* + * 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.rum.utils + +import com.datadog.android.api.InternalLogger +import com.datadog.android.api.context.DatadogContext +import com.datadog.android.api.feature.Feature +import com.datadog.android.api.feature.FeatureSdkCore +import com.datadog.android.api.storage.DataWriter +import com.datadog.android.rum.GlobalRumMonitor +import com.datadog.android.rum.internal.monitor.AdvancedRumMonitor + +internal typealias EventWriteErrorAction = (rumMonitor: AdvancedRumMonitor) -> Unit + +internal class WriteOperation( + private val sdkCore: FeatureSdkCore, + private val rumDataWriter: DataWriter, + private val eventSource: (DatadogContext) -> Any +) { + private val advancedRumMonitor = GlobalRumMonitor.get(sdkCore) as? AdvancedRumMonitor + private var onError: EventWriteErrorAction = NO_OP_EVENT_WRITE_FAILURE_ACTION + + fun onError(action: EventWriteErrorAction): WriteOperation { + onError = action + return this + } + + fun submit() { + sdkCore.getFeature(Feature.RUM_FEATURE_NAME) + ?.withWriteContext { datadogContext, eventBatchWriter -> + try { + val event = eventSource(datadogContext) + + @Suppress("ThreadSafety") // called in a worker thread context + val result = rumDataWriter.write(eventBatchWriter, event) + if (!result) { + notifyEventWriteFailure() + } + } catch (@Suppress("TooGenericExceptionCaught") e: Exception) { + notifyEventWriteFailure(e) + } + } + } + + private fun notifyEventWriteFailure(exception: Exception? = null) { + val targets = mutableListOf(InternalLogger.Target.USER).apply { + // if no exception, no need to notify telemetry, probably we already handled failure + // internally and sent it to telemetry + if (exception != null) add(InternalLogger.Target.TELEMETRY) + } + sdkCore.internalLogger.log( + level = InternalLogger.Level.ERROR, + targets = targets, + messageBuilder = { "Write operation failed." }, + throwable = exception + ) + + advancedRumMonitor?.let { + if (onError == NO_OP_EVENT_WRITE_FAILURE_ACTION) { + sdkCore.internalLogger.log( + level = InternalLogger.Level.WARN, + target = InternalLogger.Target.MAINTAINER, + { "Write operation failed, but no onError callback was provided." } + ) + } + onError(it) + } + } + + private companion object { + val NO_OP_EVENT_WRITE_FAILURE_ACTION: EventWriteErrorAction = {} + } +} + +internal fun FeatureSdkCore.createRumEventWriteOperation( + rumDataWriter: DataWriter, + eventSource: (DatadogContext) -> Any +): WriteOperation { + return WriteOperation(this, rumDataWriter, eventSource) +}