Add ribbon/zuul features and docs plus a sample
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user