From ba6cc2c9ba283e1c8d541de69d2d40be2c026f62 Mon Sep 17 00:00:00 2001 From: MartenH <72463136+mhennoch@users.noreply.github.com> Date: Thu, 13 Oct 2022 16:42:40 +0300 Subject: [PATCH] React native exporter (#381) --- .../java/com/splunk/rum/RumInitializer.java | 4 + .../java/com/splunk/rum/SplunkRumBuilder.java | 11 +++ .../splunk/rum/reactnative/RNSpanData.java | 73 +++++++++++++++++++ .../rum/reactnative/ReactNativeExporter.java | 61 ++++++++++++++++ 4 files changed, 149 insertions(+) create mode 100644 splunk-otel-android/src/main/java/com/splunk/rum/reactnative/RNSpanData.java create mode 100644 splunk-otel-android/src/main/java/com/splunk/rum/reactnative/ReactNativeExporter.java 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 469f649d5..21f9d3d7e 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 @@ -342,6 +342,10 @@ SpanExporter buildFilteringExporter(ConnectionUtil connectionUtil) { SpanExporter filteredExporter = builder.decorateWithSpanFilter(exporter); initializationEvents.add( new InitializationEvent("zipkin exporter initialized", timingClock.now())); + + if (builder.reactNativeSupportEnabled) { + return builder.decorateWithReactNativeExporter(filteredExporter); + } return filteredExporter; } 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 9bded2118..8d707265b 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 @@ -21,6 +21,7 @@ import android.app.Application; import android.util.Log; import androidx.annotation.Nullable; +import com.splunk.rum.reactnative.ReactNativeExporter; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.sdk.trace.export.SpanExporter; import io.opentelemetry.semconv.resource.attributes.ResourceAttributes; @@ -50,6 +51,7 @@ public final class SplunkRumBuilder { int maxUsageMegabytes = DEFAULT_MAX_STORAGE_USE_MB; boolean sessionBasedSamplerEnabled = false; double sessionBasedSamplerRatio = 1.0; + boolean reactNativeSupportEnabled = false; /** * Sets the application name that will be used to identify your application in the Splunk RUM @@ -138,6 +140,11 @@ public SplunkRumBuilder enableDiskBuffering() { return this; } + public SplunkRumBuilder enableReactNativeSupport() { + this.reactNativeSupportEnabled = true; + return this; + } + /** * Disables the crash reporting feature. * @@ -310,6 +317,10 @@ SpanExporter decorateWithSpanFilter(SpanExporter exporter) { return spanFilterBuilder.build().apply(exporter); } + public SpanExporter decorateWithReactNativeExporter(SpanExporter exporter) { + return new ReactNativeExporter(exporter); + } + Attributes buildInitialGlobalAttributes() { Attributes attrs = globalAttributes; if (deploymentEnvironment != null) { diff --git a/splunk-otel-android/src/main/java/com/splunk/rum/reactnative/RNSpanData.java b/splunk-otel-android/src/main/java/com/splunk/rum/reactnative/RNSpanData.java new file mode 100644 index 000000000..ec5387771 --- /dev/null +++ b/splunk-otel-android/src/main/java/com/splunk/rum/reactnative/RNSpanData.java @@ -0,0 +1,73 @@ +/* + * 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.reactnative; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.internal.ImmutableSpanContext; +import io.opentelemetry.api.trace.SpanContext; +import io.opentelemetry.sdk.trace.data.DelegatingSpanData; +import io.opentelemetry.sdk.trace.data.SpanData; + +public final class RNSpanData extends DelegatingSpanData { + + private final SpanContext modifiedContext; + private final Attributes modifiedAttributes; + + static SpanData create(SpanData original) { + SpanContext originalSpanContext = original.getSpanContext(); + Attributes attributes = original.getAttributes(); + AttributeKey traceIdKey = AttributeKey.stringKey("_reactnative_traceId"); + AttributeKey spanIdKey = AttributeKey.stringKey("_reactnative_spanId"); + + String traceIdFromRN = attributes.get(traceIdKey); + String spanIdFromRN = attributes.get(spanIdKey); + if (traceIdFromRN == null || spanIdFromRN == null) { + return original; + } + + Attributes attributesWithoutRNIds = + attributes.toBuilder().remove(traceIdKey).remove(spanIdKey).build(); + + SpanContext rnContext = + ImmutableSpanContext.create( + traceIdFromRN, + spanIdFromRN, + originalSpanContext.getTraceFlags(), + originalSpanContext.getTraceState(), + originalSpanContext.isRemote(), + false); + return new RNSpanData(original, rnContext, attributesWithoutRNIds); + } + + private RNSpanData( + SpanData delegate, SpanContext modifiedContext, Attributes modifiedAttributes) { + super(delegate); + this.modifiedContext = modifiedContext; + this.modifiedAttributes = modifiedAttributes; + } + + @Override + public SpanContext getSpanContext() { + return this.modifiedContext; + } + + @Override + public Attributes getAttributes() { + return this.modifiedAttributes; + } +} diff --git a/splunk-otel-android/src/main/java/com/splunk/rum/reactnative/ReactNativeExporter.java b/splunk-otel-android/src/main/java/com/splunk/rum/reactnative/ReactNativeExporter.java new file mode 100644 index 000000000..1013285ad --- /dev/null +++ b/splunk-otel-android/src/main/java/com/splunk/rum/reactnative/ReactNativeExporter.java @@ -0,0 +1,61 @@ +/* + * 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.reactnative; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.sdk.common.CompletableResultCode; +import io.opentelemetry.sdk.trace.data.SpanData; +import io.opentelemetry.sdk.trace.export.SpanExporter; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +public final class ReactNativeExporter implements SpanExporter { + private final SpanExporter delegate; + + public ReactNativeExporter(SpanExporter delegate) { + this.delegate = delegate; + } + + @Override + public CompletableResultCode export(Collection spans) { + List modified = new ArrayList<>(); + for (SpanData span : spans) { + modified.add(modify(span)); + } + return delegate.export(modified); + } + + private SpanData modify(SpanData span) { + Attributes attributes = span.getAttributes(); + if (attributes.get(AttributeKey.stringKey("_reactnative_traceId")) == null) { + return span; + } + return RNSpanData.create(span); + } + + @Override + public CompletableResultCode flush() { + return delegate.flush(); + } + + @Override + public CompletableResultCode shutdown() { + return delegate.shutdown(); + } +}