diff --git a/instrumentation/spring/spring-core-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/core/SpringCoreIgnoredTypesConfigurer.java b/instrumentation/spring/spring-core-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/core/SpringCoreIgnoredTypesConfigurer.java new file mode 100644 index 000000000000..77d72d15f690 --- /dev/null +++ b/instrumentation/spring/spring-core-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/core/SpringCoreIgnoredTypesConfigurer.java @@ -0,0 +1,23 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.spring.core; + +import com.google.auto.service.AutoService; +import io.opentelemetry.javaagent.extension.ignore.IgnoredTypesBuilder; +import io.opentelemetry.javaagent.extension.ignore.IgnoredTypesConfigurer; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; + +@AutoService(IgnoredTypesConfigurer.class) +public class SpringCoreIgnoredTypesConfigurer implements IgnoredTypesConfigurer { + + @Override + public void configure(IgnoredTypesBuilder builder, ConfigProperties config) { + // a Runnable task class that we don't need to touch + builder + .ignoreClass("org.springframework.util.ConcurrentLruCache$AddTask") + .ignoreTaskClass("org.springframework.util.ConcurrentLruCache$AddTask"); + } +} diff --git a/instrumentation/spring/spring-web/spring-web-3.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/springweb/v3_1/WebApplicationContextInstrumentation.java b/instrumentation/spring/spring-web/spring-web-3.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/springweb/v3_1/WebApplicationContextInstrumentation.java index 386be2899577..fb9cbd7c3bbe 100644 --- a/instrumentation/spring/spring-web/spring-web-3.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/springweb/v3_1/WebApplicationContextInstrumentation.java +++ b/instrumentation/spring/spring-web/spring-web-3.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/springweb/v3_1/WebApplicationContextInstrumentation.java @@ -75,7 +75,8 @@ public static void onEnter(@Advice.Argument(0) ConfigurableListableBeanFactory b Class clazz = dispatcherServletClass .getClassLoader() - .loadClass("org.springframework.web.servlet.OpenTelemetryHandlerMappingFilter"); + .loadClass( + "org.springframework.web.servlet.v3_1.OpenTelemetryHandlerMappingFilter"); GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); beanDefinition.setScope(SCOPE_SINGLETON); beanDefinition.setBeanClass(clazz); diff --git a/instrumentation/spring/spring-web/spring-web-6.0/javaagent/build.gradle.kts b/instrumentation/spring/spring-web/spring-web-6.0/javaagent/build.gradle.kts new file mode 100644 index 000000000000..fb6ef08ff1cf --- /dev/null +++ b/instrumentation/spring/spring-web/spring-web-6.0/javaagent/build.gradle.kts @@ -0,0 +1,16 @@ +plugins { + id("otel.javaagent-instrumentation") +} + +muzzle { + pass { + group.set("org.springframework") + module.set("spring-web") + versions.set("[6.0.0)") + assertInverse.set(true) + } +} + +dependencies { + compileOnly("org.springframework:spring-web:6.0.0") +} diff --git a/instrumentation/spring/spring-web/spring-web-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/web/v6_0/SpringWebInstrumentationModule.java b/instrumentation/spring/spring-web/spring-web-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/web/v6_0/SpringWebInstrumentationModule.java new file mode 100644 index 000000000000..24833c31c0b3 --- /dev/null +++ b/instrumentation/spring/spring-web/spring-web-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/web/v6_0/SpringWebInstrumentationModule.java @@ -0,0 +1,34 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.spring.web.v6_0; + +import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; +import static java.util.Collections.singletonList; + +import com.google.auto.service.AutoService; +import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import java.util.List; +import net.bytebuddy.matcher.ElementMatcher; + +@AutoService(InstrumentationModule.class) +public class SpringWebInstrumentationModule extends InstrumentationModule { + + public SpringWebInstrumentationModule() { + super("spring-web", "spring-web-6.0"); + } + + @Override + public ElementMatcher.Junction classLoaderMatcher() { + // class added in 6.0 + return hasClassesNamed("org.springframework.web.ErrorResponse"); + } + + @Override + public List typeInstrumentations() { + return singletonList(new WebApplicationContextInstrumentation()); + } +} diff --git a/instrumentation/spring/spring-web/spring-web-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/web/v6_0/WebApplicationContextInstrumentation.java b/instrumentation/spring/spring-web/spring-web-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/web/v6_0/WebApplicationContextInstrumentation.java new file mode 100644 index 000000000000..3ecad4e9d748 --- /dev/null +++ b/instrumentation/spring/spring-web/spring-web-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/web/v6_0/WebApplicationContextInstrumentation.java @@ -0,0 +1,92 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.spring.web.v6_0; + +import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.extendsClass; +import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; +import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; +import static org.springframework.beans.factory.config.BeanDefinition.SCOPE_SINGLETON; + +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.GenericBeanDefinition; + +/** + * This instrumentation adds the OpenTelemetryHandlerMappingFilter definition to the spring context + * When the context is created, the filter will be added to the beginning of the filter chain. + */ +public class WebApplicationContextInstrumentation implements TypeInstrumentation { + + @Override + public ElementMatcher classLoaderOptimization() { + return hasClassesNamed( + "org.springframework.context.support.AbstractApplicationContext", + "org.springframework.web.context.WebApplicationContext"); + } + + @Override + public ElementMatcher typeMatcher() { + return extendsClass(named("org.springframework.context.support.AbstractApplicationContext")) + .and(implementsInterface(named("org.springframework.web.context.WebApplicationContext"))); + } + + @Override + public void transform(TypeTransformer transformer) { + transformer.applyAdviceToMethod( + isMethod() + .and(named("postProcessBeanFactory")) + .and( + takesArgument( + 0, + named( + "org.springframework.beans.factory.config.ConfigurableListableBeanFactory"))), + WebApplicationContextInstrumentation.class.getName() + "$FilterInjectingAdvice"); + } + + @SuppressWarnings("unused") + public static class FilterInjectingAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void onEnter(@Advice.Argument(0) ConfigurableListableBeanFactory beanFactory) { + if (beanFactory instanceof BeanDefinitionRegistry + && !beanFactory.containsBean("otelAutoDispatcherFilter")) { + try { + // Firstly check whether DispatcherServlet is present. We need to load an instrumented + // class from spring-webmvc to trigger injection that makes + // OpenTelemetryHandlerMappingFilter available. + Class dispatcherServletClass = + beanFactory + .getBeanClassLoader() + .loadClass("org.springframework.web.servlet.DispatcherServlet"); + + // Now attempt to load our injected instrumentation class from the same class loader as + // DispatcherServlet + Class clazz = + dispatcherServletClass + .getClassLoader() + .loadClass( + "org.springframework.web.servlet.v6_0.OpenTelemetryHandlerMappingFilter"); + GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); + beanDefinition.setScope(SCOPE_SINGLETON); + beanDefinition.setBeanClass(clazz); + + ((BeanDefinitionRegistry) beanFactory) + .registerBeanDefinition("otelAutoDispatcherFilter", beanDefinition); + } catch (ClassNotFoundException ignored) { + // Ignore + } + } + } + } +} diff --git a/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/javaagent/build.gradle.kts b/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/javaagent/build.gradle.kts index 1bbdf37dae7d..a03906d1e9b4 100644 --- a/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/javaagent/build.gradle.kts +++ b/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/javaagent/build.gradle.kts @@ -20,10 +20,10 @@ muzzle { dependencies { bootstrap(project(":instrumentation:servlet:servlet-common:bootstrap")) + implementation(project(":instrumentation:spring:spring-webmvc:spring-webmvc-common:javaagent")) + compileOnly("org.springframework:spring-webmvc:3.1.0.RELEASE") compileOnly("javax.servlet:javax.servlet-api:3.1.0") -// compileOnly("org.springframework:spring-webmvc:2.5.6") -// compileOnly("javax.servlet:servlet-api:2.4") // Include servlet instrumentation for verifying the tomcat requests testInstrumentation(project(":instrumentation:servlet:servlet-3.0:javaagent")) @@ -31,10 +31,7 @@ dependencies { testInstrumentation(project(":instrumentation:tomcat:tomcat-7.0:javaagent")) testInstrumentation(project(":instrumentation:spring:spring-web:spring-web-3.1:javaagent")) - testImplementation("javax.validation:validation-api:1.1.0.Final") - testImplementation("org.hibernate:hibernate-validator:5.4.2.Final") - - testImplementation("org.spockframework:spock-spring") + testImplementation(project(":instrumentation:spring:spring-webmvc:spring-webmvc-common:testing")) testLibrary("org.springframework.boot:spring-boot-starter-test:1.5.17.RELEASE") testLibrary("org.springframework.boot:spring-boot-starter-web:1.5.17.RELEASE") @@ -43,12 +40,6 @@ dependencies { latestDepTestLibrary("org.springframework.boot:spring-boot-starter-test:2.+") latestDepTestLibrary("org.springframework.boot:spring-boot-starter-web:2.+") latestDepTestLibrary("org.springframework.boot:spring-boot-starter-security:2.+") - - testImplementation("org.springframework.security.oauth:spring-security-oauth2:2.0.16.RELEASE") - - // For spring security - testImplementation("jakarta.xml.bind:jakarta.xml.bind-api:2.3.2") - testImplementation("org.glassfish.jaxb:jaxb-runtime:2.3.2") } tasks.withType().configureEach { diff --git a/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webmvc/v3_1/DispatcherServletInstrumentation.java b/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webmvc/v3_1/DispatcherServletInstrumentation.java index 913395eb859e..0ecea21b5e21 100644 --- a/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webmvc/v3_1/DispatcherServletInstrumentation.java +++ b/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webmvc/v3_1/DispatcherServletInstrumentation.java @@ -24,7 +24,7 @@ import org.springframework.context.ApplicationContext; import org.springframework.web.servlet.HandlerMapping; import org.springframework.web.servlet.ModelAndView; -import org.springframework.web.servlet.OpenTelemetryHandlerMappingFilter; +import org.springframework.web.servlet.v3_1.OpenTelemetryHandlerMappingFilter; public class DispatcherServletInstrumentation implements TypeInstrumentation { diff --git a/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webmvc/v3_1/HandlerAdapterInstrumentation.java b/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webmvc/v3_1/HandlerAdapterInstrumentation.java index bd0eacded2a2..898f959ed3fa 100644 --- a/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webmvc/v3_1/HandlerAdapterInstrumentation.java +++ b/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webmvc/v3_1/HandlerAdapterInstrumentation.java @@ -22,6 +22,7 @@ import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge; import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import io.opentelemetry.javaagent.instrumentation.spring.webmvc.IsGrailsHandler; import javax.servlet.http.HttpServletRequest; import net.bytebuddy.asm.Advice; import net.bytebuddy.description.type.TypeDescription; diff --git a/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webmvc/v3_1/SpringWebMvcInstrumentationModule.java b/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webmvc/v3_1/SpringWebMvcInstrumentationModule.java index aa9aa1099cd6..a9f6c445727f 100644 --- a/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webmvc/v3_1/SpringWebMvcInstrumentationModule.java +++ b/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webmvc/v3_1/SpringWebMvcInstrumentationModule.java @@ -21,7 +21,7 @@ public SpringWebMvcInstrumentationModule() { @Override public boolean isHelperClass(String className) { return className.startsWith( - "org.springframework.web.servlet.OpenTelemetryHandlerMappingFilter"); + "org.springframework.web.servlet.v3_1.OpenTelemetryHandlerMappingFilter"); } @Override diff --git a/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webmvc/v3_1/SpringWebMvcSingletons.java b/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webmvc/v3_1/SpringWebMvcSingletons.java index b172304de75c..53ffdd67ccc2 100644 --- a/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webmvc/v3_1/SpringWebMvcSingletons.java +++ b/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webmvc/v3_1/SpringWebMvcSingletons.java @@ -5,9 +5,8 @@ package io.opentelemetry.javaagent.instrumentation.spring.webmvc.v3_1; -import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; -import io.opentelemetry.javaagent.bootstrap.internal.ExperimentalConfig; +import io.opentelemetry.javaagent.instrumentation.spring.webmvc.SpringWebMvcInstrumenterFactory; import org.springframework.web.servlet.ModelAndView; public final class SpringWebMvcSingletons { @@ -18,20 +17,10 @@ public final class SpringWebMvcSingletons { private static final Instrumenter MODEL_AND_VIEW_INSTRUMENTER; static { - HANDLER_INSTRUMENTER = - Instrumenter.builder( - GlobalOpenTelemetry.get(), INSTRUMENTATION_NAME, new HandlerSpanNameExtractor()) - .setEnabled(ExperimentalConfig.get().controllerTelemetryEnabled()) - .buildInstrumenter(); - - MODEL_AND_VIEW_INSTRUMENTER = - Instrumenter.builder( - GlobalOpenTelemetry.get(), - INSTRUMENTATION_NAME, - new ModelAndViewSpanNameExtractor()) - .addAttributesExtractor(new ModelAndViewAttributesExtractor()) - .setEnabled(ExperimentalConfig.get().viewTelemetryEnabled()) - .buildInstrumenter(); + SpringWebMvcInstrumenterFactory factory = + new SpringWebMvcInstrumenterFactory(INSTRUMENTATION_NAME); + HANDLER_INSTRUMENTER = factory.createHandlerInstrumenter(); + MODEL_AND_VIEW_INSTRUMENTER = factory.createModelAndViewInstrumenter(); } public static Instrumenter handlerInstrumenter() { diff --git a/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/javaagent/src/main/java/org/springframework/web/servlet/OpenTelemetryHandlerMappingFilter.java b/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/javaagent/src/main/java/org/springframework/web/servlet/v3_1/OpenTelemetryHandlerMappingFilter.java similarity index 97% rename from instrumentation/spring/spring-webmvc/spring-webmvc-3.1/javaagent/src/main/java/org/springframework/web/servlet/OpenTelemetryHandlerMappingFilter.java rename to instrumentation/spring/spring-webmvc/spring-webmvc-3.1/javaagent/src/main/java/org/springframework/web/servlet/v3_1/OpenTelemetryHandlerMappingFilter.java index f24d0ae731ae..c3f11894d4d2 100644 --- a/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/javaagent/src/main/java/org/springframework/web/servlet/OpenTelemetryHandlerMappingFilter.java +++ b/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/javaagent/src/main/java/org/springframework/web/servlet/v3_1/OpenTelemetryHandlerMappingFilter.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package org.springframework.web.servlet; +package org.springframework.web.servlet.v3_1; import static io.opentelemetry.instrumentation.api.instrumenter.http.HttpRouteSource.CONTROLLER; @@ -28,6 +28,8 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.core.Ordered; +import org.springframework.web.servlet.HandlerExecutionChain; +import org.springframework.web.servlet.HandlerMapping; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; public class OpenTelemetryHandlerMappingFilter implements Filter, Ordered { diff --git a/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/javaagent/src/test/groovy/test/boot/AuthServerConfig.groovy b/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/javaagent/src/test/groovy/test/boot/AuthServerConfig.groovy deleted file mode 100644 index 51c71ccfaedd..000000000000 --- a/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/javaagent/src/test/groovy/test/boot/AuthServerConfig.groovy +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package test.boot - -import org.springframework.context.annotation.Configuration -import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter -import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer - -@Configuration -@EnableAuthorizationServer -class AuthServerConfig extends AuthorizationServerConfigurerAdapter { -} diff --git a/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/javaagent/src/test/groovy/test/boot/SecurityConfig.groovy b/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/javaagent/src/test/groovy/test/boot/SecurityConfig.groovy index 9cbc14ae3812..fe4fd70d7ec9 100644 --- a/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/javaagent/src/test/groovy/test/boot/SecurityConfig.groovy +++ b/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/javaagent/src/test/groovy/test/boot/SecurityConfig.groovy @@ -5,6 +5,7 @@ package test.boot +import boot.SavingAuthenticationProvider import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.core.annotation.Order diff --git a/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/javaagent/src/test/groovy/test/boot/SpringBootBasedTest.groovy b/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/javaagent/src/test/groovy/test/boot/SpringBootBasedTest.groovy index 0db3db29b1a0..eae63b7d233c 100644 --- a/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/javaagent/src/test/groovy/test/boot/SpringBootBasedTest.groovy +++ b/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/javaagent/src/test/groovy/test/boot/SpringBootBasedTest.groovy @@ -5,193 +5,11 @@ package test.boot -import io.opentelemetry.api.trace.StatusCode -import io.opentelemetry.instrumentation.test.AgentTestTrait -import io.opentelemetry.instrumentation.test.asserts.TraceAssert -import io.opentelemetry.instrumentation.test.base.HttpServerTest -import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint -import io.opentelemetry.sdk.trace.data.SpanData -import io.opentelemetry.semconv.trace.attributes.SemanticAttributes -import io.opentelemetry.testing.internal.armeria.common.AggregatedHttpRequest -import io.opentelemetry.testing.internal.armeria.common.HttpData -import io.opentelemetry.testing.internal.armeria.common.MediaType -import io.opentelemetry.testing.internal.armeria.common.QueryParams -import org.springframework.boot.SpringApplication -import org.springframework.context.ConfigurableApplicationContext -import org.springframework.security.web.util.OnCommittedResponseWrapper -import org.springframework.web.servlet.view.RedirectView +import boot.AbstractSpringBootBasedTest -import static io.opentelemetry.api.trace.SpanKind.INTERNAL -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.AUTH_ERROR -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.LOGIN -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.NOT_FOUND -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.PATH_PARAM -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.REDIRECT -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.SUCCESS +class SpringBootBasedTest extends AbstractSpringBootBasedTest { -class SpringBootBasedTest extends HttpServerTest implements AgentTestTrait { - - @Override - ConfigurableApplicationContext startServer(int port) { - def app = new SpringApplication(AppConfig, SecurityConfig, AuthServerConfig) - app.setDefaultProperties([ - "server.port" : port, - "server.context-path" : getContextPath(), - "server.servlet.contextPath" : getContextPath(), - "server.error.include-message": "always"]) - def context = app.run() - return context - } - - @Override - void stopServer(ConfigurableApplicationContext ctx) { - ctx.close() - } - - @Override - String getContextPath() { - return "/xyz" - } - - @Override - boolean hasHandlerSpan(ServerEndpoint endpoint) { - true - } - - @Override - boolean hasRenderSpan(ServerEndpoint endpoint) { - endpoint == REDIRECT - } - - @Override - boolean hasResponseSpan(ServerEndpoint endpoint) { - endpoint == REDIRECT || endpoint == NOT_FOUND - } - - @Override - boolean testPathParam() { - true - } - - @Override - boolean hasErrorPageSpans(ServerEndpoint endpoint) { - endpoint == NOT_FOUND - } - - @Override - String expectedHttpRoute(ServerEndpoint endpoint) { - switch (endpoint) { - case PATH_PARAM: - return getContextPath() + "/path/{id}/param" - case NOT_FOUND: - return getContextPath() + "/**" - case LOGIN: - return getContextPath() + "/*" - default: - return super.expectedHttpRoute(endpoint) - } - } - - def "test spans with auth error"() { - setup: - def authProvider = server.getBean(SavingAuthenticationProvider) - def request = request(AUTH_ERROR, "GET") - - when: - authProvider.latestAuthentications.clear() - def response = client.execute(request).aggregate().join() - - then: - response.status().code() == 401 // not secured - - and: - assertTraces(1) { - trace(0, 3) { - serverSpan(it, 0, null, null, "GET", null, AUTH_ERROR) - sendErrorSpan(it, 1, span(0)) - errorPageSpans(it, 2, null) - } - } - } - - def "test character encoding of #testPassword"() { - setup: - def authProvider = server.getBean(SavingAuthenticationProvider) - - QueryParams form = QueryParams.of("username", "test", "password", testPassword) - def request = AggregatedHttpRequest.of( - request(LOGIN, "POST").headers().toBuilder().contentType(MediaType.FORM_DATA).build(), - HttpData.ofUtf8(form.toQueryString())) - - when: - authProvider.latestAuthentications.clear() - def response = client.execute(request).aggregate().join() - - then: - response.status().code() == 302 // redirect after success - authProvider.latestAuthentications.get(0).password == testPassword - - and: - assertTraces(1) { - trace(0, 2) { - serverSpan(it, 0, null, null, "POST", response.contentUtf8().length(), LOGIN) - redirectSpan(it, 1, span(0)) - } - } - - where: - testPassword << ["password", "dfsdföääöüüä", "🤓"] - } - - @Override - void errorPageSpans(TraceAssert trace, int index, Object parent, String method = "GET", ServerEndpoint endpoint = SUCCESS) { - trace.span(index) { - name "BasicErrorController.error" - kind INTERNAL - attributes { - } - } - } - - @Override - void responseSpan(TraceAssert trace, int index, Object parent, String method = "GET", ServerEndpoint endpoint = SUCCESS) { - def methodName = endpoint == NOT_FOUND ? "sendError" : "sendRedirect" - trace.span(index) { - name "OnCommittedResponseWrapper.$methodName" - kind INTERNAL - attributes { - "$SemanticAttributes.CODE_NAMESPACE" OnCommittedResponseWrapper.name - "$SemanticAttributes.CODE_FUNCTION" methodName - } - } - } - - @Override - void renderSpan(TraceAssert trace, int index, Object parent, String method = "GET", ServerEndpoint endpoint = SUCCESS) { - trace.span(index) { - name "Render RedirectView" - kind INTERNAL - attributes { - "spring-webmvc.view.type" RedirectView.name - } - } - } - - @Override - void handlerSpan(TraceAssert trace, int index, Object parent, String method = "GET", ServerEndpoint endpoint = SUCCESS) { - def handlerSpanName = "TestController.${endpoint.name().toLowerCase()}" - if (endpoint == NOT_FOUND) { - handlerSpanName = "ResourceHttpRequestHandler.handleRequest" - } - trace.span(index) { - name handlerSpanName - kind INTERNAL - if (endpoint == EXCEPTION) { - status StatusCode.ERROR - errorEvent(Exception, EXCEPTION.body) - } - childOf((SpanData) parent) - } + Class securityConfigClass() { + SecurityConfig } } diff --git a/instrumentation/spring/spring-webmvc/spring-webmvc-6.0/javaagent/build.gradle.kts b/instrumentation/spring/spring-webmvc/spring-webmvc-6.0/javaagent/build.gradle.kts new file mode 100644 index 000000000000..6707e3800662 --- /dev/null +++ b/instrumentation/spring/spring-webmvc/spring-webmvc-6.0/javaagent/build.gradle.kts @@ -0,0 +1,48 @@ +plugins { + id("otel.javaagent-instrumentation") +} + +muzzle { + pass { + group.set("org.springframework") + module.set("spring-webmvc") + versions.set("[6.0.0,)") + extraDependency("jakarta.servlet:jakarta.servlet-api:5.0.0") + assertInverse.set(true) + } +} + +dependencies { + bootstrap(project(":instrumentation:servlet:servlet-common:bootstrap")) + + implementation(project(":instrumentation:spring:spring-webmvc:spring-webmvc-common:javaagent")) + + compileOnly("org.springframework:spring-webmvc:6.0.0") + compileOnly("jakarta.servlet:jakarta.servlet-api:5.0.0") + + // Include servlet instrumentation for verifying the tomcat requests + testInstrumentation(project(":instrumentation:servlet:servlet-5.0:javaagent")) + testInstrumentation(project(":instrumentation:servlet:servlet-javax-common:javaagent")) + testInstrumentation(project(":instrumentation:tomcat:tomcat-10.0:javaagent")) + testInstrumentation(project(":instrumentation:spring:spring-core-2.0:javaagent")) + testInstrumentation(project(":instrumentation:spring:spring-web:spring-web-6.0:javaagent")) + + testImplementation(project(":instrumentation:spring:spring-webmvc:spring-webmvc-common:testing")) + + testLibrary("org.springframework.boot:spring-boot-starter-test:3.0.0") + testLibrary("org.springframework.boot:spring-boot-starter-web:3.0.0") + testLibrary("org.springframework.boot:spring-boot-starter-security:3.0.0") +} + +// spring 6 requires java 17 +otelJava { + minJavaVersionSupported.set(JavaVersion.VERSION_17) +} + +tasks.withType().configureEach { + // TODO run tests both with and without experimental span attributes + jvmArgs("-Dotel.instrumentation.spring-webmvc.experimental-span-attributes=true") + // required on jdk17 + jvmArgs("--add-opens=java.base/java.lang=ALL-UNNAMED") + jvmArgs("-XX:+IgnoreUnrecognizedVMOptions") +} diff --git a/instrumentation/spring/spring-webmvc/spring-webmvc-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webmvc/v6_0/DispatcherServletInstrumentation.java b/instrumentation/spring/spring-webmvc/spring-webmvc-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webmvc/v6_0/DispatcherServletInstrumentation.java new file mode 100644 index 000000000000..60d663663bea --- /dev/null +++ b/instrumentation/spring/spring-webmvc/spring-webmvc-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webmvc/v6_0/DispatcherServletInstrumentation.java @@ -0,0 +1,102 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.spring.webmvc.v6_0; + +import static io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge.currentContext; +import static io.opentelemetry.javaagent.instrumentation.spring.webmvc.v6_0.SpringWebMvcSingletons.modelAndViewInstrumenter; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.isProtected; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import java.util.List; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.springframework.context.ApplicationContext; +import org.springframework.web.servlet.HandlerMapping; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.v6_0.OpenTelemetryHandlerMappingFilter; + +public class DispatcherServletInstrumentation implements TypeInstrumentation { + + @Override + public ElementMatcher typeMatcher() { + return named("org.springframework.web.servlet.DispatcherServlet"); + } + + @Override + public void transform(TypeTransformer transformer) { + transformer.applyAdviceToMethod( + isMethod() + .and(isProtected()) + .and(named("onRefresh")) + .and(takesArgument(0, named("org.springframework.context.ApplicationContext"))) + .and(takesArguments(1)), + DispatcherServletInstrumentation.class.getName() + "$HandlerMappingAdvice"); + transformer.applyAdviceToMethod( + isMethod() + .and(isProtected()) + .and(named("render")) + .and(takesArgument(0, named("org.springframework.web.servlet.ModelAndView"))), + DispatcherServletInstrumentation.class.getName() + "$RenderAdvice"); + } + + /** + * This advice creates a filter that has reference to the handlerMappings from DispatcherServlet + * which allows the mappings to be evaluated outside of regular request processing. + */ + @SuppressWarnings("unused") + public static class HandlerMappingAdvice { + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void afterRefresh( + @Advice.Argument(0) ApplicationContext springCtx, + @Advice.FieldValue("handlerMappings") List handlerMappings) { + if (springCtx.containsBean("otelAutoDispatcherFilter")) { + OpenTelemetryHandlerMappingFilter filter = + (OpenTelemetryHandlerMappingFilter) springCtx.getBean("otelAutoDispatcherFilter"); + if (handlerMappings != null) { + filter.setHandlerMappings(handlerMappings); + } + } + } + } + + @SuppressWarnings("unused") + public static class RenderAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void onEnter( + @Advice.Argument(0) ModelAndView mv, + @Advice.Local("otelContext") Context context, + @Advice.Local("otelScope") Scope scope) { + Context parentContext = currentContext(); + if (modelAndViewInstrumenter().shouldStart(parentContext, mv)) { + context = modelAndViewInstrumenter().start(parentContext, mv); + scope = context.makeCurrent(); + } + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void stopSpan( + @Advice.Argument(0) ModelAndView mv, + @Advice.Thrown Throwable throwable, + @Advice.Local("otelContext") Context context, + @Advice.Local("otelScope") Scope scope) { + if (scope == null) { + return; + } + scope.close(); + modelAndViewInstrumenter().end(context, mv, null, throwable); + } + } +} diff --git a/instrumentation/spring/spring-webmvc/spring-webmvc-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webmvc/v6_0/HandlerAdapterInstrumentation.java b/instrumentation/spring/spring-webmvc/spring-webmvc-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webmvc/v6_0/HandlerAdapterInstrumentation.java new file mode 100644 index 000000000000..a05e5d16268d --- /dev/null +++ b/instrumentation/spring/spring-webmvc/spring-webmvc-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webmvc/v6_0/HandlerAdapterInstrumentation.java @@ -0,0 +1,102 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.spring.webmvc.v6_0; + +import static io.opentelemetry.instrumentation.api.instrumenter.http.HttpRouteSource.CONTROLLER; +import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; +import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface; +import static io.opentelemetry.javaagent.instrumentation.spring.webmvc.v6_0.SpringWebMvcSingletons.handlerInstrumenter; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.isPublic; +import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpRouteHolder; +import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import io.opentelemetry.javaagent.instrumentation.spring.webmvc.IsGrailsHandler; +import jakarta.servlet.http.HttpServletRequest; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +public class HandlerAdapterInstrumentation implements TypeInstrumentation { + + @Override + public ElementMatcher classLoaderOptimization() { + return hasClassesNamed("org.springframework.web.servlet.HandlerAdapter"); + } + + @Override + public ElementMatcher typeMatcher() { + return implementsInterface(named("org.springframework.web.servlet.HandlerAdapter")); + } + + @Override + public void transform(TypeTransformer transformer) { + transformer.applyAdviceToMethod( + isMethod() + .and(isPublic()) + .and(nameStartsWith("handle")) + .and(takesArgument(0, named("jakarta.servlet.http.HttpServletRequest"))) + .and(takesArguments(3)), + HandlerAdapterInstrumentation.class.getName() + "$ControllerAdvice"); + } + + @SuppressWarnings("unused") + public static class ControllerAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void nameResourceAndStartSpan( + @Advice.Argument(0) HttpServletRequest request, + @Advice.Argument(2) Object handler, + @Advice.Local("otelContext") Context context, + @Advice.Local("otelScope") Scope scope) { + // TODO (trask) should there be a way to customize Instrumenter.shouldStart()? + if (IsGrailsHandler.isGrailsHandler(handler)) { + // skip creating handler span for grails, grails instrumentation will take care of it + return; + } + + Context parentContext = Java8BytecodeBridge.currentContext(); + + // don't start a new top-level span + if (!Java8BytecodeBridge.spanFromContext(parentContext).getSpanContext().isValid()) { + return; + } + + // Name the parent span based on the matching pattern + HttpRouteHolder.updateHttpRoute( + parentContext, CONTROLLER, SpringWebMvcServerSpanNaming.SERVER_SPAN_NAME, request); + + if (!handlerInstrumenter().shouldStart(parentContext, handler)) { + return; + } + + // Now create a span for handler/controller execution. + context = handlerInstrumenter().start(parentContext, handler); + scope = context.makeCurrent(); + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void stopSpan( + @Advice.Argument(2) Object handler, + @Advice.Thrown Throwable throwable, + @Advice.Local("otelContext") Context context, + @Advice.Local("otelScope") Scope scope) { + if (scope == null) { + return; + } + scope.close(); + handlerInstrumenter().end(context, handler, null, throwable); + } + } +} diff --git a/instrumentation/spring/spring-webmvc/spring-webmvc-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webmvc/v6_0/SpringWebMvcInstrumentationModule.java b/instrumentation/spring/spring-webmvc/spring-webmvc-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webmvc/v6_0/SpringWebMvcInstrumentationModule.java new file mode 100644 index 000000000000..18bdf2b1d430 --- /dev/null +++ b/instrumentation/spring/spring-webmvc/spring-webmvc-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webmvc/v6_0/SpringWebMvcInstrumentationModule.java @@ -0,0 +1,31 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.spring.webmvc.v6_0; + +import static java.util.Arrays.asList; + +import com.google.auto.service.AutoService; +import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import java.util.List; + +@AutoService(InstrumentationModule.class) +public class SpringWebMvcInstrumentationModule extends InstrumentationModule { + public SpringWebMvcInstrumentationModule() { + super("spring-webmvc", "spring-webmvc-6.0"); + } + + @Override + public boolean isHelperClass(String className) { + return className.startsWith( + "org.springframework.web.servlet.v6_0.OpenTelemetryHandlerMappingFilter"); + } + + @Override + public List typeInstrumentations() { + return asList(new DispatcherServletInstrumentation(), new HandlerAdapterInstrumentation()); + } +} diff --git a/instrumentation/spring/spring-webmvc/spring-webmvc-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webmvc/v6_0/SpringWebMvcServerSpanNaming.java b/instrumentation/spring/spring-webmvc/spring-webmvc-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webmvc/v6_0/SpringWebMvcServerSpanNaming.java new file mode 100644 index 000000000000..9650369f22b6 --- /dev/null +++ b/instrumentation/spring/spring-webmvc/spring-webmvc-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webmvc/v6_0/SpringWebMvcServerSpanNaming.java @@ -0,0 +1,26 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.spring.webmvc.v6_0; + +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpRouteGetter; +import io.opentelemetry.javaagent.bootstrap.servlet.ServletContextPath; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.web.servlet.HandlerMapping; + +public class SpringWebMvcServerSpanNaming { + + public static final HttpRouteGetter SERVER_SPAN_NAME = + (context, request) -> { + Object bestMatchingPattern = + request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE); + if (bestMatchingPattern != null) { + return ServletContextPath.prepend(context, bestMatchingPattern.toString()); + } + return null; + }; + + private SpringWebMvcServerSpanNaming() {} +} diff --git a/instrumentation/spring/spring-webmvc/spring-webmvc-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webmvc/v6_0/SpringWebMvcSingletons.java b/instrumentation/spring/spring-webmvc/spring-webmvc-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webmvc/v6_0/SpringWebMvcSingletons.java new file mode 100644 index 000000000000..d0c58b7d656a --- /dev/null +++ b/instrumentation/spring/spring-webmvc/spring-webmvc-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webmvc/v6_0/SpringWebMvcSingletons.java @@ -0,0 +1,35 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.spring.webmvc.v6_0; + +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.javaagent.instrumentation.spring.webmvc.SpringWebMvcInstrumenterFactory; +import org.springframework.web.servlet.ModelAndView; + +public final class SpringWebMvcSingletons { + private static final String INSTRUMENTATION_NAME = "io.opentelemetry.spring-webmvc-6.0"; + + private static final Instrumenter HANDLER_INSTRUMENTER; + + private static final Instrumenter MODEL_AND_VIEW_INSTRUMENTER; + + static { + SpringWebMvcInstrumenterFactory factory = + new SpringWebMvcInstrumenterFactory(INSTRUMENTATION_NAME); + HANDLER_INSTRUMENTER = factory.createHandlerInstrumenter(); + MODEL_AND_VIEW_INSTRUMENTER = factory.createModelAndViewInstrumenter(); + } + + public static Instrumenter handlerInstrumenter() { + return HANDLER_INSTRUMENTER; + } + + public static Instrumenter modelAndViewInstrumenter() { + return MODEL_AND_VIEW_INSTRUMENTER; + } + + private SpringWebMvcSingletons() {} +} diff --git a/instrumentation/spring/spring-webmvc/spring-webmvc-6.0/javaagent/src/main/java/org/springframework/web/servlet/v6_0/OpenTelemetryHandlerMappingFilter.java b/instrumentation/spring/spring-webmvc/spring-webmvc-6.0/javaagent/src/main/java/org/springframework/web/servlet/v6_0/OpenTelemetryHandlerMappingFilter.java new file mode 100644 index 000000000000..c8da7124b44f --- /dev/null +++ b/instrumentation/spring/spring-webmvc/spring-webmvc-6.0/javaagent/src/main/java/org/springframework/web/servlet/v6_0/OpenTelemetryHandlerMappingFilter.java @@ -0,0 +1,134 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.springframework.web.servlet.v6_0; + +import static io.opentelemetry.instrumentation.api.instrumenter.http.HttpRouteSource.CONTROLLER; + +import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpRouteGetter; +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpRouteHolder; +import io.opentelemetry.javaagent.instrumentation.spring.webmvc.v6_0.SpringWebMvcServerSpanNaming; +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.FilterConfig; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import javax.annotation.Nullable; +import org.springframework.core.Ordered; +import org.springframework.http.server.RequestPath; +import org.springframework.web.servlet.HandlerExecutionChain; +import org.springframework.web.servlet.HandlerMapping; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; +import org.springframework.web.util.ServletRequestPathUtils; + +public class OpenTelemetryHandlerMappingFilter implements Filter, Ordered { + + private final HttpRouteGetter serverSpanName = + (context, request) -> { + RequestPath previousValue = null; + if (this.parseRequestPath) { + previousValue = + (RequestPath) request.getAttribute(ServletRequestPathUtils.PATH_ATTRIBUTE); + // sets new value for PATH_ATTRIBUTE of request + ServletRequestPathUtils.parseAndCache(request); + } + try { + if (findMapping(request)) { + // Name the parent span based on the matching pattern + // Let the parent span resource name be set with the attribute set in findMapping. + return SpringWebMvcServerSpanNaming.SERVER_SPAN_NAME.get(context, request); + } + } finally { + // mimic spring DispatcherServlet and restore the previous value of PATH_ATTRIBUTE + if (this.parseRequestPath) { + ServletRequestPathUtils.setParsedRequestPath(previousValue, request); + } + } + return null; + }; + + @Nullable private List handlerMappings; + private boolean parseRequestPath; + + @Override + public void init(FilterConfig filterConfig) {} + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + + if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) { + filterChain.doFilter(request, response); + return; + } + + try { + filterChain.doFilter(request, response); + } finally { + if (handlerMappings != null) { + Context context = Context.current(); + HttpRouteHolder.updateHttpRoute( + context, CONTROLLER, serverSpanName, (HttpServletRequest) request); + } + } + } + + @Override + public void destroy() {} + + /** + * When a HandlerMapping matches a request, it sets HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE + * as an attribute on the request. This attribute is read by SpringWebMvcDecorator.onRequest and + * set as the resource name. + */ + private boolean findMapping(HttpServletRequest request) { + try { + // handlerMapping already null-checked above + for (HandlerMapping mapping : Objects.requireNonNull(handlerMappings)) { + HandlerExecutionChain handler = mapping.getHandler(request); + if (handler != null) { + return true; + } + } + } catch (Exception ignored) { + // mapping.getHandler() threw exception. Ignore + } + return false; + } + + public void setHandlerMappings(List mappings) { + List handlerMappings = new ArrayList<>(); + for (HandlerMapping mapping : mappings) { + // Originally we ran findMapping at the very beginning of the request. This turned out to have + // application-crashing side-effects with grails. That is why we don't add all HandlerMapping + // classes here. Although now that we run findMapping after the request, and only when server + // span name has not been updated by a controller, the probability of bad side-effects is much + // reduced even if we did add all HandlerMapping classes here. + if (mapping instanceof RequestMappingHandlerMapping) { + handlerMappings.add(mapping); + if (mapping.usesPathPatterns()) { + this.parseRequestPath = true; + } + } + } + if (!handlerMappings.isEmpty()) { + this.handlerMappings = handlerMappings; + } + } + + @Override + public int getOrder() { + // Run after all HIGHEST_PRECEDENCE items + return Ordered.HIGHEST_PRECEDENCE + 1; + } +} diff --git a/instrumentation/spring/spring-webmvc/spring-webmvc-6.0/javaagent/src/test/groovy/SecurityConfig.groovy b/instrumentation/spring/spring-webmvc/spring-webmvc-6.0/javaagent/src/test/groovy/SecurityConfig.groovy new file mode 100644 index 000000000000..bf4d61c0973a --- /dev/null +++ b/instrumentation/spring/spring-webmvc/spring-webmvc-6.0/javaagent/src/test/groovy/SecurityConfig.groovy @@ -0,0 +1,57 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +import boot.SavingAuthenticationProvider +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.core.annotation.Order +import org.springframework.security.config.annotation.web.builders.HttpSecurity +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity +import org.springframework.security.web.SecurityFilterChain + +@Configuration +@EnableWebSecurity +class SecurityConfig { + + @Bean + SavingAuthenticationProvider savingAuthenticationProvider() { + return new SavingAuthenticationProvider() + } + + /** + * Following configuration is required for unauthorised call tests (form would redirect, we need 401) + */ + @Bean + @Order(1) + SecurityFilterChain apiWebSecurity(HttpSecurity http, SavingAuthenticationProvider savingAuthenticationProvider) { + return http + .csrf().disable() + .securityMatcher("/basicsecured/**") + .authorizeHttpRequests() + .requestMatchers("/basicsecured/**").authenticated() + .and() + .httpBasic() + .and() + .authenticationProvider(savingAuthenticationProvider) + .build() + } + + /** + * Following configuration is required in order to get form login, needed by password tests + */ + @Bean + SecurityFilterChain formLoginWebSecurity(HttpSecurity http, SavingAuthenticationProvider savingAuthenticationProvider) { + return http + .csrf().disable() + .authorizeHttpRequests() + .requestMatchers("/formsecured/**").authenticated() + .anyRequest().permitAll() + .and() + .formLogin() + .and() + .authenticationProvider(savingAuthenticationProvider) + .build() + } +} diff --git a/instrumentation/spring/spring-webmvc/spring-webmvc-6.0/javaagent/src/test/groovy/SpringBootBasedTest.groovy b/instrumentation/spring/spring-webmvc/spring-webmvc-6.0/javaagent/src/test/groovy/SpringBootBasedTest.groovy new file mode 100644 index 000000000000..9b647b549687 --- /dev/null +++ b/instrumentation/spring/spring-webmvc/spring-webmvc-6.0/javaagent/src/test/groovy/SpringBootBasedTest.groovy @@ -0,0 +1,13 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +import boot.AbstractSpringBootBasedTest + +class SpringBootBasedTest extends AbstractSpringBootBasedTest { + + Class securityConfigClass() { + SecurityConfig + } +} diff --git a/instrumentation/spring/spring-webmvc/spring-webmvc-common/javaagent/build.gradle.kts b/instrumentation/spring/spring-webmvc/spring-webmvc-common/javaagent/build.gradle.kts new file mode 100644 index 000000000000..ccf8ba94553c --- /dev/null +++ b/instrumentation/spring/spring-webmvc/spring-webmvc-common/javaagent/build.gradle.kts @@ -0,0 +1,9 @@ +plugins { + id("otel.javaagent-instrumentation") +} + +dependencies { + bootstrap(project(":instrumentation:servlet:servlet-common:bootstrap")) + + compileOnly("org.springframework:spring-webmvc:3.1.0.RELEASE") +} diff --git a/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webmvc/v3_1/HandlerSpanNameExtractor.java b/instrumentation/spring/spring-webmvc/spring-webmvc-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webmvc/HandlerSpanNameExtractor.java similarity index 85% rename from instrumentation/spring/spring-webmvc/spring-webmvc-3.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webmvc/v3_1/HandlerSpanNameExtractor.java rename to instrumentation/spring/spring-webmvc/spring-webmvc-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webmvc/HandlerSpanNameExtractor.java index 267c6790c9bb..bbc68fea19f9 100644 --- a/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webmvc/v3_1/HandlerSpanNameExtractor.java +++ b/instrumentation/spring/spring-webmvc/spring-webmvc-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webmvc/HandlerSpanNameExtractor.java @@ -3,12 +3,11 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.spring.webmvc.v3_1; +package io.opentelemetry.javaagent.instrumentation.spring.webmvc; import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor; import io.opentelemetry.instrumentation.api.instrumenter.util.SpanNames; import java.lang.reflect.Method; -import javax.servlet.Servlet; import org.springframework.web.HttpRequestHandler; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.mvc.Controller; @@ -32,7 +31,7 @@ public String extract(Object handler) { // org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter clazz = handler.getClass(); methodName = "handleRequest"; - } else if (handler instanceof Servlet) { + } else if (isServlet(handler)) { // org.springframework.web.servlet.handler.SimpleServletHandlerAdapter clazz = handler.getClass(); methodName = "service"; @@ -44,4 +43,10 @@ public String extract(Object handler) { return SpanNames.fromMethod(clazz, methodName); } + + private static boolean isServlet(Object handler) { + String handlerClassName = handler.getClass().getName(); + return handlerClassName.equals("javax.servlet.Servlet") + || handlerClassName.equals("jakarta.servlet.Servlet"); + } } diff --git a/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webmvc/v3_1/IsGrailsHandler.java b/instrumentation/spring/spring-webmvc/spring-webmvc-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webmvc/IsGrailsHandler.java similarity index 98% rename from instrumentation/spring/spring-webmvc/spring-webmvc-3.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webmvc/v3_1/IsGrailsHandler.java rename to instrumentation/spring/spring-webmvc/spring-webmvc-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webmvc/IsGrailsHandler.java index c29d75535634..dd0fdfb9798d 100644 --- a/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webmvc/v3_1/IsGrailsHandler.java +++ b/instrumentation/spring/spring-webmvc/spring-webmvc-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webmvc/IsGrailsHandler.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.spring.webmvc.v3_1; +package io.opentelemetry.javaagent.instrumentation.spring.webmvc; public final class IsGrailsHandler { diff --git a/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webmvc/v3_1/ModelAndViewAttributesExtractor.java b/instrumentation/spring/spring-webmvc/spring-webmvc-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webmvc/ModelAndViewAttributesExtractor.java similarity index 99% rename from instrumentation/spring/spring-webmvc/spring-webmvc-3.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webmvc/v3_1/ModelAndViewAttributesExtractor.java rename to instrumentation/spring/spring-webmvc/spring-webmvc-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webmvc/ModelAndViewAttributesExtractor.java index 0ed158684f85..ccfe2f8844b3 100644 --- a/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webmvc/v3_1/ModelAndViewAttributesExtractor.java +++ b/instrumentation/spring/spring-webmvc/spring-webmvc-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webmvc/ModelAndViewAttributesExtractor.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.spring.webmvc.v3_1; +package io.opentelemetry.javaagent.instrumentation.spring.webmvc; import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.context.Context; diff --git a/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webmvc/v3_1/ModelAndViewSpanNameExtractor.java b/instrumentation/spring/spring-webmvc/spring-webmvc-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webmvc/ModelAndViewSpanNameExtractor.java similarity index 99% rename from instrumentation/spring/spring-webmvc/spring-webmvc-3.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webmvc/v3_1/ModelAndViewSpanNameExtractor.java rename to instrumentation/spring/spring-webmvc/spring-webmvc-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webmvc/ModelAndViewSpanNameExtractor.java index 4d3fba901ad7..ae730aaab222 100644 --- a/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webmvc/v3_1/ModelAndViewSpanNameExtractor.java +++ b/instrumentation/spring/spring-webmvc/spring-webmvc-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webmvc/ModelAndViewSpanNameExtractor.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.spring.webmvc.v3_1; +package io.opentelemetry.javaagent.instrumentation.spring.webmvc; import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor; import org.springframework.web.servlet.ModelAndView; diff --git a/instrumentation/spring/spring-webmvc/spring-webmvc-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webmvc/SpringWebMvcInstrumenterFactory.java b/instrumentation/spring/spring-webmvc/spring-webmvc-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webmvc/SpringWebMvcInstrumenterFactory.java new file mode 100644 index 000000000000..68e76d458ff1 --- /dev/null +++ b/instrumentation/spring/spring-webmvc/spring-webmvc-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webmvc/SpringWebMvcInstrumenterFactory.java @@ -0,0 +1,35 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.spring.webmvc; + +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.javaagent.bootstrap.internal.ExperimentalConfig; +import org.springframework.web.servlet.ModelAndView; + +public final class SpringWebMvcInstrumenterFactory { + + private final String instrumentationName; + + public SpringWebMvcInstrumenterFactory(String instrumentationName) { + this.instrumentationName = instrumentationName; + } + + public Instrumenter createHandlerInstrumenter() { + return Instrumenter.builder( + GlobalOpenTelemetry.get(), instrumentationName, new HandlerSpanNameExtractor()) + .setEnabled(ExperimentalConfig.get().controllerTelemetryEnabled()) + .buildInstrumenter(); + } + + public Instrumenter createModelAndViewInstrumenter() { + return Instrumenter.builder( + GlobalOpenTelemetry.get(), instrumentationName, new ModelAndViewSpanNameExtractor()) + .addAttributesExtractor(new ModelAndViewAttributesExtractor()) + .setEnabled(ExperimentalConfig.get().viewTelemetryEnabled()) + .buildInstrumenter(); + } +} diff --git a/instrumentation/spring/spring-webmvc/spring-webmvc-common/testing/build.gradle.kts b/instrumentation/spring/spring-webmvc/spring-webmvc-common/testing/build.gradle.kts new file mode 100644 index 000000000000..725b5dbd7a08 --- /dev/null +++ b/instrumentation/spring/spring-webmvc/spring-webmvc-common/testing/build.gradle.kts @@ -0,0 +1,13 @@ +plugins { + id("otel.java-conventions") +} + +dependencies { + implementation(project(":testing-common")) + + implementation("org.spockframework:spock-spring") + + compileOnly("org.springframework.boot:spring-boot-starter-test:1.5.17.RELEASE") + compileOnly("org.springframework.boot:spring-boot-starter-web:1.5.17.RELEASE") + compileOnly("org.springframework.boot:spring-boot-starter-security:1.5.17.RELEASE") +} diff --git a/instrumentation/spring/spring-webmvc/spring-webmvc-common/testing/src/main/groovy/boot/AbstractSpringBootBasedTest.groovy b/instrumentation/spring/spring-webmvc/spring-webmvc-common/testing/src/main/groovy/boot/AbstractSpringBootBasedTest.groovy new file mode 100644 index 000000000000..c65d4f175292 --- /dev/null +++ b/instrumentation/spring/spring-webmvc/spring-webmvc-common/testing/src/main/groovy/boot/AbstractSpringBootBasedTest.groovy @@ -0,0 +1,208 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package boot + +import io.opentelemetry.api.trace.StatusCode +import io.opentelemetry.instrumentation.test.AgentTestTrait +import io.opentelemetry.instrumentation.test.asserts.TraceAssert +import io.opentelemetry.instrumentation.test.base.HttpServerTest +import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint +import io.opentelemetry.sdk.trace.data.SpanData +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes +import io.opentelemetry.testing.internal.armeria.common.AggregatedHttpRequest +import io.opentelemetry.testing.internal.armeria.common.HttpData +import io.opentelemetry.testing.internal.armeria.common.MediaType +import io.opentelemetry.testing.internal.armeria.common.QueryParams +import org.springframework.boot.SpringApplication +import org.springframework.context.ConfigurableApplicationContext +import org.springframework.security.web.util.OnCommittedResponseWrapper +import org.springframework.web.servlet.view.RedirectView + +import java.util.regex.Pattern + +import static io.opentelemetry.api.trace.SpanKind.INTERNAL +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.AUTH_ERROR +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.LOGIN +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.NOT_FOUND +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.PATH_PARAM +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.REDIRECT +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.SUCCESS + +abstract class AbstractSpringBootBasedTest extends HttpServerTest implements AgentTestTrait { + + abstract Class securityConfigClass() + + @Override + ConfigurableApplicationContext startServer(int port) { + def app = new SpringApplication(AppConfig, securityConfigClass()) + app.setDefaultProperties([ + "server.port" : port, + "server.context-path" : getContextPath(), + "server.servlet.contextPath" : getContextPath(), + "server.error.include-message": "always", + ]) + def context = app.run() + return context + } + + @Override + void stopServer(ConfigurableApplicationContext ctx) { + ctx.close() + } + + @Override + String getContextPath() { + return "/xyz" + } + + @Override + boolean hasHandlerSpan(ServerEndpoint endpoint) { + true + } + + @Override + boolean hasRenderSpan(ServerEndpoint endpoint) { + endpoint == REDIRECT + } + + @Override + boolean hasResponseSpan(ServerEndpoint endpoint) { + endpoint == REDIRECT || endpoint == NOT_FOUND + } + + @Override + boolean testPathParam() { + true + } + + @Override + boolean hasErrorPageSpans(ServerEndpoint endpoint) { + endpoint == NOT_FOUND + } + + @Override + String expectedHttpRoute(ServerEndpoint endpoint) { + switch (endpoint) { + case PATH_PARAM: + return getContextPath() + "/path/{id}/param" + case NOT_FOUND: + return getContextPath() + "/**" + case LOGIN: + return getContextPath() + "/*" + default: + return super.expectedHttpRoute(endpoint) + } + } + + def "test spans with auth error"() { + setup: + def authProvider = server.getBean(SavingAuthenticationProvider) + def request = request(AUTH_ERROR, "GET") + + when: + authProvider.latestAuthentications.clear() + def response = client.execute(request).aggregate().join() + + then: + response.status().code() == 401 // not secured + + and: + assertTraces(1) { + trace(0, 3) { + serverSpan(it, 0, null, null, "GET", null, AUTH_ERROR) + sendErrorSpan(it, 1, span(0)) + errorPageSpans(it, 2, null) + } + } + } + + def "test character encoding of #testPassword"() { + setup: + def authProvider = server.getBean(SavingAuthenticationProvider) + + QueryParams form = QueryParams.of("username", "test", "password", testPassword) + def request = AggregatedHttpRequest.of( + request(LOGIN, "POST").headers().toBuilder().contentType(MediaType.FORM_DATA).build(), + HttpData.ofUtf8(form.toQueryString())) + + when: + authProvider.latestAuthentications.clear() + def response = client.execute(request).aggregate().join() + + then: + response.status().code() == 302 // redirect after success + authProvider.latestAuthentications.get(0).password == testPassword + + and: + assertTraces(1) { + trace(0, 2) { + serverSpan(it, 0, null, null, "POST", response.contentUtf8().length(), LOGIN) + redirectSpan(it, 1, span(0)) + } + } + + where: + testPassword << ["password", "dfsdföääöüüä", "🤓"] + } + + @Override + void errorPageSpans(TraceAssert trace, int index, Object parent, String method = "GET", ServerEndpoint endpoint = SUCCESS) { + trace.span(index) { + name "BasicErrorController.error" + kind INTERNAL + attributes { + } + } + } + + @Override + void responseSpan(TraceAssert trace, int index, Object parent, String method = "GET", ServerEndpoint endpoint = SUCCESS) { + def methodName = endpoint == NOT_FOUND ? "sendError" : "sendRedirect" + // the response wrapper class names vary depending on spring version & scenario + def namePattern = Pattern.compile("(OnCommittedResponseWrapper|HttpServletResponseWrapper|FirewalledResponse).$methodName") + trace.span(index) { + name namePattern + kind INTERNAL + attributes { + "$SemanticAttributes.CODE_NAMESPACE" { + it == OnCommittedResponseWrapper.name + || it == "org.springframework.security.web.firewall.FirewalledResponse" + || it == "jakarta.servlet.http.HttpServletResponseWrapper" + } + "$SemanticAttributes.CODE_FUNCTION" methodName + } + } + } + + @Override + void renderSpan(TraceAssert trace, int index, Object parent, String method = "GET", ServerEndpoint endpoint = SUCCESS) { + trace.span(index) { + name "Render RedirectView" + kind INTERNAL + attributes { + "spring-webmvc.view.type" RedirectView.name + } + } + } + + @Override + void handlerSpan(TraceAssert trace, int index, Object parent, String method = "GET", ServerEndpoint endpoint = SUCCESS) { + def handlerSpanName = "TestController.${endpoint.name().toLowerCase()}" + if (endpoint == NOT_FOUND) { + handlerSpanName = "ResourceHttpRequestHandler.handleRequest" + } + trace.span(index) { + name handlerSpanName + kind INTERNAL + if (endpoint == EXCEPTION) { + status StatusCode.ERROR + errorEvent(Exception, EXCEPTION.body) + } + childOf((SpanData) parent) + } + } +} diff --git a/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/javaagent/src/test/groovy/test/boot/AppConfig.groovy b/instrumentation/spring/spring-webmvc/spring-webmvc-common/testing/src/main/groovy/boot/AppConfig.groovy similarity index 54% rename from instrumentation/spring/spring-webmvc/spring-webmvc-3.1/javaagent/src/test/groovy/test/boot/AppConfig.groovy rename to instrumentation/spring/spring-webmvc/spring-webmvc-common/testing/src/main/groovy/boot/AppConfig.groovy index 99fa2366adf5..48563b125e7b 100644 --- a/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/javaagent/src/test/groovy/test/boot/AppConfig.groovy +++ b/instrumentation/spring/spring-webmvc/spring-webmvc-common/testing/src/main/groovy/boot/AppConfig.groovy @@ -3,12 +3,11 @@ * SPDX-License-Identifier: Apache-2.0 */ -package test.boot +package boot import org.springframework.boot.autoconfigure.SpringBootApplication -import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter @SpringBootApplication -class AppConfig extends WebMvcConfigurerAdapter { +class AppConfig { } diff --git a/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/javaagent/src/test/groovy/test/boot/SavingAuthenticationProvider.groovy b/instrumentation/spring/spring-webmvc/spring-webmvc-common/testing/src/main/groovy/boot/SavingAuthenticationProvider.groovy similarity index 99% rename from instrumentation/spring/spring-webmvc/spring-webmvc-3.1/javaagent/src/test/groovy/test/boot/SavingAuthenticationProvider.groovy rename to instrumentation/spring/spring-webmvc/spring-webmvc-common/testing/src/main/groovy/boot/SavingAuthenticationProvider.groovy index 2c87e396c481..9ae426dc13bb 100644 --- a/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/javaagent/src/test/groovy/test/boot/SavingAuthenticationProvider.groovy +++ b/instrumentation/spring/spring-webmvc/spring-webmvc-common/testing/src/main/groovy/boot/SavingAuthenticationProvider.groovy @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package test.boot +package boot import org.springframework.security.authentication.UsernamePasswordAuthenticationToken import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider diff --git a/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/javaagent/src/test/groovy/test/boot/TestController.groovy b/instrumentation/spring/spring-webmvc/spring-webmvc-common/testing/src/main/groovy/boot/TestController.groovy similarity index 99% rename from instrumentation/spring/spring-webmvc/spring-webmvc-3.1/javaagent/src/test/groovy/test/boot/TestController.groovy rename to instrumentation/spring/spring-webmvc/spring-webmvc-common/testing/src/main/groovy/boot/TestController.groovy index 605b89ea3e92..2489a783e6f0 100644 --- a/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/javaagent/src/test/groovy/test/boot/TestController.groovy +++ b/instrumentation/spring/spring-webmvc/spring-webmvc-common/testing/src/main/groovy/boot/TestController.groovy @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package test.boot +package boot import io.opentelemetry.instrumentation.test.base.HttpServerTest import org.springframework.http.HttpStatus diff --git a/settings.gradle.kts b/settings.gradle.kts index f43fc229cc0b..f65ec1b4925f 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -447,9 +447,13 @@ hideFromDependabot(":instrumentation:spring:spring-scheduling-3.1:javaagent") hideFromDependabot(":instrumentation:spring:spring-web:spring-web-3.1:javaagent") hideFromDependabot(":instrumentation:spring:spring-web:spring-web-3.1:library") hideFromDependabot(":instrumentation:spring:spring-web:spring-web-3.1:testing") +hideFromDependabot(":instrumentation:spring:spring-web:spring-web-6.0:javaagent") hideFromDependabot(":instrumentation:spring:spring-webmvc:spring-webmvc-3.1:javaagent") hideFromDependabot(":instrumentation:spring:spring-webmvc:spring-webmvc-3.1:wildfly-testing") hideFromDependabot(":instrumentation:spring:spring-webmvc:spring-webmvc-5.3:library") +hideFromDependabot(":instrumentation:spring:spring-webmvc:spring-webmvc-6.0:javaagent") +hideFromDependabot(":instrumentation:spring:spring-webmvc:spring-webmvc-common:javaagent") +hideFromDependabot(":instrumentation:spring:spring-webmvc:spring-webmvc-common:testing") hideFromDependabot(":instrumentation:spring:spring-webflux-5.0:javaagent") hideFromDependabot(":instrumentation:spring:spring-webflux-5.0:library") hideFromDependabot(":instrumentation:spring:spring-ws-2.0:javaagent")