diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/SleuthProperties.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/SleuthProperties.java index 45d790b2b..01a2e52f2 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/SleuthProperties.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/SleuthProperties.java @@ -29,6 +29,8 @@ public class SleuthProperties { private boolean enabled = true; /** When true, generate 128-bit trace IDs instead of 64-bit ones. */ private boolean traceId128 = false; + /** When true, your tracing system allows sharing a span ID between a client and server span */ + private boolean supportsJoin = true; public boolean isEnabled() { return this.enabled; @@ -45,4 +47,12 @@ public class SleuthProperties { public void setTraceId128(boolean traceId128) { this.traceId128 = traceId128; } + + public boolean isSupportsJoin() { + return this.supportsJoin; + } + + public void setSupportsJoin(boolean supportsJoin) { + this.supportsJoin = supportsJoin; + } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceFilter.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceFilter.java index 0f91366e2..35ac746ad 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceFilter.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceFilter.java @@ -37,6 +37,7 @@ import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.SpanReporter; import org.springframework.cloud.sleuth.TraceKeys; import org.springframework.cloud.sleuth.Tracer; +import org.springframework.cloud.sleuth.autoconfig.SleuthProperties; import org.springframework.cloud.sleuth.sampler.AlwaysSampler; import org.springframework.cloud.sleuth.sampler.NeverSampler; import org.springframework.core.Ordered; @@ -97,6 +98,7 @@ public class TraceFilter extends GenericFilterBean { private Tracer tracer; private TraceKeys traceKeys; private final Pattern skipPattern; + private final boolean supportsJoin; private SpanReporter spanReporter; private HttpSpanExtractor spanExtractor; private HttpTraceKeysInjector httpTraceKeysInjector; @@ -111,6 +113,7 @@ public class TraceFilter extends GenericFilterBean { public TraceFilter(BeanFactory beanFactory, Pattern skipPattern) { this.beanFactory = beanFactory; + this.supportsJoin = beanFactory.getBean(SleuthProperties.class).isSupportsJoin(); this.skipPattern = skipPattern; } @@ -344,10 +347,14 @@ public class TraceFilter extends GenericFilterBean { if (log.isDebugEnabled()) { log.debug("Found a parent span " + parent + " in the request"); } - addRequestTagsForParentSpan(request, parent); - spanFromRequest = parent; + if (!this.supportsJoin) { // create a child span for this side of the RPC + spanFromRequest = tracer().createSpan(parent.getName(), parent); + } else { + spanFromRequest = parent; + } + addRequestTagsForParentSpan(request, spanFromRequest); tracer().continueSpan(spanFromRequest); - if (parent.isRemote()) { + if (parent.isRemote()) { // then we are in a server span parent.logEvent(Span.SERVER_RECV); } request.setAttribute(TRACE_REQUEST_ATTR, spanFromRequest); diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/trace/DefaultTracer.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/trace/DefaultTracer.java index 76546d703..c96e96331 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/trace/DefaultTracer.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/trace/DefaultTracer.java @@ -98,7 +98,7 @@ public class DefaultTracer implements Tracer { else { long id = createId(); span = Span.builder().name(shortenedName) - .traceIdHigh(this.traceId128 ? createId() : 0L) + .traceIdHigh(this.traceId128 ? createTraceIdHigh() : 0L) .traceId(id) .spanId(id).build(); if (sampler == null) { @@ -171,7 +171,7 @@ public class DefaultTracer implements Tracer { long id = createId(); if (parent == null) { Span span = Span.builder().name(shortenedName) - .traceIdHigh(this.traceId128 ? createId() : 0L) + .traceIdHigh(this.traceId128 ? createTraceIdHigh() : 0L) .traceId(id) .spanId(id).build(); span = sampledSpan(span, this.defaultSampler); @@ -208,6 +208,21 @@ public class DefaultTracer implements Tracer { return span; } + /** + * Encodes a timestamp into the upper 32-bits, so that it can be converted to an Amazon trace ID. + * + *
For example, an Amazon trace ID is composed of the following: {@code |-- 32 bits for epoch + * seconds -- | -- 96 bits for random data -- |} + * + *
To support this, {@link Span#getTraceIdHigh() traceIdHigh} holds the epoch seconds and first + * 32 random bits: and {@link Span#getTraceId()} traceId} holds the remaining 64 random bits. + */ + private long createTraceIdHigh() { + long epochSeconds = System.currentTimeMillis() / 1000; + int random = this.random.nextInt(); + return (epochSeconds & 0xffffffffL) << 32 | (random & 0xffffffffL); + } + private long createId() { return this.random.nextLong(); } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterAlwaysSamplerIntegrationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterAlwaysSamplerIntegrationTests.java index 6e0782026..7dc01b6c8 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterAlwaysSamplerIntegrationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterAlwaysSamplerIntegrationTests.java @@ -17,6 +17,7 @@ import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.SpanReporter; import org.springframework.cloud.sleuth.TraceKeys; import org.springframework.cloud.sleuth.Tracer; +import org.springframework.cloud.sleuth.autoconfig.SleuthProperties; import org.springframework.cloud.sleuth.instrument.DefaultTestAutoConfiguration; import org.springframework.cloud.sleuth.instrument.web.common.AbstractMvcIntegrationTest; import org.springframework.cloud.sleuth.sampler.AlwaysSampler; @@ -71,6 +72,7 @@ public class TraceFilterAlwaysSamplerIntegrationTests extends AbstractMvcIntegra BeanFactory beanFactory = Mockito.mock(BeanFactory.class); BDDMockito.given(beanFactory.getBean(SkipPatternProvider.class)) .willThrow(new NoSuchBeanDefinitionException("foo")); + BDDMockito.given(beanFactory.getBean(SleuthProperties.class)).willReturn(this.properties); BDDMockito.given(beanFactory.getBean(Tracer.class)).willReturn(this.tracer); BDDMockito.given(beanFactory.getBean(TraceKeys.class)).willReturn(this.traceKeys); BDDMockito.given(beanFactory.getBean(HttpSpanExtractor.class)).willReturn(this.spanExtractor); diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterMockChainIntegrationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterMockChainIntegrationTests.java index 644f05ecc..072cce768 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterMockChainIntegrationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterMockChainIntegrationTests.java @@ -30,6 +30,7 @@ import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.SpanReporter; import org.springframework.cloud.sleuth.TraceKeys; import org.springframework.cloud.sleuth.Tracer; +import org.springframework.cloud.sleuth.autoconfig.SleuthProperties; import org.springframework.cloud.sleuth.log.NoOpSpanLogger; import org.springframework.cloud.sleuth.sampler.AlwaysSampler; import org.springframework.cloud.sleuth.trace.DefaultTracer; @@ -54,6 +55,7 @@ public class TraceFilterMockChainIntegrationTests { new Random(), new DefaultSpanNamer(), new NoOpSpanLogger(), new NoOpSpanReporter(), new TraceKeys()); private TraceKeys traceKeys = new TraceKeys(); + private SleuthProperties properties = new SleuthProperties(); private HttpTraceKeysInjector keysInjector = new HttpTraceKeysInjector(this.tracer, this.traceKeys); private MockHttpServletRequest request; @@ -94,6 +96,7 @@ public class TraceFilterMockChainIntegrationTests { private BeanFactory beanFactory() { BeanFactory beanFactory = Mockito.mock(BeanFactory.class); + BDDMockito.given(beanFactory.getBean(SleuthProperties.class)).willReturn(this.properties); BDDMockito.given(beanFactory.getBean(Tracer.class)).willReturn(this.tracer); BDDMockito.given(beanFactory.getBean(TraceKeys.class)).willReturn(this.traceKeys); BDDMockito.given(beanFactory.getBean(HttpSpanExtractor.class)) diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterTests.java index 13a94b511..4b716c92a 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterTests.java @@ -38,6 +38,7 @@ import org.springframework.cloud.sleuth.SpanReporter; import org.springframework.cloud.sleuth.TraceKeys; import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.assertions.ListOfSpans; +import org.springframework.cloud.sleuth.autoconfig.SleuthProperties; import org.springframework.cloud.sleuth.log.SpanLogger; import org.springframework.cloud.sleuth.sampler.AlwaysSampler; import org.springframework.cloud.sleuth.sampler.NeverSampler; @@ -67,7 +68,6 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder public class TraceFilterTests { public static final long PARENT_ID = 10L; - public static final String PARENT_ID_AS_STRING = String.valueOf(PARENT_ID); @Mock SpanLogger spanLogger; ArrayListSpanAccumulator spanReporter = new ArrayListSpanAccumulator(); @@ -76,6 +76,7 @@ public class TraceFilterTests { private Tracer tracer; private TraceKeys traceKeys = new TraceKeys(); + private SleuthProperties properties = new SleuthProperties(); private HttpTraceKeysInjector httpTraceKeysInjector; private Span span; @@ -200,15 +201,6 @@ public class TraceFilterTests { then(ExceptionUtils.getLastException()).isNull(); } - private Span parentSpan() { - Optional parent = this.spanReporter.getSpans().stream() - .filter(span -> Span.idToHex(span.getSpanId()).equals(PARENT_ID_AS_STRING) - || span.getName().equals("http:/parent/")) - .findFirst(); - assertThat(parent.isPresent()).isTrue(); - return parent.get(); - } - @Test public void continuesSpanInRequestAttr() throws Exception { Span span = this.tracer.createSpan("http:foo"); @@ -268,6 +260,21 @@ public class TraceFilterTests { then(TestSpanContextHolder.getCurrentSpan()).isNull(); } + @Test + public void createsChildFromHeadersWhenJoinUnsupported() throws Exception { + this.properties.setSupportsJoin(false); + this.request = builder().header(Span.SPAN_ID_NAME, PARENT_ID) + .header(Span.TRACE_ID_NAME, 20L).buildRequest(new MockServletContext()); + BeanFactory beanFactory = beanFactory(); + BDDMockito.given(beanFactory.getBean(SpanReporter.class)).willReturn(this.spanReporter); + + TraceFilter filter = new TraceFilter(beanFactory); + filter.doFilter(this.request, this.response, this.filterChain); + + assertThat(this.spanReporter.getSpans().get(0).getParents().get(0)) + .isEqualTo(16); // test data is in hex! + } + @Test public void addsAdditionalHeaders() throws Exception { this.request = builder().header(Span.SPAN_ID_NAME, PARENT_ID) @@ -556,6 +563,7 @@ public class TraceFilterTests { private BeanFactory beanFactory() { BDDMockito.given(beanFactory.getBean(SkipPatternProvider.class)) .willThrow(new NoSuchBeanDefinitionException("foo")); + BDDMockito.given(beanFactory.getBean(SleuthProperties.class)).willReturn(this.properties); BDDMockito.given(beanFactory.getBean(Tracer.class)).willReturn(this.tracer); BDDMockito.given(beanFactory.getBean(TraceKeys.class)).willReturn(this.traceKeys); BDDMockito.given(beanFactory.getBean(HttpSpanExtractor.class)).willReturn(this.spanExtractor); diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/common/AbstractMvcIntegrationTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/common/AbstractMvcIntegrationTest.java index 7e9b4a174..e1c8b3edf 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/common/AbstractMvcIntegrationTest.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/common/AbstractMvcIntegrationTest.java @@ -2,6 +2,7 @@ package org.springframework.cloud.sleuth.instrument.web.common; import org.junit.Before; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cloud.sleuth.autoconfig.SleuthProperties; import org.springframework.cloud.sleuth.instrument.web.HttpSpanExtractor; import org.springframework.cloud.sleuth.TraceKeys; import org.springframework.cloud.sleuth.Tracer; @@ -30,6 +31,7 @@ public abstract class AbstractMvcIntegrationTest { @Autowired protected WebApplicationContext webApplicationContext; protected MockMvc mockMvc; + @Autowired protected SleuthProperties properties; @Autowired protected Tracer tracer; @Autowired protected TraceKeys traceKeys; @Autowired protected HttpSpanExtractor spanExtractor; diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/trace/DefaultTracerTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/trace/DefaultTracerTests.java index d60d6e06a..c2507de39 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/trace/DefaultTracerTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/trace/DefaultTracerTests.java @@ -16,6 +16,7 @@ package org.springframework.cloud.sleuth.trace; +import java.util.Date; import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -223,6 +224,20 @@ public class DefaultTracerTests { tracer.close(span); } + /** + * To support conversion to Amazon trace IDs, the first 32 bits of the trace ID are epoch seconds. + */ + @Test + public void creates128bitTraceIdWithEncodedTimestamp() { + DefaultTracer tracer = new DefaultTracer(new AlwaysSampler(), new Random(), + this.spanNamer, this.spanLogger, this.spanReporter, true, new TraceKeys()); + Span span = tracer.createSpan(bigName()); + String traceId = span.traceIdString(); + long epochSeconds = Long.parseLong(traceId.substring(0, 8), 16); + then(new Date(epochSeconds * 1000)).isToday(); + tracer.close(span); + } + @Test public void shouldCreateChildOfSpanWithShortenedName() { DefaultTracer tracer = new DefaultTracer(new AlwaysSampler(), new Random(),