Added client side tags for zuul (#302)

Now when a request is sent via Zuul, the HTTP client side tags will be added to the request
This commit is contained in:
Marcin Grzejszczak
2016-06-10 08:53:01 +02:00
committed by GitHub
parent fbfed722c7
commit 65796e9aa2
10 changed files with 163 additions and 52 deletions

View File

@@ -134,7 +134,7 @@ public class TraceFilter extends GenericFilterBean {
}
}
if (log.isTraceEnabled()) {
log.trace("Received a request to uri [" + uri + "] that matches the skip pattern [" + skip + "]");
log.trace("Received a request to uri [" + uri + "] that matches the should be skipped [" + skip + "]");
}
// in case of a response with exception status a exception controller will close the span
if (!httpStatusSuccessful(response) && isSpanContinued(request)) {

View File

@@ -18,13 +18,15 @@ package org.springframework.cloud.sleuth.instrument.zuul;
import java.lang.invoke.MethodHandles;
import com.netflix.zuul.ZuulFilter;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.TraceKeys;
import org.springframework.cloud.sleuth.Tracer;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
/**8
* A post request {@link ZuulFilter} that publishes an event upon start of the filtering
*
@@ -36,9 +38,11 @@ public class TracePostZuulFilter extends ZuulFilter {
private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass());
private final Tracer tracer;
private final TraceKeys traceKeys;
public TracePostZuulFilter(Tracer tracer) {
public TracePostZuulFilter(Tracer tracer, TraceKeys traceKeys) {
this.tracer = tracer;
this.traceKeys = traceKeys;
}
@Override
@@ -53,6 +57,8 @@ public class TracePostZuulFilter extends ZuulFilter {
if (log.isTraceEnabled()) {
log.trace("Closing current client span " + getCurrentSpan() + "");
}
this.tracer.addTag(this.traceKeys.getHttp().getStatusCode(),
String.valueOf(RequestContext.getCurrentContext().getResponse().getStatus()));
this.tracer.close(getCurrentSpan());
return null;
}

View File

@@ -17,17 +17,19 @@
package org.springframework.cloud.sleuth.instrument.zuul;
import java.lang.invoke.MethodHandles;
import com.netflix.zuul.ExecutionStatus;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.ZuulFilterResult;
import com.netflix.zuul.context.RequestContext;
import java.net.URI;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.SpanInjector;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.instrument.web.HttpTraceKeysInjector;
import com.netflix.zuul.ExecutionStatus;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.ZuulFilterResult;
import com.netflix.zuul.context.RequestContext;
/**
* A pre request {@link ZuulFilter} that sets tracing related headers on the request
@@ -44,10 +46,13 @@ public class TracePreZuulFilter extends ZuulFilter {
private final Tracer tracer;
private final SpanInjector<RequestContext> spanInjector;
private final HttpTraceKeysInjector httpTraceKeysInjector;
public TracePreZuulFilter(Tracer tracer, SpanInjector<RequestContext> spanInjector) {
public TracePreZuulFilter(Tracer tracer, SpanInjector<RequestContext> spanInjector,
HttpTraceKeysInjector httpTraceKeysInjector) {
this.tracer = tracer;
this.spanInjector = spanInjector;
this.httpTraceKeysInjector = httpTraceKeysInjector;
}
@Override
@@ -71,6 +76,7 @@ public class TracePreZuulFilter extends ZuulFilter {
Span newSpan = this.tracer.createSpan(span.getName(), span);
newSpan.tag(Span.SPAN_LOCAL_COMPONENT_TAG_NAME, ZUUL_COMPONENT);
this.spanInjector.inject(newSpan, ctx);
this.httpTraceKeysInjector.addRequestTags(newSpan, URI.create(ctx.getRequest().getRequestURI()), ctx.getRequest().getMethod());
if (log.isTraceEnabled()) {
log.trace("New Zuul Span is " + newSpan + "");
}

View File

@@ -102,7 +102,7 @@ public class TraceRestClientRibbonCommandFactory extends RestClientRibbonCommand
this.httpTraceKeysInjector.addRequestTags(span, getUri(), getVerb().verb());
span.logEvent(Span.CLIENT_SEND);
if (log.isTraceEnabled()) {
log.trace("Span is " + span);
log.trace("Span from RibbonCommandFactory is " + span);
}
}

View File

@@ -23,6 +23,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
import org.springframework.cloud.sleuth.SpanInjector;
import org.springframework.cloud.sleuth.TraceKeys;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration;
import org.springframework.cloud.sleuth.instrument.web.HttpTraceKeysInjector;
@@ -51,8 +52,8 @@ public class TraceZuulAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public TracePreZuulFilter tracePreZuulFilter(Tracer tracer,
SpanInjector<RequestContext> spanInjector) {
return new TracePreZuulFilter(tracer, spanInjector);
SpanInjector<RequestContext> spanInjector, HttpTraceKeysInjector httpTraceKeysInjector) {
return new TracePreZuulFilter(tracer, spanInjector, httpTraceKeysInjector);
}
@Bean
@@ -64,8 +65,8 @@ public class TraceZuulAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public TracePostZuulFilter tracePostZuulFilter(Tracer tracer) {
return new TracePostZuulFilter(tracer);
public TracePostZuulFilter tracePostZuulFilter(Tracer tracer, TraceKeys traceKeys) {
return new TracePostZuulFilter(tracer, traceKeys);
}
@Bean

View File

@@ -17,11 +17,9 @@
package org.springframework.cloud.sleuth.assertions;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -29,6 +27,12 @@ import org.assertj.core.api.AbstractAssert;
import org.assertj.core.api.Assertions;
import org.springframework.cloud.sleuth.Span;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toList;
public class ListOfSpansAssert extends AbstractAssert<ListOfSpansAssert, ListOfSpans> {
private static final Log log = LogFactory.getLog(ListOfSpansAssert.class);
@@ -47,18 +51,34 @@ public class ListOfSpansAssert extends AbstractAssert<ListOfSpansAssert, ListOfS
isNotNull();
printSpans();
List<Long> parentSpanIds = this.actual.spans.stream().flatMap(span -> span.getParents().stream())
.distinct().collect(Collectors.toList());
.distinct().collect(toList());
List<Long> spanIds = this.actual.spans.stream()
.map(Span::getSpanId).distinct()
.collect(Collectors.toList());
.collect(toList());
List<Long> difference = new ArrayList<>(parentSpanIds);
difference.removeAll(spanIds);
log.info("Difference between parent ids and span ids " +
difference.stream().map(span -> "id as long [" + span + "] and as hex [" + Span.idToHex(span) + "]").collect(Collectors.joining("\n")));
difference.stream().map(span -> "id as long [" + span + "] and as hex [" + Span.idToHex(span) + "]").collect(
joining("\n")));
Assertions.assertThat(spanIds).containsAll(parentSpanIds);
return this;
}
public ListOfSpansAssert clientSideSpanWithNameHasTags(String name, Map<String, String> tags) {
isNotNull();
printSpans();
List<Span> matchingSpans = this.actual.spans.stream()
.filter(span -> span.getName().equals(name) && span.logs().stream().filter(entry ->
entry.getEvent().equals(Span.CLIENT_SEND)).findAny().isPresent()).collect(toList());
Assertions.assertThat(matchingSpans).isNotEmpty();
List<Map<String, String>> matchingSpansTags = matchingSpans.stream().map(Span::tags).collect(
toList());
Map<String, String> spanTags = new HashMap<>();
matchingSpansTags.forEach(spanTags::putAll);
Assertions.assertThat(spanTags.entrySet()).containsAll(tags.entrySet());
return this;
}
private void printSpans() {
try {
log.info("Stored spans " + this.objectMapper.writeValueAsString(this.actual.spans));

View File

@@ -16,39 +16,56 @@
package org.springframework.cloud.sleuth.instrument.zuul;
import javax.servlet.http.HttpServletResponse;
import java.util.Random;
import com.netflix.zuul.context.RequestContext;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.BDDMockito;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.cloud.sleuth.DefaultSpanNamer;
import org.springframework.cloud.sleuth.NoOpSpanReporter;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.TraceKeys;
import org.springframework.cloud.sleuth.log.NoOpSpanLogger;
import org.springframework.cloud.sleuth.sampler.AlwaysSampler;
import org.springframework.cloud.sleuth.trace.DefaultTracer;
import org.springframework.cloud.sleuth.trace.TestSpanContextHolder;
import com.netflix.zuul.context.RequestContext;
import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then;
/**
* @author Dave Syer
*
*/
@RunWith(MockitoJUnitRunner.class)
public class TracePostZuulFilterTests {
@Mock HttpServletResponse httpServletResponse;
private DefaultTracer tracer = new DefaultTracer(new AlwaysSampler(),
new Random(), new DefaultSpanNamer(), new NoOpSpanLogger(), new NoOpSpanReporter());
private TracePostZuulFilter filter = new TracePostZuulFilter(this.tracer);
private TracePostZuulFilter filter = new TracePostZuulFilter(this.tracer, new TraceKeys());
@After
@Before
public void clean() {
RequestContext.getCurrentContext().unset();
TestSpanContextHolder.removeCurrentSpan();
RequestContext.testSetCurrentContext(null);
}
@Before
public void setup() {
RequestContext requestContext = new RequestContext();
BDDMockito.given(this.httpServletResponse.getStatus()).willReturn(200);
requestContext.setResponse(this.httpServletResponse);
RequestContext.testSetCurrentContext(requestContext);
}
@Test
@@ -56,7 +73,9 @@ public class TracePostZuulFilterTests {
Span span = this.tracer.createSpan("http:start");
this.filter.run();
then(span).hasLoggedAnEvent(Span.CLIENT_RECV);
then(span)
.hasLoggedAnEvent(Span.CLIENT_RECV)
.hasATag("http.status_code", "200");
then(this.tracer.getCurrentSpan()).isNull();
}
}

View File

@@ -16,47 +16,63 @@
package org.springframework.cloud.sleuth.instrument.zuul;
import javax.servlet.http.HttpServletRequest;
import java.util.Random;
import java.util.concurrent.atomic.AtomicReference;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.monitoring.MonitoringHelper;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.BDDMockito;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.cloud.sleuth.DefaultSpanNamer;
import org.springframework.cloud.sleuth.NoOpSpanReporter;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.TraceKeys;
import org.springframework.cloud.sleuth.instrument.web.HttpTraceKeysInjector;
import org.springframework.cloud.sleuth.log.NoOpSpanLogger;
import org.springframework.cloud.sleuth.sampler.AlwaysSampler;
import org.springframework.cloud.sleuth.sampler.NeverSampler;
import org.springframework.cloud.sleuth.trace.DefaultTracer;
import org.springframework.cloud.sleuth.trace.TestSpanContextHolder;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.monitoring.MonitoringHelper;
import static org.assertj.core.api.BDDAssertions.then;
import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then;
/**
* @author Dave Syer
*
*/
@RunWith(MockitoJUnitRunner.class)
public class TracePreZuulFilterTests {
@Mock HttpServletRequest httpServletRequest;
private DefaultTracer tracer = new DefaultTracer(new AlwaysSampler(), new Random(),
new DefaultSpanNamer(), new NoOpSpanLogger(), new NoOpSpanReporter());
private TracePreZuulFilter filter = new TracePreZuulFilter(this.tracer, new RequestContextInjector());
private TracePreZuulFilter filter = new TracePreZuulFilter(this.tracer, new RequestContextInjector(),
new HttpTraceKeysInjector(this.tracer, new TraceKeys()));
@After
public void clean() {
RequestContext.getCurrentContext().unset();
TestSpanContextHolder.removeCurrentSpan();
RequestContext.testSetCurrentContext(null);
}
@Before
public void setup() {
MonitoringHelper.initMocks();
}
@After
@Before
public void clean() {
RequestContext.getCurrentContext().unset();
TestSpanContextHolder.removeCurrentSpan();
RequestContext requestContext = new RequestContext();
BDDMockito.given(this.httpServletRequest.getRequestURI()).willReturn("http://foo.bar");
BDDMockito.given(this.httpServletRequest.getMethod()).willReturn("GET");
requestContext.setRequest(this.httpServletRequest);
RequestContext.testSetCurrentContext(requestContext);
}
@Test
@@ -85,13 +101,13 @@ public class TracePreZuulFilterTests {
.isEqualTo(Span.SPAN_NOT_SAMPLED);
}
@Test
public void shouldCloseSpanWhenExceptionIsThrown() throws Exception {
Span startedSpan = this.tracer.createSpan("http:start", NeverSampler.INSTANCE);
Span startedSpan = this.tracer.createSpan("http:start");
final AtomicReference<Span> span = new AtomicReference<>();
new TracePreZuulFilter(this.tracer, new RequestContextInjector()) {
new TracePreZuulFilter(this.tracer, new RequestContextInjector(),
new HttpTraceKeysInjector(this.tracer, new TraceKeys())) {
@Override
public Object run() {
super.run();
@@ -102,15 +118,17 @@ public class TracePreZuulFilterTests {
then(startedSpan).isNotEqualTo(span.get());
then(span.get().logs()).extracting("event").contains(Span.CLIENT_SEND);
then(span.get()).hasATag("http.method", "GET");
then(this.tracer.getCurrentSpan()).isEqualTo(startedSpan);
}
@Test
public void shouldNotCloseSpanWhenNoExceptionIsThrown() throws Exception {
Span startedSpan = this.tracer.createSpan("http:start", NeverSampler.INSTANCE);
Span startedSpan = this.tracer.createSpan("http:start");
final AtomicReference<Span> span = new AtomicReference<>();
new TracePreZuulFilter(this.tracer, new RequestContextInjector()) {
new TracePreZuulFilter(this.tracer, new RequestContextInjector(),
new HttpTraceKeysInjector(this.tracer, new TraceKeys())) {
@Override
public Object run() {
span.set(TracePreZuulFilterTests.this.tracer.getCurrentSpan());

View File

@@ -18,7 +18,9 @@ package org.springframework.cloud.sleuth.instrument.zuul;
import com.netflix.client.http.HttpRequest;
import com.netflix.niws.client.http.RestClient;
import com.netflix.zuul.context.RequestContext;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -31,6 +33,7 @@ import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.SpanInjector;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.instrument.web.HttpTraceKeysInjector;
import org.springframework.cloud.sleuth.trace.TestSpanContextHolder;
import static org.assertj.core.api.BDDAssertions.then;
import static org.mockito.BDDMockito.given;
@@ -63,6 +66,12 @@ public class TraceRestClientRibbonCommandFactoryTest {
given(this.tracer.isTracing()).willReturn(true);
}
@After
public void cleanup() {
RequestContext.getCurrentContext().unset();
TestSpanContextHolder.removeCurrentSpan();
}
@Test
public void should_wrap_ribbon_command_in_a_sleuth_representation() throws Exception {
RestClientRibbonCommand restClientRibbonCommand = this.traceRestClientRibbonCommandFactory

View File

@@ -1,10 +1,11 @@
package org.springframework.cloud.sleuth.instrument.zuul;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.util.HashMap;
import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.ServerList;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -26,6 +27,7 @@ import org.springframework.cloud.sleuth.SpanReporter;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.assertions.ListOfSpans;
import org.springframework.cloud.sleuth.sampler.AlwaysSampler;
import org.springframework.cloud.sleuth.trace.TestSpanContextHolder;
import org.springframework.cloud.sleuth.util.ArrayListSpanAccumulator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -42,15 +44,21 @@ import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.DefaultResponseErrorHandler;
import org.springframework.web.client.RestTemplate;
import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.ServerList;
import com.netflix.zuul.context.RequestContext;
import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = SampleZuulProxyApplication.class)
@WebAppConfiguration
@IntegrationTest({ "server.port: 0", "zuul.routes.simple: /simple/**", "hystrix.command.default.execution.isolation.strategy: SEMAPHORE"})
@IntegrationTest({ "server.port: 0", "zuul.routes.simple: /simple/**" })
@DirtiesContext
public class TraceZuulIntegrationTests {
private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass());
@Value("${local.server.port}")
private int port;
@Autowired Tracer tracer;
@@ -59,14 +67,17 @@ public class TraceZuulIntegrationTests {
@Before
public void cleanup() {
TestSpanContextHolder.removeCurrentSpan();
RequestContext.getCurrentContext().unset();
this.spanAccumulator.getSpans().clear();
}
@Test
public void should_close_span_when_routing_to_service_via_discovery() {
Span span = this.tracer.createSpan("new_span");
log.info("Started span " + span);
ResponseEntity<String> result = this.restTemplate.exchange(
"http://localhost:" + this.port + "/simple/", HttpMethod.GET,
"http://localhost:" + this.port + "/simple/foo", HttpMethod.GET,
new HttpEntity<>((Void) null), String.class);
this.tracer.close(span);
@@ -75,12 +86,17 @@ public class TraceZuulIntegrationTests {
then(result.getBody()).isEqualTo("Hello world");
then(this.tracer.getCurrentSpan()).isNull();
then(new ListOfSpans(this.spanAccumulator.getSpans()))
.everyParentIdHasItsCorrespondingSpan();
.everyParentIdHasItsCorrespondingSpan()
.clientSideSpanWithNameHasTags("http:/simple/foo", TestTag.tag()
.tag("http.method", "GET")
.tag("http.status_code", "200")
.tag("http.path", "/simple/foo"));
}
@Test
public void should_close_span_when_routing_to_service_via_discovery_to_a_non_existent_url() {
Span span = this.tracer.createSpan("new_span");
log.info("Started span " + span);
ResponseEntity<String> result = this.restTemplate.exchange(
"http://localhost:" + this.port + "/simple/nonExistentUrl", HttpMethod.GET,
new HttpEntity<>((Void) null), String.class);
@@ -90,7 +106,23 @@ public class TraceZuulIntegrationTests {
then(result.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
then(this.tracer.getCurrentSpan()).isNull();
then(new ListOfSpans(this.spanAccumulator.getSpans()))
.everyParentIdHasItsCorrespondingSpan();
.everyParentIdHasItsCorrespondingSpan()
.clientSideSpanWithNameHasTags("http:/simple/nonExistentUrl", TestTag.tag()
.tag("http.method", "GET")
.tag("http.status_code", "404")
.tag("http.path", "/simple/nonExistentUrl"));
}
private static class TestTag extends HashMap<String, String> {
public static TestTag tag() {
return new TestTag();
}
public TestTag tag(String key, String value) {
put(key, value);
return this;
}
}
}
@@ -103,7 +135,7 @@ public class TraceZuulIntegrationTests {
class SampleZuulProxyApplication {
@RequestMapping("/")
@RequestMapping("/foo")
public String home() {
return "Hello world";
}