From 347c63ea8255e87232417ca66bc9d999e2a57afa Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Sun, 4 Mar 2018 09:53:52 +0100 Subject: [PATCH] Aligned HTTP instrumentation with Brave (#878) * Aligned HTTP instrumentation with Brave with this change we remove TraceFilter and make TraceHandlerInterceptor a legacy component. All HTTP instrumentation is done via Brave. fixes gh-873 --- .../web/ExceptionLoggingFilter.java | 60 +++ .../web/SleuthTraceHandlerInterceptor.java | 127 ++++++ .../sleuth/instrument/web/TraceFilter.java | 366 ------------------ .../web/TraceHandlerInterceptor.java | 227 ----------- .../web/TraceSpringDataBeanPostProcessor.java | 26 +- .../instrument/web/TraceWebMvcConfigurer.java | 25 +- .../web/TraceWebServletAutoConfiguration.java | 28 +- .../instrument/zuul/TracePostZuulFilter.java | 7 +- ...ceZuulHandlerMappingBeanPostProcessor.java | 4 +- .../web/TraceFilterIntegrationTests.java | 46 ++- .../instrument/web/TraceFilterTests.java | 114 +----- .../web/TraceFilterWebIntegrationTests.java | 9 +- .../web/TraceHandlerInterceptorTests.java | 60 --- .../zuul/TracePostZuulFilterTests.java | 3 - .../MessagingApplicationTests.java | 7 +- 15 files changed, 295 insertions(+), 814 deletions(-) create mode 100644 spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/ExceptionLoggingFilter.java create mode 100644 spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/SleuthTraceHandlerInterceptor.java delete mode 100644 spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceFilter.java delete mode 100644 spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptor.java delete mode 100644 spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptorTests.java diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/ExceptionLoggingFilter.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/ExceptionLoggingFilter.java new file mode 100644 index 000000000..f5617a6cb --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/ExceptionLoggingFilter.java @@ -0,0 +1,60 @@ +/* + * Copyright 2013-2018 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 + * + * 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 org.springframework.cloud.sleuth.instrument.web; + +import java.io.IOException; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Filter running after {@link brave.servlet.TracingFilter} + * that logs uncaught exceptions + * + * @author Marcin Grzejszczak + * @since 2.0.0 + */ +class ExceptionLoggingFilter implements Filter { + + private static final Log log = LogFactory.getLog(ExceptionLoggingFilter.class); + + @Override public void init(FilterConfig filterConfig) throws ServletException { + + } + + @Override public void doFilter(ServletRequest request, ServletResponse response, + FilterChain chain) throws IOException, ServletException { + try { + chain.doFilter(request, response); + } catch (Exception e) { + if (log.isErrorEnabled()) { + log.error("Uncaught exception thrown", e); + } + throw e; + } + } + + @Override public void destroy() { + + } +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/SleuthTraceHandlerInterceptor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/SleuthTraceHandlerInterceptor.java new file mode 100644 index 000000000..713f9d607 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/SleuthTraceHandlerInterceptor.java @@ -0,0 +1,127 @@ +/* + * Copyright 2013-2018 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 + * + * 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 org.springframework.cloud.sleuth.instrument.web; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import brave.Span; +import brave.http.HttpTracing; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.cloud.sleuth.ErrorParser; +import org.springframework.cloud.sleuth.TraceKeys; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; + +/** + * {@link org.springframework.web.servlet.HandlerInterceptor} that wraps handling of a + * adds tags related to the class and method name. + * + * @author Marcin Grzejszczak + * @since 1.0.3 + */ +class SleuthTraceHandlerInterceptor extends HandlerInterceptorAdapter { + + private static final Log log = LogFactory.getLog(SleuthTraceHandlerInterceptor.class); + + private final BeanFactory beanFactory; + private HttpTracing tracing; + private TraceKeys traceKeys; + private ErrorParser errorParser; + + public SleuthTraceHandlerInterceptor(BeanFactory beanFactory) { + this.beanFactory = beanFactory; + } + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, + Object handler) { + Span span = httpTracing().tracing() + .tracer().currentSpan(); + if (span == null) { + return true; + } + if (log.isDebugEnabled()) { + log.debug("Adding tags to span " + span); + } + addClassMethodTag(handler, span); + addClassNameTag(handler, span); + return true; + } + + private void addClassMethodTag(Object handler, Span span) { + if (handler instanceof HandlerMethod) { + String methodName = ((HandlerMethod) handler).getMethod().getName(); + span.tag(traceKeys().getMvc().getControllerMethod(), methodName); + if (log.isDebugEnabled()) { + log.debug("Adding a method tag with value [" + methodName + "] to a span " + span); + } + } + } + + private void addClassNameTag(Object handler, Span span) { + String className; + if (handler instanceof HandlerMethod) { + className = ((HandlerMethod) handler).getBeanType().getSimpleName(); + } else { + className = handler.getClass().getSimpleName(); + } + if (log.isDebugEnabled()) { + log.debug("Adding a class tag with value [" + className + "] to a span " + span); + } + span.tag(traceKeys().getMvc().getControllerClass(), className); + } + + @Override + public void afterConcurrentHandlingStarted(HttpServletRequest request, + HttpServletResponse response, Object handler) { + + } + + @Override + public void afterCompletion(HttpServletRequest request, HttpServletResponse response, + Object handler, Exception ex) { + Span span = httpTracing().tracing().tracer().currentSpan(); + if (ex != null && span != null) { + errorParser().parseErrorTags(span, ex); + } + } + + private HttpTracing httpTracing() { + if (this.tracing == null) { + this.tracing = this.beanFactory.getBean(HttpTracing.class); + } + return this.tracing; + } + + private TraceKeys traceKeys() { + if (this.traceKeys == null) { + this.traceKeys = this.beanFactory.getBean(TraceKeys.class); + } + return this.traceKeys; + } + + private ErrorParser errorParser() { + if (this.errorParser == null) { + this.errorParser = this.beanFactory.getBean(ErrorParser.class); + } + return this.errorParser; + } + +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceFilter.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceFilter.java deleted file mode 100644 index 202fa089e..000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceFilter.java +++ /dev/null @@ -1,366 +0,0 @@ -/* - * Copyright 2013-2018 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 - * - * 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 org.springframework.cloud.sleuth.instrument.web; - -import java.io.IOException; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import brave.Span; -import brave.Tracer; -import brave.http.HttpServerHandler; -import brave.http.HttpTracing; -import brave.servlet.HttpServletAdapter; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.NoSuchBeanDefinitionException; -import org.springframework.boot.web.servlet.error.ErrorController; -import org.springframework.cloud.sleuth.TraceKeys; -import org.springframework.core.Ordered; -import org.springframework.core.annotation.Order; -import org.springframework.http.HttpStatus; -import org.springframework.web.context.request.async.WebAsyncUtils; -import org.springframework.web.filter.GenericFilterBean; -import org.springframework.web.util.UrlPathHelper; - -/** - * Filter that takes the value of the headers from either request and uses them to - * create a new span. - * - *

- * In order to keep the size of spans manageable, this only add tags defined in - * {@link TraceKeys}. - * - * @author Jakub Nabrdalik, 4financeIT - * @author Tomasz Nurkiewicz, 4financeIT - * @author Marcin Grzejszczak - * @author Spencer Gibb - * @author Dave Syer - * @since 1.0.0 - * - * @see Tracer - * @see TraceKeys - * @see TraceWebServletAutoConfiguration#traceFilter - */ -@Order(TraceFilter.ORDER) -public class TraceFilter extends GenericFilterBean { - - private static final Log log = LogFactory.getLog(TraceFilter.class); - - private static final String HTTP_COMPONENT = "http"; - - /** - * If you register your filter before the {@link TraceFilter} then you will not - * have the tracing context passed for you out of the box. That means that e.g. your - * logs will not get correlated. - */ - public static final int ORDER = Ordered.HIGHEST_PRECEDENCE + 5; - - protected static final String TRACE_REQUEST_ATTR = TraceFilter.class.getName() - + ".TRACE"; - - protected static final String TRACE_ERROR_HANDLED_REQUEST_ATTR = TraceFilter.class.getName() - + ".ERROR_HANDLED"; - - protected static final String TRACE_CLOSE_SPAN_REQUEST_ATTR = TraceFilter.class.getName() - + ".CLOSE_SPAN"; - - private static final String TRACE_SPAN_WITHOUT_PARENT = TraceFilter.class.getName() - + ".SPAN_WITH_NO_PARENT"; - - private static final String TRACE_EXCEPTION_REQUEST_ATTR = TraceFilter.class.getName() - + ".EXCEPTION"; - - private HttpTracing tracing; - private TraceKeys traceKeys; - private final BeanFactory beanFactory; - private HttpServerHandler handler; - private Boolean hasErrorController; - - private final UrlPathHelper urlPathHelper = new UrlPathHelper(); - - public TraceFilter(BeanFactory beanFactory) { - this.beanFactory = beanFactory; - } - @Override - public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, - FilterChain filterChain) throws IOException, ServletException { - if (!(servletRequest instanceof HttpServletRequest) || - !(servletResponse instanceof HttpServletResponse)) { - throw new ServletException("Filter just supports HTTP requests"); - } - HttpServletRequest request = (HttpServletRequest) servletRequest; - HttpServletResponse response = (HttpServletResponse) servletResponse; - String uri = this.urlPathHelper.getPathWithinApplication(request); - Span spanFromRequest = getSpanFromAttribute(request); - Tracer.SpanInScope ws = null; - if (spanFromRequest != null) { - ws = continueSpan(request, spanFromRequest); - } - if (log.isDebugEnabled()) { - log.debug("Received a request to uri [" + uri + "]"); - } - // in case of a response with exception status a exception controller will close the span - if (!httpStatusSuccessful(response) && isSpanContinued(request)) { - processErrorRequest(filterChain, request, response, spanFromRequest, ws); - return; - } - String name = HTTP_COMPONENT + ":" + uri; - SpanAndScope spanAndScope = new SpanAndScope(); - Throwable exception = null; - try { - spanAndScope = createSpan(request, spanFromRequest, name, ws); - filterChain.doFilter(request, response); - } catch (Throwable e) { - exception = e; - if (log.isErrorEnabled()) { - log.error("Uncaught exception thrown", e); - } - request.setAttribute(TRACE_EXCEPTION_REQUEST_ATTR, e); - throw e; - } finally { - if (isAsyncStarted(request) || request.isAsyncStarted()) { - if (log.isDebugEnabled()) { - log.debug("The span " + spanFromRequest + " was created for async"); - } - // TODO: how to deal with response annotations and async? - } else { - detachOrCloseSpans(request, response, spanAndScope, exception); - } - if (spanAndScope.scope != null) { - spanAndScope.scope.close(); - } - } - } - - private void processErrorRequest(FilterChain filterChain, HttpServletRequest request, - HttpServletResponse response, Span spanFromRequest, Tracer.SpanInScope ws) - throws IOException, ServletException { - if (log.isDebugEnabled()) { - log.debug("The span " + spanFromRequest + " was already detached once and we're processing an error"); - } - try { - filterChain.doFilter(request, response); - } finally { - request.setAttribute(TRACE_ERROR_HANDLED_REQUEST_ATTR, true); - if (request.getAttribute(TraceRequestAttributes.ERROR_HANDLED_SPAN_REQUEST_ATTR) == null) { - handler().handleSend(response, - (Throwable) request.getAttribute(TRACE_EXCEPTION_REQUEST_ATTR), spanFromRequest); - request.setAttribute(TRACE_EXCEPTION_REQUEST_ATTR, null); - } - if (ws != null) { - ws.close(); - } - } - } - - private Tracer.SpanInScope continueSpan(HttpServletRequest request, Span spanFromRequest) { - request.setAttribute(TraceRequestAttributes.SPAN_CONTINUED_REQUEST_ATTR, "true"); - if (log.isDebugEnabled()) { - log.debug("There has already been a span in the request " + spanFromRequest); - } - return httpTracing().tracing().tracer().withSpanInScope(spanFromRequest); - } - - private boolean requestHasAlreadyBeenHandled(HttpServletRequest request) { - return request.getAttribute(TraceRequestAttributes.HANDLED_SPAN_REQUEST_ATTR) != null; - } - - private void detachOrCloseSpans(HttpServletRequest request, - HttpServletResponse response, SpanAndScope spanFromRequest, Throwable exception) { - Span span = spanFromRequest.span; - if (span != null) { - addResponseTagsForSpanWithoutParent(exception, request, response, span); - // in case of a response with exception status will close the span when exception dispatch is handled - // checking if tracing is in progress due to async / different order of view controller processing - if (httpStatusSuccessful(response) && !requestHasAlreadyBeenHandled(request)) { - if (log.isDebugEnabled()) { - log.debug("Closing the span " + span + " since the response was successful"); - } - if (exception == null || !hasErrorController()) { - clearTraceAttribute(request); - handler().handleSend(response, exception, span); - } - } else if (errorAlreadyHandled(request) && !shouldCloseSpan(request)) { - if (log.isDebugEnabled()) { - log.debug( - "Won't detach the span " + span + " since error has already been handled"); - } - } else if ((shouldCloseSpan(request) || isRootSpan(span)) && stillTracingCurrentSpan(span)) { - if (log.isDebugEnabled()) { - log.debug("Will handle sent for span " + span); - } - handler().handleSend(response, exception, span); - if (shouldCloseSpan(request)) { - clearTraceAttribute(request); - } - } else if (span != null || requestHasAlreadyBeenHandled(request)) { - if (log.isDebugEnabled()) { - log.debug("Detaching the span " + span + " since the response was unsuccessful"); - } - if (!hasErrorController()) { - clearTraceAttribute(request); - } - if (exception == null || !hasErrorController()) { - handler().handleSend(response, exception, span); - } else { - abandonSpan(span); - } - } - } - } - - // visible for tests - void abandonSpan(Span span) { - span.abandon(); - } - - private void addResponseTagsForSpanWithoutParent(Throwable exception, - HttpServletRequest request, HttpServletResponse response, Span span) { - if (exception == null && spanWithoutParent(request) && response.getStatus() >= 100) { - span.tag(traceKeys().getHttp().getStatusCode(), - String.valueOf(response.getStatus())); - } - } - - private boolean spanWithoutParent(HttpServletRequest request) { - return request.getAttribute(TRACE_SPAN_WITHOUT_PARENT) != null; - } - - private boolean isRootSpan(Span span) { - return span.context().traceId() == span.context().spanId(); - } - - private boolean stillTracingCurrentSpan(Span span) { - Span currentSpan = httpTracing().tracing().tracer().currentSpan(); - return currentSpan != null && currentSpan.equals(span); - } - - private boolean httpStatusSuccessful(HttpServletResponse response) { - if (response.getStatus() == 0) { - return false; - } - HttpStatus.Series httpStatusSeries = HttpStatus.Series.valueOf(response.getStatus()); - return httpStatusSeries == HttpStatus.Series.SUCCESSFUL || httpStatusSeries == HttpStatus.Series.REDIRECTION; - } - - private Span getSpanFromAttribute(HttpServletRequest request) { - return (Span) request.getAttribute(TRACE_REQUEST_ATTR); - } - - private void clearTraceAttribute(HttpServletRequest request) { - request.setAttribute(TRACE_REQUEST_ATTR, null); - } - - private boolean errorAlreadyHandled(HttpServletRequest request) { - return Boolean.valueOf( - String.valueOf(request.getAttribute(TRACE_ERROR_HANDLED_REQUEST_ATTR))); - } - - private boolean shouldCloseSpan(HttpServletRequest request) { - return Boolean.valueOf( - String.valueOf(request.getAttribute(TRACE_CLOSE_SPAN_REQUEST_ATTR))); - } - - private boolean isSpanContinued(HttpServletRequest request) { - return getSpanFromAttribute(request) != null; - } - - /** - * Creates a span and appends it as the current request's attribute - */ - private SpanAndScope createSpan(HttpServletRequest request, - Span spanFromRequest, String name, Tracer.SpanInScope ws) { - if (spanFromRequest != null) { - if (log.isDebugEnabled()) { - log.debug("Span has already been created - continuing with the previous one"); - } - return new SpanAndScope(spanFromRequest, ws); - } - spanFromRequest = handler().handleReceive(httpTracing().tracing() - .propagation().extractor(HttpServletRequest::getHeader), request); - if (log.isDebugEnabled()) { - log.debug("Found a parent span " + spanFromRequest.context() + " in the request"); - } - request.setAttribute(TRACE_REQUEST_ATTR, spanFromRequest); - if (log.isDebugEnabled()) { - log.debug("Parent span is " + spanFromRequest + ""); - } - return new SpanAndScope(spanFromRequest, httpTracing().tracing() - .tracer().withSpanInScope(spanFromRequest)); - } - - class SpanAndScope { - - final Span span; - final Tracer.SpanInScope scope; - - SpanAndScope(Span span, Tracer.SpanInScope scope) { - this.span = span; - this.scope = scope; - } - - SpanAndScope() { - this.span = null; - this.scope = null; - } - } - - protected boolean isAsyncStarted(HttpServletRequest request) { - return WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted(); - } - - @SuppressWarnings("unchecked") - HttpServerHandler handler() { - if (this.handler == null) { - this.handler = HttpServerHandler.create(this.beanFactory.getBean(HttpTracing.class), - new HttpServletAdapter()); - } - return this.handler; - } - - TraceKeys traceKeys() { - if (this.traceKeys == null) { - this.traceKeys = this.beanFactory.getBean(TraceKeys.class); - } - return this.traceKeys; - } - - HttpTracing httpTracing() { - if (this.tracing == null) { - this.tracing = this.beanFactory.getBean(HttpTracing.class); - } - return this.tracing; - } - - // null check is only for tests - private boolean hasErrorController() { - if (this.hasErrorController == null) { - try { - this.hasErrorController = this.beanFactory.getBean(ErrorController.class) != null; - } catch (NoSuchBeanDefinitionException e) { - this.hasErrorController = false; - } - } - return this.hasErrorController; - } -} - diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptor.java deleted file mode 100644 index 09ea317ad..000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptor.java +++ /dev/null @@ -1,227 +0,0 @@ -/* - * Copyright 2013-2018 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 - * - * 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 org.springframework.cloud.sleuth.instrument.web; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.util.concurrent.atomic.AtomicReference; - -import brave.Span; -import brave.Tracer; -import brave.http.HttpServerHandler; -import brave.http.HttpTracing; -import brave.servlet.HttpServletAdapter; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.NoSuchBeanDefinitionException; -import org.springframework.boot.web.servlet.error.ErrorController; -import org.springframework.cloud.sleuth.ErrorParser; -import org.springframework.cloud.sleuth.TraceKeys; -import org.springframework.cloud.sleuth.util.SpanNameUtil; -import org.springframework.web.method.HandlerMethod; -import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; - -/** - * {@link org.springframework.web.servlet.HandlerInterceptor} that wraps handling of a - * request in a Span. Adds tags related to the class and method name. - * - * The interceptor will not create spans for error controller related paths. - * - * It's important to note that this implementation will set the request attribute - * {@link TraceRequestAttributes#HANDLED_SPAN_REQUEST_ATTR} when the request is processed. - * That way the {@link TraceFilter} will not create the "fallback" span. - * - * @author Marcin Grzejszczak - * @since 1.0.3 - */ -public class TraceHandlerInterceptor extends HandlerInterceptorAdapter { - - private static final Log log = LogFactory.getLog(TraceHandlerInterceptor.class); - - private final BeanFactory beanFactory; - - private HttpTracing tracing; - private TraceKeys traceKeys; - private ErrorParser errorParser; - private AtomicReference errorController; - private HttpServerHandler handler; - - public TraceHandlerInterceptor(BeanFactory beanFactory) { - this.beanFactory = beanFactory; - } - - @Override - public boolean preHandle(HttpServletRequest request, HttpServletResponse response, - Object handler) throws Exception { - String spanName = spanName(handler); - boolean continueSpan = getRootSpanFromAttribute(request) != null; - Span span = continueSpan ? getRootSpanFromAttribute(request) : - httpTracing().tracing().tracer().nextSpan().name(spanName).start(); - try (Tracer.SpanInScope ws = httpTracing().tracing().tracer().withSpanInScope(span)) { - if (log.isDebugEnabled()) { - log.debug("Handling span " + span); - } - addClassMethodTag(handler, span); - addClassNameTag(handler, span); - setSpanInAttribute(request, span); - if (!continueSpan) { - setNewSpanCreatedAttribute(request, span); - } - } - return true; - } - - private boolean isErrorControllerRelated(HttpServletRequest request) { - return errorController() != null && errorController().getErrorPath() - .equals(request.getRequestURI()); - } - - private void addClassMethodTag(Object handler, Span span) { - if (handler instanceof HandlerMethod) { - String methodName = ((HandlerMethod) handler).getMethod().getName(); - span.tag(traceKeys().getMvc().getControllerMethod(), methodName); - if (log.isDebugEnabled()) { - log.debug("Adding a method tag with value [" + methodName + "] to a span " + span); - } - } - } - - private void addClassNameTag(Object handler, Span span) { - String className; - if (handler instanceof HandlerMethod) { - className = ((HandlerMethod) handler).getBeanType().getSimpleName(); - } else { - className = handler.getClass().getSimpleName(); - } - if (log.isDebugEnabled()) { - log.debug("Adding a class tag with value [" + className + "] to a span " + span); - } - span.tag(traceKeys().getMvc().getControllerClass(), className); - } - - private String spanName(Object handler) { - if (handler instanceof HandlerMethod) { - return SpanNameUtil.toLowerHyphen(((HandlerMethod) handler).getMethod().getName()); - } - return SpanNameUtil.toLowerHyphen(handler.getClass().getSimpleName()); - } - - @Override - public void afterConcurrentHandlingStarted(HttpServletRequest request, - HttpServletResponse response, Object handler) throws Exception { - Span spanFromRequest = getNewSpanFromAttribute(request); - if (spanFromRequest != null) { - try (Tracer.SpanInScope ws = httpTracing().tracing().tracer().withSpanInScope(spanFromRequest)) { - if (log.isDebugEnabled()) { - log.debug("Closing the span " + spanFromRequest); - } - } finally { - spanFromRequest.finish(); - } - } - } - - @Override - public void afterCompletion(HttpServletRequest request, HttpServletResponse response, - Object handler, Exception ex) throws Exception { - if (isErrorControllerRelated(request)) { - if (log.isDebugEnabled()) { - log.debug("Skipping closing of a span for error controller processing"); - } - return; - } - Span span = getRootSpanFromAttribute(request); - if (ex != null) { - errorParser().parseErrorTags(span, ex); - } - if (getNewSpanFromAttribute(request) != null) { - if (log.isDebugEnabled()) { - log.debug("Closing span " + span); - } - Span newSpan = getNewSpanFromAttribute(request); - handler().handleSend(response, ex, newSpan); - clearNewSpanCreatedAttribute(request); - } - } - - private Span getNewSpanFromAttribute(HttpServletRequest request) { - return (Span) request.getAttribute(TraceRequestAttributes.NEW_SPAN_REQUEST_ATTR); - } - - private Span getRootSpanFromAttribute(HttpServletRequest request) { - return (Span) request.getAttribute(TraceFilter.TRACE_REQUEST_ATTR); - } - - private void setSpanInAttribute(HttpServletRequest request, Span span) { - request.setAttribute(TraceRequestAttributes.HANDLED_SPAN_REQUEST_ATTR, span); - } - - private void setNewSpanCreatedAttribute(HttpServletRequest request, Span span) { - request.setAttribute(TraceRequestAttributes.NEW_SPAN_REQUEST_ATTR, span); - } - - private void clearNewSpanCreatedAttribute(HttpServletRequest request) { - request.removeAttribute(TraceRequestAttributes.NEW_SPAN_REQUEST_ATTR); - } - - private HttpTracing httpTracing() { - if (this.tracing == null) { - this.tracing = this.beanFactory.getBean(HttpTracing.class); - } - return this.tracing; - } - - private TraceKeys traceKeys() { - if (this.traceKeys == null) { - this.traceKeys = this.beanFactory.getBean(TraceKeys.class); - } - return this.traceKeys; - } - - @SuppressWarnings("unchecked") - HttpServerHandler handler() { - if (this.handler == null) { - this.handler = HttpServerHandler.create(this.beanFactory.getBean(HttpTracing.class), - new HttpServletAdapter()); - } - return this.handler; - } - - private ErrorParser errorParser() { - if (this.errorParser == null) { - this.errorParser = this.beanFactory.getBean(ErrorParser.class); - } - return this.errorParser; - } - - ErrorController errorController() { - if (this.errorController == null) { - try { - ErrorController errorController = this.beanFactory.getBean(ErrorController.class); - this.errorController = new AtomicReference<>(errorController); - } catch (NoSuchBeanDefinitionException e) { - if (log.isTraceEnabled()) { - log.trace("ErrorController bean not found"); - } - this.errorController = new AtomicReference<>(); - } - } - return this.errorController.get(); - } - -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceSpringDataBeanPostProcessor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceSpringDataBeanPostProcessor.java index 804eb20f1..0d364aa30 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceSpringDataBeanPostProcessor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceSpringDataBeanPostProcessor.java @@ -16,14 +16,15 @@ package org.springframework.cloud.sleuth.instrument.web; -import javax.servlet.http.HttpServletRequest; import java.util.Collections; +import javax.servlet.http.HttpServletRequest; +import brave.spring.webmvc.TracingHandlerInterceptor; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeansException; -import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.context.ApplicationContext; import org.springframework.data.rest.webmvc.support.DelegatingHandlerMapping; import org.springframework.web.servlet.HandlerExecutionChain; import org.springframework.web.servlet.HandlerMapping; @@ -38,10 +39,10 @@ class TraceSpringDataBeanPostProcessor implements BeanPostProcessor { private static final Log log = LogFactory.getLog(TraceSpringDataBeanPostProcessor.class); - private final BeanFactory beanFactory; + private final ApplicationContext applicationContext; - public TraceSpringDataBeanPostProcessor(BeanFactory beanFactory) { - this.beanFactory = beanFactory; + public TraceSpringDataBeanPostProcessor(ApplicationContext applicationContext) { + this.applicationContext = applicationContext; } @Override @@ -53,7 +54,7 @@ class TraceSpringDataBeanPostProcessor implements BeanPostProcessor { "] in its trace representation"); } return new TraceDelegatingHandlerMapping((DelegatingHandlerMapping) bean, - this.beanFactory); + this.applicationContext); } return bean; } @@ -67,13 +68,13 @@ class TraceSpringDataBeanPostProcessor implements BeanPostProcessor { private static class TraceDelegatingHandlerMapping extends DelegatingHandlerMapping { private final DelegatingHandlerMapping delegate; - private final BeanFactory beanFactory; + private final ApplicationContext applicationContext; public TraceDelegatingHandlerMapping(DelegatingHandlerMapping delegate, - BeanFactory beanFactory) { + ApplicationContext beanFactory) { super(Collections.emptyList()); this.delegate = delegate; - this.beanFactory = beanFactory; + this.applicationContext = beanFactory; } @Override @@ -88,7 +89,12 @@ class TraceSpringDataBeanPostProcessor implements BeanPostProcessor { if (handlerExecutionChain == null) { return null; } - handlerExecutionChain.addInterceptor(new TraceHandlerInterceptor(this.beanFactory)); + handlerExecutionChain.addInterceptor(this.applicationContext.getBean(TracingHandlerInterceptor.class)); + String legacyEnabled = this.applicationContext.getEnvironment() + .getProperty("spring.sleuth.http.legacy.enabled", "false"); + if (Boolean.parseBoolean(legacyEnabled)) { + handlerExecutionChain.addInterceptor(this.applicationContext.getBean(SleuthTraceHandlerInterceptor.class)); + } return handlerExecutionChain; } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebMvcConfigurer.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebMvcConfigurer.java index b0b96dcb6..682589104 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebMvcConfigurer.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebMvcConfigurer.java @@ -16,15 +16,19 @@ package org.springframework.cloud.sleuth.instrument.web; +import brave.http.HttpTracing; +import brave.spring.webmvc.TracingHandlerInterceptor; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; /** - * MVC Adapter that adds the {@link TraceHandlerInterceptor} + * MVC Adapter that adds the {@link TracingHandlerInterceptor} * * @author Marcin Grzejszczak * @@ -32,15 +36,26 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; */ @Configuration class TraceWebMvcConfigurer implements WebMvcConfigurer { - @Autowired BeanFactory beanFactory; + @Autowired ApplicationContext applicationContext; @Bean - public TraceHandlerInterceptor traceHandlerInterceptor(BeanFactory beanFactory) { - return new TraceHandlerInterceptor(beanFactory); + public TracingHandlerInterceptor tracingHandlerInterceptor(HttpTracing tracing) { + return (TracingHandlerInterceptor) TracingHandlerInterceptor.create(tracing); + } + + @Bean + @ConditionalOnProperty("spring.sleuth.http.legacy.enabled") + public SleuthTraceHandlerInterceptor legacySleuthTraceHandlerInterceptor(BeanFactory beanFactory) { + return new SleuthTraceHandlerInterceptor(beanFactory); } @Override public void addInterceptors(InterceptorRegistry registry) { - registry.addInterceptor(this.beanFactory.getBean(TraceHandlerInterceptor.class)); + registry.addInterceptor(this.applicationContext.getBean(TracingHandlerInterceptor.class)); + String legacyEnabled = this.applicationContext.getEnvironment() + .getProperty("spring.sleuth.http.legacy.enabled", "false"); + if (Boolean.parseBoolean(legacyEnabled)) { + registry.addInterceptor(this.applicationContext.getBean(SleuthTraceHandlerInterceptor.class)); + } } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebServletAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebServletAutoConfiguration.java index 5528f80ab..29e50be80 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebServletAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebServletAutoConfiguration.java @@ -18,7 +18,7 @@ package org.springframework.cloud.sleuth.instrument.web; import brave.Tracer; import brave.http.HttpTracing; -import org.springframework.beans.factory.BeanFactory; +import brave.servlet.TracingFilter; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; @@ -28,9 +28,11 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplicat import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.cloud.sleuth.ErrorParser; import org.springframework.cloud.sleuth.SpanNamer; +import org.springframework.context.ApplicationContext; 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.web.servlet.config.annotation.WebMvcConfigurer; import static javax.servlet.DispatcherType.ASYNC; @@ -54,6 +56,8 @@ import static javax.servlet.DispatcherType.REQUEST; @AutoConfigureAfter(TraceHttpAutoConfiguration.class) public class TraceWebServletAutoConfiguration { + public static final int TRACING_FILTER_ORDER = Ordered.HIGHEST_PRECEDENCE + 5; + /** * Nested config that configures Web MVC if it's present (without adding a runtime * dependency to it) @@ -72,22 +76,30 @@ public class TraceWebServletAutoConfiguration { @Bean @ConditionalOnClass(name = "org.springframework.data.rest.webmvc.support.DelegatingHandlerMapping") public static TraceSpringDataBeanPostProcessor traceSpringDataBeanPostProcessor( - BeanFactory beanFactory) { - return new TraceSpringDataBeanPostProcessor(beanFactory); + ApplicationContext applicationContext) { + return new TraceSpringDataBeanPostProcessor(applicationContext); } @Bean public FilterRegistrationBean traceWebFilter( - TraceFilter traceFilter) { - FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(traceFilter); + TracingFilter tracingFilter) { + FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(tracingFilter); filterRegistrationBean.setDispatcherTypes(ASYNC, ERROR, FORWARD, INCLUDE, REQUEST); - filterRegistrationBean.setOrder(TraceFilter.ORDER); + filterRegistrationBean.setOrder(TraceWebServletAutoConfiguration.TRACING_FILTER_ORDER); + return filterRegistrationBean; + } + + @Bean + public FilterRegistrationBean exceptionThrowingFilter() { + FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new ExceptionLoggingFilter()); + filterRegistrationBean.setDispatcherTypes(ASYNC, ERROR, FORWARD, INCLUDE, REQUEST); + filterRegistrationBean.setOrder(TraceWebServletAutoConfiguration.TRACING_FILTER_ORDER + 1); return filterRegistrationBean; } @Bean @ConditionalOnMissingBean - public TraceFilter traceFilter(BeanFactory beanFactory) { - return new TraceFilter(beanFactory); + public TracingFilter tracingFilter(HttpTracing tracing) { + return (TracingFilter) TracingFilter.create(tracing); } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TracePostZuulFilter.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TracePostZuulFilter.java index 0897e8d91..adb5f6d57 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TracePostZuulFilter.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TracePostZuulFilter.java @@ -26,12 +26,10 @@ import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.cloud.sleuth.instrument.web.TraceFilter; -import org.springframework.cloud.sleuth.instrument.web.TraceRequestAttributes; import org.springframework.http.HttpStatus; /** - * A post request {@link ZuulFilter} that marks a span for closing in {@link TraceFilter} + * A post request {@link ZuulFilter} * * @author Dave Syer * @since 1.0.0 @@ -67,9 +65,6 @@ class TracePostZuulFilter extends ZuulFilter { if (log.isDebugEnabled()) { log.debug("Marking current span as handled"); } - RequestContext.getCurrentContext() - .getRequest().setAttribute( - TraceRequestAttributes.HANDLED_SPAN_REQUEST_ATTR, "true"); HttpServletResponse response = RequestContext.getCurrentContext().getResponse(); Throwable exception = RequestContext.getCurrentContext().getThrowable(); this.handler.handleSend(response, exception, this.tracer.currentSpan()); diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulHandlerMappingBeanPostProcessor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulHandlerMappingBeanPostProcessor.java index baf328ae0..735f52720 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulHandlerMappingBeanPostProcessor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulHandlerMappingBeanPostProcessor.java @@ -18,13 +18,13 @@ package org.springframework.cloud.sleuth.instrument.zuul; import java.lang.invoke.MethodHandles; +import brave.spring.webmvc.TracingHandlerInterceptor; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.cloud.netflix.zuul.web.ZuulHandlerMapping; -import org.springframework.cloud.sleuth.instrument.web.TraceHandlerInterceptor; /** * Bean post processor that wraps {@link ZuulHandlerMapping} in its @@ -58,7 +58,7 @@ class TraceZuulHandlerMappingBeanPostProcessor implements BeanPostProcessor { } ZuulHandlerMapping zuulHandlerMapping = (ZuulHandlerMapping) bean; zuulHandlerMapping.setInterceptors( - new TraceHandlerInterceptor(this.beanFactory)); + this.beanFactory.getBean(TracingHandlerInterceptor.class)); } return bean; } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterIntegrationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterIntegrationTests.java index bd13bb07f..561a34eba 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterIntegrationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterIntegrationTests.java @@ -30,6 +30,7 @@ import java.util.concurrent.CompletableFuture; import brave.Span; import brave.Tracer; import brave.sampler.Sampler; +import brave.servlet.TracingFilter; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.junit.After; @@ -40,7 +41,6 @@ import org.slf4j.MDC; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.actuate.autoconfigure.web.server.ManagementServerProperties; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.sleuth.TraceKeys; import org.springframework.cloud.sleuth.instrument.DefaultTestAutoConfiguration; @@ -69,7 +69,8 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @RunWith(SpringRunner.class) -@SpringBootTest(classes = TraceFilterIntegrationTests.Config.class) +@SpringBootTest(classes = TraceFilterIntegrationTests.Config.class, +properties = "spring.sleuth.http.legacy.enabled=true") public class TraceFilterIntegrationTests extends AbstractMvcIntegrationTest { static final String TRACE_ID_NAME = "X-B3-TraceId"; static final String SPAN_ID_NAME = "X-B3-SpanId"; @@ -78,7 +79,7 @@ public class TraceFilterIntegrationTests extends AbstractMvcIntegrationTest { private static Log logger = LogFactory.getLog( TraceFilterIntegrationTests.class); - @Autowired TraceFilter traceFilter; + @Autowired TracingFilter traceFilter; @Autowired MyFilter myFilter; @Autowired ArrayListSpanReporter reporter; @Autowired Tracer tracer; @@ -95,7 +96,7 @@ public class TraceFilterIntegrationTests extends AbstractMvcIntegrationTest { public void should_create_a_trace() throws Exception { whenSentPingWithoutTracingData(); - then(this.reporter.getSpans()).hasSize(1); + then(this.reporter.getSpans()).hasSize(2); zipkin2.Span span = this.reporter.getSpans().get(0); then(span.tags()) .containsKey(new TraceKeys().getMvc().getControllerClass()) @@ -173,7 +174,15 @@ public class TraceFilterIntegrationTests extends AbstractMvcIntegrationTest { whenSentToNonExistentEndpointWithTraceId(expectedTraceId); - then(this.reporter.getSpans()).hasSize(1); + // it's a span with the same ids + then(this.reporter.getSpans()).hasSize(2); + zipkin2.Span serverSpan = this.reporter.getSpans().get(0); + then(serverSpan.tags()) + .containsEntry("custom", "tag") + .containsEntry("http.status_code", "404"); + zipkin2.Span handlerSpan = this.reporter.getSpans().get(0); + then(handlerSpan.tags()) + .containsEntry("http.status_code", "404"); then(this.tracer.currentSpan()).isNull(); } @@ -190,8 +199,13 @@ public class TraceFilterIntegrationTests extends AbstractMvcIntegrationTest { // we need to dump the span cause it's not in TraceFilter since TF // has also error dispatch and the ErrorController would report the span - then(this.reporter.getSpans()).hasSize(1); - then(this.reporter.getSpans().get(0).tags()).containsKey("error"); + then(this.reporter.getSpans()).hasSize(2); + // server + then(this.reporter.getSpans().get(0).tags()) + .containsEntry("error", "java.lang.RuntimeException"); + // handler + then(this.reporter.getSpans().get(1).tags()) + .containsEntry("error", "Request processing failed; nested exception is java.lang.RuntimeException"); } @Test @@ -357,17 +371,7 @@ public class TraceFilterIntegrationTests extends AbstractMvcIntegrationTest { } @Bean - TraceFilter myTraceFilter(BeanFactory beanFactory) { - return new TraceFilter(beanFactory) { - @Override void abandonSpan(Span span) { - log.info("Simulating Error Controller"); - span.finish(); - } - }; - } - - @Bean - @Order(TraceFilter.ORDER + 1) + @Order(TraceWebServletAutoConfiguration.TRACING_FILTER_ORDER + 1) Filter myFilter(Tracer tracer) { return new MyFilter(tracer); } @@ -376,7 +380,7 @@ public class TraceFilterIntegrationTests extends AbstractMvcIntegrationTest { //tag::response_headers[] @Component -@Order(TraceFilter.ORDER + 1) +@Order(TraceWebServletAutoConfiguration.TRACING_FILTER_ORDER + 1) class MyFilter extends GenericFilterBean { private final Tracer tracer; @@ -388,7 +392,9 @@ class MyFilter extends GenericFilterBean { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { Span currentSpan = this.tracer.currentSpan(); - then(currentSpan).isNotNull(); + if (currentSpan == null) { + return; + } // for readability we're returning trace id in a hex form ((HttpServletResponse) response) .addHeader("ZIPKIN-TRACE-ID", diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterTests.java index afffead58..bbe3de3f4 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterTests.java @@ -18,6 +18,7 @@ package org.springframework.cloud.sleuth.instrument.web; import java.util.concurrent.atomic.AtomicReference; import java.util.regex.Pattern; +import javax.servlet.Filter; import brave.Span; import brave.Tracer; @@ -25,17 +26,12 @@ import brave.Tracing; import brave.http.HttpTracing; import brave.propagation.CurrentTraceContext; import brave.sampler.Sampler; +import brave.servlet.TracingFilter; import org.junit.After; import org.junit.Before; import org.junit.Test; -import org.mockito.BDDMockito; -import org.mockito.Mockito; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.NoSuchBeanDefinitionException; -import org.springframework.cloud.sleuth.ErrorParser; import org.springframework.cloud.sleuth.ExceptionMessageErrorParser; import org.springframework.cloud.sleuth.TraceKeys; -import org.springframework.cloud.sleuth.autoconfig.SleuthProperties; import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; import org.springframework.cloud.sleuth.util.SpanUtil; import org.springframework.http.HttpMethod; @@ -76,12 +72,11 @@ public class TraceFilterTests { new ExceptionMessageErrorParser())) .serverSampler(new SleuthHttpSampler(() -> Pattern.compile(""))) .build(); - SleuthProperties properties = new SleuthProperties(); + Filter filter = TracingFilter.create(this.httpTracing); MockHttpServletRequest request; MockHttpServletResponse response; MockFilterChain filterChain; - BeanFactory beanFactory = Mockito.mock(BeanFactory.class); @Before public void init() { @@ -103,19 +98,16 @@ public class TraceFilterTests { @Test public void notTraced() throws Exception { - BeanFactory beanFactory = neverSampleTracing(); - TraceFilter filter = new TraceFilter(beanFactory); - this.request = get("/favicon.ico").accept(MediaType.ALL) .buildRequest(new MockServletContext()); - filter.doFilter(this.request, this.response, this.filterChain); + neverSampleFilter().doFilter(this.request, this.response, this.filterChain); then(Tracing.current().tracer().currentSpan()).isNull(); then(this.reporter.getSpans()).isEmpty(); } - private BeanFactory neverSampleTracing() { + private Filter neverSampleFilter() { Tracing tracing = Tracing.newBuilder() .currentTraceContext(CurrentTraceContext.Default.create()) .spanReporter(this.reporter) @@ -128,14 +120,11 @@ public class TraceFilterTests { new ExceptionMessageErrorParser())) .serverSampler(new SleuthHttpSampler(() -> Pattern.compile(""))) .build(); - BeanFactory beanFactory = beanFactory(); - BDDMockito.given(beanFactory.getBean(HttpTracing.class)).willReturn(httpTracing); - return beanFactory; + return TracingFilter.create(httpTracing); } @Test public void startsNewTrace() throws Exception { - TraceFilter filter = new TraceFilter(beanFactory()); filter.doFilter(this.request, this.response, this.filterChain); then(this.reporter.getSpans()) @@ -149,32 +138,8 @@ public class TraceFilterTests { //.containsEntry("http.status_code", "200") } - @Test - public void startsNewTraceWithTraceHandlerInterceptor() throws Exception { - final BeanFactory beanFactory = beanFactory(); - TraceFilter filter = new TraceFilter(beanFactory); - filter.doFilter(this.request, this.response, (req, resp) -> { - this.filterChain.doFilter(req, resp); - // Simulate execution of the TraceHandlerInterceptor - request.setAttribute(TraceRequestAttributes.HANDLED_SPAN_REQUEST_ATTR, - tracing.tracer().currentSpan()); - }); - - then(Tracing.current().tracer().currentSpan()).isNull(); - then(this.reporter.getSpans()) - .hasSize(1); - then(this.reporter.getSpans().get(0).tags()) - .containsEntry("http.url", "http://localhost/?foo=bar") - .containsEntry("http.host", "localhost") - .containsEntry("http.path", "/") - .containsEntry("http.method", HttpMethod.GET.toString()); - // we don't check for status_code anymore cause Brave doesn't support it oob - //.containsEntry("http.status_code", "200") - } - @Test public void shouldNotStoreHttpStatusCodeWhenResponseCodeHasNotYetBeenSet() throws Exception { - TraceFilter filter = new TraceFilter(beanFactory()); this.response.setStatus(0); filter.doFilter(this.request, this.response, this.filterChain); @@ -192,9 +157,7 @@ public class TraceFilterTests { .header(TRACE_ID_NAME, SpanUtil.idToHex(2L)) .header(PARENT_SPAN_ID_NAME, SpanUtil.idToHex(3L)) .buildRequest(new MockServletContext()); - BeanFactory beanFactory = beanFactory(); - - TraceFilter filter = new TraceFilter(beanFactory); + filter.doFilter(this.request, this.response, this.filterChain); then(Tracing.current().tracer().currentSpan()).isNull(); @@ -217,9 +180,7 @@ public class TraceFilterTests { .header(PARENT_SPAN_ID_NAME, SpanUtil.idToHex(3L)) .header(SAMPLED_ID_NAME, 0) .buildRequest(new MockServletContext()); - BeanFactory beanFactory = beanFactory(); - - TraceFilter filter = new TraceFilter(beanFactory); + filter.doFilter(this.request, this.response, (req, resp) -> { this.filterChain.doFilter(req, resp); span.set(this.tracing.tracer().currentSpan()); @@ -233,26 +194,20 @@ public class TraceFilterTests { @Test public void continuesSpanInRequestAttr() throws Exception { Span span = this.tracer.nextSpan().name("http:foo"); - this.request.setAttribute(TraceFilter.TRACE_REQUEST_ATTR, span); - TraceFilter filter = new TraceFilter(beanFactory()); filter.doFilter(this.request, this.response, this.filterChain); then(Tracing.current().tracer().currentSpan()).isNull(); - then(this.request.getAttribute(TraceFilter.TRACE_ERROR_HANDLED_REQUEST_ATTR)).isNull(); } @Test public void closesSpanInRequestAttrIfStatusCodeNotSuccessful() throws Exception { Span span = this.tracer.nextSpan().name("http:foo"); - this.request.setAttribute(TraceFilter.TRACE_REQUEST_ATTR, span); this.response.setStatus(404); - TraceFilter filter = new TraceFilter(beanFactory()); filter.doFilter(this.request, this.response, this.filterChain); then(Tracing.current().tracer().currentSpan()).isNull(); - then(this.request.getAttribute(TraceFilter.TRACE_ERROR_HANDLED_REQUEST_ATTR)).isNotNull(); then(this.reporter.getSpans()) .hasSize(1); } @@ -260,12 +215,8 @@ public class TraceFilterTests { @Test public void doesntDetachASpanIfStatusCodeNotSuccessfulAndRequestWasProcessed() throws Exception { Span span = this.tracer.nextSpan().name("http:foo"); - this.request.setAttribute(TraceFilter.TRACE_REQUEST_ATTR, span); - this.request.setAttribute(TraceFilter.TRACE_ERROR_HANDLED_REQUEST_ATTR, true); this.response.setStatus(404); - TraceFilter filter = new TraceFilter(beanFactory()); - then(Tracing.current().tracer().currentSpan()).isNull(); filter.doFilter(this.request, this.response, this.filterChain); } @@ -275,8 +226,6 @@ public class TraceFilterTests { this.request = builder().header(SPAN_ID_NAME, PARENT_ID) .header(TRACE_ID_NAME, SpanUtil.idToHex(20L)) .buildRequest(new MockServletContext()); - BeanFactory beanFactory = beanFactory(); - TraceFilter filter = new TraceFilter(beanFactory); filter.doFilter(this.request, this.response, this.filterChain); @@ -295,11 +244,8 @@ public class TraceFilterTests { this.request = builder().header(SPAN_ID_NAME, PARENT_ID) .header(TRACE_ID_NAME, SpanUtil.idToHex(20L)) .buildRequest(new MockServletContext()); - BeanFactory beanFactory = beanFactory(); - BDDMockito.given(beanFactory.getBean(HttpTracing.class)).willReturn(httpTracing); - TraceFilter filter = new TraceFilter(beanFactory); - filter.doFilter(this.request, this.response, this.filterChain); + TracingFilter.create(httpTracing).doFilter(this.request, this.response, this.filterChain); then(Tracing.current().tracer().currentSpan()).isNull(); then(this.reporter.getSpans()) @@ -314,9 +260,7 @@ public class TraceFilterTests { .header(TRACE_ID_NAME, SpanUtil.idToHex(20L)) .buildRequest(new MockServletContext()); this.traceKeys.getHttp().getHeaders().add("x-foo"); - BeanFactory beanFactory = beanFactory(); - TraceFilter filter = new TraceFilter(beanFactory); - this.request.addHeader("X-Foo", "bar"); + this.request.addHeader("X-Foo", "bar"); filter.doFilter(this.request, this.response, this.filterChain); @@ -332,8 +276,7 @@ public class TraceFilterTests { this.request = builder().header(SPAN_ID_NAME, PARENT_ID) .header(TRACE_ID_NAME, SpanUtil.idToHex(20L)) .buildRequest(new MockServletContext()); - this.traceKeys.getHttp().getHeaders().add("x-foo");BeanFactory beanFactory = beanFactory(); - TraceFilter filter = new TraceFilter(beanFactory); + this.traceKeys.getHttp().getHeaders().add("x-foo"); this.request.addHeader("X-Foo", "bar"); this.request.addHeader("X-Foo", "spam"); filter.doFilter(this.request, this.response, this.filterChain); @@ -351,8 +294,6 @@ public class TraceFilterTests { this.request = builder().header(SPAN_ID_NAME, PARENT_ID) .header(TRACE_ID_NAME, SpanUtil.idToHex(20L)) .buildRequest(new MockServletContext()); - BeanFactory beanFactory = beanFactory(); - TraceFilter filter = new TraceFilter(beanFactory); this.filterChain = new MockFilterChain() { @Override @@ -382,7 +323,6 @@ public class TraceFilterTests { this.request = builder().header(SPAN_ID_NAME, PARENT_ID) .header(TRACE_ID_NAME, SpanUtil.idToHex(20L)) .buildRequest(new MockServletContext()); - TraceFilter filter = new TraceFilter(beanFactory()); this.response.setStatus(404); @@ -395,7 +335,6 @@ public class TraceFilterTests { this.request = builder().header(SPAN_ID_NAME, PARENT_ID) .header(TRACE_ID_NAME, SpanUtil.idToHex(20L)) .buildRequest(new MockServletContext()); - TraceFilter filter = new TraceFilter(beanFactory()); this.response.setStatus(200); filter.doFilter(this.request, this.response, this.filterChain); @@ -410,7 +349,6 @@ public class TraceFilterTests { this.request = builder().header(SPAN_ID_NAME, PARENT_ID) .header(TRACE_ID_NAME, SpanUtil.idToHex(20L)) .buildRequest(new MockServletContext()); - TraceFilter filter = new TraceFilter(beanFactory()); this.response.setStatus(302); filter.doFilter(this.request, this.response, this.filterChain); @@ -425,7 +363,6 @@ public class TraceFilterTests { this.request = builder().header(SPAN_ID_NAME, "asd") .header(TRACE_ID_NAME, SpanUtil.idToHex(20L)) .buildRequest(new MockServletContext()); - TraceFilter filter = new TraceFilter(beanFactory()); filter.doFilter(this.request, this.response, this.filterChain); @@ -440,7 +377,6 @@ public class TraceFilterTests { .header(PARENT_SPAN_ID_NAME, "-") .header(TRACE_ID_NAME, SpanUtil.idToHex(20L)) .buildRequest(new MockServletContext()); - TraceFilter filter = new TraceFilter(beanFactory()); filter.doFilter(this.request, this.response, this.filterChain); @@ -454,9 +390,8 @@ public class TraceFilterTests { this.request = builder() .header(SPAN_FLAGS, 1) .buildRequest(new MockServletContext()); - TraceFilter filter = new TraceFilter(neverSampleTracing()); - filter.doFilter(this.request, this.response, this.filterChain); + neverSampleFilter().doFilter(this.request, this.response, this.filterChain); then(Tracing.current().tracer().currentSpan()).isNull(); then(this.reporter.getSpans()).isNotEmpty(); @@ -467,7 +402,6 @@ public class TraceFilterTests { this.request = builder() .header(SPAN_FLAGS, 0) .buildRequest(new MockServletContext()); - TraceFilter filter = new TraceFilter(beanFactory()); filter.doFilter(this.request, this.response, this.filterChain); @@ -483,8 +417,7 @@ public class TraceFilterTests { .header(SPAN_ID_NAME, SpanUtil.idToHex(10L)) .buildRequest(new MockServletContext()); - TraceFilter filter = new TraceFilter(neverSampleTracing()); - filter.doFilter(this.request, this.response, this.filterChain); + neverSampleFilter().doFilter(this.request, this.response, this.filterChain); then(Tracing.current().tracer().currentSpan()).isNull(); // It is ok to go without a trace ID, if sampling or debug is set @@ -500,9 +433,8 @@ public class TraceFilterTests { .header(SPAN_FLAGS, 1) .header(TRACE_ID_NAME, SpanUtil.idToHex(10L)) .buildRequest(new MockServletContext()); - TraceFilter filter = new TraceFilter(neverSampleTracing()); - filter.doFilter(this.request, this.response, this.filterChain); + neverSampleFilter().doFilter(this.request, this.response, this.filterChain); then(Tracing.current().tracer().currentSpan()).isNull(); then(this.reporter.getSpans()).isEmpty(); @@ -515,7 +447,6 @@ public class TraceFilterTests { .param("foo", "bar") .buildRequest(new MockServletContext()); this.response.setStatus(295); - TraceFilter filter = new TraceFilter(beanFactory()); filter.doFilter(this.request, this.response, this.filterChain); @@ -536,9 +467,8 @@ public class TraceFilterTests { this.request = builder() .header(SPAN_FLAGS, 1) .buildRequest(new MockServletContext()); - TraceFilter filter = new TraceFilter(neverSampleTracing()); - filter.doFilter(this.request, this.response, this.filterChain); + neverSampleFilter().doFilter(this.request, this.response, this.filterChain); then(Tracing.current().tracer().currentSpan()).isNull(); then(this.reporter.getSpans()) @@ -581,18 +511,4 @@ public class TraceFilterTests { .containsEntry("http.status_code", status.toString()); } } - - private BeanFactory beanFactory() { - BDDMockito.given(beanFactory.getBean(SkipPatternProvider.class)) - .willThrow(new NoSuchBeanDefinitionException("foo")); - BDDMockito.given(beanFactory.getBean(SleuthProperties.class)) - .willReturn(this.properties); - BDDMockito.given(beanFactory.getBean(HttpTracing.class)) - .willReturn(this.httpTracing); - BDDMockito.given(beanFactory.getBean(TraceKeys.class)) - .willReturn(this.traceKeys); - BDDMockito.given(beanFactory.getBean(ErrorParser.class)) - .willReturn(new ExceptionMessageErrorParser()); - return beanFactory; - } } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterWebIntegrationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterWebIntegrationTests.java index 635649ddf..7aca0177a 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterWebIntegrationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterWebIntegrationTests.java @@ -85,13 +85,12 @@ public class TraceFilterWebIntegrationTests { then(fromFirstTraceFilterFlow.tags()) .containsEntry("http.status_code", "500") .containsEntry("http.method", "GET") - .containsEntry("error", "Request processing failed; nested exception is java.lang.RuntimeException: Throwing exception") + .containsEntry("error", "Throwing exception") .containsEntry("mvc.controller.class", "ExceptionThrowingController"); Span fromErrorController = this.accumulator.getSpans().get(1); then(fromErrorController.tags()) .containsEntry("http.status_code", "500") - .containsEntry("error", "Request processing failed; nested exception is java.lang.RuntimeException: Throwing exception") - .containsEntry("mvc.controller.class", "BasicErrorController"); + .containsEntry("error", "Request processing failed; nested exception is java.lang.RuntimeException: Throwing exception"); // issue#714 String hex = fromErrorController.traceId(); String[] split = capture.toString().split("\n"); @@ -110,10 +109,10 @@ public class TraceFilterWebIntegrationTests { } catch (HttpClientErrorException e) { } - //TODO: Check if it should be 1 or 2 spans then(Tracing.current().tracer().currentSpan()).isNull(); - then(this.accumulator.getSpans()).hasSize(1); + then(this.accumulator.getSpans()).hasSize(2).as("spans with same id, one from server, one from handler"); then(this.accumulator.getSpans().get(0).kind().ordinal()).isEqualTo(Span.Kind.SERVER.ordinal()); + then(this.accumulator.getSpans().get(0).tags()).containsEntry("http.status_code", "400"); } private int port() { diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptorTests.java deleted file mode 100644 index ad03401f9..000000000 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptorTests.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2013-2018 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 - * - * 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 org.springframework.cloud.sleuth.instrument.web; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.BDDMockito; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.NoSuchBeanDefinitionException; -import org.springframework.boot.web.servlet.error.ErrorController; - -import static org.assertj.core.api.BDDAssertions.then; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.only; - -/** - * @author Marcin Grzejszczak - */ -@RunWith(MockitoJUnitRunner.class) -public class TraceHandlerInterceptorTests { - - @Mock BeanFactory beanFactory; - @InjectMocks TraceHandlerInterceptor traceHandlerInterceptor; - - @Test - public void should_cache_the_retrieved_bean_when_exception_took_place() throws Exception { - given(this.beanFactory.getBean(ErrorController.class)).willThrow(new NoSuchBeanDefinitionException("errorController")); - - then(this.traceHandlerInterceptor.errorController()).isNull(); - then(this.traceHandlerInterceptor.errorController()).isNull(); - BDDMockito.then(this.beanFactory).should(only()).getBean(ErrorController.class); - } - - @Test - public void should_cache_the_retrieved_bean_when_no_exception_took_place() throws Exception { - given(this.beanFactory.getBean(ErrorController.class)).willReturn(() -> null); - - then(this.traceHandlerInterceptor.errorController()).isNotNull(); - then(this.traceHandlerInterceptor.errorController()).isNotNull(); - BDDMockito.then(this.beanFactory).should(only()).getBean(ErrorController.class); - } - -} \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TracePostZuulFilterTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TracePostZuulFilterTests.java index 6baf0d523..27fdc673d 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TracePostZuulFilterTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TracePostZuulFilterTests.java @@ -38,7 +38,6 @@ import org.springframework.cloud.netflix.zuul.metrics.EmptyTracerFactory; import org.springframework.cloud.sleuth.ExceptionMessageErrorParser; import org.springframework.cloud.sleuth.TraceKeys; import org.springframework.cloud.sleuth.instrument.web.SleuthHttpParserAccessor; -import org.springframework.cloud.sleuth.instrument.web.TraceRequestAttributes; import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; import static org.assertj.core.api.BDDAssertions.then; @@ -114,7 +113,5 @@ public class TracePostZuulFilterTests { .containsEntry("http.status_code", "456"); then(spans.get(0).name()).isEqualTo("http:start"); then(this.tracing.tracer().currentSpan()).isNull(); - BDDMockito.then(this.httpServletRequest).should().setAttribute( - TraceRequestAttributes.HANDLED_SPAN_REQUEST_ATTR, "true"); } } \ No newline at end of file diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/src/test/java/integration/MessagingApplicationTests.java b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/src/test/java/integration/MessagingApplicationTests.java index b966a104b..1fe53a086 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/src/test/java/integration/MessagingApplicationTests.java +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/src/test/java/integration/MessagingApplicationTests.java @@ -44,7 +44,8 @@ import static org.assertj.core.api.BDDAssertions.then; @RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest(classes = { IntegrationSpanCollectorConfig.class, SampleMessagingApplication.class }, webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) -@TestPropertySource(properties = { "sample.zipkin.enabled=true" }) +@TestPropertySource(properties = { "sample.zipkin.enabled=true", + "spring.sleuth.http.legacy.enabled=true" }) @DirtiesContext public class MessagingApplicationTests extends AbstractIntegrationTest { @@ -76,7 +77,7 @@ public class MessagingApplicationTests extends AbstractIntegrationTest { long spanId = new Random().nextLong(); await().atMost(15, SECONDS).untilAsserted(() -> - httpMessageWithTraceIdInHeadersIsSuccessfullySent(sampleAppUrl + "/", traceId, spanId).run() + httpMessageWithTraceIdInHeadersIsSuccessfullySent(sampleAppUrl + "/", traceId, spanId).run() ); await().atMost(15, SECONDS).untilAsserted(() -> { @@ -140,7 +141,7 @@ public class MessagingApplicationTests extends AbstractIntegrationTest { private Optional findLastHttpSpansParent() { return this.integrationTestSpanCollector.hashedSpans.stream() - .filter(span -> "get".equals(span.name()) && span.kind() != null).findFirst(); + .filter(span -> "http:/".equals(span.name()) && span.kind() != null).findFirst(); } private Optional findSpanWithKind(Span.Kind kind) {