[#19] Introduced SpanInjector and SpanExtractor

This commit is contained in:
Marcin Grzejszczak
2016-03-15 14:33:39 +01:00
parent 05540b9d2e
commit 7c5af8c84e
50 changed files with 1296 additions and 501 deletions

View File

@@ -65,6 +65,7 @@ public class Span {
public static final String SPAN_EXPORT_NAME = "X-Span-Export";
public static final String SPAN_LOCAL_COMPONENT_TAG_NAME = "lc";
/**
* <b>cr</b> - Client Receive. Signifies the end of the span. The client has successfully received the
* response from the server side. If one subtracts the cs timestamp from this timestamp one

View File

@@ -0,0 +1,39 @@
/*
* Copyright 2013-2016 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;
/**
* Adopted from <a href="https://github.com/opentracing/opentracing-java/pull/11/files#diff-eb9c3460aba76aabc0de04b05e4a2b3d"></a>OpenTracing</a>
*
* @author Marcin Grzejszczak
*
* @since 1.0.0
*/
public interface SpanExtractor<T> {
/** Returns a SpanBuilder provided
* a “carrier” object from which to extract identifying information needed by the new Span instance.
*
* If the carrier object has no such span stored within it, a new Span is created.
*
* Unless theres an error, it returns a Span.
* The Span generated from the builder can be used in the host process like any other.
*
* (Note that some OpenTracing implementations consider the Spans on either side of an RPC to have the same identity,
* and others consider the caller to be the parent and the receiver to be the child).
*/
Span joinTrace(T carrier);
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2013-2016 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;
/**
* Adopted from <a href="https://github.com/opentracing/opentracing-java/blob/master/opentracing/src/main/java/opentracing/Tracer.java"></a>OpenTracing</a>
*
* @author Marcin Grzejszczak
*
* @since 1.0.0
*/
public interface SpanInjector<T> {
/** Takes two arguments:
* a Span instance, and
* a “carrier” object in which to inject that Span for cross-process propagation.
*
* A “carrier” object is some sort of http or rpc envelope, for example HeaderGroup (from Apache HttpComponents).
*
* Attempting to inject to a carrier that has been registered/configured to this Tracer will result in a
* IllegalStateException.
*/
void inject(Span span, T carrier);
}

View File

@@ -0,0 +1,128 @@
/*
* Copyright 2013-2016 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;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* Names of default headers that need to be sent between processes
* for tracing to be operational.
*
* Default Zipkin Headers are
*
* <ul>
* <li>{@code X-B3-TraceId} 64 encoded bits</li>
* <li>{@code X-B3-SpanId} 64 encoded bits</li>
* <li>{@code X-B3-ParentSpanId} 64 encoded bits</li>
* <li>{@code X-B3-Sampled} Boolean (either “1” or “0”)</li>
* </ul>
*
* @author Marcin Grzejszczak
*
* @since 1.0.0
*/
@ConfigurationProperties("spring.sleuth.headers")
public class TraceHeaders {
private Zipkin zipkin;
private Sleuth sleuth;
public Zipkin getZipkin() {
return this.zipkin;
}
public void setZipkin(Zipkin zipkin) {
this.zipkin = zipkin;
}
public Sleuth getSleuth() {
return this.sleuth;
}
public void setSleuth(Sleuth sleuth) {
this.sleuth = sleuth;
}
private static class Zipkin {
private String traceId = "X-B3-TraceId";
private String spanId = "X-B3-SpanId";
private String parentSpanId = "X-B3-ParentSpanId";
private String sampled = "X-B3-Sampled";
public String getTraceId() {
return this.traceId;
}
public void setTraceId(String traceId) {
this.traceId = traceId;
}
public String getSpanId() {
return this.spanId;
}
public void setSpanId(String spanId) {
this.spanId = spanId;
}
public String getParentSpanId() {
return this.parentSpanId;
}
public void setParentSpanId(String parentSpanId) {
this.parentSpanId = parentSpanId;
}
public String getSampled() {
return this.sampled;
}
public void setSampled(String sampled) {
this.sampled = sampled;
}
}
private static class Sleuth {
private String processId = "X-Process-Id";
private String spanName = "X-Span-Name";
private String exportable = "X-Span-Export";
public String getProcessId() {
return this.processId;
}
public void setProcessId(String processId) {
this.processId = processId;
}
public String getSpanName() {
return this.spanName;
}
public void setSpanName(String spanName) {
this.spanName = spanName;
}
public String getExportable() {
return this.exportable;
}
public void setExportable(String exportable) {
this.exportable = exportable;
}
}
}

View File

@@ -62,8 +62,8 @@ public class TraceAutoConfiguration {
@Bean
@ConditionalOnMissingBean(Tracer.class)
public DefaultTracer traceManager(Sampler sampler, Random random,
SpanNamer spanNamer, SpanLogger spanLogger,
SpanReporter spanReporter) {
SpanNamer spanNamer, SpanLogger spanLogger,
SpanReporter spanReporter) {
return new DefaultTracer(sampler, random, spanNamer, spanLogger,
spanReporter);
}

View File

@@ -1,16 +1,17 @@
package org.springframework.cloud.sleuth.instrument.messaging;
import java.util.Random;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.SpanExtractor;
import org.springframework.cloud.sleuth.SpanInjector;
import org.springframework.cloud.sleuth.TraceKeys;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.integration.channel.AbstractMessageChannel;
import org.springframework.integration.context.IntegrationObjectSupport;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.support.ChannelInterceptorAdapter;
import org.springframework.messaging.support.ExecutorChannelInterceptor;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.util.ClassUtils;
/**
@@ -31,16 +32,17 @@ abstract class AbstractTraceChannelInterceptor extends ChannelInterceptorAdapter
protected static final String MESSAGE_COMPONENT = "message";
private final Tracer tracer;
private final Random random;
private final TraceKeys traceKeys;
private final SpanExtractor<Message> spanExtractor;
private final SpanInjector<MessageBuilder> spanInjector;
protected AbstractTraceChannelInterceptor(Tracer tracer, TraceKeys traceKeys,
Random random) {
SpanExtractor<Message> spanExtractor,
SpanInjector<MessageBuilder> spanInjector) {
this.tracer = tracer;
this.traceKeys = traceKeys;
this.random = random;
this.spanExtractor = spanExtractor;
this.spanInjector = spanInjector;
}
protected Tracer getTracer() {
@@ -51,49 +53,16 @@ abstract class AbstractTraceChannelInterceptor extends ChannelInterceptorAdapter
return this.traceKeys;
}
protected SpanInjector<MessageBuilder> getSpanInjector() {
return this.spanInjector;
}
/**
* Returns a span given the message and a channel. Returns null when there was no
* trace id passed initially.
* Returns a span given the message and a channel. Returns {@code null} if ids
* are missing.
*/
protected Span buildSpan(Message<?> message) {
if (!hasHeader(message, Span.TRACE_ID_NAME)
|| !hasHeader(message, Span.SPAN_ID_NAME)) {
return null; // cannot build a span without ids
}
long spanId = hasHeader(message, Span.SPAN_ID_NAME)
? Span.hexToId(getHeader(message, Span.SPAN_ID_NAME))
: this.random.nextLong();
long traceId = Span.hexToId(getHeader(message, Span.TRACE_ID_NAME));
Span.SpanBuilder span = Span.builder().traceId(traceId).spanId(spanId);
if (hasHeader(message, Span.NOT_SAMPLED_NAME)) {
span.exportable(false);
}
String parentId = getHeader(message, Span.PARENT_ID_NAME);
String processId = getHeader(message, Span.PROCESS_ID_NAME);
String spanName = getHeader(message, Span.SPAN_NAME_NAME);
if (spanName != null) {
span.name(spanName);
}
if (processId != null) {
span.processId(processId);
}
if (parentId != null) {
span.parent(Span.hexToId(parentId));
}
span.remote(true);
return span.build();
}
String getHeader(Message<?> message, String name) {
return getHeader(message, name, String.class);
}
<T> T getHeader(Message<?> message, String name, Class<T> type) {
return message.getHeaders().get(name, type);
}
boolean hasHeader(Message<?> message, String name) {
return message.getHeaders().containsKey(name);
return this.spanExtractor.joinTrace(message);
}
String getChannelName(MessageChannel channel) {

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2013-2016 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.messaging;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
/**
* Utility class to contain both {@link MessageBuilder} and the {@link Message}.
*
* {@link MessageBuilder} is mutable
* {@link Message} is immutable
*
* @author Marcin Grzejszczak
*/
public class MessageBuilderHolder {
final MessageBuilder messageBuilder;
final Message message;
public MessageBuilderHolder(MessageBuilder messageBuilder, Message message) {
this.messageBuilder = messageBuilder;
this.message = message;
}
public MessageBuilderHolder(MessageBuilder messageBuilder) {
this.messageBuilder = messageBuilder;
this.message = messageBuilder.build();
}
}

View File

@@ -0,0 +1,83 @@
/*
* Copyright 2013-2016 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.messaging;
import java.util.Random;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.Span.SpanBuilder;
import org.springframework.cloud.sleuth.SpanExtractor;
import org.springframework.messaging.Message;
/**
* Creates a {@link SpanBuilder} from {@link Message}
*
* @author Marcin Grzejszczak
*
* @since 1.0.0
*/
public class MessagingSpanExtractor implements SpanExtractor<Message> {
private final Random random;
public MessagingSpanExtractor(Random random) {
this.random = random;
}
@Override
public Span joinTrace(Message carrier) {
if (!hasHeader(carrier, Span.TRACE_ID_NAME)
|| !hasHeader(carrier, Span.SPAN_ID_NAME)) {
return null;
//TODO: Consider throwing IllegalArgumentException;
}
long spanId = hasHeader(carrier, Span.SPAN_ID_NAME)
? Span.hexToId(getHeader(carrier, Span.SPAN_ID_NAME))
: this.random.nextLong();
long traceId = Span.hexToId(getHeader(carrier, Span.TRACE_ID_NAME));
SpanBuilder spanBuilder = Span.builder().traceId(traceId).spanId(spanId);
if (hasHeader(carrier, Span.NOT_SAMPLED_NAME)) {
spanBuilder.exportable(false);
}
String parentId = getHeader(carrier, Span.PARENT_ID_NAME);
String processId = getHeader(carrier, Span.PROCESS_ID_NAME);
String spanName = getHeader(carrier, Span.SPAN_NAME_NAME);
if (spanName != null) {
spanBuilder.name(spanName);
}
if (processId != null) {
spanBuilder.processId(processId);
}
if (parentId != null) {
spanBuilder.parent(Span.hexToId(parentId));
}
spanBuilder.remote(true);
return spanBuilder.build();
}
String getHeader(Message<?> message, String name) {
return getHeader(message, name, String.class);
}
<T> T getHeader(Message<?> message, String name, Class<T> type) {
return message.getHeaders().get(name, type);
}
boolean hasHeader(Message<?> message, String name) {
return message.getHeaders().containsKey(name);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2015 the original author or authors.
* Copyright 2013-2016 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.
@@ -21,6 +21,7 @@ import java.util.List;
import java.util.Map;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.SpanInjector;
import org.springframework.cloud.sleuth.TraceKeys;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
@@ -29,53 +30,40 @@ import org.springframework.messaging.support.NativeMessageHeaderAccessor;
import org.springframework.util.StringUtils;
/**
* Utility for manipulating message headers related to span data.
* Creates a {@link Span.SpanBuilder} from {@link Message}
*
* @author Dave Syer
* @author Marcin Grzejszczak
*
* @since 1.0.0
*/
public class SpanMessageHeaders {
public class MessagingSpanInjector implements SpanInjector<MessageBuilder> {
public static final String SPAN_HEADER = "X-Current-Span";
public static Span getSpanFromHeader(Message<?> message) {
if (message == null) {
return null;
}
Object object = message.getHeaders().get(SPAN_HEADER);
if (object instanceof Span) {
return (Span) object;
}
return null;
private final TraceKeys traceKeys;
public MessagingSpanInjector(TraceKeys traceKeys) {
this.traceKeys = traceKeys;
}
/**
* Adds default headers for a message. Check {@link Span} constants for
* more information what the default headers are. If a span already has
* a tag set it will not get overridden.
*
* @param traceKeys - the global configuration for trace keys
* @param message - message to which headers will be added
* @param span - span from which headers will be taken
* @return the input message with updated headers
*/
public static Message<?> addSpanHeaders(TraceKeys traceKeys, Message<?> message,
Span span) {
@Override
public void inject(Span span, MessageBuilder carrier) {
Message initialMessage = carrier.build();
MessageHeaderAccessor accessor = MessageHeaderAccessor
.getMutableAccessor(message);
.getMutableAccessor(initialMessage);
if (span == null) {
if (!message.getHeaders().containsKey(Span.NOT_SAMPLED_NAME)) {
if (!initialMessage.getHeaders().containsKey(Span.NOT_SAMPLED_NAME)) {
accessor.setHeader(Span.NOT_SAMPLED_NAME, "true");
return MessageBuilder.createMessage(message.getPayload(),
accessor.getMessageHeaders());
carrier.setHeaders(accessor);
return;
}
return message;
return;
}
Map<String, String> headers = new HashMap<>();
addHeader(headers, Span.TRACE_ID_NAME, Span.idToHex(span.getTraceId()));
addHeader(headers, Span.SPAN_ID_NAME, Span.idToHex(span.getSpanId()));
if (span.isExportable()) {
addAnnotations(traceKeys, message, span);
addAnnotations(this.traceKeys, initialMessage, span);
Long parentId = getFirst(span.getParents());
if (parentId != null) {
addHeader(headers, Span.PARENT_ID_NAME, Span.idToHex(parentId));
@@ -94,11 +82,10 @@ public class SpanMessageHeaders {
nativeAccessor.setNativeHeader(name, headers.get(name));
}
}
return MessageBuilder.createMessage(message.getPayload(),
accessor.getMessageHeaders());
carrier.setHeaders(accessor);
}
public static void addAnnotations(TraceKeys traceKeys, Message<?> message,
private void addAnnotations(TraceKeys traceKeys, Message<?> message,
Span span) {
for (String name : traceKeys.getMessage().getHeaders()) {
if (message.getHeaders().containsKey(name)) {
@@ -113,7 +100,7 @@ public class SpanMessageHeaders {
addPayloadAnnotations(traceKeys, message.getPayload(), span);
}
static void addPayloadAnnotations(TraceKeys traceKeys, Object payload, Span span) {
private void addPayloadAnnotations(TraceKeys traceKeys, Object payload, Span span) {
if (payload != null) {
tagIfEntryMissing(span, traceKeys.getMessage().getPayload().getType(),
payload.getClass().getCanonicalName());
@@ -128,21 +115,20 @@ public class SpanMessageHeaders {
}
}
private static void tagIfEntryMissing(Span span, String key, String value) {
private void tagIfEntryMissing(Span span, String key, String value) {
if (!span.tags().containsKey(key)) {
span.tag(key, value);
}
}
private static void addHeader(Map<String, String> headers, String name,
private void addHeader(Map<String, String> headers, String name,
String value) {
if (StringUtils.hasText(value)) {
headers.put(name, value);
}
}
private static Long getFirst(List<Long> parents) {
private Long getFirst(List<Long> parents) {
return parents.isEmpty() ? null : parents.get(0);
}
}

View File

@@ -16,15 +16,16 @@
package org.springframework.cloud.sleuth.instrument.messaging;
import java.util.Random;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.SpanExtractor;
import org.springframework.cloud.sleuth.SpanInjector;
import org.springframework.cloud.sleuth.TraceKeys;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.sampler.NeverSampler;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.support.MessageBuilder;
/**
* A channel interceptor that automatically starts / continues / closes and detaches spans.
@@ -34,13 +35,17 @@ import org.springframework.messaging.MessageHandler;
*/
public class TraceChannelInterceptor extends AbstractTraceChannelInterceptor {
public TraceChannelInterceptor(Tracer tracer, TraceKeys traceKeys, Random random) {
super(tracer, traceKeys, random);
private static final String SPAN_HEADER = "X-Current-Span";
public TraceChannelInterceptor(Tracer tracer, TraceKeys traceKeys,
SpanExtractor<Message> spanExtractor,
SpanInjector<MessageBuilder> spanInjector) {
super(tracer, traceKeys, spanExtractor, spanInjector);
}
@Override
public void postSend(Message<?> message, MessageChannel channel, boolean sent) {
getTracer().close(SpanMessageHeaders.getSpanFromHeader(message));
getTracer().close(getSpanFromHeader(message));
}
@Override
@@ -49,7 +54,9 @@ public class TraceChannelInterceptor extends AbstractTraceChannelInterceptor {
: buildSpan(message);
String name = getMessageChannelName(channel);
Span span = startSpan(parentSpan, name, message);
return SpanMessageHeaders.addSpanHeaders(getTraceKeys(), message, span);
MessageBuilder<?> messageBuilder = MessageBuilder.fromMessage(message);
getSpanInjector().inject(span, messageBuilder);
return messageBuilder.build();
}
private Span startSpan(Span span, String name, Message<?> message) {
@@ -65,14 +72,25 @@ public class TraceChannelInterceptor extends AbstractTraceChannelInterceptor {
@Override
public Message<?> beforeHandle(Message<?> message, MessageChannel channel,
MessageHandler handler) {
getTracer().continueSpan(SpanMessageHeaders.getSpanFromHeader(message));
getTracer().continueSpan(getSpanFromHeader(message));
return message;
}
@Override
public void afterMessageHandled(Message<?> message, MessageChannel channel,
MessageHandler handler, Exception ex) {
getTracer().detach(SpanMessageHeaders.getSpanFromHeader(message));
getTracer().detach(getSpanFromHeader(message));
}
private Span getSpanFromHeader(Message<?> message) {
if (message == null) {
return null;
}
Object object = message.getHeaders().get(SPAN_HEADER);
if (object instanceof Span) {
return (Span) object;
}
return null;
}
}

View File

@@ -18,17 +18,22 @@ package org.springframework.cloud.sleuth.instrument.messaging;
import java.util.Random;
import org.springframework.beans.factory.annotation.Qualifier;
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.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.sleuth.SpanInjector;
import org.springframework.cloud.sleuth.SpanExtractor;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration;
import org.springframework.cloud.sleuth.TraceKeys;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.config.GlobalChannelInterceptor;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
/**
* {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration Auto-configuration}
@@ -50,8 +55,26 @@ public class TraceSpringIntegrationAutoConfiguration {
@Bean
@GlobalChannelInterceptor
public TraceChannelInterceptor traceChannelInterceptor(Tracer tracer,
TraceKeys traceKeys, Random random) {
return new TraceChannelInterceptor(tracer, traceKeys, random);
TraceKeys traceKeys, Random random,
@Qualifier("messagingSpanExtractor") SpanExtractor<Message> spanExtractor,
@Qualifier("messagingSpanInjector") SpanInjector<MessageBuilder> spanInjector) {
return new TraceChannelInterceptor(tracer, traceKeys, spanExtractor, spanInjector);
}
// TODO: Qualifier + ConditionalOnProp cause autowiring generics doesn't work
@Bean
@Qualifier("messagingSpanExtractor")
@ConditionalOnProperty(value = "spring.sleuth.integration.injector.enabled", matchIfMissing = true)
public SpanExtractor<Message> messagingSpanExtractor(Random random) {
return new MessagingSpanExtractor(random);
}
// TODO: Qualifier + ConditionalOnProp cause autowiring generics doesn't work
@Bean
@Qualifier("messagingSpanInjector")
@ConditionalOnProperty(value = "spring.sleuth.integration.injector.enabled", matchIfMissing = true)
public SpanInjector<MessageBuilder> messagingSpanInjector(TraceKeys traceKeys) {
return new MessagingSpanInjector(traceKeys);
}
}

View File

@@ -3,12 +3,24 @@ package org.springframework.cloud.sleuth.instrument.messaging.websocket;
import java.util.Random;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.sleuth.SpanExtractor;
import org.springframework.cloud.sleuth.SpanInjector;
import org.springframework.cloud.sleuth.TraceKeys;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.instrument.messaging.MessagingSpanExtractor;
import org.springframework.cloud.sleuth.instrument.messaging.MessagingSpanInjector;
import org.springframework.cloud.sleuth.instrument.messaging.TraceChannelInterceptor;
import org.springframework.cloud.sleuth.instrument.messaging.TraceSpringIntegrationAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.Message;
import org.springframework.messaging.simp.config.ChannelRegistration;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation.DelegatingWebSocketMessageBrokerConfiguration;
@@ -24,19 +36,17 @@ import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
* @see AbstractWebSocketMessageBrokerConfigurer
*/
@Component
@Configuration
@AutoConfigureAfter(TraceSpringIntegrationAutoConfiguration.class)
@ConditionalOnClass(DelegatingWebSocketMessageBrokerConfiguration.class)
@ConditionalOnBean(AbstractWebSocketMessageBrokerConfigurer.class)
public class TraceWebSocketAutoConfiguration
extends AbstractWebSocketMessageBrokerConfigurer {
@Autowired
private Tracer tracer;
@Autowired
private TraceKeys traceKeys;
@Autowired
private Random random;
@Autowired Tracer tracer;
@Autowired TraceKeys traceKeys;
@Autowired @Qualifier("stompMessagingSpanExtractor") SpanExtractor<Message> spanExtractor;
@Autowired @Qualifier("stompMessagingSpanInjector") SpanInjector<MessageBuilder> spanInjector;
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
@@ -46,12 +56,28 @@ public class TraceWebSocketAutoConfiguration
@Override
public void configureClientOutboundChannel(ChannelRegistration registration) {
registration.setInterceptors(
new TraceChannelInterceptor(this.tracer, this.traceKeys, this.random));
new TraceChannelInterceptor(this.tracer, this.traceKeys, this.spanExtractor, this.spanInjector));
}
@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.setInterceptors(
new TraceChannelInterceptor(this.tracer, this.traceKeys, this.random));
new TraceChannelInterceptor(this.tracer, this.traceKeys, this.spanExtractor, this.spanInjector));
}
// TODO: Qualifier + ConditionalOnProp cause autowiring generics doesn't work
@Bean
@Qualifier("stompMessagingSpanExtractor")
@ConditionalOnProperty(value = "spring.sleuth.integration.websocket.injector.enabled", matchIfMissing = true)
public SpanExtractor<Message> stompMessagingSpanExtractor(Random random) {
return new MessagingSpanExtractor(random);
}
// TODO: Qualifier + ConditionalOnProp cause autowiring generics doesn't work
@Bean
@Qualifier("stompMessagingSpanInjector")
@ConditionalOnProperty(value = "spring.sleuth.integration.websocket.injector.enabled", matchIfMissing = true)
public SpanInjector<MessageBuilder> stompMessagingSpanInjector(TraceKeys traceKeys) {
return new MessagingSpanInjector(traceKeys);
}
}

View File

@@ -0,0 +1,84 @@
/*
* Copyright 2013-2016 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.util.Random;
import java.util.regex.Pattern;
import javax.servlet.http.HttpServletRequest;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.Span.SpanBuilder;
import org.springframework.cloud.sleuth.SpanExtractor;
import org.springframework.util.StringUtils;
import org.springframework.web.util.UrlPathHelper;
/**
* Creates a {@link SpanBuilder} from {@link HttpServletRequest}
*
* @author Marcin Grzejszczak
*
* @since 1.0.0
*/
class HttpServletRequestExtractor implements SpanExtractor<HttpServletRequest> {
private static final String HTTP_COMPONENT = "http";
private final Random random;
private final Pattern skipPattern;
private UrlPathHelper urlPathHelper = new UrlPathHelper();
public HttpServletRequestExtractor(Random random, Pattern skipPattern) {
this.random = random;
this.skipPattern = skipPattern;
}
@Override
public Span joinTrace(HttpServletRequest carrier) {
String uri = this.urlPathHelper.getPathWithinApplication(carrier);
boolean skip = this.skipPattern.matcher(uri).matches()
|| carrier.getHeader(Span.NOT_SAMPLED_NAME) != null;
long traceId = Span
.hexToId(carrier.getHeader(Span.TRACE_ID_NAME));
long spanId = carrier.getHeader(Span.SPAN_ID_NAME) != null
? Span.hexToId(carrier.getHeader(Span.SPAN_ID_NAME))
: this.random.nextLong();
SpanBuilder span = Span.builder().traceId(traceId).spanId(spanId);
String processId = carrier.getHeader(Span.PROCESS_ID_NAME);
String parentName = carrier.getHeader(Span.SPAN_NAME_NAME);
if (StringUtils.hasText(parentName)) {
span.name(parentName);
}
else {
span.name(HTTP_COMPONENT + ":" + "/parent" + uri);
}
if (StringUtils.hasText(processId)) {
span.processId(processId);
}
if (carrier.getHeader(Span.PARENT_ID_NAME) != null) {
span.parent(Span
.hexToId(carrier.getHeader(Span.PARENT_ID_NAME)));
}
span.remote(true);
if (skip) {
span.exportable(false);
}
return span.build();
}
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2013-2016 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.HttpServletResponse;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.SpanInjector;
/**
* Span injector that injects tracing info to {@link HttpServletResponse}
*
* @author Marcin Grzejszczak
*
* @since 1.0.0
*/
class HttpServletResponseInjector implements SpanInjector<HttpServletResponse> {
@Override
public void inject(Span span, HttpServletResponse carrier) {
if (span == null) {
return;
}
if (!carrier.containsHeader(Span.SPAN_ID_NAME)) {
carrier.addHeader(Span.SPAN_ID_NAME, Span.idToHex(span.getSpanId()));
carrier.addHeader(Span.TRACE_ID_NAME, Span.idToHex(span.getTraceId()));
}
}
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2013-2016 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;
/**
* Utility class to retrieve data from Servlet
* HTTP request and response
*
* @author Marcin Grzejszczak
*
* @since 1.0.0
*/
class ServletUtils {
static boolean hasHeader(HttpServletRequest request, HttpServletResponse response,
String name) {
String value = request.getHeader(name);
return value != null || response.getHeader(name) != null;
}
static String getHeader(HttpServletRequest request, HttpServletResponse response,
String name) {
String value = request.getHeader(name);
return value != null ? value : response.getHeader(name);
}
}

View File

@@ -19,7 +19,6 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Random;
import java.util.regex.Pattern;
import javax.servlet.FilterChain;
@@ -28,7 +27,8 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.Span.SpanBuilder;
import org.springframework.cloud.sleuth.SpanExtractor;
import org.springframework.cloud.sleuth.SpanInjector;
import org.springframework.cloud.sleuth.SpanReporter;
import org.springframework.cloud.sleuth.TraceKeys;
import org.springframework.cloud.sleuth.Tracer;
@@ -39,6 +39,7 @@ import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.UrlPathHelper;
import static org.springframework.cloud.sleuth.instrument.web.ServletUtils.hasHeader;
import static org.springframework.util.StringUtils.hasText;
/**
@@ -66,6 +67,8 @@ import static org.springframework.util.StringUtils.hasText;
@Order(Ordered.HIGHEST_PRECEDENCE + 5)
public class TraceFilter extends OncePerRequestFilter {
private static final String HTTP_COMPONENT = "http";
protected static final String TRACE_REQUEST_ATTR = TraceFilter.class.getName()
+ ".TRACE";
@@ -75,34 +78,36 @@ public class TraceFilter extends OncePerRequestFilter {
private final Tracer tracer;
private final TraceKeys traceKeys;
private final Pattern skipPattern;
private final Random random;
private final SpanReporter spanReporter;
private final SpanExtractor<HttpServletRequest> spanExtractor;
private final SpanInjector<HttpServletResponse> spanInjector;
private UrlPathHelper urlPathHelper = new UrlPathHelper();
public TraceFilter(Tracer tracer, TraceKeys traceKeys, SpanReporter spanReporter) {
this(tracer, traceKeys, Pattern.compile(DEFAULT_SKIP_PATTERN), new Random(),
spanReporter);
public TraceFilter(Tracer tracer, TraceKeys traceKeys, SpanReporter spanReporter,
SpanExtractor<HttpServletRequest> spanExtractor, SpanInjector<HttpServletResponse> spanInjector) {
this(tracer, traceKeys, Pattern.compile(DEFAULT_SKIP_PATTERN), spanReporter,
spanExtractor, spanInjector);
}
public TraceFilter(Tracer tracer, TraceKeys traceKeys, Pattern skipPattern,
Random random, SpanReporter spanReporter) {
SpanReporter spanReporter, SpanExtractor<HttpServletRequest> spanExtractor,
SpanInjector<HttpServletResponse> spanInjector) {
this.tracer = tracer;
this.traceKeys = traceKeys;
this.skipPattern = skipPattern;
this.random = random;
this.spanReporter = spanReporter;
this.spanExtractor = spanExtractor;
this.spanInjector = spanInjector;
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String uri = this.urlPathHelper.getPathWithinApplication(request);
boolean skip = this.skipPattern.matcher(uri).matches()
|| getHeader(request, response, Span.NOT_SAMPLED_NAME) != null;
|| ServletUtils.getHeader(request, response, Span.NOT_SAMPLED_NAME) != null;
Span spanFromRequest = (Span) request.getAttribute(TRACE_REQUEST_ATTR);
if (spanFromRequest != null) {
this.tracer.continueSpan(spanFromRequest);
@@ -110,66 +115,15 @@ public class TraceFilter extends OncePerRequestFilter {
else if (skip) {
addToResponseIfNotPresent(response, Span.NOT_SAMPLED_NAME, "");
}
String protocol = "http";
String name = protocol + ":" + uri;
if (spanFromRequest == null) {
if (hasHeader(request, response, Span.TRACE_ID_NAME)) {
long traceId = Span
.hexToId(getHeader(request, response, Span.TRACE_ID_NAME));
long spanId = hasHeader(request, response, Span.SPAN_ID_NAME)
? Span.hexToId(getHeader(request, response, Span.SPAN_ID_NAME))
: this.random.nextLong();
SpanBuilder span = Span.builder().traceId(traceId).spanId(spanId);
if (skip) {
span.exportable(false);
}
String processId = getHeader(request, response, Span.PROCESS_ID_NAME);
String parentName = getHeader(request, response, Span.SPAN_NAME_NAME);
if (StringUtils.hasText(parentName)) {
span.name(parentName);
}
else {
span.name(protocol + ":" + "/parent" + uri);
}
if (StringUtils.hasText(processId)) {
span.processId(processId);
}
if (hasHeader(request, response, Span.PARENT_ID_NAME)) {
span.parent(Span
.hexToId(getHeader(request, response, Span.PARENT_ID_NAME)));
}
span.remote(true);
Span parent = span.build();
spanFromRequest = this.tracer.createSpan(name, parent);
if (parent != null && parent.isRemote()) {
parent.logEvent(Span.SERVER_RECV);
}
request.setAttribute(TRACE_REQUEST_ATTR, spanFromRequest);
}
else {
if (skip) {
spanFromRequest = this.tracer.createSpan(name, NeverSampler.INSTANCE);
}
else {
spanFromRequest = this.tracer.createSpan(name);
}
request.setAttribute(TRACE_REQUEST_ATTR, spanFromRequest);
}
}
String name = HTTP_COMPONENT + ":" + uri;
spanFromRequest = createSpan(request, response, skip, spanFromRequest, name);
Throwable exception = null;
try {
addRequestTags(request);
// Add headers before filter chain in case one of the filters flushes the
// response...
addResponseHeaders(response, spanFromRequest);
this.spanInjector.inject(spanFromRequest, response);
filterChain.doFilter(request, response);
}
catch (Throwable e) {
exception = e;
@@ -199,13 +153,33 @@ public class TraceFilter extends OncePerRequestFilter {
}
}
private void addResponseHeaders(HttpServletResponse response, Span span) {
if (span != null) {
if (!response.containsHeader(Span.SPAN_ID_NAME)) {
response.addHeader(Span.SPAN_ID_NAME, Span.idToHex(span.getSpanId()));
response.addHeader(Span.TRACE_ID_NAME, Span.idToHex(span.getTraceId()));
}
/**
* Creates a span and appends it as the current request's attribute
*/
private Span createSpan(HttpServletRequest request, HttpServletResponse response,
boolean skip, Span spanFromRequest, String name) {
if (spanFromRequest != null) {
return spanFromRequest;
}
if (hasHeader(request, response, Span.TRACE_ID_NAME)) {
Span parent = this.spanExtractor
.joinTrace(request);
spanFromRequest = this.tracer.createSpan(name, parent);
if (parent != null && parent.isRemote()) {
parent.logEvent(Span.SERVER_RECV);
}
request.setAttribute(TRACE_REQUEST_ATTR, spanFromRequest);
}
else {
if (skip) {
spanFromRequest = this.tracer.createSpan(name, NeverSampler.INSTANCE);
}
else {
spanFromRequest = this.tracer.createSpan(name);
}
request.setAttribute(TRACE_REQUEST_ATTR, spanFromRequest);
}
return spanFromRequest;
}
/** Override to add annotations not defined in {@link TraceKeys}. */
@@ -242,18 +216,6 @@ public class TraceFilter extends OncePerRequestFilter {
}
}
private boolean hasHeader(HttpServletRequest request, HttpServletResponse response,
String name) {
String value = request.getHeader(name);
return value != null || response.getHeader(name) != null;
}
private String getHeader(HttpServletRequest request, HttpServletResponse response,
String name) {
String value = request.getHeader(name);
return value != null ? value : response.getHeader(name);
}
private void addToResponseIfNotPresent(HttpServletResponse response, String name,
String value) {
if (!hasText(response.getHeader(name))) {

View File

@@ -24,7 +24,6 @@ import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.cloud.sleuth.SpanAccessor;
import org.springframework.cloud.sleuth.SpanNamer;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.instrument.async.TraceContinuingCallable;
@@ -72,12 +71,10 @@ public class TraceWebAspect {
.getLog(TraceWebAspect.class);
private final Tracer tracer;
private final SpanAccessor accessor;
private final SpanNamer spanNamer;
public TraceWebAspect(Tracer tracer, SpanAccessor accessor, SpanNamer spanNamer) {
public TraceWebAspect(Tracer tracer, SpanNamer spanNamer) {
this.tracer = tracer;
this.accessor = accessor;
this.spanNamer = spanNamer;
}
@@ -109,9 +106,9 @@ public class TraceWebAspect {
@SuppressWarnings("unchecked")
public Object wrapWithCorrelationId(ProceedingJoinPoint pjp) throws Throwable {
Callable<Object> callable = (Callable<Object>) pjp.proceed();
if (this.accessor.isTracing()) {
if (this.tracer.isTracing()) {
log.debug("Wrapping callable with span ["
+ this.accessor.getCurrentSpan() + "]");
+ this.tracer.getCurrentSpan() + "]");
return new TraceContinuingCallable<>(this.tracer, this.spanNamer, callable);
}
else {
@@ -122,10 +119,10 @@ public class TraceWebAspect {
@Around("anyControllerOrRestControllerWithPublicWebAsyncTaskMethod()")
public Object wrapWebAsyncTaskWithCorrelationId(ProceedingJoinPoint pjp) throws Throwable {
final WebAsyncTask<?> webAsyncTask = (WebAsyncTask<?>) pjp.proceed();
if (this.accessor.isTracing()) {
if (this.tracer.isTracing()) {
try {
log.debug("Wrapping callable with span ["
+ this.accessor.getCurrentSpan() + "]");
+ this.tracer.getCurrentSpan() + "]");
Field callableField = WebAsyncTask.class.getDeclaredField("callable");
callableField.setAccessible(true);
callableField.set(webAsyncTask, new TraceContinuingCallable<>(this.tracer,

View File

@@ -18,7 +18,10 @@ package org.springframework.cloud.sleuth.instrument.web;
import java.util.Random;
import java.util.regex.Pattern;
import org.springframework.beans.factory.annotation.Autowired;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.actuate.autoconfigure.ManagementServerProperties;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
@@ -29,7 +32,8 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClas
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.sleuth.SpanAccessor;
import org.springframework.cloud.sleuth.SpanInjector;
import org.springframework.cloud.sleuth.SpanExtractor;
import org.springframework.cloud.sleuth.SpanNamer;
import org.springframework.cloud.sleuth.SpanReporter;
import org.springframework.cloud.sleuth.TraceKeys;
@@ -64,26 +68,36 @@ public class TraceWebAutoConfiguration {
@Value("${spring.sleuth.web.skipPattern:}")
private String skipPattern;
@Autowired
private Tracer tracer;
@Autowired
private SpanAccessor accessor;
@Autowired
private TraceKeys traceKeys;
@Bean
public TraceWebAspect traceWebAspect(SpanNamer spanNamer) {
return new TraceWebAspect(this.tracer, this.accessor, spanNamer);
public TraceWebAspect traceWebAspect(Tracer tracer, SpanNamer spanNamer) {
return new TraceWebAspect(tracer, spanNamer);
}
@Bean
@ConditionalOnMissingBean
public TraceFilter traceFilter(Random random,
SkipPatternProvider skipPatternProvider, SpanReporter spanReporter) {
return new TraceFilter(this.tracer, this.traceKeys, skipPatternProvider.skipPattern(), random,
spanReporter);
public TraceFilter traceFilter(Tracer tracer, TraceKeys traceKeys,
SkipPatternProvider skipPatternProvider, SpanReporter spanReporter,
@Qualifier("httpServletRequestSpanExtractor") SpanExtractor<HttpServletRequest> spanExtractor,
@Qualifier("httpServletResponseInjector") SpanInjector<HttpServletResponse> spanInjector) {
return new TraceFilter(tracer, traceKeys, skipPatternProvider.skipPattern(),
spanReporter, spanExtractor, spanInjector);
}
// TODO: Qualifier + ConditionalOnProp cause autowiring generics doesn't work
@Bean
@Qualifier("httpServletRequestSpanExtractor")
@ConditionalOnProperty(value = "spring.sleuth.web.extractor.enabled", matchIfMissing = true)
public SpanExtractor<HttpServletRequest> httpServletRequestSpanExtractor(Random random,
SkipPatternProvider skipPatternProvider) {
return new HttpServletRequestExtractor(random, skipPatternProvider.skipPattern());
}
// TODO: Qualifier + ConditionalOnProp cause autowiring generics doesn't work
@Bean
@Qualifier("httpServletResponseInjector")
@ConditionalOnProperty(value = "spring.sleuth.web.injector.enabled", matchIfMissing = true)
public SpanInjector<HttpServletResponse> httpServletResponseInjector() {
return new HttpServletResponseInjector();
}
@Configuration

View File

@@ -19,9 +19,9 @@ package org.springframework.cloud.sleuth.instrument.web.client;
import java.net.URI;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.SpanInjector;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.http.HttpRequest;
import org.springframework.util.StringUtils;
/**
* Abstraction over classes that interact with Http requests. Allows you
@@ -34,38 +34,12 @@ import org.springframework.util.StringUtils;
abstract class AbstractTraceHttpRequestInterceptor {
protected final Tracer tracer;
protected final SpanInjector<HttpRequest> spanInjector;
protected AbstractTraceHttpRequestInterceptor(Tracer tracer) {
protected AbstractTraceHttpRequestInterceptor(Tracer tracer,
SpanInjector<HttpRequest> spanInjector) {
this.tracer = tracer;
}
private void enrichWithTraceHeaders(HttpRequest request, Span span) {
setIdHeader(request, Span.TRACE_ID_NAME, span.getTraceId());
setIdHeader(request, Span.SPAN_ID_NAME, span.getSpanId());
if (!span.isExportable()) {
setHeader(request, Span.NOT_SAMPLED_NAME, "true");
}
setHeader(request, Span.SPAN_NAME_NAME, span.getName());
setIdHeader(request, Span.PARENT_ID_NAME, getParentId(span));
setHeader(request, Span.PROCESS_ID_NAME, span.getProcessId());
}
private Long getParentId(Span span) {
return !span.getParents().isEmpty() ? span.getParents().get(0) : null;
}
private void setHeader(HttpRequest request, String name, String value) {
if (StringUtils.hasText(value) && !request.getHeaders().containsKey(name) &&
this.tracer.isTracing()) {
request.getHeaders().add(name, value);
}
}
private void setIdHeader(HttpRequest request, String name, Long value) {
if (value != null) {
setHeader(request, name, Span.idToHex(value));
}
this.spanInjector = spanInjector;
}
/**
@@ -76,7 +50,7 @@ abstract class AbstractTraceHttpRequestInterceptor {
URI uri = request.getURI();
String spanName = uriScheme(uri) + ":" + uri.getPath();
Span newSpan = this.tracer.createSpan(spanName);
enrichWithTraceHeaders(request, newSpan);
this.spanInjector.inject(newSpan, request);
newSpan.logEvent(Span.CLIENT_SEND);
}

View File

@@ -0,0 +1,60 @@
/*
* Copyright 2013-2016 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.client;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.SpanInjector;
import org.springframework.http.HttpRequest;
import org.springframework.util.StringUtils;
/**
* Span injector that injects tracing info to {@link HttpRequest}
*
* @author Marcin Grzejszczak
*
* @since 1.0.0
*/
class HttpRequestInjector implements SpanInjector<HttpRequest> {
@Override
public void inject(Span span, HttpRequest carrier) {
setIdHeader(carrier, Span.TRACE_ID_NAME, span.getTraceId());
setIdHeader(carrier, Span.SPAN_ID_NAME, span.getSpanId());
if (!span.isExportable()) {
setHeader(carrier, Span.NOT_SAMPLED_NAME, "true");
}
setHeader(carrier, Span.SPAN_NAME_NAME, span.getName());
setIdHeader(carrier, Span.PARENT_ID_NAME, getParentId(span));
setHeader(carrier, Span.PROCESS_ID_NAME, span.getProcessId());
}
private Long getParentId(Span span) {
return !span.getParents().isEmpty() ? span.getParents().get(0) : null;
}
private void setHeader(HttpRequest request, String name, String value) {
if (StringUtils.hasText(value) && !request.getHeaders().containsKey(name)) {
request.getHeaders().add(name, value);
}
}
private void setIdHeader(HttpRequest request, String name, Long value) {
if (value != null) {
setHeader(request, name, Span.idToHex(value));
}
}
}

View File

@@ -19,9 +19,11 @@ package org.springframework.cloud.sleuth.instrument.web.client;
import java.io.IOException;
import java.net.URI;
import org.springframework.cloud.sleuth.SpanInjector;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.core.task.AsyncListenableTaskExecutor;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.AsyncClientHttpRequest;
import org.springframework.http.client.AsyncClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpRequest;
@@ -53,9 +55,9 @@ public class TraceAsyncClientHttpRequestFactoryWrapper extends AbstractTraceHttp
*
* @see org.springframework.web.client.AsyncRestTemplate#AsyncRestTemplate(AsyncClientHttpRequestFactory)
*/
public TraceAsyncClientHttpRequestFactoryWrapper(Tracer tracer,
public TraceAsyncClientHttpRequestFactoryWrapper(Tracer tracer, SpanInjector<HttpRequest> spanInjector,
AsyncClientHttpRequestFactory asyncDelegate) {
super(tracer);
super(tracer, spanInjector);
this.asyncDelegate = asyncDelegate;
this.syncDelegate = asyncDelegate instanceof ClientHttpRequestFactory ?
(ClientHttpRequestFactory) asyncDelegate : defaultClientHttpRequestFactory();
@@ -65,16 +67,16 @@ public class TraceAsyncClientHttpRequestFactoryWrapper extends AbstractTraceHttp
* Default implementation that creates a {@link SimpleClientHttpRequestFactory} that
* has a wrapped task executor via the {@link TraceAsyncListenableTaskExecutor}
*/
public TraceAsyncClientHttpRequestFactoryWrapper(Tracer tracer) {
super(tracer);
public TraceAsyncClientHttpRequestFactoryWrapper(Tracer tracer, SpanInjector<HttpRequest> spanInjector) {
super(tracer, spanInjector);
SimpleClientHttpRequestFactory simpleClientHttpRequestFactory = defaultClientHttpRequestFactory();
this.asyncDelegate = simpleClientHttpRequestFactory;
this.syncDelegate = simpleClientHttpRequestFactory;
}
public TraceAsyncClientHttpRequestFactoryWrapper(Tracer tracer,
public TraceAsyncClientHttpRequestFactoryWrapper(Tracer tracer, SpanInjector<HttpRequest> spanInjector,
AsyncClientHttpRequestFactory asyncDelegate, ClientHttpRequestFactory syncDelegate) {
super(tracer);
super(tracer, spanInjector);
this.asyncDelegate = asyncDelegate;
this.syncDelegate = syncDelegate;
}

View File

@@ -18,6 +18,7 @@ package org.springframework.cloud.sleuth.instrument.web.client;
import java.io.IOException;
import org.springframework.cloud.sleuth.SpanInjector;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
@@ -38,8 +39,8 @@ import org.springframework.http.client.ClientHttpResponse;
public class TraceRestTemplateInterceptor extends AbstractTraceHttpRequestInterceptor
implements ClientHttpRequestInterceptor {
public TraceRestTemplateInterceptor(Tracer tracer) {
super(tracer);
public TraceRestTemplateInterceptor(Tracer tracer, SpanInjector<HttpRequest> spanInjector) {
super(tracer, spanInjector);
}
@Override

View File

@@ -22,10 +22,12 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.sleuth.SpanAccessor;
import org.springframework.cloud.sleuth.SpanInjector;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.AsyncClientHttpRequestFactory;
import org.springframework.web.client.AsyncRestTemplate;
@@ -47,8 +49,9 @@ public class TraceWebAsyncClientAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public AsyncClientHttpRequestFactory asyncClientHttpRequestFactory(Tracer tracer) {
return new TraceAsyncClientHttpRequestFactoryWrapper(tracer);
public AsyncClientHttpRequestFactory asyncClientHttpRequestFactory(Tracer tracer,
SpanInjector<HttpRequest> spanInjector) {
return new TraceAsyncClientHttpRequestFactoryWrapper(tracer, spanInjector);
}
@Bean

View File

@@ -28,10 +28,12 @@ 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.cloud.sleuth.SpanInjector;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.web.client.RestTemplate;
@@ -52,8 +54,9 @@ public class TraceWebClientAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public TraceRestTemplateInterceptor traceRestTemplateInterceptor(Tracer tracer) {
return new TraceRestTemplateInterceptor(tracer);
public TraceRestTemplateInterceptor traceRestTemplateInterceptor(Tracer tracer,
SpanInjector<HttpRequest> spanInjector) {
return new TraceRestTemplateInterceptor(tracer, spanInjector);
}
@Bean
@@ -62,6 +65,11 @@ public class TraceWebClientAutoConfiguration {
return new RestTemplate();
}
@Bean
public SpanInjector httpRequestInjector() {
return new HttpRequestInjector();
}
@Configuration
protected static class TraceInterceptorConfiguration {

View File

@@ -20,7 +20,7 @@ import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.Tracer;
/**
* Abstract class for publishing logging the client received event
* Abstract class for logging the client received event
*
* @author Marcin Grzejszczak
*

View File

@@ -0,0 +1,62 @@
/*
* Copyright 2013-2016 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.client.feign;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.SpanInjector;
import org.springframework.util.StringUtils;
import feign.RequestTemplate;
/**
* Span injector that injects tracing info to {@link RequestTemplate}
*
* @author Marcin Grzejszczak
*
* @since 1.0.0
*/
class FeignRequestTemplateInjector implements SpanInjector<RequestTemplate> {
@Override
public void inject(Span span, RequestTemplate carrier) {
if (span == null) {
setHeader(carrier, Span.NOT_SAMPLED_NAME, "true");
return;
}
carrier.header(Span.TRACE_ID_NAME, Span.idToHex(span.getTraceId()));
setHeader(carrier, Span.SPAN_NAME_NAME, span.getName());
setHeader(carrier, Span.SPAN_ID_NAME, Span.idToHex(span.getSpanId()));
if (!span.isExportable()) {
setHeader(carrier, Span.NOT_SAMPLED_NAME, "true");
}
Long parentId = getParentId(span);
if (parentId != null) {
setHeader(carrier, Span.PARENT_ID_NAME, Span.idToHex(parentId));
}
setHeader(carrier, Span.PROCESS_ID_NAME, span.getProcessId());
}
private Long getParentId(Span span) {
return !span.getParents().isEmpty() ? span.getParents().get(0) : null;
}
protected void setHeader(RequestTemplate request, String name, String value) {
if (StringUtils.hasText(value) && !request.headers().containsKey(name)) {
request.header(name, value);
}
}
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright 2013-2016 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.client.feign;
import java.util.Collection;
import java.util.Map;
/**
* Mutable holder for Feign Response headers
*
* @author Marcin Grzejszczak
*
* @since 1.0.0
*/
class FeignResponseHeadersHolder {
final Map<String, Collection<String>> responseHeaders;
FeignResponseHeadersHolder(Map<String, Collection<String>> responseHeaders) {
this.responseHeaders = responseHeaders;
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright 2013-2016 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.client.feign;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.SpanInjector;
import org.springframework.util.StringUtils;
import static java.util.Collections.singletonList;
/**
* Span injector that injects tracing info to
* {@link FeignResponseHeadersHolder#responseHeaders}
*
* @author Marcin Grzejszczak
*
* @since 1.0.0
*/
class FeignResponseHeadersInjector implements SpanInjector<FeignResponseHeadersHolder> {
@Override
public void inject(Span span, FeignResponseHeadersHolder carrier) {
Map<String, Collection<String>> headers = carrier.responseHeaders;
headersWithTraceId(span, headers);
}
private Map<String, Collection<String>> headersWithTraceId(Span span,
Map<String, Collection<String>> headers) {
Map<String, Collection<String>> newHeaders = new HashMap<>();
newHeaders.putAll(headers);
if (span == null) {
setHeader(newHeaders, Span.NOT_SAMPLED_NAME, "true");
return newHeaders;
}
setHeader(newHeaders, Span.TRACE_ID_NAME, span.getTraceId());
setHeader(newHeaders, Span.SPAN_ID_NAME, span.getSpanId());
return newHeaders;
}
void setHeader(Map<String, Collection<String>> headers, String name,
String value) {
if (StringUtils.hasText(value) && !headers.containsKey(name)) {
headers.put(name, singletonList(value));
}
}
void setHeader(Map<String, Collection<String>> headers, String name,
Long value) {
if (value != null) {
setHeader(headers, name, Span.idToHex(value));
}
}
}

View File

@@ -50,7 +50,8 @@ final class TraceFeignClient extends FeignEventPublisher implements Client {
@Override
public Response execute(Request request, Request.Options options) throws IOException {
Response response = this.delegate.execute(request, options);
if (response.body() == null || (response.body() != null && Objects.equals(response.body().length(), 0))) {
if (response.body() == null || (response.body() != null
&& Objects.equals(response.body().length(), 0))) {
finish();
}
return response;

View File

@@ -18,9 +18,6 @@ package org.springframework.cloud.sleuth.instrument.web.client.feign;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -32,7 +29,7 @@ import org.springframework.boot.autoconfigure.web.HttpMessageConverters;
import org.springframework.cloud.netflix.feign.FeignAutoConfiguration;
import org.springframework.cloud.netflix.feign.support.ResponseEntityDecoder;
import org.springframework.cloud.netflix.feign.support.SpringDecoder;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.SpanInjector;
import org.springframework.cloud.sleuth.TraceKeys;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.instrument.hystrix.SleuthHystrixAutoConfiguration;
@@ -40,7 +37,6 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Scope;
import org.springframework.util.StringUtils;
import com.netflix.hystrix.HystrixCommand;
@@ -48,11 +44,10 @@ import feign.Client;
import feign.Feign;
import feign.FeignException;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import feign.Response;
import feign.codec.Decoder;
import static java.util.Collections.singletonList;
/**
* {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration Auto-configuration}
* enables span information propagation when using Feign.
@@ -71,34 +66,33 @@ public class TraceFeignClientAutoConfiguration {
@Autowired
private ObjectFactory<HttpMessageConverters> messageConverters;
@Autowired
private Tracer tracer;
private final FeignRequestContext feignRequestContext = FeignRequestContext.getInstance();
@Bean
@Scope("prototype")
@ConditionalOnClass(HystrixCommand.class)
@ConditionalOnProperty(name = "feign.hystrix.enabled", matchIfMissing = true)
public Feign.Builder feignHystrixBuilder(Tracer tracer, TraceKeys traceKeys) {
Feign.Builder feignHystrixBuilder(Tracer tracer, TraceKeys traceKeys) {
return SleuthFeignBuilder.builder(tracer);
}
@Bean
@ConditionalOnProperty(name = "spring.sleuth.feign.processor.enabled", matchIfMissing = true)
public FeignBeanPostProcessor feignBeanPostProcessor(Tracer tracer) {
FeignBeanPostProcessor feignBeanPostProcessor(Tracer tracer) {
return new FeignBeanPostProcessor(tracer);
}
@Bean
@Primary
public Decoder feignDecoder(final Tracer tracer) {
Decoder feignDecoder(final Tracer tracer) {
return new TraceFeignDecoder(tracer, new ResponseEntityDecoder(new SpringDecoder(this.messageConverters)) {
@Override
public Object decode(Response response, Type type)
throws IOException, FeignException {
FeignRequestContext feignRequestContext = FeignRequestContext.getInstance();
FeignResponseHeadersHolder feignResponseHeadersHolder =
new FeignResponseHeadersHolder(response.headers());
feignResponseHeadersInjector().inject(feignRequestContext.getCurrentSpan(), feignResponseHeadersHolder);
return super.decode(Response.create(response.status(),
response.reason(), headersWithTraceId(response.headers()),
response.reason(), feignResponseHeadersHolder.responseHeaders,
response.body()), type);
}
});
@@ -110,36 +104,14 @@ public class TraceFeignClientAutoConfiguration {
*/
@Bean
public RequestInterceptor traceIdRequestInterceptor(Tracer tracer) {
return new TraceFeignRequestInterceptor(tracer);
return new TraceFeignRequestInterceptor(tracer, feignRequestTemplateInjector());
}
private Map<String, Collection<String>> headersWithTraceId(
Map<String, Collection<String>> headers) {
Map<String, Collection<String>> newHeaders = new HashMap<>();
newHeaders.putAll(headers);
Span span = this.feignRequestContext.getCurrentSpan();
if (span == null) {
setHeader(newHeaders, Span.NOT_SAMPLED_NAME, "true");
return newHeaders;
}
setHeader(newHeaders, Span.TRACE_ID_NAME, span.getTraceId());
setHeader(newHeaders, Span.SPAN_ID_NAME, span.getSpanId());
return newHeaders;
private SpanInjector<RequestTemplate> feignRequestTemplateInjector() {
return new FeignRequestTemplateInjector();
}
public void setHeader(Map<String, Collection<String>> headers, String name,
String value) {
if (StringUtils.hasText(value) && !headers.containsKey(name)
&& this.tracer.isTracing()) {
headers.put(name, singletonList(value));
}
private SpanInjector<FeignResponseHeadersHolder> feignResponseHeadersInjector() {
return new FeignResponseHeadersInjector();
}
public void setHeader(Map<String, Collection<String>> headers, String name,
Long value) {
if (value != null) {
setHeader(headers, name, Span.idToHex(value));
}
}
}

View File

@@ -19,8 +19,8 @@ package org.springframework.cloud.sleuth.instrument.web.client.feign;
import java.net.URI;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.SpanInjector;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.util.StringUtils;
import feign.RequestInterceptor;
import feign.RequestTemplate;
@@ -36,31 +36,20 @@ import feign.RequestTemplate;
final class TraceFeignRequestInterceptor implements RequestInterceptor {
private final Tracer tracer;
private final SpanInjector<RequestTemplate> spanInjector;
private final FeignRequestContext feignRequestContext = FeignRequestContext.getInstance();
TraceFeignRequestInterceptor(Tracer tracer) {
TraceFeignRequestInterceptor(Tracer tracer,
SpanInjector<RequestTemplate> spanInjector) {
this.tracer = tracer;
this.spanInjector = spanInjector;
}
@Override
public void apply(RequestTemplate template) {
String spanName = getSpanName(template);
Span span = getSpan(spanName);
if (span == null) {
setHeader(template, Span.NOT_SAMPLED_NAME, "true");
return;
}
template.header(Span.TRACE_ID_NAME, Span.idToHex(span.getTraceId()));
setHeader(template, Span.SPAN_NAME_NAME, span.getName());
setHeader(template, Span.SPAN_ID_NAME, Span.idToHex(span.getSpanId()));
if (!span.isExportable()) {
setHeader(template, Span.NOT_SAMPLED_NAME, "true");
}
Long parentId = getParentId(span);
if (parentId != null) {
setHeader(template, Span.PARENT_ID_NAME, Span.idToHex(parentId));
}
setHeader(template, Span.PROCESS_ID_NAME, span.getProcessId());
this.spanInjector.inject(span, template);
span.logEvent(Span.CLIENT_SEND);
}
@@ -90,15 +79,4 @@ final class TraceFeignRequestInterceptor implements RequestInterceptor {
return uri.getScheme() == null ? "http" : uri.getScheme();
}
private Long getParentId(Span span) {
return !span.getParents().isEmpty() ? span.getParents().get(0) : null;
}
protected void setHeader(RequestTemplate request, String name, String value) {
if (StringUtils.hasText(value) && !request.headers().containsKey(name)
&& this.tracer.isTracing()) {
request.header(name, value);
}
}
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright 2013-2016 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.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.SpanInjector;
import com.netflix.client.http.HttpRequest;
import com.netflix.client.http.HttpRequest.Builder;
/**
* Span injector that injects tracing info to {@link Builder}
*
* @author Marcin Grzejszczak
*
* @since 1.0.0
*/
class RequestBuilderContextInjector implements SpanInjector<Builder> {
@Override
public void inject(Span span, Builder carrier) {
if (span == null) {
setHeader(carrier, Span.NOT_SAMPLED_NAME, "true");
return;
}
setHeader(carrier, Span.TRACE_ID_NAME, Span.idToHex(span.getTraceId()));
setHeader(carrier, Span.SPAN_ID_NAME, Span.idToHex(span.getSpanId()));
setHeader(carrier, Span.SPAN_NAME_NAME, span.getName());
if (getParentId(span) != null) {
setHeader(carrier, Span.PARENT_ID_NAME,
Span.idToHex(getParentId(span)));
}
setHeader(carrier, Span.PROCESS_ID_NAME,
span.getProcessId());
}
private Long getParentId(Span span) {
return !span.getParents().isEmpty()
? span.getParents().get(0) : null;
}
public void setHeader(HttpRequest.Builder builder, String name, String value) {
if (value != null) {
builder.header(name, value);
}
}
}

View File

@@ -0,0 +1,68 @@
/*
* Copyright 2013-2016 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 java.util.Map;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.SpanInjector;
import org.springframework.util.StringUtils;
import com.netflix.zuul.context.RequestContext;
/**
* Span injector that injects tracing info to {@link RequestContext}
*
* @author Marcin Grzejszczak
*
* @since 1.0.0
*/
class RequestContextInjector implements SpanInjector<RequestContext> {
@Override
public void inject(Span span, RequestContext carrier) {
Map<String, String> requestHeaders = carrier.getZuulRequestHeaders();
if (span == null) {
setHeader(requestHeaders, Span.NOT_SAMPLED_NAME, "true");
return;
}
setHeader(requestHeaders, Span.SPAN_ID_NAME, span.getSpanId());
setHeader(requestHeaders, Span.TRACE_ID_NAME, span.getTraceId());
setHeader(requestHeaders, Span.SPAN_NAME_NAME, span.getName());
if (!span.isExportable()) {
setHeader(requestHeaders, Span.NOT_SAMPLED_NAME, "true");
}
setHeader(requestHeaders, Span.PARENT_ID_NAME, getParentId(span));
setHeader(requestHeaders, Span.PROCESS_ID_NAME, span.getProcessId());
}
private Long getParentId(Span span) {
return !span.getParents().isEmpty() ? span.getParents().get(0) : null;
}
public void setHeader(Map<String, String> request, String name, String value) {
if (StringUtils.hasText(value) && !request.containsKey(name)) {
request.put(name, value);
}
}
public void setHeader(Map<String, String> request, String name, Long value) {
if (value != null) {
setHeader(request, name, Span.idToHex(value));
}
}
}

View File

@@ -16,12 +16,9 @@
package org.springframework.cloud.sleuth.instrument.zuul;
import java.util.Map;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.SpanAccessor;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.cloud.sleuth.SpanInjector;
import org.springframework.cloud.sleuth.Tracer;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
@@ -36,10 +33,12 @@ import com.netflix.zuul.context.RequestContext;
*/
public class TracePreZuulFilter extends ZuulFilter {
private final SpanAccessor accessor;
private final Tracer tracer;
private final SpanInjector<RequestContext> spanInjector;
public TracePreZuulFilter(SpanAccessor accessor) {
this.accessor = accessor;
public TracePreZuulFilter(Tracer tracer, SpanInjector<RequestContext> spanInjector) {
this.tracer = tracer;
this.spanInjector = spanInjector;
}
@Override
@@ -50,49 +49,17 @@ public class TracePreZuulFilter extends ZuulFilter {
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
Map<String, String> requestHeaders = ctx.getZuulRequestHeaders();
Span span = getCurrentSpan();
if (span == null) {
setHeader(requestHeaders, Span.NOT_SAMPLED_NAME, "true");
return null;
}
try {
setHeader(requestHeaders, Span.SPAN_ID_NAME, span.getSpanId());
setHeader(requestHeaders, Span.TRACE_ID_NAME, span.getTraceId());
setHeader(requestHeaders, Span.SPAN_NAME_NAME, span.getName());
if (!span.isExportable()) {
setHeader(requestHeaders, Span.NOT_SAMPLED_NAME, "true");
}
setHeader(requestHeaders, Span.PARENT_ID_NAME, getParentId(span));
setHeader(requestHeaders, Span.PROCESS_ID_NAME, span.getProcessId());
// TODO: the client sent event should come from the client not the filter!
span.logEvent(Span.CLIENT_SEND);
}
catch (Exception ex) {
ReflectionUtils.rethrowRuntimeException(ex);
}
this.spanInjector.inject(span, ctx);
// TODO: the client sent event should come from the client not the filter!
span.logEvent(Span.CLIENT_SEND);
return null;
}
private Span getCurrentSpan() {
return this.accessor.getCurrentSpan();
return this.tracer.getCurrentSpan();
}
private Long getParentId(Span span) {
return !span.getParents().isEmpty() ? span.getParents().get(0) : null;
}
public void setHeader(Map<String, String> request, String name, String value) {
if (StringUtils.hasText(value) && !request.containsKey(name) && this.accessor.isTracing()) {
request.put(name, value);
}
}
public void setHeader(Map<String, String> request, String name, Long value) {
if (value != null) {
setHeader(request, name, Span.idToHex(value));
}
}
@Override
public String filterType() {

View File

@@ -26,7 +26,8 @@ import org.springframework.cloud.netflix.zuul.filters.route.RestClientRibbonComm
import org.springframework.cloud.netflix.zuul.filters.route.RestClientRibbonCommandFactory;
import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandContext;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.SpanAccessor;
import org.springframework.cloud.sleuth.SpanInjector;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.util.MultiValueMap;
import com.netflix.client.http.HttpRequest;
@@ -43,12 +44,14 @@ public class TraceRestClientRibbonCommandFactory extends RestClientRibbonCommand
private static final Log log = LogFactory.getLog(TraceRestClientRibbonCommandFactory.class);
private final SpanAccessor accessor;
private final Tracer tracer;
private final SpanInjector<HttpRequest.Builder> spanInjector;
public TraceRestClientRibbonCommandFactory(SpringClientFactory clientFactory,
SpanAccessor accessor) {
Tracer tracer, SpanInjector<HttpRequest.Builder> spanInjector) {
super(clientFactory);
this.accessor = accessor;
this.tracer = tracer;
this.spanInjector = spanInjector;
}
@Override
@@ -60,7 +63,7 @@ public class TraceRestClientRibbonCommandFactory extends RestClientRibbonCommand
return new TraceRestClientRibbonCommand(context.getServiceId(), restClient,
getVerb(context.getVerb()), context.getUri(), context.getRetryable(),
context.getHeaders(), context.getParams(), context.getRequestEntity(),
this.accessor);
this.tracer, this.spanInjector);
}
catch (URISyntaxException e) {
log.error("Exception occurred while trying to create the TraceRestClientRibbonCommand", e);
@@ -70,52 +73,31 @@ public class TraceRestClientRibbonCommandFactory extends RestClientRibbonCommand
class TraceRestClientRibbonCommand extends RestClientRibbonCommand {
private final SpanAccessor accessor;
private final Tracer tracer;
private final SpanInjector<HttpRequest.Builder> spanInjector;
@SuppressWarnings("deprecation")
public TraceRestClientRibbonCommand(String commandKey, RestClient restClient,
HttpRequest.Verb verb, String uri, Boolean retryable,
MultiValueMap<String, String> headers,
MultiValueMap<String, String> params, InputStream requestEntity,
SpanAccessor accessor)
Tracer tracer, SpanInjector<HttpRequest.Builder> spanInjector)
throws URISyntaxException {
super(commandKey, restClient, verb, uri, retryable, headers, params,
requestEntity);
this.accessor = accessor;
this.tracer = tracer;
this.spanInjector = spanInjector;
}
@Override
protected void customizeRequest(HttpRequest.Builder requestBuilder) {
Span span = getCurrentSpan();
if (span == null) {
setHeader(requestBuilder, Span.NOT_SAMPLED_NAME, "true");
return;
}
setHeader(requestBuilder, Span.TRACE_ID_NAME, Span.idToHex(span.getTraceId()));
setHeader(requestBuilder, Span.SPAN_ID_NAME, Span.idToHex(span.getSpanId()));
setHeader(requestBuilder, Span.SPAN_NAME_NAME, span.getName());
if (getParentId(span) != null) {
setHeader(requestBuilder, Span.PARENT_ID_NAME,
Span.idToHex(getParentId(span)));
}
setHeader(requestBuilder, Span.PROCESS_ID_NAME,
span.getProcessId());
this.spanInjector.inject(span, requestBuilder);
span.logEvent(Span.CLIENT_SEND);
}
private Long getParentId(Span span) {
return !span.getParents().isEmpty()
? span.getParents().get(0) : null;
}
public void setHeader(HttpRequest.Builder builder, String name, String value) {
if (value != null && this.accessor.isTracing()) {
builder.header(name, value);
}
}
private Span getCurrentSpan() {
return this.accessor.getCurrentSpan();
return this.tracer.getCurrentSpan();
}
}

View File

@@ -23,12 +23,15 @@ 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.SpanAccessor;
import org.springframework.cloud.sleuth.SpanInjector;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.netflix.client.http.HttpRequest;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
/**
* {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration Auto-configuration}
@@ -48,13 +51,14 @@ public class TraceZuulAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public TracePreZuulFilter tracePreZuulFilter(SpanAccessor accessor) {
return new TracePreZuulFilter(accessor);
public TracePreZuulFilter tracePreZuulFilter(Tracer tracer, SpanInjector<RequestContext> spanInjector) {
return new TracePreZuulFilter(tracer, spanInjector);
}
@Bean
public TraceRestClientRibbonCommandFactory traceRestClientRibbonCommandFactory(SpringClientFactory factory, SpanAccessor accessor) {
return new TraceRestClientRibbonCommandFactory(factory, accessor);
public TraceRestClientRibbonCommandFactory traceRestClientRibbonCommandFactory(SpringClientFactory factory,
Tracer tracer, SpanInjector<HttpRequest.Builder> spanInjector) {
return new TraceRestClientRibbonCommandFactory(factory, tracer, spanInjector);
}
@Bean
@@ -63,4 +67,14 @@ public class TraceZuulAutoConfiguration {
return new TracePostZuulFilter(accessor);
}
@Bean
SpanInjector<RequestContext> requestContextInjector() {
return new RequestContextInjector();
}
@Bean
SpanInjector<HttpRequest.Builder> requestBuilderContextInjector() {
return new RequestBuilderContextInjector();
}
}

View File

@@ -48,9 +48,8 @@ public class DefaultTracer implements Tracer {
private final SpanReporter spanReporter;
public DefaultTracer(Sampler defaultSampler, Random random,
SpanNamer spanNamer, SpanLogger spanLogger,
SpanReporter spanReporter) {
public DefaultTracer(Sampler defaultSampler, Random random, SpanNamer spanNamer,
SpanLogger spanLogger, SpanReporter spanReporter) {
this.defaultSampler = defaultSampler;
this.random = random;
this.spanNamer = spanNamer;
@@ -227,5 +226,4 @@ public class DefaultTracer implements Tracer {
}
return runnable;
}
}

View File

@@ -154,24 +154,24 @@ public class SpringCloudSleuthDocTests {
assertThat(initialSpan.logs()).extracting("event").doesNotContain("taxCalculated");
executorService.submit(() -> {
// tag::manual_span_continuation[]
// let's assume that we're in a thread Y and we've received
// the `initialSpan` from thread X
Span continuedSpan = this.tracer.continueSpan(initialSpan);
try {
// ...
// You can tag a span
this.tracer.addTag("taxValue", taxValue);
// ...
// You can log an event on a span
continuedSpan.logEvent("taxCalculated");
} finally {
// Once done remember to detach the span. That way you'll
// safely remove it from the current thread without closing it
this.tracer.detach(continuedSpan);
}
// end::manual_span_continuation[]
}
// tag::manual_span_continuation[]
// let's assume that we're in a thread Y and we've received
// the `initialSpan` from thread X
Span continuedSpan = this.tracer.continueSpan(initialSpan);
try {
// ...
// You can tag a span
this.tracer.addTag("taxValue", taxValue);
// ...
// You can log an event on a span
continuedSpan.logEvent("taxCalculated");
} finally {
// Once done remember to detach the span. That way you'll
// safely remove it from the current thread without closing it
this.tracer.detach(continuedSpan);
}
// end::manual_span_continuation[]
}
).get();
this.tracer.close(initialSpan);
@@ -191,26 +191,26 @@ public class SpringCloudSleuthDocTests {
assertThat(initialSpan.logs()).extracting("event").doesNotContain("commissionCalculated");
executorService.submit(() -> {
// tag::manual_span_joining[]
// let's assume that we're in a thread Y and we've received
// the `initialSpan` from thread X. `initialSpan` will be the parent
// of the `newSpan`
Span newSpan = this.tracer.createSpan("calculateCommission", initialSpan);
try {
// ...
// You can tag a span
this.tracer.addTag("commissionValue", commissionValue);
// ...
// You can log an event on a span
newSpan.logEvent("commissionCalculated");
} finally {
// Once done remember to close the span. This will allow collecting
// the span to send it to Zipkin. The tags and events set on the
// newSpan will not be present on the parent
this.tracer.close(newSpan);
}
// end::manual_span_joining[]
}
// tag::manual_span_joining[]
// let's assume that we're in a thread Y and we've received
// the `initialSpan` from thread X. `initialSpan` will be the parent
// of the `newSpan`
Span newSpan = this.tracer.createSpan("calculateCommission", initialSpan);
try {
// ...
// You can tag a span
this.tracer.addTag("commissionValue", commissionValue);
// ...
// You can log an event on a span
newSpan.logEvent("commissionCalculated");
} finally {
// Once done remember to close the span. This will allow collecting
// the span to send it to Zipkin. The tags and events set on the
// newSpan will not be present on the parent
this.tracer.close(newSpan);
}
// end::manual_span_joining[]
}
).get();
this.tracer.close(initialSpan);

View File

@@ -9,6 +9,7 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration;
import org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration;
import org.springframework.cloud.sleuth.instrument.messaging.TraceSpringIntegrationAutoConfiguration;
import org.springframework.cloud.sleuth.instrument.messaging.websocket.TraceWebSocketAutoConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@@ -16,6 +17,7 @@ import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Retention(RetentionPolicy.RUNTIME)
@EnableAutoConfiguration(exclude = { LoadBalancerAutoConfiguration.class,
JmxAutoConfiguration.class, TraceSpringIntegrationAutoConfiguration.class,
TraceWebSocketAutoConfiguration.class,
LoadBalancerAutoConfiguration.class })
@EnableAspectJAutoProxy(proxyTargetClass = true)
@Configuration

View File

@@ -32,26 +32,30 @@ import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.asser
* @author Dave Syer
*
*/
public class SpanMessageHeadersTests {
public class MessagingSpanInjectorTests {
private TraceKeys traceKeys = new TraceKeys();
private MessagingSpanInjector messagingSpanInjector = new MessagingSpanInjector(this.traceKeys);
@Test
public void spanHeadersAdded() {
Span span = Span.builder().name("http:foo").spanId(1L).traceId(2L).build();
Message<?> message = new GenericMessage<>("Hello World");
message = SpanMessageHeaders.addSpanHeaders(this.traceKeys, message, span);
assertThat(message.getHeaders()).containsKey(Span.SPAN_ID_NAME);
MessageBuilder<?> messageBuilder = MessageBuilder.fromMessage(message);
this.messagingSpanInjector.inject(span, messageBuilder);
assertThat(messageBuilder.build().getHeaders()).containsKey(Span.SPAN_ID_NAME);
}
@Test
public void shouldNotOverrideSpanTags() {
Span span = spanWithStringPayloadType();
Message<?> message = messageWithIntegerPayloadType();
MessageBuilder messageBuilder = messageWithIntegerPayloadType();
message = SpanMessageHeaders.addSpanHeaders(this.traceKeys, message, span);
this.messagingSpanInjector.inject(span, messageBuilder);
assertThat(message.getHeaders())
assertThat(messageBuilder.build().getHeaders())
.containsKeys(Span.SPAN_ID_NAME, "message/payload-type");
assertThat(span).hasATag("message/payload-type", "java.lang.String");
}
@@ -62,18 +66,22 @@ public class SpanMessageHeadersTests {
return span;
}
private Message<?> messageWithIntegerPayloadType() {
private MessageBuilder messageWithIntegerPayloadType() {
MessageHeaderAccessor accessor = SimpMessageHeaderAccessor.create();
accessor.setHeader("message/payload-type", "java.lang.Integer");
return MessageBuilder.createMessage("Hello World", accessor.getMessageHeaders());
return MessageBuilder.withPayload("Hello World").setHeaders(accessor);
}
@Test
public void nativeSpanHeadersAdded() {
Span span = Span.builder().name("http:foo").spanId(1L).traceId(2L).build();
MessageHeaderAccessor accessor = SimpMessageHeaderAccessor.create();
Message<?> message = MessageBuilder.createMessage("Hello World", accessor.getMessageHeaders());
message = SpanMessageHeaders.addSpanHeaders(this.traceKeys, message, span);
Message messageToBuild = MessageBuilder.createMessage("Hello World", accessor.getMessageHeaders());
MessageBuilder<String> messageBuilder = MessageBuilder.fromMessage(messageToBuild);
this.messagingSpanInjector.inject(span, messageBuilder);
Message<String> message = messageBuilder.build();
assertThat(message.getHeaders())
.containsKey(NativeMessageHeaderAccessor.NATIVE_HEADERS);
MessageHeaderAccessor natives = NativeMessageHeaderAccessor

View File

@@ -76,7 +76,7 @@ public class TraceFilterAlwaysSamplerIntegrationTests extends AbstractMvcIntegra
@Override
protected void configureMockMvcBuilder(DefaultMockMvcBuilder mockMvcBuilder) {
mockMvcBuilder.addFilters(new TraceFilter(this.tracer, this.traceKeys,
new NoOpSpanReporter()));
new NoOpSpanReporter(), this.spanExtractor, this.spanInjector));
}
private MvcResult whenSentPingWithTraceIdAndNotSampling(Long traceId)

View File

@@ -17,6 +17,7 @@
package org.springframework.cloud.sleuth.instrument.web;
import java.util.Random;
import java.util.regex.Pattern;
import org.junit.Before;
import org.junit.Test;
@@ -70,7 +71,9 @@ public class TraceFilterMockChainIntegrationTests {
@Test
public void startsNewTrace() throws Exception {
TraceFilter filter = new TraceFilter(this.tracer, this.traceKeys, new NoOpSpanReporter());
TraceFilter filter = new TraceFilter(this.tracer, this.traceKeys, new NoOpSpanReporter(),
new HttpServletRequestExtractor(new Random(), Pattern.compile(TraceFilter.DEFAULT_SKIP_PATTERN)),
new HttpServletResponseInjector());
filter.doFilter(this.request, this.response, this.filterChain);
assertNull(TestSpanContextHolder.getCurrentSpan());
}
@@ -80,7 +83,9 @@ public class TraceFilterMockChainIntegrationTests {
Random generator = new Random();
this.request = builder().header(Span.SPAN_ID_NAME, generator.nextLong())
.header(Span.TRACE_ID_NAME, generator.nextLong()).buildRequest(new MockServletContext());
TraceFilter filter = new TraceFilter(this.tracer, this.traceKeys, new NoOpSpanReporter());
TraceFilter filter = new TraceFilter(this.tracer, this.traceKeys, new NoOpSpanReporter(),
new HttpServletRequestExtractor(new Random(), Pattern.compile(TraceFilter.DEFAULT_SKIP_PATTERN)),
new HttpServletResponseInjector());
filter.doFilter(this.request, this.response, this.filterChain);
assertNull(TestSpanContextHolder.getCurrentSpan());
}

View File

@@ -17,6 +17,10 @@
package org.springframework.cloud.sleuth.instrument.web;
import java.util.Random;
import java.util.regex.Pattern;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.junit.Before;
import org.junit.Test;
@@ -24,9 +28,11 @@ import org.mockito.Mock;
import org.springframework.cloud.sleuth.DefaultSpanNamer;
import org.springframework.cloud.sleuth.Sampler;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.SpanExtractor;
import org.springframework.cloud.sleuth.SpanInjector;
import org.springframework.cloud.sleuth.SpanReporter;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.TraceKeys;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.log.SpanLogger;
import org.springframework.cloud.sleuth.sampler.AlwaysSampler;
import org.springframework.cloud.sleuth.sampler.NeverSampler;
@@ -55,6 +61,9 @@ public class TraceFilterTests {
@Mock SpanLogger spanLogger;
@Mock SpanReporter spanReporter;
SpanExtractor<HttpServletRequest> spanExtractor = new HttpServletRequestExtractor(new Random(), Pattern
.compile(TraceFilter.DEFAULT_SKIP_PATTERN));
SpanInjector<HttpServletResponse> spanInjector = new HttpServletResponseInjector();
private Tracer tracer;
private TraceKeys traceKeys = new TraceKeys();
@@ -91,7 +100,8 @@ public class TraceFilterTests {
@Test
public void notTraced() throws Exception {
this.sampler = NeverSampler.INSTANCE;
TraceFilter filter = new TraceFilter(this.tracer, this.traceKeys, spanReporter);
TraceFilter filter = new TraceFilter(this.tracer, this.traceKeys, this.spanReporter,
this.spanExtractor, this.spanInjector);
this.request = get("/favicon.ico").accept(MediaType.ALL)
.buildRequest(new MockServletContext());
@@ -104,7 +114,8 @@ public class TraceFilterTests {
@Test
public void startsNewTrace() throws Exception {
TraceFilter filter = new TraceFilter(this.tracer, this.traceKeys, spanReporter);
TraceFilter filter = new TraceFilter(this.tracer, this.traceKeys, this.spanReporter,
this.spanExtractor, this.spanInjector);
filter.doFilter(this.request, this.response, this.filterChain);
verifyHttpTags();
assertNull(TestSpanContextHolder.getCurrentSpan());
@@ -117,7 +128,8 @@ public class TraceFilterTests {
.header(Span.TRACE_ID_NAME, Span.idToHex(2L))
.header(Span.PARENT_ID_NAME, Span.idToHex(3L))
.buildRequest(new MockServletContext());
TraceFilter filter = new TraceFilter(this.tracer, this.traceKeys, spanReporter);
TraceFilter filter = new TraceFilter(this.tracer, this.traceKeys, this.spanReporter,
this.spanExtractor, this.spanInjector);
filter.doFilter(this.request, this.response, this.filterChain);
@@ -133,7 +145,8 @@ public class TraceFilterTests {
// It should have been removed from the thread local context so simulate that
TestSpanContextHolder.removeCurrentSpan();
TraceFilter filter = new TraceFilter(this.tracer, this.traceKeys, spanReporter);
TraceFilter filter = new TraceFilter(this.tracer, this.traceKeys, this.spanReporter,
this.spanExtractor, this.spanInjector);
filter.doFilter(this.request, this.response, this.filterChain);
verifyHttpTags();
@@ -146,7 +159,8 @@ public class TraceFilterTests {
this.request = builder().header(Span.SPAN_ID_NAME, 10L)
.header(Span.TRACE_ID_NAME, 20L).buildRequest(new MockServletContext());
TraceFilter filter = new TraceFilter(this.tracer, this.traceKeys, spanReporter);
TraceFilter filter = new TraceFilter(this.tracer, this.traceKeys, this.spanReporter,
this.spanExtractor, this.spanInjector);
filter.doFilter(this.request, this.response, this.filterChain);
verifyHttpTags();
@@ -160,7 +174,8 @@ public class TraceFilterTests {
.header(Span.TRACE_ID_NAME, 20L).buildRequest(new MockServletContext());
this.traceKeys.getHttp().getHeaders().add("x-foo");
TraceFilter filter = new TraceFilter(this.tracer, this.traceKeys, spanReporter);
TraceFilter filter = new TraceFilter(this.tracer, this.traceKeys, this.spanReporter,
this.spanExtractor, this.spanInjector);
this.request.addHeader("X-Foo", "bar");
filter.doFilter(this.request, this.response, this.filterChain);
@@ -175,7 +190,8 @@ public class TraceFilterTests {
.header(Span.TRACE_ID_NAME, 20L).buildRequest(new MockServletContext());
this.traceKeys.getHttp().getHeaders().add("x-foo");
TraceFilter filter = new TraceFilter(this.tracer, this.traceKeys, spanReporter);
TraceFilter filter = new TraceFilter(this.tracer, this.traceKeys, this.spanReporter,
this.spanExtractor, this.spanInjector);
this.request.addHeader("X-Foo", "bar");
this.request.addHeader("X-Foo", "spam");
filter.doFilter(this.request, this.response, this.filterChain);
@@ -187,7 +203,8 @@ public class TraceFilterTests {
@Test
public void catchesException() throws Exception {
TraceFilter filter = new TraceFilter(this.tracer, this.traceKeys, spanReporter);
TraceFilter filter = new TraceFilter(this.tracer, this.traceKeys, this.spanReporter,
this.spanExtractor, this.spanInjector);
this.filterChain = new MockFilterChain() {
@Override
public void doFilter(javax.servlet.ServletRequest request,

View File

@@ -59,7 +59,7 @@ public class TraceRestTemplateInterceptorIntegrationTests {
this.tracer = new DefaultTracer(new AlwaysSampler(), new Random(),
new DefaultSpanNamer(), new NoOpSpanLogger(), new NoOpSpanReporter());
this.template.setInterceptors(Arrays.<ClientHttpRequestInterceptor>asList(
new TraceRestTemplateInterceptor(this.tracer)));
new TraceRestTemplateInterceptor(this.tracer, new HttpRequestInjector())));
TestSpanContextHolder.removeCurrentSpan();
}

View File

@@ -65,7 +65,7 @@ public class TraceRestTemplateInterceptorTests {
this.tracer = new DefaultTracer(new AlwaysSampler(), new Random(),
new DefaultSpanNamer(), new NoOpSpanLogger(), new NoOpSpanReporter());
this.template.setInterceptors(Arrays.<ClientHttpRequestInterceptor>asList(
new TraceRestTemplateInterceptor(this.tracer)));
new TraceRestTemplateInterceptor(this.tracer, new HttpRequestInjector())));
TestSpanContextHolder.removeCurrentSpan();
}

View File

@@ -1,7 +1,14 @@
package org.springframework.cloud.sleuth.instrument.web.common;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.junit.Before;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.sleuth.SpanExtractor;
import org.springframework.cloud.sleuth.SpanInjector;
import org.springframework.cloud.sleuth.TraceKeys;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.util.ExceptionUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.web.WebAppConfiguration;
@@ -23,9 +30,12 @@ import org.springframework.web.context.WebApplicationContext;
@WebAppConfiguration
public abstract class AbstractMvcIntegrationTest {
@Autowired
protected WebApplicationContext webApplicationContext;
@Autowired protected WebApplicationContext webApplicationContext;
protected MockMvc mockMvc;
@Autowired protected Tracer tracer;
@Autowired protected TraceKeys traceKeys;
@Autowired protected SpanExtractor<HttpServletRequest> spanExtractor;
@Autowired protected SpanInjector<HttpServletResponse> spanInjector;
@Before
public void setup() {

View File

@@ -3,10 +3,7 @@ package org.springframework.cloud.sleuth.instrument.web.common;
import org.junit.Before;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.sleuth.NoOpSpanReporter;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.TraceKeys;
import org.springframework.cloud.sleuth.instrument.web.TraceFilter;
import org.springframework.cloud.sleuth.log.NoOpSpanLogger;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder;
@@ -30,8 +27,7 @@ public abstract class AbstractMvcWiremockIntegrationTest extends AbstractMvcInte
protected WireMock wireMock;
@Autowired protected HttpMockServer httpMockServer;
@Autowired protected Tracer tracer;
@Autowired protected TraceKeys traceKeys;
@Override
@Before
@@ -56,6 +52,6 @@ public abstract class AbstractMvcWiremockIntegrationTest extends AbstractMvcInte
@Override
protected void configureMockMvcBuilder(DefaultMockMvcBuilder mockMvcBuilder) {
mockMvcBuilder.addFilters(new TraceFilter(this.tracer, this.traceKeys,
new NoOpSpanReporter()));
new NoOpSpanReporter(), this.spanExtractor, this.spanInjector));
}
}

View File

@@ -32,13 +32,13 @@ public class MultipleHopsIntegrationTests extends AbstractMvcIntegrationTest {
@Autowired Tracer tracer;
@Autowired TraceKeys traceKeys;
@Autowired TraceFilter traceFilter;
@Autowired ArrayListSpanAccumulator arrayListSpanAccumulator;
@Autowired SpanReporter spanReporter;
@Override
protected void configureMockMvcBuilder(DefaultMockMvcBuilder mockMvcBuilder) {
mockMvcBuilder.addFilters(new TraceFilter(this.tracer, this.traceKeys,
this.spanReporter));
mockMvcBuilder.addFilters(this.traceFilter);
}
@Test

View File

@@ -46,7 +46,7 @@ public class TracePreZuulFilterTests {
private DefaultTracer tracer = new DefaultTracer(new AlwaysSampler(), new Random(),
new DefaultSpanNamer(), new NoOpSpanLogger(), new NoOpSpanReporter());
private TracePreZuulFilter filter = new TracePreZuulFilter(this.tracer);
private TracePreZuulFilter filter = new TracePreZuulFilter(this.tracer, new RequestContextInjector());
@After
@Before

View File

@@ -25,7 +25,8 @@ import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
import org.springframework.cloud.netflix.zuul.filters.route.RestClientRibbonCommand;
import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandContext;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.SpanAccessor;
import org.springframework.cloud.sleuth.SpanInjector;
import org.springframework.cloud.sleuth.Tracer;
import com.netflix.client.http.HttpRequest;
import com.netflix.niws.client.http.RestClient;
@@ -41,15 +42,16 @@ import static org.mockito.Matchers.anyString;
@RunWith(MockitoJUnitRunner.class)
public class TraceRestClientRibbonCommandFactoryTest {
@Mock SpanAccessor accessor;
@Mock Tracer tracer;
@Mock SpringClientFactory springClientFactory;
SpanInjector<HttpRequest.Builder> spanInjector = new RequestBuilderContextInjector();
TraceRestClientRibbonCommandFactory traceRestClientRibbonCommandFactory;
@Before
@SuppressWarnings({"deprecation", "unchecked"})
public void setup() {
this.traceRestClientRibbonCommandFactory = new TraceRestClientRibbonCommandFactory(
this.springClientFactory, this.accessor);
this.springClientFactory, this.tracer, this.spanInjector);
given(this.springClientFactory.getClient(anyString(), any(Class.class))).willReturn(new RestClient());
Span span = Span.builder()
.name("name")
@@ -58,13 +60,14 @@ public class TraceRestClientRibbonCommandFactoryTest {
.parent(3L)
.processId("processId")
.build();
given(this.accessor.getCurrentSpan()).willReturn(span);
given(this.accessor.isTracing()).willReturn(true);
given(this.tracer.getCurrentSpan()).willReturn(span);
given(this.tracer.isTracing()).willReturn(true);
}
@Test
public void should_wrap_ribbon_command_in_a_sleuth_representation() throws Exception {
RestClientRibbonCommand restClientRibbonCommand = this.traceRestClientRibbonCommandFactory.create(ribbonCommandContext());
RestClientRibbonCommand restClientRibbonCommand =
this.traceRestClientRibbonCommandFactory.create(ribbonCommandContext());
then(restClientRibbonCommand)
.isInstanceOf(TraceRestClientRibbonCommandFactory.TraceRestClientRibbonCommand.class);
@@ -73,7 +76,8 @@ public class TraceRestClientRibbonCommandFactoryTest {
@Test
public void should_attach_trace_headers_to_the_sent_request() throws Exception {
RestClientRibbonCommand restClientRibbonCommand = this.traceRestClientRibbonCommandFactory.create(ribbonCommandContext());
TraceRestClientRibbonCommandFactory.TraceRestClientRibbonCommand traceRestClientRibbonCommand = (TraceRestClientRibbonCommandFactory.TraceRestClientRibbonCommand) restClientRibbonCommand;
TraceRestClientRibbonCommandFactory.TraceRestClientRibbonCommand traceRestClientRibbonCommand =
(TraceRestClientRibbonCommandFactory.TraceRestClientRibbonCommand) restClientRibbonCommand;
HttpRequest.Builder builder = new HttpRequest.Builder();
traceRestClientRibbonCommand.customizeRequest(builder);