From a59c5297528521b0ce81535f9d72f7b7d9b4bf18 Mon Sep 17 00:00:00 2001 From: tonzhan2 <140451394+tonzhan2@users.noreply.github.com> Date: Tue, 15 Oct 2024 11:04:43 -0700 Subject: [PATCH] Added versionCode, applicationID, and customUUID from AndroidManifest.XML (if it exists) to crash and ANR spans (#993) (#1002) --- .../splunk/rum/ErrorIdentifierExtractor.java | 92 +++++++++++++ .../com/splunk/rum/ErrorIdentifierInfo.java | 49 +++++++ .../java/com/splunk/rum/RumInitializer.java | 74 +++++++---- .../main/java/com/splunk/rum/SplunkRum.java | 3 + .../rum/ErrorIdentifierExtractorTest.java | 122 ++++++++++++++++++ 5 files changed, 318 insertions(+), 22 deletions(-) create mode 100644 splunk-otel-android/src/main/java/com/splunk/rum/ErrorIdentifierExtractor.java create mode 100644 splunk-otel-android/src/main/java/com/splunk/rum/ErrorIdentifierInfo.java create mode 100644 splunk-otel-android/src/test/java/com/splunk/rum/ErrorIdentifierExtractorTest.java diff --git a/splunk-otel-android/src/main/java/com/splunk/rum/ErrorIdentifierExtractor.java b/splunk-otel-android/src/main/java/com/splunk/rum/ErrorIdentifierExtractor.java new file mode 100644 index 00000000..05f4f424 --- /dev/null +++ b/splunk-otel-android/src/main/java/com/splunk/rum/ErrorIdentifierExtractor.java @@ -0,0 +1,92 @@ +/* + * Copyright Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.splunk.rum; + +import android.app.Application; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.util.Log; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +public class ErrorIdentifierExtractor { + + private static final String SPLUNK_UUID_MANIFEST_KEY = "SPLUNK_O11Y_CUSTOM_UUID"; + private final Application application; + private final PackageManager packageManager; + @Nullable private final ApplicationInfo applicationInfo; + + public ErrorIdentifierExtractor(@NonNull Application application) { + this.application = application; + this.packageManager = application.getPackageManager(); + ApplicationInfo appInfo; + try { + appInfo = + packageManager.getApplicationInfo( + application.getPackageName(), PackageManager.GET_META_DATA); + } catch (Exception e) { + Log.e( + SplunkRum.LOG_TAG, + "Failed to initialize ErrorIdentifierExtractor: " + e.getMessage()); + appInfo = null; + } + this.applicationInfo = appInfo; + } + + public ErrorIdentifierInfo extractInfo() { + String applicationId = null; + String versionCode = retrieveVersionCode(); + String customUUID = retrieveCustomUUID(); + + if (applicationInfo != null) { + applicationId = applicationInfo.packageName; + } else { + Log.e(SplunkRum.LOG_TAG, "ApplicationInfo is null, cannot extract applicationId"); + } + + return new ErrorIdentifierInfo(applicationId, versionCode, customUUID); + } + + @Nullable + private String retrieveVersionCode() { + try { + PackageInfo packageInfo = + packageManager.getPackageInfo(application.getPackageName(), 0); + return String.valueOf(packageInfo.versionCode); + } catch (Exception e) { + Log.e(SplunkRum.LOG_TAG, "Failed to get application version code", e); + return null; + } + } + + @Nullable + private String retrieveCustomUUID() { + if (applicationInfo == null) { + Log.e(SplunkRum.LOG_TAG, "ApplicationInfo is null; cannot retrieve Custom UUID."); + return null; + } + Bundle bundle = applicationInfo.metaData; + if (bundle != null) { + return bundle.getString(SPLUNK_UUID_MANIFEST_KEY); + } else { + Log.e(SplunkRum.LOG_TAG, "Application MetaData bundle is null"); + return null; + } + } +} diff --git a/splunk-otel-android/src/main/java/com/splunk/rum/ErrorIdentifierInfo.java b/splunk-otel-android/src/main/java/com/splunk/rum/ErrorIdentifierInfo.java new file mode 100644 index 00000000..be6327e5 --- /dev/null +++ b/splunk-otel-android/src/main/java/com/splunk/rum/ErrorIdentifierInfo.java @@ -0,0 +1,49 @@ +/* + * Copyright Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.splunk.rum; + +import androidx.annotation.Nullable; + +public class ErrorIdentifierInfo { + @Nullable private final String applicationId; + @Nullable private final String versionCode; + @Nullable private final String customUUID; + + public ErrorIdentifierInfo( + @Nullable String applicationId, + @Nullable String versionCode, + @Nullable String customUUID) { + this.applicationId = applicationId; + this.versionCode = versionCode; + this.customUUID = customUUID; + } + + @Nullable + public String getApplicationId() { + return applicationId; + } + + @Nullable + public String getVersionCode() { + return versionCode; + } + + @Nullable + public String getCustomUUID() { + return customUUID; + } +} diff --git a/splunk-otel-android/src/main/java/com/splunk/rum/RumInitializer.java b/splunk-otel-android/src/main/java/com/splunk/rum/RumInitializer.java index 3e293733..8fed2b4e 100644 --- a/splunk-otel-android/src/main/java/com/splunk/rum/RumInitializer.java +++ b/splunk-otel-android/src/main/java/com/splunk/rum/RumInitializer.java @@ -16,12 +16,15 @@ package com.splunk.rum; +import static com.splunk.rum.SplunkRum.APPLICATION_ID_KEY; import static com.splunk.rum.SplunkRum.APP_NAME_KEY; +import static com.splunk.rum.SplunkRum.APP_VERSION_CODE_KEY; import static com.splunk.rum.SplunkRum.COMPONENT_APPSTART; import static com.splunk.rum.SplunkRum.COMPONENT_ERROR; import static com.splunk.rum.SplunkRum.COMPONENT_KEY; import static com.splunk.rum.SplunkRum.COMPONENT_UI; import static com.splunk.rum.SplunkRum.RUM_TRACER_NAME; +import static com.splunk.rum.SplunkRum.SPLUNK_OLLY_UUID_KEY; import static io.opentelemetry.android.RumConstants.APP_START_SPAN_NAME; import static io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor.constant; import static io.opentelemetry.semconv.ResourceAttributes.DEPLOYMENT_ENVIRONMENT; @@ -40,7 +43,9 @@ import io.opentelemetry.android.config.OtelRumConfig; import io.opentelemetry.android.instrumentation.activity.VisibleScreenTracker; import io.opentelemetry.android.instrumentation.anr.AnrDetector; +import io.opentelemetry.android.instrumentation.anr.AnrDetectorBuilder; import io.opentelemetry.android.instrumentation.crash.CrashReporter; +import io.opentelemetry.android.instrumentation.crash.CrashReporterBuilder; import io.opentelemetry.android.instrumentation.lifecycle.AndroidLifecycleInstrumentation; import io.opentelemetry.android.instrumentation.network.CurrentNetworkProvider; import io.opentelemetry.android.instrumentation.slowrendering.SlowRenderingDetector; @@ -286,45 +291,70 @@ private Resource createSplunkResource() { private void installAnrDetector(OpenTelemetryRumBuilder otelRumBuilder, Looper mainLooper) { otelRumBuilder.addInstrumentation( instrumentedApplication -> { - AnrDetector.builder() - .addAttributesExtractor(constant(COMPONENT_KEY, COMPONENT_ERROR)) - .setMainLooper(mainLooper) - .build() - .installOn(instrumentedApplication); + ErrorIdentifierExtractor extractor = new ErrorIdentifierExtractor(application); + ErrorIdentifierInfo errorIdentifierInfo = extractor.extractInfo(); + String applicationId = errorIdentifierInfo.getApplicationId(); + String versionCode = errorIdentifierInfo.getVersionCode(); + String uuid = errorIdentifierInfo.getCustomUUID(); - initializationEvents.emit("anrMonitorInitialized"); - }); - } + AnrDetectorBuilder builder = AnrDetector.builder(); + builder.addAttributesExtractor(constant(COMPONENT_KEY, COMPONENT_ERROR)); - private void installSlowRenderingDetector(OpenTelemetryRumBuilder otelRumBuilder) { - otelRumBuilder.addInstrumentation( - instrumentedApplication -> { - SlowRenderingDetector.builder() - .setSlowRenderingDetectionPollInterval( - builder.slowRenderingDetectionPollInterval) - .build() - .installOn(instrumentedApplication); - initializationEvents.emit("slowRenderingDetectorInitialized"); + if (applicationId != null) + builder.addAttributesExtractor(constant(APPLICATION_ID_KEY, applicationId)); + if (versionCode != null) + builder.addAttributesExtractor(constant(APP_VERSION_CODE_KEY, versionCode)); + if (uuid != null) + builder.addAttributesExtractor(constant(SPLUNK_OLLY_UUID_KEY, uuid)); + + builder.setMainLooper(mainLooper).build().installOn(instrumentedApplication); + + initializationEvents.emit("anrMonitorInitialized"); }); } private void installCrashReporter(OpenTelemetryRumBuilder otelRumBuilder) { otelRumBuilder.addInstrumentation( instrumentedApplication -> { - CrashReporter.builder() - .addAttributesExtractor( + ErrorIdentifierExtractor extractor = new ErrorIdentifierExtractor(application); + ErrorIdentifierInfo errorIdentifierInfo = extractor.extractInfo(); + String applicationId = errorIdentifierInfo.getApplicationId(); + String versionCode = errorIdentifierInfo.getVersionCode(); + String uuid = errorIdentifierInfo.getCustomUUID(); + + CrashReporterBuilder builder = CrashReporter.builder(); + builder.addAttributesExtractor( RuntimeDetailsExtractor.create( instrumentedApplication .getApplication() .getApplicationContext())) - .addAttributesExtractor(new CrashComponentExtractor()) - .build() - .installOn(instrumentedApplication); + .addAttributesExtractor(new CrashComponentExtractor()); + + if (applicationId != null) + builder.addAttributesExtractor(constant(APPLICATION_ID_KEY, applicationId)); + if (versionCode != null) + builder.addAttributesExtractor(constant(APP_VERSION_CODE_KEY, versionCode)); + if (uuid != null) + builder.addAttributesExtractor(constant(SPLUNK_OLLY_UUID_KEY, uuid)); + + builder.build().installOn(instrumentedApplication); initializationEvents.emit("crashReportingInitialized"); }); } + private void installSlowRenderingDetector(OpenTelemetryRumBuilder otelRumBuilder) { + otelRumBuilder.addInstrumentation( + instrumentedApplication -> { + SlowRenderingDetector.builder() + .setSlowRenderingDetectionPollInterval( + builder.slowRenderingDetectionPollInterval) + .build() + .installOn(instrumentedApplication); + initializationEvents.emit("slowRenderingDetectorInitialized"); + }); + } + // visible for testing SpanExporter buildFilteringExporter( CurrentNetworkProvider currentNetworkProvider, diff --git a/splunk-otel-android/src/main/java/com/splunk/rum/SplunkRum.java b/splunk-otel-android/src/main/java/com/splunk/rum/SplunkRum.java index 33551378..84eaec05 100644 --- a/splunk-otel-android/src/main/java/com/splunk/rum/SplunkRum.java +++ b/splunk-otel-android/src/main/java/com/splunk/rum/SplunkRum.java @@ -67,6 +67,9 @@ public class SplunkRum { static final AttributeKey APP_NAME_KEY = stringKey("app"); static final AttributeKey RUM_VERSION_KEY = stringKey("splunk.rum.version"); + static final AttributeKey APPLICATION_ID_KEY = stringKey("app.application.id"); + static final AttributeKey APP_VERSION_CODE_KEY = stringKey("app.version.code"); + static final AttributeKey SPLUNK_OLLY_UUID_KEY = stringKey("app.splunk.olly.uuid"); @Nullable private static SplunkRum INSTANCE; diff --git a/splunk-otel-android/src/test/java/com/splunk/rum/ErrorIdentifierExtractorTest.java b/splunk-otel-android/src/test/java/com/splunk/rum/ErrorIdentifierExtractorTest.java new file mode 100644 index 00000000..0bdee7eb --- /dev/null +++ b/splunk-otel-android/src/test/java/com/splunk/rum/ErrorIdentifierExtractorTest.java @@ -0,0 +1,122 @@ +/* + * Copyright Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.splunk.rum; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +import android.app.Application; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.os.Bundle; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +public class ErrorIdentifierExtractorTest { + private static final String SPLUNK_UUID_MANIFEST_KEY = "SPLUNK_O11Y_CUSTOM_UUID"; + private static final String TEST_PACKAGE_NAME = "splunk.test.package.name"; + private static final String TEST_VERSION_CODE = "123"; + private static final String TEST_UUID = "test-uuid"; + + @Mock private Application mockApplication; + @Mock private PackageManager mockPackageManager; + @Mock private PackageInfo mockPackageInfo; + @Mock private ApplicationInfo mockApplicationInfo; + @Mock private Bundle mockMetadata; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.openMocks(this); + + when(mockApplication.getApplicationContext()).thenReturn(mockApplication); + when(mockApplication.getPackageManager()).thenReturn(mockPackageManager); + when(mockApplication.getPackageName()).thenReturn(TEST_PACKAGE_NAME); + + mockApplicationInfo.packageName = TEST_PACKAGE_NAME; + mockApplicationInfo.metaData = mockMetadata; + + when(mockPackageManager.getApplicationInfo(TEST_PACKAGE_NAME, PackageManager.GET_META_DATA)) + .thenReturn(mockApplicationInfo); + when(mockMetadata.getString(SPLUNK_UUID_MANIFEST_KEY)).thenReturn(TEST_UUID); + + mockPackageInfo.versionCode = 123; + when(mockPackageManager.getPackageInfo(TEST_PACKAGE_NAME, 0)).thenReturn(mockPackageInfo); + } + + @Test + public void testGetApplicationId() { + ErrorIdentifierExtractor extractor = new ErrorIdentifierExtractor(mockApplication); + assertEquals(TEST_PACKAGE_NAME, extractor.extractInfo().getApplicationId()); + } + + @Test + public void testGetVersionCode() { + ErrorIdentifierExtractor extractor = new ErrorIdentifierExtractor(mockApplication); + assertEquals(TEST_VERSION_CODE, extractor.extractInfo().getVersionCode()); + } + + @Test + public void testGetCustomUUID() { + ErrorIdentifierExtractor extractor = new ErrorIdentifierExtractor(mockApplication); + assertEquals(TEST_UUID, extractor.extractInfo().getCustomUUID()); + } + + @Test + public void testCustomUUIDButDoesNotExist() { + when(mockMetadata.getString(SPLUNK_UUID_MANIFEST_KEY)).thenReturn(null); + ErrorIdentifierExtractor extractor = new ErrorIdentifierExtractor(mockApplication); + assertNull(extractor.extractInfo().getCustomUUID()); + } + + @Test + public void testApplicationInfoMetaDataIsNull() throws PackageManager.NameNotFoundException { + ApplicationInfo applicationInfoWithNullMetaData = new ApplicationInfo(); + applicationInfoWithNullMetaData.packageName = TEST_PACKAGE_NAME; + + when(mockPackageManager.getApplicationInfo(TEST_PACKAGE_NAME, PackageManager.GET_META_DATA)) + .thenReturn(applicationInfoWithNullMetaData); + + ErrorIdentifierExtractor extractor = new ErrorIdentifierExtractor(mockApplication); + assertNull(extractor.extractInfo().getCustomUUID()); + } + + @Test + public void testRetrieveVersionCodeIsNull() throws PackageManager.NameNotFoundException { + when(mockPackageManager.getPackageInfo(TEST_PACKAGE_NAME, 0)) + .thenThrow(new PackageManager.NameNotFoundException()); + + ErrorIdentifierExtractor extractor = new ErrorIdentifierExtractor(mockApplication); + assertNull(extractor.extractInfo().getVersionCode()); + } + + @Test + public void testExtractInfoWhenApplicationInfoIsNull() + throws PackageManager.NameNotFoundException { + when(mockPackageManager.getApplicationInfo(TEST_PACKAGE_NAME, PackageManager.GET_META_DATA)) + .thenThrow(new PackageManager.NameNotFoundException()); + + ErrorIdentifierExtractor extractor = new ErrorIdentifierExtractor(mockApplication); + + ErrorIdentifierInfo info = extractor.extractInfo(); + assertNull(info.getApplicationId()); + assertEquals(TEST_VERSION_CODE, info.getVersionCode()); + assertNull(info.getCustomUUID()); + } +}