From b3e540e92cb4f3f6635f612ced99e4b90377d84f Mon Sep 17 00:00:00 2001 From: Mateusz Rzeszutek Date: Wed, 12 Oct 2022 09:51:59 +0200 Subject: [PATCH] Decouple global span attributes from RumAttributeAppender (#375) * Decouple global span attributes from RumAttributeAppender * Moved OTel stuff to io.opentelemetry.rum.internal --- .../com/splunk/rum/RumAttributeAppender.java | 6 -- .../java/com/splunk/rum/RumInitializer.java | 21 ++-- .../main/java/com/splunk/rum/SplunkRum.java | 24 +---- .../java/com/splunk/rum/SplunkRumBuilder.java | 5 +- .../GlobalAttributesSpanAppender.java | 96 +++++++++++++++++++ .../com/splunk/rum/NoOpSplunkRumTest.java | 2 +- .../splunk/rum/RumAttributeAppenderTest.java | 22 +---- .../com/splunk/rum/SplunkRumBuilderTest.java | 4 +- .../java/com/splunk/rum/SplunkRumTest.java | 49 +++++----- .../GlobalAttributesSpanAppenderTest.java | 56 +++++++++++ 10 files changed, 204 insertions(+), 81 deletions(-) create mode 100644 splunk-otel-android/src/main/java/io/opentelemetry/rum/internal/GlobalAttributesSpanAppender.java create mode 100644 splunk-otel-android/src/test/java/io/opentelemetry/rum/internal/GlobalAttributesSpanAppenderTest.java diff --git a/splunk-otel-android/src/main/java/com/splunk/rum/RumAttributeAppender.java b/splunk-otel-android/src/main/java/com/splunk/rum/RumAttributeAppender.java index a2176f240..ee1595838 100644 --- a/splunk-otel-android/src/main/java/com/splunk/rum/RumAttributeAppender.java +++ b/splunk-otel-android/src/main/java/com/splunk/rum/RumAttributeAppender.java @@ -32,13 +32,11 @@ import android.os.Build; import androidx.annotation.Nullable; import io.opentelemetry.api.common.AttributeKey; -import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.trace.Span; import io.opentelemetry.context.Context; import io.opentelemetry.sdk.trace.ReadWriteSpan; import io.opentelemetry.sdk.trace.ReadableSpan; import io.opentelemetry.sdk.trace.SpanProcessor; -import java.util.function.Supplier; class RumAttributeAppender implements SpanProcessor { static final AttributeKey APP_NAME_KEY = stringKey("app"); @@ -48,7 +46,6 @@ class RumAttributeAppender implements SpanProcessor { static final AttributeKey SPLUNK_OPERATION_KEY = stringKey("_splunk_operation"); private final String applicationName; - private final Supplier globalAttributesSupplier; private final SessionId sessionId; private final String rumVersion; private final VisibleScreenTracker visibleScreenTracker; @@ -56,13 +53,11 @@ class RumAttributeAppender implements SpanProcessor { RumAttributeAppender( String applicationName, - Supplier globalAttributesSupplier, SessionId sessionId, String rumVersion, VisibleScreenTracker visibleScreenTracker, ConnectionUtil connectionUtil) { this.applicationName = applicationName; - this.globalAttributesSupplier = globalAttributesSupplier; this.sessionId = sessionId; this.rumVersion = rumVersion; this.visibleScreenTracker = visibleScreenTracker; @@ -84,7 +79,6 @@ public void onStart(Context parentContext, ReadWriteSpan span) { span.setAttribute(OS_NAME, "Android"); span.setAttribute(OS_TYPE, "linux"); span.setAttribute(OS_VERSION, Build.VERSION.RELEASE); - span.setAllAttributes(globalAttributesSupplier.get()); String currentScreen = visibleScreenTracker.getCurrentlyVisibleScreen(); span.setAttribute(SplunkRum.SCREEN_NAME_KEY, currentScreen); 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 cd8dfe4e2..a7ff4c927 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 @@ -27,12 +27,12 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.splunk.android.rum.R; -import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.context.Context; import io.opentelemetry.exporter.logging.LoggingSpanExporter; import io.opentelemetry.exporter.zipkin.ZipkinSpanExporter; +import io.opentelemetry.rum.internal.GlobalAttributesSpanAppender; import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.common.Clock; import io.opentelemetry.sdk.common.CompletableResultCode; @@ -40,6 +40,7 @@ import io.opentelemetry.sdk.trace.SdkTracerProvider; import io.opentelemetry.sdk.trace.SdkTracerProviderBuilder; 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; @@ -53,7 +54,6 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; import java.util.logging.Level; import zipkin2.reporter.Sender; @@ -66,7 +66,6 @@ class RumInitializer { static final int MAX_ATTRIBUTE_LENGTH = 256 * 128; private final SplunkRumBuilder builder; - private final AtomicReference globalAttributes; private final Application application; private final AppStartupTimer startupTimer; private final List initializationEvents = new ArrayList<>(); @@ -75,7 +74,6 @@ class RumInitializer { RumInitializer( SplunkRumBuilder builder, Application application, AppStartupTimer startupTimer) { this.builder = builder; - this.globalAttributes = builder.buildGlobalAttributesRef(); this.application = application; this.startupTimer = startupTimer; this.timingClock = startupTimer.startupClock; @@ -102,6 +100,8 @@ SplunkRum initialize(ConnectionUtil.Factory connectionUtilFactory, Looper mainLo initializationEvents.add( new RumInitializer.InitializationEvent("sessionIdInitialized", timingClock.now())); + GlobalAttributesSpanAppender globalAttributesSpanAppender = + GlobalAttributesSpanAppender.create(builder.buildInitialGlobalAttributes()); SdkTracerProvider sdkTracerProvider = buildTracerProvider( Clock.getDefault(), @@ -109,7 +109,8 @@ SplunkRum initialize(ConnectionUtil.Factory connectionUtilFactory, Looper mainLo sessionId, rumVersion, visibleScreenTracker, - connectionUtil); + connectionUtil, + globalAttributesSpanAppender); initializationEvents.add( new RumInitializer.InitializationEvent( "tracerProviderInitialized", timingClock.now())); @@ -173,7 +174,7 @@ SplunkRum initialize(ConnectionUtil.Factory connectionUtilFactory, Looper mainLo recordInitializationSpans(startTimeNanos, initializationEvents, tracer); - return new SplunkRum(openTelemetrySdk, sessionId, globalAttributes); + return new SplunkRum(openTelemetrySdk, sessionId, globalAttributesSpanAppender); } private SlowRenderingDetector buildSlowRenderingDetector(Tracer tracer) { @@ -277,7 +278,8 @@ private SdkTracerProvider buildTracerProvider( SessionId sessionId, String rumVersion, VisibleScreenTracker visibleScreenTracker, - ConnectionUtil connectionUtil) { + ConnectionUtil connectionUtil, + SpanProcessor... additionalProcessors) { BatchSpanProcessor batchSpanProcessor = BatchSpanProcessor.builder(zipkinExporter).build(); initializationEvents.add( new RumInitializer.InitializationEvent( @@ -287,7 +289,6 @@ private SdkTracerProvider buildTracerProvider( RumAttributeAppender attributeAppender = new RumAttributeAppender( applicationName, - globalAttributes::get, sessionId, rumVersion, visibleScreenTracker, @@ -320,6 +321,10 @@ private SdkTracerProvider buildTracerProvider( new SessionIdRatioBasedSampler(builder.sessionBasedSamplerRatio, sessionId)); } + for (SpanProcessor spanProcessor : additionalProcessors) { + tracerProviderBuilder.addSpanProcessor(spanProcessor); + } + if (builder.debugEnabled) { tracerProviderBuilder.addSpanProcessor( SimpleSpanProcessor.create( 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 4da90ac36..376ebdf8f 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 @@ -19,7 +19,6 @@ import static io.opentelemetry.api.common.AttributeKey.doubleKey; import static io.opentelemetry.api.common.AttributeKey.longKey; import static io.opentelemetry.api.common.AttributeKey.stringKey; -import static java.util.Objects.requireNonNull; import android.app.Application; import android.location.Location; @@ -36,10 +35,10 @@ import io.opentelemetry.api.trace.StatusCode; import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.instrumentation.okhttp.v3_0.OkHttpTelemetry; +import io.opentelemetry.rum.internal.GlobalAttributesSpanAppender; import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; import okhttp3.Call; import okhttp3.OkHttpClient; @@ -79,7 +78,7 @@ public class SplunkRum { private final SessionId sessionId; private final OpenTelemetrySdk openTelemetrySdk; - private final AtomicReference globalAttributes; + private final GlobalAttributesSpanAppender globalAttributes; static { Handler handler = new Handler(Looper.getMainLooper()); @@ -89,7 +88,7 @@ public class SplunkRum { SplunkRum( OpenTelemetrySdk openTelemetrySdk, SessionId sessionId, - AtomicReference globalAttributes) { + GlobalAttributesSpanAppender globalAttributes) { this.openTelemetrySdk = openTelemetrySdk; this.sessionId = sessionId; this.globalAttributes = globalAttributes; @@ -280,11 +279,9 @@ private String formatStackTrace(StackTraceElement[] stackTrace) { * @param key The {@link AttributeKey} for the attribute. * @param value The value of the attribute, which must match the generic type of the key. * @param The generic type of the value. - * @return this. */ - public SplunkRum setGlobalAttribute(AttributeKey key, T value) { + public void setGlobalAttribute(AttributeKey key, T value) { updateGlobalAttributes(attributesBuilder -> attributesBuilder.put(key, value)); - return this; } /** @@ -297,18 +294,7 @@ public SplunkRum setGlobalAttribute(AttributeKey key, T value) { * operating on a {@link AttributesBuilder} from the current set. */ public void updateGlobalAttributes(Consumer attributesUpdater) { - while (true) { - // we're absolutely certain this will never be null - Attributes oldAttributes = requireNonNull(globalAttributes.get()); - - AttributesBuilder builder = oldAttributes.toBuilder(); - attributesUpdater.accept(builder); - Attributes newAttributes = builder.build(); - - if (globalAttributes.compareAndSet(oldAttributes, newAttributes)) { - break; - } - } + globalAttributes.update(attributesUpdater); } // for testing only diff --git a/splunk-otel-android/src/main/java/com/splunk/rum/SplunkRumBuilder.java b/splunk-otel-android/src/main/java/com/splunk/rum/SplunkRumBuilder.java index bededf94d..9bded2118 100644 --- a/splunk-otel-android/src/main/java/com/splunk/rum/SplunkRumBuilder.java +++ b/splunk-otel-android/src/main/java/com/splunk/rum/SplunkRumBuilder.java @@ -25,7 +25,6 @@ import io.opentelemetry.sdk.trace.export.SpanExporter; import io.opentelemetry.semconv.resource.attributes.ResourceAttributes; import java.time.Duration; -import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; /** A builder of {@link SplunkRum}. */ @@ -311,7 +310,7 @@ SpanExporter decorateWithSpanFilter(SpanExporter exporter) { return spanFilterBuilder.build().apply(exporter); } - AtomicReference buildGlobalAttributesRef() { + Attributes buildInitialGlobalAttributes() { Attributes attrs = globalAttributes; if (deploymentEnvironment != null) { attrs = @@ -319,6 +318,6 @@ AtomicReference buildGlobalAttributesRef() { .put(ResourceAttributes.DEPLOYMENT_ENVIRONMENT, deploymentEnvironment) .build(); } - return new AtomicReference<>(attrs); + return attrs; } } diff --git a/splunk-otel-android/src/main/java/io/opentelemetry/rum/internal/GlobalAttributesSpanAppender.java b/splunk-otel-android/src/main/java/io/opentelemetry/rum/internal/GlobalAttributesSpanAppender.java new file mode 100644 index 000000000..75f8885e6 --- /dev/null +++ b/splunk-otel-android/src/main/java/io/opentelemetry/rum/internal/GlobalAttributesSpanAppender.java @@ -0,0 +1,96 @@ +/* + * 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 java.util.Objects.requireNonNull; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.context.Context; +import io.opentelemetry.sdk.trace.ReadWriteSpan; +import io.opentelemetry.sdk.trace.ReadableSpan; +import io.opentelemetry.sdk.trace.SpanProcessor; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; + +/** + * A {@link SpanProcessor} implementation that appends a set of {@linkplain Attributes attributes} + * to every span that is exported. The attributes collection is mutable, and can be updated by + * calling {@link #update(Consumer)}. + * + *

This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. + */ +public final class GlobalAttributesSpanAppender implements SpanProcessor { + + /** + * Returns a new {@link GlobalAttributesSpanAppender} with a given initial attributes. + * + * @param initialState The initial collection of attributes to append to every span. + */ + public static GlobalAttributesSpanAppender create(Attributes initialState) { + return new GlobalAttributesSpanAppender(initialState); + } + + private final AtomicReference attributes; + + private GlobalAttributesSpanAppender(Attributes initialState) { + this.attributes = new AtomicReference<>(initialState); + } + + @Override + public void onStart(Context parentContext, ReadWriteSpan span) { + span.setAllAttributes(attributes.get()); + } + + @Override + public boolean isStartRequired() { + return true; + } + + @Override + public void onEnd(ReadableSpan span) {} + + @Override + public boolean isEndRequired() { + return false; + } + + /** + * Update the global set of attributes that will be appended to every span. + * + *

Note: this operation performs an atomic update. The passed function should be free from + * side effects, since it may be called multiple times in case of thread contention. + * + * @param attributesUpdater A function which will update the current set of attributes, by + * operating on a {@link AttributesBuilder} from the current set. + */ + public void update(Consumer attributesUpdater) { + while (true) { + // we're absolutely certain this will never be null + Attributes oldAttributes = requireNonNull(attributes.get()); + + AttributesBuilder builder = oldAttributes.toBuilder(); + attributesUpdater.accept(builder); + Attributes newAttributes = builder.build(); + + if (attributes.compareAndSet(oldAttributes, newAttributes)) { + break; + } + } + } +} diff --git a/splunk-otel-android/src/test/java/com/splunk/rum/NoOpSplunkRumTest.java b/splunk-otel-android/src/test/java/com/splunk/rum/NoOpSplunkRumTest.java index c3caf5f25..c8bb9852a 100644 --- a/splunk-otel-android/src/test/java/com/splunk/rum/NoOpSplunkRumTest.java +++ b/splunk-otel-android/src/test/java/com/splunk/rum/NoOpSplunkRumTest.java @@ -43,7 +43,7 @@ public void doesNotThrow() { assertSame(okHttpClient, instance.createRumOkHttpCallFactory(okHttpClient)); instance.updateGlobalAttributes(attributesBuilder -> {}); - assertSame(instance, instance.setGlobalAttribute(AttributeKey.stringKey("foo"), "bar")); + instance.setGlobalAttribute(AttributeKey.stringKey("foo"), "bar"); instance.flushSpans(); instance.integrateWithBrowserRum(mock(WebView.class)); diff --git a/splunk-otel-android/src/test/java/com/splunk/rum/RumAttributeAppenderTest.java b/splunk-otel-android/src/test/java/com/splunk/rum/RumAttributeAppenderTest.java index 4dd4d1ceb..3538567d4 100644 --- a/splunk-otel-android/src/test/java/com/splunk/rum/RumAttributeAppenderTest.java +++ b/splunk-otel-android/src/test/java/com/splunk/rum/RumAttributeAppenderTest.java @@ -16,8 +16,6 @@ package com.splunk.rum; -import static io.opentelemetry.api.common.AttributeKey.longKey; -import static io.opentelemetry.api.common.AttributeKey.stringKey; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; @@ -27,7 +25,6 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import io.opentelemetry.api.common.Attributes; import io.opentelemetry.context.Context; import io.opentelemetry.sdk.trace.ReadWriteSpan; import io.opentelemetry.semconv.resource.attributes.ResourceAttributes; @@ -57,7 +54,6 @@ public void interfaceMethods() { RumAttributeAppender rumAttributeAppender = new RumAttributeAppender( APP_NAME, - Attributes::empty, mock(SessionId.class), "rumVersion", visibleScreenTracker, @@ -69,9 +65,6 @@ public void interfaceMethods() { @Test public void appendAttributesOnStart() { - Attributes globalAttributes = - Attributes.of(stringKey("cheese"), "Camembert", longKey("size"), 5L); - SessionId sessionId = mock(SessionId.class); when(sessionId.getSessionId()).thenReturn("rumSessionId"); when(visibleScreenTracker.getCurrentlyVisibleScreen()).thenReturn("ScreenOne"); @@ -80,12 +73,7 @@ public void appendAttributesOnStart() { RumAttributeAppender rumAttributeAppender = new RumAttributeAppender( - APP_NAME, - () -> globalAttributes, - sessionId, - "rumVersion", - visibleScreenTracker, - connectionUtil); + APP_NAME, sessionId, "rumVersion", visibleScreenTracker, connectionUtil); rumAttributeAppender.onStart(Context.current(), span); verify(span).setAttribute(RumAttributeAppender.RUM_VERSION_KEY, "rumVersion"); @@ -102,7 +90,6 @@ public void appendAttributesOnStart() { verify(span).setAttribute(eq(ResourceAttributes.DEVICE_MODEL_IDENTIFIER), any()); verify(span).setAttribute(eq(ResourceAttributes.DEVICE_MODEL_NAME), any()); verify(span).setAttribute(eq(ResourceAttributes.OS_VERSION), any()); - verify(span).setAllAttributes(globalAttributes); } @Test @@ -115,12 +102,7 @@ public void appendAttributes_noCurrentScreens() { RumAttributeAppender rumAttributeAppender = new RumAttributeAppender( - APP_NAME, - Attributes::empty, - sessionId, - "rumVersion", - visibleScreenTracker, - connectionUtil); + APP_NAME, sessionId, "rumVersion", visibleScreenTracker, connectionUtil); rumAttributeAppender.onStart(Context.current(), span); verify(span).setAttribute(SplunkRum.SCREEN_NAME_KEY, "unknown"); diff --git a/splunk-otel-android/src/test/java/com/splunk/rum/SplunkRumBuilderTest.java b/splunk-otel-android/src/test/java/com/splunk/rum/SplunkRumBuilderTest.java index d98dbfd83..a572bc2e1 100644 --- a/splunk-otel-android/src/test/java/com/splunk/rum/SplunkRumBuilderTest.java +++ b/splunk-otel-android/src/test/java/com/splunk/rum/SplunkRumBuilderTest.java @@ -66,14 +66,14 @@ public void defaultValues() { assertTrue(builder.networkMonitorEnabled); assertTrue(builder.anrDetectionEnabled); assertTrue(builder.slowRenderingDetectionEnabled); - assertEquals(Attributes.empty(), builder.buildGlobalAttributesRef().get()); + assertEquals(Attributes.empty(), builder.buildInitialGlobalAttributes()); assertFalse(builder.sessionBasedSamplerEnabled); } @Test public void handleNullAttributes() { SplunkRumBuilder builder = SplunkRum.builder().setGlobalAttributes(null); - assertEquals(Attributes.empty(), builder.buildGlobalAttributesRef().get()); + assertEquals(Attributes.empty(), builder.buildInitialGlobalAttributes()); } @Test diff --git a/splunk-otel-android/src/test/java/com/splunk/rum/SplunkRumTest.java b/splunk-otel-android/src/test/java/com/splunk/rum/SplunkRumTest.java index a32c565a7..25d2568ef 100644 --- a/splunk-otel-android/src/test/java/com/splunk/rum/SplunkRumTest.java +++ b/splunk-otel-android/src/test/java/com/splunk/rum/SplunkRumTest.java @@ -23,9 +23,11 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isA; import static org.mockito.Mockito.RETURNS_DEEP_STUBS; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -35,10 +37,12 @@ import android.location.Location; import android.webkit.WebView; import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanId; import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.context.Scope; +import io.opentelemetry.rum.internal.GlobalAttributesSpanAppender; import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter; import io.opentelemetry.sdk.testing.junit4.OpenTelemetryRule; @@ -50,17 +54,22 @@ import java.io.File; import java.util.List; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +@RunWith(MockitoJUnitRunner.class) public class SplunkRumTest { @Rule public OpenTelemetryRule otelTesting = OpenTelemetryRule.create(); private Tracer tracer; - private final AtomicReference globalAttributes = - new AtomicReference<>(Attributes.of(stringKey("key"), "value")); + + @Mock private GlobalAttributesSpanAppender globalAttributes; @Before public void setup() { @@ -165,23 +174,6 @@ public void addEvent() { assertEquals(attributes.asMap(), spans.get(0).getAttributes().asMap()); } - @Test - public void updateGlobalAttributes() { - SplunkRum splunkRum = - new SplunkRum( - (OpenTelemetrySdk) otelTesting.getOpenTelemetry(), - new SessionId(new SessionIdTimeoutHandler()), - globalAttributes); - - splunkRum.updateGlobalAttributes( - attributesBuilder -> attributesBuilder.put("key", "value2")); - splunkRum.setGlobalAttribute(longKey("otherKey"), 1234L); - - assertEquals( - Attributes.of(stringKey("key"), "value2", longKey("otherKey"), 1234L), - globalAttributes.get()); - } - @Test public void recordAnr() { StackTraceElement[] stackTrace = new Exception().getStackTrace(); @@ -301,7 +293,20 @@ public void integrateWithBrowserRum() { @Test public void updateLocation() { - AtomicReference globalAttributes = new AtomicReference<>(Attributes.empty()); + AtomicReference updatedAttributes = new AtomicReference<>(); + GlobalAttributesSpanAppender globalAttributes = mock(GlobalAttributesSpanAppender.class); + doAnswer( + invocation -> { + Consumer updater = invocation.getArgument(0); + + AttributesBuilder attributesBuilder = Attributes.builder(); + updater.accept(attributesBuilder); + updatedAttributes.set(attributesBuilder.build()); + return null; + }) + .when(globalAttributes) + .update(any()); + SplunkRum splunkRum = new SplunkRum( (OpenTelemetrySdk) otelTesting.getOpenTelemetry(), @@ -319,10 +324,10 @@ public void updateLocation() { 42d, SplunkRum.LOCATION_LONGITUDE_KEY, 43d), - globalAttributes.get()); + updatedAttributes.get()); splunkRum.updateLocation(null); - assertTrue(globalAttributes.get().isEmpty()); + assertTrue(updatedAttributes.get().isEmpty()); } } diff --git a/splunk-otel-android/src/test/java/io/opentelemetry/rum/internal/GlobalAttributesSpanAppenderTest.java b/splunk-otel-android/src/test/java/io/opentelemetry/rum/internal/GlobalAttributesSpanAppenderTest.java new file mode 100644 index 000000000..75a231cf9 --- /dev/null +++ b/splunk-otel-android/src/test/java/io/opentelemetry/rum/internal/GlobalAttributesSpanAppenderTest.java @@ -0,0 +1,56 @@ +/* + * 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.api.common.AttributeKey.longKey; +import static io.opentelemetry.api.common.AttributeKey.stringKey; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.verify; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.context.Context; +import io.opentelemetry.sdk.trace.ReadWriteSpan; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class GlobalAttributesSpanAppenderTest { + + @Mock private ReadWriteSpan span; + + private final GlobalAttributesSpanAppender globalAttributes = + GlobalAttributesSpanAppender.create(Attributes.of(stringKey("key"), "value")); + + @Test + public void shouldAppendGlobalAttributes() { + globalAttributes.update(attributesBuilder -> attributesBuilder.put("key", "value2")); + globalAttributes.update( + attributesBuilder -> attributesBuilder.put(longKey("otherKey"), 1234L)); + + assertTrue(globalAttributes.isStartRequired()); + globalAttributes.onStart(Context.root(), span); + + verify(span) + .setAllAttributes( + Attributes.of(stringKey("key"), "value2", longKey("otherKey"), 1234L)); + + assertFalse(globalAttributes.isEndRequired()); + } +}