Skip to content

Commit

Permalink
Migrate the bulk of resource building to otel package. (#524)
Browse files Browse the repository at this point in the history
* migrate the bulk of resource building to otel package.

* guard against nulls and fix tests

* add tests

* spotless

* javadoc
  • Loading branch information
breedx-splk authored Apr 20, 2023
1 parent cfcd0f3 commit 7505d19
Show file tree
Hide file tree
Showing 9 changed files with 231 additions and 40 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 =
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)));

Expand Down
Original file line number Diff line number Diff line change
@@ -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<String> fn, String defaultValue) {
try {
return fn.get();
} catch (Exception e) {
return defaultValue;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

package io.opentelemetry.rum.internal;

import android.app.Application;

import io.opentelemetry.api.OpenTelemetry;

/**
Expand All @@ -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}. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
public final class OpenTelemetryRumBuilder {

private final SessionId sessionId;
private Resource resource = Resource.getDefault();
private final Application application;
private final List<BiFunction<SdkTracerProviderBuilder, Application, SdkTracerProviderBuilder>>
tracerProviderCustomizers = new ArrayList<>();
private final List<BiFunction<SdkMeterProviderBuilder, Application, SdkMeterProviderBuilder>>
Expand All @@ -53,15 +53,18 @@ public final class OpenTelemetryRumBuilder {
loggerProviderCustomizers = new ArrayList<>();
private final List<Consumer<InstrumentedApplication>> 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}
*/
Expand All @@ -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
Expand Down Expand Up @@ -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}
*/
Expand All @@ -152,10 +167,9 @@ public SessionId getSessionId() {
* <p>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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ public class RumConstants {
AttributeKey.stringKey("screen.name");
public static final AttributeKey<String> START_TYPE_KEY = stringKey("start.type");

public static final AttributeKey<String> RUM_SDK_VERSION = stringKey("rum.sdk.version");

public static final AttributeKey<String> PREVIOUS_SESSION_ID_KEY =
stringKey("rum.session.previous_id");

Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
Loading

0 comments on commit 7505d19

Please sign in to comment.