From 7505d199fc03267bb2ad6921782bd7edede37cf9 Mon Sep 17 00:00:00 2001 From: jason plumb <75337021+breedx-splk@users.noreply.github.com> Date: Thu, 20 Apr 2023 15:40:22 -0700 Subject: [PATCH] Migrate the bulk of resource building to otel package. (#524) * migrate the bulk of resource building to otel package. * guard against nulls and fix tests * add tests * spotless * javadoc --- CHANGELOG.md | 2 +- .../java/com/splunk/rum/RumInitializer.java | 33 ++---- .../splunk/rum/SplunkSpanDataModifier.java | 1 + .../rum/internal/AndroidResource.java | 84 +++++++++++++++ .../rum/internal/OpenTelemetryRum.java | 12 ++- .../rum/internal/OpenTelemetryRumBuilder.java | 26 +++-- .../rum/internal/RumConstants.java | 2 + .../rum/internal/AndroidResourceTest.java | 101 ++++++++++++++++++ .../internal/OpenTelemetryRumBuilderTest.java | 10 +- 9 files changed, 231 insertions(+), 40 deletions(-) create mode 100644 splunk-otel-android/src/main/java/io/opentelemetry/rum/internal/AndroidResource.java create mode 100644 splunk-otel-android/src/test/java/io/opentelemetry/rum/internal/AndroidResourceTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f798e36c..ce82795bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## Unreleased -* TBD +* `splunk.rum.version` attribute has been renamed to `rum.sdk.version` ## Version 1.0.0 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 37836585f..e3faf7e99 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 @@ -22,22 +22,15 @@ 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.RUM_VERSION_KEY; import static io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor.constant; import static io.opentelemetry.rum.internal.RumConstants.APP_START_SPAN_NAME; +import static io.opentelemetry.rum.internal.RumConstants.RUM_SDK_VERSION; import static io.opentelemetry.semconv.resource.attributes.ResourceAttributes.DEPLOYMENT_ENVIRONMENT; -import static io.opentelemetry.semconv.resource.attributes.ResourceAttributes.DEVICE_MODEL_IDENTIFIER; -import static io.opentelemetry.semconv.resource.attributes.ResourceAttributes.DEVICE_MODEL_NAME; -import static io.opentelemetry.semconv.resource.attributes.ResourceAttributes.OS_NAME; -import static io.opentelemetry.semconv.resource.attributes.ResourceAttributes.OS_TYPE; -import static io.opentelemetry.semconv.resource.attributes.ResourceAttributes.OS_VERSION; -import static io.opentelemetry.semconv.resource.attributes.ResourceAttributes.SERVICE_NAME; import static java.util.Objects.requireNonNull; import android.app.Application; -import android.os.Build; import android.os.Looper; import androidx.annotation.NonNull; @@ -106,9 +99,9 @@ SplunkRum initialize( VisibleScreenTracker visibleScreenTracker = new VisibleScreenTracker(); initializationEvents.begin(); - OpenTelemetryRumBuilder otelRumBuilder = OpenTelemetryRum.builder(); + OpenTelemetryRumBuilder otelRumBuilder = OpenTelemetryRum.builder(application); - otelRumBuilder.setResource(createResource()); + otelRumBuilder.mergeResource(createSplunkResource()); initializationEvents.emit("resourceInitialized"); CurrentNetworkProvider currentNetworkProvider = @@ -211,7 +204,7 @@ SplunkRum initialize( // Lifecycle events instrumentation are always installed. installLifecycleInstrumentations(otelRumBuilder, visibleScreenTracker); - OpenTelemetryRum openTelemetryRum = otelRumBuilder.build(application); + OpenTelemetryRum openTelemetryRum = otelRumBuilder.build(); initializationEvents.recordInitializationSpans( builder.getConfigFlags(), @@ -248,30 +241,20 @@ private void installLifecycleInstrumentations( }); } - private Resource createResource() { + private Resource createSplunkResource() { // applicationName can't be null at this stage String applicationName = requireNonNull(builder.applicationName); ResourceBuilder resourceBuilder = - Resource.getDefault().toBuilder() - .put(APP_NAME_KEY, applicationName) - .put(SERVICE_NAME, applicationName); + Resource.getDefault().toBuilder().put(APP_NAME_KEY, applicationName); if (builder.deploymentEnvironment != null) { resourceBuilder.put(DEPLOYMENT_ENVIRONMENT, builder.deploymentEnvironment); } - return resourceBuilder - .put(RUM_VERSION_KEY, detectRumVersion()) - .put(DEVICE_MODEL_NAME, Build.MODEL) - .put(DEVICE_MODEL_IDENTIFIER, Build.MODEL) - .put(OS_NAME, "Android") - .put(OS_TYPE, "linux") - .put(OS_VERSION, Build.VERSION.RELEASE) - .build(); + return resourceBuilder.put(RUM_SDK_VERSION, detectRumVersion()).build(); } + // TODO: Remove this method that is duplicated from upstream AndroidResource private String detectRumVersion() { try { - // todo: figure out if there's a way to get access to resources from pure non-UI library - // code. return application .getApplicationContext() .getResources() diff --git a/splunk-otel-android/src/main/java/com/splunk/rum/SplunkSpanDataModifier.java b/splunk-otel-android/src/main/java/com/splunk/rum/SplunkSpanDataModifier.java index d6fb6411e..7aaa78504 100644 --- a/splunk-otel-android/src/main/java/com/splunk/rum/SplunkSpanDataModifier.java +++ b/splunk-otel-android/src/main/java/com/splunk/rum/SplunkSpanDataModifier.java @@ -65,6 +65,7 @@ final class SplunkSpanDataModifier implements SpanExporter { ResourceAttributes.OS_NAME, ResourceAttributes.OS_TYPE, ResourceAttributes.OS_VERSION, + RumConstants.RUM_SDK_VERSION, SplunkRum.APP_NAME_KEY, SplunkRum.RUM_VERSION_KEY))); diff --git a/splunk-otel-android/src/main/java/io/opentelemetry/rum/internal/AndroidResource.java b/splunk-otel-android/src/main/java/io/opentelemetry/rum/internal/AndroidResource.java new file mode 100644 index 000000000..03efc6a1d --- /dev/null +++ b/splunk-otel-android/src/main/java/io/opentelemetry/rum/internal/AndroidResource.java @@ -0,0 +1,84 @@ +/* + * 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 io.opentelemetry.rum.internal; + +import static io.opentelemetry.rum.internal.RumConstants.RUM_SDK_VERSION; +import static io.opentelemetry.semconv.resource.attributes.ResourceAttributes.DEVICE_MODEL_IDENTIFIER; +import static io.opentelemetry.semconv.resource.attributes.ResourceAttributes.DEVICE_MODEL_NAME; +import static io.opentelemetry.semconv.resource.attributes.ResourceAttributes.OS_NAME; +import static io.opentelemetry.semconv.resource.attributes.ResourceAttributes.OS_TYPE; +import static io.opentelemetry.semconv.resource.attributes.ResourceAttributes.OS_VERSION; +import static io.opentelemetry.semconv.resource.attributes.ResourceAttributes.SERVICE_NAME; + +import android.app.Application; +import android.os.Build; + +import com.splunk.android.rum.R; + +import io.opentelemetry.sdk.resources.Resource; +import io.opentelemetry.sdk.resources.ResourceBuilder; + +import java.util.function.Supplier; + +final class AndroidResource { + + static Resource createDefault(Application application) { + String appName = readAppName(application); + ResourceBuilder resourceBuilder = + Resource.getDefault().toBuilder().put(SERVICE_NAME, appName); + + return resourceBuilder + .put(RUM_SDK_VERSION, detectRumVersion(application)) + .put(DEVICE_MODEL_NAME, Build.MODEL) + .put(DEVICE_MODEL_IDENTIFIER, Build.MODEL) + .put(OS_NAME, "Android") + .put(OS_TYPE, "linux") + .put(OS_VERSION, Build.VERSION.RELEASE) + .build(); + } + + private static String readAppName(Application application) { + return trapTo( + () -> { + int stringId = + application.getApplicationContext().getApplicationInfo().labelRes; + return application.getApplicationContext().getString(stringId); + }, + "unknown_service:android"); + } + + private static String detectRumVersion(Application application) { + return trapTo( + () -> { + // TODO: Verify that this will be in the lib/jar at runtime. + // TODO: After donation, package of R file will change + return application + .getApplicationContext() + .getResources() + .getString(R.string.rum_version); + }, + "unknown"); + } + + private static String trapTo(Supplier fn, String defaultValue) { + try { + return fn.get(); + } catch (Exception e) { + return defaultValue; + } + } +} diff --git a/splunk-otel-android/src/main/java/io/opentelemetry/rum/internal/OpenTelemetryRum.java b/splunk-otel-android/src/main/java/io/opentelemetry/rum/internal/OpenTelemetryRum.java index 13ca7a902..f3b7d9eaa 100644 --- a/splunk-otel-android/src/main/java/io/opentelemetry/rum/internal/OpenTelemetryRum.java +++ b/splunk-otel-android/src/main/java/io/opentelemetry/rum/internal/OpenTelemetryRum.java @@ -16,6 +16,8 @@ package io.opentelemetry.rum.internal; +import android.app.Application; + import io.opentelemetry.api.OpenTelemetry; /** @@ -26,9 +28,13 @@ */ public interface OpenTelemetryRum { - /** Returns a new {@link OpenTelemetryRumBuilder} for {@link OpenTelemetryRum}. */ - static OpenTelemetryRumBuilder builder() { - return new OpenTelemetryRumBuilder(); + /** + * Returns a new {@link OpenTelemetryRumBuilder} for {@link OpenTelemetryRum}. + * + * @param application The {@link Application} that is being instrumented. + */ + static OpenTelemetryRumBuilder builder(Application application) { + return new OpenTelemetryRumBuilder(application); } /** Returns a no-op implementation of {@link OpenTelemetryRum}. */ diff --git a/splunk-otel-android/src/main/java/io/opentelemetry/rum/internal/OpenTelemetryRumBuilder.java b/splunk-otel-android/src/main/java/io/opentelemetry/rum/internal/OpenTelemetryRumBuilder.java index 0430d7533..013dae28c 100644 --- a/splunk-otel-android/src/main/java/io/opentelemetry/rum/internal/OpenTelemetryRumBuilder.java +++ b/splunk-otel-android/src/main/java/io/opentelemetry/rum/internal/OpenTelemetryRumBuilder.java @@ -44,7 +44,7 @@ public final class OpenTelemetryRumBuilder { private final SessionId sessionId; - private Resource resource = Resource.getDefault(); + private final Application application; private final List> tracerProviderCustomizers = new ArrayList<>(); private final List> @@ -53,15 +53,18 @@ public final class OpenTelemetryRumBuilder { loggerProviderCustomizers = new ArrayList<>(); private final List> instrumentationInstallers = new ArrayList<>(); + private Resource resource; - OpenTelemetryRumBuilder() { + OpenTelemetryRumBuilder(Application application) { SessionIdTimeoutHandler timeoutHandler = new SessionIdTimeoutHandler(); + this.application = application; this.sessionId = new SessionId(timeoutHandler); + this.resource = AndroidResource.createDefault(application); } /** * Assign a {@link Resource} to be attached to all telemetry emitted by the {@link - * OpenTelemetryRum} created by this builder. + * OpenTelemetryRum} created by this builder. This replaces any existing resource. * * @return {@code this} */ @@ -70,6 +73,18 @@ public OpenTelemetryRumBuilder setResource(Resource resource) { return this; } + /** + * Merges a new {@link Resource} with any existing {@link Resource} in this builder. The + * resulting {@link Resource} will be attached to all telemetry emitted by the {@link + * OpenTelemetryRum} created by this builder. + * + * @return {@code this} + */ + public OpenTelemetryRumBuilder mergeResource(Resource resource) { + this.resource = this.resource.merge(resource); + return this; + } + /** * Adds a {@link BiFunction} to invoke the with the {@link SdkTracerProviderBuilder} to allow * customization. The return value of the {@link BiFunction} will replace the passed-in @@ -131,7 +146,7 @@ public OpenTelemetryRumBuilder addLoggerProviderCustomizer( /** * Adds an instrumentation installer function that will be run on an {@link - * InstrumentedApplication} instance as a part of the {@link #build(Application)} method call. + * InstrumentedApplication} instance as a part of the {@link #build()} method call. * * @return {@code this} */ @@ -152,10 +167,9 @@ public SessionId getSessionId() { *

This method will initialize the OpenTelemetry SDK and install built-in system * instrumentations in the passed Android {@link Application}. * - * @param application The {@link Application} that is being instrumented. * @return A new {@link OpenTelemetryRum} instance. */ - public OpenTelemetryRum build(Application application) { + public OpenTelemetryRum build() { // the app state listeners need to be run in the first ActivityLifecycleCallbacks since they // might turn off/on additional telemetry depending on whether the app is active or not ApplicationStateWatcher applicationStateWatcher = new ApplicationStateWatcher(); diff --git a/splunk-otel-android/src/main/java/io/opentelemetry/rum/internal/RumConstants.java b/splunk-otel-android/src/main/java/io/opentelemetry/rum/internal/RumConstants.java index 5c0e5260f..f2305e53f 100644 --- a/splunk-otel-android/src/main/java/io/opentelemetry/rum/internal/RumConstants.java +++ b/splunk-otel-android/src/main/java/io/opentelemetry/rum/internal/RumConstants.java @@ -32,6 +32,8 @@ public class RumConstants { AttributeKey.stringKey("screen.name"); public static final AttributeKey START_TYPE_KEY = stringKey("start.type"); + public static final AttributeKey RUM_SDK_VERSION = stringKey("rum.sdk.version"); + public static final AttributeKey PREVIOUS_SESSION_ID_KEY = stringKey("rum.session.previous_id"); diff --git a/splunk-otel-android/src/test/java/io/opentelemetry/rum/internal/AndroidResourceTest.java b/splunk-otel-android/src/test/java/io/opentelemetry/rum/internal/AndroidResourceTest.java new file mode 100644 index 000000000..bdf2ea70d --- /dev/null +++ b/splunk-otel-android/src/test/java/io/opentelemetry/rum/internal/AndroidResourceTest.java @@ -0,0 +1,101 @@ +/* + * 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 io.opentelemetry.rum.internal; + +import static io.opentelemetry.rum.internal.RumConstants.RUM_SDK_VERSION; +import static io.opentelemetry.semconv.resource.attributes.ResourceAttributes.DEVICE_MODEL_IDENTIFIER; +import static io.opentelemetry.semconv.resource.attributes.ResourceAttributes.DEVICE_MODEL_NAME; +import static io.opentelemetry.semconv.resource.attributes.ResourceAttributes.OS_NAME; +import static io.opentelemetry.semconv.resource.attributes.ResourceAttributes.OS_TYPE; +import static io.opentelemetry.semconv.resource.attributes.ResourceAttributes.OS_VERSION; +import static io.opentelemetry.semconv.resource.attributes.ResourceAttributes.SERVICE_NAME; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.when; + +import android.app.Application; +import android.content.pm.ApplicationInfo; +import android.os.Build; + +import com.splunk.android.rum.R; + +import io.opentelemetry.sdk.resources.Resource; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Answers; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class AndroidResourceTest { + + String appName = "robotron"; + String rumSdkVersion = "1.2.3"; + + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + Application app; + + @Test + void testFullResource() { + ApplicationInfo appInfo = new ApplicationInfo(); + appInfo.labelRes = 12345; + when(app.getApplicationContext().getApplicationInfo()).thenReturn(appInfo); + when(app.getApplicationContext().getString(appInfo.labelRes)).thenReturn(appName); + when(app.getApplicationContext().getResources().getString(R.string.rum_version)) + .thenReturn(rumSdkVersion); + + Resource expected = + Resource.getDefault() + .merge( + Resource.builder() + .put(SERVICE_NAME, appName) + .put(RUM_SDK_VERSION, rumSdkVersion) + .put(DEVICE_MODEL_NAME, Build.MODEL) + .put(DEVICE_MODEL_IDENTIFIER, Build.MODEL) + .put(OS_NAME, "Android") + .put(OS_TYPE, "linux") + .put(OS_VERSION, Build.VERSION.RELEASE) + .build()); + + Resource result = AndroidResource.createDefault(app); + assertEquals(expected, result); + } + + @Test + void testProblematicContext() { + when(app.getApplicationContext().getApplicationInfo()) + .thenThrow(new SecurityException("cannot do that")); + when(app.getApplicationContext().getResources()).thenThrow(new SecurityException("boom")); + + Resource expected = + Resource.getDefault() + .merge( + Resource.builder() + .put(SERVICE_NAME, "unknown_service:android") + .put(RUM_SDK_VERSION, "unknown") + .put(DEVICE_MODEL_NAME, Build.MODEL) + .put(DEVICE_MODEL_IDENTIFIER, Build.MODEL) + .put(OS_NAME, "Android") + .put(OS_TYPE, "linux") + .put(OS_VERSION, Build.VERSION.RELEASE) + .build()); + + Resource result = AndroidResource.createDefault(app); + assertEquals(expected, result); + } +} diff --git a/splunk-otel-android/src/test/java/io/opentelemetry/rum/internal/OpenTelemetryRumBuilderTest.java b/splunk-otel-android/src/test/java/io/opentelemetry/rum/internal/OpenTelemetryRumBuilderTest.java index 1d1477d8d..0369eb543 100644 --- a/splunk-otel-android/src/test/java/io/opentelemetry/rum/internal/OpenTelemetryRumBuilderTest.java +++ b/splunk-otel-android/src/test/java/io/opentelemetry/rum/internal/OpenTelemetryRumBuilderTest.java @@ -56,7 +56,7 @@ class OpenTelemetryRumBuilderTest { @Test void shouldRegisterApplicationStateWatcher() { - OpenTelemetryRum.builder().build(application); + OpenTelemetryRum.builder(application).build(); verify(application).registerActivityLifecycleCallbacks(isA(ApplicationStateWatcher.class)); } @@ -64,13 +64,13 @@ void shouldRegisterApplicationStateWatcher() { @Test void shouldBuildTracerProvider() { OpenTelemetryRum openTelemetryRum = - OpenTelemetryRum.builder() + OpenTelemetryRum.builder(application) .setResource(resource) .addTracerProviderCustomizer( (tracerProviderBuilder, app) -> tracerProviderBuilder.addSpanProcessor( SimpleSpanProcessor.create(spanExporter))) - .build(application); + .build(); String sessionId = openTelemetryRum.getRumSessionId(); openTelemetryRum @@ -90,14 +90,14 @@ void shouldBuildTracerProvider() { @Test void shouldInstallInstrumentation() { - OpenTelemetryRum.builder() + OpenTelemetryRum.builder(application) .addInstrumentation( instrumentedApplication -> { assertThat(instrumentedApplication.getApplication()) .isSameAs(application); instrumentedApplication.registerApplicationStateListener(listener); }) - .build(application); + .build(); verify(application).registerActivityLifecycleCallbacks(activityCallbacksCaptor.capture());