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 8, 2023
1 parent bece8a1 commit 742d249
Show file tree
Hide file tree
Showing 33 changed files with 333 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.IOException
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,21 @@ internal class CoreFeature(
serviceName = configuration.service ?: appContext.packageName
envName = configuration.env
variant = configuration.variant
appBuildId = with(appContext.assets) {
try {
open(BUILD_ID_FILE_NAME).bufferedReader().use {
it.readText().trim()
}
} catch (@Suppress("SwallowedException") e: IOException) {
internalLogger.log(
InternalLogger.Level.INFO,
InternalLogger.Target.USER,
{ BUILD_ID_IS_MISSING_INFO_MESSAGE }
)
null
}
}

contextRef = WeakReference(appContext)
}

Expand All @@ -341,7 +358,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 Down Expand Up @@ -539,6 +555,13 @@ 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 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,10 @@ import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
import org.mockito.quality.Strictness
import java.io.File
import java.io.IOException
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 +121,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 +133,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 +473,44 @@ 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() { build ID is missing }`() {
// Given
whenever(
appContext.mockInstance.assets.open(CoreFeature.BUILD_ID_FILE_NAME)
) doThrow IOException()

// 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 `𝕄 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
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import java.io.File
import java.lang.ref.WeakReference
import java.nio.file.Files
import java.util.Locale
import java.util.UUID
import java.util.concurrent.ExecutorService
import java.util.concurrent.ScheduledThreadPoolExecutor

Expand All @@ -52,6 +53,7 @@ internal class CoreFeatureTestConfiguration<T : Context>(
lateinit var fakeFeaturesContext: MutableMap<String, Map<String, Any?>>
lateinit var fakeFilePersistenceConfig: FilePersistenceConfig
lateinit var fakeBatchSize: BatchSize
var fakeBuildId: String? = null

lateinit var mockUploadExecutor: ScheduledThreadPoolExecutor
lateinit var mockOkHttpClient: OkHttpClient
Expand Down Expand Up @@ -102,6 +104,7 @@ internal class CoreFeatureTestConfiguration<T : Context>(
}.toMutableMap()
fakeFilePersistenceConfig = forge.getForgery()
fakeBatchSize = forge.aValueFrom(BatchSize::class.java)
fakeBuildId = forge.aNullable { getForgery<UUID>().toString() }
}

private fun createMocks() {
Expand Down Expand Up @@ -136,6 +139,7 @@ internal class CoreFeatureTestConfiguration<T : Context>(
whenever(mockInstance.storageDir) doReturn fakeStorageDir
whenever(mockInstance.uploadFrequency) doReturn fakeUploadFrequency
whenever(mockInstance.site) doReturn fakeSite
whenever(mockInstance.appBuildId) doReturn fakeBuildId
whenever(mockInstance.featuresContext) doReturn fakeFeaturesContext

whenever(mockInstance.persistenceExecutorService) doReturn mockPersistenceExecutor
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import com.datadog.android.privacy.TrackingConsent
import fr.xgouchet.elmyr.Forge
import fr.xgouchet.elmyr.ForgeryFactory
import java.util.Locale
import java.util.UUID

class DatadogContextForgeryFactory : ForgeryFactory<DatadogContext> {

Expand All @@ -31,6 +32,7 @@ class DatadogContextForgeryFactory : ForgeryFactory<DatadogContext> {
deviceInfo = forge.getForgery(),
userInfo = forge.getForgery(),
trackingConsent = forge.aValueFrom(TrackingConsent::class.java),
appBuildId = forge.aNullable { getForgery<UUID>().toString() },
// building nested maps with default size slows down tests quite a lot, so will use
// an explicit small size
featuresContext = forge.aMap(size = 2) {
Expand Down

0 comments on commit 742d249

Please sign in to comment.