Skip to content

Commit

Permalink
Merge pull request #1909 from DataDog/nogorodnikov/rum-2878/sdk-captu…
Browse files Browse the repository at this point in the history
…res-and-reports-fatal-anrs

RUM-2878: Report fatal ANRs
  • Loading branch information
0xnm authored Mar 18, 2024
2 parents 9859578 + 5ffa623 commit d6cec44
Show file tree
Hide file tree
Showing 41 changed files with 3,620 additions and 351 deletions.
11 changes: 8 additions & 3 deletions dd-sdk-android-core/api/apiSurface
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ interface com.datadog.android.api.feature.Feature
const val RUM_FEATURE_NAME: String
const val TRACING_FEATURE_NAME: String
const val SESSION_REPLAY_FEATURE_NAME: String
const val NDK_CRASH_REPORTS_FEATURE_NAME: String
interface com.datadog.android.api.feature.FeatureEventReceiver
fun onReceive(Any)
interface com.datadog.android.api.feature.FeatureScope
Expand Down Expand Up @@ -145,7 +146,11 @@ interface com.datadog.android.core.InternalSdkCore : com.datadog.android.api.fea
val rootStorageDir: java.io.File?
val isDeveloperModeEnabled: Boolean
val firstPartyHostResolver: com.datadog.android.core.internal.net.FirstPartyHostHeaderTypeResolver
val lastViewEvent: com.google.gson.JsonObject?
val lastFatalAnrSent: Long?
fun writeLastViewEvent(ByteArray)
fun deleteLastViewEvent()
fun writeLastFatalAnrSent(Long)
fun getPersistenceExecutorService(): java.util.concurrent.ExecutorService
fun getAllFeatures(): List<com.datadog.android.api.feature.FeatureScope>
fun getDatadogContext(): com.datadog.android.api.context.DatadogContext?
Expand Down Expand Up @@ -228,9 +233,9 @@ fun java.io.File.existsSafe(com.datadog.android.api.InternalLogger): Boolean
fun java.io.File.readTextSafe(java.nio.charset.Charset = Charsets.UTF_8, com.datadog.android.api.InternalLogger): String?
fun java.io.File.readLinesSafe(java.nio.charset.Charset = Charsets.UTF_8, com.datadog.android.api.InternalLogger): List<String>?
interface com.datadog.android.core.internal.system.BuildSdkVersionProvider
fun version(): Int
class com.datadog.android.core.internal.system.DefaultBuildSdkVersionProvider : BuildSdkVersionProvider
override fun version(): Int
val version: Int
companion object
val DEFAULT: BuildSdkVersionProvider
class com.datadog.android.core.internal.thread.LoggingScheduledThreadPoolExecutor : java.util.concurrent.ScheduledThreadPoolExecutor
constructor(Int, com.datadog.android.api.InternalLogger)
override fun afterExecute(Runnable?, Throwable?)
Expand Down
14 changes: 10 additions & 4 deletions dd-sdk-android-core/api/dd-sdk-android-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,7 @@ public final class com/datadog/android/api/context/UserInfo {
public abstract interface class com/datadog/android/api/feature/Feature {
public static final field Companion Lcom/datadog/android/api/feature/Feature$Companion;
public static final field LOGS_FEATURE_NAME Ljava/lang/String;
public static final field NDK_CRASH_REPORTS_FEATURE_NAME Ljava/lang/String;
public static final field RUM_FEATURE_NAME Ljava/lang/String;
public static final field SESSION_REPLAY_FEATURE_NAME Ljava/lang/String;
public static final field TRACING_FEATURE_NAME Ljava/lang/String;
Expand All @@ -304,6 +305,7 @@ public abstract interface class com/datadog/android/api/feature/Feature {

public final class com/datadog/android/api/feature/Feature$Companion {
public static final field LOGS_FEATURE_NAME Ljava/lang/String;
public static final field NDK_CRASH_REPORTS_FEATURE_NAME Ljava/lang/String;
public static final field RUM_FEATURE_NAME Ljava/lang/String;
public static final field SESSION_REPLAY_FEATURE_NAME Ljava/lang/String;
public static final field TRACING_FEATURE_NAME Ljava/lang/String;
Expand Down Expand Up @@ -430,14 +432,18 @@ public final class com/datadog/android/api/storage/RawBatchEvent {
}

public abstract interface class com/datadog/android/core/InternalSdkCore : com/datadog/android/api/feature/FeatureSdkCore {
public abstract fun deleteLastViewEvent ()V
public abstract fun getAllFeatures ()Ljava/util/List;
public abstract fun getDatadogContext ()Lcom/datadog/android/api/context/DatadogContext;
public abstract fun getFirstPartyHostResolver ()Lcom/datadog/android/core/internal/net/FirstPartyHostHeaderTypeResolver;
public abstract fun getLastFatalAnrSent ()Ljava/lang/Long;
public abstract fun getLastViewEvent ()Lcom/google/gson/JsonObject;
public abstract fun getNetworkInfo ()Lcom/datadog/android/api/context/NetworkInfo;
public abstract fun getPersistenceExecutorService ()Ljava/util/concurrent/ExecutorService;
public abstract fun getRootStorageDir ()Ljava/io/File;
public abstract fun getTrackingConsent ()Lcom/datadog/android/privacy/TrackingConsent;
public abstract fun isDeveloperModeEnabled ()Z
public abstract fun writeLastFatalAnrSent (J)V
public abstract fun writeLastViewEvent ([B)V
}

Expand Down Expand Up @@ -624,12 +630,12 @@ public final class com/datadog/android/core/internal/persistence/file/FileExtKt
}

public abstract interface class com/datadog/android/core/internal/system/BuildSdkVersionProvider {
public abstract fun version ()I
public static final field Companion Lcom/datadog/android/core/internal/system/BuildSdkVersionProvider$Companion;
public abstract fun getVersion ()I
}

public final class com/datadog/android/core/internal/system/DefaultBuildSdkVersionProvider : com/datadog/android/core/internal/system/BuildSdkVersionProvider {
public fun <init> ()V
public fun version ()I
public final class com/datadog/android/core/internal/system/BuildSdkVersionProvider$Companion {
public final fun getDEFAULT ()Lcom/datadog/android/core/internal/system/BuildSdkVersionProvider;
}

public final class com/datadog/android/core/internal/thread/LoggingScheduledThreadPoolExecutor : java/util/concurrent/ScheduledThreadPoolExecutor {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,10 @@ interface Feature {
* Session Replay feature name.
*/
const val SESSION_REPLAY_FEATURE_NAME: String = "session-replay"

/**
* NDK Crash Reports feature name.
*/
const val NDK_CRASH_REPORTS_FEATURE_NAME: String = "ndk-crash-reporting"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ package com.datadog.android.core
import android.app.Application
import android.content.Context
import android.content.pm.ApplicationInfo
import android.os.Build
import android.util.Log
import androidx.annotation.WorkerThread
import com.datadog.android.Datadog
Expand All @@ -22,7 +23,6 @@ import com.datadog.android.api.feature.Feature
import com.datadog.android.api.feature.FeatureEventReceiver
import com.datadog.android.api.feature.FeatureScope
import com.datadog.android.api.feature.FeatureSdkCore
import com.datadog.android.api.storage.RawBatchEvent
import com.datadog.android.core.configuration.BatchSize
import com.datadog.android.core.configuration.Configuration
import com.datadog.android.core.configuration.UploadFrequency
Expand All @@ -32,14 +32,12 @@ import com.datadog.android.core.internal.SdkFeature
import com.datadog.android.core.internal.lifecycle.ProcessLifecycleCallback
import com.datadog.android.core.internal.lifecycle.ProcessLifecycleMonitor
import com.datadog.android.core.internal.net.FirstPartyHostHeaderTypeResolver
import com.datadog.android.core.internal.persistence.file.FileWriter
import com.datadog.android.core.internal.persistence.file.batch.BatchFileReaderWriter
import com.datadog.android.core.internal.persistence.file.existsSafe
import com.datadog.android.core.internal.system.BuildSdkVersionProvider
import com.datadog.android.core.internal.utils.scheduleSafe
import com.datadog.android.error.internal.CrashReportsFeature
import com.datadog.android.ndk.internal.DatadogNdkCrashHandler
import com.datadog.android.ndk.internal.NdkCrashHandler
import com.datadog.android.privacy.TrackingConsent
import com.google.gson.JsonObject
import java.io.File
import java.util.Locale
import java.util.concurrent.ExecutorService
Expand All @@ -52,6 +50,7 @@ import java.util.concurrent.TimeUnit
* @param name the name of this instance
* @param internalLoggerProvider Provider for [InternalLogger] instance.
* @param persistenceExecutorServiceFactory Custom factory for persistence executor, used only in unit-tests
* @param buildSdkVersionProvider Build.VERSION.SDK_INT provider used for the test
*/
@Suppress("TooManyFunctions")
internal class DatadogCore(
Expand All @@ -60,7 +59,8 @@ internal class DatadogCore(
override val name: String,
internalLoggerProvider: (FeatureSdkCore) -> InternalLogger = { SdkInternalLogger(it) },
// only for unit tests
private val persistenceExecutorServiceFactory: ((InternalLogger) -> ExecutorService)? = null
private val persistenceExecutorServiceFactory: ((InternalLogger) -> ExecutorService)? = null,
private val buildSdkVersionProvider: BuildSdkVersionProvider = BuildSdkVersionProvider.DEFAULT
) : InternalSdkCore {

internal lateinit var coreFeature: CoreFeature
Expand All @@ -81,13 +81,6 @@ internal class DatadogCore(
internal val isActive: Boolean
get() = coreFeature.initialized.get()

private val ndkLastViewEventFileWriter: FileWriter<RawBatchEvent> by lazy {
BatchFileReaderWriter.create(
internalLogger = internalLogger,
encryption = coreFeature.localDataEncryption
)
}

private var processLifecycleMonitor: ProcessLifecycleMonitor? = null

// region SdkCore
Expand Down Expand Up @@ -183,6 +176,10 @@ internal class DatadogCore(
features.values.forEach {
it.clearAllData()
}
@Suppress("ThreadSafety") // removal of the data is done in synchronous manner
coreFeature.deleteLastViewEvent()
@Suppress("ThreadSafety") // removal of the data is done in synchronous manner
coreFeature.deleteLastFatalAnrSent()
}

/** @inheritDoc */
Expand Down Expand Up @@ -245,23 +242,41 @@ internal class DatadogCore(
override val rootStorageDir: File
get() = coreFeature.storageDir

@get:WorkerThread
override val lastViewEvent: JsonObject?
get() = coreFeature.lastViewEvent

@get:WorkerThread
override val lastFatalAnrSent: Long?
get() = coreFeature.lastFatalAnrSent

@WorkerThread
override fun writeLastViewEvent(data: ByteArray) {
// directory structure may not exist: currently it is a file which is located in NDK reports
// folder, so if NDK reporting plugin is not initialized, this NDK reports dir won't exist
// as well (and no need to write).
val lastViewEventFile = DatadogNdkCrashHandler.getLastViewEventFile(coreFeature.storageDir)
if (lastViewEventFile.parentFile?.existsSafe(internalLogger) == true) {
ndkLastViewEventFileWriter.writeData(lastViewEventFile, RawBatchEvent(data), false)
// we need to write it only if we are going to read ApplicationExitInfo (available on
// API 30+) or if there is NDK crash tracking enabled
if (buildSdkVersionProvider.version >= Build.VERSION_CODES.R ||
features.containsKey(Feature.NDK_CRASH_REPORTS_FEATURE_NAME)
) {
coreFeature.writeLastViewEvent(data)
} else {
internalLogger.log(
InternalLogger.Level.WARN,
InternalLogger.Level.INFO,
InternalLogger.Target.MAINTAINER,
{ LAST_VIEW_EVENT_DIR_MISSING_MESSAGE.format(Locale.US, lastViewEventFile.parent) }
{ NO_NEED_TO_WRITE_LAST_VIEW_EVENT }
)
}
}

@WorkerThread
override fun deleteLastViewEvent() {
coreFeature.deleteLastViewEvent()
}

@WorkerThread
override fun writeLastFatalAnrSent(anrTimestamp: Long) {
coreFeature.writeLastFatalAnrSent(anrTimestamp)
}

override fun getPersistenceExecutorService(): ExecutorService {
return coreFeature.persistenceExecutorService
}
Expand Down Expand Up @@ -487,8 +502,9 @@ internal class DatadogCore(
internal const val EVENT_RECEIVER_ALREADY_EXISTS =
"Feature \"%s\" already has event receiver registered, overwriting it."

const val LAST_VIEW_EVENT_DIR_MISSING_MESSAGE = "Directory structure %s for writing" +
" last view event doesn't exist."
internal const val NO_NEED_TO_WRITE_LAST_VIEW_EVENT =
"No need to write last RUM view event: NDK" +
" crash reports feature is not enabled and API is below 30."

internal val CONFIGURATION_TELEMETRY_DELAY_MS = TimeUnit.SECONDS.toMillis(5)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import com.datadog.android.api.feature.FeatureSdkCore
import com.datadog.android.core.internal.net.FirstPartyHostHeaderTypeResolver
import com.datadog.android.lint.InternalApi
import com.datadog.android.privacy.TrackingConsent
import com.google.gson.JsonObject
import java.io.File
import java.util.concurrent.ExecutorService

Expand Down Expand Up @@ -54,6 +55,20 @@ interface InternalSdkCore : FeatureSdkCore {
*/
val firstPartyHostResolver: FirstPartyHostHeaderTypeResolver

/**
* Reads last known RUM view event stored.
*/
@get:WorkerThread
@InternalApi
val lastViewEvent: JsonObject?

/**
* Reads information about last fatal ANR sent.
*/
@get:WorkerThread
@InternalApi
val lastFatalAnrSent: Long?

/**
* Writes current RUM view event to the dedicated file for the needs of NDK crash reporting.
*
Expand All @@ -63,6 +78,20 @@ interface InternalSdkCore : FeatureSdkCore {
@WorkerThread
fun writeLastViewEvent(data: ByteArray)

/**
* Deletes last RUM view event written.
*/
@InternalApi
@WorkerThread
fun deleteLastViewEvent()

/**
* Writes timestamp of the last fatal ANR sent.
*/
@InternalApi
@WorkerThread
fun writeLastFatalAnrSent(anrTimestamp: Long)

/**
* Get an executor service for persistence purposes.
* @return the persistence executor to use for this SDK
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import com.datadog.android.api.feature.FeatureScope
import com.datadog.android.core.internal.net.DefaultFirstPartyHostHeaderTypeResolver
import com.datadog.android.core.internal.net.FirstPartyHostHeaderTypeResolver
import com.datadog.android.privacy.TrackingConsent
import com.google.gson.JsonObject
import java.io.File
import java.util.concurrent.Callable
import java.util.concurrent.ExecutorService
Expand Down Expand Up @@ -57,6 +58,10 @@ internal object NoOpInternalSdkCore : InternalSdkCore {
get() = false
override val firstPartyHostResolver: FirstPartyHostHeaderTypeResolver
get() = DefaultFirstPartyHostHeaderTypeResolver(emptyMap())
override val lastViewEvent: JsonObject?
get() = null
override val lastFatalAnrSent: Long?
get() = null

// endregion

Expand Down Expand Up @@ -100,6 +105,10 @@ internal object NoOpInternalSdkCore : InternalSdkCore {

override fun writeLastViewEvent(data: ByteArray) = Unit

override fun deleteLastViewEvent() = Unit

override fun writeLastFatalAnrSent(anrTimestamp: Long) = Unit

override fun getPersistenceExecutorService(): ExecutorService = NoOpExecutorService()

override fun getAllFeatures(): List<FeatureScope> = emptyList()
Expand Down
Loading

0 comments on commit d6cec44

Please sign in to comment.