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
This commit is contained in:
Marcin Grzejszczak
2018-03-04 09:53:52 +01:00
committed by GitHub
parent a9779b15d8
commit 347c63ea82
15 changed files with 295 additions and 814 deletions

View File

@@ -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() {
}
}

View File

@@ -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;
}
}

View File

@@ -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.
*
* <p>
* 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<HttpServletRequest, HttpServletResponse> 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<HttpServletRequest, HttpServletResponse> 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;
}
}

View File

@@ -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> errorController;
private HttpServerHandler<HttpServletRequest, HttpServletResponse> 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<HttpServletRequest, HttpServletResponse> 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();
}
}

View File

@@ -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.<HandlerMapping>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;
}
}

View File

@@ -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));
}
}
}

View File

@@ -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);
}
}

View File

@@ -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());

View File

@@ -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;
}

View File

@@ -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",

View File

@@ -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;
}
}

View File

@@ -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() {

View File

@@ -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);
}
}

View File

@@ -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");
}
}

View File

@@ -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<Span> 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<Span> findSpanWithKind(Span.Kind kind) {