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

Add experimental JdkHttpSender #5557

Merged
merged 9 commits into from
Jul 6, 2023
7 changes: 4 additions & 3 deletions all/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ otelJava.moduleName.set("io.opentelemetry.all")
tasks {
// We don't compile much here, just some API boundary tests. This project is mostly for
// aggregating jacoco reports and it doesn't work if this isn't at least as high as the
// highest supported Java version in any of our projects. All of our projects target
// Java 8.
// highest supported Java version in any of our projects. All of our
// projects target Java 8 except :exporters:http-sender:jdk, which targets
// Java 11
withType(JavaCompile::class) {
options.release.set(8)
options.release.set(11)
}

val testJavaVersion: String? by project
Expand Down
32 changes: 32 additions & 0 deletions exporters/common/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,35 @@ dependencies {
testImplementation("io.grpc:grpc-testing")
testRuntimeOnly("io.grpc:grpc-netty-shaded")
}

val testJavaVersion: String? by project

testing {
suites {
register<JvmTestSuite>("testHttpSenderProvider") {
dependencies {
implementation(project(":exporters:sender:jdk"))
implementation(project(":exporters:sender:okhttp"))
}
targets {
all {
testTask {
enabled = !testJavaVersion.equals("8")
}
}
}
}
}
}

tasks {
check {
dependsOn(testing.suites)
}
}

afterEvaluate {
tasks.named<JavaCompile>("compileTestHttpSenderProviderJava") {
options.release.set(11)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
package io.opentelemetry.exporter.internal.http;

import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.internal.ConfigUtil;
import io.opentelemetry.api.metrics.MeterProvider;
import io.opentelemetry.exporter.internal.ExporterBuilderUtil;
import io.opentelemetry.exporter.internal.TlsConfigHelper;
Expand Down Expand Up @@ -129,29 +130,80 @@ public HttpExporter<T> build() {
Map<String, String> headers = this.headers == null ? Collections.emptyMap() : this.headers;
Supplier<Map<String, String>> headerSupplier = () -> headers;

HttpSender httpSender = null;
// TODO: once we publish multiple HttpSenderProviders, log warning when multiple are found
for (HttpSenderProvider httpSenderProvider :
HttpSenderProvider httpSenderProvider = resolveHttpSenderProvider();
HttpSender httpSender =
httpSenderProvider.createSender(
endpoint,
compressionEnabled,
exportAsJson ? "application/json" : "application/x-protobuf",
timeoutNanos,
headerSupplier,
authenticator,
retryPolicy,
tlsConfigHelper.getSslContext(),
tlsConfigHelper.getTrustManager());
LOGGER.log(Level.FINE, "Using HttpSender: " + httpSender.getClass().getName());

return new HttpExporter<>(exporterName, type, httpSender, meterProviderSupplier, exportAsJson);
}

/**
* Resolve the {@link HttpSenderProvider}.
*
* <p>If no {@link HttpSenderProvider} is available, throw {@link IllegalStateException}.
*
* <p>If only one {@link HttpSenderProvider} is available, use it.
*
* <p>If multiple are available and..
*
* <ul>
* <li>{@code io.opentelemetry.exporter.internal.http.HttpSenderProvider} is empty, use the
* first found.
* <li>{@code io.opentelemetry.exporter.internal.http.HttpSenderProvider} is set, use the
* matching provider. If none match, throw {@link IllegalStateException}.
* </ul>
*/
private static HttpSenderProvider resolveHttpSenderProvider() {
jack-berg marked this conversation as resolved.
Show resolved Hide resolved
Map<String, HttpSenderProvider> httpSenderProviders = new HashMap<>();
for (HttpSenderProvider spi :
ServiceLoader.load(HttpSenderProvider.class, HttpExporterBuilder.class.getClassLoader())) {
httpSender =
httpSenderProvider.createSender(
endpoint,
compressionEnabled,
exportAsJson ? "application/json" : "application/x-protobuf",
timeoutNanos,
headerSupplier,
authenticator,
retryPolicy,
tlsConfigHelper.getSslContext(),
tlsConfigHelper.getTrustManager());
LOGGER.log(Level.FINE, "Using HttpSender: " + httpSender.getClass().getName());
break;
httpSenderProviders.put(spi.getClass().getName(), spi);
}
if (httpSender == null) {

// No provider on classpath, throw
if (httpSenderProviders.isEmpty()) {
throw new IllegalStateException(
"No HttpSenderProvider found on classpath. Please add dependency on opentelemetry-exporter-sender-okhttp");
"No HttpSenderProvider found on classpath. Please add dependency on "
+ "opentelemetry-exporter-sender-okhttp or opentelemetry-exporter-sender-jdk");
}

return new HttpExporter<>(exporterName, type, httpSender, meterProviderSupplier, exportAsJson);
// Exactly one provider on classpath, use it
if (httpSenderProviders.size() == 1) {
return httpSenderProviders.values().stream().findFirst().get();
}

// If we've reached here, there are multiple HttpSenderProviders
String configuredSender =
ConfigUtil.getString("io.opentelemetry.exporter.internal.http.HttpSenderProvider", "");

// Multiple providers but none configured, use first we find and log a warning
if (configuredSender.isEmpty()) {
LOGGER.log(
Level.WARNING,
"Multiple HttpSenderProvider found. Please include only one, "
+ "or specify preference setting io.opentelemetry.exporter.internal.http.HttpSenderProvider "
+ "to the FQCN of the preferred provider.");
return httpSenderProviders.values().stream().findFirst().get();
}

// Multiple providers with configuration match, use configuration match
if (httpSenderProviders.containsKey(configuredSender)) {
return httpSenderProviders.get(configuredSender);
}

// Multiple providers, configured does not match, throw
throw new IllegalStateException(
"No HttpSenderProvider matched configured io.opentelemetry.exporter.internal.http.HttpSenderProvider: "
+ configuredSender);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ void build_NoHttpSenderProvider() {
assertThatThrownBy(() -> new HttpExporterBuilder<>("name", "type", "http://localhost").build())
.isInstanceOf(IllegalStateException.class)
.hasMessage(
"No HttpSenderProvider found on classpath. Please add dependency on opentelemetry-exporter-sender-okhttp");
"No HttpSenderProvider found on classpath. Please add dependency on "
+ "opentelemetry-exporter-sender-okhttp or opentelemetry-exporter-sender-jdk");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.exporter.internal.http;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

import io.github.netmikey.logunit.api.LogCapturer;
import io.opentelemetry.exporter.sender.jdk.internal.JdkHttpSender;
import io.opentelemetry.exporter.sender.okhttp.internal.OkHttpHttpSender;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junitpioneer.jupiter.SetSystemProperty;

class HttpExporterTest {

@RegisterExtension
LogCapturer logCapturer =
LogCapturer.create().captureForLogger(HttpExporterBuilder.class.getName());

@Test
void build_multipleSendersNoConfiguration() {
Assertions.assertThatCode(
() -> new HttpExporterBuilder<>("exporter", "type", "http://localhost").build())
.doesNotThrowAnyException();

logCapturer.assertContains(
"Multiple HttpSenderProvider found. Please include only one, "
+ "or specify preference setting io.opentelemetry.exporter.internal.http.HttpSenderProvider "
+ "to the FQCN of the preferred provider.");
}

@Test
@SetSystemProperty(
key = "io.opentelemetry.exporter.internal.http.HttpSenderProvider",
value = "io.opentelemetry.exporter.sender.jdk.internal.JdkHttpSenderProvider")
void build_multipleSendersWithJdk() {
assertThat(new HttpExporterBuilder<>("exporter", "type", "http://localhost").build())
.extracting("httpSender")
.isInstanceOf(JdkHttpSender.class);

assertThat(logCapturer.getEvents()).isEmpty();
}

@Test
@SetSystemProperty(
key = "io.opentelemetry.exporter.internal.http.HttpSenderProvider",
value = "io.opentelemetry.exporter.sender.okhttp.internal.OkHttpHttpSenderProvider")
void build_multipleSendersWithOkHttp() {
assertThat(new HttpExporterBuilder<>("exporter", "type", "http://localhost").build())
.extracting("httpSender")
.isInstanceOf(OkHttpHttpSender.class);

assertThat(logCapturer.getEvents()).isEmpty();
}

@Test
@SetSystemProperty(
key = "io.opentelemetry.exporter.internal.http.HttpSenderProvider",
value = "foo")
void build_multipleSendersNoMatch() {
assertThatThrownBy(
() -> new HttpExporterBuilder<>("exporter", "type", "http://localhost").build())
.isInstanceOf(IllegalStateException.class)
.hasMessage(
"No HttpSenderProvider matched configured io.opentelemetry.exporter.internal.http.HttpSenderProvider: foo");

assertThat(logCapturer.getEvents()).isEmpty();
}
}
24 changes: 24 additions & 0 deletions exporters/otlp/all/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ dependencies {
jmhRuntimeOnly("io.grpc:grpc-netty")
}

val testJavaVersion: String? by project

testing {
suites {
register<JvmTestSuite>("testGrpcNetty") {
Expand All @@ -65,6 +67,22 @@ testing {
implementation("io.grpc:grpc-stub")
}
}
register<JvmTestSuite>("testJdkHttpSender") {
dependencies {
implementation(project(":exporters:sender:jdk"))
implementation(project(":exporters:otlp:testing-internal"))

implementation("io.grpc:grpc-stub")
}
targets {
all {
testTask {
systemProperty("io.opentelemetry.exporter.internal.http.HttpSenderProvider", "io.opentelemetry.exporter.sender.jdk.internal.JdkHttpSenderProvider")
enabled = !testJavaVersion.equals("8")
}
}
}
}
register<JvmTestSuite>("testSpanPipeline") {
dependencies {
implementation("io.opentelemetry.proto:opentelemetry-proto")
Expand All @@ -86,3 +104,9 @@ tasks {
)
}
}

afterEvaluate {
tasks.named<JavaCompile>("compileTestJdkHttpSenderJava") {
options.release.set(11)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@
import javax.net.ssl.SSLContext;
import javax.net.ssl.X509TrustManager;

class OtlpHttpLogRecordExporterTest
class OtlpHttpLogRecordExporterOkHttpSenderTest
extends AbstractHttpTelemetryExporterTest<LogRecordData, ResourceLogs> {

protected OtlpHttpLogRecordExporterTest() {
protected OtlpHttpLogRecordExporterOkHttpSenderTest() {
super("log", "/v1/logs", ResourceLogs.getDefaultInstance());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@
import javax.net.ssl.X509TrustManager;
import org.junit.jupiter.api.Test;

class OtlpHttpMetricExporterTest
class OtlpHttpMetricExporterOkHttpSenderTest
extends AbstractHttpTelemetryExporterTest<MetricData, ResourceMetrics> {

protected OtlpHttpMetricExporterTest() {
protected OtlpHttpMetricExporterOkHttpSenderTest() {
super("metric", "/v1/metrics", ResourceMetrics.getDefaultInstance());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@
import javax.net.ssl.SSLContext;
import javax.net.ssl.X509TrustManager;

class OtlpHttpSpanExporterTest extends AbstractHttpTelemetryExporterTest<SpanData, ResourceSpans> {
class OtlpHttpSpanExporterOkHttpSenderTest
extends AbstractHttpTelemetryExporterTest<SpanData, ResourceSpans> {

protected OtlpHttpSpanExporterTest() {
protected OtlpHttpSpanExporterOkHttpSenderTest() {
super("span", "/v1/traces", ResourceSpans.getDefaultInstance());
}

Expand Down
Loading