Add ribbon/zuul features and docs plus a sample

This commit is contained in:
Dave Syer
2015-08-13 10:44:43 +01:00
parent 74ee062d6d
commit 4b7154e1e5
25 changed files with 493 additions and 44 deletions

View File

@@ -56,6 +56,7 @@ public interface Trace {
String SPAN_NAME_NAME = "X-Span-Name";
String PARENT_ID_NAME = "X-Parent-Id";
String PROCESS_ID_NAME = "X-Process-Id";
String NOT_SAMPLED_NAME = "X-Not-Sampled";
/**
* Creates a trace scope wrapping a new span.

View File

@@ -16,6 +16,7 @@
package org.springframework.cloud.sleuth.instrument.integration;
import static org.springframework.cloud.sleuth.Trace.NOT_SAMPLED_NAME;
import static org.springframework.cloud.sleuth.Trace.PARENT_ID_NAME;
import static org.springframework.cloud.sleuth.Trace.PROCESS_ID_NAME;
import static org.springframework.cloud.sleuth.Trace.SPAN_ID_NAME;
@@ -37,7 +38,11 @@ import org.springframework.messaging.Message;
public class SpanMessageHeaders {
public static Message<?> addSpanHeaders(Message<?> message, Span span) {
if (span==null) {
if (span == null) {
if (!message.getHeaders().containsKey(NOT_SAMPLED_NAME)) {
return MessageBuilder.fromMessage(message)
.setHeader(NOT_SAMPLED_NAME, "").build();
}
return message;
}
Map<String, String> headers = new HashMap<String, String>();
@@ -50,14 +55,13 @@ public class SpanMessageHeaders {
}
private static void addHeader(Map<String, String> headers, String name, String value) {
if (value!=null) {
if (value != null) {
headers.put(name, value);
}
}
private static String getFirst(List<String> parents) {
return parents==null || parents.isEmpty() ? null : parents.get(0);
return parents == null || parents.isEmpty() ? null : parents.get(0);
}
}

View File

@@ -16,6 +16,7 @@
package org.springframework.cloud.sleuth.instrument.integration;
import static org.springframework.cloud.sleuth.Trace.NOT_SAMPLED_NAME;
import static org.springframework.cloud.sleuth.Trace.PARENT_ID_NAME;
import static org.springframework.cloud.sleuth.Trace.PROCESS_ID_NAME;
import static org.springframework.cloud.sleuth.Trace.SPAN_ID_NAME;
@@ -47,8 +48,10 @@ public class TraceChannelInterceptor extends ChannelInterceptorAdapter {
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
if (TraceContextHolder.isTracing()) {
return SpanMessageHeaders.addSpanHeaders(message, TraceContextHolder.getCurrentSpan());
if (TraceContextHolder.isTracing()
|| message.getHeaders().containsKey(NOT_SAMPLED_NAME)) {
return SpanMessageHeaders.addSpanHeaders(message,
TraceContextHolder.getCurrentSpan());
}
String spanId = getHeader(message, SPAN_ID_NAME);
String traceId = getHeader(message, TRACE_ID_NAME);

View File

@@ -15,6 +15,7 @@
*/
package org.springframework.cloud.sleuth.instrument.web;
import static org.springframework.cloud.sleuth.Trace.NOT_SAMPLED_NAME;
import static org.springframework.cloud.sleuth.Trace.PARENT_ID_NAME;
import static org.springframework.cloud.sleuth.Trace.PROCESS_ID_NAME;
import static org.springframework.cloud.sleuth.Trace.SPAN_ID_NAME;
@@ -82,13 +83,17 @@ public class TraceFilter extends OncePerRequestFilter {
throws ServletException, IOException {
String uri = this.urlPathHelper.getPathWithinApplication(request);
boolean skip = this.skipPattern.matcher(uri).matches();
boolean skip = this.skipPattern.matcher(uri).matches()
|| getHeader(request, response, NOT_SAMPLED_NAME) != null;
TraceScope traceScope = (TraceScope) request.getAttribute(TRACE_REQUEST_ATTR);
if (traceScope != null) {
this.trace.continueSpan(traceScope.getSpan());
}
else if (!skip) {
else if (skip) {
addToResponseIfNotPresent(response, NOT_SAMPLED_NAME, "");
}
else {
String spanId = getHeader(request, response, SPAN_ID_NAME);
String traceId = getHeader(request, response, TRACE_ID_NAME);
String name = "http" + uri;
@@ -133,7 +138,7 @@ public class TraceFilter extends OncePerRequestFilter {
}
finally {
if (isAsyncStarted(request) || request.isAsyncStarted()) {
//TODO: how to deal with response annotations and async?
// TODO: how to deal with response annotations and async?
return;
}
if (traceScope != null) {
@@ -145,11 +150,10 @@ public class TraceFilter extends OncePerRequestFilter {
protected void addRequestAnnotations(HttpServletRequest request) {
String uri = this.urlPathHelper.getPathWithinApplication(request);
this.trace.addKVAnnotation("/http/request/uri",
request.getRequestURL().toString());
this.trace.addKVAnnotation("/http/request/uri", request.getRequestURL()
.toString());
this.trace.addKVAnnotation("/http/request/endpoint", uri);
this.trace.addKVAnnotation("/http/request/method",
request.getMethod());
this.trace.addKVAnnotation("/http/request/method", request.getMethod());
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
@@ -157,7 +161,7 @@ public class TraceFilter extends OncePerRequestFilter {
Enumeration<String> values = request.getHeaders(name);
while (values.hasMoreElements()) {
String value = values.nextElement();
String key = "/http/request/headers/"+name.toLowerCase();
String key = "/http/request/headers/" + name.toLowerCase();
this.trace.addKVAnnotation(key, value);
}
@@ -170,7 +174,7 @@ public class TraceFilter extends OncePerRequestFilter {
for (String name : response.getHeaderNames()) {
for (String value : response.getHeaders(name)) {
String key = "/http/response/headers/"+name.toLowerCase();
String key = "/http/response/headers/" + name.toLowerCase();
this.trace.addKVAnnotation(key, value);
}
}

View File

@@ -15,6 +15,7 @@
*/
package org.springframework.cloud.sleuth.instrument.web.client;
import static org.springframework.cloud.sleuth.Trace.NOT_SAMPLED_NAME;
import static org.springframework.cloud.sleuth.Trace.PARENT_ID_NAME;
import static org.springframework.cloud.sleuth.Trace.PROCESS_ID_NAME;
import static org.springframework.cloud.sleuth.Trace.SPAN_ID_NAME;
@@ -61,6 +62,7 @@ ApplicationEventPublisherAware {
public ClientHttpResponse intercept(HttpRequest request, byte[] body,
ClientHttpRequestExecution execution) throws IOException {
if (getCurrentSpan() == null) {
setHeader(request, NOT_SAMPLED_NAME, "");
return execution.execute(request, body);
}
setHeader(request, SPAN_ID_NAME, getCurrentSpan().getSpanId());

View File

@@ -0,0 +1,109 @@
/*
* Copyright 2015 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.zuul;
import static org.springframework.cloud.sleuth.Trace.NOT_SAMPLED_NAME;
import static org.springframework.cloud.sleuth.Trace.PARENT_ID_NAME;
import static org.springframework.cloud.sleuth.Trace.PROCESS_ID_NAME;
import static org.springframework.cloud.sleuth.Trace.SPAN_ID_NAME;
import static org.springframework.cloud.sleuth.Trace.SPAN_NAME_NAME;
import static org.springframework.cloud.sleuth.Trace.TRACE_ID_NAME;
import static org.springframework.cloud.sleuth.TraceContextHolder.getCurrentSpan;
import static org.springframework.cloud.sleuth.TraceContextHolder.isTracing;
import java.util.Map;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.event.ClientSentEvent;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.util.ReflectionUtils;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
/**
* @author Dave Syer
*
*/
public class TracePreZuulFilter extends ZuulFilter implements
ApplicationEventPublisherAware {
private ApplicationEventPublisher publisher;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
Map<String, String> response = ctx.getZuulRequestHeaders();
// N.B. this will only work with the simple host filter (not ribbon) unless you set hystrix.execution.isolation.strategy=SEMAPHORE
if (getCurrentSpan() == null) {
setHeader(response, NOT_SAMPLED_NAME, "");
return null;
}
try {
setHeader(response, SPAN_ID_NAME, getCurrentSpan().getSpanId());
setHeader(response, TRACE_ID_NAME, getCurrentSpan().getTraceId());
setHeader(response, SPAN_NAME_NAME, getCurrentSpan().getName());
setHeader(response, PARENT_ID_NAME, getParentId(getCurrentSpan()));
setHeader(response, PROCESS_ID_NAME, getCurrentSpan().getProcessId());
publish(new ClientSentEvent(this, getCurrentSpan()));
}
catch (Exception ex) {
ReflectionUtils.rethrowRuntimeException(ex);
}
return null;
}
private String getParentId(Span span) {
return span.getParents() != null && !span.getParents().isEmpty() ? span
.getParents().get(0) : null;
}
public void setHeader(Map<String, String> request, String name, String value) {
if (value != null && !request.containsKey(name) && isTracing()) {
request.put(name, value);
}
}
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 0;
}
private void publish(ApplicationEvent event) {
if (this.publisher != null) {
this.publisher.publishEvent(event);
}
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright 2013-2015 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.zuul;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.cloud.sleuth.Trace;
import org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.netflix.zuul.ZuulFilter;
/**
* Registers beans that add tracing to requests
*
* @author Dave Syer
*/
@Configuration
@ConditionalOnProperty(value = "spring.sleuth.zuul.enabled", matchIfMissing = true)
@ConditionalOnWebApplication
@ConditionalOnClass(ZuulFilter.class)
@ConditionalOnBean(Trace.class)
@AutoConfigureAfter(TraceAutoConfiguration.class)
public class TraceZuulAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public TracePreZuulFilter traceZuulFilter() {
return new TracePreZuulFilter();
}
}

View File

@@ -8,4 +8,5 @@ org.springframework.cloud.sleuth.instrument.async.AsyncDefaultAutoConfiguration,
org.springframework.cloud.sleuth.instrument.scheduling.TraceSchedulingAutoConfiguration,\
org.springframework.cloud.sleuth.instrument.web.TraceWebAutoConfiguration,\
org.springframework.cloud.sleuth.instrument.web.client.TraceWebClientAutoConfiguration,\
org.springframework.cloud.sleuth.instrument.web.client.TraceFeignClientAutoConfiguration
org.springframework.cloud.sleuth.instrument.web.client.TraceFeignClientAutoConfiguration,\
org.springframework.cloud.sleuth.instrument.zuul.TraceZuulAutoConfiguration

View File

@@ -17,6 +17,8 @@
package org.springframework.cloud.sleuth.instrument.integration;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.springframework.cloud.sleuth.Trace.NOT_SAMPLED_NAME;
import static org.springframework.cloud.sleuth.Trace.SPAN_ID_NAME;
import static org.springframework.cloud.sleuth.Trace.TRACE_ID_NAME;
@@ -48,7 +50,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
* @author Dave Syer
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes=App.class)
@SpringApplicationConfiguration(classes = App.class)
@IntegrationTest
@DirtiesContext
public class TraceChannelInterceptorTests implements MessageHandler {
@@ -78,6 +80,16 @@ public class TraceChannelInterceptorTests implements MessageHandler {
this.channel.unsubscribe(this);
}
@Test
public void testNoSpanCreation() {
this.channel.send(MessageBuilder.withPayload("hi").setHeader(NOT_SAMPLED_NAME, "")
.build());
assertNotNull("message was null", this.message);
String spanId = this.message.getHeaders().get(SPAN_ID_NAME, String.class);
assertNull("spanId was not null", spanId);
}
@Test
public void testSpanCreation() {
this.channel.send(MessageBuilder.withPayload("hi").build());
@@ -92,7 +104,8 @@ public class TraceChannelInterceptorTests implements MessageHandler {
@Test
public void testHeaderCreation() {
TraceScope traceScope = this.trace.startSpan("testSendMessage", new AlwaysSampler(), null);
TraceScope traceScope = this.trace.startSpan("testSendMessage",
new AlwaysSampler(), null);
this.channel.send(MessageBuilder.withPayload("hi").build());
traceScope.close();
assertNotNull("message was null", this.message);

View File

@@ -88,7 +88,7 @@ public class TraceRestTemplateInterceptorTests {
public Map<String, String> home(@RequestHeader HttpHeaders headers) {
Map<String, String> map = new HashMap<String, String>();
addHeaders(map, headers, Trace.SPAN_ID_NAME, Trace.TRACE_ID_NAME,
Trace.PARENT_ID_NAME, Trace.SPAN_NAME_NAME, Trace.PROCESS_ID_NAME);
Trace.PARENT_ID_NAME);
return map;
}