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

RUM-2568: Better handling of event write errors in RUM #1766

Merged
merged 1 commit into from
Dec 21, 2023
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
1 change: 1 addition & 0 deletions detekt_custom.yml
Original file line number Diff line number Diff line change
Expand Up @@ -846,6 +846,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)"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,6 @@ internal class RumFeature constructor(
return RumDataWriter(
eventSerializer = MapperSerializer(
RumEventMapper(
sdkCore,
viewEventMapper = configuration.viewEventMapper,
errorEventMapper = configuration.errorEventMapper,
resourceEventMapper = configuration.resourceEventMapper,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,7 @@ import com.datadog.android.api.storage.RawBatchEvent
import com.datadog.android.core.InternalSdkCore
import com.datadog.android.core.persistence.Serializer
import com.datadog.android.core.persistence.serializeToByteArray
import com.datadog.android.rum.GlobalRumMonitor
import com.datadog.android.rum.internal.domain.event.RumEventMeta
import com.datadog.android.rum.internal.monitor.AdvancedRumMonitor
import com.datadog.android.rum.internal.monitor.StorageEvent
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.ResourceEvent
import com.datadog.android.rum.model.ViewEvent

internal class RumDataWriter(
Expand Down Expand Up @@ -70,36 +63,7 @@ internal class RumDataWriter(
@WorkerThread
internal fun onDataWritten(data: Any, rawData: ByteArray) {
when (data) {
is ViewEvent -> persistViewEvent(rawData)
is ActionEvent -> notifyEventSent(
data.view.id,
StorageEvent.Action(data.action.frustration?.type?.size ?: 0)
)
is ResourceEvent -> notifyEventSent(data.view.id, StorageEvent.Resource)
is ErrorEvent -> {
if (data.error.isCrash != true) {
notifyEventSent(data.view.id, StorageEvent.Error)
}
}
is LongTaskEvent -> {
if (data.longTask.isFrozenFrame == true) {
notifyEventSent(data.view.id, StorageEvent.FrozenFrame)
} else {
notifyEventSent(data.view.id, StorageEvent.LongTask)
}
}
}
}

@WorkerThread
private fun persistViewEvent(data: ByteArray) {
sdkCore.writeLastViewEvent(data)
}

private fun notifyEventSent(viewId: String, storageEvent: StorageEvent) {
val rumMonitor = GlobalRumMonitor.get(sdkCore)
if (rumMonitor is AdvancedRumMonitor) {
rumMonitor.eventSent(viewId, storageEvent)
is ViewEvent -> sdkCore.writeLastViewEvent(rawData)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,8 @@
package com.datadog.android.rum.internal.domain.event

import com.datadog.android.api.InternalLogger
import com.datadog.android.api.SdkCore
import com.datadog.android.event.EventMapper
import com.datadog.android.event.NoOpEventMapper
import com.datadog.android.rum.GlobalRumMonitor
import com.datadog.android.rum.internal.monitor.AdvancedRumMonitor
import com.datadog.android.rum.internal.monitor.StorageEvent
import com.datadog.android.rum.model.ActionEvent
import com.datadog.android.rum.model.ErrorEvent
import com.datadog.android.rum.model.LongTaskEvent
Expand All @@ -24,7 +20,6 @@ import com.datadog.android.telemetry.model.TelemetryErrorEvent
import java.util.Locale

internal data class RumEventMapper(
val sdkCore: SdkCore,
val viewEventMapper: EventMapper<ViewEvent> = NoOpEventMapper(),
val errorEventMapper: EventMapper<ErrorEvent> = NoOpEventMapper(),
val resourceEventMapper: EventMapper<ResourceEvent> = NoOpEventMapper(),
Expand All @@ -35,11 +30,7 @@ internal data class RumEventMapper(
) : EventMapper<Any> {

override fun map(event: Any): Any? {
val mappedEvent = resolveEvent(event)
if (mappedEvent == null) {
notifyEventDropped(event)
}
return mappedEvent
return resolveEvent(event)
}

// region Internal
Expand Down Expand Up @@ -113,28 +104,6 @@ internal data class RumEventMapper(
}
}

private fun notifyEventDropped(event: Any) {
val monitor = (GlobalRumMonitor.get(sdkCore) as? AdvancedRumMonitor) ?: return
when (event) {
is ActionEvent -> monitor.eventDropped(
event.view.id,
StorageEvent.Action(frustrationCount = event.action.frustration?.type?.size ?: 0)
)
is ResourceEvent -> monitor.eventDropped(event.view.id, StorageEvent.Resource)
is ErrorEvent -> monitor.eventDropped(event.view.id, StorageEvent.Error)
is LongTaskEvent -> {
if (event.longTask.isFrozenFrame == true) {
monitor.eventDropped(event.view.id, StorageEvent.FrozenFrame)
} else {
monitor.eventDropped(event.view.id, StorageEvent.LongTask)
}
}
else -> {
// Nothing to do
}
}
}

// endregion

companion object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,17 @@
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
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.hasUserData
import com.datadog.android.rum.utils.newRumEventWriteOperation
import java.lang.ref.WeakReference
import java.util.UUID
import java.util.concurrent.TimeUnit
Expand Down Expand Up @@ -220,90 +221,96 @@ internal class RumActionScope(
} else {
ActionEvent.ActionEventSessionType.SYNTHETICS
}
val frustrations = mutableListOf<ActionEvent.Type>()
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<ActionEvent.Type>()
if (trackFrustrations && eventErrorCount > 0 && actualType == RumActionType.TAP) {
frustrations.add(ActionEvent.Type.ERROR_TAP)
}
sdkCore.newRumEventWriteOperation(writer) { datadogContext ->
val user = datadogContext.userInfo
val hasReplay = featuresContextResolver.resolveViewHasReplay(
datadogContext,
rumContext.viewId.orEmpty()
)

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()
)
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
}
),
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()
),
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)
),
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
)
}
.apply {
val storageEvent = StorageEvent.Action(frustrations.size)
onError {
it.eventDropped(rumContext.viewId.orEmpty(), storageEvent)
}
onSuccess {
it.eventSent(rumContext.viewId.orEmpty(), storageEvent)
}
}
.submit()

sent = true
}
Expand Down
Loading
Loading