diff --git a/README.adoc b/README.adoc index d20d1c2a4..964988f2e 100644 --- a/README.adoc +++ b/README.adoc @@ -636,11 +636,11 @@ a modified file in the correct place. Just commit it and push the change. If you don't have an IDE preference we would recommend that you use http://www.springsource.com/developer/sts[Spring Tools Suite] or http://eclipse.org[Eclipse] when working with the code. We use the -http://eclipse.org/m2e/[m2eclipe] eclipse plugin for maven support. Other IDEs and tools +http://eclipse.org/m2e/[m2eclipse] eclipse plugin for maven support. Other IDEs and tools should also work without issue as long as they use Maven 3.3.3 or better. ==== Importing into eclipse with m2eclipse -We recommend the http://eclipse.org/m2e/[m2eclipe] eclipse plugin when working with +We recommend the http://eclipse.org/m2e/[m2eclipse] eclipse plugin when working with eclipse. If you don't already have m2eclipse installed it is available from the "eclipse marketplace". diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/HttpSpanExtractor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/HttpSpanExtractor.java new file mode 100644 index 000000000..de2b443ad --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/HttpSpanExtractor.java @@ -0,0 +1,11 @@ +package org.springframework.cloud.sleuth; + +/** + * Contract for extracting tracing headers from a {@link SpanTextMap} + * via HTTP headers + * + * @author Marcin Grzejszczak + * @since 1.2.0 + */ +public interface HttpSpanExtractor extends SpanExtractor { +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/HttpSpanInjector.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/HttpSpanInjector.java new file mode 100644 index 000000000..5b7e5eb6c --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/HttpSpanInjector.java @@ -0,0 +1,11 @@ +package org.springframework.cloud.sleuth; + +/** + * Contract for injecting tracing headers from a {@link SpanTextMap} + * via HTTP headers + * + * @author Marcin Grzejszczak + * @since 1.2.0 + */ +public interface HttpSpanInjector extends SpanInjector { +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/SpanTextMap.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/SpanTextMap.java new file mode 100644 index 000000000..54e77e2d2 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/SpanTextMap.java @@ -0,0 +1,31 @@ +package org.springframework.cloud.sleuth; + +import java.util.Iterator; +import java.util.Map; + +/** + * Adopted from: https://github.com/opentracing/opentracing-java/blob/master/opentracing-api/src/main/java/io/opentracing/propagation/TextMap.java + * + * TextMap is a built-in carrier for {@link SpanInjector} and {@link SpanExtractor}. TextMap implementations allows Tracers to + * read and write key:value String pairs from arbitrary underlying sources of data. + * + * @author Marcin Grzejszczak + * @since 1.2.0 + */ +public interface SpanTextMap extends Iterable> { + /** + * Gets an iterator over arbitrary key:value pairs from the TextMapReader. + * + * @return entries in the TextMap backing store; note that for some Formats, the iterator may include entries that + * were never injected by a Tracer implementation (e.g., unrelated HTTP headers) + */ + Iterator> iterator(); + + /** + * Puts a key:value pair into the TextMapWriter's backing store. + * + * @param key a String, possibly with constraints dictated by the particular Format this TextMap is paired with + * @param value a String, possibly with constraints dictated by the particular Format this TextMap is paired with + */ + void put(String key, String value); +} \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/AbstractTraceChannelInterceptor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/AbstractTraceChannelInterceptor.java index df22ff6e8..95bed7071 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/AbstractTraceChannelInterceptor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/AbstractTraceChannelInterceptor.java @@ -5,17 +5,14 @@ import java.lang.invoke.MethodHandles; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.SpanExtractor; -import org.springframework.cloud.sleuth.SpanInjector; +import org.springframework.cloud.sleuth.SpanTextMap; 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; /** @@ -40,12 +37,12 @@ abstract class AbstractTraceChannelInterceptor extends ChannelInterceptorAdapter private final Tracer tracer; private final TraceKeys traceKeys; - private final SpanExtractor> spanExtractor; - private final SpanInjector> spanInjector; + private final MessagingSpanTextMapExtractor spanExtractor; + private final MessagingSpanTextMapInjector spanInjector; protected AbstractTraceChannelInterceptor(Tracer tracer, TraceKeys traceKeys, - SpanExtractor> spanExtractor, - SpanInjector> spanInjector) { + MessagingSpanTextMapExtractor spanExtractor, + MessagingSpanTextMapInjector spanInjector) { this.tracer = tracer; this.traceKeys = traceKeys; this.spanExtractor = spanExtractor; @@ -60,7 +57,7 @@ abstract class AbstractTraceChannelInterceptor extends ChannelInterceptorAdapter return this.traceKeys; } - protected SpanInjector> getSpanInjector() { + protected MessagingSpanTextMapInjector getSpanInjector() { return this.spanInjector; } @@ -68,9 +65,9 @@ abstract class AbstractTraceChannelInterceptor extends ChannelInterceptorAdapter * Returns a span given the message and a channel. Returns {@code null} if ids are * missing. */ - protected Span buildSpan(Message message) { + protected Span buildSpan(SpanTextMap carrier) { try { - return this.spanExtractor.joinTrace(message); + return this.spanExtractor.joinTrace(carrier); } catch (Exception e) { log.error("Exception occurred while trying to extract span from carrier", e); return null; diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/HeaderBasedMessagingExtractor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/HeaderBasedMessagingExtractor.java new file mode 100644 index 000000000..7d1349af1 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/HeaderBasedMessagingExtractor.java @@ -0,0 +1,61 @@ +package org.springframework.cloud.sleuth.instrument.messaging; + +import java.util.Map; + +import org.springframework.cloud.sleuth.Span; +import org.springframework.cloud.sleuth.SpanTextMap; +import org.springframework.cloud.sleuth.util.TextMapUtil; + +/** + * Default implementation for messaging + * + * @author Marcin Grzejszczak + * @since 1.2.0 + */ +public class HeaderBasedMessagingExtractor implements MessagingSpanTextMapExtractor { + + @Override + public Span joinTrace(SpanTextMap textMap) { + Map carrier = TextMapUtil.asMap(textMap); + if (!hasHeader(carrier, TraceMessageHeaders.SPAN_ID_NAME) + || !hasHeader(carrier, TraceMessageHeaders.TRACE_ID_NAME)) { + return null; + // TODO: Consider throwing IllegalArgumentException; + } + return extractSpanFromHeaders(carrier, Span.builder()); + } + + private Span extractSpanFromHeaders(Map carrier, Span.SpanBuilder builder) { + Span.SpanBuilder spanBuilder = builder; + long traceId = Span + .hexToId(carrier.get(TraceMessageHeaders.TRACE_ID_NAME)); + long spanId = Span.hexToId(carrier.get(TraceMessageHeaders.SPAN_ID_NAME)); + spanBuilder = spanBuilder.traceId(traceId).spanId(spanId); + spanBuilder.exportable( + Span.SPAN_SAMPLED.equals(carrier.get(TraceMessageHeaders.SAMPLED_NAME))); + String processId = carrier.get(TraceMessageHeaders.PROCESS_ID_NAME); + String spanName = carrier.get(TraceMessageHeaders.SPAN_NAME_NAME); + if (spanName != null) { + spanBuilder.name(spanName); + } + if (processId != null) { + spanBuilder.processId(processId); + } + setParentIdIfApplicable(carrier, spanBuilder, TraceMessageHeaders.PARENT_ID_NAME); + spanBuilder.remote(true); + return spanBuilder.build(); + } + + boolean hasHeader(Map message, String name) { + return message.containsKey(name); + } + + private void setParentIdIfApplicable(Map carrier, Span.SpanBuilder spanBuilder, + String spanParentIdHeader) { + String parentId = carrier.get(spanParentIdHeader); + if (parentId != null) { + spanBuilder.parent(Span.hexToId(parentId)); + } + } + +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/HeaderBasedMessagingInjector.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/HeaderBasedMessagingInjector.java new file mode 100644 index 000000000..f5e840d8a --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/HeaderBasedMessagingInjector.java @@ -0,0 +1,102 @@ +package org.springframework.cloud.sleuth.instrument.messaging; + +import java.util.List; +import java.util.Map; + +import org.springframework.cloud.sleuth.Span; +import org.springframework.cloud.sleuth.SpanTextMap; +import org.springframework.cloud.sleuth.TraceKeys; +import org.springframework.cloud.sleuth.util.TextMapUtil; +import org.springframework.util.StringUtils; + +/** + * Default implementation for messaging + * + * @author Marcin Grzejszczak + * @since 1.2.0 + */ +public class HeaderBasedMessagingInjector implements MessagingSpanTextMapInjector { + + private final TraceKeys traceKeys; + + public HeaderBasedMessagingInjector(TraceKeys traceKeys) { + this.traceKeys = traceKeys; + } + + @Override + public void inject(Span span, SpanTextMap carrier) { + Map map = TextMapUtil.asMap(carrier); + if (span == null) { + if (!isSampled(map, TraceMessageHeaders.SAMPLED_NAME)) { + carrier.put(TraceMessageHeaders.SAMPLED_NAME, Span.SPAN_NOT_SAMPLED); + return; + } + return; + } + addHeaders(span, carrier); + } + + private boolean isSampled(Map initialMessage, String sampledHeaderName) { + return Span.SPAN_SAMPLED.equals(initialMessage.get(sampledHeaderName)); + } + + private void addHeaders(Span span, SpanTextMap textMap) { + addHeader(textMap, TraceMessageHeaders.TRACE_ID_NAME, Span.idToHex(span.getTraceId())); + addHeader(textMap, TraceMessageHeaders.SPAN_ID_NAME, Span.idToHex(span.getSpanId())); + if (span.isExportable()) { + addAnnotations(this.traceKeys, textMap, span); + Long parentId = getFirst(span.getParents()); + if (parentId != null) { + addHeader(textMap, TraceMessageHeaders.PARENT_ID_NAME, Span.idToHex(parentId)); + } + addHeader(textMap, TraceMessageHeaders.SPAN_NAME_NAME, span.getName()); + addHeader(textMap, TraceMessageHeaders.PROCESS_ID_NAME, span.getProcessId()); + addHeader(textMap, TraceMessageHeaders.SAMPLED_NAME, Span.SPAN_SAMPLED); + } + else { + addHeader(textMap, TraceMessageHeaders.SAMPLED_NAME, Span.SPAN_NOT_SAMPLED); + } + } + + private void addAnnotations(TraceKeys traceKeys, SpanTextMap spanTextMap, Span span) { + Map map = TextMapUtil.asMap(spanTextMap); + for (String name : traceKeys.getMessage().getHeaders()) { + if (map.containsKey(name)) { + String key = traceKeys.getMessage().getPrefix() + name.toLowerCase(); + Object value = map.get(name); + if (value == null) { + value = "null"; + } + // TODO: better way to serialize? + tagIfEntryMissing(span, key, value.toString()); + } + } + addPayloadAnnotations(traceKeys, map, span); + } + + private void addPayloadAnnotations(TraceKeys traceKeys, Map map, Span span) { + if (map.containsKey(traceKeys.getMessage().getPayload().getType())) { + tagIfEntryMissing(span, traceKeys.getMessage().getPayload().getType(), + map.get(traceKeys.getMessage().getPayload().getType())); + tagIfEntryMissing(span, traceKeys.getMessage().getPayload().getSize(), + map.get(traceKeys.getMessage().getPayload().getSize())); + } + } + + private void tagIfEntryMissing(Span span, String key, String value) { + if (!span.tags().containsKey(key)) { + span.tag(key, value); + } + } + + private void addHeader(SpanTextMap textMap, String name, String value) { + if (StringUtils.hasText(value)) { + textMap.put(name, value); + } + } + + private Long getFirst(List parents) { + return parents.isEmpty() ? null : parents.get(0); + } + +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/IntegrationTraceChannelInterceptor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/IntegrationTraceChannelInterceptor.java index 824870955..5cc14ca11 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/IntegrationTraceChannelInterceptor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/IntegrationTraceChannelInterceptor.java @@ -16,15 +16,11 @@ package org.springframework.cloud.sleuth.instrument.messaging; -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.ChannelInterceptorAware; import org.springframework.integration.channel.interceptor.VetoCapableInterceptor; -import org.springframework.messaging.Message; import org.springframework.messaging.support.ChannelInterceptor; -import org.springframework.messaging.support.MessageBuilder; /** * @author Dave Syer @@ -32,9 +28,10 @@ import org.springframework.messaging.support.MessageBuilder; */ class IntegrationTraceChannelInterceptor extends TraceChannelInterceptor implements VetoCapableInterceptor { + public IntegrationTraceChannelInterceptor(Tracer tracer, TraceKeys traceKeys, - SpanExtractor> spanExtractor, - SpanInjector> spanInjector) { + MessagingSpanTextMapExtractor spanExtractor, + MessagingSpanTextMapInjector spanInjector) { super(tracer, traceKeys, spanExtractor, spanInjector); } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingSpanExtractor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingSpanExtractor.java deleted file mode 100644 index b7cdc809f..000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingSpanExtractor.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * 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.lang.invoke.MethodHandles; -import java.util.Random; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -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 - */ -class MessagingSpanExtractor implements SpanExtractor> { - - private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); - - 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)) - && (!hasHeader(carrier, TraceMessageHeaders.SPAN_ID_NAME) - || !hasHeader(carrier, TraceMessageHeaders.TRACE_ID_NAME))) { - return null; - // TODO: Consider throwing IllegalArgumentException; - } - if (hasHeader(carrier, Span.TRACE_ID_NAME) - || hasHeader(carrier, Span.SPAN_ID_NAME)) { - log.warn("Deprecated trace headers detected. Please upgrade Sleuth to 1.1 " - + "or start sending headers present in the TraceMessageHeaders class"); - return extractSpanFromOldHeaders(carrier, Span.builder()); - } - return extractSpanFromNewHeaders(carrier, Span.builder()); - } - - // Backwards compatibility - private Span extractSpanFromOldHeaders(Message carrier, SpanBuilder spanBuilder) { - return extractSpanFromHeaders(carrier, spanBuilder, Span.TRACE_ID_NAME, Span.SPAN_ID_NAME, - Span.SAMPLED_NAME, Span.PROCESS_ID_NAME, Span.SPAN_NAME_NAME, Span.PARENT_ID_NAME); - } - - private Span extractSpanFromNewHeaders(Message carrier, SpanBuilder spanBuilder) { - return extractSpanFromHeaders(carrier, spanBuilder, TraceMessageHeaders.TRACE_ID_NAME, - TraceMessageHeaders.SPAN_ID_NAME, TraceMessageHeaders.SAMPLED_NAME, - TraceMessageHeaders.PROCESS_ID_NAME, TraceMessageHeaders.SPAN_NAME_NAME, - TraceMessageHeaders.PARENT_ID_NAME); - } - - private Span extractSpanFromHeaders(Message carrier, SpanBuilder spanBuilder, - String traceIdHeader, String spanIdHeader, String spanSampledHeader, - String spanProcessIdHeader, String spanNameHeader, String spanParentIdHeader) { - long traceId = Span - .hexToId(getHeader(carrier, traceIdHeader)); - long spanId = hasHeader(carrier, spanIdHeader) - ? Span.hexToId(getHeader(carrier, spanIdHeader)) - : this.random.nextLong(); - spanBuilder = spanBuilder.traceId(traceId).spanId(spanId); - spanBuilder.exportable( - Span.SPAN_SAMPLED.equals(getHeader(carrier, spanSampledHeader))); - String processId = getHeader(carrier, spanProcessIdHeader); - String spanName = getHeader(carrier, spanNameHeader); - if (spanName != null) { - spanBuilder.name(spanName); - } - if (processId != null) { - spanBuilder.processId(processId); - } - setParentIdIfApplicable(carrier, spanBuilder, spanParentIdHeader); - spanBuilder.remote(true); - return spanBuilder.build(); - } - - String getHeader(Message message, String name) { - return getHeader(message, name, String.class); - } - - T getHeader(Message message, String name, Class type) { - return message.getHeaders().get(name, type); - } - - boolean hasHeader(Message message, String name) { - return message.getHeaders().containsKey(name); - } - - private void setParentIdIfApplicable(Message carrier, SpanBuilder spanBuilder, - String spanParentIdHeader) { - String parentId = getHeader(carrier, spanParentIdHeader); - if (parentId != null) { - spanBuilder.parent(Span.hexToId(parentId)); - } - } -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingSpanInjector.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingSpanInjector.java deleted file mode 100644 index 11098b447..000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingSpanInjector.java +++ /dev/null @@ -1,161 +0,0 @@ -/* - * 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.HashMap; -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; -import org.springframework.messaging.support.MessageHeaderAccessor; -import org.springframework.messaging.support.NativeMessageHeaderAccessor; -import org.springframework.util.StringUtils; - -/** - * Creates a {@link Span.SpanBuilder} from {@link Message} - * - * @author Marcin Grzejszczak - */ -class MessagingSpanInjector implements SpanInjector> { - - private final TraceKeys traceKeys; - - public MessagingSpanInjector(TraceKeys traceKeys) { - this.traceKeys = traceKeys; - } - - @Override - public void inject(Span span, MessageBuilder carrier) { - Message initialMessage = carrier.build(); - MessageHeaderAccessor accessor = MessageHeaderAccessor - .getMutableAccessor(initialMessage); - if (span == null) { - if (!isSampled(initialMessage, Span.SAMPLED_NAME) || - !isSampled(initialMessage, TraceMessageHeaders.SAMPLED_NAME)) { - // Backwards compatibility - accessor.setHeader(Span.SAMPLED_NAME, Span.SPAN_NOT_SAMPLED); - accessor.setHeader(TraceMessageHeaders.SAMPLED_NAME, Span.SPAN_NOT_SAMPLED); - carrier.setHeaders(accessor); - return; - } - return; - } - Map headers = new HashMap<>(); - addOldHeaders(span, initialMessage, accessor, headers); - addNewHeaders(span, initialMessage, accessor, headers); - accessor.copyHeaders(headers); - if (accessor instanceof NativeMessageHeaderAccessor) { - NativeMessageHeaderAccessor nativeAccessor = (NativeMessageHeaderAccessor) accessor; - for (String name : headers.keySet()) { - nativeAccessor.setNativeHeader(name, headers.get(name)); - } - } - carrier.setHeaders(accessor); - } - - private boolean isSampled(Message initialMessage, String sampledHeaderName) { - return Span.SPAN_SAMPLED - .equals(initialMessage.getHeaders().get(sampledHeaderName)); - } - - // Backwards compatibility - private void addOldHeaders(Span span, Message initialMessage, - MessageHeaderAccessor accessor, Map headers) { - addHeaders(span, initialMessage, accessor, headers, Span.TRACE_ID_NAME, - Span.SPAN_ID_NAME, Span.PARENT_ID_NAME, Span.SPAN_NAME_NAME, Span.PROCESS_ID_NAME, - Span.SAMPLED_NAME, TraceMessageHeaders.OLD_SPAN_HEADER); - } - - private void addNewHeaders(Span span, Message initialMessage, - MessageHeaderAccessor accessor, Map headers) { - addHeaders(span, initialMessage, accessor, headers, TraceMessageHeaders.TRACE_ID_NAME, - TraceMessageHeaders.SPAN_ID_NAME, TraceMessageHeaders.PARENT_ID_NAME, TraceMessageHeaders.SPAN_NAME_NAME, - TraceMessageHeaders.PROCESS_ID_NAME, TraceMessageHeaders.SAMPLED_NAME, TraceMessageHeaders.SPAN_HEADER); - } - - private void addHeaders(Span span, Message initialMessage, - MessageHeaderAccessor accessor, Map headers, String traceIdHeader, - String spanIdHeader, String parentIdHeader, String spanNameHeader, String processIdHeader, - String spanSampledHeader, String spanHeader) { - addHeader(headers, traceIdHeader, Span.idToHex(span.getTraceId())); - addHeader(headers, spanIdHeader, Span.idToHex(span.getSpanId())); - if (span.isExportable()) { - addAnnotations(this.traceKeys, initialMessage, span); - Long parentId = getFirst(span.getParents()); - if (parentId != null) { - addHeader(headers, parentIdHeader, Span.idToHex(parentId)); - } - addHeader(headers, spanNameHeader, span.getName()); - addHeader(headers, processIdHeader, span.getProcessId()); - addHeader(headers, spanSampledHeader, Span.SPAN_SAMPLED); - } - else { - addHeader(headers, spanSampledHeader, Span.SPAN_NOT_SAMPLED); - } - accessor.setHeader(spanHeader, span); - } - - private void addAnnotations(TraceKeys traceKeys, Message message, Span span) { - for (String name : traceKeys.getMessage().getHeaders()) { - if (message.getHeaders().containsKey(name)) { - String key = traceKeys.getMessage().getPrefix() + name.toLowerCase(); - Object value = message.getHeaders().get(name); - if (value == null) { - value = "null"; - } - // TODO: better way to serialize? - tagIfEntryMissing(span, key, value.toString()); - } - } - addPayloadAnnotations(traceKeys, message.getPayload(), span); - } - - private void addPayloadAnnotations(TraceKeys traceKeys, Object payload, Span span) { - if (payload != null) { - tagIfEntryMissing(span, traceKeys.getMessage().getPayload().getType(), - payload.getClass().getCanonicalName()); - if (payload instanceof String) { - tagIfEntryMissing(span, traceKeys.getMessage().getPayload().getSize(), - String.valueOf(((String) payload).length())); - } - else if (payload instanceof byte[]) { - tagIfEntryMissing(span, traceKeys.getMessage().getPayload().getSize(), - String.valueOf(((byte[]) payload).length)); - } - } - } - - private void tagIfEntryMissing(Span span, String key, String value) { - if (!span.tags().containsKey(key)) { - span.tag(key, value); - } - } - - private void addHeader(Map headers, String name, String value) { - if (StringUtils.hasText(value)) { - headers.put(name, value); - } - } - - private Long getFirst(List parents) { - return parents.isEmpty() ? null : parents.get(0); - } -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingSpanTextMapExtractor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingSpanTextMapExtractor.java new file mode 100644 index 000000000..4ac3a2490 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingSpanTextMapExtractor.java @@ -0,0 +1,14 @@ +package org.springframework.cloud.sleuth.instrument.messaging; + +import org.springframework.cloud.sleuth.SpanExtractor; +import org.springframework.cloud.sleuth.SpanTextMap; + +/** + * Contract for extracting tracing headers from a {@link SpanTextMap} + * via message headers + * + * @author Marcin Grzejszczak + * @since 1.2.0 + */ +public interface MessagingSpanTextMapExtractor extends SpanExtractor { +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingSpanTextMapInjector.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingSpanTextMapInjector.java new file mode 100644 index 000000000..ad32edaa5 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingSpanTextMapInjector.java @@ -0,0 +1,14 @@ +package org.springframework.cloud.sleuth.instrument.messaging; + +import org.springframework.cloud.sleuth.SpanInjector; +import org.springframework.cloud.sleuth.SpanTextMap; + +/** + * Contract for injecting tracing headers from a {@link SpanTextMap} + * via message headers + * + * @author Marcin Grzejszczak + * @since 1.2.0 + */ +public interface MessagingSpanTextMapInjector extends SpanInjector { +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingTextMap.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingTextMap.java new file mode 100644 index 000000000..5b75d46a6 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingTextMap.java @@ -0,0 +1,74 @@ +/* + * 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.HashMap; +import java.util.Iterator; +import java.util.Map; + +import org.springframework.cloud.sleuth.SpanTextMap; +import org.springframework.messaging.Message; +import org.springframework.messaging.support.MessageBuilder; +import org.springframework.messaging.support.MessageHeaderAccessor; +import org.springframework.messaging.support.NativeMessageHeaderAccessor; +import org.springframework.util.StringUtils; + +/** + * A {@link SpanTextMap} abstraction over {@link MessageBuilder} + * + * @author Marcin Grzejszczak + * @since 1.2.0 + */ +class MessagingTextMap implements SpanTextMap { + + private final MessageBuilder delegate; + + public MessagingTextMap(MessageBuilder delegate) { + this.delegate = delegate; + } + + @Override + public Iterator> iterator() { + Map map = new HashMap<>(); + for (Map.Entry entry : this.delegate.build().getHeaders() + .entrySet()) { + map.put(entry.getKey(), String.valueOf(entry.getValue())); + } + return map.entrySet().iterator(); + } + + @Override + @SuppressWarnings("unchecked") + public void put(String key, String value) { + if (!StringUtils.hasText(value)) { + return; + } + Message initialMessage = this.delegate.build(); + MessageHeaderAccessor accessor = MessageHeaderAccessor + .getMutableAccessor(initialMessage); + Map headers = new HashMap<>(); + headers.put(key, value); + accessor.copyHeaders(headers); + if (accessor instanceof NativeMessageHeaderAccessor) { + NativeMessageHeaderAccessor nativeAccessor = (NativeMessageHeaderAccessor) accessor; + for (String name : headers.keySet()) { + nativeAccessor.setNativeHeader(name, headers.get(name)); + } + } + this.delegate.copyHeaders(accessor.toMessageHeaders()); + } +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceChannelInterceptor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceChannelInterceptor.java index 171ab01ad..e61ef7b5b 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceChannelInterceptor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceChannelInterceptor.java @@ -18,8 +18,6 @@ package org.springframework.cloud.sleuth.instrument.messaging; import org.springframework.cloud.sleuth.Log; import org.springframework.cloud.sleuth.Span; -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; @@ -41,21 +39,21 @@ import org.springframework.messaging.support.MessageHeaderAccessor; public class TraceChannelInterceptor extends AbstractTraceChannelInterceptor { public TraceChannelInterceptor(Tracer tracer, TraceKeys traceKeys, - SpanExtractor> spanExtractor, - SpanInjector> spanInjector) { + MessagingSpanTextMapExtractor spanExtractor, + MessagingSpanTextMapInjector spanInjector) { super(tracer, traceKeys, spanExtractor, spanInjector); } @Override public void afterSendCompletion(Message message, MessageChannel channel, boolean sent, Exception ex) { - Span spanFromHeader = getSpanFromHeader(message); - if (containsServerReceived(spanFromHeader)) { - spanFromHeader.logEvent(Span.SERVER_SEND); - } else if (spanFromHeader != null) { - spanFromHeader.logEvent(Span.CLIENT_RECV); + Span currentSpan = getTracer().getCurrentSpan(); + if (containsServerReceived(currentSpan)) { + currentSpan.logEvent(Span.SERVER_SEND); + } else if (currentSpan != null) { + currentSpan.logEvent(Span.CLIENT_RECV); } addErrorTag(ex); - getTracer().close(spanFromHeader); + getTracer().close(currentSpan); } private boolean containsServerReceived(Span span) { @@ -72,22 +70,18 @@ public class TraceChannelInterceptor extends AbstractTraceChannelInterceptor { @Override public Message preSend(Message message, MessageChannel channel) { + MessageBuilder messageBuilder = MessageBuilder.fromMessage(message); Span parentSpan = getTracer().isTracing() ? getTracer().getCurrentSpan() - : buildSpan(message); + : buildSpan(new MessagingTextMap(messageBuilder)); String name = getMessageChannelName(channel); Span span = startSpan(parentSpan, name, message); - MessageBuilder messageBuilder = MessageBuilder.fromMessage(message); - // Backwards compatibility - if (message.getHeaders().containsKey(TraceMessageHeaders.OLD_MESSAGE_SENT_FROM_CLIENT) || - message.getHeaders().containsKey(TraceMessageHeaders.MESSAGE_SENT_FROM_CLIENT)) { + if (message.getHeaders().containsKey(TraceMessageHeaders.MESSAGE_SENT_FROM_CLIENT)) { span.logEvent(Span.SERVER_RECV); } else { span.logEvent(Span.CLIENT_SEND); - // Backwards compatibility - messageBuilder.setHeader(TraceMessageHeaders.OLD_MESSAGE_SENT_FROM_CLIENT, true); messageBuilder.setHeader(TraceMessageHeaders.MESSAGE_SENT_FROM_CLIENT, true); } - getSpanInjector().inject(span, messageBuilder); + getSpanInjector().inject(span, new MessagingTextMap(messageBuilder)); MessageHeaderAccessor headers = MessageHeaderAccessor.getMutableAccessor(message); headers.copyHeaders(messageBuilder.build().getHeaders()); return new GenericMessage(message.getPayload(), headers.getMessageHeaders()); @@ -97,9 +91,7 @@ public class TraceChannelInterceptor extends AbstractTraceChannelInterceptor { if (span != null) { return getTracer().createSpan(name, span); } - // Backwards compatibility - if (Span.SPAN_NOT_SAMPLED.equals(message.getHeaders().get(Span.SAMPLED_NAME)) || - Span.SPAN_NOT_SAMPLED.equals(message.getHeaders().get(TraceMessageHeaders.SAMPLED_NAME))) { + if (Span.SPAN_NOT_SAMPLED.equals(message.getHeaders().get(TraceMessageHeaders.SAMPLED_NAME))) { return getTracer().createSpan(name, NeverSampler.INSTANCE); } return getTracer().createSpan(name); @@ -108,7 +100,7 @@ public class TraceChannelInterceptor extends AbstractTraceChannelInterceptor { @Override public Message beforeHandle(Message message, MessageChannel channel, MessageHandler handler) { - Span spanFromHeader = getSpanFromHeader(message); + Span spanFromHeader = getTracer().getCurrentSpan(); if (spanFromHeader!= null) { spanFromHeader.logEvent(Span.SERVER_RECV); } @@ -119,7 +111,7 @@ public class TraceChannelInterceptor extends AbstractTraceChannelInterceptor { @Override public void afterMessageHandled(Message message, MessageChannel channel, MessageHandler handler, Exception ex) { - Span spanFromHeader = getSpanFromHeader(message); + Span spanFromHeader = getTracer().getCurrentSpan(); if (spanFromHeader!= null) { spanFromHeader.logEvent(Span.SERVER_SEND); addErrorTag(ex); @@ -133,19 +125,4 @@ public class TraceChannelInterceptor extends AbstractTraceChannelInterceptor { } } - private Span getSpanFromHeader(Message message) { - if (message == null) { - return null; - } - Object object = message.getHeaders().get(TraceMessageHeaders.OLD_SPAN_HEADER); - if (object instanceof Span) { - return (Span) object; - } - object = message.getHeaders().get(TraceMessageHeaders.SPAN_HEADER); - if (object instanceof Span) { - return (Span) object; - } - return null; - } - } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceMessageHeaders.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceMessageHeaders.java index 1bd173148..4ed97a6b1 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceMessageHeaders.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceMessageHeaders.java @@ -33,9 +33,6 @@ public class TraceMessageHeaders { public static final String TRACE_ID_NAME = "spanTraceId"; public static final String SPAN_NAME_NAME = "spanName"; - static final String OLD_SPAN_HEADER = "X-Current-Span"; - static final String OLD_MESSAGE_SENT_FROM_CLIENT = "X-Message-Sent"; - static final String SPAN_HEADER = "currentSpan"; static final String MESSAGE_SENT_FROM_CLIENT = "messageSent"; private TraceMessageHeaders() {} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceSpanMessagingAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceSpanMessagingAutoConfiguration.java index 30a0470eb..bc1bdcd49 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceSpanMessagingAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceSpanMessagingAutoConfiguration.java @@ -20,13 +20,11 @@ import java.util.Random; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.cloud.sleuth.SpanExtractor; -import org.springframework.cloud.sleuth.SpanInjector; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.cloud.sleuth.TraceKeys; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.messaging.Message; -import org.springframework.messaging.support.MessageBuilder; /** * AutoConfiguration containing Span extractor and injector for messaging. Will be reused @@ -41,12 +39,14 @@ import org.springframework.messaging.support.MessageBuilder; public class TraceSpanMessagingAutoConfiguration { @Bean - public SpanExtractor> messagingSpanExtractor(Random random) { - return new MessagingSpanExtractor(random); + @ConditionalOnMissingBean + public MessagingSpanTextMapExtractor messagingSpanExtractor() { + return new HeaderBasedMessagingExtractor(); } @Bean - public SpanInjector> messagingSpanInjector(TraceKeys traceKeys) { - return new MessagingSpanInjector(traceKeys); + @ConditionalOnMissingBean + public MessagingSpanTextMapInjector messagingSpanInjector(TraceKeys traceKeys) { + return new HeaderBasedMessagingInjector(traceKeys); } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceSpringIntegrationAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceSpringIntegrationAutoConfiguration.java index d9eb1b008..31111ea55 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceSpringIntegrationAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceSpringIntegrationAutoConfiguration.java @@ -23,16 +23,12 @@ 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.SpanExtractor; -import org.springframework.cloud.sleuth.SpanInjector; import org.springframework.cloud.sleuth.TraceKeys; import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration; import org.springframework.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 @@ -56,8 +52,8 @@ public class TraceSpringIntegrationAutoConfiguration { @Bean @GlobalChannelInterceptor(patterns = "${spring.sleuth.integration.patterns:*}") public TraceChannelInterceptor traceChannelInterceptor(Tracer tracer, - TraceKeys traceKeys, Random random, SpanExtractor> spanExtractor, - SpanInjector> spanInjector) { + TraceKeys traceKeys, Random random, MessagingSpanTextMapExtractor spanExtractor, + MessagingSpanTextMapInjector spanInjector) { return new IntegrationTraceChannelInterceptor(tracer, traceKeys, spanExtractor, spanInjector); } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/websocket/TraceWebSocketAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/websocket/TraceWebSocketAutoConfiguration.java index 40bcc504b..86c2ed4e2 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/websocket/TraceWebSocketAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/websocket/TraceWebSocketAutoConfiguration.java @@ -5,16 +5,14 @@ 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.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.MessagingSpanTextMapExtractor; +import org.springframework.cloud.sleuth.instrument.messaging.MessagingSpanTextMapInjector; import org.springframework.cloud.sleuth.instrument.messaging.TraceChannelInterceptor; import org.springframework.cloud.sleuth.instrument.messaging.TraceSpanMessagingAutoConfiguration; 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; @@ -43,9 +41,9 @@ public class TraceWebSocketAutoConfiguration @Autowired TraceKeys traceKeys; @Autowired - SpanExtractor> spanExtractor; + MessagingSpanTextMapExtractor spanExtractor; @Autowired - SpanInjector> spanInjector; + MessagingSpanTextMapInjector spanInjector; @Override public void registerStompEndpoints(StompEndpointRegistry registry) { diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/HttpServletRequestExtractor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/HttpServletRequestExtractor.java deleted file mode 100644 index 2a682bfe6..000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/HttpServletRequestExtractor.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * 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.lang.invoke.MethodHandles; -import java.util.regex.Pattern; -import javax.servlet.http.HttpServletRequest; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -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 { - - private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); - private static final String HTTP_COMPONENT = "http"; - - private final Pattern skipPattern; - - private UrlPathHelper urlPathHelper = new UrlPathHelper(); - - public HttpServletRequestExtractor(Pattern skipPattern) { - this.skipPattern = skipPattern; - } - - @Override - public Span joinTrace(HttpServletRequest carrier) { - if (carrier.getHeader(Span.TRACE_ID_NAME) == null) { - // can't build a Span without trace id - return null; - } - try { - String uri = this.urlPathHelper.getPathWithinApplication(carrier); - boolean skip = this.skipPattern.matcher(uri).matches() - || Span.SPAN_NOT_SAMPLED.equals(carrier.getHeader(Span.SAMPLED_NAME)); - long traceId = Span - .hexToId(carrier.getHeader(Span.TRACE_ID_NAME)); - long spanId = spanId(carrier, traceId); - return buildParentSpan(carrier, uri, skip, traceId, spanId); - } catch (Exception e) { - log.error("Exception occurred while trying to extract span from carrier", e); - return null; - } - } - - private long spanId(HttpServletRequest carrier, long traceId) { - String spanId = carrier.getHeader(Span.SPAN_ID_NAME); - if (spanId == null) { - if (log.isDebugEnabled()) { - log.debug("Request is missing a span id but it has a trace id. We'll assume that this is " - + "a root span with span id equal to trace id"); - } - return traceId; - } else { - return Span.hexToId(spanId); - } - } - - private Span buildParentSpan(HttpServletRequest carrier, String uri, boolean skip, - long traceId, long spanId) { - 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(); - } -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/HttpServletRequestTextMap.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/HttpServletRequestTextMap.java new file mode 100644 index 000000000..2981271c8 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/HttpServletRequestTextMap.java @@ -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; + +import javax.servlet.http.HttpServletRequest; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import org.springframework.cloud.sleuth.SpanTextMap; +import org.springframework.web.util.UrlPathHelper; + +/** + * A {@link SpanTextMap} abstraction over {@link HttpServletRequest} + * + * @author Marcin Grzejszczak + * @since 1.2.0 + */ +class HttpServletRequestTextMap implements SpanTextMap { + + private final HttpServletRequest delegate; + private final Map additionalHeaders = new HashMap<>(); + + HttpServletRequestTextMap(HttpServletRequest delegate) { + this.delegate = delegate; + UrlPathHelper urlPathHelper = new UrlPathHelper(); + this.additionalHeaders.put(ZipkinHttpSpanExtractor.URI_HEADER, + urlPathHelper.getPathWithinApplication(delegate)); + } + + @Override + public Iterator> iterator() { + Map map = new HashMap<>(); + Enumeration headerNames = this.delegate.getHeaderNames(); + while (headerNames != null && headerNames.hasMoreElements()) { + String name = headerNames.nextElement(); + map.put(name, this.delegate.getHeader(name)); + } + map.putAll(this.additionalHeaders); + return map.entrySet().iterator(); + } + + @Override + public void put(String key, String value) { + this.additionalHeaders.put(key, value); + } +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceFilter.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceFilter.java index 96ed118a7..c31f92d84 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceFilter.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceFilter.java @@ -15,23 +15,23 @@ */ package org.springframework.cloud.sleuth.instrument.web; -import java.io.IOException; -import java.lang.invoke.MethodHandles; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Enumeration; -import java.util.regex.Pattern; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.lang.invoke.MethodHandles; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.regex.Pattern; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.cloud.sleuth.HttpSpanExtractor; import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.SpanExtractor; import org.springframework.cloud.sleuth.SpanReporter; import org.springframework.cloud.sleuth.TraceKeys; import org.springframework.cloud.sleuth.Tracer; @@ -88,20 +88,20 @@ public class TraceFilter extends GenericFilterBean { private final TraceKeys traceKeys; private final Pattern skipPattern; private final SpanReporter spanReporter; - private final SpanExtractor spanExtractor; + private final HttpSpanExtractor spanExtractor; private final HttpTraceKeysInjector httpTraceKeysInjector; private UrlPathHelper urlPathHelper = new UrlPathHelper(); public TraceFilter(Tracer tracer, TraceKeys traceKeys, SpanReporter spanReporter, - SpanExtractor spanExtractor, + HttpSpanExtractor spanExtractor, HttpTraceKeysInjector httpTraceKeysInjector) { this(tracer, traceKeys, Pattern.compile(DEFAULT_SKIP_PATTERN), spanReporter, spanExtractor, httpTraceKeysInjector); } public TraceFilter(Tracer tracer, TraceKeys traceKeys, Pattern skipPattern, - SpanReporter spanReporter, SpanExtractor spanExtractor, + SpanReporter spanReporter, HttpSpanExtractor spanExtractor, HttpTraceKeysInjector httpTraceKeysInjector) { this.tracer = tracer; this.traceKeys = traceKeys; @@ -288,7 +288,7 @@ public class TraceFilter extends GenericFilterBean { } return spanFromRequest; } - Span parent = this.spanExtractor.joinTrace(request); + Span parent = this.spanExtractor.joinTrace(new HttpServletRequestTextMap(request)); if (parent != null) { if (log.isDebugEnabled()) { log.debug("Found a parent span " + parent + " in the request"); diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java index 43117dae3..7e6b23d32 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java @@ -15,7 +15,6 @@ */ package org.springframework.cloud.sleuth.instrument.web; -import javax.servlet.http.HttpServletRequest; import java.util.regex.Pattern; import org.springframework.beans.factory.BeanFactory; @@ -30,7 +29,8 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.context.embedded.FilterRegistrationBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.cloud.sleuth.SpanExtractor; +import org.springframework.cloud.sleuth.HttpSpanExtractor; +import org.springframework.cloud.sleuth.HttpSpanInjector; import org.springframework.cloud.sleuth.SpanNamer; import org.springframework.cloud.sleuth.SpanReporter; import org.springframework.cloud.sleuth.TraceKeys; @@ -90,10 +90,7 @@ public class TraceWebAutoConfiguration { } @Bean - public FilterRegistrationBean traceWebFilter(Tracer tracer, TraceKeys traceKeys, - SkipPatternProvider skipPatternProvider, SpanReporter spanReporter, - SpanExtractor spanExtractor, - HttpTraceKeysInjector httpTraceKeysInjector, TraceFilter traceFilter) { + public FilterRegistrationBean traceWebFilter(TraceFilter traceFilter) { FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(traceFilter); filterRegistrationBean.setDispatcherTypes(ASYNC, ERROR, FORWARD, INCLUDE, REQUEST); filterRegistrationBean.setOrder(TraceFilter.ORDER); @@ -103,16 +100,22 @@ public class TraceWebAutoConfiguration { @Bean public TraceFilter traceFilter(Tracer tracer, TraceKeys traceKeys, SkipPatternProvider skipPatternProvider, SpanReporter spanReporter, - SpanExtractor spanExtractor, + HttpSpanExtractor spanExtractor, HttpTraceKeysInjector httpTraceKeysInjector) { return new TraceFilter(tracer, traceKeys, skipPatternProvider.skipPattern(), spanReporter, spanExtractor, httpTraceKeysInjector); } @Bean - public SpanExtractor httpServletRequestSpanExtractor( - SkipPatternProvider skipPatternProvider) { - return new HttpServletRequestExtractor(skipPatternProvider.skipPattern()); + @ConditionalOnMissingBean + public HttpSpanExtractor httpSpanExtractor(@Value("${spring.sleuth.web.skipPattern:}") String skipPattern) { + return new ZipkinHttpSpanExtractor(Pattern.compile(skipPattern)); + } + + @Bean + @ConditionalOnMissingBean + public HttpSpanInjector httpSpanInjector() { + return new ZipkinHttpSpanInjector(); } @Configuration diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/ZipkinHttpSpanExtractor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/ZipkinHttpSpanExtractor.java new file mode 100644 index 000000000..caaca81c1 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/ZipkinHttpSpanExtractor.java @@ -0,0 +1,92 @@ +package org.springframework.cloud.sleuth.instrument.web; + +import java.lang.invoke.MethodHandles; +import java.util.Map; +import java.util.regex.Pattern; + +import org.apache.commons.logging.LogFactory; +import org.springframework.cloud.sleuth.HttpSpanExtractor; +import org.springframework.cloud.sleuth.Span; +import org.springframework.cloud.sleuth.SpanTextMap; +import org.springframework.cloud.sleuth.util.TextMapUtil; +import org.springframework.util.StringUtils; + +/** + * Default implementation, compatible with Zipkin propagation. + * + * @author Marcin Grzejszczak + * @since 1.2.0 + */ +public class ZipkinHttpSpanExtractor implements HttpSpanExtractor { + + private static final org.apache.commons.logging.Log log = LogFactory.getLog( + MethodHandles.lookup().lookupClass()); + static final String URI_HEADER = "X-Span-Uri"; + private static final String HTTP_COMPONENT = "http"; + + private final Pattern skipPattern; + + public ZipkinHttpSpanExtractor(Pattern skipPattern) { + this.skipPattern = skipPattern; + } + + @Override + public Span joinTrace(SpanTextMap textMap) { + Map carrier = TextMapUtil.asMap(textMap); + if (carrier.get(Span.TRACE_ID_NAME) == null) { + // can't build a Span without trace id + return null; + } + try { + String uri = carrier.get(URI_HEADER); + boolean skip = this.skipPattern.matcher(uri).matches() + || Span.SPAN_NOT_SAMPLED.equals(carrier.get(Span.SAMPLED_NAME)); + long traceId = Span + .hexToId(carrier.get(Span.TRACE_ID_NAME)); + long spanId = spanId(carrier, traceId); + return buildParentSpan(carrier, uri, skip, traceId, spanId); + } catch (Exception e) { + log.error("Exception occurred while trying to extract span from carrier", e); + return null; + } + } + + private long spanId(Map carrier, long traceId) { + String spanId = carrier.get(Span.SPAN_ID_NAME); + if (spanId == null) { + if (log.isDebugEnabled()) { + log.debug("Request is missing a span id but it has a trace id. We'll assume that this is " + + "a root span with span id equal to trace id"); + } + return traceId; + } else { + return Span.hexToId(spanId); + } + } + + private Span buildParentSpan(Map carrier, String uri, boolean skip, + long traceId, long spanId) { + Span.SpanBuilder span = Span.builder().traceId(traceId).spanId(spanId); + String processId = carrier.get(Span.PROCESS_ID_NAME); + String parentName = carrier.get(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.containsKey(Span.PARENT_ID_NAME)) { + span.parent(Span + .hexToId(carrier.get(Span.PARENT_ID_NAME))); + } + span.remote(true); + if (skip) { + span.exportable(false); + } + return span.build(); + } + +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/ZipkinHttpSpanInjector.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/ZipkinHttpSpanInjector.java new file mode 100644 index 000000000..8b19df931 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/ZipkinHttpSpanInjector.java @@ -0,0 +1,53 @@ +package org.springframework.cloud.sleuth.instrument.web; + +import java.util.Map; + +import org.springframework.cloud.sleuth.HttpSpanInjector; +import org.springframework.cloud.sleuth.Span; +import org.springframework.cloud.sleuth.SpanTextMap; +import org.springframework.util.StringUtils; + +/** + * Default implementation of {@link HttpSpanInjector}, compatible with Zipkin propagation. + * + * @author Marcin Grzejszczak + * @since 1.2.0 + */ +public class ZipkinHttpSpanInjector implements HttpSpanInjector { + + @Override + public void inject(Span span, SpanTextMap carrier) { + setIdHeader(carrier, Span.TRACE_ID_NAME, span.getTraceId()); + setIdHeader(carrier, Span.SPAN_ID_NAME, span.getSpanId()); + setHeader(carrier, Span.SAMPLED_NAME, span.isExportable() ? Span.SPAN_SAMPLED : Span.SPAN_NOT_SAMPLED); + 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(SpanTextMap carrier, String name, String value) { + if (StringUtils.hasText(value) && !entryPresent(carrier, name)) { + carrier.put(name, value); + } + } + + private void setIdHeader(SpanTextMap carrier, String name, Long value) { + if (value != null) { + setHeader(carrier, name, Span.idToHex(value)); + } + } + + private boolean entryPresent(SpanTextMap carrier, String name) { + for (Map.Entry entry : carrier) { + if (entry.getKey().equals(name)) { + return true; + } + } + return false; + } + +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/AbstractTraceHttpRequestInterceptor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/AbstractTraceHttpRequestInterceptor.java index 413cf88f7..03bc73d5a 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/AbstractTraceHttpRequestInterceptor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/AbstractTraceHttpRequestInterceptor.java @@ -21,8 +21,8 @@ import java.net.URI; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.cloud.sleuth.HttpSpanInjector; import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.SpanInjector; import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.instrument.web.HttpTraceKeysInjector; import org.springframework.http.HttpRequest; @@ -38,11 +38,11 @@ abstract class AbstractTraceHttpRequestInterceptor { protected static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); protected final Tracer tracer; - protected final SpanInjector spanInjector; + protected final HttpSpanInjector spanInjector; protected final HttpTraceKeysInjector keysInjector; protected AbstractTraceHttpRequestInterceptor(Tracer tracer, - SpanInjector spanInjector, HttpTraceKeysInjector keysInjector) { + HttpSpanInjector spanInjector, HttpTraceKeysInjector keysInjector) { this.tracer = tracer; this.spanInjector = spanInjector; this.keysInjector = keysInjector; @@ -56,7 +56,7 @@ abstract class AbstractTraceHttpRequestInterceptor { URI uri = request.getURI(); String spanName = uriScheme(uri) + ":" + uri.getPath(); Span newSpan = this.tracer.createSpan(spanName); - this.spanInjector.inject(newSpan, request); + this.spanInjector.inject(newSpan, new HttpRequestTextMap(request)); addRequestTags(request); newSpan.logEvent(Span.CLIENT_SEND); if (log.isDebugEnabled()) { diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/HttpRequestInjector.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/HttpRequestInjector.java deleted file mode 100644 index 8f12f3150..000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/HttpRequestInjector.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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 { - - @Override - public void inject(Span span, HttpRequest carrier) { - setIdHeader(carrier, Span.TRACE_ID_NAME, span.getTraceId()); - setIdHeader(carrier, Span.SPAN_ID_NAME, span.getSpanId()); - setHeader(carrier, Span.SAMPLED_NAME, span.isExportable() ? Span.SPAN_SAMPLED : Span.SPAN_NOT_SAMPLED); - 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)); - } - } -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/HttpRequestTextMap.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/HttpRequestTextMap.java new file mode 100644 index 000000000..417681490 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/HttpRequestTextMap.java @@ -0,0 +1,67 @@ +/* + * 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 java.util.AbstractMap; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.springframework.cloud.sleuth.SpanTextMap; +import org.springframework.http.HttpRequest; +import org.springframework.util.StringUtils; + +/** + * A {@link SpanTextMap} abstraction over {@link HttpRequest} + * + * @author Marcin Grzejszczak + * @since 1.2.0 + */ +class HttpRequestTextMap implements SpanTextMap { + + private final HttpRequest delegate; + + HttpRequestTextMap(HttpRequest delegate) { + this.delegate = delegate; + } + + @Override + public Iterator> iterator() { + final Iterator>> iterator = this.delegate.getHeaders() + .entrySet().iterator(); + return new Iterator>() { + @Override public boolean hasNext() { + return iterator.hasNext(); + } + + @Override public Map.Entry next() { + Map.Entry> next = iterator.next(); + List value = next.getValue(); + return new AbstractMap.SimpleEntry<>(next.getKey(), value.isEmpty() ? "" : value.get(0)); + } + }; + } + + @Override + public void put(String key, String value) { + if (!StringUtils.hasText(value)) { + return; + } + this.delegate.getHeaders().put(key, Collections.singletonList(value)); + } +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncClientHttpRequestFactoryWrapper.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncClientHttpRequestFactoryWrapper.java index 49e26f6c4..152316d3b 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncClientHttpRequestFactoryWrapper.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncClientHttpRequestFactoryWrapper.java @@ -19,12 +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.HttpSpanInjector; import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.instrument.web.HttpTraceKeysInjector; 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; @@ -56,7 +55,7 @@ public class TraceAsyncClientHttpRequestFactoryWrapper extends AbstractTraceHttp * @see org.springframework.web.client.AsyncRestTemplate#AsyncRestTemplate(AsyncClientHttpRequestFactory) */ public TraceAsyncClientHttpRequestFactoryWrapper(Tracer tracer, - SpanInjector spanInjector, + HttpSpanInjector spanInjector, AsyncClientHttpRequestFactory asyncDelegate, HttpTraceKeysInjector httpTraceKeysInjector) { super(tracer, spanInjector, httpTraceKeysInjector); @@ -70,7 +69,7 @@ public class TraceAsyncClientHttpRequestFactoryWrapper extends AbstractTraceHttp * has a wrapped task executor via the {@link TraceAsyncListenableTaskExecutor} */ public TraceAsyncClientHttpRequestFactoryWrapper(Tracer tracer, - SpanInjector spanInjector, HttpTraceKeysInjector httpTraceKeysInjector) { + HttpSpanInjector spanInjector, HttpTraceKeysInjector httpTraceKeysInjector) { super(tracer, spanInjector, httpTraceKeysInjector); SimpleClientHttpRequestFactory simpleClientHttpRequestFactory = defaultClientHttpRequestFactory(); this.asyncDelegate = simpleClientHttpRequestFactory; @@ -78,7 +77,7 @@ public class TraceAsyncClientHttpRequestFactoryWrapper extends AbstractTraceHttp } public TraceAsyncClientHttpRequestFactoryWrapper(Tracer tracer, - SpanInjector spanInjector, + HttpSpanInjector spanInjector, AsyncClientHttpRequestFactory asyncDelegate, ClientHttpRequestFactory syncDelegate, HttpTraceKeysInjector httpTraceKeysInjector) { diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptor.java index ce71beb84..1ef20e719 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptor.java @@ -18,8 +18,8 @@ package org.springframework.cloud.sleuth.instrument.web.client; import java.io.IOException; +import org.springframework.cloud.sleuth.HttpSpanInjector; import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.SpanInjector; import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.instrument.web.HttpTraceKeysInjector; import org.springframework.cloud.sleuth.util.ExceptionUtils; @@ -41,7 +41,7 @@ import org.springframework.http.client.ClientHttpResponse; public class TraceRestTemplateInterceptor extends AbstractTraceHttpRequestInterceptor implements ClientHttpRequestInterceptor { - public TraceRestTemplateInterceptor(Tracer tracer, SpanInjector spanInjector, + public TraceRestTemplateInterceptor(Tracer tracer, HttpSpanInjector spanInjector, HttpTraceKeysInjector httpTraceKeysInjector) { super(tracer, spanInjector, httpTraceKeysInjector); } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfiguration.java index d5fb8f2e6..f4d1e70ef 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfiguration.java @@ -22,14 +22,13 @@ 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.HttpSpanInjector; import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.instrument.web.HttpTraceKeysInjector; import org.springframework.cloud.sleuth.instrument.web.TraceWebAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.task.AsyncListenableTaskExecutor; -import org.springframework.http.HttpRequest; import org.springframework.http.client.AsyncClientHttpRequestFactory; import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.http.client.SimpleClientHttpRequestFactory; @@ -54,7 +53,7 @@ public class TraceWebAsyncClientAutoConfiguration { @Autowired Tracer tracer; @Autowired private HttpTraceKeysInjector httpTraceKeysInjector; - @Autowired private SpanInjector spanInjector; + @Autowired private HttpSpanInjector spanInjector; @Autowired(required = false) private ClientHttpRequestFactory clientHttpRequestFactory; @Autowired(required = false) private AsyncClientHttpRequestFactory asyncClientHttpRequestFactory; diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebClientAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebClientAutoConfiguration.java index 6585b0b13..8f7094c33 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebClientAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebClientAutoConfiguration.java @@ -26,13 +26,12 @@ import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.cloud.sleuth.SpanInjector; +import org.springframework.cloud.sleuth.HttpSpanInjector; import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.instrument.web.HttpTraceKeysInjector; import org.springframework.cloud.sleuth.instrument.web.TraceWebAutoConfiguration; 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; @@ -54,16 +53,11 @@ public class TraceWebClientAutoConfiguration { @Bean @ConditionalOnMissingBean public TraceRestTemplateInterceptor traceRestTemplateInterceptor(Tracer tracer, - SpanInjector spanInjector, + HttpSpanInjector spanInjector, HttpTraceKeysInjector httpTraceKeysInjector) { return new TraceRestTemplateInterceptor(tracer, spanInjector, httpTraceKeysInjector); } - @Bean - public SpanInjector httpRequestSpanInjector() { - return new HttpRequestInjector(); - } - @Configuration protected static class TraceInterceptorConfiguration { diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignRequestInjector.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignRequestInjector.java deleted file mode 100644 index f1323a35e..000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignRequestInjector.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * 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.nio.charset.Charset; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.atomic.AtomicReference; - -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.SpanInjector; -import org.springframework.util.StringUtils; - -import feign.Request; - -/** - * Span injector that injects tracing info to {@link Request} via {@link AtomicReference} - * since {@link Request} is immutable. - * - * @author Marcin Grzejszczak - * - * @since 1.0.0 - */ -class FeignRequestInjector implements SpanInjector> { - - @Override - public void inject(Span span, AtomicReference carrier) { - String method = carrier.get().method(); - String url = carrier.get().url(); - Map> headers = new HashMap<>(carrier.get().headers()); - byte[] body = carrier.get().body(); - Charset charset = carrier.get().charset(); - if (span == null) { - setHeader(headers, Span.SAMPLED_NAME, Span.SPAN_NOT_SAMPLED); - carrier.set(Request.create(method, url, headers, body, charset)); - return; - } - setHeader(headers, Span.TRACE_ID_NAME, Span.idToHex(span.getTraceId())); - setHeader(headers, Span.SPAN_NAME_NAME, span.getName()); - setHeader(headers, Span.SPAN_ID_NAME, Span.idToHex(span.getSpanId())); - setHeader(headers, Span.SAMPLED_NAME, span.isExportable() ? - Span.SPAN_SAMPLED : Span.SPAN_NOT_SAMPLED); - Long parentId = getParentId(span); - if (parentId != null) { - setHeader(headers, Span.PARENT_ID_NAME, Span.idToHex(parentId)); - } - setHeader(headers, Span.PROCESS_ID_NAME, span.getProcessId()); - carrier.set(Request.create(method, url, headers, body, charset)); - } - - private Long getParentId(Span span) { - return !span.getParents().isEmpty() ? span.getParents().get(0) : null; - } - - protected void setHeader(Map> headers, String name, String value) { - if (StringUtils.hasText(value) && !headers.containsKey(name)) { - List list = new ArrayList<>(); - list.add(value); - headers.put(name, list); - } - } -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignRequestTextMap.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignRequestTextMap.java new file mode 100644 index 000000000..287603d04 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignRequestTextMap.java @@ -0,0 +1,86 @@ +/* + * 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.nio.charset.Charset; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; + +import org.springframework.cloud.sleuth.SpanTextMap; +import org.springframework.util.StringUtils; + +import feign.Request; + +/** + * A {@link SpanTextMap} abstraction over {@link AtomicReference} + * + * @author Marcin Grzejszczak + * @since 1.2.0 + */ +class FeignRequestTextMap implements SpanTextMap { + + private final AtomicReference delegate; + + FeignRequestTextMap(AtomicReference delegate) { + this.delegate = delegate; + } + + @Override + public Iterator> iterator() { + final Iterator>> iterator = this.delegate.get().headers().entrySet().iterator(); + return new Iterator>() { + @Override public boolean hasNext() { + return iterator.hasNext(); + } + + @Override public Map.Entry next() { + Map.Entry> next = iterator.next(); + Collection value = next.getValue(); + return new AbstractMap.SimpleEntry<>(next.getKey(), value.isEmpty() ? "" : value.iterator().next()); + } + }; + } + + @Override + public void put(String key, String value) { + if (!StringUtils.hasText(value)) { + return; + } + String method = this.delegate.get().method(); + String url = this.delegate.get().url(); + Map> headers = new HashMap<>(this.delegate.get().headers()); + byte[] body = this.delegate.get().body(); + Charset charset = this.delegate.get().charset(); + addHeader(key, value, headers); + this.delegate.set(Request.create(method, url, headers, body, charset)); + } + + private void addHeader(String key, String value, + Map> headers) { + if (!headers.containsKey(key)) { + List list = new ArrayList<>(); + list.add(value); + headers.put(key, list); + } + } +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClient.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClient.java index 33acce88b..c3226fae2 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClient.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClient.java @@ -24,14 +24,15 @@ import java.util.concurrent.atomic.AtomicReference; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.BeanFactory; +import org.springframework.cloud.sleuth.HttpSpanInjector; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.instrument.web.HttpTraceKeysInjector; +import org.springframework.cloud.sleuth.util.ExceptionUtils; import feign.Client; import feign.Request; import feign.Response; -import org.springframework.cloud.sleuth.util.ExceptionUtils; /** * A Feign Client that closes a Span if there is no response body. In other cases Span @@ -49,7 +50,7 @@ class TraceFeignClient implements Client { private HttpTraceKeysInjector keysInjector; private final BeanFactory beanFactory; private Tracer tracer; - private final FeignRequestInjector spanInjector = new FeignRequestInjector(); + private HttpSpanInjector spanInjector; TraceFeignClient(BeanFactory beanFactory) { this.beanFactory = beanFactory; @@ -70,7 +71,7 @@ class TraceFeignClient implements Client { } try { AtomicReference feignRequest = new AtomicReference<>(request); - this.spanInjector.inject(span, feignRequest); + spanInjector().inject(span, new FeignRequestTextMap(feignRequest)); span.logEvent(Span.CLIENT_SEND); addRequestTags(request); Request modifiedRequest = feignRequest.get(); @@ -102,17 +103,24 @@ class TraceFeignClient implements Client { */ private void addRequestTags(Request request) { URI uri = URI.create(request.url()); - getKeysInjector().addRequestTags(uri.toString(), uri.getHost(), uri.getPath(), + keysInjector().addRequestTags(uri.toString(), uri.getHost(), uri.getPath(), request.method(), request.headers()); } - private HttpTraceKeysInjector getKeysInjector() { + private HttpTraceKeysInjector keysInjector() { if (this.keysInjector == null) { this.keysInjector = this.beanFactory.getBean(HttpTraceKeysInjector.class); } return this.keysInjector; } + private HttpSpanInjector spanInjector() { + if (this.spanInjector == null) { + this.spanInjector = this.beanFactory.getBean(HttpSpanInjector.class); + } + return this.spanInjector; + } + private void closeSpan(Span span) { if (span != null) { if (log.isDebugEnabled()) { diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/ApacheHttpClientRibbonRequestCustomizer.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/ApacheHttpClientRibbonRequestCustomizer.java index 0715052d0..7ab591946 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/ApacheHttpClientRibbonRequestCustomizer.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/ApacheHttpClientRibbonRequestCustomizer.java @@ -16,7 +16,13 @@ package org.springframework.cloud.sleuth.instrument.zuul; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import org.apache.http.Header; import org.apache.http.client.methods.RequestBuilder; +import org.springframework.cloud.sleuth.SpanTextMap; import org.springframework.cloud.sleuth.Tracer; /** @@ -37,9 +43,20 @@ class ApacheHttpClientRibbonRequestCustomizer extends SpanInjectingRibbonRequest } @Override - void setHeader(RequestBuilder builder, String name, String value) { - if (value != null) { - builder.setHeader(name, value); - } + protected SpanTextMap toSpanTextMap(final RequestBuilder context) { + return new SpanTextMap() { + @Override public Iterator> iterator() { + Map map = new HashMap<>(); + for (Header header : context.build().getAllHeaders()) { + map.put(header.getName(), header.getValue()); + } + return map.entrySet().iterator(); + } + + @Override public void put(String key, String value) { + context.addHeader(key, value); + } + }; } + } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/OkHttpClientRibbonRequestCustomizer.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/OkHttpClientRibbonRequestCustomizer.java index 01d11adb8..c6948533d 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/OkHttpClientRibbonRequestCustomizer.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/OkHttpClientRibbonRequestCustomizer.java @@ -16,6 +16,12 @@ package org.springframework.cloud.sleuth.instrument.zuul; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.springframework.cloud.sleuth.SpanTextMap; import org.springframework.cloud.sleuth.Tracer; import okhttp3.Request; @@ -38,9 +44,21 @@ class OkHttpClientRibbonRequestCustomizer extends SpanInjectingRibbonRequestCust } @Override - void setHeader(Request.Builder builder, String name, String value) { - if (value != null) { - builder.addHeader(name, value); - } + protected SpanTextMap toSpanTextMap(final Request.Builder context) { + return new SpanTextMap() { + @Override public Iterator> iterator() { + Map map = new HashMap<>(); + for (Map.Entry> entry : context.build().headers().toMultimap().entrySet()) { + if (!entry.getValue().isEmpty()) { + map.put(entry.getKey(), entry.getValue().get(0)); + } + } + return map.entrySet().iterator(); + } + + @Override public void put(String key, String value) { + context.addHeader(key, value); + } + }; } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/RequestContextInjector.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/RequestContextInjector.java deleted file mode 100644 index ca730870e..000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/RequestContextInjector.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * 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 com.netflix.zuul.context.RequestContext; - -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.SpanInjector; -import org.springframework.util.StringUtils; - -/** - * Span injector that injects tracing info to {@link RequestContext} - * - * @author Marcin Grzejszczak - * @since 1.0.0 - */ -class RequestContextInjector implements SpanInjector { - - @Override - public void inject(Span span, RequestContext carrier) { - Map requestHeaders = carrier.getZuulRequestHeaders(); - if (span == null) { - setHeader(requestHeaders, Span.SAMPLED_NAME, Span.SPAN_NOT_SAMPLED); - 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()); - setHeader(requestHeaders, Span.SAMPLED_NAME, span.isExportable() ? - Span.SPAN_SAMPLED : Span.SPAN_NOT_SAMPLED); - 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 request, String name, String value) { - if (StringUtils.hasText(value) && !request.containsKey(name)) { - request.put(name, value); - } - } - - public void setHeader(Map request, String name, Long value) { - if (value != null) { - setHeader(request, name, Span.idToHex(value)); - } - } -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/RequestContextTextMap.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/RequestContextTextMap.java new file mode 100644 index 000000000..43110d251 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/RequestContextTextMap.java @@ -0,0 +1,49 @@ +/* + * 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.Iterator; +import java.util.Map; + +import com.netflix.zuul.context.RequestContext; + +import org.springframework.cloud.sleuth.SpanTextMap; + +/** + * A {@link SpanTextMap} abstraction over {@link RequestContext} + * + * @author Marcin Grzejszczak + * @since 1.2.0 + */ +class RequestContextTextMap implements SpanTextMap { + + private final RequestContext carrier; + + RequestContextTextMap(RequestContext carrier) { + this.carrier = carrier; + } + + @Override + public Iterator> iterator() { + return this.carrier.getZuulRequestHeaders().entrySet().iterator(); + } + + @Override + public void put(String key, String value) { + this.carrier.getZuulRequestHeaders().put(key, value); + } +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/RestClientRibbonRequestCustomizer.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/RestClientRibbonRequestCustomizer.java index d3fca982e..3b882e255 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/RestClientRibbonRequestCustomizer.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/RestClientRibbonRequestCustomizer.java @@ -16,8 +16,12 @@ package org.springframework.cloud.sleuth.instrument.zuul; +import java.util.Iterator; +import java.util.Map; + import com.netflix.client.http.HttpRequest; +import org.springframework.cloud.sleuth.SpanTextMap; import org.springframework.cloud.sleuth.Tracer; /** @@ -38,9 +42,16 @@ class RestClientRibbonRequestCustomizer extends SpanInjectingRibbonRequestCustom } @Override - void setHeader(HttpRequest.Builder builder, String name, String value) { - if (value != null) { - builder.header(name, value); - } + protected SpanTextMap toSpanTextMap(final HttpRequest.Builder context) { + context.build().getHttpHeaders(); + return new SpanTextMap() { + @Override public Iterator> iterator() { + return context.build().getHttpHeaders().getAllHeaders().iterator(); + } + + @Override public void put(String key, String value) { + context.header(key, value); + } + }; } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/SpanInjectingRibbonRequestCustomizer.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/SpanInjectingRibbonRequestCustomizer.java index db7fbf1ce..90575c1c2 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/SpanInjectingRibbonRequestCustomizer.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/SpanInjectingRibbonRequestCustomizer.java @@ -23,6 +23,7 @@ import org.apache.commons.logging.LogFactory; import org.springframework.cloud.netflix.ribbon.support.RibbonRequestCustomizer; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.SpanInjector; +import org.springframework.cloud.sleuth.SpanTextMap; import org.springframework.cloud.sleuth.Tracer; /** @@ -30,14 +31,14 @@ import org.springframework.cloud.sleuth.Tracer; * into their respective context. The only difference is how those contexts set the headers. * In order to add a new implementation of the {@link RibbonRequestCustomizer} it's * necessary only to provide the {@link RibbonRequestCustomizer#accepts(Class)} method - * with the context class name and {@link SpanInjectingRibbonRequestCustomizer#setHeader(Object, String, String)} + * with the context class name and {@link SpanInjectingRibbonRequestCustomizer#toSpanTextMap(Object)} * to tell Sleuth how to set a header using the particular library. * * @author Marcin Grzejszczak * @since 1.1.0 */ abstract class SpanInjectingRibbonRequestCustomizer implements RibbonRequestCustomizer, - SpanInjector { + SpanInjector { private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); @@ -50,30 +51,30 @@ abstract class SpanInjectingRibbonRequestCustomizer implements RibbonRequestC @Override public void customize(T context) { Span span = getCurrentSpan(); - inject(span, context); + inject(span, toSpanTextMap(context)); span.logEvent(Span.CLIENT_SEND); if (log.isDebugEnabled()) { log.debug("Span in the RibbonRequestCustomizer is" + span); } } + + protected abstract SpanTextMap toSpanTextMap(T context); @Override - public void inject(Span span, T carrier) { + public void inject(Span span, SpanTextMap carrier) { if (span == null) { - setHeader(carrier, Span.SAMPLED_NAME, Span.SPAN_NOT_SAMPLED); + carrier.put(Span.SAMPLED_NAME, Span.SPAN_NOT_SAMPLED); return; } - setHeader(carrier, Span.SAMPLED_NAME, span.isExportable() ? + carrier.put(Span.SAMPLED_NAME, span.isExportable() ? Span.SPAN_SAMPLED : Span.SPAN_NOT_SAMPLED); - 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()); + carrier.put(Span.TRACE_ID_NAME, Span.idToHex(span.getTraceId())); + carrier.put(Span.SPAN_ID_NAME, Span.idToHex(span.getSpanId())); + carrier.put(Span.SPAN_NAME_NAME, span.getName()); if (getParentId(span) != null) { - setHeader(carrier, Span.PARENT_ID_NAME, - Span.idToHex(getParentId(span))); + carrier.put(Span.PARENT_ID_NAME, Span.idToHex(getParentId(span))); } - setHeader(carrier, Span.PROCESS_ID_NAME, - span.getProcessId()); + carrier.put(Span.PROCESS_ID_NAME, span.getProcessId()); } private Long getParentId(Span span) { @@ -84,6 +85,4 @@ abstract class SpanInjectingRibbonRequestCustomizer implements RibbonRequestC private Span getCurrentSpan() { return this.tracer.getCurrentSpan(); } - - abstract void setHeader(T builder, String name, String value); } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TracePreZuulFilter.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TracePreZuulFilter.java index 6d610fed7..9c9f142cb 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TracePreZuulFilter.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TracePreZuulFilter.java @@ -16,21 +16,22 @@ package org.springframework.cloud.sleuth.instrument.zuul; +import java.lang.invoke.MethodHandles; +import java.net.URI; + import com.netflix.zuul.ExecutionStatus; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.ZuulFilterResult; import com.netflix.zuul.context.RequestContext; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.cloud.sleuth.HttpSpanInjector; import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.SpanInjector; import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.instrument.web.HttpTraceKeysInjector; import org.springframework.cloud.sleuth.instrument.web.TraceRequestAttributes; -import java.lang.invoke.MethodHandles; -import java.net.URI; - /** * A pre request {@link ZuulFilter} that sets tracing related headers on the request * from the current span. We're doing so to ensure tracing propagates to the next hop. @@ -45,10 +46,10 @@ public class TracePreZuulFilter extends ZuulFilter { private static final String ZUUL_COMPONENT = "zuul"; private final Tracer tracer; - private final SpanInjector spanInjector; + private final HttpSpanInjector spanInjector; private final HttpTraceKeysInjector httpTraceKeysInjector; - public TracePreZuulFilter(Tracer tracer, SpanInjector spanInjector, + public TracePreZuulFilter(Tracer tracer, HttpSpanInjector spanInjector, HttpTraceKeysInjector httpTraceKeysInjector) { this.tracer = tracer; this.spanInjector = spanInjector; @@ -76,7 +77,7 @@ public class TracePreZuulFilter extends ZuulFilter { markRequestAsHandled(ctx); Span newSpan = this.tracer.createSpan(span.getName(), span); newSpan.tag(Span.SPAN_LOCAL_COMPONENT_TAG_NAME, ZUUL_COMPONENT); - this.spanInjector.inject(newSpan, ctx); + this.spanInjector.inject(newSpan, new RequestContextTextMap(ctx)); this.httpTraceKeysInjector.addRequestTags(newSpan, URI.create(ctx.getRequest().getRequestURI()), ctx.getRequest().getMethod()); if (log.isDebugEnabled()) { log.debug("New Zuul Span is " + newSpan + ""); diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulAutoConfiguration.java index fbe34bebf..5b53c7322 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulAutoConfiguration.java @@ -15,6 +15,9 @@ */ package org.springframework.cloud.sleuth.instrument.zuul; +import com.netflix.client.http.HttpRequest; +import com.netflix.zuul.ZuulFilter; + import org.apache.http.client.methods.RequestBuilder; import org.springframework.beans.factory.BeanFactory; import org.springframework.boot.autoconfigure.AutoConfigureAfter; @@ -24,7 +27,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.cloud.netflix.ribbon.support.RibbonRequestCustomizer; -import org.springframework.cloud.sleuth.SpanInjector; +import org.springframework.cloud.sleuth.HttpSpanInjector; import org.springframework.cloud.sleuth.TraceKeys; import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.instrument.web.HttpTraceKeysInjector; @@ -33,9 +36,6 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import okhttp3.Request; -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} @@ -55,7 +55,7 @@ public class TraceZuulAutoConfiguration { @Bean @ConditionalOnMissingBean public TracePreZuulFilter tracePreZuulFilter(Tracer tracer, - SpanInjector spanInjector, HttpTraceKeysInjector httpTraceKeysInjector) { + HttpSpanInjector spanInjector, HttpTraceKeysInjector httpTraceKeysInjector) { return new TracePreZuulFilter(tracer, spanInjector, httpTraceKeysInjector); } @@ -65,11 +65,6 @@ public class TraceZuulAutoConfiguration { return new TracePostZuulFilter(tracer, traceKeys); } - @Bean - public SpanInjector requestContextSpanInjector() { - return new RequestContextInjector(); - } - @Bean public TraceRibbonCommandFactoryBeanPostProcessor traceRibbonCommandFactoryBeanPostProcessor(BeanFactory beanFactory) { return new TraceRibbonCommandFactoryBeanPostProcessor(beanFactory); diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/util/TextMapUtil.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/util/TextMapUtil.java new file mode 100644 index 000000000..5ecaa239c --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/util/TextMapUtil.java @@ -0,0 +1,28 @@ +package org.springframework.cloud.sleuth.util; + +import java.util.Comparator; +import java.util.Map; +import java.util.TreeMap; + +/** + * Utility class related to {@link org.springframework.cloud.sleuth.SpanTextMap} + * + * @author Marcin Grzejszczak + * @since 1.2.0 + */ +public final class TextMapUtil { + + private TextMapUtil() {} + + public static Map asMap(Iterable> iterable) { + Map map = new TreeMap<>(new Comparator() { + @Override public int compare(String o1, String o2) { + return o1.toLowerCase().compareTo(o2.toLowerCase()); + } + }); + for (Map.Entry entry : iterable) { + map.put(entry.getKey(), entry.getValue()); + } + return map; + } +} diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue410/Issue410Tests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue410/Issue410Tests.java index dd3c17a08..1e67d3061 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue410/Issue410Tests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue410/Issue410Tests.java @@ -16,6 +16,7 @@ package org.springframework.cloud.sleuth.instrument.async.issues.issue410; +import java.lang.invoke.MethodHandles; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; @@ -63,6 +64,8 @@ import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; @TestPropertySource(properties = {"ribbon.eureka.enabled=false", "feign.hystrix.enabled=false", "server.port=0"}) public class Issue410Tests { + private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); + @Autowired Environment environment; @Autowired Tracer tracer; @Autowired AsyncTask asyncTask; @@ -71,6 +74,7 @@ public class Issue410Tests { @Test public void should_pass_tracing_info_for_tasks_running_without_a_pool() { Span span = this.tracer.createSpan("foo"); + log.info("Starting test"); try { String response = this.restTemplate.getForObject("http://localhost:" + port() + "/without_pool", String.class); @@ -87,6 +91,7 @@ public class Issue410Tests { @Test public void should_pass_tracing_info_for_tasks_running_with_a_pool() { Span span = this.tracer.createSpan("foo"); + log.info("Starting test"); try { String response = this.restTemplate.getForObject("http://localhost:" + port() + "/with_pool", String.class); @@ -106,6 +111,7 @@ public class Issue410Tests { @Test public void should_pass_tracing_info_for_completable_futures_with_executor() { Span span = this.tracer.createSpan("foo"); + log.info("Starting test"); try { String response = this.restTemplate.getForObject("http://localhost:" + port() + "/completable", String.class); @@ -125,6 +131,7 @@ public class Issue410Tests { @Test public void should_pass_tracing_info_for_completable_futures_with_task_scheduler() { Span span = this.tracer.createSpan("foo"); + log.info("Starting test"); try { String response = this.restTemplate.getForObject("http://localhost:" + port() + "/taskScheduler", String.class); diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingSpanExtractorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingSpanExtractorTests.java index acd296951..1122e96b8 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingSpanExtractorTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingSpanExtractorTests.java @@ -21,8 +21,6 @@ import java.util.Map; import java.util.Random; import org.junit.Test; -import org.springframework.cloud.sleuth.Span; -import org.springframework.messaging.Message; import org.springframework.messaging.MessageHeaders; import org.springframework.messaging.support.MessageBuilder; import org.springframework.util.StringUtils; @@ -32,24 +30,23 @@ import static org.assertj.core.api.BDDAssertions.then; import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; public class MessagingSpanExtractorTests { - MessagingSpanExtractor extractor = new MessagingSpanExtractor(new Random()); + HeaderBasedMessagingExtractor extractor = new HeaderBasedMessagingExtractor(); @Test public void should_return_null_if_trace_or_span_is_missing() { - Message message = MessageBuilder.createMessage("", headers()); - then(this.extractor.joinTrace(message)).isNull(); + then(this.extractor.joinTrace( + new MessagingTextMap(MessageBuilder.withPayload("")))).isNull(); - message = MessageBuilder.createMessage("", headers("trace")); - then(this.extractor.joinTrace(message)).isNull(); + then(this.extractor.joinTrace( + new MessagingTextMap(MessageBuilder.withPayload("").copyHeaders(headers("trace"))))).isNull(); } @Test public void should_set_random_traceid_if_header_value_is_invalid() { - Message message = MessageBuilder.createMessage("", - headers("invalid", randomId())); - try { - this.extractor.joinTrace(message); + this.extractor.joinTrace( + new MessagingTextMap(MessageBuilder.withPayload("") + .copyHeaders(headers("invalid", randomId())))); fail("should throw an exception"); } catch (IllegalArgumentException e) { then(e).hasMessageContaining("Malformed id"); @@ -58,11 +55,10 @@ public class MessagingSpanExtractorTests { @Test public void should_set_random_spanid_if_header_value_is_invalid() { - Message message = MessageBuilder.createMessage("", - headers(randomId(), "invalid")); - try { - this.extractor.joinTrace(message); + this.extractor.joinTrace( + new MessagingTextMap(MessageBuilder.withPayload("") + .copyHeaders(headers(randomId(), "invalid")))); fail("should throw an exception"); } catch (IllegalArgumentException e) { then(e).hasMessageContaining("Malformed id"); @@ -71,21 +67,16 @@ public class MessagingSpanExtractorTests { @Test public void should_not_throw_exception_if_parent_id_is_invalid() { - Message message = MessageBuilder.createMessage("", - headers(randomId(), randomId(), "invalid")); - try { - this.extractor.joinTrace(message); + this.extractor.joinTrace( + new MessagingTextMap(MessageBuilder.withPayload("") + .copyHeaders(headers(randomId(), randomId(), "invalid")))); fail("should throw an exception"); } catch (IllegalArgumentException e) { then(e).hasMessageContaining("Malformed id"); } } - private MessageHeaders headers() { - return headers(null, null, null); - } - private MessageHeaders headers(String traceId) { return headers(traceId, null, null); } @@ -97,13 +88,13 @@ public class MessagingSpanExtractorTests { private MessageHeaders headers(String traceId, String spanId, String parentId) { Map map = new HashMap<>(); if (StringUtils.hasText(traceId)) { - map.put(Span.TRACE_ID_NAME, traceId); + map.put(TraceMessageHeaders.TRACE_ID_NAME, traceId); } if (StringUtils.hasText(spanId)) { - map.put(Span.SPAN_ID_NAME, spanId); + map.put(TraceMessageHeaders.SPAN_ID_NAME, spanId); } if (StringUtils.hasText(parentId)) { - map.put(Span.PARENT_ID_NAME, parentId); + map.put(TraceMessageHeaders.PARENT_ID_NAME, parentId); } return new MessageHeaders(map); } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingSpanInjectorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingSpanInjectorTests.java index 1be79222a..8fd2a8bec 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingSpanInjectorTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingSpanInjectorTests.java @@ -16,9 +16,6 @@ package org.springframework.cloud.sleuth.instrument.messaging; -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.assertThat; - import org.junit.Test; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.TraceKeys; @@ -29,15 +26,16 @@ import org.springframework.messaging.support.MessageBuilder; import org.springframework.messaging.support.MessageHeaderAccessor; import org.springframework.messaging.support.NativeMessageHeaderAccessor; +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.assertThat; + /** * @author Dave Syer * */ public class MessagingSpanInjectorTests { - private TraceKeys traceKeys = new TraceKeys(); - private MessagingSpanInjector messagingSpanInjector = new MessagingSpanInjector( - this.traceKeys); + private HeaderBasedMessagingInjector spanInjector = new HeaderBasedMessagingInjector(new TraceKeys()); @Test public void spanHeadersAdded() { @@ -45,9 +43,9 @@ public class MessagingSpanInjectorTests { Message message = new GenericMessage<>("Hello World"); MessageBuilder messageBuilder = MessageBuilder.fromMessage(message); - this.messagingSpanInjector.inject(span, messageBuilder); + this.spanInjector.inject(span, new MessagingTextMap(messageBuilder)); - assertThat(messageBuilder.build().getHeaders()).containsKey(Span.SPAN_ID_NAME); + assertThat(messageBuilder.build().getHeaders()).containsKey(TraceMessageHeaders.SPAN_ID_NAME); } @Test @@ -55,9 +53,9 @@ public class MessagingSpanInjectorTests { Span span = spanWithStringPayloadType(); MessageBuilder messageBuilder = messageWithIntegerPayloadType(); - this.messagingSpanInjector.inject(span, messageBuilder); + this.spanInjector.inject(span, new MessagingTextMap(messageBuilder)); - assertThat(messageBuilder.build().getHeaders()).containsKeys(Span.SPAN_ID_NAME, + assertThat(messageBuilder.build().getHeaders()).containsKeys(TraceMessageHeaders.SPAN_ID_NAME, "message/payload-type"); assertThat(span).hasATag("message/payload-type", "java.lang.String"); } @@ -83,14 +81,14 @@ public class MessagingSpanInjectorTests { MessageBuilder messageBuilder = MessageBuilder .fromMessage(messageToBuild); - this.messagingSpanInjector.inject(span, messageBuilder); + this.spanInjector.inject(span, new MessagingTextMap(messageBuilder)); Message message = messageBuilder.build(); assertThat(message.getHeaders()) .containsKey(NativeMessageHeaderAccessor.NATIVE_HEADERS); MessageHeaderAccessor natives = NativeMessageHeaderAccessor .getMutableAccessor(message); - assertThat(natives.getMessageHeaders()).containsKey(Span.SPAN_ID_NAME); + assertThat(natives.getMessageHeaders()).containsKey(TraceMessageHeaders.SPAN_ID_NAME); } } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/TraceChannelInterceptorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/TraceChannelInterceptorTests.java index f01752ce3..35cfdc2b6 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/TraceChannelInterceptorTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/TraceChannelInterceptorTests.java @@ -16,10 +16,6 @@ package org.springframework.cloud.sleuth.instrument.messaging; -import static org.assertj.core.api.BDDAssertions.then; -import static org.junit.Assert.assertNotNull; -import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; - import java.util.HashMap; import java.util.Map; @@ -41,6 +37,7 @@ import org.springframework.cloud.sleuth.instrument.messaging.TraceChannelInterce import org.springframework.cloud.sleuth.sampler.AlwaysSampler; import org.springframework.cloud.sleuth.trace.TestSpanContextHolder; import org.springframework.cloud.sleuth.util.ArrayListSpanAccumulator; +import org.springframework.cloud.sleuth.util.ExceptionUtils; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.integration.channel.DirectChannel; @@ -54,6 +51,10 @@ import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import static org.assertj.core.api.BDDAssertions.then; +import static org.junit.Assert.assertNotNull; +import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; + /** * @author Dave Syer */ @@ -102,6 +103,7 @@ public class TraceChannelInterceptorTests implements MessageHandler { @After public void close() { + then(ExceptionUtils.getLastException()).isNull(); TestSpanContextHolder.removeCurrentSpan(); this.tracedChannel.unsubscribe(this); this.ignoredChannel.unsubscribe(this); @@ -111,10 +113,10 @@ public class TraceChannelInterceptorTests implements MessageHandler { @Test public void nonExportableSpanCreation() { this.tracedChannel.send(MessageBuilder.withPayload("hi") - .setHeader(Span.SAMPLED_NAME, Span.SPAN_NOT_SAMPLED).build()); + .setHeader(TraceMessageHeaders.SAMPLED_NAME, Span.SPAN_NOT_SAMPLED).build()); assertNotNull("message was null", this.message); - String spanId = this.message.getHeaders().get(Span.SPAN_ID_NAME, String.class); + String spanId = this.message.getHeaders().get(TraceMessageHeaders.SPAN_ID_NAME, String.class); then(spanId).isNotNull(); then(TestSpanContextHolder.getCurrentSpan()).isNull(); then(this.span.isExportable()).isFalse(); @@ -123,7 +125,7 @@ public class TraceChannelInterceptorTests implements MessageHandler { @Test public void messageHeadersStillMutable() { this.tracedChannel.send(MessageBuilder.withPayload("hi") - .setHeader(Span.SAMPLED_NAME, Span.SPAN_NOT_SAMPLED).build()); + .setHeader(TraceMessageHeaders.SAMPLED_NAME, Span.SPAN_NOT_SAMPLED).build()); assertNotNull("message was null", this.message); MessageHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(this.message, MessageHeaderAccessor.class); assertNotNull("Message header accessor should be still available", accessor); @@ -131,64 +133,18 @@ public class TraceChannelInterceptorTests implements MessageHandler { @Test public void parentSpanIncluded() { - this.tracedChannel.send(MessageBuilder.withPayload("hi") - .setHeader(Span.TRACE_ID_NAME, Span.idToHex(10L)) - .setHeader(Span.SPAN_ID_NAME, Span.idToHex(20L)).build()); - then(this.message).isNotNull(); - - String spanId = this.message.getHeaders().get(Span.SPAN_ID_NAME, String.class); - then(spanId).isNotNull(); - long traceId = Span - .hexToId(this.message.getHeaders().get(Span.TRACE_ID_NAME, String.class)); - then(traceId).isEqualTo(10L); - then(spanId).isNotEqualTo(20L); - then(this.accumulator.getSpans()).hasSize(1); - } - - // #332 - @Test - public void shouldSendNewAndOldHeadersWhenNewHeadersWerePassed() { this.tracedChannel.send(MessageBuilder.withPayload("hi") .setHeader(TraceMessageHeaders.TRACE_ID_NAME, Span.idToHex(10L)) .setHeader(TraceMessageHeaders.SPAN_ID_NAME, Span.idToHex(20L)).build()); then(this.message).isNotNull(); - String newSpanId = thenNewSpanIdEqualsOldSpanId(); - thenNewTraceIdEqualsOldTraceId(); - then(newSpanId).isNotEqualTo(20L); - then(this.accumulator.getSpans()).hasSize(1); - } - - private String thenNewSpanIdEqualsOldSpanId() { - String newSpanId = this.message.getHeaders().get(TraceMessageHeaders.SPAN_ID_NAME, - String.class); - then(newSpanId).isNotNull(); - String oldSpanId = this.message.getHeaders().get(Span.SPAN_ID_NAME, String.class); - then(oldSpanId).isEqualTo(newSpanId); - return newSpanId; - } - - // #332 - @Test - public void shouldSendNewAndOldHeadersWhenOldHeadersWerePassed() { - this.tracedChannel.send(MessageBuilder.withPayload("hi") - .setHeader(Span.TRACE_ID_NAME, Span.idToHex(10L)) - .setHeader(Span.SPAN_ID_NAME, Span.idToHex(20L)).build()); - then(this.message).isNotNull(); - - String newSpanId = thenNewSpanIdEqualsOldSpanId(); - thenNewTraceIdEqualsOldTraceId(); - then(newSpanId).isNotEqualTo(20L); - then(this.accumulator.getSpans()).hasSize(1); - } - - private void thenNewTraceIdEqualsOldTraceId() { - long traceId = Span.hexToId(this.message.getHeaders() - .get(TraceMessageHeaders.TRACE_ID_NAME, String.class)); + String spanId = this.message.getHeaders().get(TraceMessageHeaders.SPAN_ID_NAME, String.class); + then(spanId).isNotNull(); + long traceId = Span + .hexToId(this.message.getHeaders().get(TraceMessageHeaders.TRACE_ID_NAME, String.class)); then(traceId).isEqualTo(10L); - long oldTraceId = Span - .hexToId(this.message.getHeaders().get(Span.TRACE_ID_NAME, String.class)); - then(oldTraceId).isEqualTo(traceId); + then(spanId).isNotEqualTo(20L); + then(this.accumulator.getSpans()).hasSize(1); } @Test @@ -196,10 +152,10 @@ public class TraceChannelInterceptorTests implements MessageHandler { this.tracedChannel.send(MessageBuilder.withPayload("hi").build()); then(this.message).isNotNull(); - String spanId = this.message.getHeaders().get(Span.SPAN_ID_NAME, String.class); + String spanId = this.message.getHeaders().get(TraceMessageHeaders.SPAN_ID_NAME, String.class); then(spanId).isNotNull(); - String traceId = this.message.getHeaders().get(Span.TRACE_ID_NAME, String.class); + String traceId = this.message.getHeaders().get(TraceMessageHeaders.TRACE_ID_NAME, String.class); then(traceId).isNotNull(); then(TestSpanContextHolder.getCurrentSpan()).isNull(); } @@ -208,16 +164,18 @@ public class TraceChannelInterceptorTests implements MessageHandler { public void shouldLogClientReceivedClientSentEventWhenTheMessageIsSentAndReceived() { this.tracedChannel.send(MessageBuilder.withPayload("hi").build()); - then(this.span.logs()).extracting("event").contains(Span.CLIENT_SEND, + then(this.accumulator.getSpans()).hasSize(1); + then(this.accumulator.getSpans().get(0).logs()).extracting("event").contains(Span.CLIENT_SEND, Span.CLIENT_RECV); } @Test public void shouldLogServerReceivedServerSentEventWhenTheMessageIsPropagatedToTheNextListener() { this.tracedChannel.send(MessageBuilder.withPayload("hi") - .setHeader("X-Message-Sent", true).build()); + .setHeader(TraceMessageHeaders.MESSAGE_SENT_FROM_CLIENT, true).build()); - then(this.span.logs()).extracting("event").contains(Span.SERVER_RECV, + then(this.accumulator.getSpans()).hasSize(1); + then(this.accumulator.getSpans().get(0).logs()).extracting("event").contains(Span.SERVER_RECV, Span.SERVER_SEND); } @@ -228,10 +186,10 @@ public class TraceChannelInterceptorTests implements MessageHandler { this.tracer.close(span); then(this.message).isNotNull(); - String spanId = this.message.getHeaders().get(Span.SPAN_ID_NAME, String.class); + String spanId = this.message.getHeaders().get(TraceMessageHeaders.SPAN_ID_NAME, String.class); then(spanId).isNotNull(); - String traceId = this.message.getHeaders().get(Span.TRACE_ID_NAME, String.class); + String traceId = this.message.getHeaders().get(TraceMessageHeaders.TRACE_ID_NAME, String.class); then(traceId).isNotNull(); then(TestSpanContextHolder.getCurrentSpan()).isNull(); } @@ -245,10 +203,10 @@ public class TraceChannelInterceptorTests implements MessageHandler { this.tracer.close(span); then(this.message).isNotNull(); - String spanId = this.message.getHeaders().get(Span.SPAN_ID_NAME, String.class); + String spanId = this.message.getHeaders().get(TraceMessageHeaders.SPAN_ID_NAME, String.class); then(spanId).isNotNull(); - String traceId = this.message.getHeaders().get(Span.TRACE_ID_NAME, String.class); + String traceId = this.message.getHeaders().get(TraceMessageHeaders.TRACE_ID_NAME, String.class); then(traceId).isNotNull(); then(TestSpanContextHolder.getCurrentSpan()).isNull(); } @@ -280,10 +238,10 @@ public class TraceChannelInterceptorTests implements MessageHandler { this.ignoredChannel.send(MessageBuilder.withPayload("hi").build()); then(this.message).isNotNull(); - String spanId = this.message.getHeaders().get(Span.SPAN_ID_NAME, String.class); + String spanId = this.message.getHeaders().get(TraceMessageHeaders.SPAN_ID_NAME, String.class); then(spanId).isNull(); - String traceId = this.message.getHeaders().get(Span.TRACE_ID_NAME, String.class); + String traceId = this.message.getHeaders().get(TraceMessageHeaders.TRACE_ID_NAME, String.class); then(traceId).isNull(); then(this.accumulator.getSpans()).isEmpty(); @@ -295,8 +253,8 @@ public class TraceChannelInterceptorTests implements MessageHandler { String hex128Bits = "463ac35c9f6413ad48485a3953bb6124"; String lower64Bits = "48485a3953bb6124"; this.tracedChannel.send(MessageBuilder.withPayload("hi") - .setHeader(Span.TRACE_ID_NAME, hex128Bits) - .setHeader(Span.SPAN_ID_NAME, Span.idToHex(20L)).build()); + .setHeader(TraceMessageHeaders.TRACE_ID_NAME, hex128Bits) + .setHeader(TraceMessageHeaders.SPAN_ID_NAME, Span.idToHex(20L)).build()); then(this.message).isNotNull(); long traceId = Span.hexToId(this.message.getHeaders() diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/TraceContextPropagationChannelInterceptorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/TraceContextPropagationChannelInterceptorTests.java index 5461d7283..357e4a115 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/TraceContextPropagationChannelInterceptorTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/TraceContextPropagationChannelInterceptorTests.java @@ -77,15 +77,15 @@ public class TraceContextPropagationChannelInterceptorTests { assertNotNull("message was null", message); Long spanId = Span - .hexToId(message.getHeaders().get(Span.SPAN_ID_NAME, String.class)); + .hexToId(message.getHeaders().get(TraceMessageHeaders.SPAN_ID_NAME, String.class)); assertNotEquals("spanId was equal to parent's id", expectedSpanId, spanId); long traceId = Span - .hexToId(message.getHeaders().get(Span.TRACE_ID_NAME, String.class)); + .hexToId(message.getHeaders().get(TraceMessageHeaders.TRACE_ID_NAME, String.class)); assertNotNull("traceId was null", traceId); Long parentId = Span - .hexToId(message.getHeaders().get(Span.PARENT_ID_NAME, String.class)); + .hexToId(message.getHeaders().get(TraceMessageHeaders.PARENT_ID_NAME, String.class)); assertEquals("parentId was not equal to parent's id", expectedSpanId, parentId); } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/HttpServletRequestExtractorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/HttpServletRequestExtractorTests.java index 6cc85427f..66e118e1f 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/HttpServletRequestExtractorTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/HttpServletRequestExtractorTests.java @@ -16,9 +16,11 @@ package org.springframework.cloud.sleuth.instrument.web; -import java.util.Random; -import java.util.regex.Pattern; import javax.servlet.http.HttpServletRequest; +import java.util.Arrays; +import java.util.Random; +import java.util.Vector; +import java.util.regex.Pattern; import org.junit.Before; import org.junit.Test; @@ -34,7 +36,7 @@ import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; public class HttpServletRequestExtractorTests { @Mock HttpServletRequest request; - HttpServletRequestExtractor extractor = new HttpServletRequestExtractor( + ZipkinHttpSpanExtractor extractor = new ZipkinHttpSpanExtractor( Pattern.compile("")); @Before @@ -45,7 +47,7 @@ public class HttpServletRequestExtractorTests { @Test public void should_return_null_if_there_is_no_trace_id() { - then(extractor.joinTrace(request)).isNull(); + then(extractor.joinTrace(new HttpServletRequestTextMap(this.request))).isNull(); } @Test @@ -53,7 +55,7 @@ public class HttpServletRequestExtractorTests { BDDMockito.given(this.request.getHeader(Span.TRACE_ID_NAME)) .willReturn("invalid"); - then(this.extractor.joinTrace(this.request)).isNull(); + then(this.extractor.joinTrace(new HttpServletRequestTextMap(this.request))).isNull(); } @Test @@ -63,7 +65,7 @@ public class HttpServletRequestExtractorTests { BDDMockito.given(this.request.getHeader(Span.SPAN_ID_NAME)) .willReturn("invalid"); - then(this.extractor.joinTrace(this.request)).isNull(); + then(this.extractor.joinTrace(new HttpServletRequestTextMap(this.request))).isNull(); } @Test @@ -75,7 +77,7 @@ public class HttpServletRequestExtractorTests { BDDMockito.given(this.request.getHeader(Span.PARENT_ID_NAME)) .willReturn("invalid"); - then(this.extractor.joinTrace(this.request)).isNull(); + then(this.extractor.joinTrace(new HttpServletRequestTextMap(this.request))).isNull(); } @Test @@ -83,12 +85,14 @@ public class HttpServletRequestExtractorTests { String hex128Bits = "463ac35c9f6413ad48485a3953bb6124"; String lower64Bits = "48485a3953bb6124"; + BDDMockito.given(this.request.getHeaderNames()) + .willReturn(new Vector<>(Arrays.asList(Span.TRACE_ID_NAME, Span.SPAN_ID_NAME)).elements()); BDDMockito.given(this.request.getHeader(Span.TRACE_ID_NAME)) .willReturn(hex128Bits); BDDMockito.given(this.request.getHeader(Span.SPAN_ID_NAME)) .willReturn(lower64Bits); - Span span = this.extractor.joinTrace(this.request); + Span span = this.extractor.joinTrace(new HttpServletRequestTextMap(this.request)); then(span.getTraceId()).isEqualTo(Span.hexToId(lower64Bits)); } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceCustomFilterResponseInjectorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceCustomFilterResponseInjectorTests.java index 72fa73a7c..36bd3f8c9 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceCustomFilterResponseInjectorTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceCustomFilterResponseInjectorTests.java @@ -22,8 +22,9 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.context.embedded.EmbeddedServletContainerInitializedEvent; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.sleuth.HttpSpanInjector; import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.SpanInjector; +import org.springframework.cloud.sleuth.SpanTextMap; import org.springframework.cloud.sleuth.Tracer; import org.springframework.context.ApplicationListener; import org.springframework.context.annotation.Bean; @@ -46,7 +47,10 @@ import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.net.URI; +import java.util.Collection; +import java.util.Enumeration; import java.util.HashMap; +import java.util.Iterator; import java.util.Map; import static org.assertj.core.api.BDDAssertions.then; @@ -83,8 +87,7 @@ public class TraceCustomFilterResponseInjectorTests { int port; // tag::configuration[] - @Bean - SpanInjector customHttpServletResponseSpanInjector() { + @Bean HttpSpanInjector customHttpServletResponseSpanInjector() { return new CustomHttpServletResponseSpanInjector(); } @@ -113,22 +116,22 @@ public class TraceCustomFilterResponseInjectorTests { } // tag::injector[] - static class CustomHttpServletResponseSpanInjector - implements SpanInjector { + static class CustomHttpServletResponseSpanInjector extends ZipkinHttpSpanInjector { @Override - public void inject(Span span, HttpServletResponse carrier) { - carrier.addHeader(Span.TRACE_ID_NAME, Span.idToHex(span.getTraceId())); - carrier.addHeader(Span.SPAN_ID_NAME, Span.idToHex(span.getSpanId())); + public void inject(Span span, SpanTextMap carrier) { + super.inject(span, carrier); + carrier.put(Span.TRACE_ID_NAME, Span.idToHex(span.getTraceId())); + carrier.put(Span.SPAN_ID_NAME, Span.idToHex(span.getSpanId())); } } static class HttpResponseInjectingTraceFilter extends GenericFilterBean { private final Tracer tracer; - private final SpanInjector spanInjector; + private final HttpSpanInjector spanInjector; - public HttpResponseInjectingTraceFilter(Tracer tracer, SpanInjector spanInjector) { + public HttpResponseInjectingTraceFilter(Tracer tracer, HttpSpanInjector spanInjector) { this.tracer = tracer; this.spanInjector = spanInjector; } @@ -137,9 +140,32 @@ public class TraceCustomFilterResponseInjectorTests { public void doFilter(ServletRequest request, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletResponse response = (HttpServletResponse) servletResponse; Span currentSpan = this.tracer.getCurrentSpan(); - this.spanInjector.inject(currentSpan, response); + this.spanInjector.inject(currentSpan, new HttpServletResponseTextMap(response)); filterChain.doFilter(request, response); } + + class HttpServletResponseTextMap implements SpanTextMap { + + private final HttpServletResponse delegate; + + HttpServletResponseTextMap(HttpServletResponse delegate) { + this.delegate = delegate; + } + + @Override + public Iterator> iterator() { + Map map = new HashMap<>(); + for (String header : this.delegate.getHeaderNames()) { + map.put(header, this.delegate.getHeader(header)); + } + return map.entrySet().iterator(); + } + + @Override + public void put(String key, String value) { + this.delegate.addHeader(key, value); + } + } } // end::injector[] diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterCustomExtractorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterCustomExtractorTests.java index 3fb83db62..96f2aadf7 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterCustomExtractorTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterCustomExtractorTests.java @@ -16,6 +16,11 @@ package org.springframework.cloud.sleuth.instrument.web; +import java.net.URI; +import java.util.HashMap; +import java.util.Map; + +import org.assertj.core.api.BDDAssertions; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -23,16 +28,19 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.context.embedded.EmbeddedServletContainerInitializedEvent; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.sleuth.HttpSpanExtractor; +import org.springframework.cloud.sleuth.HttpSpanInjector; import org.springframework.cloud.sleuth.Sampler; import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.SpanExtractor; import org.springframework.cloud.sleuth.SpanReporter; +import org.springframework.cloud.sleuth.SpanTextMap; +import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.sampler.AlwaysSampler; import org.springframework.cloud.sleuth.util.ArrayListSpanAccumulator; +import org.springframework.cloud.sleuth.util.TextMapUtil; import org.springframework.context.ApplicationListener; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Primary; import org.springframework.http.HttpHeaders; import org.springframework.http.RequestEntity; import org.springframework.http.ResponseEntity; @@ -43,15 +51,8 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; -import javax.servlet.http.HttpServletRequest; -import java.net.URI; -import java.util.HashMap; -import java.util.Map; -import java.util.Random; - import static com.jayway.awaitility.Awaitility.await; import static java.util.concurrent.TimeUnit.SECONDS; -import static org.assertj.core.api.BDDAssertions.then; import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; @RunWith(SpringJUnit4ClassRunner.class) @@ -59,11 +60,11 @@ import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @DirtiesContext public class TraceFilterCustomExtractorTests { - @Autowired Random random; @Autowired RestTemplate restTemplate; @Autowired Config config; @Autowired CustomRestController customRestController; @Autowired ArrayListSpanAccumulator accumulator; + @Autowired Tracer tracer; @Before public void setup() { @@ -73,23 +74,23 @@ public class TraceFilterCustomExtractorTests { @Test @SuppressWarnings("unchecked") public void should_create_a_valid_span_from_custom_headers() { - long spanId = this.random.nextLong(); - long traceId = this.random.nextLong(); - RequestEntity requestEntity = RequestEntity - .get(URI.create("http://localhost:" + this.config.port + "/headers")) - .header("correlationId", Span.idToHex(traceId)) - .header("mySpanId", Span.idToHex(spanId)).build(); - - @SuppressWarnings("rawtypes") - ResponseEntity responseEntity = this.restTemplate.exchange(requestEntity, - Map.class); + final Span newSpan = this.tracer.createSpan("new_span"); + ResponseEntity responseEntity = null; + try { + RequestEntity requestEntity = RequestEntity + .get(URI.create("http://localhost:" + this.config.port + "/headers")) + .build(); + responseEntity = this.restTemplate.exchange(requestEntity, Map.class); + } finally { + this.tracer.close(newSpan); + } await().atMost(5, SECONDS).until(() -> then(this.accumulator.getSpans().stream().filter( - span -> span.getSpanId() == spanId).findFirst().get()) - .hasTraceIdEqualTo(traceId)); - then(responseEntity.getBody()) - .containsEntry("correlationid", Span.idToHex(traceId)) - .containsEntry("myspanid", Span.idToHex(spanId)) + span -> span.getSpanId() == newSpan.getSpanId()).findFirst().get()) + .hasTraceIdEqualTo(newSpan.getTraceId())); + BDDAssertions.then(responseEntity.getBody()) + .containsEntry("correlationid", Span.idToHex(newSpan.getTraceId())) + .containsKey("myspanid") .as("input request headers"); } @@ -101,9 +102,13 @@ public class TraceFilterCustomExtractorTests { // tag::configuration[] @Bean - @Primary - SpanExtractor customHttpServletRequestSpanExtractor() { - return new CustomHttpServletRequestSpanExtractor(); + HttpSpanInjector customHttpSpanInjector() { + return new CustomHttpSpanInjector(); + } + + @Bean + HttpSpanExtractor customHttpSpanExtractor() { + return new CustomHttpSpanExtractor(); } // end::configuration[] @@ -134,24 +139,34 @@ public class TraceFilterCustomExtractorTests { } // tag::extractor[] - static class CustomHttpServletRequestSpanExtractor - implements SpanExtractor { + static class CustomHttpSpanExtractor implements HttpSpanExtractor { - @Override - public Span joinTrace(HttpServletRequest carrier) { - long traceId = Span.hexToId(carrier.getHeader("correlationId")); - long spanId = Span.hexToId(carrier.getHeader("mySpanId")); + @Override public Span joinTrace(SpanTextMap carrier) { + Map map = TextMapUtil.asMap(carrier); + long traceId = Span.hexToId(map.get("correlationid")); + long spanId = Span.hexToId(map.get("myspanid")); // extract all necessary headers Span.SpanBuilder builder = Span.builder().traceId(traceId).spanId(spanId); // build rest of the Span return builder.build(); } } + + static class CustomHttpSpanInjector implements HttpSpanInjector { + + @Override + public void inject(Span span, SpanTextMap carrier) { + carrier.put("correlationId", Span.idToHex(span.getTraceId())); + carrier.put("mySpanId", Span.idToHex(span.getSpanId())); + } + } // end::extractor[] @RestController static class CustomRestController { + @Autowired Tracer tracer; + @RequestMapping("/headers") public Map headers(@RequestHeader HttpHeaders headers) { Map map = new HashMap<>(); diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterMockChainIntegrationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterMockChainIntegrationTests.java index 5f29beea6..ce76d86da 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterMockChainIntegrationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterMockChainIntegrationTests.java @@ -73,7 +73,7 @@ public class TraceFilterMockChainIntegrationTests { @Test public void startsNewTrace() throws Exception { TraceFilter filter = new TraceFilter(this.tracer, this.traceKeys, new NoOpSpanReporter(), - new HttpServletRequestExtractor(Pattern.compile(TraceFilter.DEFAULT_SKIP_PATTERN)), + new ZipkinHttpSpanExtractor(Pattern.compile(TraceFilter.DEFAULT_SKIP_PATTERN)), keysInjector); filter.doFilter(this.request, this.response, this.filterChain); assertNull(TestSpanContextHolder.getCurrentSpan()); @@ -85,7 +85,7 @@ public class TraceFilterMockChainIntegrationTests { 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(), - new HttpServletRequestExtractor(Pattern.compile(TraceFilter.DEFAULT_SKIP_PATTERN)), + new ZipkinHttpSpanExtractor(Pattern.compile(TraceFilter.DEFAULT_SKIP_PATTERN)), keysInjector); filter.doFilter(this.request, this.response, this.filterChain); assertNull(TestSpanContextHolder.getCurrentSpan()); diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterTests.java index db7a6a213..b6f541a09 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterTests.java @@ -27,6 +27,7 @@ import org.junit.Before; import org.junit.Test; import org.mockito.Mock; import org.springframework.cloud.sleuth.DefaultSpanNamer; +import org.springframework.cloud.sleuth.HttpSpanExtractor; import org.springframework.cloud.sleuth.Sampler; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.SpanExtractor; @@ -66,7 +67,7 @@ public class TraceFilterTests { @Mock SpanLogger spanLogger; ArrayListSpanAccumulator spanReporter = new ArrayListSpanAccumulator(); - SpanExtractor spanExtractor = new HttpServletRequestExtractor(Pattern + HttpSpanExtractor spanExtractor = new ZipkinHttpSpanExtractor(Pattern .compile(TraceFilter.DEFAULT_SKIP_PATTERN)); private Tracer tracer; diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptorIntegrationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptorIntegrationTests.java index 73a363ecc..3853a0450 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptorIntegrationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptorIntegrationTests.java @@ -32,6 +32,7 @@ import org.springframework.cloud.sleuth.TraceKeys; import org.springframework.cloud.sleuth.assertions.ListOfSpans; import org.springframework.cloud.sleuth.assertions.SleuthAssertions; import org.springframework.cloud.sleuth.instrument.web.HttpTraceKeysInjector; +import org.springframework.cloud.sleuth.instrument.web.ZipkinHttpSpanInjector; import org.springframework.cloud.sleuth.log.NoOpSpanLogger; import org.springframework.cloud.sleuth.sampler.AlwaysSampler; import org.springframework.cloud.sleuth.trace.DefaultTracer; @@ -67,7 +68,7 @@ public class TraceRestTemplateInterceptorIntegrationTests { this.tracer = new DefaultTracer(new AlwaysSampler(), new Random(), new DefaultSpanNamer(), new NoOpSpanLogger(), this.spanAccumulator); this.template.setInterceptors(Arrays.asList( - new TraceRestTemplateInterceptor(this.tracer, new HttpRequestInjector(), + new TraceRestTemplateInterceptor(this.tracer, new ZipkinHttpSpanInjector(), new HttpTraceKeysInjector(this.tracer, new TraceKeys())))); TestSpanContextHolder.removeCurrentSpan(); } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptorTests.java index dc9e58b3b..857394153 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptorTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptorTests.java @@ -30,6 +30,7 @@ import org.springframework.cloud.sleuth.DefaultSpanNamer; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.TraceKeys; import org.springframework.cloud.sleuth.instrument.web.HttpTraceKeysInjector; +import org.springframework.cloud.sleuth.instrument.web.ZipkinHttpSpanInjector; import org.springframework.cloud.sleuth.log.NoOpSpanLogger; import org.springframework.cloud.sleuth.sampler.AlwaysSampler; import org.springframework.cloud.sleuth.trace.DefaultTracer; @@ -70,7 +71,7 @@ public class TraceRestTemplateInterceptorTests { this.tracer = new DefaultTracer(new AlwaysSampler(), new Random(), new DefaultSpanNamer(), new NoOpSpanLogger(), this.spanAccumulator); this.template.setInterceptors(Arrays.asList( - new TraceRestTemplateInterceptor(this.tracer, new HttpRequestInjector(), + new TraceRestTemplateInterceptor(this.tracer, new ZipkinHttpSpanInjector(), new HttpTraceKeysInjector(this.tracer, new TraceKeys())))); TestSpanContextHolder.removeCurrentSpan(); } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignRetriesTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignRetriesTests.java index daaf6fba4..a99a71f48 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignRetriesTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignRetriesTests.java @@ -32,10 +32,12 @@ import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; import org.springframework.beans.factory.BeanFactory; import org.springframework.cloud.sleuth.DefaultSpanNamer; +import org.springframework.cloud.sleuth.HttpSpanInjector; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.TraceKeys; import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.instrument.web.HttpTraceKeysInjector; +import org.springframework.cloud.sleuth.instrument.web.ZipkinHttpSpanInjector; import org.springframework.cloud.sleuth.log.NoOpSpanLogger; import org.springframework.cloud.sleuth.sampler.AlwaysSampler; import org.springframework.cloud.sleuth.trace.DefaultTracer; @@ -76,6 +78,8 @@ public class FeignRetriesTests { TestSpanContextHolder.removeCurrentSpan(); BDDMockito.given(this.beanFactory.getBean(HttpTraceKeysInjector.class)) .willReturn(new HttpTraceKeysInjector(this.tracer, new TraceKeys())); + BDDMockito.given(this.beanFactory.getBean(HttpSpanInjector.class)) + .willReturn(new ZipkinHttpSpanInjector()); BDDMockito.given(this.beanFactory.getBean(Tracer.class)).willReturn(this.tracer); } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientTests.java index ce8f7ea21..cf77c30ad 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientTests.java @@ -31,11 +31,13 @@ import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; import org.springframework.beans.factory.BeanFactory; import org.springframework.cloud.sleuth.DefaultSpanNamer; +import org.springframework.cloud.sleuth.HttpSpanInjector; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.TraceKeys; import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.assertions.SleuthAssertions; import org.springframework.cloud.sleuth.instrument.web.HttpTraceKeysInjector; +import org.springframework.cloud.sleuth.instrument.web.ZipkinHttpSpanInjector; import org.springframework.cloud.sleuth.log.NoOpSpanLogger; import org.springframework.cloud.sleuth.sampler.AlwaysSampler; import org.springframework.cloud.sleuth.trace.DefaultTracer; @@ -69,6 +71,8 @@ public class TraceFeignClientTests { ExceptionUtils.setFail(true); BDDMockito.given(this.beanFactory.getBean(HttpTraceKeysInjector.class)) .willReturn(new HttpTraceKeysInjector(this.tracer, new TraceKeys())); + BDDMockito.given(this.beanFactory.getBean(HttpSpanInjector.class)) + .willReturn(new ZipkinHttpSpanInjector()); BDDMockito.given(this.beanFactory.getBean(Tracer.class)).willReturn(this.tracer); } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/common/AbstractMvcIntegrationTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/common/AbstractMvcIntegrationTest.java index 1ad11b4d3..3f20599f7 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/common/AbstractMvcIntegrationTest.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/common/AbstractMvcIntegrationTest.java @@ -1,10 +1,8 @@ package org.springframework.cloud.sleuth.instrument.web.common; -import javax.servlet.http.HttpServletRequest; - import org.junit.Before; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.cloud.sleuth.SpanExtractor; +import org.springframework.cloud.sleuth.HttpSpanExtractor; import org.springframework.cloud.sleuth.TraceKeys; import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.instrument.web.HttpTraceKeysInjector; @@ -34,7 +32,7 @@ public abstract class AbstractMvcIntegrationTest { protected MockMvc mockMvc; @Autowired protected Tracer tracer; @Autowired protected TraceKeys traceKeys; - @Autowired protected SpanExtractor spanExtractor; + @Autowired protected HttpSpanExtractor spanExtractor; @Autowired protected HttpTraceKeysInjector httpTraceKeysInjector; @Before diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/ApacheHttpClientRibbonRequestCustomizerTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/ApacheHttpClientRibbonRequestCustomizerTests.java index 34e6a613e..28c97ead3 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/ApacheHttpClientRibbonRequestCustomizerTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/ApacheHttpClientRibbonRequestCustomizerTests.java @@ -50,7 +50,7 @@ public class ApacheHttpClientRibbonRequestCustomizerTests { public void should_set_not_sampled_on_the_context_when_there_is_no_span() throws Exception { RequestBuilder requestBuilder = RequestBuilder.create("GET"); - this.customizer.inject(null, requestBuilder); + this.customizer.inject(null, this.customizer.toSpanTextMap(requestBuilder)); HttpUriRequest request = requestBuilder.build(); Header header = request.getFirstHeader(Span.SAMPLED_NAME); @@ -62,7 +62,7 @@ public class ApacheHttpClientRibbonRequestCustomizerTests { public void should_set_tracing_headers_on_the_context_when_there_is_a_span() throws Exception { RequestBuilder requestBuilder = RequestBuilder.create("GET"); - this.customizer.inject(this.span, requestBuilder); + this.customizer.inject(this.span, this.customizer.toSpanTextMap(requestBuilder)); HttpUriRequest request = requestBuilder.build(); thenThereIsAHeaderWithNameAndValue(request, Span.SPAN_ID_NAME, "1"); diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/OkHttpClientRibbonRequestCustomizerTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/OkHttpClientRibbonRequestCustomizerTests.java index f3e1c0d8c..08e6cdb55 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/OkHttpClientRibbonRequestCustomizerTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/OkHttpClientRibbonRequestCustomizerTests.java @@ -49,7 +49,7 @@ public class OkHttpClientRibbonRequestCustomizerTests { public void should_set_not_sampled_on_the_context_when_there_is_no_span() throws Exception { Request.Builder requestBuilder = requestBuilder(); - this.customizer.inject(null, requestBuilder); + this.customizer.inject(null, this.customizer.toSpanTextMap(requestBuilder)); Request request = requestBuilder.build(); then(request.header(Span.SAMPLED_NAME)).isEqualTo(Span.SPAN_NOT_SAMPLED); @@ -59,7 +59,7 @@ public class OkHttpClientRibbonRequestCustomizerTests { public void should_set_tracing_headers_on_the_context_when_there_is_a_span() throws Exception { Request.Builder requestBuilder = requestBuilder(); - this.customizer.inject(this.span, requestBuilder); + this.customizer.inject(this.span, this.customizer.toSpanTextMap(requestBuilder)); Request request = requestBuilder.build(); thenThereIsAHeaderWithNameAndValue(request, Span.SPAN_ID_NAME, "1"); diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/RestClientRibbonRequestCustomizerTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/RestClientRibbonRequestCustomizerTests.java index 4fc895e21..12b37bbc5 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/RestClientRibbonRequestCustomizerTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/RestClientRibbonRequestCustomizerTests.java @@ -49,7 +49,7 @@ public class RestClientRibbonRequestCustomizerTests { public void should_set_not_sampled_on_the_context_when_there_is_no_span() throws Exception { HttpRequest.Builder requestBuilder = requestBuilder(); - this.customizer.inject(null, requestBuilder); + this.customizer.inject(null, this.customizer.toSpanTextMap(requestBuilder)); HttpRequest request = requestBuilder.build(); then(request.getHttpHeaders().getFirstValue(Span.SAMPLED_NAME)).isEqualTo(Span.SPAN_NOT_SAMPLED); @@ -59,7 +59,7 @@ public class RestClientRibbonRequestCustomizerTests { public void should_set_tracing_headers_on_the_context_when_there_is_a_span() throws Exception { HttpRequest.Builder requestBuilder = requestBuilder(); - this.customizer.inject(this.span, requestBuilder); + this.customizer.inject(this.span, this.customizer.toSpanTextMap(requestBuilder)); HttpRequest request = requestBuilder.build(); thenThereIsAHeaderWithNameAndValue(request, Span.SPAN_ID_NAME, "1"); diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TracePreZuulFilterTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TracePreZuulFilterTests.java index fd4bf49af..05ca52aeb 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TracePreZuulFilterTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TracePreZuulFilterTests.java @@ -35,6 +35,7 @@ import org.springframework.cloud.sleuth.NoOpSpanReporter; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.TraceKeys; import org.springframework.cloud.sleuth.instrument.web.HttpTraceKeysInjector; +import org.springframework.cloud.sleuth.instrument.web.ZipkinHttpSpanInjector; import org.springframework.cloud.sleuth.log.NoOpSpanLogger; import org.springframework.cloud.sleuth.sampler.AlwaysSampler; import org.springframework.cloud.sleuth.sampler.NeverSampler; @@ -55,7 +56,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, new RequestContextInjector(), + private TracePreZuulFilter filter = new TracePreZuulFilter(this.tracer, new ZipkinHttpSpanInjector(), new HttpTraceKeysInjector(this.tracer, new TraceKeys())); @After @@ -106,7 +107,7 @@ public class TracePreZuulFilterTests { Span startedSpan = this.tracer.createSpan("http:start"); final AtomicReference span = new AtomicReference<>(); - new TracePreZuulFilter(this.tracer, new RequestContextInjector(), + new TracePreZuulFilter(this.tracer, new ZipkinHttpSpanInjector(), new HttpTraceKeysInjector(this.tracer, new TraceKeys())) { @Override public Object run() { @@ -127,7 +128,7 @@ public class TracePreZuulFilterTests { Span startedSpan = this.tracer.createSpan("http:start"); final AtomicReference span = new AtomicReference<>(); - new TracePreZuulFilter(this.tracer, new RequestContextInjector(), + new TracePreZuulFilter(this.tracer, new ZipkinHttpSpanInjector(), new HttpTraceKeysInjector(this.tracer, new TraceKeys())) { @Override public Object run() { diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/util/TextMapUtilTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/util/TextMapUtilTests.java new file mode 100644 index 000000000..4f8669439 --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/util/TextMapUtilTests.java @@ -0,0 +1,31 @@ +package org.springframework.cloud.sleuth.util; + +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.junit.Test; + +import static org.assertj.core.api.BDDAssertions.then; + +/** + * @author Marcin Grzejszczak + */ +public class TextMapUtilTests { + + @Test + public void should_convert_an_iterable_to_a_caseinsensitive_map() throws Exception { + List> iterable = new ArrayList<>(); + iterable.add(new AbstractMap.SimpleEntry<>("foo", "bar")); + + Map map = TextMapUtil.asMap(iterable); + + then(map) + .containsKey("foo") + .containsKey("FOO") + .containsKey("FoO") + .contains(new AbstractMap.SimpleEntry<>("foo", "bar")); + } + +} \ No newline at end of file