Text map introduction (#439)

without this change we had to treat every mean of injecting and extracting span information separately
with this change all means of communications are abstracted. That way you configure things once only.

part of #237
This commit is contained in:
Marcin Grzejszczak
2016-11-04 12:23:14 +01:00
committed by GitHub
parent 43ae6dd7e3
commit 83012a5aa6
63 changed files with 1156 additions and 946 deletions

View File

@@ -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".

View File

@@ -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<SpanTextMap> {
}

View File

@@ -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<SpanTextMap> {
}

View File

@@ -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<Map.Entry<String, String>> {
/**
* 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<Map.Entry<String,String>> 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);
}

View File

@@ -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<Message<?>> spanExtractor;
private final SpanInjector<MessageBuilder<?>> spanInjector;
private final MessagingSpanTextMapExtractor spanExtractor;
private final MessagingSpanTextMapInjector spanInjector;
protected AbstractTraceChannelInterceptor(Tracer tracer, TraceKeys traceKeys,
SpanExtractor<Message<?>> spanExtractor,
SpanInjector<MessageBuilder<?>> 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<MessageBuilder<?>> 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;

View File

@@ -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<String, String> 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<String, String> 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<String, String> message, String name) {
return message.containsKey(name);
}
private void setParentIdIfApplicable(Map<String, String> carrier, Span.SpanBuilder spanBuilder,
String spanParentIdHeader) {
String parentId = carrier.get(spanParentIdHeader);
if (parentId != null) {
spanBuilder.parent(Span.hexToId(parentId));
}
}
}

View File

@@ -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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<Long> parents) {
return parents.isEmpty() ? null : parents.get(0);
}
}

View File

@@ -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<Message<?>> spanExtractor,
SpanInjector<MessageBuilder<?>> spanInjector) {
MessagingSpanTextMapExtractor spanExtractor,
MessagingSpanTextMapInjector spanInjector) {
super(tracer, traceKeys, spanExtractor, spanInjector);
}

View File

@@ -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<Message<?>> {
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> T getHeader(Message<?> message, String name, Class<T> type) {
return message.getHeaders().get(name, type);
}
boolean hasHeader(Message<?> message, String name) {
return message.getHeaders().containsKey(name);
}
private void setParentIdIfApplicable(Message<?> carrier, SpanBuilder spanBuilder,
String spanParentIdHeader) {
String parentId = getHeader(carrier, spanParentIdHeader);
if (parentId != null) {
spanBuilder.parent(Span.hexToId(parentId));
}
}
}

View File

@@ -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<MessageBuilder<?>> {
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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> headers, String name, String value) {
if (StringUtils.hasText(value)) {
headers.put(name, value);
}
}
private Long getFirst(List<Long> parents) {
return parents.isEmpty() ? null : parents.get(0);
}
}

View File

@@ -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<SpanTextMap> {
}

View File

@@ -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<SpanTextMap> {
}

View File

@@ -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<Map.Entry<String, String>> iterator() {
Map<String, String> map = new HashMap<>();
for (Map.Entry<String, Object> 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<String, String> 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());
}
}

View File

@@ -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<Message<?>> spanExtractor,
SpanInjector<MessageBuilder<?>> 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<Object>(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;
}
}

View File

@@ -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() {}

View File

@@ -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<Message<?>> messagingSpanExtractor(Random random) {
return new MessagingSpanExtractor(random);
@ConditionalOnMissingBean
public MessagingSpanTextMapExtractor messagingSpanExtractor() {
return new HeaderBasedMessagingExtractor();
}
@Bean
public SpanInjector<MessageBuilder<?>> messagingSpanInjector(TraceKeys traceKeys) {
return new MessagingSpanInjector(traceKeys);
@ConditionalOnMissingBean
public MessagingSpanTextMapInjector messagingSpanInjector(TraceKeys traceKeys) {
return new HeaderBasedMessagingInjector(traceKeys);
}
}

View File

@@ -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<Message<?>> spanExtractor,
SpanInjector<MessageBuilder<?>> spanInjector) {
TraceKeys traceKeys, Random random, MessagingSpanTextMapExtractor spanExtractor,
MessagingSpanTextMapInjector spanInjector) {
return new IntegrationTraceChannelInterceptor(tracer, traceKeys, spanExtractor,
spanInjector);
}

View File

@@ -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<Message<?>> spanExtractor;
MessagingSpanTextMapExtractor spanExtractor;
@Autowired
SpanInjector<MessageBuilder<?>> spanInjector;
MessagingSpanTextMapInjector spanInjector;
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {

View File

@@ -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<HttpServletRequest> {
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();
}
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright 2013-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.sleuth.instrument.web;
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<String, String> 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<Map.Entry<String, String>> iterator() {
Map<String, String> map = new HashMap<>();
Enumeration<String> 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);
}
}

View File

@@ -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<HttpServletRequest> spanExtractor;
private final HttpSpanExtractor spanExtractor;
private final HttpTraceKeysInjector httpTraceKeysInjector;
private UrlPathHelper urlPathHelper = new UrlPathHelper();
public TraceFilter(Tracer tracer, TraceKeys traceKeys, SpanReporter spanReporter,
SpanExtractor<HttpServletRequest> 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<HttpServletRequest> 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");

View File

@@ -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<HttpServletRequest> 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<HttpServletRequest> spanExtractor,
HttpSpanExtractor spanExtractor,
HttpTraceKeysInjector httpTraceKeysInjector) {
return new TraceFilter(tracer, traceKeys, skipPatternProvider.skipPattern(),
spanReporter, spanExtractor, httpTraceKeysInjector);
}
@Bean
public SpanExtractor<HttpServletRequest> 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

View File

@@ -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<String, String> 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<String, String> 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<String, String> 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();
}
}

View File

@@ -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<String, String> entry : carrier) {
if (entry.getKey().equals(name)) {
return true;
}
}
return false;
}
}

View File

@@ -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<HttpRequest> spanInjector;
protected final HttpSpanInjector spanInjector;
protected final HttpTraceKeysInjector keysInjector;
protected AbstractTraceHttpRequestInterceptor(Tracer tracer,
SpanInjector<HttpRequest> 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()) {

View File

@@ -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<HttpRequest> {
@Override
public void inject(Span span, HttpRequest carrier) {
setIdHeader(carrier, Span.TRACE_ID_NAME, span.getTraceId());
setIdHeader(carrier, Span.SPAN_ID_NAME, span.getSpanId());
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));
}
}
}

View File

@@ -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<Map.Entry<String, String>> iterator() {
final Iterator<Map.Entry<String, List<String>>> iterator = this.delegate.getHeaders()
.entrySet().iterator();
return new Iterator<Map.Entry<String, String>>() {
@Override public boolean hasNext() {
return iterator.hasNext();
}
@Override public Map.Entry<String, String> next() {
Map.Entry<String, List<String>> next = iterator.next();
List<String> 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));
}
}

View File

@@ -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<HttpRequest> 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<HttpRequest> 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<HttpRequest> spanInjector,
HttpSpanInjector spanInjector,
AsyncClientHttpRequestFactory asyncDelegate,
ClientHttpRequestFactory syncDelegate,
HttpTraceKeysInjector httpTraceKeysInjector) {

View File

@@ -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<HttpRequest> spanInjector,
public TraceRestTemplateInterceptor(Tracer tracer, HttpSpanInjector spanInjector,
HttpTraceKeysInjector httpTraceKeysInjector) {
super(tracer, spanInjector, httpTraceKeysInjector);
}

View File

@@ -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<HttpRequest> spanInjector;
@Autowired private HttpSpanInjector spanInjector;
@Autowired(required = false) private ClientHttpRequestFactory clientHttpRequestFactory;
@Autowired(required = false) private AsyncClientHttpRequestFactory asyncClientHttpRequestFactory;

View File

@@ -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<HttpRequest> spanInjector,
HttpSpanInjector spanInjector,
HttpTraceKeysInjector httpTraceKeysInjector) {
return new TraceRestTemplateInterceptor(tracer, spanInjector, httpTraceKeysInjector);
}
@Bean
public SpanInjector<HttpRequest> httpRequestSpanInjector() {
return new HttpRequestInjector();
}
@Configuration
protected static class TraceInterceptorConfiguration {

View File

@@ -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<AtomicReference<Request>> {
@Override
public void inject(Span span, AtomicReference<Request> carrier) {
String method = carrier.get().method();
String url = carrier.get().url();
Map<String, Collection<String>> 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<String, Collection<String>> headers, String name, String value) {
if (StringUtils.hasText(value) && !headers.containsKey(name)) {
List<String> list = new ArrayList<>();
list.add(value);
headers.put(name, list);
}
}
}

View File

@@ -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<Request>}
*
* @author Marcin Grzejszczak
* @since 1.2.0
*/
class FeignRequestTextMap implements SpanTextMap {
private final AtomicReference<Request> delegate;
FeignRequestTextMap(AtomicReference<Request> delegate) {
this.delegate = delegate;
}
@Override
public Iterator<Map.Entry<String, String>> iterator() {
final Iterator<Map.Entry<String, Collection<String>>> iterator = this.delegate.get().headers().entrySet().iterator();
return new Iterator<Map.Entry<String, String>>() {
@Override public boolean hasNext() {
return iterator.hasNext();
}
@Override public Map.Entry<String, String> next() {
Map.Entry<String, Collection<String>> next = iterator.next();
Collection<String> 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<String, Collection<String>> 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<String, Collection<String>> headers) {
if (!headers.containsKey(key)) {
List<String> list = new ArrayList<>();
list.add(value);
headers.put(key, list);
}
}
}

View File

@@ -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<Request> 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()) {

View File

@@ -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<Map.Entry<String, String>> iterator() {
Map<String, String> 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);
}
};
}
}

View File

@@ -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<Map.Entry<String, String>> iterator() {
Map<String, String> map = new HashMap<>();
for (Map.Entry<String, List<String>> 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);
}
};
}
}

View File

@@ -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<RequestContext> {
@Override
public void inject(Span span, RequestContext carrier) {
Map<String, String> 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<String, String> request, String name, String value) {
if (StringUtils.hasText(value) && !request.containsKey(name)) {
request.put(name, value);
}
}
public void setHeader(Map<String, String> request, String name, Long value) {
if (value != null) {
setHeader(request, name, Span.idToHex(value));
}
}
}

View File

@@ -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<Map.Entry<String, String>> iterator() {
return this.carrier.getZuulRequestHeaders().entrySet().iterator();
}
@Override
public void put(String key, String value) {
this.carrier.getZuulRequestHeaders().put(key, value);
}
}

View File

@@ -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<Map.Entry<String, String>> iterator() {
return context.build().getHttpHeaders().getAllHeaders().iterator();
}
@Override public void put(String key, String value) {
context.header(key, value);
}
};
}
}

View File

@@ -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<T> implements RibbonRequestCustomizer<T>,
SpanInjector<T> {
SpanInjector<SpanTextMap> {
private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass());
@@ -50,30 +51,30 @@ abstract class SpanInjectingRibbonRequestCustomizer<T> 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<T> implements RibbonRequestC
private Span getCurrentSpan() {
return this.tracer.getCurrentSpan();
}
abstract void setHeader(T builder, String name, String value);
}

View File

@@ -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<RequestContext> spanInjector;
private final HttpSpanInjector spanInjector;
private final HttpTraceKeysInjector httpTraceKeysInjector;
public TracePreZuulFilter(Tracer tracer, SpanInjector<RequestContext> 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 + "");

View File

@@ -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<RequestContext> 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<RequestContext> requestContextSpanInjector() {
return new RequestContextInjector();
}
@Bean
public TraceRibbonCommandFactoryBeanPostProcessor traceRibbonCommandFactoryBeanPostProcessor(BeanFactory beanFactory) {
return new TraceRibbonCommandFactoryBeanPostProcessor(beanFactory);

View File

@@ -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<String, String> asMap(Iterable<Map.Entry<String, String>> iterable) {
Map<String, String> map = new TreeMap<>(new Comparator<String>() {
@Override public int compare(String o1, String o2) {
return o1.toLowerCase().compareTo(o2.toLowerCase());
}
});
for (Map.Entry<String, String> entry : iterable) {
map.put(entry.getKey(), entry.getValue());
}
return map;
}
}

View File

@@ -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);

View File

@@ -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<String, Object> 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);
}

View File

@@ -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<String> messageBuilder = MessageBuilder
.fromMessage(messageToBuild);
this.messagingSpanInjector.inject(span, messageBuilder);
this.spanInjector.inject(span, new MessagingTextMap(messageBuilder));
Message<String> 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);
}
}

View File

@@ -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()

View File

@@ -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);
}

View File

@@ -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));
}

View File

@@ -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<HttpServletResponse> customHttpServletResponseSpanInjector() {
@Bean HttpSpanInjector customHttpServletResponseSpanInjector() {
return new CustomHttpServletResponseSpanInjector();
}
@@ -113,22 +116,22 @@ public class TraceCustomFilterResponseInjectorTests {
}
// tag::injector[]
static class CustomHttpServletResponseSpanInjector
implements SpanInjector<HttpServletResponse> {
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<HttpServletResponse> spanInjector;
private final HttpSpanInjector spanInjector;
public HttpResponseInjectingTraceFilter(Tracer tracer, SpanInjector<HttpServletResponse> 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<Map.Entry<String, String>> iterator() {
Map<String, String> 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[]

View File

@@ -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<Map> responseEntity = this.restTemplate.exchange(requestEntity,
Map.class);
final Span newSpan = this.tracer.createSpan("new_span");
ResponseEntity<Map> 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<HttpServletRequest> 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<HttpServletRequest> {
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<String, String> 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<String, String> headers(@RequestHeader HttpHeaders headers) {
Map<String, String> map = new HashMap<>();

View File

@@ -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());

View File

@@ -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<HttpServletRequest> spanExtractor = new HttpServletRequestExtractor(Pattern
HttpSpanExtractor spanExtractor = new ZipkinHttpSpanExtractor(Pattern
.compile(TraceFilter.DEFAULT_SKIP_PATTERN));
private Tracer tracer;

View File

@@ -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.<ClientHttpRequestInterceptor>asList(
new TraceRestTemplateInterceptor(this.tracer, new HttpRequestInjector(),
new TraceRestTemplateInterceptor(this.tracer, new ZipkinHttpSpanInjector(),
new HttpTraceKeysInjector(this.tracer, new TraceKeys()))));
TestSpanContextHolder.removeCurrentSpan();
}

View File

@@ -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.<ClientHttpRequestInterceptor>asList(
new TraceRestTemplateInterceptor(this.tracer, new HttpRequestInjector(),
new TraceRestTemplateInterceptor(this.tracer, new ZipkinHttpSpanInjector(),
new HttpTraceKeysInjector(this.tracer, new TraceKeys()))));
TestSpanContextHolder.removeCurrentSpan();
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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<HttpServletRequest> spanExtractor;
@Autowired protected HttpSpanExtractor spanExtractor;
@Autowired protected HttpTraceKeysInjector httpTraceKeysInjector;
@Before

View File

@@ -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");

View File

@@ -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");

View File

@@ -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");

View File

@@ -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> 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> 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() {

View File

@@ -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<Map.Entry<String, String>> iterable = new ArrayList<>();
iterable.add(new AbstractMap.SimpleEntry<>("foo", "bar"));
Map<String, String> map = TextMapUtil.asMap(iterable);
then(map)
.containsKey("foo")
.containsKey("FOO")
.containsKey("FoO")
.contains(new AbstractMap.SimpleEntry<>("foo", "bar"));
}
}