diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index cb242b3257..a8f0a5f382 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -35,6 +35,7 @@ Use subheadings with the "=====" level for adding notes for unreleased changes: ===== Features * Added support for OpenTelemetry annotations - `WithSpan` and `SpanAttribute` - {pull}3406[#3406] * Only automatically apply redacted exceptions for Corretto JVM 17-20. Outside that, user should use capture_exception_details=false to workaround the JVM race-condition bug if it gets triggered: {pull}3438[#3438] +* Added support for Spring 6.1 / Spring-Boot 3.2 - {pull}3440[#3440] [[release-notes-1.x]] === Java Agent version 1.x diff --git a/apm-agent-plugins/apm-spring-webflux/apm-spring-webclient-plugin/pom.xml b/apm-agent-plugins/apm-spring-webflux/apm-spring-webclient-plugin/pom.xml index 5471808cd5..c9f3638827 100755 --- a/apm-agent-plugins/apm-spring-webflux/apm-spring-webclient-plugin/pom.xml +++ b/apm-agent-plugins/apm-spring-webflux/apm-spring-webclient-plugin/pom.xml @@ -38,6 +38,11 @@ apm-httpclient-core ${project.version} + + ${project.groupId} + apm-spring-webflux-common + ${project.version} + org.springframework spring-web @@ -60,6 +65,11 @@ spring-webflux provided + + org.springframework + spring-context + provided + ${project.groupId} diff --git a/apm-agent-plugins/apm-spring-webflux/apm-spring-webclient-plugin/src/main/java/co/elastic/apm/agent/springwebclient/WebClientSubscriber.java b/apm-agent-plugins/apm-spring-webflux/apm-spring-webclient-plugin/src/main/java/co/elastic/apm/agent/springwebclient/WebClientSubscriber.java index e86d0c750e..ed000ca409 100755 --- a/apm-agent-plugins/apm-spring-webflux/apm-spring-webclient-plugin/src/main/java/co/elastic/apm/agent/springwebclient/WebClientSubscriber.java +++ b/apm-agent-plugins/apm-spring-webflux/apm-spring-webclient-plugin/src/main/java/co/elastic/apm/agent/springwebclient/WebClientSubscriber.java @@ -23,6 +23,7 @@ import co.elastic.apm.agent.tracer.Span; import co.elastic.apm.agent.tracer.Tracer; import co.elastic.apm.agent.tracer.reference.ReferenceCountedMap; +import co.elastic.apm.agent.webfluxcommon.SpringWebVersionUtils; import org.reactivestreams.Subscription; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -74,14 +75,15 @@ public void onNext(T t) { try { if (span != null && t instanceof ClientResponse) { ClientResponse clientResponse = (ClientResponse) t; - int statusCode = clientResponse.rawStatusCode(); + int statusCode = SpringWebVersionUtils.getClientStatusCode(clientResponse); span.withOutcome(ResultUtil.getOutcomeByHttpClientStatus(statusCode)); span.getContext().getHttp().withStatusCode(statusCode); } subscriber.onNext(t); } catch (Throwable e) { thrown = e; - throw e; + // Since spring 6.1 we can't throw checked exceptions here anymore, so we have to use this trick + throwException(e); } finally { doExit(hasActivated, "onNext", span); discardIf(thrown != null); @@ -193,4 +195,17 @@ private void cancelSpan() { } } + @SuppressWarnings("unchecked") + private static void throwException(Throwable exception, Object dummy) throws T { + throw (T) exception; + } + + /** + * Utility method for throwing a checked exception like an unchecked one. + */ + private static void throwException(Throwable exception) { + throwException(exception, null); + } + + } diff --git a/apm-agent-plugins/apm-spring-webflux/apm-spring-webclient-plugin/src/main/java/co/elastic/apm/agent/springwebclient/WebclientPluginRootPackageCustomizer.java b/apm-agent-plugins/apm-spring-webflux/apm-spring-webclient-plugin/src/main/java/co/elastic/apm/agent/springwebclient/WebclientPluginRootPackageCustomizer.java new file mode 100644 index 0000000000..9b2837d1b8 --- /dev/null +++ b/apm-agent-plugins/apm-spring-webflux/apm-spring-webclient-plugin/src/main/java/co/elastic/apm/agent/springwebclient/WebclientPluginRootPackageCustomizer.java @@ -0,0 +1,31 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you 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 co.elastic.apm.agent.springwebclient; + +import co.elastic.apm.agent.sdk.internal.PluginClassLoaderRootPackageCustomizer; + +import java.util.Arrays; +import java.util.Collection; + +public class WebclientPluginRootPackageCustomizer extends PluginClassLoaderRootPackageCustomizer { + @Override + public Collection pluginClassLoaderRootPackages() { + return Arrays.asList(getPluginPackage(), "co.elastic.apm.agent.webfluxcommon"); + } +} diff --git a/apm-agent-plugins/apm-spring-webflux/apm-spring-webclient-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.internal.PluginClassLoaderRootPackageCustomizer b/apm-agent-plugins/apm-spring-webflux/apm-spring-webclient-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.internal.PluginClassLoaderRootPackageCustomizer new file mode 100644 index 0000000000..98bd77b6fc --- /dev/null +++ b/apm-agent-plugins/apm-spring-webflux/apm-spring-webclient-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.internal.PluginClassLoaderRootPackageCustomizer @@ -0,0 +1 @@ +co.elastic.apm.agent.springwebclient.WebclientPluginRootPackageCustomizer diff --git a/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-common-spring5/pom.xml b/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-common-spring5/pom.xml new file mode 100644 index 0000000000..1b9121cc87 --- /dev/null +++ b/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-common-spring5/pom.xml @@ -0,0 +1,41 @@ + + + 4.0.0 + + co.elastic.apm + apm-spring-webflux + 1.44.1-SNAPSHOT + + + apm-spring-webflux-common-spring5 + ${project.groupId}:${project.artifactId} + + + + ${project.basedir}/../../.. + true + + + + + + org.springframework.boot + spring-boot-dependencies + ${version.spring-boot-2} + pom + import + + + + + + + org.springframework + spring-webflux + provided + + + + diff --git a/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-spring5/src/main/java/co/elastic/apm/agent/springwebflux/SpringWeb5Utils.java b/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-common-spring5/src/main/java/co/elastic/apm/agent/webfluxcommon/SpringWeb5Utils.java similarity index 82% rename from apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-spring5/src/main/java/co/elastic/apm/agent/springwebflux/SpringWeb5Utils.java rename to apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-common-spring5/src/main/java/co/elastic/apm/agent/webfluxcommon/SpringWeb5Utils.java index a649c79b7f..39c303104a 100644 --- a/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-spring5/src/main/java/co/elastic/apm/agent/springwebflux/SpringWeb5Utils.java +++ b/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-common-spring5/src/main/java/co/elastic/apm/agent/webfluxcommon/SpringWeb5Utils.java @@ -16,10 +16,11 @@ * specific language governing permissions and limitations * under the License. */ -package co.elastic.apm.agent.springwebflux; +package co.elastic.apm.agent.webfluxcommon; import org.springframework.http.HttpStatus; import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.web.reactive.function.client.ClientResponse; /** * This class is compiled with spring-web 5.x, relying on the {@link ServerHttpResponse#getStatusCode()}, which changed in 6.0.0. @@ -29,8 +30,13 @@ public class SpringWeb5Utils implements SpringWebVersionUtils.ISpringWebVersionUtils { @Override - public int getStatusCode(Object response) { + public int getServerStatusCode(Object response) { HttpStatus statusCode = ((ServerHttpResponse) response).getStatusCode(); return statusCode != null ? statusCode.value() : 200; } + + @Override + public int getClientStatusCode(Object response) { + return ((ClientResponse) response).rawStatusCode(); + } } diff --git a/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-spring5/src/main/java/co/elastic/apm/agent/springwebflux/SpringWebVersionUtils.java b/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-common-spring5/src/main/java/co/elastic/apm/agent/webfluxcommon/SpringWebVersionUtils.java similarity index 55% rename from apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-spring5/src/main/java/co/elastic/apm/agent/springwebflux/SpringWebVersionUtils.java rename to apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-common-spring5/src/main/java/co/elastic/apm/agent/webfluxcommon/SpringWebVersionUtils.java index 2451029042..2a47e026b7 100644 --- a/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-spring5/src/main/java/co/elastic/apm/agent/springwebflux/SpringWebVersionUtils.java +++ b/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-common-spring5/src/main/java/co/elastic/apm/agent/webfluxcommon/SpringWebVersionUtils.java @@ -16,7 +16,9 @@ * specific language governing permissions and limitations * under the License. */ -package co.elastic.apm.agent.springwebflux; +package co.elastic.apm.agent.webfluxcommon; + +import org.springframework.web.reactive.function.server.ServerResponse; import javax.annotation.Nullable; @@ -27,8 +29,8 @@ */ public class SpringWebVersionUtils { - private static final String SPRING_WEB_5_UTILS_CLASS_NAME = "co.elastic.apm.agent.springwebflux.SpringWeb5Utils"; - private static final String SPRING_WEB_6_UTILS_CLASS_NAME = "co.elastic.apm.agent.springwebflux.SpringWeb6Utils"; + private static final String SPRING_WEB_5_UTILS_CLASS_NAME = "co.elastic.apm.agent.webfluxcommon.SpringWeb5Utils"; + private static final String SPRING_WEB_6_UTILS_CLASS_NAME = "co.elastic.apm.agent.webfluxcommon.SpringWeb6Utils"; @Nullable private static ISpringWebVersionUtils instance = null; @@ -40,21 +42,16 @@ private static synchronized void initialize() throws Exception { return; } try { - // check if using spring 6.0.0 or higher - Class.forName("org.springframework.http.HttpStatusCode"); - try { - // loading class by name so to avoid linkage attempt when spring-web 6 is unavailable - instance = (ISpringWebVersionUtils) Class.forName(SPRING_WEB_6_UTILS_CLASS_NAME).getDeclaredConstructor().newInstance(); - } catch (Exception e) { - throw new IllegalStateException("Spring-web 6.x+ identified, but failed to load related utility class", e); - } - } catch (ClassNotFoundException ignored) { - // assuming spring-web < 6.x - try { - // loading class by name so to avoid linkage attempt on spring-web 6, where the getStatusCode API has changed - instance = (ISpringWebVersionUtils) Class.forName(SPRING_WEB_5_UTILS_CLASS_NAME).getDeclaredConstructor().newInstance(); - } catch (Exception e) { - throw new IllegalStateException("Spring-web < 6.x identified, but failed to load related utility class", e); + Class statusCodeType = ServerResponse.class.getMethod("statusCode").getReturnType(); + switch (statusCodeType.getName()) { + case "org.springframework.http.HttpStatusCode": // spring 6.0.0 or higher + instance = (ISpringWebVersionUtils) Class.forName(SPRING_WEB_6_UTILS_CLASS_NAME).getDeclaredConstructor().newInstance(); + break; + case "org.springframework.http.HttpStatus": // spring < 6 + instance = (ISpringWebVersionUtils) Class.forName(SPRING_WEB_5_UTILS_CLASS_NAME).getDeclaredConstructor().newInstance(); + break; + default: + throw new IllegalStateException("Unexpected type: "+statusCodeType.getName()); } } finally { initialized = true; @@ -77,10 +74,27 @@ private static ISpringWebVersionUtils getImplementation() throws Exception { * expected * @return the status code of the provided response */ - public static int getStatusCode(Object response) throws Exception { + public static int getServerStatusCode(Object response) throws Exception { + ISpringWebVersionUtils implementation = getImplementation(); + if (implementation != null) { + return implementation.getServerStatusCode(response); + } + return 200; + } + + + /** + * A utility method to get the status code of a {@code org.springframework.web.reactive.function.client.ClientResponse} from any version + * of Spring framework. + * + * @param response must be of type {@code org.springframework.web.reactive.function.client.ClientResponse}, otherwise an Exception is + * expected + * @return the status code of the provided response + */ + public static int getClientStatusCode(Object response) throws Exception { ISpringWebVersionUtils implementation = getImplementation(); if (implementation != null) { - return implementation.getStatusCode(response); + return implementation.getClientStatusCode(response); } return 200; } @@ -93,6 +107,16 @@ public interface ISpringWebVersionUtils { * @param response must be of type {@code org.springframework.http.server.reactive.ServerHttpResponse} * @return the corresponding status code */ - int getStatusCode(Object response); + int getServerStatusCode(Object response); + + /** + * A utility method to get the status code of a {@code org.springframework.web.reactive.function.client.ClientResponse} from any version + * of Spring framework. + * + * @param response must be of type {@code org.springframework.web.reactive.function.client.ClientResponse} + * @return the corresponding status code + */ + int getClientStatusCode(Object response); + } } diff --git a/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-spring5/src/test/java/co/elastic/apm/agent/springwebflux/SpringWeb5UtilsTest.java b/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-common-spring5/src/test/java/co/elastic/apm/agent/webfluxcommon/SpringWeb5UtilsTest.java similarity index 67% rename from apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-spring5/src/test/java/co/elastic/apm/agent/springwebflux/SpringWeb5UtilsTest.java rename to apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-common-spring5/src/test/java/co/elastic/apm/agent/webfluxcommon/SpringWeb5UtilsTest.java index f0fb189b42..5d0e524d2d 100644 --- a/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-spring5/src/test/java/co/elastic/apm/agent/springwebflux/SpringWeb5UtilsTest.java +++ b/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-common-spring5/src/test/java/co/elastic/apm/agent/webfluxcommon/SpringWeb5UtilsTest.java @@ -16,11 +16,12 @@ * specific language governing permissions and limitations * under the License. */ -package co.elastic.apm.agent.springwebflux; +package co.elastic.apm.agent.webfluxcommon; import org.junit.jupiter.api.Test; import org.springframework.http.HttpStatus; import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.web.reactive.function.client.ClientResponse; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -30,14 +31,21 @@ class SpringWeb5UtilsTest { @Test - void testGetStatusCode() throws Exception { + void testGetServerStatusCode() throws Exception { ServerHttpResponse mockResponse = mock(ServerHttpResponse.class); doReturn(HttpStatus.IM_USED).when(mockResponse).getStatusCode(); - assertThat(SpringWebVersionUtils.getStatusCode(mockResponse)).isEqualTo(226); + assertThat(SpringWebVersionUtils.getServerStatusCode(mockResponse)).isEqualTo(226); } @Test void testWrongResponseType() { - assertThatThrownBy(() -> SpringWebVersionUtils.getStatusCode(new Object())).isInstanceOf(ClassCastException.class); + assertThatThrownBy(() -> SpringWebVersionUtils.getServerStatusCode(new Object())).isInstanceOf(ClassCastException.class); + } + + @Test + void testGetClientStatusCode() throws Exception { + ClientResponse mockResponse = mock(ClientResponse.class); + doReturn(226).when(mockResponse).rawStatusCode(); + assertThat(SpringWebVersionUtils.getClientStatusCode(mockResponse)).isEqualTo(226); } } diff --git a/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-common/pom.xml b/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-common/pom.xml new file mode 100644 index 0000000000..3ee1c8ab58 --- /dev/null +++ b/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-common/pom.xml @@ -0,0 +1,46 @@ + + + 4.0.0 + + co.elastic.apm + apm-spring-webflux + 1.44.1-SNAPSHOT + + + apm-spring-webflux-common + ${project.groupId}:${project.artifactId} + + + + ${project.basedir}/../../.. + true + + + + + + org.springframework.boot + spring-boot-dependencies + ${version.spring-boot-3} + pom + import + + + + + + + ${project.groupId} + apm-spring-webflux-common-spring5 + ${project.version} + + + org.springframework + spring-webflux + provided + + + + diff --git a/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-plugin/src/main/java/co/elastic/apm/agent/springwebflux/SpringWeb6Utils.java b/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-common/src/main/java/co/elastic/apm/agent/webfluxcommon/SpringWeb6Utils.java similarity index 80% rename from apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-plugin/src/main/java/co/elastic/apm/agent/springwebflux/SpringWeb6Utils.java rename to apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-common/src/main/java/co/elastic/apm/agent/webfluxcommon/SpringWeb6Utils.java index 3029363ef2..5792bfe450 100644 --- a/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-plugin/src/main/java/co/elastic/apm/agent/springwebflux/SpringWeb6Utils.java +++ b/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-common/src/main/java/co/elastic/apm/agent/webfluxcommon/SpringWeb6Utils.java @@ -16,10 +16,12 @@ * specific language governing permissions and limitations * under the License. */ -package co.elastic.apm.agent.springwebflux; +package co.elastic.apm.agent.webfluxcommon; + import org.springframework.http.HttpStatusCode; import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.web.reactive.function.client.ClientResponse; /** * This class is compiled with spring-web 6.x, as it relies on {@link HttpStatusCode} and an API that was introduced in 6.0.0. @@ -28,8 +30,14 @@ @SuppressWarnings("unused") //Created via reflection public class SpringWeb6Utils implements SpringWebVersionUtils.ISpringWebVersionUtils { @Override - public int getStatusCode(Object response) { + public int getServerStatusCode(Object response) { HttpStatusCode statusCode = ((ServerHttpResponse) response).getStatusCode(); return statusCode != null ? statusCode.value() : 200; } + + @Override + public int getClientStatusCode(Object response) { + HttpStatusCode statusCode = ((ClientResponse) response).statusCode(); + return statusCode.value(); + } } diff --git a/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-plugin/src/test/java/co/elastic/apm/agent/springwebflux/SpringWeb6UtilsTest.java b/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-common/src/test/java/co/elastic/apm/agent/webfluxcommon/SpringWeb6UtilsTest.java similarity index 67% rename from apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-plugin/src/test/java/co/elastic/apm/agent/springwebflux/SpringWeb6UtilsTest.java rename to apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-common/src/test/java/co/elastic/apm/agent/webfluxcommon/SpringWeb6UtilsTest.java index dfc6060b4e..71c910202c 100644 --- a/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-plugin/src/test/java/co/elastic/apm/agent/springwebflux/SpringWeb6UtilsTest.java +++ b/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-common/src/test/java/co/elastic/apm/agent/webfluxcommon/SpringWeb6UtilsTest.java @@ -16,17 +16,21 @@ * specific language governing permissions and limitations * under the License. */ -package co.elastic.apm.agent.springwebflux; +package co.elastic.apm.agent.webfluxcommon; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledForJreRange; import org.junit.jupiter.api.condition.JRE; import org.mockito.Mockito; +import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatusCode; import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.web.reactive.function.client.ClientResponse; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; @EnabledForJreRange(min = JRE.JAVA_17) public class SpringWeb6UtilsTest { @@ -35,11 +39,18 @@ public class SpringWeb6UtilsTest { void testGetStatusCode() throws Exception { ServerHttpResponse mockResponse = Mockito.mock(ServerHttpResponse.class); Mockito.doReturn(HttpStatusCode.valueOf(222)).when(mockResponse).getStatusCode(); - assertThat(SpringWebVersionUtils.getStatusCode(mockResponse)).isEqualTo(222); + assertThat(SpringWebVersionUtils.getServerStatusCode(mockResponse)).isEqualTo(222); } @Test void testWrongResponseType() { - assertThatThrownBy(() -> SpringWebVersionUtils.getStatusCode(new Object())).isInstanceOf(ClassCastException.class); + assertThatThrownBy(() -> SpringWebVersionUtils.getServerStatusCode(new Object())).isInstanceOf(ClassCastException.class); + } + + @Test + void testGetClientStatusCode() throws Exception { + ClientResponse mockResponse = mock(ClientResponse.class); + doReturn(HttpStatusCode.valueOf(222)).when(mockResponse).statusCode(); + assertThat(SpringWebVersionUtils.getClientStatusCode(mockResponse)).isEqualTo(222); } } diff --git a/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-spring5/pom.xml b/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-spring5/pom.xml index 13205c66ee..9473db30f0 100644 --- a/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-spring5/pom.xml +++ b/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-spring5/pom.xml @@ -14,7 +14,6 @@ ${project.basedir}/../../.. - true @@ -36,6 +35,11 @@ apm-httpserver-core ${project.version} + + ${project.groupId} + apm-spring-webflux-common + ${project.version} + org.springframework diff --git a/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-spring5/src/main/java/co/elastic/apm/agent/springwebflux/WebfluxHelper.java b/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-spring5/src/main/java/co/elastic/apm/agent/springwebflux/WebfluxHelper.java index 39fa391b35..01a68fd110 100644 --- a/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-spring5/src/main/java/co/elastic/apm/agent/springwebflux/WebfluxHelper.java +++ b/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-spring5/src/main/java/co/elastic/apm/agent/springwebflux/WebfluxHelper.java @@ -35,9 +35,11 @@ import co.elastic.apm.agent.sdk.internal.util.LoggerUtils; import co.elastic.apm.agent.sdk.internal.util.PrivilegedActionUtils; import co.elastic.apm.agent.tracer.util.TransactionNameUtils; +import co.elastic.apm.agent.webfluxcommon.SpringWebVersionUtils; import org.reactivestreams.Publisher; import org.springframework.http.HttpCookie; import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.util.MultiValueMap; @@ -197,7 +199,11 @@ public static void setTransactionName(@Nullable Transaction transaction, Serv path = exchange.getRequest().getPath().value(); } } - String method = exchange.getRequest().getMethodValue(); + String method = "unknown"; + HttpMethod methodObj = exchange.getRequest().getMethod(); + if(methodObj != null) { + method = methodObj.name(); + } StringBuilder transactionName = transaction.getAndOverrideName(namePriority, false); if (path != null) { @@ -252,7 +258,11 @@ private static void fillRequest(Transaction transaction, ServerWebExchange ex ServerHttpRequest serverRequest = exchange.getRequest(); Request request = transaction.getContext().getRequest(); - request.withMethod(serverRequest.getMethodValue()); + + HttpMethod method = serverRequest.getMethod(); + if (method != null) { + request.withMethod(method.name()); + } InetSocketAddress remoteAddress = serverRequest.getRemoteAddress(); if (remoteAddress != null && remoteAddress.getAddress() != null) { @@ -273,7 +283,7 @@ private static void fillResponse(Transaction transaction, ServerWebExchange e ServerHttpResponse serverResponse = exchange.getResponse(); int status = 0; try { - status = SpringWebVersionUtils.getStatusCode(serverResponse); + status = SpringWebVersionUtils.getServerStatusCode(serverResponse); } catch (Exception e) { oneTimeResponseCodeErrorLogger.error("Failed to get response code", e); } diff --git a/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-spring5/src/main/java/co/elastic/apm/agent/springwebflux/WebfluxPluginRootPackageCustomizer.java b/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-spring5/src/main/java/co/elastic/apm/agent/springwebflux/WebfluxPluginRootPackageCustomizer.java new file mode 100644 index 0000000000..799da0d45e --- /dev/null +++ b/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-spring5/src/main/java/co/elastic/apm/agent/springwebflux/WebfluxPluginRootPackageCustomizer.java @@ -0,0 +1,34 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you 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 co.elastic.apm.agent.springwebflux; + +import co.elastic.apm.agent.sdk.internal.PluginClassLoaderRootPackageCustomizer; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +public class WebfluxPluginRootPackageCustomizer extends PluginClassLoaderRootPackageCustomizer { + @Override + public Collection pluginClassLoaderRootPackages() { + return Arrays.asList(getPluginPackage(), "co.elastic.apm.agent.webfluxcommon"); + } +} diff --git a/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-spring5/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.internal.PluginClassLoaderRootPackageCustomizer b/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-spring5/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.internal.PluginClassLoaderRootPackageCustomizer new file mode 100644 index 0000000000..ecece5539c --- /dev/null +++ b/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-spring5/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.internal.PluginClassLoaderRootPackageCustomizer @@ -0,0 +1 @@ +co.elastic.apm.agent.springwebflux.WebfluxPluginRootPackageCustomizer diff --git a/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-testapp/src/main/java/co/elastic/apm/agent/springwebflux/testapp/GreetingAnnotated.java b/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-testapp/src/main/java/co/elastic/apm/agent/springwebflux/testapp/GreetingAnnotated.java index 378358409d..3ce7f49bc5 100644 --- a/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-testapp/src/main/java/co/elastic/apm/agent/springwebflux/testapp/GreetingAnnotated.java +++ b/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-testapp/src/main/java/co/elastic/apm/agent/springwebflux/testapp/GreetingAnnotated.java @@ -141,7 +141,7 @@ public Mono patchMapping() { @RequestMapping(path = "/hello-mapping", method = {RequestMethod.HEAD, RequestMethod.OPTIONS, RequestMethod.TRACE}) public Mono otherMapping(ServerHttpRequest request) { - return greetingHandler.helloMessage(request.getMethodValue()); + return greetingHandler.helloMessage(request.getMethod().name()); } @GetMapping("/with-parameters/{id}") diff --git a/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-testapp/src/main/java/co/elastic/apm/agent/springwebflux/testapp/GreetingWebClient.java b/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-testapp/src/main/java/co/elastic/apm/agent/springwebflux/testapp/GreetingWebClient.java index 49778ccbe8..6b6b428d33 100644 --- a/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-testapp/src/main/java/co/elastic/apm/agent/springwebflux/testapp/GreetingWebClient.java +++ b/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-testapp/src/main/java/co/elastic/apm/agent/springwebflux/testapp/GreetingWebClient.java @@ -222,7 +222,7 @@ private WebClient.ResponseSpec request(String method, Function .headers(httpHeaders -> httpHeaders.addAll(headers)) .cookies(httpCookies -> httpCookies.addAll(cookies)) .retrieve() - .onRawStatus(status -> status != expectedStatus, r -> Mono.error(new IllegalStateException(String.format("unexpected response status %d", r.rawStatusCode())))); + .onRawStatus(status -> status != expectedStatus, r -> Mono.error(new IllegalStateException("unexpected response status"))); } @Override diff --git a/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-testapp/src/main/java/co/elastic/apm/agent/springwebflux/testapp/WebFluxConfig.java b/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-testapp/src/main/java/co/elastic/apm/agent/springwebflux/testapp/WebFluxConfig.java index dcf8a485b3..a68b77687e 100644 --- a/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-testapp/src/main/java/co/elastic/apm/agent/springwebflux/testapp/WebFluxConfig.java +++ b/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-testapp/src/main/java/co/elastic/apm/agent/springwebflux/testapp/WebFluxConfig.java @@ -105,7 +105,22 @@ ReactorResourceFactory reactorServerResourceFactory() { NettyReactiveWebServerFactory nettyReactiveWebServerFactory(@Qualifier("reactorServerResourceFactory") ReactorResourceFactory resourceFactory, ObjectProvider routes, ObjectProvider serverCustomizers) { NettyReactiveWebServerFactory serverFactory = new NettyReactiveWebServerFactory(); - serverFactory.setResourceFactory(resourceFactory); + + //The argument type of setResourceFactory was changed in spring 6.1 from + // org.springframework.http.client.reactive.ReactorResourceFactory to + // org.springframework.http.client.ReactorResourceFactory + // we therefor use reflection to call the correct one + try { + try { + Class argType = Class.forName("org.springframework.http.client.reactive.ReactorResourceFactory"); + serverFactory.getClass().getMethod("setResourceFactory", argType).invoke(serverFactory, resourceFactory); + } catch (ClassNotFoundException | NoSuchMethodException e) { + Class argType = Class.forName("org.springframework.http.client.ReactorResourceFactory"); + serverFactory.getClass().getMethod("setResourceFactory", argType).invoke(serverFactory, resourceFactory); + } + } catch (Exception e) { + throw new RuntimeException(e); + } routes.orderedStream().forEach(serverFactory::addRouteProviders); serverFactory.getServerCustomizers().addAll(serverCustomizers.orderedStream().collect(Collectors.toList())); return serverFactory; diff --git a/apm-agent-plugins/apm-spring-webflux/pom.xml b/apm-agent-plugins/apm-spring-webflux/pom.xml index a8c08366da..5e1b1e2b6a 100644 --- a/apm-agent-plugins/apm-spring-webflux/pom.xml +++ b/apm-agent-plugins/apm-spring-webflux/pom.xml @@ -18,7 +18,7 @@ 2.7.16 - 3.1.5 + 3.2.0 @@ -26,6 +26,8 @@ apm-spring-webclient-plugin apm-spring-webflux-testapp apm-spring-webflux-spring5 + apm-spring-webflux-common + apm-spring-webflux-common-spring5 diff --git a/docs/supported-technologies.asciidoc b/docs/supported-technologies.asciidoc index c5adbdf713..910b421953 100644 --- a/docs/supported-technologies.asciidoc +++ b/docs/supported-technologies.asciidoc @@ -112,7 +112,7 @@ the JVM arguments, or setting `ELASTIC_APM_DISABLE_BOOTSTRAP_CHECKS=true` for th |Spring Webflux |5.2.3+ |Creates transactions for incoming HTTP requests, supports annotated and functional endpoints. -|1.24.0 (experimental), 1.34.0 (GA) +|1.24.0 (experimental), 1.34.0 (GA), 6.1+ since 1.45.0 |JavaServer Faces |2.2.x, 2.3.x, 3.0.x