From 551ae8306e3f4412a07049a838908ec6875bff92 Mon Sep 17 00:00:00 2001 From: Peeters Tim EXT Date: Mon, 24 Jun 2024 11:46:49 +0200 Subject: [PATCH 1/8] Add auto-configuration for OTLP gRPC format. --- .../tracing/otlp/OtlpAutoConfiguration.java | 6 +++-- .../tracing/otlp/OtlpProperties.java | 27 +++++++++++++++++++ .../otlp/OtlpTracingConfigurations.java | 24 +++++++++++++++-- .../otlp/OtlpAutoConfigurationTests.java | 15 +++++++++++ 4 files changed, 68 insertions(+), 4 deletions(-) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpAutoConfiguration.java index abb3253f2f99..ac483f9f9802 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpAutoConfiguration.java @@ -36,8 +36,10 @@ * the future, see: opentelemetry-java#3651. * Because this class configures components from the OTel SDK, it can't support HTTP/JSON. - * To keep things simple, we only auto-configure HTTP/protobuf. If you want to use gRPC, - * define an {@link OtlpGrpcSpanExporter} and this auto-configuration will back off. + * By default, we auto-configure HTTP/protobuf. If you want to use gRPC, you need to set + * {@code management.otlp.tracing.exporter=grpc}. If you define a + * {@link OtlpHttpSpanExporter} or {@link OtlpGrpcSpanExporter}, this auto-configuration + * will back off. * * @author Jonatan Ivanov * @author Moritz Halbritter diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpProperties.java index 371de8491146..d4768335ad8b 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpProperties.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpProperties.java @@ -44,6 +44,11 @@ public class OtlpProperties { */ private Duration timeout = Duration.ofSeconds(10); + /** + * Transport used to send the spans. Defaults to HTTP. + */ + private Transport transport = Transport.HTTP; + /** * Method used to compress the payload. */ @@ -70,6 +75,14 @@ public void setTimeout(Duration timeout) { this.timeout = timeout; } + public Transport getExporter() { + return this.transport; + } + + public void setExporter(Transport transport) { + this.transport = transport; + } + public Compression getCompression() { return this.compression; } @@ -86,6 +99,20 @@ public void setHeaders(Map headers) { this.headers = headers; } + enum Transport { + + /** + * HTTP exporter. + */ + HTTP, + + /** + * gRPC exporter. + */ + GRPC + + } + enum Compression { /** diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpTracingConfigurations.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpTracingConfigurations.java index 142696717172..2925eab12a77 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpTracingConfigurations.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpTracingConfigurations.java @@ -20,6 +20,8 @@ import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter; import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporterBuilder; +import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter; +import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporterBuilder; import org.springframework.boot.actuate.autoconfigure.tracing.ConditionalOnEnabledTracing; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; @@ -69,10 +71,11 @@ public String getUrl() { static class Exporters { @Bean - @ConditionalOnMissingBean(value = OtlpHttpSpanExporter.class, - type = "io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter") + @ConditionalOnMissingBean({ OtlpGrpcSpanExporter.class, OtlpHttpSpanExporter.class }) @ConditionalOnBean(OtlpTracingConnectionDetails.class) @ConditionalOnEnabledTracing("otlp") + @ConditionalOnProperty(prefix = "management.otlp.tracing", name = "transport", havingValue = "http", + matchIfMissing = true) OtlpHttpSpanExporter otlpHttpSpanExporter(OtlpProperties properties, OtlpTracingConnectionDetails connectionDetails) { OtlpHttpSpanExporterBuilder builder = OtlpHttpSpanExporter.builder() @@ -85,6 +88,23 @@ OtlpHttpSpanExporter otlpHttpSpanExporter(OtlpProperties properties, return builder.build(); } + @Bean + @ConditionalOnMissingBean({ OtlpGrpcSpanExporter.class, OtlpHttpSpanExporter.class }) + @ConditionalOnBean(OtlpTracingConnectionDetails.class) + @ConditionalOnEnabledTracing("otlp") + @ConditionalOnProperty(prefix = "management.otlp.tracing", name = "transport", havingValue = "grpc") + OtlpGrpcSpanExporter otlpGrpcSpanExporter(OtlpProperties properties, + OtlpTracingConnectionDetails connectionDetails) { + OtlpGrpcSpanExporterBuilder builder = OtlpGrpcSpanExporter.builder() + .setEndpoint(connectionDetails.getUrl()) + .setTimeout(properties.getTimeout()) + .setCompression(properties.getCompression().name().toLowerCase()); + for (Entry header : properties.getHeaders().entrySet()) { + builder.addHeader(header.getKey(), header.getValue()); + } + return builder.build(); + } + } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpAutoConfigurationTests.java index eba8fe11251a..2f9259c6db71 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpAutoConfigurationTests.java @@ -51,6 +51,12 @@ void shouldNotSupplyBeansIfPropertyIsNotSet() { this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(OtlpHttpSpanExporter.class)); } + @Test + void shouldNotSupplyBeansIfGrpcTransportIsEnabledButPropertyIsNotSet() { + this.contextRunner.withPropertyValues("management.otlp.tracing.transport=grpc") + .run((context) -> assertThat(context).doesNotHaveBean(OtlpGrpcSpanExporter.class)); + } + @Test void shouldSupplyBeans() { this.contextRunner.withPropertyValues("management.otlp.tracing.endpoint=http://localhost:4318/v1/traces") @@ -58,6 +64,15 @@ void shouldSupplyBeans() { .hasSingleBean(SpanExporter.class)); } + @Test + void shouldSupplyBeansIfGrpcTransportIsEnabled() { + this.contextRunner + .withPropertyValues("management.otlp.tracing.endpoint=http://localhost:4317/v1/traces", + "management.otlp.tracing.transport=grpc") + .run((context) -> assertThat(context).hasSingleBean(OtlpGrpcSpanExporter.class) + .hasSingleBean(SpanExporter.class)); + } + @Test void shouldNotSupplyBeansIfGlobalTracingIsDisabled() { this.contextRunner.withPropertyValues("management.tracing.enabled=false") From 3c163dc3f3f64742c859c75cb3e618d67227201f Mon Sep 17 00:00:00 2001 From: Tim Peeters Date: Mon, 24 Jun 2024 15:16:03 +0200 Subject: [PATCH 2/8] Fix getter/setter for transport property. --- .../actuate/autoconfigure/tracing/otlp/OtlpProperties.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpProperties.java index d4768335ad8b..b6bd517a5bd7 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpProperties.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpProperties.java @@ -75,11 +75,11 @@ public void setTimeout(Duration timeout) { this.timeout = timeout; } - public Transport getExporter() { + public Transport getTransport() { return this.transport; } - public void setExporter(Transport transport) { + public void setTransport(Transport transport) { this.transport = transport; } From b25a674e455466e38a9531910eb60057fde21100 Mon Sep 17 00:00:00 2001 From: Tim Peeters Date: Mon, 24 Jun 2024 15:49:55 +0200 Subject: [PATCH 3/8] Format source code. --- .../tracing/otlp/OtlpTracingConfigurations.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpTracingConfigurations.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpTracingConfigurations.java index 2925eab12a77..95f0ec4a4afe 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpTracingConfigurations.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpTracingConfigurations.java @@ -96,9 +96,9 @@ OtlpHttpSpanExporter otlpHttpSpanExporter(OtlpProperties properties, OtlpGrpcSpanExporter otlpGrpcSpanExporter(OtlpProperties properties, OtlpTracingConnectionDetails connectionDetails) { OtlpGrpcSpanExporterBuilder builder = OtlpGrpcSpanExporter.builder() - .setEndpoint(connectionDetails.getUrl()) - .setTimeout(properties.getTimeout()) - .setCompression(properties.getCompression().name().toLowerCase()); + .setEndpoint(connectionDetails.getUrl()) + .setTimeout(properties.getTimeout()) + .setCompression(properties.getCompression().name().toLowerCase()); for (Entry header : properties.getHeaders().entrySet()) { builder.addHeader(header.getKey(), header.getValue()); } From 633e12127daace0be3cdc3aaad51c8bb48178e1a Mon Sep 17 00:00:00 2001 From: Tim Peeters Date: Mon, 24 Jun 2024 17:11:50 +0200 Subject: [PATCH 4/8] Fix property name in Javadoc. --- .../autoconfigure/tracing/otlp/OtlpAutoConfiguration.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpAutoConfiguration.java index ac483f9f9802..9d797721fb88 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpAutoConfiguration.java @@ -37,7 +37,7 @@ * "https://github.com/open-telemetry/opentelemetry-java/issues/3651">opentelemetry-java#3651. * Because this class configures components from the OTel SDK, it can't support HTTP/JSON. * By default, we auto-configure HTTP/protobuf. If you want to use gRPC, you need to set - * {@code management.otlp.tracing.exporter=grpc}. If you define a + * {@code management.otlp.tracing.transport=grpc}. If you define a * {@link OtlpHttpSpanExporter} or {@link OtlpGrpcSpanExporter}, this auto-configuration * will back off. * From bf4da7dff40781209289daf4b9a514c1ce5b40e5 Mon Sep 17 00:00:00 2001 From: Tim Peeters Date: Wed, 26 Jun 2024 11:49:41 +0200 Subject: [PATCH 5/8] Add integration test for OtlpGrpcSpanExporter. --- .../build.gradle | 1 + ...OtlpAutoConfigurationIntegrationTests.java | 61 ++++++++++++++++++- 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/build.gradle b/spring-boot-project/spring-boot-actuator-autoconfigure/build.gradle index 3b4e685872b0..bb7ba979b089 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/build.gradle +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/build.gradle @@ -141,6 +141,7 @@ dependencies { testImplementation(project(":spring-boot-project:spring-boot-test")) testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support")) testImplementation("io.micrometer:micrometer-observation-test") + testImplementation("io.opentelemetry.proto:opentelemetry-proto:1.2.0-alpha") testImplementation("io.projectreactor:reactor-test") testImplementation("io.prometheus:prometheus-metrics-exposition-formats") testImplementation("io.r2dbc:r2dbc-h2") diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpAutoConfigurationIntegrationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpAutoConfigurationIntegrationTests.java index 0fcc77811f95..68d5da3d664b 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpAutoConfigurationIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpAutoConfigurationIntegrationTests.java @@ -18,10 +18,20 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; +import io.grpc.Server; +import io.grpc.ServerBuilder; +import io.grpc.stub.StreamObserver; import io.micrometer.tracing.Tracer; +import io.opentelemetry.api.trace.Span; import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter; +import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter; +import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest; +import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceResponse; +import io.opentelemetry.proto.collector.trace.v1.TraceServiceGrpc; import io.opentelemetry.sdk.common.CompletableResultCode; import io.opentelemetry.sdk.trace.export.SpanExporter; import okhttp3.mockwebserver.MockResponse; @@ -57,16 +67,29 @@ class OtlpAutoConfigurationIntegrationTests { private final MockWebServer mockWebServer = new MockWebServer(); + private final MockGrpcService mockGrpcService = new MockGrpcService(); + private final Server mockGrpcServer = ServerBuilder.forPort(0).addService(this.mockGrpcService).build(); + @BeforeEach - void setUp() throws IOException { + void startMockWebServer() throws IOException { this.mockWebServer.start(); } + @BeforeEach + void startMockGrpcServer() throws IOException { + this.mockGrpcServer.start(); + } + @AfterEach - void tearDown() throws IOException { + void stopMockWebServer() throws IOException { this.mockWebServer.close(); } + @AfterEach + void stopMockGrpcServer() throws InterruptedException { + this.mockGrpcServer.shutdown().awaitTermination(10, TimeUnit.SECONDS); + } + @Test void httpSpanExporterShouldUseProtobufAndNoCompressionByDefault() { this.mockWebServer.enqueue(new MockResponse()); @@ -113,4 +136,38 @@ void httpSpanExporterCanBeConfiguredToUseGzipCompression() { }); } + @Test + void grpcSpanExporter() { + this.contextRunner + .withPropertyValues( + "management.otlp.tracing.endpoint=http://localhost:%d".formatted(this.mockGrpcServer.getPort()), + "management.otlp.tracing.transport=grpc") + .run((context) -> { + context.getBean(Tracer.class).nextSpan().name("test").end(); + assertThat(context.getBean(OtlpGrpcSpanExporter.class).flush()) + .isSameAs(CompletableResultCode.ofSuccess()); + ExportTraceServiceRequest request = this.mockGrpcService.takeRequest(10, TimeUnit.SECONDS); + assertThat(request).isNotNull(); + assertThat(request.getResourceSpansCount()).isEqualTo(1); + }); + } + + private static final class MockGrpcService extends TraceServiceGrpc.TraceServiceImplBase { + + private final BlockingQueue recordedRequests = new LinkedBlockingQueue<>(); + + @Override + public void export(ExportTraceServiceRequest request, + StreamObserver responseObserver) { + this.recordedRequests.add(request); + responseObserver.onNext(ExportTraceServiceResponse.getDefaultInstance()); + responseObserver.onCompleted(); + } + + private ExportTraceServiceRequest takeRequest(int timeout, TimeUnit unit) throws InterruptedException { + return this.recordedRequests.poll(timeout, unit); + } + + } + } From 008b8a3fd35ec4eb47715fccd8b1f483a7c05936 Mon Sep 17 00:00:00 2001 From: Tim Peeters Date: Thu, 27 Jun 2024 10:00:05 +0200 Subject: [PATCH 6/8] Format. --- .../tracing/otlp/OtlpAutoConfigurationIntegrationTests.java | 1 + 1 file changed, 1 insertion(+) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpAutoConfigurationIntegrationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpAutoConfigurationIntegrationTests.java index 68d5da3d664b..2ebcee220495 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpAutoConfigurationIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpAutoConfigurationIntegrationTests.java @@ -68,6 +68,7 @@ class OtlpAutoConfigurationIntegrationTests { private final MockWebServer mockWebServer = new MockWebServer(); private final MockGrpcService mockGrpcService = new MockGrpcService(); + private final Server mockGrpcServer = ServerBuilder.forPort(0).addService(this.mockGrpcService).build(); @BeforeEach From d1ee0a6a4ff3e4c081a3d625a65c5d8d65dbda77 Mon Sep 17 00:00:00 2001 From: Tim Peeters Date: Thu, 27 Jun 2024 12:54:20 +0200 Subject: [PATCH 7/8] Remove unused import. --- .../tracing/otlp/OtlpAutoConfigurationIntegrationTests.java | 1 - 1 file changed, 1 deletion(-) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpAutoConfigurationIntegrationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpAutoConfigurationIntegrationTests.java index 2ebcee220495..859799aff84c 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpAutoConfigurationIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpAutoConfigurationIntegrationTests.java @@ -26,7 +26,6 @@ import io.grpc.ServerBuilder; import io.grpc.stub.StreamObserver; import io.micrometer.tracing.Tracer; -import io.opentelemetry.api.trace.Span; import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter; import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter; import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest; From ddddd0ccb6f5cf21a5bb62a0aa68ac0992f4efd0 Mon Sep 17 00:00:00 2001 From: Tim Peeters Date: Mon, 1 Jul 2024 14:03:49 +0200 Subject: [PATCH 8/8] Replace gRPC server with Jetty server. --- .../build.gradle | 2 +- ...OtlpAutoConfigurationIntegrationTests.java | 103 ++++++++++++++---- 2 files changed, 80 insertions(+), 25 deletions(-) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/build.gradle b/spring-boot-project/spring-boot-actuator-autoconfigure/build.gradle index bb7ba979b089..533adfbb10d6 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/build.gradle +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/build.gradle @@ -141,7 +141,6 @@ dependencies { testImplementation(project(":spring-boot-project:spring-boot-test")) testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support")) testImplementation("io.micrometer:micrometer-observation-test") - testImplementation("io.opentelemetry.proto:opentelemetry-proto:1.2.0-alpha") testImplementation("io.projectreactor:reactor-test") testImplementation("io.prometheus:prometheus-metrics-exposition-formats") testImplementation("io.r2dbc:r2dbc-h2") @@ -162,6 +161,7 @@ dependencies { testImplementation("org.awaitility:awaitility") testImplementation("org.cache2k:cache2k-api") testImplementation("org.eclipse.jetty.ee10:jetty-ee10-webapp") + testImplementation("org.eclipse.jetty.http2:jetty-http2-server") testImplementation("org.glassfish.jersey.ext:jersey-spring6") testImplementation("org.glassfish.jersey.media:jersey-media-json-jackson") testImplementation("org.hamcrest:hamcrest") diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpAutoConfigurationIntegrationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpAutoConfigurationIntegrationTests.java index 859799aff84c..4f001a499380 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpAutoConfigurationIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpAutoConfigurationIntegrationTests.java @@ -17,20 +17,15 @@ package org.springframework.boot.actuate.autoconfigure.tracing.otlp; import java.io.IOException; +import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; -import io.grpc.Server; -import io.grpc.ServerBuilder; -import io.grpc.stub.StreamObserver; import io.micrometer.tracing.Tracer; import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter; import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter; -import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest; -import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceResponse; -import io.opentelemetry.proto.collector.trace.v1.TraceServiceGrpc; import io.opentelemetry.sdk.common.CompletableResultCode; import io.opentelemetry.sdk.trace.export.SpanExporter; import okhttp3.mockwebserver.MockResponse; @@ -38,6 +33,16 @@ import okhttp3.mockwebserver.RecordedRequest; import okio.Buffer; import okio.GzipSource; +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http2.server.HTTP2CServerConnectionFactory; +import org.eclipse.jetty.io.Content; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.util.Callback; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -45,8 +50,10 @@ import org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.opentelemetry.OpenTelemetryAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.tracing.MicrometerTracingAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.tracing.otlp.OtlpAutoConfigurationIntegrationTests.MockGrpcServer.RecordedGrpcRequest; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.util.StreamUtils; import static org.assertj.core.api.Assertions.assertThat; @@ -66,9 +73,7 @@ class OtlpAutoConfigurationIntegrationTests { private final MockWebServer mockWebServer = new MockWebServer(); - private final MockGrpcService mockGrpcService = new MockGrpcService(); - - private final Server mockGrpcServer = ServerBuilder.forPort(0).addService(this.mockGrpcService).build(); + private final MockGrpcServer mockGrpcServer = new MockGrpcServer(); @BeforeEach void startMockWebServer() throws IOException { @@ -76,7 +81,7 @@ void startMockWebServer() throws IOException { } @BeforeEach - void startMockGrpcServer() throws IOException { + void startMockGrpcServer() throws Exception { this.mockGrpcServer.start(); } @@ -86,8 +91,8 @@ void stopMockWebServer() throws IOException { } @AfterEach - void stopMockGrpcServer() throws InterruptedException { - this.mockGrpcServer.shutdown().awaitTermination(10, TimeUnit.SECONDS); + void stopMockGrpcServer() throws Exception { + this.mockGrpcServer.stop(); } @Test @@ -141,33 +146,83 @@ void grpcSpanExporter() { this.contextRunner .withPropertyValues( "management.otlp.tracing.endpoint=http://localhost:%d".formatted(this.mockGrpcServer.getPort()), - "management.otlp.tracing.transport=grpc") + "management.otlp.tracing.headers.custom=42", "management.otlp.tracing.transport=grpc") .run((context) -> { context.getBean(Tracer.class).nextSpan().name("test").end(); assertThat(context.getBean(OtlpGrpcSpanExporter.class).flush()) .isSameAs(CompletableResultCode.ofSuccess()); - ExportTraceServiceRequest request = this.mockGrpcService.takeRequest(10, TimeUnit.SECONDS); + RecordedGrpcRequest request = this.mockGrpcServer.takeRequest(10, TimeUnit.SECONDS); assertThat(request).isNotNull(); - assertThat(request.getResourceSpansCount()).isEqualTo(1); + assertThat(request.headers().get("Content-Type")).isEqualTo("application/grpc"); + assertThat(request.headers().get("custom")).isEqualTo("42"); + assertThat(request.body()).contains("org.springframework.boot"); }); } - private static final class MockGrpcService extends TraceServiceGrpc.TraceServiceImplBase { + static class MockGrpcServer { - private final BlockingQueue recordedRequests = new LinkedBlockingQueue<>(); + private final Server server = createServer(); - @Override - public void export(ExportTraceServiceRequest request, - StreamObserver responseObserver) { - this.recordedRequests.add(request); - responseObserver.onNext(ExportTraceServiceResponse.getDefaultInstance()); - responseObserver.onCompleted(); + private final BlockingQueue recordedRequests = new LinkedBlockingQueue<>(); + + void start() throws Exception { + this.server.start(); + } + + void stop() throws Exception { + this.server.stop(); + } + + int getPort() { + return this.server.getURI().getPort(); } - private ExportTraceServiceRequest takeRequest(int timeout, TimeUnit unit) throws InterruptedException { + RecordedGrpcRequest takeRequest(int timeout, TimeUnit unit) throws InterruptedException { return this.recordedRequests.poll(timeout, unit); } + void recordRequest(RecordedGrpcRequest request) { + this.recordedRequests.add(request); + } + + private Server createServer() { + Server server = new Server(); + server.addConnector(createConnector(server)); + server.setHandler(new GrpcHandler()); + + return server; + } + + private ServerConnector createConnector(Server server) { + ServerConnector connector = new ServerConnector(server, + new HTTP2CServerConnectionFactory(new HttpConfiguration())); + connector.setPort(0); + + return connector; + } + + class GrpcHandler extends Handler.Abstract { + + @Override + public boolean handle(Request request, Response response, Callback callback) throws Exception { + try (InputStream in = Content.Source.asInputStream(request)) { + recordRequest(new RecordedGrpcRequest(request.getHeaders(), + StreamUtils.copyToString(in, StandardCharsets.UTF_8))); + } + + response.getHeaders().add("Content-Type", "application/grpc"); + response.getHeaders().add("Grpc-Status", "0"); + + callback.succeeded(); + + return true; + } + + } + + record RecordedGrpcRequest(HttpFields headers, String body) { + } + } }