Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Enable manual creation of screen view spans
Browse files Browse the repository at this point in the history
jtmalinowski committed Jun 10, 2024

Verified

This commit was signed with the committer’s verified signature.
davidfischer David Fischer
1 parent 1bc3c4c commit c6dfded
Showing 10 changed files with 156 additions and 9 deletions.
7 changes: 6 additions & 1 deletion sample-app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -21,10 +21,15 @@
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".JetpackComposeActivity"
android:theme="@style/Theme.SplunkRUMSampleApp.NoActionBar"
android:label="@string/app_name"
android:exported="true">
</activity>
<service
android:name=".SplunkBackgroundService"
android:exported="false"
Original file line number Diff line number Diff line change
@@ -19,6 +19,7 @@
import static io.opentelemetry.api.common.AttributeKey.longKey;

import android.annotation.SuppressLint;
import android.content.Intent;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
@@ -80,6 +81,10 @@ public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
NavHostFragment.findNavController(FirstFragment.this)
.navigate(R.id.action_FirstFragment_to_SecondFragment));

binding.buttonScreenName.setOnClickListener(v -> {
SplunkRum.getInstance().setScreenName("custom-screen");
});

binding.crash.setOnClickListener(v -> multiThreadCrashing());

binding.httpMe.setOnClickListener(
@@ -184,12 +189,22 @@ public LiveData<String> getSessionId() {
public void onDestroyView() {
super.onDestroyView();
binding = null;

SplunkRum.getInstance().setScreenName(null);
}

@Override
public void onResume() {
super.onResume();
sessionId.postValue(splunkRum.getRumSessionId());

SplunkRum.getInstance().setScreenName("custom-resumed-name", "Resumed");
}

@Override
public void onPause() {
super.onPause();
SplunkRum.getInstance().setScreenName(null);
}

@SuppressLint("AllowAllHostnameVerifier")
6 changes: 6 additions & 0 deletions sample-app/src/main/res/layout/fragment_first.xml
Original file line number Diff line number Diff line change
@@ -42,6 +42,12 @@
android:layout_height="wrap_content"
android:text="@string/next" />

<Button
android:id="@+id/button_screen_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/screen_name" />

<Button
android:id="@+id/crash"
android:layout_width="wrap_content"
1 change: 1 addition & 0 deletions sample-app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@
<string name="second_fragment_label">Second Fragment</string>
<string name="next">Next</string>
<string name="previous">Previous</string>
<string name="screen_name">Custom screen name</string>

<string name="hello_first_fragment">Hello first fragment</string>
<string name="hello_second_fragment">Hello second fragment. Arg: %1$s</string>
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.splunk.rum;

import androidx.annotation.Nullable;

import java.util.concurrent.atomic.AtomicReference;

import io.opentelemetry.android.instrumentation.activity.VisibleScreenTracker;

public class ExplicitVisibleScreenNameTracker extends VisibleScreenTracker {
private final AtomicReference<String> lastScreenName = new AtomicReference<>();
private final AtomicReference<String> previouslyLastScreenName = new AtomicReference<>();

public void setExplicitScreenName(String screenName) {
this.previouslyLastScreenName.set(this.lastScreenName.get());
this.lastScreenName.set(screenName);
}

@Nullable
@Override
public String getPreviouslyVisibleScreen() {
String screenName = previouslyLastScreenName.get();
if (screenName != null) {
return screenName;
}

return super.getPreviouslyVisibleScreen();
}

@Override
public String getCurrentlyVisibleScreen() {
String screenName = lastScreenName.get();
if (screenName != null) {
return screenName;
}

return super.getCurrentlyVisibleScreen();
}

}
Original file line number Diff line number Diff line change
@@ -32,7 +32,7 @@ class NoOpSplunkRum extends SplunkRum {
// passing null values here is fine, they'll never get used anyway
@SuppressWarnings("NullAway")
private NoOpSplunkRum() {
super(OpenTelemetryRum.noop(), null);
super(OpenTelemetryRum.noop(), null, null);
}

@Override
@@ -79,4 +79,9 @@ public void integrateWithBrowserRum(WebView webView) {
void flushSpans() {
// no-op
}

@Override
public void setScreenName(String screenName) {
// no-op
}
}
Original file line number Diff line number Diff line change
@@ -36,6 +36,7 @@
import io.opentelemetry.android.OpenTelemetryRum;
import io.opentelemetry.android.OpenTelemetryRumBuilder;
import io.opentelemetry.android.RuntimeDetailsExtractor;
import io.opentelemetry.android.ScreenAttributesSpanProcessor;
import io.opentelemetry.android.config.OtelRumConfig;
import io.opentelemetry.android.instrumentation.activity.VisibleScreenTracker;
import io.opentelemetry.android.instrumentation.anr.AnrDetector;
@@ -52,6 +53,7 @@
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.resources.ResourceBuilder;
import io.opentelemetry.sdk.trace.SpanLimits;
import io.opentelemetry.sdk.trace.SpanProcessor;
import io.opentelemetry.sdk.trace.data.SpanData;
import io.opentelemetry.sdk.trace.export.BatchSpanProcessor;
import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor;
@@ -86,7 +88,7 @@ class RumInitializer {
}

SplunkRum initialize(Looper mainLooper) {
VisibleScreenTracker visibleScreenTracker = new VisibleScreenTracker();
ExplicitVisibleScreenNameTracker visibleScreenTracker = new ExplicitVisibleScreenNameTracker();

initializationEvents.begin();

@@ -98,6 +100,7 @@ SplunkRum initialize(Looper mainLooper) {
config.disableNetworkChangeMonitoring();
}

config.disableScreenAttributes();
OpenTelemetryRumBuilder otelRumBuilder = OpenTelemetryRum.builder(application, config);

otelRumBuilder.mergeResource(createSplunkResource());
@@ -193,6 +196,13 @@ SplunkRum initialize(Looper mainLooper) {
installCrashReporter(otelRumBuilder);
}

otelRumBuilder.addTracerProviderCustomizer(
(tracerProviderBuilder, app) -> {
SpanProcessor screenAttributesAppender =
new ScreenAttributesSpanProcessor(visibleScreenTracker);
return tracerProviderBuilder.addSpanProcessor(screenAttributesAppender);
});

// Lifecycle events instrumentation are always installed.
installLifecycleInstrumentations(otelRumBuilder, visibleScreenTracker);

@@ -204,7 +214,7 @@ SplunkRum initialize(Looper mainLooper) {
builder.getConfigFlags(),
openTelemetryRum.getOpenTelemetry().getTracer(RUM_TRACER_NAME));

return new SplunkRum(openTelemetryRum, globalAttributeSupplier);
return new SplunkRum(openTelemetryRum, globalAttributeSupplier, visibleScreenTracker);
}

@NonNull
25 changes: 24 additions & 1 deletion splunk-otel-android/src/main/java/com/splunk/rum/SplunkRum.java
Original file line number Diff line number Diff line change
@@ -16,6 +16,8 @@

package com.splunk.rum;

import static io.opentelemetry.android.RumConstants.LAST_SCREEN_NAME_KEY;
import static io.opentelemetry.android.RumConstants.SCREEN_NAME_KEY;
import static io.opentelemetry.api.common.AttributeKey.doubleKey;
import static io.opentelemetry.api.common.AttributeKey.stringKey;

@@ -28,6 +30,7 @@
import androidx.annotation.Nullable;
import com.splunk.rum.internal.GlobalAttributesSupplier;
import io.opentelemetry.android.OpenTelemetryRum;
import io.opentelemetry.android.instrumentation.activity.VisibleScreenTracker;
import io.opentelemetry.android.instrumentation.startup.AppStartupTimer;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.common.AttributeKey;
@@ -72,15 +75,17 @@ public class SplunkRum {

private final OpenTelemetryRum openTelemetryRum;
private final GlobalAttributesSupplier globalAttributes;
private final ExplicitVisibleScreenNameTracker visibleScreenTracker;

static {
Handler handler = new Handler(Looper.getMainLooper());
startupTimer.detectBackgroundStart(handler);
}

SplunkRum(OpenTelemetryRum openTelemetryRum, GlobalAttributesSupplier globalAttributes) {
SplunkRum(OpenTelemetryRum openTelemetryRum, GlobalAttributesSupplier globalAttributes, ExplicitVisibleScreenNameTracker visibleScreenTracker) {
this.openTelemetryRum = openTelemetryRum;
this.globalAttributes = globalAttributes;
this.visibleScreenTracker = visibleScreenTracker;
}

/** Creates a new {@link SplunkRumBuilder}, used to set up a {@link SplunkRum} instance. */
@@ -113,6 +118,24 @@ static SplunkRum initialize(SplunkRumBuilder builder, Application application) {
return INSTANCE;
}

public void setScreenName(String screenName, String spanType) {
visibleScreenTracker.setExplicitScreenName(screenName);

if (screenName != null) {
Span span = getTracer()
.spanBuilder(spanType)
.setAttribute(COMPONENT_KEY, "ui")
.startSpan();
span.setAttribute(SCREEN_NAME_KEY, screenName);
span.setAttribute(LAST_SCREEN_NAME_KEY, visibleScreenTracker.getPreviouslyVisibleScreen());
span.end();
}
}

public void setScreenName(String screenName) {
setScreenName(screenName, "Created");
}

/** Returns {@code true} if the Splunk RUM library has been successfully initialized. */
public static boolean isInitialized() {
return INSTANCE != null;
Original file line number Diff line number Diff line change
@@ -50,5 +50,8 @@ void doesNotThrow() {

Location location = mock(Location.class);
instance.updateLocation(location);

instance.setScreenName("explicit-screen-name");
instance.flushSpans();
}
}
Original file line number Diff line number Diff line change
@@ -17,11 +17,14 @@
package com.splunk.rum;

import static com.splunk.rum.SplunkRum.COMPONENT_KEY;
import static io.opentelemetry.android.RumConstants.LAST_SCREEN_NAME_KEY;
import static io.opentelemetry.android.RumConstants.SCREEN_NAME_KEY;
import static io.opentelemetry.api.common.AttributeKey.longKey;
import static io.opentelemetry.api.common.AttributeKey.stringKey;
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.eq;
@@ -70,8 +73,12 @@ public class SplunkRumTest {
@Mock private OpenTelemetryRum openTelemetryRum;
@Mock private GlobalAttributesSupplier globalAttributes;

private ExplicitVisibleScreenNameTracker screenNameTracker;

@BeforeEach
public void setup() {
screenNameTracker = new ExplicitVisibleScreenNameTracker();

tracer = otelTesting.getOpenTelemetry().getTracer("testTracer");
SplunkRum.resetSingletonForTest();
}
@@ -148,7 +155,7 @@ void nonNullMethods() {
void addEvent() {
when(openTelemetryRum.getOpenTelemetry()).thenReturn(otelTesting.getOpenTelemetry());

SplunkRum splunkRum = new SplunkRum(openTelemetryRum, globalAttributes);
SplunkRum splunkRum = new SplunkRum(openTelemetryRum, globalAttributes, screenNameTracker);

Attributes attributes = Attributes.of(stringKey("one"), "1", longKey("two"), 2L);
splunkRum.addRumEvent("foo", attributes);
@@ -166,7 +173,7 @@ void addException() {

when(openTelemetryRum.getOpenTelemetry()).thenReturn(testSdk);

SplunkRum splunkRum = new SplunkRum(openTelemetryRum, globalAttributes);
SplunkRum splunkRum = new SplunkRum(openTelemetryRum, globalAttributes, screenNameTracker);

NullPointerException exception = new NullPointerException("oopsie");
Attributes attributes = Attributes.of(stringKey("one"), "1", longKey("two"), 2L);
@@ -197,7 +204,7 @@ private OpenTelemetrySdk buildTestSdk(InMemorySpanExporter testExporter) {
void createAndEnd() {
when(openTelemetryRum.getOpenTelemetry()).thenReturn(otelTesting.getOpenTelemetry());

SplunkRum splunkRum = new SplunkRum(openTelemetryRum, globalAttributes);
SplunkRum splunkRum = new SplunkRum(openTelemetryRum, globalAttributes, screenNameTracker);

Span span = splunkRum.startWorkflow("workflow");
Span inner = tracer.spanBuilder("foo").startSpan();
@@ -255,7 +262,7 @@ void updateLocation() {
.when(globalAttributes)
.update(isA(Consumer.class));

SplunkRum splunkRum = new SplunkRum(openTelemetryRum, globalAttributes);
SplunkRum splunkRum = new SplunkRum(openTelemetryRum, globalAttributes, screenNameTracker);

Location location = mock(Location.class);
when(location.getLatitude()).thenReturn(42d);
@@ -274,4 +281,37 @@ void updateLocation() {

assertTrue(updatedAttributes.get().isEmpty());
}

@Test
void canSetScreenName() {
when(openTelemetryRum.getOpenTelemetry()).thenReturn(otelTesting.getOpenTelemetry());

SplunkRum splunkRum = new SplunkRum(openTelemetryRum, globalAttributes, screenNameTracker);
splunkRum.setScreenName("screen-1");
splunkRum.setScreenName("screen-2");

// pause and resume
splunkRum.setScreenName(null);
splunkRum.setScreenName("screen-2", "Resumed");

// exit the view with explicit screen names
// both last screen name and second last screen name have to be cleared, hence the doubled call
splunkRum.setScreenName(null);
splunkRum.setScreenName(null);

List<SpanData> spans = otelTesting.getSpans();
assertEquals(3, spans.size());

assertEquals("Created", spans.get(0).getName());
assertEquals("screen-1", spans.get(0).getAttributes().get(SCREEN_NAME_KEY));
assertNull(spans.get(0).getAttributes().get(LAST_SCREEN_NAME_KEY));

assertEquals("Created", spans.get(1).getName());
assertEquals("screen-2", spans.get(1).getAttributes().get(SCREEN_NAME_KEY));
assertEquals("screen-1", spans.get(1).getAttributes().get(LAST_SCREEN_NAME_KEY));

assertEquals("Resumed", spans.get(2).getName());
assertEquals("screen-2", spans.get(2).getAttributes().get(SCREEN_NAME_KEY));
assertNull(spans.get(2).getAttributes().get(LAST_SCREEN_NAME_KEY));
}
}

0 comments on commit c6dfded

Please sign in to comment.