Skip to content

Commit aab7159

Browse files
authored
Add experimental JdkHttpSender (#5557)
1 parent cb4d13d commit aab7159

File tree

22 files changed

+1064
-36
lines changed

22 files changed

+1064
-36
lines changed

all/build.gradle.kts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,11 @@ otelJava.moduleName.set("io.opentelemetry.all")
88
tasks {
99
// We don't compile much here, just some API boundary tests. This project is mostly for
1010
// aggregating jacoco reports and it doesn't work if this isn't at least as high as the
11-
// highest supported Java version in any of our projects. All of our projects target
12-
// Java 8.
11+
// highest supported Java version in any of our projects. All of our
12+
// projects target Java 8 except :exporters:http-sender:jdk, which targets
13+
// Java 11
1314
withType(JavaCompile::class) {
14-
options.release.set(8)
15+
options.release.set(11)
1516
}
1617

1718
val testJavaVersion: String? by project

exporters/common/build.gradle.kts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,35 @@ dependencies {
3434
testImplementation("io.grpc:grpc-testing")
3535
testRuntimeOnly("io.grpc:grpc-netty-shaded")
3636
}
37+
38+
val testJavaVersion: String? by project
39+
40+
testing {
41+
suites {
42+
register<JvmTestSuite>("testHttpSenderProvider") {
43+
dependencies {
44+
implementation(project(":exporters:sender:jdk"))
45+
implementation(project(":exporters:sender:okhttp"))
46+
}
47+
targets {
48+
all {
49+
testTask {
50+
enabled = !testJavaVersion.equals("8")
51+
}
52+
}
53+
}
54+
}
55+
}
56+
}
57+
58+
tasks {
59+
check {
60+
dependsOn(testing.suites)
61+
}
62+
}
63+
64+
afterEvaluate {
65+
tasks.named<JavaCompile>("compileTestHttpSenderProviderJava") {
66+
options.release.set(11)
67+
}
68+
}

exporters/common/src/main/java/io/opentelemetry/exporter/internal/http/HttpExporterBuilder.java

Lines changed: 71 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
package io.opentelemetry.exporter.internal.http;
77

88
import io.opentelemetry.api.GlobalOpenTelemetry;
9+
import io.opentelemetry.api.internal.ConfigUtil;
910
import io.opentelemetry.api.metrics.MeterProvider;
1011
import io.opentelemetry.exporter.internal.ExporterBuilderUtil;
1112
import io.opentelemetry.exporter.internal.TlsConfigHelper;
@@ -129,29 +130,80 @@ public HttpExporter<T> build() {
129130
Map<String, String> headers = this.headers == null ? Collections.emptyMap() : this.headers;
130131
Supplier<Map<String, String>> headerSupplier = () -> headers;
131132

132-
HttpSender httpSender = null;
133-
// TODO: once we publish multiple HttpSenderProviders, log warning when multiple are found
134-
for (HttpSenderProvider httpSenderProvider :
133+
HttpSenderProvider httpSenderProvider = resolveHttpSenderProvider();
134+
HttpSender httpSender =
135+
httpSenderProvider.createSender(
136+
endpoint,
137+
compressionEnabled,
138+
exportAsJson ? "application/json" : "application/x-protobuf",
139+
timeoutNanos,
140+
headerSupplier,
141+
authenticator,
142+
retryPolicy,
143+
tlsConfigHelper.getSslContext(),
144+
tlsConfigHelper.getTrustManager());
145+
LOGGER.log(Level.FINE, "Using HttpSender: " + httpSender.getClass().getName());
146+
147+
return new HttpExporter<>(exporterName, type, httpSender, meterProviderSupplier, exportAsJson);
148+
}
149+
150+
/**
151+
* Resolve the {@link HttpSenderProvider}.
152+
*
153+
* <p>If no {@link HttpSenderProvider} is available, throw {@link IllegalStateException}.
154+
*
155+
* <p>If only one {@link HttpSenderProvider} is available, use it.
156+
*
157+
* <p>If multiple are available and..
158+
*
159+
* <ul>
160+
* <li>{@code io.opentelemetry.exporter.internal.http.HttpSenderProvider} is empty, use the
161+
* first found.
162+
* <li>{@code io.opentelemetry.exporter.internal.http.HttpSenderProvider} is set, use the
163+
* matching provider. If none match, throw {@link IllegalStateException}.
164+
* </ul>
165+
*/
166+
private static HttpSenderProvider resolveHttpSenderProvider() {
167+
Map<String, HttpSenderProvider> httpSenderProviders = new HashMap<>();
168+
for (HttpSenderProvider spi :
135169
ServiceLoader.load(HttpSenderProvider.class, HttpExporterBuilder.class.getClassLoader())) {
136-
httpSender =
137-
httpSenderProvider.createSender(
138-
endpoint,
139-
compressionEnabled,
140-
exportAsJson ? "application/json" : "application/x-protobuf",
141-
timeoutNanos,
142-
headerSupplier,
143-
authenticator,
144-
retryPolicy,
145-
tlsConfigHelper.getSslContext(),
146-
tlsConfigHelper.getTrustManager());
147-
LOGGER.log(Level.FINE, "Using HttpSender: " + httpSender.getClass().getName());
148-
break;
170+
httpSenderProviders.put(spi.getClass().getName(), spi);
149171
}
150-
if (httpSender == null) {
172+
173+
// No provider on classpath, throw
174+
if (httpSenderProviders.isEmpty()) {
151175
throw new IllegalStateException(
152-
"No HttpSenderProvider found on classpath. Please add dependency on opentelemetry-exporter-sender-okhttp");
176+
"No HttpSenderProvider found on classpath. Please add dependency on "
177+
+ "opentelemetry-exporter-sender-okhttp or opentelemetry-exporter-sender-jdk");
153178
}
154179

155-
return new HttpExporter<>(exporterName, type, httpSender, meterProviderSupplier, exportAsJson);
180+
// Exactly one provider on classpath, use it
181+
if (httpSenderProviders.size() == 1) {
182+
return httpSenderProviders.values().stream().findFirst().get();
183+
}
184+
185+
// If we've reached here, there are multiple HttpSenderProviders
186+
String configuredSender =
187+
ConfigUtil.getString("io.opentelemetry.exporter.internal.http.HttpSenderProvider", "");
188+
189+
// Multiple providers but none configured, use first we find and log a warning
190+
if (configuredSender.isEmpty()) {
191+
LOGGER.log(
192+
Level.WARNING,
193+
"Multiple HttpSenderProvider found. Please include only one, "
194+
+ "or specify preference setting io.opentelemetry.exporter.internal.http.HttpSenderProvider "
195+
+ "to the FQCN of the preferred provider.");
196+
return httpSenderProviders.values().stream().findFirst().get();
197+
}
198+
199+
// Multiple providers with configuration match, use configuration match
200+
if (httpSenderProviders.containsKey(configuredSender)) {
201+
return httpSenderProviders.get(configuredSender);
202+
}
203+
204+
// Multiple providers, configured does not match, throw
205+
throw new IllegalStateException(
206+
"No HttpSenderProvider matched configured io.opentelemetry.exporter.internal.http.HttpSenderProvider: "
207+
+ configuredSender);
156208
}
157209
}

exporters/common/src/test/java/io/opentelemetry/exporter/internal/http/HttpExporterTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ void build_NoHttpSenderProvider() {
1616
assertThatThrownBy(() -> new HttpExporterBuilder<>("name", "type", "http://localhost").build())
1717
.isInstanceOf(IllegalStateException.class)
1818
.hasMessage(
19-
"No HttpSenderProvider found on classpath. Please add dependency on opentelemetry-exporter-sender-okhttp");
19+
"No HttpSenderProvider found on classpath. Please add dependency on "
20+
+ "opentelemetry-exporter-sender-okhttp or opentelemetry-exporter-sender-jdk");
2021
}
2122
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.exporter.internal.http;
7+
8+
import static org.assertj.core.api.Assertions.assertThat;
9+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
10+
11+
import io.github.netmikey.logunit.api.LogCapturer;
12+
import io.opentelemetry.exporter.sender.jdk.internal.JdkHttpSender;
13+
import io.opentelemetry.exporter.sender.okhttp.internal.OkHttpHttpSender;
14+
import org.assertj.core.api.Assertions;
15+
import org.junit.jupiter.api.Test;
16+
import org.junit.jupiter.api.extension.RegisterExtension;
17+
import org.junitpioneer.jupiter.SetSystemProperty;
18+
19+
class HttpExporterTest {
20+
21+
@RegisterExtension
22+
LogCapturer logCapturer =
23+
LogCapturer.create().captureForLogger(HttpExporterBuilder.class.getName());
24+
25+
@Test
26+
void build_multipleSendersNoConfiguration() {
27+
Assertions.assertThatCode(
28+
() -> new HttpExporterBuilder<>("exporter", "type", "http://localhost").build())
29+
.doesNotThrowAnyException();
30+
31+
logCapturer.assertContains(
32+
"Multiple HttpSenderProvider found. Please include only one, "
33+
+ "or specify preference setting io.opentelemetry.exporter.internal.http.HttpSenderProvider "
34+
+ "to the FQCN of the preferred provider.");
35+
}
36+
37+
@Test
38+
@SetSystemProperty(
39+
key = "io.opentelemetry.exporter.internal.http.HttpSenderProvider",
40+
value = "io.opentelemetry.exporter.sender.jdk.internal.JdkHttpSenderProvider")
41+
void build_multipleSendersWithJdk() {
42+
assertThat(new HttpExporterBuilder<>("exporter", "type", "http://localhost").build())
43+
.extracting("httpSender")
44+
.isInstanceOf(JdkHttpSender.class);
45+
46+
assertThat(logCapturer.getEvents()).isEmpty();
47+
}
48+
49+
@Test
50+
@SetSystemProperty(
51+
key = "io.opentelemetry.exporter.internal.http.HttpSenderProvider",
52+
value = "io.opentelemetry.exporter.sender.okhttp.internal.OkHttpHttpSenderProvider")
53+
void build_multipleSendersWithOkHttp() {
54+
assertThat(new HttpExporterBuilder<>("exporter", "type", "http://localhost").build())
55+
.extracting("httpSender")
56+
.isInstanceOf(OkHttpHttpSender.class);
57+
58+
assertThat(logCapturer.getEvents()).isEmpty();
59+
}
60+
61+
@Test
62+
@SetSystemProperty(
63+
key = "io.opentelemetry.exporter.internal.http.HttpSenderProvider",
64+
value = "foo")
65+
void build_multipleSendersNoMatch() {
66+
assertThatThrownBy(
67+
() -> new HttpExporterBuilder<>("exporter", "type", "http://localhost").build())
68+
.isInstanceOf(IllegalStateException.class)
69+
.hasMessage(
70+
"No HttpSenderProvider matched configured io.opentelemetry.exporter.internal.http.HttpSenderProvider: foo");
71+
72+
assertThat(logCapturer.getEvents()).isEmpty();
73+
}
74+
}

exporters/otlp/all/build.gradle.kts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ dependencies {
3939
jmhRuntimeOnly("io.grpc:grpc-netty")
4040
}
4141

42+
val testJavaVersion: String? by project
43+
4244
testing {
4345
suites {
4446
register<JvmTestSuite>("testGrpcNetty") {
@@ -65,6 +67,22 @@ testing {
6567
implementation("io.grpc:grpc-stub")
6668
}
6769
}
70+
register<JvmTestSuite>("testJdkHttpSender") {
71+
dependencies {
72+
implementation(project(":exporters:sender:jdk"))
73+
implementation(project(":exporters:otlp:testing-internal"))
74+
75+
implementation("io.grpc:grpc-stub")
76+
}
77+
targets {
78+
all {
79+
testTask {
80+
systemProperty("io.opentelemetry.exporter.internal.http.HttpSenderProvider", "io.opentelemetry.exporter.sender.jdk.internal.JdkHttpSenderProvider")
81+
enabled = !testJavaVersion.equals("8")
82+
}
83+
}
84+
}
85+
}
6886
register<JvmTestSuite>("testSpanPipeline") {
6987
dependencies {
7088
implementation("io.opentelemetry.proto:opentelemetry-proto")
@@ -86,3 +104,9 @@ tasks {
86104
)
87105
}
88106
}
107+
108+
afterEvaluate {
109+
tasks.named<JavaCompile>("compileTestJdkHttpSenderJava") {
110+
options.release.set(11)
111+
}
112+
}

exporters/otlp/all/src/test/java/io/opentelemetry/exporter/otlp/http/logs/OtlpHttpLogRecordExporterTest.java renamed to exporters/otlp/all/src/test/java/io/opentelemetry/exporter/otlp/http/logs/OtlpHttpLogRecordExporterOkHttpSenderTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@
2121
import javax.net.ssl.SSLContext;
2222
import javax.net.ssl.X509TrustManager;
2323

24-
class OtlpHttpLogRecordExporterTest
24+
class OtlpHttpLogRecordExporterOkHttpSenderTest
2525
extends AbstractHttpTelemetryExporterTest<LogRecordData, ResourceLogs> {
2626

27-
protected OtlpHttpLogRecordExporterTest() {
27+
protected OtlpHttpLogRecordExporterOkHttpSenderTest() {
2828
super("log", "/v1/logs", ResourceLogs.getDefaultInstance());
2929
}
3030

exporters/otlp/all/src/test/java/io/opentelemetry/exporter/otlp/http/metrics/OtlpHttpMetricExporterTest.java renamed to exporters/otlp/all/src/test/java/io/opentelemetry/exporter/otlp/http/metrics/OtlpHttpMetricExporterOkHttpSenderTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,10 @@
3131
import javax.net.ssl.X509TrustManager;
3232
import org.junit.jupiter.api.Test;
3333

34-
class OtlpHttpMetricExporterTest
34+
class OtlpHttpMetricExporterOkHttpSenderTest
3535
extends AbstractHttpTelemetryExporterTest<MetricData, ResourceMetrics> {
3636

37-
protected OtlpHttpMetricExporterTest() {
37+
protected OtlpHttpMetricExporterOkHttpSenderTest() {
3838
super("metric", "/v1/metrics", ResourceMetrics.getDefaultInstance());
3939
}
4040

exporters/otlp/all/src/test/java/io/opentelemetry/exporter/otlp/http/trace/OtlpHttpSpanExporterTest.java renamed to exporters/otlp/all/src/test/java/io/opentelemetry/exporter/otlp/http/trace/OtlpHttpSpanExporterOkHttpSenderTest.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,10 @@
2121
import javax.net.ssl.SSLContext;
2222
import javax.net.ssl.X509TrustManager;
2323

24-
class OtlpHttpSpanExporterTest extends AbstractHttpTelemetryExporterTest<SpanData, ResourceSpans> {
24+
class OtlpHttpSpanExporterOkHttpSenderTest
25+
extends AbstractHttpTelemetryExporterTest<SpanData, ResourceSpans> {
2526

26-
protected OtlpHttpSpanExporterTest() {
27+
protected OtlpHttpSpanExporterOkHttpSenderTest() {
2728
super("span", "/v1/traces", ResourceSpans.getDefaultInstance());
2829
}
2930

0 commit comments

Comments
 (0)