diff --git a/circle.yml b/circle.yml
index c74596f7d..67412025d 100644
--- a/circle.yml
+++ b/circle.yml
@@ -8,10 +8,11 @@ dependencies:
- ./mvnw -s .settings.xml --fail-never dependency:go-offline || true
test:
override:
- - ./mvnw -s .settings.xml clean install -nsu --batch-mode -Dmaven.test.redirectTestOutputToFile=true -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn
+ - ./mvnw -s .settings.xml clean org.jacoco:jacoco-maven-plugin:prepare-agent install -P sonar -nsu --batch-mode -Dmaven.test.redirectTestOutputToFile=true -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn
post:
- mkdir -p $CIRCLE_TEST_REPORTS/junit/
- find . -type f -regex ".*/target/.*-reports/.*" -exec cp {} $CIRCLE_TEST_REPORTS/junit/ \;
+ - bash <(curl -s https://codecov.io/bash)
notify:
webhooks:
diff --git a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc
index e9e7fed8f..351d35172 100644
--- a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc
+++ b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc
@@ -449,6 +449,9 @@ We're injecting a `RestTemplate` interceptor that ensures that all the tracing i
call is made a new Span is created. It gets closed upon receiving the response. In order to block the synchronous `RestTemplate` features
just set `spring.sleuth.web.client.enabled` to `false`.
+IMPORTANT: You have to register `RestTemplate` as a bean so that the interceptors will get injected.
+If you create a `RestTemplate` instance with a `new` keyword then the instrumentation WILL NOT work.
+
==== Asynchronous Rest Template
Custom instrumentation is set to create and close Spans upon sending and receiving requests. You can customize the `ClientHttpRequestFactory`
@@ -512,6 +515,11 @@ include::../../../../spring-cloud-sleuth-core/src/test/java/org/springframework/
Spring Cloud Sleuth integrates with http://projects.spring.io/spring-integration/[Spring Integration]. It creates spans for publish and
subscribe events. To disable Spring Integration instrumentation, set `spring.sleuth.integration.enabled` to false.
+Spring Cloud Sleuth up till version 1.0.4 is sending invalid tracing headers when using messaging. Those headers are actually
+the same as the ones sent in HTTP (they contain a `-`) in its name. For the sake of
+backwards compatibility in 1.0.4 we've started sending both valid and invalid headers. Please upgrade to 1.0.4 because
+in Spring Cloud Sleuth 1.1 we will remove the support for the deprecated headers.
+
=== Zuul
We're registering Zuul filters to propagate the tracing information (the request header is enriched with tracing data).
diff --git a/pom.xml b/pom.xml
index c1faa30dc..11e2815b5 100644
--- a/pom.xml
+++ b/pom.xml
@@ -195,7 +195,7 @@
org.assertj
assertj-core
- 2.1.0
+ 3.5.1
test
diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingSpanExtractor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingSpanExtractor.java
index 92bd1a780..b7cdc809f 100644
--- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingSpanExtractor.java
+++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingSpanExtractor.java
@@ -16,8 +16,11 @@
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;
@@ -29,7 +32,9 @@ import org.springframework.messaging.Message;
* @author Marcin Grzejszczak
* @since 1.0.0
*/
-public class MessagingSpanExtractor implements SpanExtractor> {
+class MessagingSpanExtractor implements SpanExtractor> {
+
+ private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass());
private final Random random;
@@ -39,28 +44,55 @@ public class MessagingSpanExtractor implements SpanExtractor> {
@Override
public Span joinTrace(Message> carrier) {
- if (!hasHeader(carrier, Span.TRACE_ID_NAME)
- || !hasHeader(carrier, Span.SPAN_ID_NAME)) {
+ 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, Span.TRACE_ID_NAME));
- long spanId = hasHeader(carrier, Span.SPAN_ID_NAME)
- ? Span.hexToId(getHeader(carrier, Span.SPAN_ID_NAME))
+ .hexToId(getHeader(carrier, traceIdHeader));
+ long spanId = hasHeader(carrier, spanIdHeader)
+ ? Span.hexToId(getHeader(carrier, spanIdHeader))
: this.random.nextLong();
- SpanBuilder spanBuilder = Span.builder().traceId(traceId).spanId(spanId);
+ spanBuilder = spanBuilder.traceId(traceId).spanId(spanId);
spanBuilder.exportable(
- Span.SPAN_SAMPLED.equals(getHeader(carrier, Span.SAMPLED_NAME)));
- String processId = getHeader(carrier, Span.PROCESS_ID_NAME);
- String spanName = getHeader(carrier, Span.SPAN_NAME_NAME);
+ 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);
+ setParentIdIfApplicable(carrier, spanBuilder, spanParentIdHeader);
spanBuilder.remote(true);
return spanBuilder.build();
}
@@ -77,8 +109,9 @@ public class MessagingSpanExtractor implements SpanExtractor> {
return message.getHeaders().containsKey(name);
}
- private void setParentIdIfApplicable(Message> carrier, SpanBuilder spanBuilder) {
- String parentId = getHeader(carrier, Span.PARENT_ID_NAME);
+ private void setParentIdIfApplicable(Message> carrier, SpanBuilder spanBuilder,
+ String spanParentIdHeader) {
+ String parentId = getHeader(carrier, spanParentIdHeader);
if (parentId != null) {
spanBuilder.parent(Span.hexToId(parentId));
}
diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingSpanInjector.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingSpanInjector.java
index 09f37deaf..11098b447 100644
--- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingSpanInjector.java
+++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingSpanInjector.java
@@ -36,8 +36,6 @@ import org.springframework.util.StringUtils;
*/
class MessagingSpanInjector implements SpanInjector> {
- public static final String SPAN_HEADER = "X-Current-Span";
-
private final TraceKeys traceKeys;
public MessagingSpanInjector(TraceKeys traceKeys) {
@@ -50,31 +48,19 @@ class MessagingSpanInjector implements SpanInjector> {
MessageHeaderAccessor accessor = MessageHeaderAccessor
.getMutableAccessor(initialMessage);
if (span == null) {
- if (!Span.SPAN_SAMPLED
- .equals(initialMessage.getHeaders().get(Span.SAMPLED_NAME))) {
+ if (!isSampled(initialMessage, Span.SAMPLED_NAME) ||
+ !isSampled(initialMessage, TraceMessageHeaders.SAMPLED_NAME)) {
+ // Backwards compatibility
accessor.setHeader(Span.SAMPLED_NAME, Span.SPAN_NOT_SAMPLED);
+ accessor.setHeader(TraceMessageHeaders.SAMPLED_NAME, Span.SPAN_NOT_SAMPLED);
carrier.setHeaders(accessor);
return;
}
return;
}
Map headers = new HashMap<>();
- addHeader(headers, Span.TRACE_ID_NAME, Span.idToHex(span.getTraceId()));
- addHeader(headers, Span.SPAN_ID_NAME, Span.idToHex(span.getSpanId()));
- if (span.isExportable()) {
- addAnnotations(this.traceKeys, initialMessage, span);
- Long parentId = getFirst(span.getParents());
- if (parentId != null) {
- addHeader(headers, Span.PARENT_ID_NAME, Span.idToHex(parentId));
- }
- addHeader(headers, Span.SPAN_NAME_NAME, span.getName());
- addHeader(headers, Span.PROCESS_ID_NAME, span.getProcessId());
- addHeader(headers, Span.SAMPLED_NAME, Span.SPAN_SAMPLED);
- }
- else {
- addHeader(headers, Span.SAMPLED_NAME, Span.SPAN_NOT_SAMPLED);
- }
- accessor.setHeader(SPAN_HEADER, span);
+ addOldHeaders(span, initialMessage, accessor, headers);
+ addNewHeaders(span, initialMessage, accessor, headers);
accessor.copyHeaders(headers);
if (accessor instanceof NativeMessageHeaderAccessor) {
NativeMessageHeaderAccessor nativeAccessor = (NativeMessageHeaderAccessor) accessor;
@@ -85,6 +71,48 @@ class MessagingSpanInjector implements SpanInjector> {
carrier.setHeaders(accessor);
}
+ private boolean isSampled(Message> initialMessage, String sampledHeaderName) {
+ return Span.SPAN_SAMPLED
+ .equals(initialMessage.getHeaders().get(sampledHeaderName));
+ }
+
+ // Backwards compatibility
+ private void addOldHeaders(Span span, Message> initialMessage,
+ MessageHeaderAccessor accessor, Map headers) {
+ addHeaders(span, initialMessage, accessor, headers, Span.TRACE_ID_NAME,
+ Span.SPAN_ID_NAME, Span.PARENT_ID_NAME, Span.SPAN_NAME_NAME, Span.PROCESS_ID_NAME,
+ Span.SAMPLED_NAME, TraceMessageHeaders.OLD_SPAN_HEADER);
+ }
+
+ private void addNewHeaders(Span span, Message> initialMessage,
+ MessageHeaderAccessor accessor, Map headers) {
+ addHeaders(span, initialMessage, accessor, headers, TraceMessageHeaders.TRACE_ID_NAME,
+ TraceMessageHeaders.SPAN_ID_NAME, TraceMessageHeaders.PARENT_ID_NAME, TraceMessageHeaders.SPAN_NAME_NAME,
+ TraceMessageHeaders.PROCESS_ID_NAME, TraceMessageHeaders.SAMPLED_NAME, TraceMessageHeaders.SPAN_HEADER);
+ }
+
+ private void addHeaders(Span span, Message> initialMessage,
+ MessageHeaderAccessor accessor, Map headers, String traceIdHeader,
+ String spanIdHeader, String parentIdHeader, String spanNameHeader, String processIdHeader,
+ String spanSampledHeader, String spanHeader) {
+ addHeader(headers, traceIdHeader, Span.idToHex(span.getTraceId()));
+ addHeader(headers, spanIdHeader, Span.idToHex(span.getSpanId()));
+ if (span.isExportable()) {
+ addAnnotations(this.traceKeys, initialMessage, span);
+ Long parentId = getFirst(span.getParents());
+ if (parentId != null) {
+ addHeader(headers, parentIdHeader, Span.idToHex(parentId));
+ }
+ addHeader(headers, spanNameHeader, span.getName());
+ addHeader(headers, processIdHeader, span.getProcessId());
+ addHeader(headers, spanSampledHeader, Span.SPAN_SAMPLED);
+ }
+ else {
+ addHeader(headers, spanSampledHeader, Span.SPAN_NOT_SAMPLED);
+ }
+ accessor.setHeader(spanHeader, span);
+ }
+
private void addAnnotations(TraceKeys traceKeys, Message> message, Span span) {
for (String name : traceKeys.getMessage().getHeaders()) {
if (message.getHeaders().containsKey(name)) {
diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceChannelInterceptor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceChannelInterceptor.java
index bbd7573a7..98358be53 100644
--- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceChannelInterceptor.java
+++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceChannelInterceptor.java
@@ -37,9 +37,6 @@ import org.springframework.messaging.support.MessageBuilder;
*/
public class TraceChannelInterceptor extends AbstractTraceChannelInterceptor {
- private static final String SPAN_HEADER = "X-Current-Span";
- private static final String MESSAGE_SENT_FROM_CLIENT = "X-Message-Sent";
-
public TraceChannelInterceptor(Tracer tracer, TraceKeys traceKeys,
SpanExtractor> spanExtractor,
SpanInjector> spanInjector) {
@@ -76,11 +73,15 @@ public class TraceChannelInterceptor extends AbstractTraceChannelInterceptor {
String name = getMessageChannelName(channel);
Span span = startSpan(parentSpan, name, message);
MessageBuilder> messageBuilder = MessageBuilder.fromMessage(message);
- if (message.getHeaders().containsKey(MESSAGE_SENT_FROM_CLIENT)) {
+ // Backwards compatibility
+ if (message.getHeaders().containsKey(TraceMessageHeaders.OLD_MESSAGE_SENT_FROM_CLIENT) ||
+ message.getHeaders().containsKey(TraceMessageHeaders.MESSAGE_SENT_FROM_CLIENT)) {
span.logEvent(Span.SERVER_RECV);
} else {
span.logEvent(Span.CLIENT_SEND);
- messageBuilder.setHeader(MESSAGE_SENT_FROM_CLIENT, true);
+ // Backwards compatibility
+ messageBuilder.setHeader(TraceMessageHeaders.OLD_MESSAGE_SENT_FROM_CLIENT, true);
+ messageBuilder.setHeader(TraceMessageHeaders.MESSAGE_SENT_FROM_CLIENT, true);
}
getSpanInjector().inject(span, messageBuilder);
return messageBuilder.build();
@@ -90,7 +91,9 @@ public class TraceChannelInterceptor extends AbstractTraceChannelInterceptor {
if (span != null) {
return getTracer().createSpan(name, span);
}
- if (Span.SPAN_NOT_SAMPLED.equals(message.getHeaders().get(Span.SAMPLED_NAME))) {
+ // 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))) {
return getTracer().createSpan(name, NeverSampler.INSTANCE);
}
return getTracer().createSpan(name);
@@ -121,7 +124,11 @@ public class TraceChannelInterceptor extends AbstractTraceChannelInterceptor {
if (message == null) {
return null;
}
- Object object = message.getHeaders().get(SPAN_HEADER);
+ 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;
}
diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceMessageHeaders.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceMessageHeaders.java
new file mode 100644
index 000000000..fc2edb59a
--- /dev/null
+++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceMessageHeaders.java
@@ -0,0 +1,40 @@
+/*
+ * 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;
+
+/**
+ * Contains trace related messaging headers
+ *
+ * @author Marcin Grzejszczak
+ * @since 1.0.4
+ */
+public class TraceMessageHeaders {
+
+ public static final String SPAN_ID_NAME = "spanId";
+ public static final String SAMPLED_NAME = "spanSampled";
+ public static final String PROCESS_ID_NAME = "spanProcessId";
+ public static final String PARENT_ID_NAME = "spanParentSpanId";
+ public static final String TRACE_ID_NAME = "spanTraceId";
+ public static final String SPAN_NAME_NAME = "spanName";
+
+ static final String OLD_SPAN_HEADER = "X-Current-Span";
+ static final String OLD_MESSAGE_SENT_FROM_CLIENT = "X-Message-Sent";
+ static final String SPAN_HEADER = "currentSpan";
+ static final String MESSAGE_SENT_FROM_CLIENT = "messageSent";
+
+ private TraceMessageHeaders() {}
+}
diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/TraceChannelInterceptorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/TraceChannelInterceptorTests.java
index 63e57dc67..6702bfcbb 100644
--- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/TraceChannelInterceptorTests.java
+++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/TraceChannelInterceptorTests.java
@@ -88,12 +88,14 @@ public class TraceChannelInterceptorTests implements MessageHandler {
@Before
public void init() {
this.channel.subscribe(this);
+ this.accumulator.getSpans().clear();
}
@After
public void close() {
TestSpanContextHolder.removeCurrentSpan();
this.channel.unsubscribe(this);
+ this.accumulator.getSpans().clear();
}
@Test
@@ -124,6 +126,51 @@ public class TraceChannelInterceptorTests implements MessageHandler {
then(this.accumulator.getSpans()).hasSize(1);
}
+ // #332
+ @Test
+ public void shouldSendNewAndOldHeadersWhenNewHeadersWerePassed() {
+ this.channel.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.channel.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));
+ then(traceId).isEqualTo(10L);
+ long oldTraceId = Span
+ .hexToId(this.message.getHeaders().get(Span.TRACE_ID_NAME, String.class));
+ then(oldTraceId).isEqualTo(traceId);
+ }
+
@Test
public void spanCreation() {
this.channel.send(MessageBuilder.withPayload("hi").build());