Skip to content
Open
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
4 changes: 3 additions & 1 deletion features/dd-sdk-android-rum/api/apiSurface
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ data class com.datadog.android.rum.RumConfiguration
fun setSessionSampleRate(Float): Builder
fun setRemoteConfigurationEnabled(Boolean): Builder
fun setBeforeSampling(BeforeSamplingCallback): Builder
fun setSessionOnError(Boolean): Builder
fun setSessionReplayOnError(Boolean): Builder
fun collectAccessibility(Boolean): Builder
fun setTelemetrySampleRate(Float): Builder
fun trackUserInteractions(Array<com.datadog.android.rum.tracking.ViewAttributesProvider> = emptyArray(), com.datadog.android.rum.tracking.InteractionPredicate = NoOpInteractionPredicate()): Builder
Expand Down Expand Up @@ -1659,7 +1661,7 @@ data class com.datadog.android.rum.model.ViewEvent
fun fromJson(kotlin.String): Application
fun fromJsonObject(com.google.gson.JsonObject): Application
data class ViewEventSession
constructor(kotlin.String, ViewEventSessionType, kotlin.Boolean? = null, kotlin.Boolean? = true, kotlin.Boolean? = null)
constructor(kotlin.String, ViewEventSessionType, kotlin.Boolean? = null, kotlin.Boolean? = true, kotlin.Boolean? = null, kotlin.Boolean? = null, kotlin.Boolean? = null)
fun toJson(): com.google.gson.JsonElement
companion object
fun fromJson(kotlin.String): ViewEventSession
Expand Down
14 changes: 10 additions & 4 deletions features/dd-sdk-android-rum/api/dd-sdk-android-rum.api
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ public final class com/datadog/android/rum/RumConfiguration$Builder {
public final fun setRemoteConfigurationEnabled (Z)Lcom/datadog/android/rum/RumConfiguration$Builder;
public final fun setResourceEventMapper (Lcom/datadog/android/event/EventMapper;)Lcom/datadog/android/rum/RumConfiguration$Builder;
public final fun setSessionListener (Lcom/datadog/android/rum/RumSessionListener;)Lcom/datadog/android/rum/RumConfiguration$Builder;
public final fun setSessionOnError (Z)Lcom/datadog/android/rum/RumConfiguration$Builder;
public final fun setSessionReplayOnError (Z)Lcom/datadog/android/rum/RumConfiguration$Builder;
public final fun setSessionSampleRate (F)Lcom/datadog/android/rum/RumConfiguration$Builder;
public final fun setSlowFramesConfiguration (Lcom/datadog/android/rum/configuration/SlowFramesConfiguration;)Lcom/datadog/android/rum/RumConfiguration$Builder;
public final fun setTelemetrySampleRate (F)Lcom/datadog/android/rum/RumConfiguration$Builder;
Expand Down Expand Up @@ -6037,20 +6039,24 @@ public final class com/datadog/android/rum/model/ViewEvent$Usr$Companion {

public final class com/datadog/android/rum/model/ViewEvent$ViewEventSession {
public static final field Companion Lcom/datadog/android/rum/model/ViewEvent$ViewEventSession$Companion;
public fun <init> (Ljava/lang/String;Lcom/datadog/android/rum/model/ViewEvent$ViewEventSessionType;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;)V
public synthetic fun <init> (Ljava/lang/String;Lcom/datadog/android/rum/model/ViewEvent$ViewEventSessionType;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun <init> (Ljava/lang/String;Lcom/datadog/android/rum/model/ViewEvent$ViewEventSessionType;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;)V
public synthetic fun <init> (Ljava/lang/String;Lcom/datadog/android/rum/model/ViewEvent$ViewEventSessionType;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Ljava/lang/String;
public final fun component2 ()Lcom/datadog/android/rum/model/ViewEvent$ViewEventSessionType;
public final fun component3 ()Ljava/lang/Boolean;
public final fun component4 ()Ljava/lang/Boolean;
public final fun component5 ()Ljava/lang/Boolean;
public final fun copy (Ljava/lang/String;Lcom/datadog/android/rum/model/ViewEvent$ViewEventSessionType;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;)Lcom/datadog/android/rum/model/ViewEvent$ViewEventSession;
public static synthetic fun copy$default (Lcom/datadog/android/rum/model/ViewEvent$ViewEventSession;Ljava/lang/String;Lcom/datadog/android/rum/model/ViewEvent$ViewEventSessionType;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;ILjava/lang/Object;)Lcom/datadog/android/rum/model/ViewEvent$ViewEventSession;
public final fun component6 ()Ljava/lang/Boolean;
public final fun component7 ()Ljava/lang/Boolean;
public final fun copy (Ljava/lang/String;Lcom/datadog/android/rum/model/ViewEvent$ViewEventSessionType;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;)Lcom/datadog/android/rum/model/ViewEvent$ViewEventSession;
public static synthetic fun copy$default (Lcom/datadog/android/rum/model/ViewEvent$ViewEventSession;Ljava/lang/String;Lcom/datadog/android/rum/model/ViewEvent$ViewEventSessionType;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;ILjava/lang/Object;)Lcom/datadog/android/rum/model/ViewEvent$ViewEventSession;
public fun equals (Ljava/lang/Object;)Z
public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/ViewEvent$ViewEventSession;
public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/ViewEvent$ViewEventSession;
public final fun getHasReplay ()Ljava/lang/Boolean;
public final fun getId ()Ljava/lang/String;
public final fun getSampledForError ()Ljava/lang/Boolean;
public final fun getSampledForErrorReplay ()Ljava/lang/Boolean;
public final fun getSampledForReplay ()Ljava/lang/Boolean;
public final fun getType ()Lcom/datadog/android/rum/model/ViewEvent$ViewEventSessionType;
public fun hashCode ()I
Expand Down
10 changes: 10 additions & 0 deletions features/dd-sdk-android-rum/src/main/json/rum/view-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,16 @@
"type": "boolean",
"description": "Whether this session has been sampled for replay",
"readOnly": true
},
"sampled_for_error": {
"type": "boolean",
"description": "FlashCat fork - whether this session was kept only because it reported an error; its detail starts where the withheld buffer reached",
"readOnly": true
},
"sampled_for_error_replay": {
"type": "boolean",
"description": "FlashCat fork - whether the replay of this session was kept only because the session reported an error",
"readOnly": true
}
},
"readOnly": true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,10 @@ object Rum {

val rumAppStartupTelemetryReporter = RumAppStartupTelemetryReporter.create(sdkCore = sdkCore)

val handler = Handler(Looper.getMainLooper())

val withheldEvents = rumFeature.withheldEvents

return DatadogRumMonitor(
applicationId = rumFeature.applicationId,
sdkCore = sdkCore,
Expand All @@ -137,8 +141,11 @@ object Rum {
// simply asks again, and there is nothing to ask with when the app did not opt in.
onSessionDrawn = { rumFeature.remoteConfigController?.onSessionStarted() },
beforeSampling = rumFeature.configuration.beforeSampling,
writer = rumFeature.dataWriter,
handler = Handler(Looper.getMainLooper()),
writer = withheldEvents ?: rumFeature.dataWriter,
withheldEvents = withheldEvents,
sessionOnError = rumFeature.configuration.sessionOnError,
sessionReplayOnError = rumFeature.configuration.sessionReplayOnError,
handler = handler,
telemetryEventHandler = TelemetryEventHandler(
sdkCore = sdkCore,
eventSampler = RateBasedSampler(rumFeature.telemetrySampleRate),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,51 @@ data class RumConfiguration internal constructor(
return this
}

/**
* Also keep the sessions the session sample rate does not keep, but only those that
* report an error.
*
* Such a session is collected in memory without uploading anything, keeping only its last
* minute. If it reports an error, that minute is uploaded and the session carries on like
* any collected one; if it ends without one, what it held is thrown away and nothing of it
* is ever sent. Its events report a session sample rate of 0 - it stands for itself rather
* than for the population a rate would imply - and its view events carry
* `session.sampled_for_error`. An error dropped by an event mapper does not count, and
* neither does a session whose rate [setBeforeSampling] set to 0.
*
* A native crash is reported at the next launch with the last view, like for any collected
* session, but without the minute leading up to it.
*
* Disabled by default. With [setRemoteConfigurationEnabled], the console's value wins over
* this one.
*
* @param enabled whether to keep the sessions that report an error.
*/
fun setSessionOnError(enabled: Boolean): Builder {
rumConfig = rumConfig.copy(sessionOnError = enabled)
return this
}

/**
* Also keep the replays the Session Replay sample rate does not keep, but only those of
* sessions that report an error.
*
* Such a replay is recorded in memory without uploading anything, keeping only its last
* minute; it is uploaded if the session reports an error, and thrown away if it ends
* without one. A session kept only through [setSessionOnError] holds its replay until its
* events are released, whether or not the replay rate kept it. View events of a session
* whose replay is kept this way carry `session.sampled_for_error_replay`.
*
* Needs the Session Replay feature. Disabled by default. With
* [setRemoteConfigurationEnabled], the console's value wins over this one.
*
* @param enabled whether to keep the replays of sessions that report an error.
*/
fun setSessionReplayOnError(enabled: Boolean): Builder {
rumConfig = rumConfig.copy(sessionReplayOnError = enabled)
return this
}

/**
* Whether to collect accessibility attributes - this is disabled by default.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ internal class DatadogLateCrashReporter(
)
writeScope {
rumWriter.write(it, toSendErrorEvent, EventType.CRASH)
if (lastViewEvent.isWithinSessionAvailability) {
if (lastViewEvent.isWithinSessionAvailability || lastViewEvent.isOnlyKnownHere) {
val updatedViewEvent = updateViewEvent(lastViewEvent)
rumWriter.write(it, updatedViewEvent, EventType.CRASH)
}
Expand Down Expand Up @@ -156,7 +156,7 @@ internal class DatadogLateCrashReporter(
// RUM events are written
sdkCore.writeLastFatalAnrSent(anrExitInfo.timestamp)
rumWriter.write(it, toSendErrorEvent, EventType.CRASH)
if (lastViewEvent.isWithinSessionAvailability) {
if (lastViewEvent.isWithinSessionAvailability || lastViewEvent.isOnlyKnownHere) {
val updatedViewEvent = updateViewEvent(lastViewEvent)
rumWriter.write(it, updatedViewEvent, EventType.CRASH)
}
Expand Down Expand Up @@ -326,6 +326,13 @@ internal class DatadogLateCrashReporter(
private val ViewEvent.sampleRate: Float
get() = dd.configuration?.sessionSampleRate?.toFloat() ?: 0f

/**
* FLASHCAT FORK - a session kept only on error uploaded nothing before it crashed: the view
* written locally is the only one the intake will ever get, however old it is by now.
*/
private val ViewEvent.isOnlyKnownHere: Boolean
get() = session.sampledForError == true

private val ViewEvent.isWithinSessionAvailability: Boolean
get() {
val now = sdkCore.timeProvider.getDeviceTimestampMillis()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,37 @@ internal class FeaturesContextResolver {
return (sessionReplayMetadata?.get(VIEW_RECORDS_COUNT_KEY) as? Long) ?: 0L
}

/**
* FLASHCAT FORK - whether the replay of this session is kept only because it reports an error,
* or null when Session Replay is not enabled.
*/
fun resolveSampledForErrorReplay(datadogContext: DatadogContext, sessionId: String): Boolean? {
val sessionReplayContext =
datadogContext.featuresContext[Feature.SESSION_REPLAY_FEATURE_NAME] ?: return null
return sessionReplayContext[REPLAY_ON_ERROR_SESSION_KEY] == sessionId
}

/**
* FLASHCAT FORK - whether this session is sampled for replay, reported only where an on-error
* mode is in play for it (null otherwise, so nothing changes for anyone who did not opt in). A
* replay still withheld counts when the session's events are withheld too: it is released along
* with them, so if these events ever reach the intake, so does the replay.
*/
fun resolveSampledForReplay(datadogContext: DatadogContext, sessionId: String, sampledForError: Boolean): Boolean? {
val sessionReplayContext =
datadogContext.featuresContext[Feature.SESSION_REPLAY_FEATURE_NAME] ?: return null
if (!sampledForError && sessionReplayContext[REPLAY_ON_ERROR_SESSION_KEY] != sessionId) return null
val isRecording = sessionReplayContext[REPLAY_ENABLED_KEY] as? Boolean ?: false
val isWithheld = sessionReplayContext[REPLAY_WITHHELD_KEY] as? Boolean ?: false
return isRecording && (!isWithheld || sampledForError)
}

companion object {
internal const val HAS_REPLAY_KEY = "has_replay"
internal const val VIEW_RECORDS_COUNT_KEY = "records_count"
internal const val VIEW_WITHHELD_RECORDS_COUNT_KEY = "withheld_records_count"
internal const val REPLAY_ENABLED_KEY = "session_replay_is_enabled"
internal const val REPLAY_ON_ERROR_SESSION_KEY = "session_replay_on_error_session_id"
internal const val REPLAY_WITHHELD_KEY = "session_replay_withheld"
}
}
Loading
Loading