Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds a new API that can customize the ingest sender. #742

Merged
merged 3 commits into from
Jan 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

### Unreleased

* tbd
* Add new incubating API: `SplunkRumBuilder.setHttpSenderCustomizer()` to allow customization
of the HTTP client used for sending data to Splunk. This can be useful when devices are
behind a proxy or API gateway.

## Version 1.3.1 - 2023-12-14

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import io.opentelemetry.api.common.Attributes;
import java.time.Duration;
import java.util.regex.Pattern;
import okhttp3.Request;

public class SampleApplication extends Application {

Expand Down Expand Up @@ -65,6 +66,20 @@ public void onCreate() {
HTTP_URL_SENSITIVE_DATA_PATTERN
.matcher(value)
.replaceAll("$1=<redacted>")))
.setHttpSenderCustomizer(
okHttpBuilder -> {
okHttpBuilder.compressionEnabled(true);
okHttpBuilder
.clientBuilder()
.addInterceptor(
chain -> {
Request.Builder requestBuilder =
chain.request().newBuilder();
requestBuilder.header(
"X-My-Custom-Header", "abc123");
return chain.proceed(requestBuilder.build());
});
})
.build(this);
}
}
2 changes: 1 addition & 1 deletion splunk-otel-android/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,14 @@ dependencies {

implementation("io.opentelemetry:opentelemetry-sdk")
implementation("io.opentelemetry:opentelemetry-exporter-zipkin")
implementation("io.zipkin.reporter2:zipkin-sender-okhttp3")
implementation("io.opentelemetry:opentelemetry-exporter-logging")

implementation("io.opentelemetry.semconv:opentelemetry-semconv:$otelSemconvVersion")
implementation("io.opentelemetry.instrumentation:opentelemetry-okhttp-3.0")

api("io.opentelemetry:opentelemetry-api")
api("com.squareup.okhttp3:okhttp:4.12.0")
api("io.zipkin.reporter2:zipkin-sender-okhttp3:2.17.2")

testImplementation("org.mockito:mockito-core:5.8.0")
testImplementation("org.mockito:mockito-junit-jupiter:5.8.0")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,8 @@ private SpanExporter buildExporter(

private SpanExporter buildStorageBufferingExporter(
CurrentNetworkProvider currentNetworkProvider, SpanStorage spanStorage) {
Sender sender = OkHttpSender.newBuilder().endpoint(getEndpoint()).build();
Sender sender = buildCustomizedZipkinSender();

BandwidthTracker bandwidthTracker = new BandwidthTracker();

FileSender fileSender =
Expand All @@ -359,6 +360,13 @@ private SpanExporter buildStorageBufferingExporter(
return getToDiskExporter(spanStorage);
}

@NonNull
private Sender buildCustomizedZipkinSender() {
OkHttpSender.Builder okBuilder = OkHttpSender.newBuilder().endpoint(getEndpoint());
builder.httpSenderCustomizer.customize(okBuilder);
return okBuilder.build();
}

@NonNull
private String getEndpoint() {
return builder.beaconEndpoint + "?auth=" + builder.rumAccessToken;
Expand Down Expand Up @@ -388,13 +396,15 @@ SpanExporter getToDiskExporter(SpanStorage spanStorage) {
SpanExporter getCoreSpanExporter(String endpoint) {
// return a lazy init exporter so the main thread doesn't block on the setup.
return new LazyInitSpanExporter(
() ->
ZipkinSpanExporter.builder()
.setEncoder(new CustomZipkinEncoder())
.setEndpoint(endpoint)
// remove the local IP address
.setLocalIpAddressSupplier(() -> null)
.build());
() -> {
return ZipkinSpanExporter.builder()
.setEncoder(new CustomZipkinEncoder())
.setEndpoint(endpoint)
// remove the local IP address
.setLocalIpAddressSupplier(() -> null)
.setSender(buildCustomizedZipkinSender())
.build();
});
}

private static class LazyInitSpanExporter implements SpanExporter {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import android.app.Application;
import android.util.Log;
import androidx.annotation.Nullable;
import com.splunk.rum.incubating.HttpSenderCustomizer;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.sdk.trace.export.SpanExporter;
import java.time.Duration;
Expand All @@ -42,6 +43,7 @@ public final class SplunkRumBuilder {
Duration slowRenderingDetectionPollInterval = DEFAULT_SLOW_RENDERING_DETECTION_POLL_INTERVAL;
Attributes globalAttributes = Attributes.empty();
@Nullable String deploymentEnvironment;
HttpSenderCustomizer httpSenderCustomizer = HttpSenderCustomizer.DEFAULT;
private Consumer<SpanFilterBuilder> spanFilterConfigurer = x -> {};
int maxUsageMegabytes = DEFAULT_MAX_STORAGE_USE_MB;
boolean sessionBasedSamplerEnabled = false;
Expand Down Expand Up @@ -78,6 +80,23 @@ public SplunkRumBuilder setBeaconEndpoint(String beaconEndpoint) {
return this;
}

/**
* This method can be used to provide a customizer that will have access to the
* OkHttpSender.Builder before the sender is created. Typical use cases for this are to provide
* custom headers or to modify compression settings. This is a pretty large hammer and should be
* used with caution.
*
* <p>This API is considered incubating and is subject to change.
*
* @param customizer that can make changes to the OkHttpSender.Builder
* @return {@code this}
* @since 1.4.0
*/
public SplunkRumBuilder setHttpSenderCustomizer(HttpSenderCustomizer customizer) {
this.httpSenderCustomizer = customizer;
return this;
}

/**
* Sets the realm for the beacon to send RUM telemetry to. This should be used in place of the
* {@link #setBeaconEndpoint(String)} method in most cases.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* 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.incubating;

import zipkin2.reporter.okhttp3.OkHttpSender;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shucks, I was hoping this might be forward-compatible if/when we move to OTLP....

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, headers can be specified through OtlpHttpSpanExporterBuilder.setHeaders() and compression can be swizzled with OtlpHttpSpanExporterBuilder.setCompression().

These methods also exist on OtlpHttpLogRecordExporterBuilder, so I think we'll be ok.


/**
* This interface can be used to customize the exporter used to send telemetry to Splunk. It is not
* yet stable and its APIs are subject to change at any time.
*
* @since 1.4.0
*/
public interface HttpSenderCustomizer {

HttpSenderCustomizer DEFAULT = x -> {};

void customize(OkHttpSender.Builder builder);
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,15 @@
import static java.util.concurrent.TimeUnit.MINUTES;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import android.app.Application;
import android.content.Context;
import android.os.Looper;
import com.splunk.rum.incubating.HttpSenderCustomizer;
import io.opentelemetry.android.instrumentation.activity.VisibleScreenTracker;
import io.opentelemetry.android.instrumentation.network.CurrentNetwork;
import io.opentelemetry.android.instrumentation.network.CurrentNetworkProvider;
Expand All @@ -48,10 +50,12 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import zipkin2.reporter.okhttp3.OkHttpSender;

@ExtendWith(MockitoExtension.class)
class RumInitializerTest {
Expand Down Expand Up @@ -214,6 +218,30 @@ private TestSpanData createTestSpan(long startTimeNanos) {
.build();
}

@Test
void canCustomizeHttpSender() {
AtomicReference<OkHttpSender.Builder> seenBuilder = new AtomicReference<>();
HttpSenderCustomizer customizer = seenBuilder::set;
SplunkRumBuilder splunkRumBuilder =
new SplunkRumBuilder()
.setRealm("us0")
.setRumAccessToken("secret!")
.setApplicationName("test")
.disableAnrDetection()
.setHttpSenderCustomizer(customizer);

when(application.getApplicationContext()).thenReturn(context);

RumInitializer testInitializer =
new RumInitializer(splunkRumBuilder, application, new AppStartupTimer());
SplunkRum rum = testInitializer.initialize(mainLooper);
rum.addRumEvent("foo", Attributes.empty()); // need to trigger export
rum.flushSpans();

assertNotNull(rum);
assertNotNull(seenBuilder.get());
}

@Test
void shouldTranslateExceptionEventsToSpanAttributes() {
InMemorySpanExporter spanExporter = InMemorySpanExporter.create();
Expand Down
Loading