Skip to content

Commit

Permalink
RUM-1823: Add buildId to the RUM error and Log events
Browse files Browse the repository at this point in the history
  • Loading branch information
0xnm committed Dec 13, 2023
1 parent 2f894e9 commit 86834e6
Show file tree
Hide file tree
Showing 33 changed files with 403 additions and 113 deletions.
2 changes: 1 addition & 1 deletion dd-sdk-android-core/api/apiSurface
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ interface com.datadog.android.api.SdkCore
fun addUserProperties(Map<String, Any?>)
fun clearAllData()
data class com.datadog.android.api.context.DatadogContext
constructor(com.datadog.android.DatadogSite, String, String, String, String, String, String, String, TimeInfo, ProcessInfo, NetworkInfo, DeviceInfo, UserInfo, com.datadog.android.privacy.TrackingConsent, Map<String, Map<String, Any?>>)
constructor(com.datadog.android.DatadogSite, String, String, String, String, String, String, String, TimeInfo, ProcessInfo, NetworkInfo, DeviceInfo, UserInfo, com.datadog.android.privacy.TrackingConsent, String?, Map<String, Map<String, Any?>>)
data class com.datadog.android.api.context.DeviceInfo
constructor(String, String, String, DeviceType, String, String, String, String, String)
enum com.datadog.android.api.context.DeviceType
Expand Down
10 changes: 6 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 @@ -122,14 +122,15 @@ public final class com/datadog/android/api/SdkCore$DefaultImpls {
}

public final class com/datadog/android/api/context/DatadogContext {
public fun <init> (Lcom/datadog/android/DatadogSite;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/api/context/TimeInfo;Lcom/datadog/android/api/context/ProcessInfo;Lcom/datadog/android/api/context/NetworkInfo;Lcom/datadog/android/api/context/DeviceInfo;Lcom/datadog/android/api/context/UserInfo;Lcom/datadog/android/privacy/TrackingConsent;Ljava/util/Map;)V
public fun <init> (Lcom/datadog/android/DatadogSite;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/api/context/TimeInfo;Lcom/datadog/android/api/context/ProcessInfo;Lcom/datadog/android/api/context/NetworkInfo;Lcom/datadog/android/api/context/DeviceInfo;Lcom/datadog/android/api/context/UserInfo;Lcom/datadog/android/privacy/TrackingConsent;Ljava/lang/String;Ljava/util/Map;)V
public final fun component1 ()Lcom/datadog/android/DatadogSite;
public final fun component10 ()Lcom/datadog/android/api/context/ProcessInfo;
public final fun component11 ()Lcom/datadog/android/api/context/NetworkInfo;
public final fun component12 ()Lcom/datadog/android/api/context/DeviceInfo;
public final fun component13 ()Lcom/datadog/android/api/context/UserInfo;
public final fun component14 ()Lcom/datadog/android/privacy/TrackingConsent;
public final fun component15 ()Ljava/util/Map;
public final fun component15 ()Ljava/lang/String;
public final fun component16 ()Ljava/util/Map;
public final fun component2 ()Ljava/lang/String;
public final fun component3 ()Ljava/lang/String;
public final fun component4 ()Ljava/lang/String;
Expand All @@ -138,9 +139,10 @@ public final class com/datadog/android/api/context/DatadogContext {
public final fun component7 ()Ljava/lang/String;
public final fun component8 ()Ljava/lang/String;
public final fun component9 ()Lcom/datadog/android/api/context/TimeInfo;
public final fun copy (Lcom/datadog/android/DatadogSite;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/api/context/TimeInfo;Lcom/datadog/android/api/context/ProcessInfo;Lcom/datadog/android/api/context/NetworkInfo;Lcom/datadog/android/api/context/DeviceInfo;Lcom/datadog/android/api/context/UserInfo;Lcom/datadog/android/privacy/TrackingConsent;Ljava/util/Map;)Lcom/datadog/android/api/context/DatadogContext;
public static synthetic fun copy$default (Lcom/datadog/android/api/context/DatadogContext;Lcom/datadog/android/DatadogSite;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/api/context/TimeInfo;Lcom/datadog/android/api/context/ProcessInfo;Lcom/datadog/android/api/context/NetworkInfo;Lcom/datadog/android/api/context/DeviceInfo;Lcom/datadog/android/api/context/UserInfo;Lcom/datadog/android/privacy/TrackingConsent;Ljava/util/Map;ILjava/lang/Object;)Lcom/datadog/android/api/context/DatadogContext;
public final fun copy (Lcom/datadog/android/DatadogSite;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/api/context/TimeInfo;Lcom/datadog/android/api/context/ProcessInfo;Lcom/datadog/android/api/context/NetworkInfo;Lcom/datadog/android/api/context/DeviceInfo;Lcom/datadog/android/api/context/UserInfo;Lcom/datadog/android/privacy/TrackingConsent;Ljava/lang/String;Ljava/util/Map;)Lcom/datadog/android/api/context/DatadogContext;
public static synthetic fun copy$default (Lcom/datadog/android/api/context/DatadogContext;Lcom/datadog/android/DatadogSite;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/api/context/TimeInfo;Lcom/datadog/android/api/context/ProcessInfo;Lcom/datadog/android/api/context/NetworkInfo;Lcom/datadog/android/api/context/DeviceInfo;Lcom/datadog/android/api/context/UserInfo;Lcom/datadog/android/privacy/TrackingConsent;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)Lcom/datadog/android/api/context/DatadogContext;
public fun equals (Ljava/lang/Object;)Z
public final fun getAppBuildId ()Ljava/lang/String;
public final fun getClientToken ()Ljava/lang/String;
public final fun getDeviceInfo ()Lcom/datadog/android/api/context/DeviceInfo;
public final fun getEnv ()Ljava/lang/String;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import com.datadog.android.privacy.TrackingConsent
* @property deviceInfo information about device
* @property userInfo information about the current user
* @property trackingConsent information about the current tracking consent
* @property appBuildId unique build ID of the running application. Will be missing if Datadog Gradle Plugin is not applied or obfuscation is not enabled for the running build.
* @property featuresContext agnostic dictionary with information from all features registered to
* the parent SDK instance
*/
Expand All @@ -48,5 +49,6 @@ data class DatadogContext(
val deviceInfo: DeviceInfo,
val userInfo: UserInfo,
val trackingConsent: TrackingConsent,
val appBuildId: String?,
val featuresContext: Map<String, Map<String, Any?>>
)
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ import okhttp3.OkHttpClient
import okhttp3.Protocol
import okhttp3.TlsVersion
import java.io.File
import java.io.FileNotFoundException
import java.lang.ref.WeakReference
import java.util.Locale
import java.util.concurrent.ConcurrentHashMap
Expand Down Expand Up @@ -134,6 +135,7 @@ internal class CoreFeature(
internal var batchProcessingLevel: BatchProcessingLevel = BatchProcessingLevel.MEDIUM
internal var ndkCrashHandler: NdkCrashHandler = NoOpNdkCrashHandler()
internal var site: DatadogSite = DatadogSite.US1
internal var appBuildId: String? = null

internal lateinit var uploadExecutorService: ScheduledThreadPoolExecutor
internal lateinit var persistenceExecutorService: ExecutorService
Expand Down Expand Up @@ -332,6 +334,8 @@ internal class CoreFeature(
serviceName = configuration.service ?: appContext.packageName
envName = configuration.env
variant = configuration.variant
appBuildId = readBuildId(appContext)

contextRef = WeakReference(appContext)
}

Expand All @@ -341,7 +345,6 @@ internal class CoreFeature(
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
getPackageInfo(packageName, PackageManager.PackageInfoFlags.of(0))
} else {
@Suppress("DEPRECATION")
getPackageInfo(packageName, 0)
}
}
Expand All @@ -356,6 +359,31 @@ internal class CoreFeature(
}
}

private fun readBuildId(context: Context): String? {
return with(context.assets) {
try {
open(BUILD_ID_FILE_NAME).bufferedReader().use {
it.readText().trim()
}
} catch (@Suppress("SwallowedException") e: FileNotFoundException) {
internalLogger.log(
InternalLogger.Level.INFO,
InternalLogger.Target.USER,
{ BUILD_ID_IS_MISSING_INFO_MESSAGE }
)
null
} catch (@Suppress("TooGenericExceptionCaught") e: Exception) {
internalLogger.log(
InternalLogger.Level.ERROR,
targets = listOf(InternalLogger.Target.USER, InternalLogger.Target.TELEMETRY),
{ BUILD_ID_READ_ERROR },
e
)
null
}
}
}

private fun readConfigurationSettings(configuration: Configuration.Core) {
batchSize = configuration.batchSize
uploadFrequency = configuration.uploadFrequency
Expand Down Expand Up @@ -539,6 +567,15 @@ internal class CoreFeature(
internal const val DEFAULT_SDK_VERSION = BuildConfig.SDK_VERSION_NAME
internal const val DEFAULT_APP_VERSION = "?"

// should be the same as in dd-sdk-android-gradle-plugin
internal const val BUILD_ID_FILE_NAME = "datadog.buildId"
internal const val BUILD_ID_IS_MISSING_INFO_MESSAGE =
"Build ID is not found in the application" +
" assets. If you are using obfuscation, please use Datadog Gradle Plugin 1.13.0" +
" or above to be able to de-obfuscate stacktraces."
internal const val BUILD_ID_READ_ERROR =
"Failed to read Build ID information, de-obfuscation may not work properly."

internal val RESTRICTED_CIPHER_SUITES = arrayOf(
// TLS 1.3

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ internal class DatadogContextProvider(val coreFeature: CoreFeature) : ContextPro
},
userInfo = coreFeature.userInfoProvider.getUserInfo(),
trackingConsent = coreFeature.trackingConsentProvider.getConsent(),
appBuildId = coreFeature.appBuildId,
// toMap call here (and in getFeatureContext) is VERY important - this will make
// independent snapshot of the features context which is not affected by the
// changes which can be made later by another thread.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ internal class NoOpContextProvider : ContextProvider {
deviceInfo = DeviceInfo("", "", "", DeviceType.OTHER, "", "", "", "", ""),
userInfo = UserInfo(null, null, null, emptyMap()),
trackingConsent = TrackingConsent.NOT_GRANTED,
appBuildId = null,
featuresContext = emptyMap()
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import com.datadog.android.privacy.TrackingConsent
import com.datadog.android.security.Encryption
import com.datadog.android.utils.config.ApplicationContextTestConfiguration
import com.datadog.android.utils.forge.Configurator
import com.datadog.android.utils.verifyLog
import com.datadog.tools.unit.annotations.TestConfigurationsProvider
import com.datadog.tools.unit.annotations.TestTargetApi
import com.datadog.tools.unit.assertj.containsInstanceOf
Expand Down Expand Up @@ -79,8 +80,13 @@ import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
import org.mockito.quality.Strictness
import java.io.File
import java.io.FileNotFoundException
import java.io.IOException
import java.io.InputStream
import java.lang.RuntimeException
import java.net.Proxy
import java.util.Locale
import java.util.UUID
import java.util.concurrent.ExecutorService
import java.util.concurrent.LinkedBlockingQueue
import java.util.concurrent.ScheduledThreadPoolExecutor
Expand Down Expand Up @@ -118,6 +124,9 @@ internal class CoreFeatureTest {
@StringForgery(type = StringForgeryType.ALPHA_NUMERICAL)
lateinit var fakeSdkInstanceId: String

@Forgery
lateinit var fakeBuildId: UUID

@BeforeEach
fun `set up`() {
CoreFeature.disableKronosBackgroundSync = true
Expand All @@ -127,6 +136,9 @@ internal class CoreFeatureTest {
)
whenever(appContext.mockInstance.getSystemService(Context.CONNECTIVITY_SERVICE))
.doReturn(mockConnectivityMgr)
whenever(
appContext.mockInstance.assets.open(CoreFeature.BUILD_ID_FILE_NAME)
) doReturn fakeBuildId.toString().byteInputStream()
whenever(mockPersistenceExecutorService.execute(any())) doAnswer {
it.getArgument<Runnable>(0).run()
}
Expand Down Expand Up @@ -464,6 +476,97 @@ internal class CoreFeatureTest {
assertThat(testedFeature.uploadFrequency).isEqualTo(fakeConfig.coreConfig.uploadFrequency)
}

@Test
fun `𝕄 initializes build ID 𝕎 initialize()`() {
// When
testedFeature.initialize(
appContext.mockInstance,
fakeSdkInstanceId,
fakeConfig,
fakeConsent
)

// Then
assertThat(testedFeature.appBuildId).isEqualTo(fakeBuildId.toString())
}

@Test
fun `𝕄 initializes build ID 𝕎 initialize() { asset manager is closed }`() {
// Given
whenever(
appContext.mockInstance.assets.open(CoreFeature.BUILD_ID_FILE_NAME)
) doThrow RuntimeException()

// When
testedFeature.initialize(
appContext.mockInstance,
fakeSdkInstanceId,
fakeConfig,
fakeConsent
)

// Then
assertThat(testedFeature.appBuildId).isNull()
mockInternalLogger.verifyLog(
InternalLogger.Level.ERROR,
targets = listOf(InternalLogger.Target.USER, InternalLogger.Target.TELEMETRY),
message = CoreFeature.BUILD_ID_READ_ERROR,
throwableClass = RuntimeException::class.java
)
}

@Test
fun `𝕄 initializes build ID 𝕎 initialize() { build ID file is missing }`() {
// Given
whenever(
appContext.mockInstance.assets.open(CoreFeature.BUILD_ID_FILE_NAME)
) doThrow FileNotFoundException()

// When
testedFeature.initialize(
appContext.mockInstance,
fakeSdkInstanceId,
fakeConfig,
fakeConsent
)

// Then
assertThat(testedFeature.appBuildId).isNull()
mockInternalLogger.verifyLog(
InternalLogger.Level.INFO,
InternalLogger.Target.USER,
CoreFeature.BUILD_ID_IS_MISSING_INFO_MESSAGE
)
}

@Test
fun `𝕄 initializes build ID 𝕎 initialize() { IOException during build ID read }`() {
// Given
val mockBrokenStream = mock<InputStream>().apply {
whenever(read(any())) doThrow IOException()
}
whenever(
appContext.mockInstance.assets.open(CoreFeature.BUILD_ID_FILE_NAME)
) doReturn mockBrokenStream

// When
testedFeature.initialize(
appContext.mockInstance,
fakeSdkInstanceId,
fakeConfig,
fakeConsent
)

// Then
assertThat(testedFeature.appBuildId).isNull()
mockInternalLogger.verifyLog(
InternalLogger.Level.ERROR,
targets = listOf(InternalLogger.Target.USER, InternalLogger.Target.TELEMETRY),
message = CoreFeature.BUILD_ID_READ_ERROR,
throwableClass = IOException::class.java
)
}

@Test
fun `𝕄 initialize okhttp with strict network policy 𝕎 initialize()`() {
// When
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ internal class DatadogContextProviderTest {
assertThat(context.userInfo.additionalProperties)
.isEqualTo(fakeUserInfo.additionalProperties)

assertThat(context.appBuildId).isEqualTo(coreFeature.mockInstance.appBuildId)
assertThat(context.trackingConsent).isEqualTo(fakeTrackingConsent)

assertThat(context.featuresContext).isEqualTo(coreFeature.mockInstance.featuresContext)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ import com.datadog.android.core.internal.data.upload.UploadWorker
import com.datadog.android.core.internal.utils.TAG_DATADOG_UPLOAD
import com.datadog.android.core.internal.utils.UPLOAD_WORKER_NAME
import com.datadog.android.utils.config.ApplicationContextTestConfiguration
import com.datadog.android.utils.forge.Configurator
import com.datadog.tools.unit.annotations.TestConfigurationsProvider
import com.datadog.tools.unit.extensions.TestConfigurationExtension
import com.datadog.tools.unit.extensions.config.TestConfiguration
import com.datadog.tools.unit.setStaticValue
import fr.xgouchet.elmyr.junit5.ForgeConfiguration
import fr.xgouchet.elmyr.junit5.ForgeExtension
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
Expand All @@ -45,6 +47,7 @@ import org.mockito.quality.Strictness
ExtendWith(TestConfigurationExtension::class)
)
@MockitoSettings(strictness = Strictness.LENIENT)
@ForgeConfiguration(Configurator::class)
internal class ProcessLifecycleCallbackTest {

lateinit var testedCallback: ProcessLifecycleCallback
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import android.content.Context
import android.content.pm.ApplicationInfo
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.content.res.AssetManager
import com.datadog.android.core.internal.CoreFeature
import com.datadog.tools.unit.extensions.config.MockTestConfiguration
import fr.xgouchet.elmyr.Forge
import org.mockito.kotlin.any
Expand All @@ -18,6 +20,7 @@ import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
import java.io.File
import java.nio.file.Files
import java.util.UUID

internal open class ApplicationContextTestConfiguration<T : Context>(klass: Class<T>) :
MockTestConfiguration<T>(klass) {
Expand All @@ -26,10 +29,12 @@ internal open class ApplicationContextTestConfiguration<T : Context>(klass: Clas
lateinit var fakeVersionName: String
var fakeVersionCode: Int = 0
lateinit var fakeVariant: String
lateinit var fakeBuildId: String

lateinit var fakePackageInfo: PackageInfo
lateinit var fakeAppInfo: ApplicationInfo
lateinit var mockPackageManager: PackageManager
lateinit var mockAssetManager: AssetManager

lateinit var fakeSandboxDir: File
lateinit var fakeCacheDir: File
Expand All @@ -42,11 +47,13 @@ internal open class ApplicationContextTestConfiguration<T : Context>(klass: Clas

createFakeInfo(forge)
mockPackageManager()
mockAssetManager()

whenever(mockInstance.applicationContext) doReturn mockInstance
whenever(mockInstance.packageManager) doReturn mockPackageManager
whenever(mockInstance.packageName) doReturn fakePackageName
whenever(mockInstance.applicationInfo) doReturn fakeAppInfo
whenever(mockInstance.assets) doReturn mockAssetManager

// ???
whenever(mockInstance.getSystemService(Context.ACTIVITY_SERVICE)) doReturn mock()
Expand Down Expand Up @@ -74,6 +81,7 @@ internal open class ApplicationContextTestConfiguration<T : Context>(klass: Clas
fakeVersionName = forge.aStringMatching("[0-9](\\.[0-9]{1,3}){2,3}")
fakeVersionCode = forge.anInt(1, 65536)
fakeVariant = forge.anElementFrom(forge.anAlphabeticalString(), "")
fakeBuildId = forge.getForgery<UUID>().toString()

fakePackageInfo = PackageInfo()
fakePackageInfo.packageName = fakePackageName
Expand All @@ -93,9 +101,15 @@ internal open class ApplicationContextTestConfiguration<T : Context>(klass: Clas
PackageManager.PackageInfoFlags.of(0)
)
) doReturn fakePackageInfo
@Suppress("DEPRECATION")
whenever(mockPackageManager.getPackageInfo(fakePackageName, 0)) doReturn fakePackageInfo
}

private fun mockAssetManager() {
mockAssetManager = mock()
whenever(
mockAssetManager.open(CoreFeature.BUILD_ID_FILE_NAME)
) doReturn fakeBuildId.byteInputStream()
}

// endregion
}

0 comments on commit 86834e6

Please sign in to comment.