Fix Spring handler mapping affecting character encoding (DataDog/dd-trace-java#1595)
MODIFY
agent-tooling/src/main/java/io/opentelemetry/auto/tooling/matcher/AdditionalLibraryIgnoresMatcher.java
agent-tooling/src/main/java/io/opentelemetry/auto/tooling/matcher/AdditionalLibraryIgnoresMatcher.java
@@ -116,7 +116,11 @@ public class AdditionalLibraryIgnoresMatcher<T extends TypeDescription>
|| name.startsWith(
"org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainer$")
|| name.equals(
- "org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedWebappClassLoader")) {
+ "org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedWebappClassLoader")
+ || name.equals(
+ "org.springframework.boot.context.embedded.EmbeddedWebApplicationContext")
+ || name.equals(
+ "org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext")) {
return false;
}
return true;
@@ -183,7 +187,11 @@ public class AdditionalLibraryIgnoresMatcher<T extends TypeDescription>
if (name.startsWith("org.springframework.web.")) {
if (name.startsWith("org.springframework.web.servlet.")
|| name.startsWith("org.springframework.web.reactive.")
- || name.startsWith("org.springframework.web.context.request.async.")) {
+ || name.startsWith("org.springframework.web.context.request.async.")
+ || name.equals(
+ "org.springframework.web.context.support.AbstractRefreshableWebApplicationContext")
+ || name.equals("org.springframework.web.context.support.GenericWebApplicationContext")
+ || name.equals("org.springframework.web.context.support.XmlWebApplicationContext")) {
return false;
}
return true;

MODIFY
instrumentation/servlet/servlet-3.0/src/main/java/io/opentelemetry/auto/instrumentation/servlet/v3_0/Servlet3HttpServerTracer.java
instrumentation/servlet/servlet-3.0/src/main/java/io/opentelemetry/auto/instrumentation/servlet/v3_0/Servlet3HttpServerTracer.java
package io.opentelemetry.auto.instrumentation.servlet.v3_0;
import static io.opentelemetry.trace.TracingContextUtils.getSpan;
-import static io.opentelemetry.trace.TracingContextUtils.withSpan;
import io.grpc.Context;
import io.opentelemetry.auto.instrumentation.servlet.ServletHttpServerTracer;
import io.opentelemetry.trace.Span;
import io.opentelemetry.trace.Status;
-import java.io.IOException;
-import javax.servlet.Filter;
-import javax.servlet.ServletContext;
-import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
@@ -66,39 +61,6 @@ public class Servlet3HttpServerTracer extends ServletHttpServerTracer {
return !sameTrace(getSpan(Context.current()), getSpan(attachedContext));
}
- @Override
- public void onRequest(Span span, HttpServletRequest request) {
- super.onRequest(span, request);
- onContext(span, request, request.getServletContext());
- }
-
- /**
- * This method executes the filter created by
- * io.opentelemetry.auto.instrumentation.springwebmvc.DispatcherServletInstrumentation$HandlerMappingAdvice.
- * This was easier and less "hacky" than other ways to add the filter to the front of the filter
- * chain.
- */
- // TODO review this hacky-hacky
- private void onContext(
- final Span span, final HttpServletRequest request, final ServletContext servletContext) {
- if (servletContext == null) {
- // some frameworks (jetty) may return a null context.
- return;
- }
- final Object attribute = servletContext.getAttribute("io.opentelemetry.auto.dispatcher-filter");
- if (attribute instanceof Filter) {
- final Object priorAttr = request.getAttribute(CONTEXT_ATTRIBUTE);
- request.setAttribute(CONTEXT_ATTRIBUTE, withSpan(span, Context.current()));
- try {
- ((Filter) attribute).doFilter(request, null, null);
- } catch (final IOException | ServletException e) {
- log.debug("Exception unexpectedly thrown by filter", e);
- } finally {
- request.setAttribute(CONTEXT_ATTRIBUTE, priorAttr);
- }
- }
- }
-
private static boolean sameTrace(final Span oneSpan, final Span otherSpan) {
return oneSpan.getContext().getTraceId().equals(otherSpan.getContext().getTraceId());
}

MODIFY
instrumentation/servlet/servlet-3.0/src/test/groovy/AbstractServlet3Test.groovy
instrumentation/servlet/servlet-3.0/src/test/groovy/AbstractServlet3Test.groovy
@@ -20,6 +20,7 @@ import io.opentelemetry.auto.test.asserts.TraceAssert
import io.opentelemetry.auto.test.base.HttpServerTest
import io.opentelemetry.trace.Span
import okhttp3.Request
+import okhttp3.RequestBody
import javax.servlet.Servlet
@@ -64,7 +65,7 @@ abstract class AbstractServlet3Test<SERVER, CONTEXT> extends HttpServerTest<SERV
protected ServerEndpoint lastRequest
@Override
- Request.Builder request(ServerEndpoint uri, String method, String body) {
+ Request.Builder request(ServerEndpoint uri, String method, RequestBody body) {
lastRequest = uri
super.request(uri, method, body)
}

MODIFY
instrumentation/spring-webmvc-3.1/spring-webmvc-3.1.gradle
instrumentation/spring-webmvc-3.1/spring-webmvc-3.1.gradle
@@ -42,4 +42,10 @@ dependencies {
testCompile group: 'org.springframework.boot', name: 'spring-boot-starter-test', version: '1.5.17.RELEASE'
testCompile group: 'org.springframework.boot', name: 'spring-boot-starter-web', version: '1.5.17.RELEASE'
+ testCompile group: 'org.springframework.boot', name: 'spring-boot-starter-security', version: '1.5.17.RELEASE'
+ testCompile group: 'org.springframework.security.oauth', name: 'spring-security-oauth2', version: '2.0.16.RELEASE'
+
+ // For spring security
+ testCompile "jakarta.xml.bind:jakarta.xml.bind-api:2.3.2"
+ testCompile "org.glassfish.jaxb:jaxb-runtime:2.3.2"
}

MODIFY
instrumentation/spring-webmvc-3.1/src/main/java/io/opentelemetry/auto/instrumentation/springwebmvc/DispatcherServletInstrumentation.java
instrumentation/spring-webmvc-3.1/src/main/java/io/opentelemetry/auto/instrumentation/springwebmvc/DispatcherServletInstrumentation.java
@@ -19,7 +19,6 @@ package io.opentelemetry.auto.instrumentation.springwebmvc;
import static io.opentelemetry.auto.instrumentation.springwebmvc.SpringWebMvcDecorator.DECORATE;
import static io.opentelemetry.auto.instrumentation.springwebmvc.SpringWebMvcDecorator.TRACER;
import static io.opentelemetry.trace.TracingContextUtils.currentContextWith;
-import static java.util.Collections.singletonMap;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.isProtected;
import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith;
@@ -28,21 +27,17 @@ import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
import com.google.auto.service.AutoService;
-import io.opentelemetry.auto.bootstrap.ContextStore;
-import io.opentelemetry.auto.bootstrap.InstrumentationContext;
import io.opentelemetry.auto.instrumentation.api.SpanWithScope;
import io.opentelemetry.auto.tooling.Instrumenter;
import io.opentelemetry.trace.Span;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import javax.servlet.ServletContext;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
import org.springframework.context.ApplicationContext;
-import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.ModelAndView;
@@ -58,13 +53,6 @@ public final class DispatcherServletInstrumentation extends Instrumenter.Default
return named("org.springframework.web.servlet.DispatcherServlet");
}
- @Override
- public Map<String, String> contextStore() {
- return singletonMap(
- "org.springframework.web.servlet.DispatcherServlet",
- packageName + ".HandlerMappingResourceNameFilter");
- }
-
@Override
public String[] helperClassNames() {
return new String[] {
@@ -106,23 +94,14 @@ public final class DispatcherServletInstrumentation extends Instrumenter.Default
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void afterRefresh(
- @Advice.This final DispatcherServlet dispatcher,
@Advice.Argument(0) final ApplicationContext springCtx,
- @Advice.FieldValue("handlerMappings") final List<HandlerMapping> handlerMappings,
- @Advice.Thrown final Throwable throwable) {
- final ServletContext servletContext = springCtx.getBean(ServletContext.class);
- if (handlerMappings != null && servletContext != null) {
- final ContextStore<DispatcherServlet, HandlerMappingResourceNameFilter> contextStore =
- InstrumentationContext.get(
- DispatcherServlet.class, HandlerMappingResourceNameFilter.class);
- HandlerMappingResourceNameFilter filter = contextStore.get(dispatcher);
- if (filter == null) {
- filter = new HandlerMappingResourceNameFilter();
- contextStore.put(dispatcher, filter);
- }
+ @Advice.FieldValue("handlerMappings") final List<HandlerMapping> handlerMappings) {
+ if (springCtx.containsBean("otelAutoDispatcherFilter")) {
+ final HandlerMappingResourceNameFilter filter =
+ (HandlerMappingResourceNameFilter) springCtx.getBean("otelAutoDispatcherFilter");
+ if (handlerMappings != null && filter != null) {
filter.setHandlerMappings(handlerMappings);
- // attribute used by Servlet3Decorator.onContext
- servletContext.setAttribute("io.opentelemetry.auto.dispatcher-filter", filter);
+ }
}
}
}

MODIFY
instrumentation/spring-webmvc-3.1/src/main/java/io/opentelemetry/auto/instrumentation/springwebmvc/HandlerMappingResourceNameFilter.java
instrumentation/spring-webmvc-3.1/src/main/java/io/opentelemetry/auto/instrumentation/springwebmvc/HandlerMappingResourceNameFilter.java
@@ -20,51 +20,55 @@ import static io.opentelemetry.auto.bootstrap.instrumentation.decorator.HttpServ
import static io.opentelemetry.auto.instrumentation.springwebmvc.SpringWebMvcDecorator.DECORATE;
import io.grpc.Context;
+import io.opentelemetry.trace.Span;
import io.opentelemetry.trace.TracingContextUtils;
+import java.io.IOException;
import java.util.List;
-import javax.servlet.Filter;
import javax.servlet.FilterChain;
-import javax.servlet.FilterConfig;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
+import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.springframework.beans.factory.support.GenericBeanDefinition;
+import org.springframework.core.Ordered;
+import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.HandlerMapping;
-public class HandlerMappingResourceNameFilter implements Filter {
+public class HandlerMappingResourceNameFilter extends OncePerRequestFilter implements Ordered {
private volatile List<HandlerMapping> handlerMappings;
@Override
- public void init(final FilterConfig filterConfig) {}
+ protected void doFilterInternal(
+ final HttpServletRequest request,
+ final HttpServletResponse response,
+ final FilterChain filterChain)
+ throws ServletException, IOException {
- @Override
- public void doFilter(
- final ServletRequest servletRequest,
- final ServletResponse servletResponse,
- final FilterChain filterChain) {
- if (servletRequest instanceof HttpServletRequest && handlerMappings != null) {
- final HttpServletRequest request = (HttpServletRequest) servletRequest;
+ final Object parentContext = request.getAttribute(CONTEXT_ATTRIBUTE);
+ Span parentSpan = null;
+ if (parentContext instanceof Context) {
+ parentSpan = TracingContextUtils.getSpan((Context) parentContext);
+ }
+
+ if (handlerMappings != null && parentSpan != null) {
try {
if (findMapping(request)) {
// Name the parent span based on the matching pattern
- final Object parentContext = request.getAttribute(CONTEXT_ATTRIBUTE);
- if (parentContext instanceof Context) {
// Let the parent span resource name be set with the attribute set in findMapping.
- DECORATE.onRequest(TracingContextUtils.getSpan((Context) parentContext), request);
+ DECORATE.onRequest(parentSpan, request);
}
+ } catch (final Exception ignored) {
+ // mapping.getHandler() threw exception. Ignore
}
- } catch (final Exception e) {
- }
+
+ filterChain.doFilter(request, response);
}
}
- @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
- * SpringWebHttpServerDecorator.onRequest and set as the resource name.
+ * as an attribute on the request. This attribute is read by SpringWebMvcDecorator.onRequest and
+ * set as the resource name.
*/
private boolean findMapping(final HttpServletRequest request) throws Exception {
for (final HandlerMapping mapping : handlerMappings) {
@@ -79,4 +83,18 @@ public class HandlerMappingResourceNameFilter implements Filter {
public void setHandlerMappings(final List<HandlerMapping> handlerMappings) {
this.handlerMappings = handlerMappings;
}
+
+ @Override
+ public int getOrder() {
+ // Run after all HIGHEST_PRECEDENCE items
+ return Ordered.HIGHEST_PRECEDENCE + 1;
+ }
+
+ public static class BeanDefinition extends GenericBeanDefinition {
+ public BeanDefinition() {
+ setScope(SCOPE_SINGLETON);
+ setBeanClass(HandlerMappingResourceNameFilter.class);
+ setBeanClassName(HandlerMappingResourceNameFilter.class.getName());
+ }
+ }
}

ADD
instrumentation/spring-webmvc-3.1/src/main/java/io/opentelemetry/auto/instrumentation/springwebmvc/WebApplicationContextInstrumentation.java
+/*
+ * Copyright The OpenTelemetry Authors
+ *
+ * Licensed 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 io.opentelemetry.auto.instrumentation.springwebmvc;
+
+import static io.opentelemetry.auto.tooling.ClassLoaderMatcher.hasClassesNamed;
+import static io.opentelemetry.auto.tooling.bytebuddy.matcher.AgentElementMatchers.extendsClass;
+import static io.opentelemetry.auto.tooling.bytebuddy.matcher.AgentElementMatchers.implementsInterface;
+import static java.util.Collections.singletonMap;
+import static net.bytebuddy.matcher.ElementMatchers.isMethod;
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
+
+import com.google.auto.service.AutoService;
+import io.opentelemetry.auto.tooling.Instrumenter;
+import java.util.Map;
+import net.bytebuddy.asm.Advice;
+import net.bytebuddy.description.method.MethodDescription;
+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;
+
+@AutoService(Instrumenter.class)
+public class WebApplicationContextInstrumentation extends Instrumenter.Default {
+ public WebApplicationContextInstrumentation() {
+ super("spring-web");
+ }
+
+ @Override
+ public ElementMatcher<ClassLoader> classLoaderMatcher() {
+ // Optimization for expensive typeMatcher.
+ return hasClassesNamed(
+ "org.springframework.context.support.AbstractApplicationContext",
+ "org.springframework.web.context.WebApplicationContext");
+ }
+
+ @Override
+ public ElementMatcher<TypeDescription> typeMatcher() {
+ return extendsClass(named("org.springframework.context.support.AbstractApplicationContext"))
+ .and(implementsInterface(named("org.springframework.web.context.WebApplicationContext")));
+ }
+
+ @Override
+ public String[] helperClassNames() {
+ return new String[] {
+ packageName + ".SpringWebMvcDecorator",
+ packageName + ".HandlerMappingResourceNameFilter",
+ packageName + ".HandlerMappingResourceNameFilter$BeanDefinition",
+ };
+ }
+
+ @Override
+ public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
+
+ return singletonMap(
+ isMethod()
+ .and(named("postProcessBeanFactory"))
+ .and(
+ takesArgument(
+ 0,
+ named(
+ "org.springframework.beans.factory.config.ConfigurableListableBeanFactory"))),
+ WebApplicationContextInstrumentation.class.getName() + "$FilterInjectingAdvice");
+ }
+
+ public static class FilterInjectingAdvice {
+ @Advice.OnMethodEnter(suppress = Throwable.class)
+ public static void onEnter(
+ @Advice.Argument(0) final ConfigurableListableBeanFactory beanFactory) {
+ if (beanFactory instanceof BeanDefinitionRegistry
+ && !beanFactory.containsBean("otelAutoDispatcherFilter")) {
+
+ ((BeanDefinitionRegistry) beanFactory)
+ .registerBeanDefinition(
+ "otelAutoDispatcherFilter", new HandlerMappingResourceNameFilter.BeanDefinition());
+ }
+ }
+ }
+}

MODIFY
instrumentation/spring-webmvc-3.1/src/test/groovy/test/boot/AppConfig.groovy
instrumentation/spring-webmvc-3.1/src/test/groovy/test/boot/AppConfig.groovy
@@ -22,30 +22,11 @@ import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory
import org.springframework.boot.context.embedded.tomcat.TomcatConnectorCustomizer
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory
import org.springframework.context.annotation.Bean
-import org.springframework.http.MediaType
-import org.springframework.web.HttpMediaTypeNotAcceptableException
-import org.springframework.web.accept.ContentNegotiationStrategy
-import org.springframework.web.context.request.NativeWebRequest
-import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter
@SpringBootApplication
class AppConfig extends WebMvcConfigurerAdapter {
- @Override
- void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
- configurer.favorPathExtension(false)
- .favorParameter(true)
- .ignoreAcceptHeader(true)
- .useJaf(false)
- .defaultContentTypeStrategy(new ContentNegotiationStrategy() {
- @Override
- List<MediaType> resolveMediaTypes(NativeWebRequest webRequest) throws HttpMediaTypeNotAcceptableException {
- return [MediaType.TEXT_PLAIN]
- }
- })
- }
-
@Bean
EmbeddedServletContainerFactory servletContainerFactory() {
def factory = new TomcatEmbeddedServletContainerFactory()

ADD
instrumentation/spring-webmvc-3.1/src/test/groovy/test/boot/AuthServerConfig.groovy
+/*
+ * Copyright The OpenTelemetry Authors
+ *
+ * Licensed 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 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 {
+}

ADD
instrumentation/spring-webmvc-3.1/src/test/groovy/test/boot/SavingAuthenticationProvider.groovy
+/*
+ * Copyright The OpenTelemetry Authors
+ *
+ * Licensed 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 test.boot
+
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
+import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider
+import org.springframework.security.core.AuthenticationException
+import org.springframework.security.core.GrantedAuthority
+import org.springframework.security.core.userdetails.UserDetails
+
+class SavingAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
+ List<TestUserDetails> latestAuthentications = new ArrayList<>()
+
+ @Override
+ protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
+ // none
+ }
+
+ @Override
+ protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
+ def details = new TestUserDetails(username, authentication.credentials.toString())
+
+ latestAuthentications.add(details)
+
+ return details
+ }
+}
+
+class TestUserDetails implements UserDetails {
+ private final String username
+ private final String password
+
+ TestUserDetails(String username, String password) {
+ this.username = username
+ this.password = password
+ }
+
+ @Override
+ Collection<? extends GrantedAuthority> getAuthorities() {
+ return Collections.emptySet()
+ }
+
+ @Override
+ String getPassword() {
+ return password
+ }
+
+ @Override
+ String getUsername() {
+ return username
+ }
+
+ @Override
+ boolean isAccountNonExpired() {
+ return true
+ }
+
+ @Override
+ boolean isAccountNonLocked() {
+ return true
+ }
+
+ @Override
+ boolean isCredentialsNonExpired() {
+ return true
+ }
+
+ @Override
+ boolean isEnabled() {
+ return true
+ }
+}

ADD
instrumentation/spring-webmvc-3.1/src/test/groovy/test/boot/SecurityConfig.groovy
+/*
+ * Copyright The OpenTelemetry Authors
+ *
+ * Licensed 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 test.boot
+
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
+
+@Configuration
+@EnableWebSecurity
+class SecurityConfig extends WebSecurityConfigurerAdapter {
+
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+
+ http
+ .authorizeRequests()
+ .antMatchers("/secure/**").authenticated()
+ .and().formLogin()
+ .and().authenticationProvider(applicationContext.getBean(SavingAuthenticationProvider))
+ .csrf().disable()
+ }
+
+ @Bean
+ SavingAuthenticationProvider savingAuthenticationProvider() {
+ return new SavingAuthenticationProvider()
+ }
+}

MODIFY
instrumentation/spring-webmvc-3.1/src/test/groovy/test/filter/ServletFilterTest.groovy
instrumentation/spring-webmvc-3.1/src/test/groovy/test/filter/ServletFilterTest.groovy
@@ -24,6 +24,7 @@ import io.opentelemetry.sdk.trace.data.SpanData
import org.apache.catalina.core.ApplicationFilterChain
import org.springframework.boot.SpringApplication
import org.springframework.context.ConfigurableApplicationContext
+import test.boot.SecurityConfig
import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.ERROR
import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.EXCEPTION
@@ -38,7 +39,7 @@ class ServletFilterTest extends HttpServerTest<ConfigurableApplicationContext> {
@Override
ConfigurableApplicationContext startServer(int port) {
- def app = new SpringApplication(FilteredAppConfig)
+ def app = new SpringApplication(FilteredAppConfig, SecurityConfig)
app.setDefaultProperties(singletonMap("server.port", port))
def context = app.run()
return context
@@ -93,21 +94,6 @@ class ServletFilterTest extends HttpServerTest<ConfigurableApplicationContext> {
}
}
- @Override
- void handlerSpan(TraceAssert trace, int index, Object parent, String method = "GET", ServerEndpoint endpoint = SUCCESS) {
- trace.span(index) {
- operationName "TestController.${endpoint.name().toLowerCase()}"
- spanKind INTERNAL
- errored endpoint == EXCEPTION
- childOf((SpanData) parent)
- tags {
- if (endpoint == EXCEPTION) {
- errorTags(Exception, EXCEPTION.body)
- }
- }
- }
- }
-
@Override
void serverSpan(TraceAssert trace, int index, String traceID = null, String parentID = null, String method = "GET", ServerEndpoint endpoint = SUCCESS) {
trace.span(index) {

MODIFY
instrumentation/spring-webmvc-3.1/src/test/groovy/test/filter/TestController.groovy
instrumentation/spring-webmvc-3.1/src/test/groovy/test/filter/TestController.groovy
package test.filter
-import io.opentelemetry.auto.test.base.HttpServerTest
-import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.stereotype.Controller
import org.springframework.web.bind.annotation.ExceptionHandler
@@ -27,64 +25,45 @@ import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.ResponseBody
import org.springframework.web.servlet.view.RedirectView
-import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.ERROR
-import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.EXCEPTION
-import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.PATH_PARAM
-import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.QUERY_PARAM
-import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.REDIRECT
-import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.SUCCESS
-
@Controller
class TestController {
@RequestMapping("/success")
@ResponseBody
String success() {
- HttpServerTest.controller(SUCCESS) {
- SUCCESS.body
- }
+ // stub to test that route is captured when intercepted by filter
}
@RequestMapping("/query")
@ResponseBody
String query_param(@RequestParam("some") String param) {
- HttpServerTest.controller(QUERY_PARAM) {
- "some=$param"
- }
+ // stub to test that route is captured when intercepted by filter
}
@RequestMapping("/path/{id}/param")
@ResponseBody
String path_param(@PathVariable Integer id) {
- HttpServerTest.controller(PATH_PARAM) {
- "$id"
- }
+ // stub to test that route is captured when intercepted by filter
}
@RequestMapping("/redirect")
@ResponseBody
RedirectView redirect() {
- HttpServerTest.controller(REDIRECT) {
- new RedirectView(REDIRECT.body)
- }
+ // stub to test that route is captured when intercepted by filter
}
@RequestMapping("/error-status")
ResponseEntity error() {
- HttpServerTest.controller(ERROR) {
- new ResponseEntity(ERROR.body, HttpStatus.valueOf(ERROR.status))
- }
+ // stub to test that route is captured when intercepted by filter
}
@RequestMapping("/exception")
ResponseEntity exception() {
- HttpServerTest.controller(EXCEPTION) {
- throw new Exception(EXCEPTION.body)
- }
+ // stub to test that route is captured when intercepted by filter
}
@ExceptionHandler
ResponseEntity handleException(Throwable throwable) {
- new ResponseEntity(throwable.message, HttpStatus.INTERNAL_SERVER_ERROR)
+ // stub to test that route is captured when intercepted by filter
}
}

MODIFY
testing/src/main/groovy/io/opentelemetry/auto/test/base/HttpServerTest.groovy
testing/src/main/groovy/io/opentelemetry/auto/test/base/HttpServerTest.groovy
@@ -26,16 +26,17 @@ import io.opentelemetry.auto.test.utils.OkHttpUtils
import io.opentelemetry.auto.test.utils.PortUtils
import io.opentelemetry.sdk.trace.data.SpanData
import io.opentelemetry.trace.Span
-import java.util.concurrent.Callable
import okhttp3.HttpUrl
import okhttp3.OkHttpClient
import okhttp3.Request
+import okhttp3.RequestBody
import okhttp3.Response
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import spock.lang.Shared
import spock.lang.Unroll
+import java.util.concurrent.Callable
import java.util.concurrent.atomic.AtomicBoolean
import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.ERROR
@@ -152,6 +153,7 @@ abstract class HttpServerTest<SERVER> extends AgentTestRunner {
// QUERY_FRAGMENT_PARAM("query/fragment?some=query#some-fragment", 200, "some=query#some-fragment"),
PATH_PARAM("path/123/param", 200, "123"),
AUTH_REQUIRED("authRequired", 200, null),
+ LOGIN("login", 302, null),
private final URI uriObj
private final String path
@@ -199,7 +201,7 @@ abstract class HttpServerTest<SERVER> extends AgentTestRunner {
}
}
- Request.Builder request(ServerEndpoint uri, String method, String body) {
+ Request.Builder request(ServerEndpoint uri, String method, RequestBody body) {
def url = HttpUrl.get(uri.resolvePath(address)).newBuilder()
.query(uri.query)
.fragment(uri.fragment)

Fix Spring handler mapping affecting character encoding (DataDog/dd-trace-java#1595)