Skip to content

Commit 8cf2217

Browse files
authored
add support for webflux server in spring starter (#11185)
1 parent 483387d commit 8cf2217

File tree

4 files changed

+68
-5
lines changed

4 files changed

+68
-5
lines changed

instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/instrumentation/webflux/SpringWebfluxInstrumentationAutoConfiguration.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import io.opentelemetry.api.OpenTelemetry;
99
import io.opentelemetry.instrumentation.spring.autoconfigure.internal.SdkEnabled;
10+
import io.opentelemetry.instrumentation.spring.webflux.v5_3.SpringWebfluxTelemetry;
1011
import org.springframework.beans.factory.ObjectProvider;
1112
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
1213
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
@@ -15,6 +16,7 @@
1516
import org.springframework.context.annotation.Conditional;
1617
import org.springframework.context.annotation.Configuration;
1718
import org.springframework.web.reactive.function.client.WebClient;
19+
import org.springframework.web.server.WebFilter;
1820

1921
/**
2022
* Configures {@link WebClient} for tracing.
@@ -36,4 +38,11 @@ static WebClientBeanPostProcessor otelWebClientBeanPostProcessor(
3638
ObjectProvider<OpenTelemetry> openTelemetryProvider) {
3739
return new WebClientBeanPostProcessor(openTelemetryProvider);
3840
}
41+
42+
@Bean
43+
WebFilter telemetryFilter(OpenTelemetry openTelemetry) {
44+
return SpringWebfluxTelemetry.builder(openTelemetry)
45+
.build()
46+
.createWebFilterAndRegisterReactorHook();
47+
}
3948
}

smoke-tests-otel-starter/build.gradle.kts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,17 @@ otelJava {
1313
dependencies {
1414
implementation("org.springframework.boot:spring-boot-starter-web")
1515
implementation("org.springframework.boot:spring-boot-starter-data-jdbc")
16-
implementation("com.h2database:h2")
16+
runtimeOnly("com.h2database:h2")
1717
implementation("org.apache.commons:commons-dbcp2")
1818
implementation(project(":instrumentation:jdbc:library"))
1919
implementation("org.springframework.kafka:spring-kafka") // not tested here, just make sure there are no warnings when it's included
20-
implementation("org.springframework.boot:spring-boot-starter-webflux") // not tested here, just make sure there are no warnings when it's included
2120
implementation("io.opentelemetry:opentelemetry-extension-trace-propagators")
2221
implementation(project(":instrumentation:spring:starters:spring-boot-starter"))
2322
implementation(platform(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES))
2423

24+
// webflux / reactive style
25+
implementation("org.springframework.boot:spring-boot-starter-webflux")
26+
2527
testImplementation("org.springframework.boot:spring-boot-starter-test")
2628
testImplementation(project(":testing-common"))
2729
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.spring.smoketest;
7+
8+
import org.springframework.web.bind.annotation.GetMapping;
9+
import org.springframework.web.bind.annotation.RestController;
10+
import reactor.core.publisher.Mono;
11+
12+
@RestController
13+
public class OtelSpringStarterWebfluxSmokeTestController {
14+
15+
public static final String WEBFLUX = "/webflux";
16+
17+
@GetMapping(WEBFLUX)
18+
public Mono<String> getStock() {
19+
return Mono.just("pong");
20+
}
21+
}

smoke-tests-otel-starter/src/test/java/io/opentelemetry/smoketest/OtelSpringStarterSmokeTest.java

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import io.opentelemetry.semconv.incubating.ServiceIncubatingAttributes;
4242
import io.opentelemetry.spring.smoketest.OtelSpringStarterSmokeTestApplication;
4343
import io.opentelemetry.spring.smoketest.OtelSpringStarterSmokeTestController;
44+
import io.opentelemetry.spring.smoketest.OtelSpringStarterWebfluxSmokeTestController;
4445
import java.time.Duration;
4546
import java.util.Collections;
4647
import java.util.List;
@@ -65,6 +66,13 @@
6566
import org.springframework.core.annotation.Order;
6667
import org.springframework.core.env.Environment;
6768

69+
/**
70+
* This test class enforces the order of the tests to make sure that {@link #shouldSendTelemetry()},
71+
* which asserts the telemetry data from the application startup, is executed first.
72+
*
73+
* <p>The exporters are not reset using {@link org.junit.jupiter.api.BeforeEach}, because it would
74+
* prevent the telemetry data from the application startup to be asserted.
75+
*/
6876
@ExtendWith(OutputCaptureExtension.class)
6977
@SpringBootTest(
7078
classes = {
@@ -187,7 +195,6 @@ void tearDown(CapturedOutput output) {
187195
}
188196

189197
@Test
190-
@org.junit.jupiter.api.Order(10)
191198
void propertyConversion() {
192199
ConfigProperties configProperties =
193200
SpringConfigProperties.create(
@@ -268,7 +275,6 @@ void shouldSendTelemetry() {
268275
}
269276

270277
@Test
271-
@org.junit.jupiter.api.Order(2)
272278
void restTemplate() {
273279
assertClient(OtelSpringStarterSmokeTestController.REST_TEMPLATE);
274280
}
@@ -279,7 +285,7 @@ void restClient() {
279285
}
280286

281287
private void assertClient(String url) {
282-
resetExporters(); // ignore the telemetry from application startup
288+
resetExporters();
283289

284290
testRestTemplate.getForObject(url, String.class);
285291

@@ -307,6 +313,31 @@ private void assertClient(String url) {
307313
.hasAttribute(HttpAttributes.HTTP_ROUTE, "/ping")));
308314
}
309315

316+
@Test
317+
void webflux() {
318+
resetExporters();
319+
320+
testRestTemplate.getForObject(
321+
OtelSpringStarterWebfluxSmokeTestController.WEBFLUX, String.class);
322+
323+
TracesAssert.assertThat(expectSpans(2))
324+
.hasTracesSatisfyingExactly(
325+
traceAssert ->
326+
traceAssert.hasSpansSatisfyingExactly(
327+
clientSpan ->
328+
clientSpan
329+
.hasKind(SpanKind.CLIENT)
330+
.hasAttributesSatisfying(
331+
a ->
332+
assertThat(a.get(UrlAttributes.URL_FULL)).endsWith("/webflux")),
333+
serverSpan ->
334+
serverSpan
335+
.hasKind(SpanKind.SERVER)
336+
.hasAttribute(HttpAttributes.HTTP_REQUEST_METHOD, "GET")
337+
.hasAttribute(HttpAttributes.HTTP_RESPONSE_STATUS_CODE, 200L)
338+
.hasAttribute(HttpAttributes.HTTP_ROUTE, "/webflux")));
339+
}
340+
310341
private static List<SpanData> expectSpans(int spans) {
311342
with()
312343
.conditionEvaluationListener(

0 commit comments

Comments
 (0)