diff --git a/docs/src/main/asciidoc/_configprops.adoc b/docs/src/main/asciidoc/_configprops.adoc index 95f1c0a35..2fa255bb1 100644 --- a/docs/src/main/asciidoc/_configprops.adoc +++ b/docs/src/main/asciidoc/_configprops.adoc @@ -70,6 +70,7 @@ |spring.sleuth.web.ignore-auto-configured-skip-patterns | `false` | If set to true, auto-configured skip patterns will be ignored. |spring.sleuth.web.servlet.enabled | `true` | Enable servlet instrumentation. |spring.sleuth.web.skip-pattern | `/api-docs.*\|/swagger.*\|.*\.png\|.*\.css\|.*\.js\|.*\.html\|/favicon.ico\|/hystrix.stream` | Pattern for URLs that should be skipped in tracing. +|spring.sleuth.web.tomcat.enabled | `true` | Enable tracing instrumentation for Tomcat. |spring.sleuth.web.webclient.enabled | `true` | Enable tracing instrumentation for WebClient. |spring.zipkin.activemq.message-max-bytes | `100000` | Maximum number of bytes for a given message with spans sent to Zipkin over ActiveMQ. |spring.zipkin.activemq.queue | `zipkin` | Name of the ActiveMQ queue where spans should be sent to Zipkin. diff --git a/docs/src/main/asciidoc/integrations.adoc b/docs/src/main/asciidoc/integrations.adoc index 4127620f7..8c6368eee 100644 --- a/docs/src/main/asciidoc/integrations.adoc +++ b/docs/src/main/asciidoc/integrations.adoc @@ -635,7 +635,6 @@ This feature is available for all tracer implementations. If you have R2DBC Proxy on the classpath we will instrument the `ConnectionFactory`so that it contains a custom `ProxyExecutionListener`. In order to disable this instrumentation set `spring.sleuth.r2dbc.enabled` to `false`. - [[sleuth-vault-integration]] == Spring Vault @@ -643,3 +642,11 @@ This feature is available for all tracer implementations. We're instrumenting the `RestTemplate` or `WebClient` instances used by Spring Vault to communicate with Vault. In order to disable this instrumentation set `spring.sleuth.vault.enabled` to `false`. + +[[sleuth-tomcat-integration]] +== Spring Tomcat + +This feature is available for all tracer implementations. + +We're adding an instrumented Tomcat's `Valve` that originates the span. +In order to disable this instrumentation set `spring.sleuth.web.tomcat.enabled` to `false`. diff --git a/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/web/TraceWebServletConfiguration.java b/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/web/TraceWebServletConfiguration.java index 1c0e73d5f..abb21771a 100644 --- a/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/web/TraceWebServletConfiguration.java +++ b/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/web/TraceWebServletConfiguration.java @@ -26,10 +26,14 @@ import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; +import org.apache.catalina.Valve; + import org.springframework.beans.factory.BeanFactory; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.web.embedded.tomcat.ConfigurableTomcatWebServerFactory; +import org.springframework.boot.web.server.WebServerFactoryCustomizer; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.cloud.sleuth.CurrentTraceContext; import org.springframework.cloud.sleuth.SpanNamer; @@ -38,9 +42,12 @@ import org.springframework.cloud.sleuth.http.HttpServerHandler; import org.springframework.cloud.sleuth.instrument.web.TraceWebAspect; import org.springframework.cloud.sleuth.instrument.web.mvc.SpanCustomizingAsyncHandlerInterceptor; import org.springframework.cloud.sleuth.instrument.web.servlet.TracingFilter; +import org.springframework.cloud.sleuth.instrument.web.tomcat.TraceValve; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; @@ -57,74 +64,84 @@ import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) @ConditionalOnClass(HandlerInterceptorAdapter.class) @Import(SpanCustomizingAsyncHandlerInterceptor.class) +@ConditionalOnProperty(value = "spring.sleuth.web.servlet.enabled", matchIfMissing = true) class TraceWebServletConfiguration { + @Bean + TraceWebAspect traceWebAspect(Tracer tracer, CurrentTraceContext currentTraceContext, SpanNamer spanNamer) { + return new TraceWebAspect(tracer, currentTraceContext, spanNamer); + } + + @Bean + FilterRegistrationBean traceWebFilter(BeanFactory beanFactory, SleuthWebProperties webProperties) { + FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new LazyTracingFilter(beanFactory)); + filterRegistrationBean.setDispatcherTypes(DispatcherType.ASYNC, DispatcherType.ERROR, DispatcherType.FORWARD, + DispatcherType.INCLUDE, DispatcherType.REQUEST); + filterRegistrationBean.setOrder(webProperties.getFilterOrder()); + return filterRegistrationBean; + } + + /** + * Nested config that configures Web MVC if it's present (without adding a runtime + * dependency to it). + */ @Configuration(proxyBeanMethods = false) - @ConditionalOnProperty(value = "spring.sleuth.web.servlet.enabled", matchIfMissing = true) - static class ServletConfiguration { + @ConditionalOnClass(WebMvcConfigurer.class) + @Import(TraceWebMvcConfigurer.class) + protected static class TraceWebMvcAutoConfiguration { - @Bean - TraceWebAspect traceWebAspect(Tracer tracer, CurrentTraceContext currentTraceContext, SpanNamer spanNamer) { - return new TraceWebAspect(tracer, currentTraceContext, spanNamer); + } + + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass({ Valve.class, ConfigurableTomcatWebServerFactory.class }) + @ConditionalOnProperty(value = "spring.sleuth.web.tomcat.enabled", matchIfMissing = true) + protected static class TraceTomcatConfiguration { + + static final String CUSTOMIZER_NAME = "traceTomcatWebServerFactoryCustomizer"; + + @Bean(name = CUSTOMIZER_NAME) + @Order(Ordered.HIGHEST_PRECEDENCE) + WebServerFactoryCustomizer traceTomcatWebServerFactoryCustomizer( + HttpServerHandler httpServerHandler, CurrentTraceContext currentTraceContext) { + return factory -> factory.addEngineValves(new TraceValve(httpServerHandler, currentTraceContext)); } - @Bean - FilterRegistrationBean traceWebFilter(BeanFactory beanFactory, SleuthWebProperties webProperties) { - FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean( - new LazyTracingFilter(beanFactory)); - filterRegistrationBean.setDispatcherTypes(DispatcherType.ASYNC, DispatcherType.ERROR, - DispatcherType.FORWARD, DispatcherType.INCLUDE, DispatcherType.REQUEST); - filterRegistrationBean.setOrder(webProperties.getFilterOrder()); - return filterRegistrationBean; + } + + static final class LazyTracingFilter implements Filter { + + private final BeanFactory beanFactory; + + private Filter tracingFilter; + + LazyTracingFilter(BeanFactory beanFactory) { + this.beanFactory = beanFactory; } - /** - * Nested config that configures Web MVC if it's present (without adding a runtime - * dependency to it). - */ - @Configuration(proxyBeanMethods = false) - @ConditionalOnClass(WebMvcConfigurer.class) - @Import(TraceWebMvcConfigurer.class) - protected static class TraceWebMvcAutoConfiguration { + @Override + public void init(FilterConfig filterConfig) throws ServletException { + tracingFilter().init(filterConfig); + } + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + tracingFilter().doFilter(request, response, chain); + } + + @Override + public void destroy() { + tracingFilter().destroy(); + } + + private Filter tracingFilter() { + if (this.tracingFilter == null) { + this.tracingFilter = TracingFilter.create(this.beanFactory.getBean(CurrentTraceContext.class), + this.beanFactory.getBean(HttpServerHandler.class)); + } + return this.tracingFilter; } } } - -final class LazyTracingFilter implements Filter { - - private final BeanFactory beanFactory; - - private Filter tracingFilter; - - LazyTracingFilter(BeanFactory beanFactory) { - this.beanFactory = beanFactory; - } - - @Override - public void init(FilterConfig filterConfig) throws ServletException { - tracingFilter().init(filterConfig); - } - - @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) - throws IOException, ServletException { - tracingFilter().doFilter(request, response, chain); - } - - @Override - public void destroy() { - tracingFilter().destroy(); - } - - private Filter tracingFilter() { - if (this.tracingFilter == null) { - this.tracingFilter = TracingFilter.create(this.beanFactory.getBean(CurrentTraceContext.class), - this.beanFactory.getBean(HttpServerHandler.class)); - } - return this.tracingFilter; - } - -} diff --git a/spring-cloud-sleuth-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-cloud-sleuth-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json index a428a7a2d..95cc9ca24 100644 --- a/spring-cloud-sleuth-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-cloud-sleuth-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -131,6 +131,12 @@ "description": "Enable tracing instrumentation for WebClient.", "defaultValue": true }, + { + "name": "spring.sleuth.web.tomcat.enabled", + "type": "java.lang.Boolean", + "description": "Enable tracing instrumentation for Tomcat.", + "defaultValue": true + }, { "name": "spring.sleuth.integration.enabled", "type": "java.lang.Boolean", diff --git a/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/web/TraceTomcatConfigurationTests.java b/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/web/TraceTomcatConfigurationTests.java new file mode 100644 index 000000000..f8b2aa4e3 --- /dev/null +++ b/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/web/TraceTomcatConfigurationTests.java @@ -0,0 +1,81 @@ +/* + * Copyright 2013-2021 the original author or 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 + * + * https://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 org.springframework.cloud.sleuth.autoconfig.instrument.web; + +import org.assertj.core.api.BDDAssertions; +import org.junit.jupiter.api.Test; + +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.test.context.FilteredClassLoader; +import org.springframework.boot.test.context.runner.WebApplicationContextRunner; +import org.springframework.cloud.sleuth.autoconfig.TraceNoOpAutoConfiguration; +import org.springframework.context.annotation.Configuration; + +class TraceTomcatConfigurationTests { + + private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner() + .withPropertyValues("spring.sleuth.noop.enabled=true").withUserConfiguration(TestConfig.class) + .withConfiguration( + AutoConfigurations.of(TraceWebServletConfiguration.class, TraceNoOpAutoConfiguration.class)); + + @Test + public void should_not_register_customizer_when_tomcat_not_present() throws Exception { + contextRunner.withClassLoader(new FilteredClassLoader("org.apache.catalina.Valve")).run(context -> BDDAssertions + .then(context).doesNotHaveBean(TraceWebServletConfiguration.TraceTomcatConfiguration.CUSTOMIZER_NAME)); + } + + @Test + public void should_not_register_customizer_when_tomcat_customizer_not_present() throws Exception { + contextRunner + .withClassLoader(new FilteredClassLoader( + "org.springframework.boot.web.embedded.tomcat.ConfigurableTomcatWebServerFactory")) + .run(context -> BDDAssertions.then(context) + .doesNotHaveBean(TraceWebServletConfiguration.TraceTomcatConfiguration.CUSTOMIZER_NAME)); + } + + @Test + public void should_not_register_customizer_when_tomcat_disabled() throws Exception { + contextRunner.withPropertyValues("spring.sleuth.web.tomcat.enabled=false").run(context -> BDDAssertions + .then(context).doesNotHaveBean(TraceWebServletConfiguration.TraceTomcatConfiguration.CUSTOMIZER_NAME)); + } + + @Test + public void should_not_register_customizer_when_servlet_disabled() throws Exception { + contextRunner.withPropertyValues("spring.sleuth.web.servlet.enabled=false").run(context -> BDDAssertions + .then(context).doesNotHaveBean(TraceWebServletConfiguration.TraceTomcatConfiguration.CUSTOMIZER_NAME)); + } + + @Test + public void should_not_register_customizer_when_web_disabled() throws Exception { + contextRunner.withPropertyValues("spring.sleuth.web.enabled=false").run(context -> BDDAssertions.then(context) + .doesNotHaveBean(TraceWebServletConfiguration.TraceTomcatConfiguration.CUSTOMIZER_NAME)); + } + + @Test + public void should_register_customizer_when_tomcat_present() throws Exception { + contextRunner.run(context -> BDDAssertions.then(context) + .hasBean(TraceWebServletConfiguration.TraceTomcatConfiguration.CUSTOMIZER_NAME)); + } + + @Configuration(proxyBeanMethods = false) + @EnableConfigurationProperties(SleuthWebProperties.class) + static class TestConfig { + + } + +} diff --git a/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/web/servlet/HttpServletRequestWrapper.java b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/web/servlet/HttpServletRequestWrapper.java index 133c195ba..8fe1bc235 100644 --- a/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/web/servlet/HttpServletRequestWrapper.java +++ b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/web/servlet/HttpServletRequestWrapper.java @@ -31,8 +31,7 @@ import org.springframework.lang.Nullable; * * @since 5.10 */ -// Public for use in sparkjava or other frameworks that re-use servlet types -class HttpServletRequestWrapper implements HttpServerRequest { +public class HttpServletRequestWrapper implements HttpServerRequest { /** @since 5.10 */ public static HttpServerRequest create(HttpServletRequest request) { diff --git a/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/web/servlet/HttpServletResponseWrapper.java b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/web/servlet/HttpServletResponseWrapper.java index 5971fc1ef..3d70cbd80 100644 --- a/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/web/servlet/HttpServletResponseWrapper.java +++ b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/web/servlet/HttpServletResponseWrapper.java @@ -31,8 +31,7 @@ import org.springframework.lang.Nullable; * * @since 5.10 */ -// Public for use in sparkjava or other frameworks that re-use servlet types -class HttpServletResponseWrapper implements HttpServerResponse { +public class HttpServletResponseWrapper implements HttpServerResponse { // not final for inner // subtype diff --git a/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/web/tomcat/TraceValve.java b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/web/tomcat/TraceValve.java new file mode 100644 index 000000000..1a67d98b6 --- /dev/null +++ b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/web/tomcat/TraceValve.java @@ -0,0 +1,88 @@ +/* + * Copyright 2013-2021 the original author or 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 + * + * https://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 org.springframework.cloud.sleuth.instrument.web.tomcat; + +import java.io.IOException; + +import javax.servlet.ServletException; + +import org.apache.catalina.Valve; +import org.apache.catalina.connector.Request; +import org.apache.catalina.connector.Response; +import org.apache.catalina.valves.ValveBase; + +import org.springframework.cloud.sleuth.CurrentTraceContext; +import org.springframework.cloud.sleuth.Span; +import org.springframework.cloud.sleuth.SpanCustomizer; +import org.springframework.cloud.sleuth.TraceContext; +import org.springframework.cloud.sleuth.http.HttpServerHandler; +import org.springframework.cloud.sleuth.instrument.web.servlet.HttpServletRequestWrapper; +import org.springframework.cloud.sleuth.instrument.web.servlet.HttpServletResponseWrapper; +import org.springframework.core.log.LogAccessor; + +/** + * A trace representation of a {@link Valve}. + * + * @author Marcin Grzejszczak + * @since 3.1.0 + */ +public class TraceValve extends ValveBase { + + private static final LogAccessor log = new LogAccessor(TraceValve.class); + + private final HttpServerHandler httpServerHandler; + + private final CurrentTraceContext currentTraceContext; + + public TraceValve(HttpServerHandler httpServerHandler, CurrentTraceContext currentTraceContext) { + this.httpServerHandler = httpServerHandler; + this.currentTraceContext = currentTraceContext; + } + + @Override + public void invoke(Request request, Response response) throws IOException, ServletException { + Exception ex = null; + Span handleReceive = this.httpServerHandler + .handleReceive(HttpServletRequestWrapper.create(request.getRequest())); + if (log.isDebugEnabled()) { + log.debug("Created a server receive span [" + handleReceive + "]"); + } + request.setAttribute(SpanCustomizer.class.getName(), handleReceive); + request.setAttribute(TraceContext.class.getName(), handleReceive.context()); + request.setAttribute(Span.class.getName(), handleReceive); + try (CurrentTraceContext.Scope ws = this.currentTraceContext.maybeScope(handleReceive.context())) { + Valve next = getNext(); + if (null == next) { + // no next valve + return; + } + next.invoke(request, response); + } + catch (Exception exception) { + ex = exception; + throw exception; + } + finally { + this.httpServerHandler.handleSend( + HttpServletResponseWrapper.create(request.getRequest(), response.getResponse(), ex), handleReceive); + if (log.isDebugEnabled()) { + log.debug("Handled send of span [" + handleReceive + "]"); + } + } + } + +} diff --git a/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/instrument/deployer/TraceAppDeployerTests.java b/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/instrument/deployer/TraceAppDeployerTests.java index 208abe3f3..45ee9aaf6 100644 --- a/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/instrument/deployer/TraceAppDeployerTests.java +++ b/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/instrument/deployer/TraceAppDeployerTests.java @@ -36,7 +36,7 @@ import org.springframework.cloud.deployer.spi.app.AppStatus; import org.springframework.cloud.deployer.spi.core.AppDefinition; import org.springframework.cloud.deployer.spi.core.AppDeploymentRequest; import org.springframework.cloud.deployer.spi.core.RuntimeEnvironmentInfo; -import org.springframework.cloud.sleuth.tracer.NoOpCurrentTraceContext; +import org.springframework.cloud.sleuth.tracer.SimpleCurrentTraceContext; import org.springframework.cloud.sleuth.tracer.SimpleTracer; import org.springframework.core.env.Environment; import org.springframework.core.io.PathResource; @@ -145,7 +145,7 @@ class TraceAppDeployerTests { private BeanFactory beanFactory() { StaticListableBeanFactory beanFactory = new StaticListableBeanFactory(); beanFactory.addBean("tracer", this.simpleTracer); - beanFactory.addBean("currentTraceContext", new NoOpCurrentTraceContext()); + beanFactory.addBean("currentTraceContext", new SimpleCurrentTraceContext()); return beanFactory; } diff --git a/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/instrument/web/tomcat/TraceValveTests.java b/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/instrument/web/tomcat/TraceValveTests.java new file mode 100644 index 000000000..2a80595f9 --- /dev/null +++ b/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/instrument/web/tomcat/TraceValveTests.java @@ -0,0 +1,104 @@ +/* + * Copyright 2013-2021 the original author or 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 + * + * https://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 org.springframework.cloud.sleuth.instrument.web.tomcat; + +import java.io.IOException; + +import javax.servlet.ServletException; + +import org.apache.catalina.Valve; +import org.apache.catalina.connector.Connector; +import org.apache.catalina.connector.Request; +import org.apache.catalina.connector.Response; +import org.apache.catalina.valves.ValveBase; +import org.junit.jupiter.api.Test; + +import org.springframework.cloud.sleuth.Span; +import org.springframework.cloud.sleuth.TraceContext; +import org.springframework.cloud.sleuth.http.HttpServerHandler; +import org.springframework.cloud.sleuth.http.HttpServerRequest; +import org.springframework.cloud.sleuth.http.HttpServerResponse; +import org.springframework.cloud.sleuth.tracer.SimpleCurrentTraceContext; +import org.springframework.cloud.sleuth.tracer.SimpleSpan; + +import static org.assertj.core.api.BDDAssertions.then; + +class TraceValveTests { + + SimpleSpan simpleSpan = new SimpleSpan(); + + HttpServerHandler httpServerHandler = new HttpServerHandler() { + @Override + public SimpleSpan handleReceive(HttpServerRequest request) { + return simpleSpan.start(); + } + + @Override + public void handleSend(HttpServerResponse response, Span span) { + span.end(); + } + }; + + TraceValve traceValve = new TraceValve(this.httpServerHandler, new SimpleCurrentTraceContext()); + + @Test + void should_populate_tracecontext_attribute_for_tracing_filter_to_reuse() throws ServletException, IOException { + Request request = request(); + + this.traceValve.invoke(request, new Response()); + + then(request.getAttribute(TraceContext.class.getName())).isNotNull(); + thenSpanIsStartedAndStopped(); + } + + private void thenSpanIsStartedAndStopped() { + then(simpleSpan.started).isTrue(); + then(simpleSpan.ended).isTrue(); + } + + @Test + void should_populate_tracecontext_attribute_for_tracing_filter_to_reuse_when_there_is_another_valve_in_chain() + throws ServletException, IOException { + Request request = request(); + + new TraceValve(this.httpServerHandler, new SimpleCurrentTraceContext()) { + @Override + public Valve getNext() { + return new MyValve(); + } + }.invoke(request, new Response()); + + then(request.getAttribute(TraceContext.class.getName())).isNotNull(); + thenSpanIsStartedAndStopped(); + } + + private Request request() { + Request request = new Request(new Connector()); + request.setCoyoteRequest(new org.apache.coyote.Request()); + return request; + } + +} + +class MyValve extends ValveBase { + + @Override + public void invoke(Request request, Response response) throws IOException, ServletException { + + } + +} diff --git a/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/tracer/NoOpCurrentTraceContext.java b/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/tracer/SimpleCurrentTraceContext.java similarity index 88% rename from spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/tracer/NoOpCurrentTraceContext.java rename to spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/tracer/SimpleCurrentTraceContext.java index 2634c18cf..6c171a1d2 100644 --- a/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/tracer/NoOpCurrentTraceContext.java +++ b/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/tracer/SimpleCurrentTraceContext.java @@ -29,21 +29,25 @@ import org.springframework.cloud.sleuth.TraceContext; * @author Marcin Grzejszczak * @since 3.0.0 */ -public class NoOpCurrentTraceContext implements CurrentTraceContext { +public class SimpleCurrentTraceContext implements CurrentTraceContext { + + public TraceContext traceContext; @Override public TraceContext context() { - return null; + return this.traceContext; } @Override public Scope newScope(TraceContext context) { + this.traceContext = context; return () -> { }; } @Override public Scope maybeScope(TraceContext context) { + this.traceContext = context; return () -> { }; } diff --git a/tests/brave/spring-cloud-sleuth-instrumentation-mvc-tests/src/test/java/org/springframework/cloud/sleuth/brave/instrument/web/TraceFilterWebIntegrationTests.java b/tests/brave/spring-cloud-sleuth-instrumentation-mvc-tests/src/test/java/org/springframework/cloud/sleuth/brave/instrument/web/TraceFilterWebIntegrationTests.java index 0eb5bc3cd..8ed8ad588 100644 --- a/tests/brave/spring-cloud-sleuth-instrumentation-mvc-tests/src/test/java/org/springframework/cloud/sleuth/brave/instrument/web/TraceFilterWebIntegrationTests.java +++ b/tests/brave/spring-cloud-sleuth-instrumentation-mvc-tests/src/test/java/org/springframework/cloud/sleuth/brave/instrument/web/TraceFilterWebIntegrationTests.java @@ -117,7 +117,7 @@ public class TraceFilterWebIntegrationTests { } @Test - public void exception_logging_span_handler_logs_synchronous_exceptions(CapturedOutput capture) { + public void should_instrument_logs_for_tomcat_entries(CapturedOutput capture) { try { new RestTemplate().getForObject("http://localhost:" + port() + "/", String.class); BDDAssertions.fail("should fail due to runtime exception"); @@ -127,7 +127,7 @@ public class TraceFilterWebIntegrationTests { then(this.currentTraceContext.get()).isNull(); MutableSpan fromFirstTraceFilterFlow = spanHandler.takeRemoteSpanWithErrorMessage(Kind.SERVER, - "Request processing failed; nested exception is java.lang.RuntimeException: Throwing exception"); + "Throwing exception"); then(fromFirstTraceFilterFlow.tags()).containsEntry("http.method", "GET").containsEntry("mvc.controller.class", "BasicErrorController"); // Trace IDs in logs: issue#714 @@ -137,7 +137,7 @@ public class TraceFilterWebIntegrationTests { private void thenLogsForExceptionLoggingFilterContainTracingInformation(CapturedOutput capture, String hex) { String[] split = capture.toString().split("\n"); - List list = Arrays.stream(split).filter(s -> s.contains("Uncaught exception thrown")) + List list = Arrays.stream(split).filter(s -> s.contains("Servlet.service() for servlet")) .filter(s -> s.contains(hex + "," + hex + "]")).collect(Collectors.toList()); then(list).isNotEmpty(); }