From c965b8287095750458085df38a9efa58f92b4879 Mon Sep 17 00:00:00 2001 From: Tomasz Dziurko Date: Sat, 25 Jun 2016 10:29:01 +0200 Subject: [PATCH 001/235] Introduce assertj assertions (#315) * Introduce assertJ assertions in some tests * Fix code formatting to be inline with Spring rules --- .../sleuth/assertions/ListOfSpansAssert.java | 8 ++--- .../WebClientDiscoveryExceptionTests.java | 10 +++---- .../sleuth/trace/DefaultTracerTests.java | 30 ++++++++----------- 3 files changed, 21 insertions(+), 27 deletions(-) diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/ListOfSpansAssert.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/ListOfSpansAssert.java index cd3e6ee98..22408ad03 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/ListOfSpansAssert.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/ListOfSpansAssert.java @@ -24,7 +24,6 @@ import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.assertj.core.api.AbstractAssert; -import org.assertj.core.api.Assertions; import org.springframework.cloud.sleuth.Span; import com.fasterxml.jackson.core.JsonProcessingException; @@ -32,6 +31,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.toList; +import static org.assertj.core.api.Assertions.assertThat; public class ListOfSpansAssert extends AbstractAssert { @@ -60,7 +60,7 @@ public class ListOfSpansAssert extends AbstractAssert "id as long [" + span + "] and as hex [" + Span.idToHex(span) + "]").collect( joining("\n"))); - Assertions.assertThat(spanIds).containsAll(parentSpanIds); + assertThat(spanIds).containsAll(parentSpanIds); return this; } @@ -70,12 +70,12 @@ public class ListOfSpansAssert extends AbstractAssert matchingSpans = this.actual.spans.stream() .filter(span -> span.getName().equals(name) && span.logs().stream().filter(entry -> entry.getEvent().equals(Span.CLIENT_SEND)).findAny().isPresent()).collect(toList()); - Assertions.assertThat(matchingSpans).isNotEmpty(); + assertThat(matchingSpans).isNotEmpty(); List> matchingSpansTags = matchingSpans.stream().map(Span::tags).collect( toList()); Map spanTags = new HashMap<>(); matchingSpansTags.forEach(spanTags::putAll); - Assertions.assertThat(spanTags.entrySet()).containsAll(tags.entrySet()); + assertThat(spanTags.entrySet()).containsAll(tags.entrySet()); return this; } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/WebClientDiscoveryExceptionTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/WebClientDiscoveryExceptionTests.java index 971fbd653..10bf439c4 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/WebClientDiscoveryExceptionTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/WebClientDiscoveryExceptionTests.java @@ -19,8 +19,8 @@ package org.springframework.cloud.sleuth.instrument.web.client; import java.io.IOException; import java.util.Map; +import org.assertj.core.api.Assertions; import org.junit.After; -import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -49,9 +49,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.client.RestTemplate; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.nullValue; -import static org.junit.Assert.assertThat; +import static org.assertj.core.api.Assertions.assertThat; @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = { @@ -84,12 +82,12 @@ public class WebClientDiscoveryExceptionTests { try { provider.get(this); - Assert.fail("should throw an exception"); + Assertions.fail("should throw an exception"); } catch (RuntimeException e) { } - assertThat(ExceptionUtils.getLastException(), is(nullValue())); + assertThat(ExceptionUtils.getLastException()).isNull(); SleuthAssertions.then(this.tracer.getCurrentSpan()).isEqualTo(span); this.tracer.close(span); 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 49448a939..74058665e 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 @@ -30,14 +30,11 @@ import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.SpanNamer; import org.springframework.cloud.sleuth.SpanReporter; import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.assertions.SleuthAssertions; import org.springframework.cloud.sleuth.log.SpanLogger; import org.springframework.cloud.sleuth.sampler.AlwaysSampler; import org.springframework.cloud.sleuth.sampler.NeverSampler; -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertThat; +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -93,14 +90,14 @@ public class DefaultTracerTests { List spans = new ArrayList<>(captor.getAllValues()); - assertThat("spans was wrong size", spans.size(), is(NUM_SPANS)); + assertThat(spans).hasSize(NUM_SPANS); Span root = assertSpan(spans, null, CREATE_SIMPLE_TRACE); Span child = assertSpan(spans, root.getSpanId(), IMPORTANT_WORK_1); Span grandChild = assertSpan(spans, child.getSpanId(), IMPORTANT_WORK_2); List gen4 = findSpans(spans, grandChild.getSpanId()); - assertThat("gen4 was non-empty", gen4.isEmpty(), is(true)); + assertThat(gen4).isEmpty(); } @Test @@ -108,7 +105,7 @@ public class DefaultTracerTests { DefaultTracer tracer = new DefaultTracer(NeverSampler.INSTANCE, new Random(), this.spanNamer, this.spanLogger, this.spanReporter); Span span = tracer.createSpan(CREATE_SIMPLE_TRACE); - assertThat(span.isExportable(), is(false)); + assertThat(span.isExportable()).isFalse(); } @Test @@ -116,7 +113,7 @@ public class DefaultTracerTests { DefaultTracer tracer = new DefaultTracer(new AlwaysSampler(), new Random(), this.spanNamer, this.spanLogger, this.spanReporter); Span span = tracer.createSpan(CREATE_SIMPLE_TRACE); - assertThat(span.isExportable(), is(true)); + assertThat(span.isExportable()).isTrue(); } @Test @@ -124,9 +121,9 @@ public class DefaultTracerTests { DefaultTracer tracer = new DefaultTracer(new AlwaysSampler(), new Random(), this.spanNamer, this.spanLogger, this.spanReporter); Span span = tracer.createSpan(CREATE_SIMPLE_TRACE, NeverSampler.INSTANCE); - assertThat(span.isExportable(), is(false)); + assertThat(span.isExportable()).isFalse(); Span child = tracer.createSpan(CREATE_SIMPLE_TRACE_SPAN_NAME + "/child", span); - assertThat(child.isExportable(), is(false)); + assertThat(child.isExportable()).isFalse(); } @Test @@ -136,7 +133,7 @@ public class DefaultTracerTests { Span parent = tracer.createSpan(CREATE_SIMPLE_TRACE); Span span = tracer.createSpan(IMPORTANT_WORK_1, parent); tracer.close(span); - assertThat(tracer.getCurrentSpan(), is(equalTo(parent))); + assertThat(tracer.getCurrentSpan()).isEqualTo(parent); } @Test @@ -147,7 +144,7 @@ public class DefaultTracerTests { .build(); Span span = tracer.createSpan(IMPORTANT_WORK_1, parent); tracer.close(span); - assertThat(tracer.getCurrentSpan(), is(equalTo(null))); + assertThat(tracer.getCurrentSpan()).isNull(); } @Test @@ -159,7 +156,7 @@ public class DefaultTracerTests { .build(); Span span = tracer.createSpan(IMPORTANT_WORK_2, parent); tracer.close(span); - assertThat(tracer.getCurrentSpan(), is(equalTo(grandParent))); + assertThat(tracer.getCurrentSpan()).isEqualTo(grandParent); } @Test @@ -169,7 +166,7 @@ public class DefaultTracerTests { Span span = tracer.createChild(null, "childName"); - SleuthAssertions.assertThat(span.isExportable()).isFalse(); + assertThat(span.isExportable()).isFalse(); } @Test @@ -190,10 +187,9 @@ public class DefaultTracerTests { private Span assertSpan(List spans, Long parentId, String name) { List found = findSpans(spans, parentId); - assertThat("more than one span with parentId " + parentId, found.size(), is(1)); + assertThat(found).as("More than one span with parentId %s", parentId).hasSize(1); Span span = found.get(0); - assertThat("name is wrong for span with parentId " + parentId, span.getName(), - is(name)); + assertThat(span.getName()).as("Name should be %s", name).isEqualTo(name); return span; } From ed66870290a55f2c2a25b95644ff74d6b313c4d0 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Mon, 27 Jun 2016 15:46:54 +0200 Subject: [PATCH 002/235] Fixed integration tests to use ZipkinRule --- .../java/tools/AbstractIntegrationTest.java | 10 ---- .../spring-cloud-sleuth-sample-zipkin/pom.xml | 5 ++ .../test/java/integration/ZipkinTests.java | 48 ++----------------- 3 files changed, 10 insertions(+), 53 deletions(-) diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/src/main/java/tools/AbstractIntegrationTest.java b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/src/main/java/tools/AbstractIntegrationTest.java index 346a0383a..34c5e79a1 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/src/main/java/tools/AbstractIntegrationTest.java +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/src/main/java/tools/AbstractIntegrationTest.java @@ -70,10 +70,6 @@ public abstract class AbstractIntegrationTest { return Awaitility.await().pollInterval(POLL_INTERVAL, SECONDS).atMost(TIMEOUT, SECONDS); } - protected Runnable zipkinQueryServerIsUp() { - return checkServerHealth("Zipkin Query Server", this::endpointToCheckZipkinQueryHealth); - } - protected Runnable zipkinServerIsUp() { return checkServerHealth("Zipkin Stream Server", this::endpointToCheckZipkinServerHealth); } @@ -91,12 +87,6 @@ public abstract class AbstractIntegrationTest { ResponseEntity exchange(); } - protected ResponseEntity endpointToCheckZipkinQueryHealth() { - URI uri = URI.create(getZipkinServicesQueryUrl()); - log.info(String.format("Sending request to the Zipkin query service [%s]", uri)); - return exchangeRequest(uri); - } - protected ResponseEntity endpointToCheckZipkinServerHealth() { URI uri = URI.create("http://localhost:" +getZipkinServerPort()+"/health"); log.info(String.format("Sending request to the Zipkin Server [%s]", uri)); diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml index 5fb7d4731..8deb7423c 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml @@ -102,6 +102,11 @@ zipkin-server test + + io.zipkin.java + zipkin-junit + test + diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/src/test/java/integration/ZipkinTests.java b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/src/test/java/integration/ZipkinTests.java index c8dc7abd3..e82565de5 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/src/test/java/integration/ZipkinTests.java +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/src/test/java/integration/ZipkinTests.java @@ -18,9 +18,7 @@ package integration; import java.net.URI; import java.util.Random; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.junit.Before; +import org.junit.ClassRule; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; @@ -29,20 +27,17 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.boot.test.WebIntegrationTest; -import org.springframework.cloud.sleuth.metric.SpanMetricReporter; -import org.springframework.cloud.sleuth.zipkin.HttpZipkinSpanReporter; import org.springframework.cloud.sleuth.zipkin.ZipkinProperties; -import org.springframework.cloud.sleuth.zipkin.ZipkinSpanReporter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.util.SocketUtils; import integration.ZipkinTests.WaitUntilZipkinIsUpConfig; import sample.SampleZipkinApplication; import tools.AbstractIntegrationTest; +import zipkin.junit.ZipkinRule; import zipkin.server.EnableZipkinServer; @RunWith(SpringJUnit4ClassRunner.class) @@ -52,18 +47,14 @@ import zipkin.server.EnableZipkinServer; @TestPropertySource(properties = {"sample.zipkin.enabled=true"}) public class ZipkinTests extends AbstractIntegrationTest { + @ClassRule public static final ZipkinRule zipkin = new ZipkinRule(); + private static final String APP_NAME = "testsleuthzipkin"; @Value("${local.server.port}") private int port = 3380; private String sampleAppUrl = "http://localhost:" + this.port; @Autowired ZipkinProperties zipkinProperties; - @Before - public void setup() { - ZipkinServer.main(new String[] { "--server.port=" + getPortFromProps() }); - await().until(zipkinQueryServerIsUp()); - } - @Override protected int getZipkinServerPort() { return getPortFromProps(); } @@ -90,42 +81,13 @@ public class ZipkinTests extends AbstractIntegrationTest { @Configuration public static class WaitUntilZipkinIsUpConfig { - private static final Log log = LogFactory.getLog(WaitUntilZipkinIsUpConfig.class); - @Bean @Primary ZipkinProperties testZipkinProperties() { - int freePort = SocketUtils.findAvailableTcpPort(); ZipkinProperties zipkinProperties = new ZipkinProperties(); - zipkinProperties.setBaseUrl("http://localhost:" + freePort); + zipkinProperties.setBaseUrl(zipkin.httpUrl()); return zipkinProperties; } - - @Bean - public ZipkinSpanReporter spanCollector(final ZipkinProperties zipkin, - final SpanMetricReporter spanMetricReporter) { - await().until(new Runnable() { - @Override - public void run() { - try { - WaitUntilZipkinIsUpConfig.this.getSpanCollector(zipkin, - spanMetricReporter); - } - catch (Exception e) { - log.error("Exception occurred while trying to connect to zipkin [" - + e.getCause() + "]"); - throw new AssertionError(e); - } - } - }); - return getSpanCollector(zipkin, spanMetricReporter); - } - - private ZipkinSpanReporter getSpanCollector(ZipkinProperties zipkin, - SpanMetricReporter spanMetricReporter) { - return new HttpZipkinSpanReporter(zipkin.getBaseUrl(), zipkin.getFlushInterval(), - zipkin.getCompression().isEnabled(), spanMetricReporter); - } } @SpringBootApplication From c2ab0d59cfa247999f43454b92419118ebd81b9a Mon Sep 17 00:00:00 2001 From: Adrian Cole Date: Wed, 29 Jun 2016 16:42:02 +0800 Subject: [PATCH 003/235] Exposes getAccumulatedMicros for more precise Span duration (#317) Formerly, `Span.getAccumulatedMillis()` worked, but could returns imprecise measurements, particularly local spans. This changes the internals of Span to keep track of a start tick. Using this, it can return a more precise `Span.getAccumulatedMicros()`. To ensure this precision isn't lost in serialization, this adds a json field `durationMicros`, which is only set when the span is stopped. This is set instead of start tick because `System.nanoTime()` is JVM specific and so cannot be used across the network. `durationMicros` uses null instead of zero comparisons because nano time can be negative. Fixes #312 --- .../springframework/cloud/sleuth/Span.java | 69 ++++++++++++++++--- .../cloud/sleuth/trace/DefaultTracer.java | 6 +- .../cloud/sleuth/SpanTests.java | 21 ++++++ .../stream/ConvertToZipkinSpanList.java | 4 +- .../stream/ConvertToZipkinSpanListTests.java | 33 +++++++++ .../sleuth/zipkin/ZipkinSpanListener.java | 4 +- .../zipkin/ZipkinSpanListenerTests.java | 15 +++- 7 files changed, 134 insertions(+), 18 deletions(-) diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java index 5034aad0f..29b8a0d59 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java @@ -145,6 +145,11 @@ public class Span { private final List logs; private final Span savedSpan; + // Null means we don't know the start tick, so fallback to time + @JsonIgnore + private final Long startNanos; + private Long durationMicros; // serialized in json so micros precision isn't lost + @SuppressWarnings("unused") private Span() { this(-1, -1, "dummy", 0, Collections.emptyList(), 0, false, false, null); @@ -167,6 +172,8 @@ public class Span { this.processId = current.getProcessId(); this.tags = current.tags; this.logs = current.logs; + this.startNanos = current.startNanos; + this.durationMicros = current.durationMicros; this.savedSpan = savedSpan; } @@ -179,8 +186,17 @@ public class Span { public Span(long begin, long end, String name, long traceId, List parents, long spanId, boolean remote, boolean exportable, String processId, Span savedSpan) { - this.begin = begin <= 0 ? System.currentTimeMillis() : begin; - this.end = end; + if (begin > 0) { // conventionally, 0 indicates unset + this.startNanos = null; // don't know the start tick + this.begin = begin; + } else { + this.startNanos = System.nanoTime(); + this.begin = System.currentTimeMillis(); + } + if (end > 0) { + this.end = end; + this.durationMicros = (end - begin) * 1000; + } this.name = name != null ? name : ""; this.traceId = traceId; this.parents = parents; @@ -201,28 +217,52 @@ public class Span { * The block has completed, stop the clock */ public synchronized void stop() { - if (this.end == 0) { + if (this.durationMicros == null) { if (this.begin == 0) { throw new IllegalStateException( "Span for " + this.name + " has not been started"); } - this.end = System.currentTimeMillis(); + if (this.end == 0) { + this.end = System.currentTimeMillis(); + } + if (this.startNanos != null) { // set a precise duration + this.durationMicros = (System.nanoTime() - this.startNanos) / 1000; + } else { + this.durationMicros = (this.end - this.begin) * 1000; + } } } /** * Return the total amount of time elapsed since start was called, if running, or * difference between stop and start + * + * @deprecated use {@link #getAccumulatedMicros()} as it is more precise. */ + @Deprecated @JsonIgnore public synchronized long getAccumulatedMillis() { - if (this.begin == 0) { - return 0; + return getAccumulatedMicros() / 1000; + } + + /** + * Return the total amount of time elapsed since start was called, if running, or + * difference between stop and start, in microseconds. + */ + @JsonIgnore + public synchronized long getAccumulatedMicros() { + if (this.durationMicros != null) { + return this.durationMicros; + } else { // stop() hasn't yet been called + if (this.begin == 0) { + return 0; + } + if (this.startNanos != null) { + return (System.nanoTime() - this.startNanos) / 1000; + } else { + return (System.currentTimeMillis() - this.begin) * 1000; + } } - if (this.end > 0) { - return this.end - this.begin; - } - return System.currentTimeMillis() - this.begin; } /** @@ -230,7 +270,7 @@ public class Span { */ @JsonIgnore public synchronized boolean isRunning() { - return this.begin != 0 && this.end == 0; + return this.begin != 0 && this.durationMicros == null; } /** @@ -430,6 +470,13 @@ public class Span { SpanBuilder() { } + /** + * Call this to record a begin time of a Span you didn't start. Don't call this when you are + * starting the span. + * + *

In other words, don't call {@code builder.begin(System.currentTimeMillis());}. doing so is + * redundant and will result in less precision when calculating elapsed time. + */ public Span.SpanBuilder begin(long begin) { this.begin = begin; return this; 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 b4bb2821b..e1bc938b6 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 @@ -77,7 +77,7 @@ public class DefaultTracer implements Tracer { } else { long id = createId(); - span = Span.builder().begin(System.currentTimeMillis()).name(name).traceId(id) + span = Span.builder().name(name).traceId(id) .spanId(id).build(); if (sampler==null) { sampler = this.defaultSampler; @@ -141,7 +141,7 @@ public class DefaultTracer implements Tracer { protected Span createChild(Span parent, String name) { long id = createId(); if (parent == null) { - Span span = Span.builder().begin(System.currentTimeMillis()).name(name) + Span span = Span.builder().name(name) .traceId(id).spanId(id).build(); span = sampledSpan(name, id, span, this.defaultSampler); this.spanLogger.logStartedSpan(null, span); @@ -151,7 +151,7 @@ public class DefaultTracer implements Tracer { if (!isTracing()) { SpanContextHolder.push(parent, true); } - Span span = Span.builder().begin(System.currentTimeMillis()).name(name) + Span span = Span.builder().name(name) .traceId(parent.getTraceId()).parent(parent.getSpanId()).spanId(id) .processId(parent.getProcessId()).savedSpan(parent) .exportable(parent.isExportable()).build(); diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/SpanTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/SpanTests.java index c8eb2a071..1021a6213 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/SpanTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/SpanTests.java @@ -24,6 +24,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.Test; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.BDDAssertions.then; /** @@ -114,4 +115,24 @@ public class SpanTests { public void should_throw_exception_when_converting_invalid_hex_value() { Span.hexToId("invalid"); } + + /** When going over a transport like spring-cloud-stream, we must retain the precise duration. */ + @Test public void shouldSerializeDurationMicros() throws IOException { + Span span = Span.builder().traceId(1L).name("http:parent").remote(true).build(); + span.stop(); + + assertThat(span.getAccumulatedMicros()) + .isGreaterThan(0L); // sanity check + + ObjectMapper objectMapper = new ObjectMapper(); + + String serialized = objectMapper.writeValueAsString(span); + assertThat(serialized) + .contains("\"durationMicros\""); + + Span deserialized = objectMapper.readValue(serialized, Span.class); + + assertThat(deserialized.getAccumulatedMicros()) + .isEqualTo(span.getAccumulatedMicros()); + } } diff --git a/spring-cloud-sleuth-zipkin-stream/src/main/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanList.java b/spring-cloud-sleuth-zipkin-stream/src/main/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanList.java index 6d6da4ae2..b086c876f 100644 --- a/spring-cloud-sleuth-zipkin-stream/src/main/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanList.java +++ b/spring-cloud-sleuth-zipkin-stream/src/main/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanList.java @@ -90,7 +90,9 @@ final class ConvertToZipkinSpanList { ensureServerAddr(span, zipkinSpan, ep); } zipkinSpan.timestamp(span.getBegin() * 1000); - zipkinSpan.duration(span.getAccumulatedMillis() * 1000); + if (!span.isRunning()) { // duration is authoritative, only write when the span stopped + zipkinSpan.duration(span.getAccumulatedMicros()); + } zipkinSpan.traceId(span.getTraceId()); if (span.getParents().size() > 0) { if (span.getParents().size() > 1) { diff --git a/spring-cloud-sleuth-zipkin-stream/src/test/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanListTests.java b/spring-cloud-sleuth-zipkin-stream/src/test/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanListTests.java index ae04abac9..ff282ff70 100644 --- a/spring-cloud-sleuth-zipkin-stream/src/test/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanListTests.java +++ b/spring-cloud-sleuth-zipkin-stream/src/test/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanListTests.java @@ -108,6 +108,39 @@ public class ConvertToZipkinSpanListTests { .contains("barservice"); } + /** Sleuth timestamps are millisecond granularity while zipkin is microsecond. */ + @Test + public void convertsTimestampToMicrosecondsAndSetsDurationToAccumulatedMicros() { + long start = System.currentTimeMillis(); + Span span = span("foo"); + span.logEvent(Constants.CLIENT_SEND); + span.stop(); + + Spans spans = new Spans(this.host, Collections.singletonList(span)); + zipkin.Span result = ConvertToZipkinSpanList.convert(spans).get(0); + + assertThat(result.timestamp) + .isEqualTo(span.getBegin() * 1000); + assertThat(result.duration) + .isEqualTo(span.getAccumulatedMicros()); + assertThat(result.annotations.get(0).timestamp) + .isGreaterThanOrEqualTo(start * 1000) + .isLessThanOrEqualTo(System.currentTimeMillis() * 1000); + } + + /** Zipkin's duration should only be set when the span is finished. */ + @Test + public void doesntSetDurationWhenStillRunning() { + Span running = Span.builder().traceId(1L).name("http:parent").remote(true).build(); + Spans spans = new Spans(this.host, Collections.singletonList(running)); + zipkin.Span result = ConvertToZipkinSpanList.convert(spans).get(0); + + assertThat(result.timestamp) + .isGreaterThan(0); // sanity check it did start + assertThat(result.duration) + .isNull(); + } + Span span(String name) { Long id = new Random().nextLong(); return new Span(1, 3, "message:" + name, id, Collections.emptyList(), id, true, true, diff --git a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListener.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListener.java index 2fa339d3c..36858de6a 100644 --- a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListener.java +++ b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListener.java @@ -86,7 +86,9 @@ public class ZipkinSpanListener implements SpanReporter { ensureServerAddr(span, zipkinSpan); } zipkinSpan.timestamp(span.getBegin() * 1000L); - zipkinSpan.duration(span.getAccumulatedMillis() * 1000L); + if (!span.isRunning()) { // duration is authoritative, only write when the span stopped + zipkinSpan.duration(span.getAccumulatedMicros()); + } zipkinSpan.traceId(span.getTraceId()); if (span.getParents().size() > 0) { if (span.getParents().size() > 1) { diff --git a/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListenerTests.java b/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListenerTests.java index ad8630173..1b9a2cb4a 100644 --- a/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListenerTests.java +++ b/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListenerTests.java @@ -64,7 +64,7 @@ public class ZipkinSpanListenerTests { /** Sleuth timestamps are millisecond granularity while zipkin is microsecond. */ @Test - public void convertsTimestampAndDurationToMicroseconds() { + public void convertsTimestampToMicrosecondsAndSetsDurationToAccumulatedMicros() { long start = System.currentTimeMillis(); this.parent.logEvent("hystrix/retry"); // System.currentTimeMillis this.parent.stop(); @@ -74,12 +74,23 @@ public class ZipkinSpanListenerTests { assertThat(result.timestamp) .isEqualTo(this.parent.getBegin() * 1000); assertThat(result.duration) - .isEqualTo((this.parent.getEnd() - this.parent.getBegin()) * 1000); + .isEqualTo(this.parent.getAccumulatedMicros()); assertThat(result.annotations.get(0).timestamp) .isGreaterThanOrEqualTo(start * 1000) .isLessThanOrEqualTo(System.currentTimeMillis() * 1000); } + /** Zipkin's duration should only be set when the span is finished. */ + @Test + public void doesntSetDurationWhenStillRunning() { + zipkin.Span result = this.spanReporter.convert(this.parent); + + assertThat(result.timestamp) + .isGreaterThan(0); // sanity check it did start + assertThat(result.duration) + .isNull(); + } + /** Sleuth host corresponds to annotation/binaryAnnotation.host in zipkin. */ @Test public void annotationsIncludeHost() { From a40450b161c204041fa4f7cfea92cd4308885972 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Thu, 30 Jun 2016 11:20:21 +0200 Subject: [PATCH 004/235] Fixed not clearing spans on messaging exception with this change we're closing spans on afterSendCompletion and not afterPost thus even if an exception is thrown we will close the span fixes #319 --- .../messaging/TraceChannelInterceptor.java | 2 +- .../TraceChannelInterceptorTests.java | 58 +++++++++++++------ 2 files changed, 41 insertions(+), 19 deletions(-) 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 e9f7ef257..bbd7573a7 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 @@ -47,7 +47,7 @@ public class TraceChannelInterceptor extends AbstractTraceChannelInterceptor { } @Override - public void postSend(Message message, MessageChannel channel, boolean sent) { + public void afterSendCompletion(Message message, MessageChannel channel, boolean sent, Exception ex) { Span spanFromHeader = getSpanFromHeader(message); if (containsServerReceived(spanFromHeader)) { spanFromHeader.logEvent(Span.SERVER_SEND); 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 a144fd388..63e57dc67 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/TraceChannelInterceptorTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/TraceChannelInterceptorTests.java @@ -16,6 +16,9 @@ package org.springframework.cloud.sleuth.instrument.messaging; +import java.util.HashMap; +import java.util.Map; + import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -28,6 +31,7 @@ import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.cloud.sleuth.Sampler; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.Tracer; +import org.springframework.cloud.sleuth.assertions.SleuthAssertions; import org.springframework.cloud.sleuth.instrument.messaging.TraceChannelInterceptorTests.App; import org.springframework.cloud.sleuth.sampler.AlwaysSampler; import org.springframework.cloud.sleuth.trace.TestSpanContextHolder; @@ -43,10 +47,8 @@ import org.springframework.messaging.MessagingException; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import static org.assertj.core.api.BDDAssertions.then; -import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; +import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; /** * @author Dave Syer @@ -78,6 +80,9 @@ public class TraceChannelInterceptorTests implements MessageHandler { public void handleMessage(Message message) throws MessagingException { this.message = message; this.span = TestSpanContextHolder.getCurrentSpan(); + if (message.getHeaders().containsKey("THROW_EXCEPTION")) { + throw new RuntimeException(); + } } @Before @@ -108,28 +113,28 @@ public class TraceChannelInterceptorTests implements MessageHandler { this.channel.send(MessageBuilder.withPayload("hi") .setHeader(Span.TRACE_ID_NAME, Span.idToHex(10L)) .setHeader(Span.SPAN_ID_NAME, Span.idToHex(20L)).build()); - assertNotNull("message was null", this.message); + then(this.message).isNotNull(); String spanId = this.message.getHeaders().get(Span.SPAN_ID_NAME, String.class); - assertNotNull("spanId was null", spanId); + 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); - assertEquals(1, this.accumulator.getSpans().size()); + then(this.accumulator.getSpans()).hasSize(1); } @Test public void spanCreation() { this.channel.send(MessageBuilder.withPayload("hi").build()); - assertNotNull("message was null", this.message); + then(this.message).isNotNull(); String spanId = this.message.getHeaders().get(Span.SPAN_ID_NAME, String.class); - assertNotNull("spanId was null", spanId); + then(spanId).isNotNull(); String traceId = this.message.getHeaders().get(Span.TRACE_ID_NAME, String.class); - assertNotNull("traceId was null", traceId); - assertNull(TestSpanContextHolder.getCurrentSpan()); + then(traceId).isNotNull(); + then(TestSpanContextHolder.getCurrentSpan()).isNull(); } @Test @@ -151,14 +156,14 @@ public class TraceChannelInterceptorTests implements MessageHandler { Span span = this.tracer.createSpan("http:testSendMessage", new AlwaysSampler()); this.channel.send(MessageBuilder.withPayload("hi").build()); this.tracer.close(span); - assertNotNull("message was null", this.message); + then(this.message).isNotNull(); String spanId = this.message.getHeaders().get(Span.SPAN_ID_NAME, String.class); - assertNotNull("spanId was null", spanId); + then(spanId).isNotNull(); String traceId = this.message.getHeaders().get(Span.TRACE_ID_NAME, String.class); - assertNotNull("traceId was null", traceId); - assertNull(TestSpanContextHolder.getCurrentSpan()); + then(traceId).isNotNull(); + then(TestSpanContextHolder.getCurrentSpan()).isNull(); } // TODO: Refactor to parametrized test together with sending messages via channel @@ -166,15 +171,32 @@ public class TraceChannelInterceptorTests implements MessageHandler { public void headerCreationViaMessagingTemplate() { Span span = this.tracer.createSpan("http:testSendMessage", new AlwaysSampler()); this.messagingTemplate.send(MessageBuilder.withPayload("hi").build()); + this.tracer.close(span); - assertNotNull("message was null", this.message); + then(this.message).isNotNull(); String spanId = this.message.getHeaders().get(Span.SPAN_ID_NAME, String.class); - assertNotNull("spanId was null", spanId); + then(spanId).isNotNull(); String traceId = this.message.getHeaders().get(Span.TRACE_ID_NAME, String.class); - assertNotNull("traceId was null", traceId); - assertNull(TestSpanContextHolder.getCurrentSpan()); + then(traceId).isNotNull(); + then(TestSpanContextHolder.getCurrentSpan()).isNull(); + } + + @Test + public void shouldCloseASpanWhenExceptionOccurred() { + Span span = this.tracer.createSpan("http:testSendMessage", new AlwaysSampler()); + Map errorHeaders = new HashMap<>(); + errorHeaders.put("THROW_EXCEPTION", "TRUE"); + + try { + this.messagingTemplate.send(MessageBuilder.withPayload("hi").copyHeaders(errorHeaders).build()); + SleuthAssertions.fail("Exception should occur"); + } catch (RuntimeException e) {} + + then(this.message).isNotNull(); + this.tracer.close(span); + then(TestSpanContextHolder.getCurrentSpan()).isNull(); } @Configuration From 1964d1811372800c4867eeb876958234d0efaaca Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Thu, 30 Jun 2016 11:48:21 +0200 Subject: [PATCH 005/235] Fixed the hacks for Feign --- .../feign/FeignContextBeanPostProcessor.java | 62 +++++++++++++++++++ .../TraceFeignClientAutoConfiguration.java | 22 ++----- .../web/client/feign/TraceFeignContext.java | 9 ++- 3 files changed, 74 insertions(+), 19 deletions(-) create mode 100644 spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignContextBeanPostProcessor.java diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignContextBeanPostProcessor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignContextBeanPostProcessor.java new file mode 100644 index 000000000..632a6d306 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignContextBeanPostProcessor.java @@ -0,0 +1,62 @@ +/* + * Copyright 2013-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.sleuth.instrument.web.client.feign; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.cloud.netflix.feign.FeignContext; + +/** + * Post processor that wraps Feign Context in its tracing representations. + * + * @author Marcin Grzejszczak + * + * @since 1.0.2 + */ +final class FeignContextBeanPostProcessor implements BeanPostProcessor { + + private final BeanFactory beanFactory; + private TraceFeignObjectWrapper traceFeignObjectWrapper; + + FeignContextBeanPostProcessor(BeanFactory beanFactory) { + this.beanFactory = beanFactory; + } + + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) + throws BeansException { + if (bean instanceof FeignContext) { + return new TraceFeignContext(getTraceFeignObjectWrapper(), + (FeignContext) bean); + } + return bean; + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) + throws BeansException { + return bean; + } + + TraceFeignObjectWrapper getTraceFeignObjectWrapper() { + if (this.traceFeignObjectWrapper == null) { + this.traceFeignObjectWrapper = this.beanFactory.getBean(TraceFeignObjectWrapper.class); + } + return this.traceFeignObjectWrapper; + } +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientAutoConfiguration.java index 74d0cb7d4..504af1167 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientAutoConfiguration.java @@ -18,8 +18,6 @@ package org.springframework.cloud.sleuth.instrument.web.client.feign; import java.io.IOException; import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.List; import com.netflix.hystrix.HystrixCommand; @@ -33,8 +31,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.web.HttpMessageConverters; import org.springframework.cloud.netflix.feign.FeignAutoConfiguration; -import org.springframework.cloud.netflix.feign.FeignClientSpecification; -import org.springframework.cloud.netflix.feign.FeignContext; import org.springframework.cloud.netflix.feign.support.ResponseEntityDecoder; import org.springframework.cloud.netflix.feign.support.SpringDecoder; import org.springframework.cloud.sleuth.SpanInjector; @@ -80,13 +76,18 @@ public class TraceFeignClientAutoConfiguration { } @Configuration + @ConditionalOnProperty(name = "spring.sleuth.feign.processor.enabled", matchIfMissing = true) protected static class FeignBeanPostProcessorConfiguration { @Bean - @ConditionalOnProperty(name = "spring.sleuth.feign.processor.enabled", matchIfMissing = true) FeignBeanPostProcessor feignBeanPostProcessor(TraceFeignObjectWrapper traceFeignObjectWrapper) { return new FeignBeanPostProcessor(traceFeignObjectWrapper); } + + @Bean + FeignContextBeanPostProcessor feignContextBeanPostProcessor(BeanFactory beanFactory) { + return new FeignContextBeanPostProcessor(beanFactory); + } } @Bean @@ -127,17 +128,6 @@ public class TraceFeignClientAutoConfiguration { return new TraceFeignRequestInterceptor(tracer, feignRequestTemplateInjector()); } - @Autowired(required = false) - private List configurations = new ArrayList<>(); - - @Bean - @Primary - FeignContext sleuthFeignContext(TraceFeignObjectWrapper traceFeignObjectWrapper) { - FeignContext feignContext = new TraceFeignContext(traceFeignObjectWrapper); - feignContext.setConfigurations(this.configurations); - return feignContext; - } - private SpanInjector feignRequestTemplateInjector() { return new FeignRequestTemplateInjector(); } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignContext.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignContext.java index c9f88e960..86f977737 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignContext.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignContext.java @@ -15,20 +15,23 @@ import org.springframework.cloud.netflix.feign.FeignContext; class TraceFeignContext extends FeignContext { private final TraceFeignObjectWrapper traceFeignObjectWrapper; + private final FeignContext delegate; - TraceFeignContext(TraceFeignObjectWrapper traceFeignObjectWrapper) { + TraceFeignContext(TraceFeignObjectWrapper traceFeignObjectWrapper, + FeignContext delegate) { this.traceFeignObjectWrapper = traceFeignObjectWrapper; + this.delegate = delegate; } @Override public T getInstance(String name, Class type) { - T object = super.getInstance(name, type); + T object = this.delegate.getInstance(name, type); return (T) this.traceFeignObjectWrapper.wrap(object); } @Override public Map getInstances(String name, Class type) { - Map instances = super.getInstances(name, type); + Map instances = this.delegate.getInstances(name, type); Map convertedInstances = new HashMap<>(); for (Map.Entry entry : instances.entrySet()) { convertedInstances.put(entry.getKey(), (T) this.traceFeignObjectWrapper.wrap(entry.getValue())); From 54c9e45d09261a93fe19cac47b1e8f7fa0bd3267 Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Thu, 30 Jun 2016 17:06:56 -0600 Subject: [PATCH 006/235] Update SNAPSHOT to 1.0.2.RELEASE --- benchmarks/pom.xml | 2 +- docs/pom.xml | 2 +- pom.xml | 2 +- spring-cloud-sleuth-core/pom.xml | 2 +- spring-cloud-sleuth-dependencies/pom.xml | 4 ++-- spring-cloud-sleuth-samples/pom.xml | 2 +- .../spring-cloud-sleuth-sample-feign/pom.xml | 2 +- .../spring-cloud-sleuth-sample-messaging/pom.xml | 2 +- .../spring-cloud-sleuth-sample-ribbon/pom.xml | 2 +- .../spring-cloud-sleuth-sample-stream/pom.xml | 2 +- .../spring-cloud-sleuth-sample-test-core/pom.xml | 2 +- .../spring-cloud-sleuth-sample-websocket/pom.xml | 2 +- .../spring-cloud-sleuth-sample-zipkin-stream/pom.xml | 2 +- .../spring-cloud-sleuth-sample-zipkin/pom.xml | 2 +- .../spring-cloud-sleuth-sample/pom.xml | 2 +- spring-cloud-sleuth-stream/pom.xml | 2 +- spring-cloud-sleuth-zipkin-stream/pom.xml | 2 +- spring-cloud-sleuth-zipkin/pom.xml | 2 +- spring-cloud-starter-sleuth/pom.xml | 2 +- spring-cloud-starter-zipkin/pom.xml | 2 +- 20 files changed, 21 insertions(+), 21 deletions(-) diff --git a/benchmarks/pom.xml b/benchmarks/pom.xml index b35117f38..e9e9d1a55 100644 --- a/benchmarks/pom.xml +++ b/benchmarks/pom.xml @@ -17,7 +17,7 @@ Benchmarks Benchmarks (JMH) org.springframework.cloud - 1.0.2.BUILD-SNAPSHOT + 1.0.2.RELEASE benchmarks diff --git a/docs/pom.xml b/docs/pom.xml index 89f932987..7de2f9073 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.2.BUILD-SNAPSHOT + 1.0.2.RELEASE spring-cloud-sleuth-docs pom diff --git a/pom.xml b/pom.xml index 067576ee5..8d690021a 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 spring-cloud-sleuth - 1.0.2.BUILD-SNAPSHOT + 1.0.2.RELEASE pom Spring Cloud Sleuth Spring Cloud Sleuth diff --git a/spring-cloud-sleuth-core/pom.xml b/spring-cloud-sleuth-core/pom.xml index 699d50fa1..32a2e9dbf 100644 --- a/spring-cloud-sleuth-core/pom.xml +++ b/spring-cloud-sleuth-core/pom.xml @@ -11,7 +11,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.2.BUILD-SNAPSHOT + 1.0.2.RELEASE .. diff --git a/spring-cloud-sleuth-dependencies/pom.xml b/spring-cloud-sleuth-dependencies/pom.xml index 8f25f6aaf..3582831e7 100644 --- a/spring-cloud-sleuth-dependencies/pom.xml +++ b/spring-cloud-sleuth-dependencies/pom.xml @@ -9,12 +9,12 @@ spring-cloud-sleuth-dependencies - 1.0.2.BUILD-SNAPSHOT + 1.0.2.RELEASE pom spring-cloud-sleuth-dependencies Spring Cloud Sleuth Dependencies - 1.1.2.RELEASE + 1.1.3.RELEASE 1.8.4 1.1.5 diff --git a/spring-cloud-sleuth-samples/pom.xml b/spring-cloud-sleuth-samples/pom.xml index eba0a690d..55f48ba6a 100644 --- a/spring-cloud-sleuth-samples/pom.xml +++ b/spring-cloud-sleuth-samples/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.2.BUILD-SNAPSHOT + 1.0.2.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml index 7c2af14aa..3ff6c54ed 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml @@ -20,7 +20,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.2.BUILD-SNAPSHOT + 1.0.2.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml index 4e7503874..8c8387689 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.2.BUILD-SNAPSHOT + 1.0.2.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml index be9cd3359..73bd34ec6 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.2.BUILD-SNAPSHOT + 1.0.2.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml index d202b7b92..fc1c94e4b 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.2.BUILD-SNAPSHOT + 1.0.2.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml index 1abc8da4e..d431cd343 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.2.BUILD-SNAPSHOT + 1.0.2.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml index 403781dcb..74fc11cb8 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.2.BUILD-SNAPSHOT + 1.0.2.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml index dcb57cb89..51426b3c8 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.2.BUILD-SNAPSHOT + 1.0.2.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml index 8deb7423c..9a0d2d53a 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml @@ -27,7 +27,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.2.BUILD-SNAPSHOT + 1.0.2.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml index b3a6603d4..085497356 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.2.BUILD-SNAPSHOT + 1.0.2.RELEASE .. diff --git a/spring-cloud-sleuth-stream/pom.xml b/spring-cloud-sleuth-stream/pom.xml index 5d73cc245..335b3fa75 100644 --- a/spring-cloud-sleuth-stream/pom.xml +++ b/spring-cloud-sleuth-stream/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.2.BUILD-SNAPSHOT + 1.0.2.RELEASE diff --git a/spring-cloud-sleuth-zipkin-stream/pom.xml b/spring-cloud-sleuth-zipkin-stream/pom.xml index 2852d4c25..e5264b1c8 100644 --- a/spring-cloud-sleuth-zipkin-stream/pom.xml +++ b/spring-cloud-sleuth-zipkin-stream/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.2.BUILD-SNAPSHOT + 1.0.2.RELEASE diff --git a/spring-cloud-sleuth-zipkin/pom.xml b/spring-cloud-sleuth-zipkin/pom.xml index 9fc2d7233..e0a2ad610 100644 --- a/spring-cloud-sleuth-zipkin/pom.xml +++ b/spring-cloud-sleuth-zipkin/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.2.BUILD-SNAPSHOT + 1.0.2.RELEASE .. diff --git a/spring-cloud-starter-sleuth/pom.xml b/spring-cloud-starter-sleuth/pom.xml index fc6c238b7..342401b18 100644 --- a/spring-cloud-starter-sleuth/pom.xml +++ b/spring-cloud-starter-sleuth/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.2.BUILD-SNAPSHOT + 1.0.2.RELEASE .. spring-cloud-starter-sleuth diff --git a/spring-cloud-starter-zipkin/pom.xml b/spring-cloud-starter-zipkin/pom.xml index ef0962cb2..5b35ec8c6 100644 --- a/spring-cloud-starter-zipkin/pom.xml +++ b/spring-cloud-starter-zipkin/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.2.BUILD-SNAPSHOT + 1.0.2.RELEASE .. spring-cloud-starter-zipkin From fbefa81d52c5beacdab2ce10cfc22c12ce678e2d Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Thu, 30 Jun 2016 17:30:59 -0600 Subject: [PATCH 007/235] Update to SNAPSHOT --- benchmarks/pom.xml | 2 +- docs/pom.xml | 2 +- pom.xml | 6 +++--- spring-cloud-sleuth-core/pom.xml | 2 +- spring-cloud-sleuth-dependencies/pom.xml | 6 +++--- spring-cloud-sleuth-samples/pom.xml | 2 +- .../spring-cloud-sleuth-sample-feign/pom.xml | 2 +- .../spring-cloud-sleuth-sample-messaging/pom.xml | 2 +- .../spring-cloud-sleuth-sample-ribbon/pom.xml | 2 +- .../spring-cloud-sleuth-sample-stream/pom.xml | 2 +- .../spring-cloud-sleuth-sample-test-core/pom.xml | 2 +- .../spring-cloud-sleuth-sample-websocket/pom.xml | 2 +- .../spring-cloud-sleuth-sample-zipkin-stream/pom.xml | 2 +- .../spring-cloud-sleuth-sample-zipkin/pom.xml | 2 +- .../spring-cloud-sleuth-sample/pom.xml | 2 +- spring-cloud-sleuth-stream/pom.xml | 2 +- spring-cloud-sleuth-zipkin-stream/pom.xml | 2 +- spring-cloud-sleuth-zipkin/pom.xml | 2 +- spring-cloud-starter-sleuth/pom.xml | 2 +- spring-cloud-starter-zipkin/pom.xml | 2 +- 20 files changed, 24 insertions(+), 24 deletions(-) diff --git a/benchmarks/pom.xml b/benchmarks/pom.xml index e9e9d1a55..893f0220a 100644 --- a/benchmarks/pom.xml +++ b/benchmarks/pom.xml @@ -17,7 +17,7 @@ Benchmarks Benchmarks (JMH) org.springframework.cloud - 1.0.2.RELEASE + 1.0.3.BUILD-SNAPSHOT benchmarks diff --git a/docs/pom.xml b/docs/pom.xml index 7de2f9073..97500b55d 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.2.RELEASE + 1.0.3.BUILD-SNAPSHOT spring-cloud-sleuth-docs pom diff --git a/pom.xml b/pom.xml index 8d690021a..fd8f3fe3e 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 spring-cloud-sleuth - 1.0.2.RELEASE + 1.0.3.BUILD-SNAPSHOT pom Spring Cloud Sleuth Spring Cloud Sleuth @@ -13,7 +13,7 @@ org.springframework.cloud spring-cloud-build - 1.1.1.RELEASE + 1.1.2.BUILD-SNAPSHOT @@ -208,7 +208,7 @@ 1.8 2.19.1 2.17 - 1.1.1.RELEASE + 1.1.2.BUILD-SNAPSHOT diff --git a/spring-cloud-sleuth-core/pom.xml b/spring-cloud-sleuth-core/pom.xml index 32a2e9dbf..748c71507 100644 --- a/spring-cloud-sleuth-core/pom.xml +++ b/spring-cloud-sleuth-core/pom.xml @@ -11,7 +11,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.2.RELEASE + 1.0.3.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-dependencies/pom.xml b/spring-cloud-sleuth-dependencies/pom.xml index 3582831e7..38f3704cd 100644 --- a/spring-cloud-sleuth-dependencies/pom.xml +++ b/spring-cloud-sleuth-dependencies/pom.xml @@ -5,16 +5,16 @@ spring-cloud-dependencies-parent org.springframework.cloud - 1.1.1.RELEASE + 1.1.2.BUILD-SNAPSHOT spring-cloud-sleuth-dependencies - 1.0.2.RELEASE + 1.0.3.BUILD-SNAPSHOT pom spring-cloud-sleuth-dependencies Spring Cloud Sleuth Dependencies - 1.1.3.RELEASE + 1.1.4.BUILD-SNAPSHOT 1.8.4 1.1.5 diff --git a/spring-cloud-sleuth-samples/pom.xml b/spring-cloud-sleuth-samples/pom.xml index 55f48ba6a..f490bec60 100644 --- a/spring-cloud-sleuth-samples/pom.xml +++ b/spring-cloud-sleuth-samples/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.2.RELEASE + 1.0.3.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml index 3ff6c54ed..b041761f6 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml @@ -20,7 +20,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.2.RELEASE + 1.0.3.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml index 8c8387689..94abc7617 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.2.RELEASE + 1.0.3.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml index 73bd34ec6..340520845 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.2.RELEASE + 1.0.3.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml index fc1c94e4b..e348e8229 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.2.RELEASE + 1.0.3.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml index d431cd343..e821cc703 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.2.RELEASE + 1.0.3.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml index 74fc11cb8..631a9cbf3 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.2.RELEASE + 1.0.3.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml index 51426b3c8..567671e47 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.2.RELEASE + 1.0.3.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml index 9a0d2d53a..24f24b970 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml @@ -27,7 +27,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.2.RELEASE + 1.0.3.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml index 085497356..b0e59f0e1 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.2.RELEASE + 1.0.3.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-stream/pom.xml b/spring-cloud-sleuth-stream/pom.xml index 335b3fa75..596e9b56f 100644 --- a/spring-cloud-sleuth-stream/pom.xml +++ b/spring-cloud-sleuth-stream/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.2.RELEASE + 1.0.3.BUILD-SNAPSHOT diff --git a/spring-cloud-sleuth-zipkin-stream/pom.xml b/spring-cloud-sleuth-zipkin-stream/pom.xml index e5264b1c8..746add274 100644 --- a/spring-cloud-sleuth-zipkin-stream/pom.xml +++ b/spring-cloud-sleuth-zipkin-stream/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.2.RELEASE + 1.0.3.BUILD-SNAPSHOT diff --git a/spring-cloud-sleuth-zipkin/pom.xml b/spring-cloud-sleuth-zipkin/pom.xml index e0a2ad610..f42b1baa3 100644 --- a/spring-cloud-sleuth-zipkin/pom.xml +++ b/spring-cloud-sleuth-zipkin/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.2.RELEASE + 1.0.3.BUILD-SNAPSHOT .. diff --git a/spring-cloud-starter-sleuth/pom.xml b/spring-cloud-starter-sleuth/pom.xml index 342401b18..57c757fd8 100644 --- a/spring-cloud-starter-sleuth/pom.xml +++ b/spring-cloud-starter-sleuth/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.2.RELEASE + 1.0.3.BUILD-SNAPSHOT .. spring-cloud-starter-sleuth diff --git a/spring-cloud-starter-zipkin/pom.xml b/spring-cloud-starter-zipkin/pom.xml index 5b35ec8c6..aa8ae96ac 100644 --- a/spring-cloud-starter-zipkin/pom.xml +++ b/spring-cloud-starter-zipkin/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.2.RELEASE + 1.0.3.BUILD-SNAPSHOT .. spring-cloud-starter-zipkin From 4f1ef52e6caa11ba982616cc439196fbee1211cc Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Thu, 7 Jul 2016 10:47:22 +0200 Subject: [PATCH 008/235] Re-added missing server side span (#322) * Re-added missing server side span with this change - HandlerInterceptor is responsible for wrapping requests in spans - added Sring Data REST instrumentation - added Zuul instrumentation - it will not wrap error controller related requests with spans - tests have been changed to ensure that ALWAYS there is at least one span on the server side (that way it will show up in Zipkin) - "fallback" mechanism has been added that if a HandlerInterceptor hasn't been used then we are forcing creation of a Span at the server side fixes #321 --- .travis.yml | 21 +- .../main/asciidoc/spring-cloud-sleuth.adoc | 11 +- spring-cloud-sleuth-core/pom.xml | 26 +++ .../cloud/sleuth/TraceKeys.java | 44 +++- .../autoconfig/TraceAutoConfiguration.java | 8 +- .../sleuth/instrument/web/TraceFilter.java | 86 +++++--- .../web/TraceHandlerInterceptor.java | 186 +++++++++++++++++ .../web/TraceRequestAttributes.java | 42 ++++ .../web/TraceSpringDataBeanPostProcessor.java | 96 +++++++++ .../sleuth/instrument/web/TraceWebAspect.java | 20 -- .../web/TraceWebAutoConfiguration.java | 14 +- .../instrument/web/TraceWebMvcConfigurer.java | 46 +++++ .../instrument/zuul/TracePostZuulFilter.java | 15 +- .../instrument/zuul/TracePreZuulFilter.java | 17 +- .../zuul/TraceZuulAutoConfiguration.java | 6 + ...ceZuulHandlerMappingBeanPostProcessor.java | 114 ++++++++++ .../cloud/sleuth/util/SpanNameUtil.java | 6 +- .../cloud/sleuth/assertions/ListOfSpans.java | 3 +- .../sleuth/assertions/ListOfSpansAssert.java | 77 ++++++- .../cloud/sleuth/assertions/SpanAssert.java | 13 ++ ...stTemplateTraceAspectIntegrationTests.java | 6 + .../web/SpringDataInstrumentationTests.java | 194 ++++++++++++++++++ .../web/TraceFilterIntegrationTests.java | 17 ++ .../instrument/web/TraceFilterTests.java | 6 +- .../web/TraceFilterWebIntegrationTests.java | 119 +++++++++++ ...stTemplateInterceptorIntegrationTests.java | 4 + .../WebClientDiscoveryExceptionTests.java | 3 +- .../instrument/web/client/WebClientTests.java | 21 +- .../feign/FeignClientServerErrorTests.java | 5 + .../client/feign/issues/Issue307Tests.java | 11 + .../common/AbstractMvcIntegrationTest.java | 2 + .../zuul/TraceZuulIntegrationTests.java | 5 + .../cloud/sleuth/util/SpanNameUtilTests.java | 6 + .../src/test/resources/logback.xml | 2 - 34 files changed, 1143 insertions(+), 109 deletions(-) create mode 100644 spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptor.java create mode 100644 spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceRequestAttributes.java create mode 100644 spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceSpringDataBeanPostProcessor.java create mode 100644 spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebMvcConfigurer.java create mode 100644 spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulHandlerMappingBeanPostProcessor.java create mode 100644 spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SpringDataInstrumentationTests.java create mode 100644 spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterWebIntegrationTests.java diff --git a/.travis.yml b/.travis.yml index d6bd23957..2d2cc22a2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,13 +1,11 @@ language: java jdk: - oraclejdk8 -sudo: false -before_install: - - git config user.name "$GIT_NAME" - - git config user.email "$GIT_EMAIL" - - git config credential.helper "store --file=.git/credentials" - - echo "https://$GH_TOKEN:@github.com" > .git/credentials - - gem install asciidoctor +sudo: required +dist: trusty +cache: + directories: + - $HOME/.m2 install: - | cat < log.log || echo "FAILED!" && grep -v '^.*Download.* http.*$' log.log && exit 1 +- './mvnw -s .settings.xml $MVN_GOAL $MVN_PROFILE -nsu -U --batch-mode -Dmaven.test.redirectTestOutputToFile=true' env: global: - - GIT_NAME="Spencer Gibb" - - GIT_EMAIL=sgibb@pivotal.io - - CI_DEPLOY_USERNAME=sgibb - FEATURE_BRANCH=$(echo ${TRAVIS_BRANCH} | grep -v "master" && echo true || echo false) - SPRING_CLOUD_BUILD=$(echo ${TRAVIS_REPO_SLUG} | grep -q "^spring-cloud/.*$" && echo true || echo false) - MVN_GOAL=$([ "${TRAVIS_PULL_REQUEST}" == "false" -a "${TRAVIS_TAG}" == "" -a "${FEATURE_BRANCH}" == "false" -a "${SPRING_CLOUD_BUILD}" == "true" ] && echo deploy || echo install) - VERSION=$(mvn validate | grep Building | head -1 | sed -e 's/.* //') - MILESTONE=$(echo ${VERSION} | egrep 'M|RC' && echo true || echo false) - MVN_PROFILE=$([ "${MILESTONE}" == "true" ] && echo -P milestone) - - secure: dxec/7oFht2WMaw4GNFNvuWHlnkm1wmFagE3ZrtCcU2SHUW/P2FgG5tiNW9hT2eXHf2H0YUF1ROkL4BrH5qpIlOBtYd0J8Q/67vBqyB112IN2FqoB/F6Erkfp1FKMBBrXXYaGoSvqzmJs6zqS3JRpyr010W2aU8klK0QfqRoJ0g= - TERM=dumb diff --git a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc index 741dfc3b2..e9e7fed8f 100644 --- a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc +++ b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc @@ -145,7 +145,7 @@ be low cardinality (e.g. not include identifiers). Since there is a lot of instrumentation going on some of the span names will be artificial like: -- `http:path` when received an http request on a given path +- `controller-method-name` when received by a Controller with a method name `conrollerMethodName` - `async` for asynchronous operations done via wrapped `Callable` and `Runnable`. - `@Scheduled` annotated methods will return the simple name of the class. @@ -428,6 +428,15 @@ Via the `TraceFilter` all sampled incoming requests result in creation of a Span like to skip via the `spring.sleuth.web.skipPattern` property. If you have `ManagementServerProperties` on classpath then its value of `contextPath` gets appended to the provided skip pattern. +==== HandlerInterceptor + +Since we want the span names to be precise we're using a `TraceHandlerInterceptor` that either wraps an + existing `HandlerInterceptor` or is added directly to the list of existing `HandlerInterceptors`. The + `TraceHandlerInterceptor` adds a special request attribute to the given `HttpServletRequest`. If the + the `TraceFilter` doesn't see this attribute set it will create a "fallback" span which is an additional + span created on the server side so that the trace is presented properly in the UI. Seeing that most likely + signifies that there is a missing instrumentation. In that case please file an issue in Spring Cloud Sleuth. + ==== Async Servlet support If your controller returns a `Callable` or a `WebAsyncTask` Spring Cloud Sleuth will continue the existing span instead of creating a new one. diff --git a/spring-cloud-sleuth-core/pom.xml b/spring-cloud-sleuth-core/pom.xml index 748c71507..33c4e9f0d 100644 --- a/spring-cloud-sleuth-core/pom.xml +++ b/spring-cloud-sleuth-core/pom.xml @@ -75,6 +75,12 @@ rxjava true + + + org.springframework.data + spring-data-rest-webmvc + true + org.aspectj aspectjrt @@ -115,6 +121,26 @@ JUnitParams test + + org.springframework.boot + spring-boot-starter-data-jpa + test + + + org.springframework + spring-orm + test + + + org.springframework.boot + spring-boot-starter-data-rest + test + + + com.h2database + h2 + test + diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/TraceKeys.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/TraceKeys.java index 5c3ecd999..42468cd0a 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/TraceKeys.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/TraceKeys.java @@ -59,6 +59,8 @@ public class TraceKeys { private Async async = new Async(); + private Mvc mvc = new Mvc(); + public Http getHttp() { return this.http; } @@ -75,6 +77,10 @@ public class TraceKeys { return this.async; } + public Mvc getMvc() { + return this.mvc; + } + public void setHttp(Http http) { this.http = http; } @@ -91,6 +97,10 @@ public class TraceKeys { this.async = async; } + public void setMvc(Mvc mvc) { + this.mvc = mvc; + } + public static class Message { private Payload payload = new Payload(); @@ -376,7 +386,6 @@ public class TraceKeys { public void setThreadPoolKey(String threadPoolKey) { this.threadPoolKey = threadPoolKey; } - } /** @@ -442,7 +451,40 @@ public class TraceKeys { public void setMethodNameKey(String methodNameKey) { this.methodNameKey = methodNameKey; } + } + /** + * Trace keys related to MVC controller tags + */ + public static class Mvc { + + /** + * The lower case, hyphen delimited name of the class that processes the request. + * Ex. class named "BookController" will result in "book-controller" tag value. + */ + private String controllerClass = "mvc.controller.class"; + + /** + * The lower case, hyphen delimited name of the class that processes the request. + * Ex. method named "listOfBooks" will result in "list-of-books" tag value. + */ + private String controllerMethod = "mvc.controller.method"; + + public String getControllerClass() { + return this.controllerClass; + } + + public void setControllerClass(String controllerClass) { + this.controllerClass = controllerClass; + } + + public String getControllerMethod() { + return this.controllerMethod; + } + + public void setControllerMethod(String controllerMethod) { + this.controllerMethod = controllerMethod; + } } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfiguration.java index bb4967dcf..579f81f5b 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfiguration.java @@ -44,7 +44,7 @@ import org.springframework.context.annotation.Configuration; */ @Configuration @ConditionalOnProperty(value="spring.sleuth.enabled", matchIfMissing=true) -@EnableConfigurationProperties +@EnableConfigurationProperties(TraceKeys.class) public class TraceAutoConfiguration { @Bean @@ -68,12 +68,6 @@ public class TraceAutoConfiguration { spanReporter); } - @Bean - @ConditionalOnMissingBean - public TraceKeys traceKeys() { - return new TraceKeys(); - } - @Bean @ConditionalOnMissingBean public SpanNamer spanNamer() { 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 f50b1fea2..30efa50c4 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 @@ -128,26 +128,14 @@ public class TraceFilter extends GenericFilterBean { || Span.SPAN_NOT_SAMPLED.equals(ServletUtils.getHeader(request, response, Span.SAMPLED_NAME)); Span spanFromRequest = getSpanFromAttribute(request); if (spanFromRequest != null) { - this.tracer.continueSpan(spanFromRequest); - if (log.isDebugEnabled()) { - log.debug("There has already been a span in the request " + spanFromRequest + ""); - } + continueSpan(request, spanFromRequest); } if (log.isDebugEnabled()) { log.debug("Received a request to uri [" + uri + "] that should be skipped [" + skip + "]"); } // in case of a response with exception status a exception controller will close the span if (!httpStatusSuccessful(response) && isSpanContinued(request)) { - if (log.isDebugEnabled()) { - log.debug( - "The span was already detached once and we're processing an error"); - } - try { - filterChain.doFilter(request, response); - } finally { - request.setAttribute(TRACE_ERROR_HANDLED_REQUEST_ATTR, true); - this.tracer.close(spanFromRequest); - } + processErrorRequest(filterChain, request, response, spanFromRequest); return; } addToResponseIfNotPresent(response, Span.SAMPLED_NAME, skip ? Span.SPAN_NOT_SAMPLED : Span.SPAN_SAMPLED); @@ -166,33 +154,76 @@ public class TraceFilter extends GenericFilterBean { // Add headers before filter chain in case one of the filters flushes the // response... filterChain.doFilter(request, response); - } - catch (Throwable e) { + } catch (Throwable e) { exception = e; throw e; - } - finally { + } finally { if (isAsyncStarted(request) || request.isAsyncStarted()) { if (log.isDebugEnabled()) { - log.debug("Detaching the span " + spanFromRequest + " since the request is asynchronous"); + log.debug("The span " + spanFromRequest + " will get detached by a HandleInterceptor"); } - this.tracer.detach(spanFromRequest); // TODO: how to deal with response annotations and async? return; } + spanFromRequest = createSpanIfRequestNotHandled(request, spanFromRequest, name, skip); addToResponseIfNotPresent(response, Span.SAMPLED_NAME, skip ? Span.SPAN_NOT_SAMPLED : Span.SPAN_SAMPLED); detachOrCloseSpans(request, response, spanFromRequest, exception); } } + private void processErrorRequest(FilterChain filterChain, HttpServletRequest request, + HttpServletResponse response, Span spanFromRequest) + throws IOException, ServletException { + if (log.isDebugEnabled()) { + log.debug("The span [" + spanFromRequest + "] was already detached once and we're processing an error"); + } + try { + filterChain.doFilter(request, response); + } finally { + request.setAttribute(TRACE_ERROR_HANDLED_REQUEST_ATTR, true); + addResponseTags(response, null); + this.tracer.close(spanFromRequest); + } + } + + private void continueSpan(HttpServletRequest request, Span spanFromRequest) { + this.tracer.continueSpan(spanFromRequest); + request.setAttribute(TraceRequestAttributes.SPAN_CONTINUED_REQUEST_ATTR, "true"); + if (log.isDebugEnabled()) { + log.debug("There has already been a span in the request " + spanFromRequest); + } + } + + // This method is a fallback in case if handler interceptors didn't catch the request. + // In that case we are creating an artificial span so that it can be visible in Zipkin. + private Span createSpanIfRequestNotHandled(HttpServletRequest request, + Span spanFromRequest, String name, boolean skip) { + if (!requestHasAlreadyBeenHandled(request)) { + spanFromRequest = this.tracer.createSpan(name); + request.setAttribute(TRACE_REQUEST_ATTR, spanFromRequest); + if (log.isDebugEnabled() && !skip) { + log.debug("The request with uri [" + request.getRequestURI() + "] hasn't been handled by any of Sleuth's components. " + + "That means that most likely you're using custom HandlerMappings and didn't add Sleuth's TraceHandlerInterceptor. " + + "Sleuth will create a span to ensure that the graph of calls remains valid in Zipkin"); + } + } + return spanFromRequest; + } + + private boolean requestHasAlreadyBeenHandled(HttpServletRequest request) { + return request.getAttribute(TraceRequestAttributes.HANDLED_SPAN_REQUEST_ATTR) != null; + } + private void detachOrCloseSpans(HttpServletRequest request, HttpServletResponse response, Span spanFromRequest, Throwable exception) { if (spanFromRequest != null) { addResponseTags(response, exception); - if (spanFromRequest.hasSavedSpan()) { - closeParentSpan(spanFromRequest.getSavedSpan()); + if (spanFromRequest.hasSavedSpan() && requestHasAlreadyBeenHandled(request)) { + recordParentSpan(spanFromRequest.getSavedSpan()); + } else if (!requestHasAlreadyBeenHandled(request)) { + spanFromRequest = this.tracer.close(spanFromRequest); } - closeParentSpan(spanFromRequest); + recordParentSpan(spanFromRequest); // in case of a response with exception status will close the span when exception dispatch is handled if (httpStatusSuccessful(response)) { if (log.isDebugEnabled()) { @@ -213,7 +244,10 @@ public class TraceFilter extends GenericFilterBean { } } - private void closeParentSpan(Span parent) { + private void recordParentSpan(Span parent) { + if (parent == null) { + return; + } if (parent.isRemote()) { if (log.isDebugEnabled()) { log.debug("Sending the parent span " + parent + " to Zipkin"); @@ -247,6 +281,10 @@ public class TraceFilter extends GenericFilterBean { return getSpanFromAttribute(request) != null; } + /** + * In order not to send unnecessary data we're not adding request tags to the server + * side spans. All the tags are there on the client side. + */ private void addRequestTagsForParentSpan(HttpServletRequest request, Span spanFromRequest) { if (spanFromRequest.getName().contains("parent")) { addRequestTags(spanFromRequest, request); diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptor.java new file mode 100644 index 000000000..c9d87e7bc --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptor.java @@ -0,0 +1,186 @@ +/* + * Copyright 2013-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.sleuth.instrument.web; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.lang.invoke.MethodHandles; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.boot.autoconfigure.web.ErrorController; +import org.springframework.cloud.sleuth.Span; +import org.springframework.cloud.sleuth.TraceKeys; +import org.springframework.cloud.sleuth.Tracer; +import org.springframework.cloud.sleuth.util.SpanNameUtil; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; + +/** + * {@link org.springframework.web.servlet.HandlerInterceptor} that wraps handling of a + * request in a Span. Adds tags related to the class and method name. + * + * The interceptor will not create spans for error controller related paths. + * + * It's important to note that this implementation will set the request attribute + * {@link TraceRequestAttributes#HANDLED_SPAN_REQUEST_ATTR} when the request is processed. + * That way the {@link TraceFilter} will not create the "fallback" span. + * + * @author Marcin Grzejszczak + * @since 1.0.3 + */ +public class TraceHandlerInterceptor extends HandlerInterceptorAdapter { + + private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); + + private final BeanFactory beanFactory; + + private Tracer tracer; + private TraceKeys traceKeys; + private ErrorController errorController; + + public TraceHandlerInterceptor(BeanFactory beanFactory) { + this.beanFactory = beanFactory; + } + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, + Object handler) throws Exception { + if (isErrorControllerRelated(request)) { + log.debug("Skipping creation of a span for error controller processing"); + return true; + } + if (isSpanContinued(request)) { + log.debug("Skipping creation of a span since the span is continued"); + return true; + } + String spanName = spanName(handler); + Span span = getTracer().createSpan(spanName); + if (log.isDebugEnabled()) { + log.debug("Created new span " + span + " with name [" + spanName + "]"); + } + addClassMethodTag(handler, span); + addClassNameTag(handler, span); + setSpanInAttribute(request, span); + return true; + } + + private boolean isErrorControllerRelated(HttpServletRequest request) { + return getErrorController() != null && getErrorController().getErrorPath() + .equals(request.getRequestURI()); + } + + private void addClassMethodTag(Object handler, Span span) { + if (handler instanceof HandlerMethod) { + String methodName = SpanNameUtil.toLowerHyphen( + ((HandlerMethod) handler).getMethod().getName()); + getTracer().addTag(getTraceKeys().getMvc().getControllerMethod(), methodName); + if (log.isDebugEnabled()) { + log.debug("Adding a method tag with value [" + methodName + "] to a span " + span); + } + } + } + + private void addClassNameTag(Object handler, Span span) { + String className; + if (handler instanceof HandlerMethod) { + className = SpanNameUtil.toLowerHyphen( + ((HandlerMethod) handler).getBeanType().getSimpleName()); + } else { + className = SpanNameUtil.toLowerHyphen(handler.getClass().getSimpleName()); + } + if (log.isDebugEnabled()) { + log.debug("Adding a class tag with value [" + className + "] to a span " + span); + } + getTracer().addTag(getTraceKeys().getMvc().getControllerClass(), className); + } + + private String spanName(Object handler) { + if (handler instanceof HandlerMethod) { + return SpanNameUtil.toLowerHyphen(((HandlerMethod) handler).getMethod().getName()); + } + return SpanNameUtil.toLowerHyphen(handler.getClass().getSimpleName()); + } + + @Override + public void afterConcurrentHandlingStarted(HttpServletRequest request, + HttpServletResponse response, Object handler) throws Exception { + Span spanFromRequest = getSpanFromAttribute(request); + Span rootSpanFromRequest = getRootSpanFromAttribute(request); + if (log.isDebugEnabled()) { + log.debug("Closing the span " + spanFromRequest + " and detaching its parent " + rootSpanFromRequest + " since the request is asynchronous"); + } + getTracer().close(spanFromRequest); + getTracer().detach(rootSpanFromRequest); + } + + @Override + public void afterCompletion(HttpServletRequest request, HttpServletResponse response, + Object handler, Exception ex) throws Exception { + if (isErrorControllerRelated(request)) { + log.debug("Skipping closing of a span for error controller processing"); + return; + } + if (isSpanContinued(request)) { + log.debug("Skipping closing of a span since it's been continued"); + return; + } + Span span = getSpanFromAttribute(request); + if (log.isDebugEnabled()) { + log.debug("Closing span " + span); + } + getTracer().close(span); + } + + private boolean isSpanContinued(HttpServletRequest request) { + return request.getAttribute(TraceRequestAttributes.SPAN_CONTINUED_REQUEST_ATTR) != null; + } + + private Span getSpanFromAttribute(HttpServletRequest request) { + return (Span) request.getAttribute(TraceRequestAttributes.HANDLED_SPAN_REQUEST_ATTR); + } + + private Span getRootSpanFromAttribute(HttpServletRequest request) { + return (Span) request.getAttribute(TraceFilter.TRACE_REQUEST_ATTR); + } + + private void setSpanInAttribute(HttpServletRequest request, Span span) { + request.setAttribute(TraceRequestAttributes.HANDLED_SPAN_REQUEST_ATTR, span); + } + + private Tracer getTracer() { + if (this.tracer == null) { + this.tracer = this.beanFactory.getBean(Tracer.class); + } + return this.tracer; + } + + private TraceKeys getTraceKeys() { + if (this.traceKeys == null) { + this.traceKeys = this.beanFactory.getBean(TraceKeys.class); + } + return this.traceKeys; + } + + private ErrorController getErrorController() { + if (this.errorController == null) { + this.errorController = this.beanFactory.getBean(ErrorController.class); + } + return this.errorController; + } +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceRequestAttributes.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceRequestAttributes.java new file mode 100644 index 000000000..683293807 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceRequestAttributes.java @@ -0,0 +1,42 @@ +/* + * Copyright 2013-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.sleuth.instrument.web; + +/** + * Utility class containing values of {@link javax.servlet.http.HttpServletRequest} attributes + * + * @author Marcin Grzejszczak + * @since 1.0.3 + */ +public final class TraceRequestAttributes { + + /** + * Attribute containing a {@link org.springframework.cloud.sleuth.Span} set on a request when it got handled by a Sleuth component. + * If that attribute is set then {@link TraceFilter} will not create a "fallback" server-side span. + */ + public static final String HANDLED_SPAN_REQUEST_ATTR = TraceRequestAttributes.class.getName() + + ".TRACE_HANDLED"; + + /** + * Attribute set when the {@link org.springframework.cloud.sleuth.Span} got continued in the {@link TraceFilter}. + * The Sleuth tracing components will most likely continue the current Span instead of creating a new one. + */ + public static final String SPAN_CONTINUED_REQUEST_ATTR = TraceRequestAttributes.class.getName() + + ".TRACE_CONTINUED"; + + private TraceRequestAttributes() {} +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceSpringDataBeanPostProcessor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceSpringDataBeanPostProcessor.java new file mode 100644 index 000000000..98e2f42a6 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceSpringDataBeanPostProcessor.java @@ -0,0 +1,96 @@ +/* + * 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.lang.invoke.MethodHandles; +import java.util.Collections; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.data.rest.webmvc.support.DelegatingHandlerMapping; +import org.springframework.web.servlet.HandlerExecutionChain; +import org.springframework.web.servlet.HandlerMapping; + +/** + * Bean post processor that wraps Spring Data REST Controllers in named Spans + * + * @author Marcin Grzejszczak + * @since 1.0.3 + */ +class TraceSpringDataBeanPostProcessor implements BeanPostProcessor { + + private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); + + private final BeanFactory beanFactory; + + public TraceSpringDataBeanPostProcessor(BeanFactory beanFactory) { + this.beanFactory = beanFactory; + } + + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) + throws BeansException { + if (bean instanceof DelegatingHandlerMapping && !(bean instanceof TraceDelegatingHandlerMapping)) { + if (log.isDebugEnabled()) { + log.debug("Wrapping bean [" + beanName + "] of type [" + bean.getClass().getSimpleName() + + "] in its trace representation"); + } + return new TraceDelegatingHandlerMapping((DelegatingHandlerMapping) bean, + this.beanFactory); + } + return bean; + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) + throws BeansException { + return bean; + } + + private static class TraceDelegatingHandlerMapping extends DelegatingHandlerMapping { + + private final DelegatingHandlerMapping delegate; + private final BeanFactory beanFactory; + + public TraceDelegatingHandlerMapping(DelegatingHandlerMapping delegate, + BeanFactory beanFactory) { + super(Collections.emptyList()); + this.delegate = delegate; + this.beanFactory = beanFactory; + } + + @Override + public int getOrder() { + return this.delegate.getOrder(); + } + + @Override + public HandlerExecutionChain getHandler(HttpServletRequest request) + throws Exception { + HandlerExecutionChain handlerExecutionChain = this.delegate.getHandler(request); + if (handlerExecutionChain == null) { + return null; + } + handlerExecutionChain.addInterceptor(new TraceHandlerInterceptor(this.beanFactory)); + return handlerExecutionChain; + } + } +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAspect.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAspect.java index 4a33f04a0..a56ac27a3 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAspect.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAspect.java @@ -24,11 +24,9 @@ import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; -import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.SpanNamer; import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.instrument.async.TraceContinuingCallable; -import org.springframework.cloud.sleuth.util.SpanNameUtil; import org.springframework.web.context.request.async.WebAsyncTask; /** @@ -85,9 +83,6 @@ public class TraceWebAspect { @Pointcut("@within(org.springframework.stereotype.Controller)") private void anyControllerAnnotated() { } // NOSONAR - @Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping)") - private void anyRequestMappingAnnotatedMethod() { } // NOSONAR - @Pointcut("execution(public java.util.concurrent.Callable *(..))") private void anyPublicMethodReturningCallable() { } // NOSONAR @@ -100,21 +95,6 @@ public class TraceWebAspect { @Pointcut("(anyRestControllerAnnotated() || anyControllerAnnotated()) && anyPublicMethodReturningWebAsyncTask()") private void anyControllerOrRestControllerWithPublicWebAsyncTaskMethod() { } // NOSONAR - @Around("(anyRestControllerAnnotated() || anyControllerAnnotated()) && anyRequestMappingAnnotatedMethod()") - @SuppressWarnings("unchecked") - public Object wrapControllerMethodWithCorrelationId(ProceedingJoinPoint pjp) throws Throwable { - String spanName = SpanNameUtil.toLowerHyphen(pjp.getSignature().getName()); - Span span = this.tracer.createSpan(spanName); - if (log.isDebugEnabled()) { - log.debug("Wrapping controller method [" + spanName + "] in a span " + span); - } - try { - return pjp.proceed(); - } finally { - this.tracer.close(span); - } - } - @Around("anyControllerOrRestControllerWithPublicAsyncMethod()") @SuppressWarnings("unchecked") public Object wrapWithCorrelationId(ProceedingJoinPoint pjp) throws Throwable { diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java index 47c5bee0d..64bb9819a 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java @@ -15,11 +15,11 @@ */ package org.springframework.cloud.sleuth.instrument.web; -import java.util.regex.Pattern; - import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import java.util.regex.Pattern; +import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.actuate.autoconfigure.ManagementServerProperties; import org.springframework.boot.autoconfigure.AutoConfigureAfter; @@ -31,8 +31,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.SpanInjector; import org.springframework.cloud.sleuth.SpanExtractor; +import org.springframework.cloud.sleuth.SpanInjector; import org.springframework.cloud.sleuth.SpanNamer; import org.springframework.cloud.sleuth.SpanReporter; import org.springframework.cloud.sleuth.TraceKeys; @@ -40,6 +40,7 @@ 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.context.annotation.Import; import org.springframework.util.StringUtils; import static javax.servlet.DispatcherType.ASYNC; @@ -64,6 +65,7 @@ import static javax.servlet.DispatcherType.REQUEST; @ConditionalOnBean(Tracer.class) @AutoConfigureAfter(TraceAutoConfiguration.class) @EnableConfigurationProperties(TraceKeys.class) +@Import(TraceWebMvcConfigurer.class) public class TraceWebAutoConfiguration { /** @@ -77,6 +79,12 @@ public class TraceWebAutoConfiguration { return new TraceWebAspect(tracer, spanNamer); } + @Bean + @ConditionalOnClass(name = "org.springframework.data.rest.webmvc.support.DelegatingHandlerMapping") + public TraceSpringDataBeanPostProcessor traceSpringDataBeanPostProcessor(BeanFactory beanFactory) { + return new TraceSpringDataBeanPostProcessor(beanFactory); + } + @Bean public FilterRegistrationBean traceWebFilter(Tracer tracer, TraceKeys traceKeys, SkipPatternProvider skipPatternProvider, SpanReporter spanReporter, diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebMvcConfigurer.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebMvcConfigurer.java new file mode 100644 index 000000000..35935a39b --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebMvcConfigurer.java @@ -0,0 +1,46 @@ +/* + * 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 org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; + +/** + * MVC Adapter that adds the {@link TraceHandlerInterceptor} + * + * @author Marcin Grzejszczak + * + * @since 1.0.3 + */ +@Configuration +class TraceWebMvcConfigurer extends WebMvcConfigurerAdapter { + @Autowired TraceHandlerInterceptor traceHandlerInterceptor; + + @Bean + public TraceHandlerInterceptor traceHandlerInterceptor(BeanFactory beanFactory) { + return new TraceHandlerInterceptor(beanFactory); + } + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(this.traceHandlerInterceptor); + } +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TracePostZuulFilter.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TracePostZuulFilter.java index 3ad574f14..14685fd74 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TracePostZuulFilter.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TracePostZuulFilter.java @@ -26,7 +26,6 @@ import org.apache.commons.logging.LogFactory; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.TraceKeys; import org.springframework.cloud.sleuth.Tracer; -import org.springframework.http.HttpStatus; /**8 * A post request {@link ZuulFilter} that publishes an event upon start of the filtering @@ -56,7 +55,7 @@ public class TracePostZuulFilter extends ZuulFilter { // TODO: the client sent event should come from the client not the filter! getCurrentSpan().logEvent(Span.CLIENT_RECV); if (log.isDebugEnabled()) { - log.debug("Closing current client span " + getCurrentSpan() + ""); + log.debug("Closing current client span " + getCurrentSpan()); } int httpStatus = RequestContext.getCurrentContext().getResponse().getStatus(); if (httpStatus > 0) { @@ -64,21 +63,9 @@ public class TracePostZuulFilter extends ZuulFilter { String.valueOf(httpStatus)); } this.tracer.close(getCurrentSpan()); - closeParentSpanIfResponseIsNotSuccess(httpStatus); return null; } - private void closeParentSpanIfResponseIsNotSuccess(int httpStatus) { - if (httpStatus > 0 && httpStatusIsNotSuccess(httpStatus)) { - this.tracer.close(getCurrentSpan()); - } - } - - private boolean httpStatusIsNotSuccess(int httpStatus) { - return HttpStatus.valueOf(httpStatus).is4xxClientError() || - HttpStatus.valueOf(httpStatus).is5xxServerError(); - } - @Override public String filterType() { return "post"; diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TracePreZuulFilter.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TracePreZuulFilter.java index 2717d5b9a..efe3c9113 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TracePreZuulFilter.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TracePreZuulFilter.java @@ -19,17 +19,18 @@ 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.Span; import org.springframework.cloud.sleuth.SpanInjector; import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.instrument.web.HttpTraceKeysInjector; - -import com.netflix.zuul.ExecutionStatus; -import com.netflix.zuul.ZuulFilter; -import com.netflix.zuul.ZuulFilterResult; -import com.netflix.zuul.context.RequestContext; +import org.springframework.cloud.sleuth.instrument.web.TraceRequestAttributes; /** * A pre request {@link ZuulFilter} that sets tracing related headers on the request @@ -73,6 +74,7 @@ public class TracePreZuulFilter extends ZuulFilter { if (log.isDebugEnabled()) { log.debug("Current span is " + span + ""); } + 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); @@ -94,6 +96,11 @@ public class TracePreZuulFilter extends ZuulFilter { return result; } + // TraceFilter will not create the "fallback" span + private void markRequestAsHandled(RequestContext ctx) { + ctx.getRequest().setAttribute(TraceRequestAttributes.HANDLED_SPAN_REQUEST_ATTR, "true"); + } + private Span getCurrentSpan() { return this.tracer.getCurrentSpan(); } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulAutoConfiguration.java index 54101331e..65dc9a89e 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulAutoConfiguration.java @@ -15,6 +15,7 @@ */ package org.springframework.cloud.sleuth.instrument.zuul; +import org.springframework.beans.factory.BeanFactory; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; @@ -79,4 +80,9 @@ public class TraceZuulAutoConfiguration { return new RequestBuilderContextInjector(); } + @Bean + public TraceZuulHandlerMappingBeanPostProcessor traceHandlerMappingBeanPostProcessor(BeanFactory beanFactory) { + return new TraceZuulHandlerMappingBeanPostProcessor(beanFactory); + } + } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulHandlerMappingBeanPostProcessor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulHandlerMappingBeanPostProcessor.java new file mode 100644 index 000000000..14eed9913 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulHandlerMappingBeanPostProcessor.java @@ -0,0 +1,114 @@ +/* + * 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.lang.invoke.MethodHandles; +import java.util.List; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.boot.autoconfigure.web.ErrorController; +import org.springframework.cloud.netflix.zuul.filters.RouteLocator; +import org.springframework.cloud.netflix.zuul.web.ZuulController; +import org.springframework.cloud.netflix.zuul.web.ZuulHandlerMapping; +import org.springframework.cloud.sleuth.instrument.web.TraceHandlerInterceptor; + +/** + * Bean post processor that wraps {@link ZuulHandlerMapping} in its + * trace representation. + * + * @author Marcin Grzejszczak + * @since 1.0.3 + */ +class TraceZuulHandlerMappingBeanPostProcessor implements BeanPostProcessor { + + private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); + + private final BeanFactory beanFactory; + private RouteLocator routeLocator; + private ZuulController zuul; + private ErrorController errorController; + + public TraceZuulHandlerMappingBeanPostProcessor(BeanFactory beanFactory) { + this.beanFactory = beanFactory; + } + + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) + throws BeansException { + if (bean instanceof ZuulHandlerMapping && !(bean instanceof TraceZuulHandlerMapping)) { + if (log.isDebugEnabled()) { + log.debug("Wrapping bean [" + beanName + "] of type [" + bean.getClass().getSimpleName() + + "] in its trace representation"); + } + return new TraceZuulHandlerMapping(this.beanFactory, routeLocator(), zuulController(), + errorController()); + } + return bean; + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) + throws BeansException { + return bean; + } + + private static class TraceZuulHandlerMapping extends ZuulHandlerMapping { + + private final BeanFactory beanFactory; + + public TraceZuulHandlerMapping(BeanFactory beanFactory, RouteLocator routeLocator, + ZuulController zuulController, ErrorController errorController) { + super(routeLocator, zuulController); + this.beanFactory = beanFactory; + setErrorController(errorController); + } + + @Override + protected void extendInterceptors(List interceptors) { + interceptors.add(new TraceHandlerInterceptor(this.beanFactory)); + } + } + + private RouteLocator routeLocator() { + if (this.routeLocator == null) { + this.routeLocator = this.beanFactory.getBean(RouteLocator.class); + } + return this.routeLocator; + } + + private ZuulController zuulController() { + if (this.zuul == null) { + this.zuul = this.beanFactory.getBean(ZuulController.class); + } + return this.zuul; + } + + private ErrorController errorController() { + if (this.errorController == null) { + try { + this.errorController = this.beanFactory.getBean(ErrorController.class); + } catch (BeansException b) { + return null; + } + } + return this.errorController; + } +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/util/SpanNameUtil.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/util/SpanNameUtil.java index cc563298b..78ef5ec7d 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/util/SpanNameUtil.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/util/SpanNameUtil.java @@ -20,7 +20,6 @@ package org.springframework.cloud.sleuth.util; * Utility class that provides the name in hyphen based notation * * @author Adrian Cole - * * @since 1.0.2 */ public final class SpanNameUtil { @@ -29,8 +28,9 @@ public final class SpanNameUtil { StringBuilder result = new StringBuilder(); for (int i = 0; i < name.length(); i++) { char c = name.charAt(i); - if (c >= 'A' && c <= 'Z') { - result.append('-').append((char) (c + 'a' - 'A')); + if (Character.isUpperCase(c)) { + if (i != 0) result.append('-'); + result.append(Character.toLowerCase(c)); } else { result.append(c); } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/ListOfSpans.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/ListOfSpans.java index 697a2b079..cbec93f1c 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/ListOfSpans.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/ListOfSpans.java @@ -15,6 +15,7 @@ */ package org.springframework.cloud.sleuth.assertions; +import java.util.ArrayList; import java.util.List; import org.springframework.cloud.sleuth.Span; @@ -27,6 +28,6 @@ public class ListOfSpans { public final List spans; public ListOfSpans(List spans) { - this.spans = spans; + this.spans = new ArrayList<>(spans); } } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/ListOfSpansAssert.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/ListOfSpansAssert.java index 22408ad03..9f460d24f 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/ListOfSpansAssert.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/ListOfSpansAssert.java @@ -20,15 +20,16 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.assertj.core.api.AbstractAssert; import org.springframework.cloud.sleuth.Span; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; - import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.toList; import static org.assertj.core.api.Assertions.assertThat; @@ -79,12 +80,80 @@ public class ListOfSpansAssert extends AbstractAssert \nto contain at least one span with tag key " + + "equal to <%s>", spansToString(), tagKey); + } + return this; + } + + private boolean spanWithKeyTagExists(String tagKey) { + for (Span span : this.actual.spans) { + if (span.tags().containsKey(tagKey)) { + return true; + } + } + return false; + } + + public ListOfSpansAssert hasASpanWithTagEqualTo(String tagKey, String tagValue) { + isNotNull(); + printSpans(); + List matchingSpans = this.actual.spans.stream() + .filter(span -> tagValue.equals(span.tags().get(tagKey))) + .collect(toList()); + if (matchingSpans.isEmpty()) { + failWithMessage("Expected spans \n <%s> \nto contain at least one span with tag key " + + "equal to <%s> and value equal to <%s>", spansToString(), tagKey, tagValue); + } + return this; + } + + private String spansToString() { + return this.actual.spans.stream().map(span -> "\nSPAN: " + span.toString() + " with name [" + span.getName() + "] " + + "\nwith tags " + span.tags() + "\nwith logs " + span.logs()).collect(Collectors.joining("\n")); + } + + public ListOfSpansAssert doesNotHaveASpanWithName(String name) { + isNotNull(); + printSpans(); + List matchingSpans = findSpansWithName(name); + if (!matchingSpans.isEmpty()) { + failWithMessage("Expected spans \n <%s> \nnot to contain a span with name <%s>", spansToString(), name); + } + return this; + } + + private List findSpansWithName(String name) { + return this.actual.spans.stream() + .filter(span -> span.getName().equals(name)) + .collect(toList()); + } + + public ListOfSpansAssert hasASpanWithName(String name) { + isNotNull(); + printSpans(); + List matchingSpans = findSpansWithName(name); + if (matchingSpans.isEmpty()) { + failWithMessage("Expected spans <%s> to contain a span with name <%s>", spansToString(), name); + } + return this; + } + private void printSpans() { try { - log.info("Stored spans " + this.objectMapper.writeValueAsString(this.actual.spans)); + log.info("Stored spans " + this.objectMapper.writeValueAsString(new ArrayList<>(this.actual.spans))); } catch (JsonProcessingException e) { } } + @Override + protected void failWithMessage(String errorMessage, Object... arguments) { + log.error(errorMessage); + super.failWithMessage(errorMessage, arguments); + } } \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/SpanAssert.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/SpanAssert.java index 2f53ea20d..3503e64df 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/SpanAssert.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/SpanAssert.java @@ -99,6 +99,19 @@ public class SpanAssert extends AbstractAssert { return this; } + public SpanAssert hasATagWithKey(String tagKey) { + isNotNull(); + assertThatTagIsPresent(tagKey); + boolean foundTagValue = this.actual.tags().containsKey(tagKey); + if (!foundTagValue) { + String message = String.format("Expected span to have the tag with key <%s>. " + + "Found tags are <%s>", tagKey, this.actual.tags()); + log.error(message); + failWithMessage(message); + } + return this; + } + public SpanAssert matchesATag(String tagKey, String tagRegex) { isNotNull(); assertThatTagIsPresent(tagKey); diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/RestTemplateTraceAspectIntegrationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/RestTemplateTraceAspectIntegrationTests.java index e401aa213..3a3de25bd 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/RestTemplateTraceAspectIntegrationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/RestTemplateTraceAspectIntegrationTests.java @@ -14,6 +14,7 @@ import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.instrument.DefaultTestAutoConfiguration; import org.springframework.cloud.sleuth.sampler.AlwaysSampler; +import org.springframework.cloud.sleuth.util.ExceptionUtils; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; import org.springframework.core.env.Environment; @@ -35,6 +36,7 @@ import org.springframework.web.context.request.async.WebAsyncTask; import static java.util.concurrent.TimeUnit.SECONDS; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.BDDAssertions.then; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request; @@ -67,6 +69,7 @@ public class RestTemplateTraceAspectIntegrationTests { whenARequestIsSentToASyncEndpoint(); thenTraceIdHasBeenSetOnARequestHeader(); + then(ExceptionUtils.getLastException()).isNull(); } @Test @@ -75,6 +78,7 @@ public class RestTemplateTraceAspectIntegrationTests { whenARequestIsSentToAnAsyncRestTemplateEndpoint(); thenTraceIdHasBeenSetOnARequestHeader(); + then(ExceptionUtils.getLastException()).isNull(); } @Test @@ -82,6 +86,7 @@ public class RestTemplateTraceAspectIntegrationTests { throws Exception { whenARequestIsSentToAnAsyncEndpoint("/callablePing"); thenTraceIdHasBeenSetOnARequestHeader(); + then(ExceptionUtils.getLastException()).isNull(); } @Test @@ -89,6 +94,7 @@ public class RestTemplateTraceAspectIntegrationTests { throws Exception { whenARequestIsSentToAnAsyncEndpoint("/webAsyncTaskPing"); thenTraceIdHasBeenSetOnARequestHeader(); + then(ExceptionUtils.getLastException()).isNull(); } private void whenARequestIsSentToAnAsyncRestTemplateEndpoint() throws Exception { diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SpringDataInstrumentationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SpringDataInstrumentationTests.java new file mode 100644 index 000000000..742b382f2 --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SpringDataInstrumentationTests.java @@ -0,0 +1,194 @@ +/* + * 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.annotation.PostConstruct; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import java.util.Collection; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import com.jayway.awaitility.Awaitility; + +import org.assertj.core.api.BDDAssertions; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.orm.jpa.EntityScan; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.boot.test.WebIntegrationTest; +import org.springframework.cloud.sleuth.Sampler; +import org.springframework.cloud.sleuth.Tracer; +import org.springframework.cloud.sleuth.assertions.ListOfSpans; +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.core.ParameterizedTypeReference; +import org.springframework.core.env.Environment; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.rest.core.annotation.RepositoryRestResource; +import org.springframework.hateoas.Resources; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.web.client.RestTemplate; + +import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; + +/** + * @author Marcin Grzejszczak + */ +@RunWith(SpringJUnit4ClassRunner.class) +@WebIntegrationTest({ "server.port=0" }) +@SpringApplicationConfiguration(classes = { ReservationServiceApplication.class }) +@DirtiesContext +public class SpringDataInstrumentationTests { + + @Autowired RestTemplate restTemplate; + @Autowired Environment environment; + @Autowired Tracer tracer; + @Autowired ArrayListSpanAccumulator arrayListSpanAccumulator; + + @Before + public void setup() { + TestSpanContextHolder.removeCurrentSpan(); + } + + @Test + public void should_create_span_instrumented_by_a_handler_interceptor() { + Collection names = names(); + + then(names).isNotEmpty(); + then(this.arrayListSpanAccumulator.getSpans()).isNotEmpty(); + Awaitility.await().until( () -> { + then(new ListOfSpans(this.arrayListSpanAccumulator.getSpans())).hasASpanWithName("http:/reservations") + .hasASpanWithTagKeyEqualTo("mvc.controller.class"); + }); + then(this.tracer.getCurrentSpan()).isNull(); + then(ExceptionUtils.getLastException()).isNull(); + } + + Collection names() { + ParameterizedTypeReference> ptr = + new ParameterizedTypeReference>() { + }; + ResponseEntity> responseEntity = + this.restTemplate.exchange("http://localhost:" + port() + "/reservations", + HttpMethod.GET, + null, + ptr + ); + return responseEntity + .getBody() + .getContent() + .stream() + .map(Reservation::getReservationName) + .collect(Collectors.toList()); + } + + private int port() { + return this.environment.getProperty("local.server.port", Integer.class); + } +} + +@Configuration +@EnableAutoConfiguration +@EntityScan(basePackageClasses = Reservation.class) +class ReservationServiceApplication { + + @Bean RestTemplate restTemplate() { + return new RestTemplate(); + } + + @Bean SampleRecords sampleRecords(ReservationRepository reservationRepository) { + return new SampleRecords(reservationRepository); + } + + @Bean ArrayListSpanAccumulator arrayListSpanAccumulator() { + return new ArrayListSpanAccumulator(); + } + + @Bean Sampler alwaysSampler() { + return new AlwaysSampler(); + } +} + +class SampleRecords { + + private final ReservationRepository reservationRepository; + + @Autowired + public SampleRecords(ReservationRepository reservationRepository) { + this.reservationRepository = reservationRepository; + } + + @PostConstruct + public void create() throws Exception { + Stream.of("Josh", "Jungryeol", "Nosung", "Hyobeom", + "Soeun", "Seunghue", "Peter", "Jooyong") + .forEach(name -> reservationRepository.save(new Reservation(name))); + reservationRepository.findAll().forEach(System.out::println); + } +} + +@RepositoryRestResource +interface ReservationRepository extends JpaRepository { +} + +@Entity +class Reservation { + + @Id + @GeneratedValue + private Long id; // id + + private String reservationName; // reservation_name + + public Long getId() { + return id; + } + + public String getReservationName() { + return reservationName; + } + + @Override + public String toString() { + return "Reservation{" + + "id=" + id + + ", reservationName='" + reservationName + '\'' + + '}'; + } + + Reservation() {// why JPA why??? + } + + public Reservation(String reservationName) { + + this.reservationName = reservationName; + } +} + + diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterIntegrationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterIntegrationTests.java index 62fab6cbd..05ee3bb3a 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterIntegrationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterIntegrationTests.java @@ -22,6 +22,7 @@ import org.springframework.cloud.sleuth.instrument.DefaultTestAutoConfiguration; import org.springframework.cloud.sleuth.instrument.web.common.AbstractMvcIntegrationTest; import org.springframework.cloud.sleuth.sampler.AlwaysSampler; 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.http.HttpStatus; @@ -66,6 +67,7 @@ public class TraceFilterIntegrationTests extends AbstractMvcIntegrationTest { .findFirst().get(); then(parentSpan).hasLoggedAnEvent(Span.SERVER_RECV) .hasLoggedAnEvent(Span.SERVER_SEND); + then(ExceptionUtils.getLastException()).isNull(); } @Test @@ -74,6 +76,7 @@ public class TraceFilterIntegrationTests extends AbstractMvcIntegrationTest { MvcResult mvcResult = whenSentInfoWithTraceId(new Random().nextLong()); then(notSampledHeaderIsPresent(mvcResult)).isEqualTo(true); + then(ExceptionUtils.getLastException()).isNull(); } @Test @@ -84,6 +87,7 @@ public class TraceFilterIntegrationTests extends AbstractMvcIntegrationTest { MvcResult mvcResult = whenSentPingWithTraceId(expectedTraceId); then(tracingHeaderFrom(mvcResult)).isEqualTo(expectedTraceId); + then(ExceptionUtils.getLastException()).isNull(); } @Test @@ -93,6 +97,7 @@ public class TraceFilterIntegrationTests extends AbstractMvcIntegrationTest { whenSentPingWithTraceId(expectedTraceId); then(MDC.getCopyOfContextMap()).isEmpty(); + then(ExceptionUtils.getLastException()).isNull(); } @Test @@ -104,6 +109,8 @@ public class TraceFilterIntegrationTests extends AbstractMvcIntegrationTest { .andExpect(status().isOk()).andReturn(); then(tracingHeaderFrom(mvcResult)).isEqualTo(expectedTraceId); + then(this.tracer.getCurrentSpan()).isNull(); + then(ExceptionUtils.getLastException()).isNull(); } @Test @@ -119,6 +126,9 @@ public class TraceFilterIntegrationTests extends AbstractMvcIntegrationTest { .filter(span -> span.tags().containsKey("tag")).findFirst(); then(taggedSpan.isPresent()).isTrue(); then(taggedSpan.get()).hasATag("tag", "value"); + then(taggedSpan.get()).hasATag("mvc.controller.method", "deferred"); + then(taggedSpan.get()).hasATag("mvc.controller.class", "test-controller"); + then(ExceptionUtils.getLastException()).isNull(); } @Test @@ -129,6 +139,7 @@ public class TraceFilterIntegrationTests extends AbstractMvcIntegrationTest { then(tracingHeaderFrom(mvcResult)).isEqualTo(expectedTraceId); then(this.tracer.getCurrentSpan()).isNull(); + then(ExceptionUtils.getLastException()).isNull(); } @Test @@ -141,6 +152,7 @@ public class TraceFilterIntegrationTests extends AbstractMvcIntegrationTest { then(this.spanAccumulator.getSpans().stream().filter(span -> span.getSpanId() == span.getTraceId()).findAny().isPresent()).as("a root span exists").isTrue(); then(this.tracer.getCurrentSpan()).isNull(); + then(ExceptionUtils.getLastException()).isNull(); } @Override @@ -239,6 +251,11 @@ public class TraceFilterIntegrationTests extends AbstractMvcIntegrationTest { return "ping"; } + @RequestMapping("/throwsException") + public void throwsException() { + throw new RuntimeException(); + } + @RequestMapping("/deferred") public DeferredResult deferred() { logger.info("deferred"); 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 c36e7957d..28c80214e 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 @@ -158,8 +158,10 @@ public class TraceFilterTests { filter.doFilter(this.request, this.response, this.filterChain); - // this creates a child span which is why we'd expect the parents to include 1L) - assertThat(this.span.getParents()).containsOnly(3L); + // this creates a child span which is why we'd expect the parents to include the parent id + // especially important if no handler interceptors have been used. + // We add a child span on the server side to show which controller serviced the request + assertThat(this.span.getParents()).containsOnly(PARENT_ID); assertThat(parentSpan()) .hasATag("http.url", "http://localhost/?foo=bar") .hasATag("http.host", "localhost") diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterWebIntegrationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterWebIntegrationTests.java new file mode 100644 index 000000000..8ed1a8ad1 --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterWebIntegrationTests.java @@ -0,0 +1,119 @@ +/* + * 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.io.IOException; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.boot.test.WebIntegrationTest; +import org.springframework.cloud.sleuth.Sampler; +import org.springframework.cloud.sleuth.Tracer; +import org.springframework.cloud.sleuth.assertions.ListOfSpans; +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.core.env.Environment; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.client.DefaultResponseErrorHandler; +import org.springframework.web.client.RestTemplate; + +import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; + +/** + * @author Marcin Grzejszczak + */ +@RunWith(SpringJUnit4ClassRunner.class) +@WebIntegrationTest({ "server.port=0" }) +@SpringApplicationConfiguration(classes = { TraceFilterWebIntegrationTests.Config.class }) +public class TraceFilterWebIntegrationTests { + + @Autowired Tracer tracer; + @Autowired ArrayListSpanAccumulator accumulator; + @Autowired RestTemplate restTemplate; + @Autowired Environment environment; + + @Before + @After + public void cleanup() { + ExceptionUtils.setFail(true); + TestSpanContextHolder.removeCurrentSpan(); + } + + @Test + public void should_not_create_a_span_for_error_controller() { + this.restTemplate.getForObject("http://localhost:" + port() + "/", String.class); + + then(this.tracer.getCurrentSpan()).isNull(); + then(new ListOfSpans(this.accumulator.getSpans())) + .doesNotHaveASpanWithName("error") + .hasASpanWithTagEqualTo("http.status_code", "500"); + then(ExceptionUtils.getLastException()).isNull(); + } + + private int port() { + return this.environment.getProperty("local.server.port", Integer.class); + } + + @EnableAutoConfiguration + @Configuration + public static class Config { + + @Bean ExceptionThrowingController controller() { + return new ExceptionThrowingController(); + } + + @Bean ArrayListSpanAccumulator arrayListSpanAccumulator() { + return new ArrayListSpanAccumulator(); + } + + @Bean Sampler alwaysSampler() { + return new AlwaysSampler(); + } + + + @Bean RestTemplate restTemplate() { + RestTemplate restTemplate = new RestTemplate(); + restTemplate.setErrorHandler(new DefaultResponseErrorHandler() { + @Override public void handleError(ClientHttpResponse response) + throws IOException { + } + }); + return restTemplate; + } + } + + @RestController + public static class ExceptionThrowingController { + + @RequestMapping("/") + public void throwException() { + throw new RuntimeException("Throwing exception"); + } + } +} diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptorIntegrationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptorIntegrationTests.java index d47951f67..48c4e662e 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptorIntegrationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptorIntegrationTests.java @@ -36,6 +36,7 @@ import org.springframework.cloud.sleuth.log.NoOpSpanLogger; import org.springframework.cloud.sleuth.sampler.AlwaysSampler; import org.springframework.cloud.sleuth.trace.DefaultTracer; import org.springframework.cloud.sleuth.trace.TestSpanContextHolder; +import org.springframework.cloud.sleuth.util.ExceptionUtils; import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; @@ -45,6 +46,8 @@ import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import okhttp3.mockwebserver.SocketPolicy; +import static org.assertj.core.api.BDDAssertions.then; + /** * @author Marcin Grzejszczak */ @@ -88,6 +91,7 @@ public class TraceRestTemplateInterceptorIntegrationTests { SleuthAssertions.then(this.tracer.getCurrentSpan()).isEqualTo(span); this.tracer.close(span); + then(ExceptionUtils.getLastException()).isNull(); } private ClientHttpRequestFactory clientHttpRequestFactory() { diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/WebClientDiscoveryExceptionTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/WebClientDiscoveryExceptionTests.java index 10bf439c4..73ff1f476 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/WebClientDiscoveryExceptionTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/WebClientDiscoveryExceptionTests.java @@ -50,6 +50,7 @@ import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.client.RestTemplate; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.BDDAssertions.then; @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = { @@ -71,7 +72,6 @@ public class WebClientDiscoveryExceptionTests { @After public void close() { - ExceptionUtils.setFail(false); TestSpanContextHolder.removeCurrentSpan(); } @@ -91,6 +91,7 @@ public class WebClientDiscoveryExceptionTests { SleuthAssertions.then(this.tracer.getCurrentSpan()).isEqualTo(span); this.tracer.close(span); + then(ExceptionUtils.getLastException()).isNull(); } @Test diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/WebClientTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/WebClientTests.java index 5757a78de..3dbae387a 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/WebClientTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/WebClientTests.java @@ -18,6 +18,7 @@ package org.springframework.cloud.sleuth.instrument.web.client; import javax.servlet.http.HttpServletRequest; import java.lang.invoke.MethodHandles; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -170,12 +171,17 @@ public class WebClientTests { } private Span spanWithClientEvents() { - return this.listener.getSpans().stream() - .filter(span -> span.logs().stream() - .filter(log -> log.getEvent().contains(Span.CLIENT_RECV) - || log.getEvent().contains(Span.CLIENT_SEND)) - .findFirst().isPresent()) - .findFirst().get(); + List spans = new ArrayList<>(this.listener.getSpans()); + for(Span span : spans) { + boolean present = span.logs().stream() + .filter(log -> log.getEvent().contains(Span.CLIENT_RECV) + || log.getEvent().contains(Span.CLIENT_SEND)) + .findFirst().isPresent(); + if (present) { + return span; + } + } + return null; } Object[] parametersForShouldAttachTraceIdWhenCallingAnotherService() { @@ -220,7 +226,8 @@ public class WebClientTests { Optional storedSpan = this.listener.getSpans().stream() .filter(span -> "404".equals(span.tags().get("http.status_code"))).findFirst(); then(storedSpan.isPresent()).isTrue(); - this.listener.getSpans().stream() + List spans = new ArrayList<>(this.listener.getSpans()); + spans.stream() .forEach(span -> { int initialSize = span.logs().size(); int distinctSize = span.logs().stream().map(Log::getEvent).distinct().collect(Collectors.toList()).size(); diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignClientServerErrorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignClientServerErrorTests.java index 3bfd2ec93..b5277b03a 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignClientServerErrorTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignClientServerErrorTests.java @@ -94,6 +94,7 @@ public class FeignClientServerErrorTests { Thread.sleep(100); then(this.capture.toString()) .doesNotContain("Tried to close span but it is not the current span"); + then(ExceptionUtils.getLastException()).isNull(); } @Test @@ -107,6 +108,7 @@ public class FeignClientServerErrorTests { Thread.sleep(100); then(this.capture.toString()) .doesNotContain("Tried to close span but it is not the current span"); + then(ExceptionUtils.getLastException()).isNull(); } @Test @@ -120,6 +122,7 @@ public class FeignClientServerErrorTests { Thread.sleep(100); then(this.capture.toString()) .doesNotContain("Tried to close span but it is not the current span"); + then(ExceptionUtils.getLastException()).isNull(); } @Test @@ -133,6 +136,7 @@ public class FeignClientServerErrorTests { Thread.sleep(100); then(this.capture.toString()) .doesNotContain("Tried to close span but it is not the current span"); + then(ExceptionUtils.getLastException()).isNull(); } @Test @@ -146,6 +150,7 @@ public class FeignClientServerErrorTests { Thread.sleep(100); then(this.capture.toString()) .doesNotContain("Tried to close span but it is not the current span"); + then(ExceptionUtils.getLastException()).isNull(); } @Configuration diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/Issue307Tests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/Issue307Tests.java index c2d491f09..7ba667b43 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/Issue307Tests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/Issue307Tests.java @@ -23,6 +23,7 @@ import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; import org.apache.log4j.Level; import org.apache.log4j.Logger; +import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; @@ -31,6 +32,8 @@ import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker; import org.springframework.cloud.netflix.feign.EnableFeignClients; import org.springframework.cloud.netflix.feign.FeignClient; import org.springframework.cloud.sleuth.sampler.AlwaysSampler; +import org.springframework.cloud.sleuth.trace.TestSpanContextHolder; +import org.springframework.cloud.sleuth.util.ExceptionUtils; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; @@ -40,13 +43,21 @@ import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; +import static org.assertj.core.api.BDDAssertions.then; + public class Issue307Tests { + @Before + public void setup() { + TestSpanContextHolder.removeCurrentSpan(); + } + @Test public void should_start_context() { try (ConfigurableApplicationContext applicationContext = SpringApplication .run(SleuthSampleApplication.class, "--spring.jmx.enabled=false")) { } + then(ExceptionUtils.getLastException()).isNull(); } } 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 fee7e35f3..af3f80282 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 @@ -10,6 +10,7 @@ import org.springframework.cloud.sleuth.SpanInjector; 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.trace.TestSpanContextHolder; import org.springframework.cloud.sleuth.util.ExceptionUtils; import org.springframework.context.ApplicationContext; import org.springframework.test.context.web.WebAppConfiguration; @@ -42,6 +43,7 @@ public abstract class AbstractMvcIntegrationTest { @Before public void setup() { ExceptionUtils.setFail(true); + TestSpanContextHolder.removeCurrentSpan(); DefaultMockMvcBuilder mockMvcBuilder = MockMvcBuilders.webAppContextSetup(this.webApplicationContext); configureMockMvcBuilder(mockMvcBuilder); this.mockMvc = mockMvcBuilder.build(); diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulIntegrationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulIntegrationTests.java index c7810c18d..df168fe7d 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulIntegrationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulIntegrationTests.java @@ -10,6 +10,7 @@ import com.netflix.zuul.context.RequestContext; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -33,6 +34,7 @@ import org.springframework.cloud.sleuth.assertions.ListOfSpans; 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.http.HttpEntity; @@ -67,6 +69,7 @@ public class TraceZuulIntegrationTests { @Autowired RestTemplate restTemplate; @Before + @After public void cleanup() { TestSpanContextHolder.removeCurrentSpan(); RequestContext.getCurrentContext().unset(); @@ -92,6 +95,7 @@ public class TraceZuulIntegrationTests { .tag("http.method", "GET") .tag("http.status_code", "200") .tag("http.path", "/simple/foo")); + then(ExceptionUtils.getLastException()).isNull(); } @Test @@ -112,6 +116,7 @@ public class TraceZuulIntegrationTests { .tag("http.method", "GET") .tag("http.status_code", "404") .tag("http.path", "/simple/nonExistentUrl")); + then(ExceptionUtils.getLastException()).isNull(); } private static class TestTag extends HashMap { diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/util/SpanNameUtilTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/util/SpanNameUtilTests.java index 35ba6c3f7..517576f2b 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/util/SpanNameUtilTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/util/SpanNameUtilTests.java @@ -26,4 +26,10 @@ public class SpanNameUtilTests { SleuthAssertions.then(SpanNameUtil.toLowerHyphen("aMethodNameInCamelCaseNotation")) .isEqualTo("a-method-name-in-camel-case-notation"); } + + @Test + public void should_convert_a_class_name_in_hyphen_based_notation() throws Exception { + SleuthAssertions.then(SpanNameUtil.toLowerHyphen("MySuperClassName")) + .isEqualTo("my-super-class-name"); + } } \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/test/resources/logback.xml b/spring-cloud-sleuth-core/src/test/resources/logback.xml index 307883eea..fd0c9a35a 100644 --- a/spring-cloud-sleuth-core/src/test/resources/logback.xml +++ b/spring-cloud-sleuth-core/src/test/resources/logback.xml @@ -5,8 +5,6 @@ - - From cb027db522a25f86d7e8228aaeb07a952eb250ee Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Thu, 7 Jul 2016 17:04:06 +0200 Subject: [PATCH 009/235] Update SNAPSHOT to 1.0.3.RELEASE --- benchmarks/pom.xml | 2 +- docs/pom.xml | 2 +- pom.xml | 6 +++--- spring-cloud-sleuth-core/pom.xml | 2 +- spring-cloud-sleuth-dependencies/pom.xml | 6 +++--- spring-cloud-sleuth-samples/pom.xml | 2 +- .../spring-cloud-sleuth-sample-feign/pom.xml | 2 +- .../spring-cloud-sleuth-sample-messaging/pom.xml | 2 +- .../spring-cloud-sleuth-sample-ribbon/pom.xml | 2 +- .../spring-cloud-sleuth-sample-stream/pom.xml | 2 +- .../spring-cloud-sleuth-sample-test-core/pom.xml | 2 +- .../spring-cloud-sleuth-sample-websocket/pom.xml | 2 +- .../spring-cloud-sleuth-sample-zipkin-stream/pom.xml | 2 +- .../spring-cloud-sleuth-sample-zipkin/pom.xml | 2 +- .../spring-cloud-sleuth-sample/pom.xml | 2 +- spring-cloud-sleuth-stream/pom.xml | 2 +- spring-cloud-sleuth-zipkin-stream/pom.xml | 2 +- spring-cloud-sleuth-zipkin/pom.xml | 2 +- spring-cloud-starter-sleuth/pom.xml | 2 +- spring-cloud-starter-zipkin/pom.xml | 2 +- 20 files changed, 24 insertions(+), 24 deletions(-) diff --git a/benchmarks/pom.xml b/benchmarks/pom.xml index 893f0220a..e5d50a9f0 100644 --- a/benchmarks/pom.xml +++ b/benchmarks/pom.xml @@ -17,7 +17,7 @@ Benchmarks Benchmarks (JMH) org.springframework.cloud - 1.0.3.BUILD-SNAPSHOT + 1.0.3.RELEASE benchmarks diff --git a/docs/pom.xml b/docs/pom.xml index 97500b55d..1dbc934ce 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.3.BUILD-SNAPSHOT + 1.0.3.RELEASE spring-cloud-sleuth-docs pom diff --git a/pom.xml b/pom.xml index fd8f3fe3e..d8175bcef 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 spring-cloud-sleuth - 1.0.3.BUILD-SNAPSHOT + 1.0.3.RELEASE pom Spring Cloud Sleuth Spring Cloud Sleuth @@ -13,7 +13,7 @@ org.springframework.cloud spring-cloud-build - 1.1.2.BUILD-SNAPSHOT + 1.1.1.RELEASE @@ -208,7 +208,7 @@ 1.8 2.19.1 2.17 - 1.1.2.BUILD-SNAPSHOT + 1.1.1.RELEASE diff --git a/spring-cloud-sleuth-core/pom.xml b/spring-cloud-sleuth-core/pom.xml index 33c4e9f0d..506e8db90 100644 --- a/spring-cloud-sleuth-core/pom.xml +++ b/spring-cloud-sleuth-core/pom.xml @@ -11,7 +11,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.3.BUILD-SNAPSHOT + 1.0.3.RELEASE .. diff --git a/spring-cloud-sleuth-dependencies/pom.xml b/spring-cloud-sleuth-dependencies/pom.xml index 38f3704cd..368a780b4 100644 --- a/spring-cloud-sleuth-dependencies/pom.xml +++ b/spring-cloud-sleuth-dependencies/pom.xml @@ -5,16 +5,16 @@ spring-cloud-dependencies-parent org.springframework.cloud - 1.1.2.BUILD-SNAPSHOT + 1.1.1.RELEASE spring-cloud-sleuth-dependencies - 1.0.3.BUILD-SNAPSHOT + 1.0.3.RELEASE pom spring-cloud-sleuth-dependencies Spring Cloud Sleuth Dependencies - 1.1.4.BUILD-SNAPSHOT + 1.1.3.RELEASE 1.8.4 1.1.5 diff --git a/spring-cloud-sleuth-samples/pom.xml b/spring-cloud-sleuth-samples/pom.xml index f490bec60..03f8fd5c4 100644 --- a/spring-cloud-sleuth-samples/pom.xml +++ b/spring-cloud-sleuth-samples/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.3.BUILD-SNAPSHOT + 1.0.3.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml index b041761f6..ff003e228 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml @@ -20,7 +20,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.3.BUILD-SNAPSHOT + 1.0.3.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml index 94abc7617..3536b940b 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.3.BUILD-SNAPSHOT + 1.0.3.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml index 340520845..d0f7878ed 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.3.BUILD-SNAPSHOT + 1.0.3.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml index e348e8229..d693a266c 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.3.BUILD-SNAPSHOT + 1.0.3.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml index e821cc703..5b9c0b4f7 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.3.BUILD-SNAPSHOT + 1.0.3.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml index 631a9cbf3..4e0b6d0f6 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.3.BUILD-SNAPSHOT + 1.0.3.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml index 567671e47..202cd11a7 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.3.BUILD-SNAPSHOT + 1.0.3.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml index 24f24b970..4aebedf47 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml @@ -27,7 +27,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.3.BUILD-SNAPSHOT + 1.0.3.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml index b0e59f0e1..a4b83c4f5 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.3.BUILD-SNAPSHOT + 1.0.3.RELEASE .. diff --git a/spring-cloud-sleuth-stream/pom.xml b/spring-cloud-sleuth-stream/pom.xml index 596e9b56f..4f0560b6d 100644 --- a/spring-cloud-sleuth-stream/pom.xml +++ b/spring-cloud-sleuth-stream/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.3.BUILD-SNAPSHOT + 1.0.3.RELEASE diff --git a/spring-cloud-sleuth-zipkin-stream/pom.xml b/spring-cloud-sleuth-zipkin-stream/pom.xml index 746add274..b23e0ae00 100644 --- a/spring-cloud-sleuth-zipkin-stream/pom.xml +++ b/spring-cloud-sleuth-zipkin-stream/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.3.BUILD-SNAPSHOT + 1.0.3.RELEASE diff --git a/spring-cloud-sleuth-zipkin/pom.xml b/spring-cloud-sleuth-zipkin/pom.xml index f42b1baa3..0b5150d24 100644 --- a/spring-cloud-sleuth-zipkin/pom.xml +++ b/spring-cloud-sleuth-zipkin/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.3.BUILD-SNAPSHOT + 1.0.3.RELEASE .. diff --git a/spring-cloud-starter-sleuth/pom.xml b/spring-cloud-starter-sleuth/pom.xml index 57c757fd8..c2cc424e5 100644 --- a/spring-cloud-starter-sleuth/pom.xml +++ b/spring-cloud-starter-sleuth/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.3.BUILD-SNAPSHOT + 1.0.3.RELEASE .. spring-cloud-starter-sleuth diff --git a/spring-cloud-starter-zipkin/pom.xml b/spring-cloud-starter-zipkin/pom.xml index aa8ae96ac..80a2413a1 100644 --- a/spring-cloud-starter-zipkin/pom.xml +++ b/spring-cloud-starter-zipkin/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.3.BUILD-SNAPSHOT + 1.0.3.RELEASE .. spring-cloud-starter-zipkin From 66d11435e8dc3858bdc446710b305ba540947cfb Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Thu, 7 Jul 2016 17:33:52 +0200 Subject: [PATCH 010/235] Update to SNAPSHOT --- benchmarks/pom.xml | 2 +- docs/pom.xml | 2 +- pom.xml | 6 +++--- spring-cloud-sleuth-core/pom.xml | 2 +- spring-cloud-sleuth-dependencies/pom.xml | 6 +++--- spring-cloud-sleuth-samples/pom.xml | 2 +- .../spring-cloud-sleuth-sample-feign/pom.xml | 2 +- .../spring-cloud-sleuth-sample-messaging/pom.xml | 2 +- .../spring-cloud-sleuth-sample-ribbon/pom.xml | 2 +- .../spring-cloud-sleuth-sample-stream/pom.xml | 2 +- .../spring-cloud-sleuth-sample-test-core/pom.xml | 2 +- .../spring-cloud-sleuth-sample-websocket/pom.xml | 2 +- .../spring-cloud-sleuth-sample-zipkin-stream/pom.xml | 2 +- .../spring-cloud-sleuth-sample-zipkin/pom.xml | 2 +- .../spring-cloud-sleuth-sample/pom.xml | 2 +- spring-cloud-sleuth-stream/pom.xml | 2 +- spring-cloud-sleuth-zipkin-stream/pom.xml | 2 +- spring-cloud-sleuth-zipkin/pom.xml | 2 +- spring-cloud-starter-sleuth/pom.xml | 2 +- spring-cloud-starter-zipkin/pom.xml | 2 +- 20 files changed, 24 insertions(+), 24 deletions(-) diff --git a/benchmarks/pom.xml b/benchmarks/pom.xml index e5d50a9f0..d1db34781 100644 --- a/benchmarks/pom.xml +++ b/benchmarks/pom.xml @@ -17,7 +17,7 @@ Benchmarks Benchmarks (JMH) org.springframework.cloud - 1.0.3.RELEASE + 1.0.4.BUILD-SNAPSHOT benchmarks diff --git a/docs/pom.xml b/docs/pom.xml index 1dbc934ce..2e58efa82 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.3.RELEASE + 1.0.4.BUILD-SNAPSHOT spring-cloud-sleuth-docs pom diff --git a/pom.xml b/pom.xml index d8175bcef..a19296c10 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 spring-cloud-sleuth - 1.0.3.RELEASE + 1.0.4.BUILD-SNAPSHOT pom Spring Cloud Sleuth Spring Cloud Sleuth @@ -13,7 +13,7 @@ org.springframework.cloud spring-cloud-build - 1.1.1.RELEASE + 1.1.2.BUILD-SNAPSHOT @@ -208,7 +208,7 @@ 1.8 2.19.1 2.17 - 1.1.1.RELEASE + 1.1.2.BUILD-SNAPSHOT diff --git a/spring-cloud-sleuth-core/pom.xml b/spring-cloud-sleuth-core/pom.xml index 506e8db90..eb88bdfb2 100644 --- a/spring-cloud-sleuth-core/pom.xml +++ b/spring-cloud-sleuth-core/pom.xml @@ -11,7 +11,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.3.RELEASE + 1.0.4.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-dependencies/pom.xml b/spring-cloud-sleuth-dependencies/pom.xml index 368a780b4..448e923cc 100644 --- a/spring-cloud-sleuth-dependencies/pom.xml +++ b/spring-cloud-sleuth-dependencies/pom.xml @@ -5,16 +5,16 @@ spring-cloud-dependencies-parent org.springframework.cloud - 1.1.1.RELEASE + 1.1.2.BUILD-SNAPSHOT spring-cloud-sleuth-dependencies - 1.0.3.RELEASE + 1.0.4.BUILD-SNAPSHOT pom spring-cloud-sleuth-dependencies Spring Cloud Sleuth Dependencies - 1.1.3.RELEASE + 1.1.4.BUILD-SNAPSHOT 1.8.4 1.1.5 diff --git a/spring-cloud-sleuth-samples/pom.xml b/spring-cloud-sleuth-samples/pom.xml index 03f8fd5c4..b8fe37876 100644 --- a/spring-cloud-sleuth-samples/pom.xml +++ b/spring-cloud-sleuth-samples/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.3.RELEASE + 1.0.4.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml index ff003e228..ad7362362 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml @@ -20,7 +20,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.3.RELEASE + 1.0.4.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml index 3536b940b..48ed16f29 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.3.RELEASE + 1.0.4.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml index d0f7878ed..4b25877d7 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.3.RELEASE + 1.0.4.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml index d693a266c..f651f500c 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.3.RELEASE + 1.0.4.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml index 5b9c0b4f7..de89d7bb1 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.3.RELEASE + 1.0.4.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml index 4e0b6d0f6..5c26d0fb3 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.3.RELEASE + 1.0.4.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml index 202cd11a7..6f3a647f7 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.3.RELEASE + 1.0.4.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml index 4aebedf47..5f8f0ff7f 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml @@ -27,7 +27,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.3.RELEASE + 1.0.4.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml index a4b83c4f5..e7348b3de 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.3.RELEASE + 1.0.4.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-stream/pom.xml b/spring-cloud-sleuth-stream/pom.xml index 4f0560b6d..15b44239a 100644 --- a/spring-cloud-sleuth-stream/pom.xml +++ b/spring-cloud-sleuth-stream/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.3.RELEASE + 1.0.4.BUILD-SNAPSHOT diff --git a/spring-cloud-sleuth-zipkin-stream/pom.xml b/spring-cloud-sleuth-zipkin-stream/pom.xml index b23e0ae00..32842df5e 100644 --- a/spring-cloud-sleuth-zipkin-stream/pom.xml +++ b/spring-cloud-sleuth-zipkin-stream/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.3.RELEASE + 1.0.4.BUILD-SNAPSHOT diff --git a/spring-cloud-sleuth-zipkin/pom.xml b/spring-cloud-sleuth-zipkin/pom.xml index 0b5150d24..9e425eaa1 100644 --- a/spring-cloud-sleuth-zipkin/pom.xml +++ b/spring-cloud-sleuth-zipkin/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.3.RELEASE + 1.0.4.BUILD-SNAPSHOT .. diff --git a/spring-cloud-starter-sleuth/pom.xml b/spring-cloud-starter-sleuth/pom.xml index c2cc424e5..57a364f61 100644 --- a/spring-cloud-starter-sleuth/pom.xml +++ b/spring-cloud-starter-sleuth/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.3.RELEASE + 1.0.4.BUILD-SNAPSHOT .. spring-cloud-starter-sleuth diff --git a/spring-cloud-starter-zipkin/pom.xml b/spring-cloud-starter-zipkin/pom.xml index 80a2413a1..2191ec89b 100644 --- a/spring-cloud-starter-zipkin/pom.xml +++ b/spring-cloud-starter-zipkin/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.3.RELEASE + 1.0.4.BUILD-SNAPSHOT .. spring-cloud-starter-zipkin From e7838743afedbfc45fd8058d407571fd6d234175 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Thu, 14 Jul 2016 11:57:09 +0200 Subject: [PATCH 011/235] Fix headers in messaging (#335) * Fix headers in messaging with this change the span related headers are changed to be compatible with messaging specs. The change is backwards compatible so we accept the old ones as input, and as output we're producing both new and old headers. fixes #332 --- .travis.yml | 38 ----------- circle.yml | 21 ++++++ .../main/asciidoc/spring-cloud-sleuth.adoc | 8 +++ pom.xml | 2 +- .../messaging/MessagingSpanExtractor.java | 59 ++++++++++++---- .../messaging/MessagingSpanInjector.java | 68 +++++++++++++------ .../messaging/TraceChannelInterceptor.java | 21 ++++-- .../messaging/TraceMessageHeaders.java | 40 +++++++++++ .../TraceChannelInterceptorTests.java | 47 +++++++++++++ 9 files changed, 225 insertions(+), 79 deletions(-) delete mode 100644 .travis.yml create mode 100644 circle.yml create mode 100644 spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceMessageHeaders.java diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 2d2cc22a2..000000000 --- a/.travis.yml +++ /dev/null @@ -1,38 +0,0 @@ -language: java -jdk: - - oraclejdk8 -sudo: required -dist: trusty -cache: - directories: - - $HOME/.m2 -install: -- | - cat < log.log || echo "FAILED!" && grep -v '^.*Download.* http.*$' log.log && exit 1 -- './mvnw -s .settings.xml $MVN_GOAL $MVN_PROFILE -nsu -U --batch-mode -Dmaven.test.redirectTestOutputToFile=true' -env: - global: - - FEATURE_BRANCH=$(echo ${TRAVIS_BRANCH} | grep -v "master" && echo true || echo false) - - SPRING_CLOUD_BUILD=$(echo ${TRAVIS_REPO_SLUG} | grep -q "^spring-cloud/.*$" && echo true || echo false) - - MVN_GOAL=$([ "${TRAVIS_PULL_REQUEST}" == "false" -a "${TRAVIS_TAG}" == "" -a "${FEATURE_BRANCH}" == "false" -a "${SPRING_CLOUD_BUILD}" == "true" ] && echo deploy || echo install) - - VERSION=$(mvn validate | grep Building | head -1 | sed -e 's/.* //') - - MILESTONE=$(echo ${VERSION} | egrep 'M|RC' && echo true || echo false) - - MVN_PROFILE=$([ "${MILESTONE}" == "true" ] && echo -P milestone) - - TERM=dumb - diff --git a/circle.yml b/circle.yml new file mode 100644 index 000000000..67412025d --- /dev/null +++ b/circle.yml @@ -0,0 +1,21 @@ +machine: + java: + version: oraclejdk8 + environment: + _JAVA_OPTIONS: "-Xms1024m -Xmx2048m" +dependencies: + override: + - ./mvnw -s .settings.xml --fail-never dependency:go-offline || true +test: + override: + - ./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: + # A list of hook hashes, containing the url field + # gitter hook + - url: https://webhooks.gitter.im/e/fac69f6fc000ef14f7ec \ No newline at end of file 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 a19296c10..5b7f70fed 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()); From b2cc03bc16200994f9a9a724eab26d09811a3eef Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Thu, 14 Jul 2016 12:54:37 +0200 Subject: [PATCH 012/235] Fixed missing wrapping of ExecutorService with this change the missing methods in ExecutorService are wrapped with LocalComponentTraceCallable. We are wrapping only those callables that have not already been wrapped. Kudos to @PranayPS for finding the issue and proposing a solution. Fixes #333 --- .../async/TraceableExecutorService.java | 22 +++++-- .../async/TraceableExecutorServiceTests.java | 58 +++++++++++++++++++ 2 files changed, 75 insertions(+), 5 deletions(-) diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceableExecutorService.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceableExecutorService.java index a3fb9b7a1..4bd0d578e 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceableExecutorService.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceableExecutorService.java @@ -15,6 +15,7 @@ */ package org.springframework.cloud.sleuth.instrument.async; +import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.concurrent.Callable; @@ -25,8 +26,8 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import org.springframework.cloud.sleuth.SpanNamer; -import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.TraceKeys; +import org.springframework.cloud.sleuth.Tracer; /** * A decorator class for {@link ExecutorService} to support tracing in Executors @@ -110,24 +111,35 @@ public class TraceableExecutorService implements ExecutorService { @Override public List> invokeAll(Collection> tasks) throws InterruptedException { - return this.delegate.invokeAll(tasks); + return this.delegate.invokeAll(wrapCallableCollection(tasks)); } @Override public List> invokeAll(Collection> tasks, long timeout, TimeUnit unit) throws InterruptedException { - return this.delegate.invokeAll(tasks, timeout, unit); + return this.delegate.invokeAll(wrapCallableCollection(tasks), timeout, unit); } @Override public T invokeAny(Collection> tasks) throws InterruptedException, ExecutionException { - return this.delegate.invokeAny(tasks); + return this.delegate.invokeAny(wrapCallableCollection(tasks)); } @Override public T invokeAny(Collection> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { - return this.delegate.invokeAny(tasks, timeout, unit); + return this.delegate.invokeAny(wrapCallableCollection(tasks), timeout, unit); + } + + private Collection> wrapCallableCollection(Collection> tasks) { + List> ts = new ArrayList<>(); + for (Callable task : tasks) { + if (!(task instanceof LocalComponentTraceCallable)) { + ts.add(new LocalComponentTraceCallable<>(this.tracer, this.traceKeys, + this.spanNamer, this.spanName, task)); + } + } + return ts; } } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/TraceableExecutorServiceTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/TraceableExecutorServiceTests.java index e44077ccf..cc51a77f5 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/TraceableExecutorServiceTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/TraceableExecutorServiceTests.java @@ -1,19 +1,27 @@ package org.springframework.cloud.sleuth.instrument.async; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.Queue; import java.util.Random; +import java.util.concurrent.Callable; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.BDDMockito; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.runners.MockitoJUnitRunner; import org.springframework.cloud.sleuth.DefaultSpanNamer; import org.springframework.cloud.sleuth.NoOpSpanReporter; @@ -21,6 +29,7 @@ import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.SpanNamer; import org.springframework.cloud.sleuth.TraceKeys; import org.springframework.cloud.sleuth.Tracer; +import org.springframework.cloud.sleuth.assertions.SleuthAssertions; import org.springframework.cloud.sleuth.log.NoOpSpanLogger; import org.springframework.cloud.sleuth.sampler.AlwaysSampler; import org.springframework.cloud.sleuth.trace.DefaultTracer; @@ -67,6 +76,55 @@ public class TraceableExecutorServiceTests { then(this.spanVerifyingRunnable.spanIds.stream().distinct().collect(toList())).hasSize(TOTAL_THREADS); } + @Test + @SuppressWarnings("unchecked") + public void should_wrap_methods_in_trace_representation_only_for_non_tracing_callables() throws Exception { + ExecutorService executorService = Mockito.mock(ExecutorService.class); + TraceableExecutorService traceManagerableExecutorService = new TraceableExecutorService( + executorService, this.tracer, new TraceKeys(), this.spanNamer); + + traceManagerableExecutorService.invokeAll(callables()); + BDDMockito.then(executorService).should().invokeAll(BDDMockito.argThat(withOneLocalComponentTraceCallable())); + + traceManagerableExecutorService.invokeAll(callables(), 1L, TimeUnit.DAYS); + BDDMockito.then(executorService).should().invokeAll(BDDMockito.argThat(withOneLocalComponentTraceCallable()), + BDDMockito.eq(1L) , BDDMockito.eq(TimeUnit.DAYS)); + + traceManagerableExecutorService.invokeAny(callables()); + BDDMockito.then(executorService).should().invokeAny(BDDMockito.argThat(withOneLocalComponentTraceCallable())); + + traceManagerableExecutorService.invokeAny(callables(), 1L, TimeUnit.DAYS); + BDDMockito.then(executorService).should().invokeAny(BDDMockito.argThat(withOneLocalComponentTraceCallable()), + BDDMockito.eq(1L) , BDDMockito.eq(TimeUnit.DAYS)); + } + + private Matcher>> withOneLocalComponentTraceCallable() { + return new TypeSafeMatcher>>() { + @Override + protected boolean matchesSafely(Collection> item) { + try { + SleuthAssertions.then(item) + .flatExtracting(Object::getClass) + .containsExactly(LocalComponentTraceCallable.class); + } catch (AssertionError e) { + return false; + } + return true; + } + + @Override public void describeTo(Description description) { + description.appendText("should contain a single local component trace callable"); + } + }; + } + + private List callables() { + List list = new ArrayList<>(); + list.add(new LocalComponentTraceCallable(this.tracer, new TraceKeys(), this.spanNamer, () -> "foo")); + list.add((Callable) () -> "bar"); + return list; + } + @Test public void should_propagate_trace_info_when_compleable_future_is_used() throws Exception { Tracer tracer = this.tracer; From 23097dd2f9de7f508558781ea59422f429028751 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Thu, 14 Jul 2016 13:46:00 +0200 Subject: [PATCH 013/235] Added Codecov integration --- README.adoc | 2 +- docs/src/main/asciidoc/README.adoc | 3 ++- pom.xml | 42 ++++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/README.adoc b/README.adoc index 80b9f038f..401323638 100644 --- a/README.adoc +++ b/README.adoc @@ -2,7 +2,7 @@ :jdkversion: 1.8 -image::https://api.travis-ci.org/spring-cloud/spring-cloud-sleuth.svg?branch=master[Build Status, link=https://travis-ci.org/spring-cloud/spring-cloud-sleuth] +image::https://circleci.com/gh/spring-cloud/spring-cloud-sleuth.svg?style=svg["CircleCI", link="https://circleci.com/gh/spring-cloud/spring-cloud-sleuth"] image::https://badges.gitter.im/spring-cloud/spring-cloud-sleuth.svg[Gitter, link="https://gitter.im/spring-cloud/spring-cloud-sleuth?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge"] == Spring Cloud Sleuth diff --git a/docs/src/main/asciidoc/README.adoc b/docs/src/main/asciidoc/README.adoc index 8a946ee0a..2e418d33f 100644 --- a/docs/src/main/asciidoc/README.adoc +++ b/docs/src/main/asciidoc/README.adoc @@ -1,6 +1,7 @@ :jdkversion: 1.8 -image::https://api.travis-ci.org/spring-cloud/spring-cloud-sleuth.svg?branch=master[Build Status, link=https://travis-ci.org/spring-cloud/spring-cloud-sleuth] +image::https://circleci.com/gh/spring-cloud/spring-cloud-sleuth.svg?style=svg["CircleCI", link="https://circleci.com/gh/spring-cloud/spring-cloud-sleuth"] +image::https://codecov.io/gh/spring-cloud/spring-cloud-sleuth/branch/master/graph/badge.svg["codecov", link="https://codecov.io/gh/spring-cloud/spring-cloud-sleuth"] image::https://badges.gitter.im/spring-cloud/spring-cloud-sleuth.svg[Gitter, link="https://gitter.im/spring-cloud/spring-cloud-sleuth?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge"] == Spring Cloud Sleuth diff --git a/pom.xml b/pom.xml index 5b7f70fed..f39ca2411 100644 --- a/pom.xml +++ b/pom.xml @@ -309,6 +309,48 @@ benchmarks + + sonar + + + + org.jacoco + jacoco-maven-plugin + + + pre-unit-test + + prepare-agent + + + surefireArgLine + ${project.build.directory}/jacoco.exec + + + + post-unit-test + test + + report + + + + ${project.build.directory}/jacoco.exec + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + ${surefireArgLine} + + + + + From 2cbad45fa4da6161e4663790ebe985f5405e66c7 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Thu, 14 Jul 2016 13:47:40 +0200 Subject: [PATCH 014/235] Added additional javadoc information for #332 --- .../sleuth/instrument/messaging/TraceMessageHeaders.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceMessageHeaders.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceMessageHeaders.java index fc2edb59a..1bd173148 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceMessageHeaders.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceMessageHeaders.java @@ -17,7 +17,9 @@ package org.springframework.cloud.sleuth.instrument.messaging; /** - * Contains trace related messaging headers + * Contains trace related messaging headers. The deprecated headers contained `-` which + * for example in the JMS specs is invalid. That's why the public constants in this class + * represent the valid names for the tracing headers. * * @author Marcin Grzejszczak * @since 1.0.4 From 56af4d5b1df4d8c3f6ee6a37b81420e001956989 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Thu, 14 Jul 2016 14:21:46 +0200 Subject: [PATCH 015/235] Fixed the missing hyphen in logging pattern --- .../cloud/sleuth/autoconfig/TraceEnvironmentPostProcessor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/TraceEnvironmentPostProcessor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/TraceEnvironmentPostProcessor.java index 9de92aff2..8a4c1ae37 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/TraceEnvironmentPostProcessor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/TraceEnvironmentPostProcessor.java @@ -51,7 +51,7 @@ public class TraceEnvironmentPostProcessor implements EnvironmentPostProcessor { // This doesn't work with all logging systems but it's a useful default so you see // traces in logs without having to configure it. map.put("logging.pattern.level", - "%clr(%5p) %clr([${spring.application.name:},%X{X-B3-TraceId:-},%X{X-B3-SpanId:-},%X{X-Span-Export:-}]){yellow}"); + "%clr(%5p) %clr([${spring.application.name:-},%X{X-B3-TraceId:-},%X{X-B3-SpanId:-},%X{X-Span-Export:-}]){yellow}"); map.put("spring.aop.proxyTargetClass", "true"); addOrReplace(environment.getPropertySources(), map); } From 843f21195937c27d8aca3fe66d2449dc9f2e9e42 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Tue, 12 Jul 2016 14:20:54 +0200 Subject: [PATCH 016/235] Randomized port for a test --- .../web/client/feign/issues/Issue307Tests.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/Issue307Tests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/Issue307Tests.java index 7ba667b43..2bb42443d 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/Issue307Tests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/Issue307Tests.java @@ -36,6 +36,7 @@ import org.springframework.cloud.sleuth.trace.TestSpanContextHolder; import org.springframework.cloud.sleuth.util.ExceptionUtils; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; +import org.springframework.core.env.Environment; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; @@ -55,7 +56,7 @@ public class Issue307Tests { @Test public void should_start_context() { try (ConfigurableApplicationContext applicationContext = SpringApplication - .run(SleuthSampleApplication.class, "--spring.jmx.enabled=false")) { + .run(SleuthSampleApplication.class, "--spring.jmx.enabled=false", "server.port=0")) { } then(ExceptionUtils.getLastException()).isNull(); } @@ -72,6 +73,9 @@ class SleuthSampleApplication { @Autowired private RestTemplate restTemplate; + @Autowired + private Environment environment; + @Autowired private ParticipantsBean participantsBean; @@ -94,7 +98,11 @@ class SleuthSampleApplication { @RequestMapping("/callhome") public String callHome() { LOG.log(Level.INFO, "calling home"); - return restTemplate.getForObject("http://localhost:8080", String.class); + return restTemplate.getForObject("http://localhost:" + port(), String.class); + } + + private int port() { + return this.environment.getProperty("local.server.port", Integer.class); } } From 5861826c6218e6f611a503bfa2b37600be5579fe Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Thu, 14 Jul 2016 23:43:23 +0200 Subject: [PATCH 017/235] Fixed the port --- .../instrument/web/client/feign/issues/Issue307Tests.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/Issue307Tests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/Issue307Tests.java index 2bb42443d..42d58f80e 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/Issue307Tests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/Issue307Tests.java @@ -19,8 +19,6 @@ package org.springframework.cloud.sleuth.instrument.web.client.feign.issues; import java.util.ArrayList; import java.util.List; -import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; - import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.junit.Before; @@ -44,6 +42,8 @@ import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; +import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; + import static org.assertj.core.api.BDDAssertions.then; public class Issue307Tests { @@ -56,7 +56,7 @@ public class Issue307Tests { @Test public void should_start_context() { try (ConfigurableApplicationContext applicationContext = SpringApplication - .run(SleuthSampleApplication.class, "--spring.jmx.enabled=false", "server.port=0")) { + .run(SleuthSampleApplication.class, "--spring.jmx.enabled=false", "--server.port=0")) { } then(ExceptionUtils.getLastException()).isNull(); } From 0e06e4dc92f8ab27ce7061d060aaf43c0fb25b6b Mon Sep 17 00:00:00 2001 From: Steve Oakey Date: Fri, 15 Jul 2016 02:21:03 -0400 Subject: [PATCH 018/235] Added patterns to @GlobalChannelInterceptor (#337) Makes use of property place holders to set the patterns attribute of the @GlobalChannelInterceptor annotation used for the TraceChannelInterceptor. As supported by the annotation, only simple patterns are supported. Added the Spring configuration metadata for spring.sleuth.integration properties. Fixes gh-323 --- ...aceSpringIntegrationAutoConfiguration.java | 2 +- ...itional-spring-configuration-metadata.json | 14 +++ .../TraceChannelInterceptorTests.java | 91 +++++++++++++------ 3 files changed, 78 insertions(+), 29 deletions(-) create mode 100644 spring-cloud-sleuth-core/src/main/resources/META-INF/additional-spring-configuration-metadata.json diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceSpringIntegrationAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceSpringIntegrationAutoConfiguration.java index b7d1271b5..d9eb1b008 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceSpringIntegrationAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceSpringIntegrationAutoConfiguration.java @@ -54,7 +54,7 @@ import org.springframework.messaging.support.MessageBuilder; public class TraceSpringIntegrationAutoConfiguration { @Bean - @GlobalChannelInterceptor + @GlobalChannelInterceptor(patterns = "${spring.sleuth.integration.patterns:*}") public TraceChannelInterceptor traceChannelInterceptor(Tracer tracer, TraceKeys traceKeys, Random random, SpanExtractor> spanExtractor, SpanInjector> spanInjector) { diff --git a/spring-cloud-sleuth-core/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-cloud-sleuth-core/src/main/resources/META-INF/additional-spring-configuration-metadata.json new file mode 100644 index 000000000..3dfc54046 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -0,0 +1,14 @@ +{"properties": [ + { + "name": "spring.sleuth.integration.patterns", + "type": "java.lang.String[]", + "description": "An array of simple patterns against which channel names will be matched. Default is * (all channels). See org.springframework.util.PatternMatchUtils.simpleMatch(String, String).", + "defaultValue": "*" + }, + { + "name": "spring.sleuth.integration.enabled", + "type": "java.lang.Boolean", + "description": "Enable Spring Integration sleuth instrumentation.", + "defaultValue": true + } +]} \ No newline at end of file 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 6702bfcbb..2a8bb6589 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/TraceChannelInterceptorTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/TraceChannelInterceptorTests.java @@ -16,6 +16,10 @@ 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; @@ -26,8 +30,8 @@ import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.IntegrationTest; -import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.cloud.sleuth.Sampler; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.Tracer; @@ -45,23 +49,25 @@ import org.springframework.messaging.Message; import org.springframework.messaging.MessageHandler; import org.springframework.messaging.MessagingException; import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import static org.junit.Assert.assertNotNull; -import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; - /** * @author Dave Syer */ @RunWith(SpringJUnit4ClassRunner.class) -@SpringApplicationConfiguration(classes = App.class) -@IntegrationTest +@SpringBootTest(classes = App.class, webEnvironment = WebEnvironment.NONE) @DirtiesContext +@TestPropertySource(properties = "spring.sleuth.integration.patterns=traced*") public class TraceChannelInterceptorTests implements MessageHandler { @Autowired - @Qualifier("channel") - private DirectChannel channel; + @Qualifier("tracedChannel") + private DirectChannel tracedChannel; + + @Autowired + @Qualifier("ignoredChannel") + private DirectChannel ignoredChannel; @Autowired private Tracer tracer; @@ -87,20 +93,22 @@ public class TraceChannelInterceptorTests implements MessageHandler { @Before public void init() { - this.channel.subscribe(this); + this.tracedChannel.subscribe(this); + this.ignoredChannel.subscribe(this); this.accumulator.getSpans().clear(); } @After public void close() { TestSpanContextHolder.removeCurrentSpan(); - this.channel.unsubscribe(this); + this.tracedChannel.unsubscribe(this); + this.ignoredChannel.unsubscribe(this); this.accumulator.getSpans().clear(); } @Test public void nonExportableSpanCreation() { - this.channel.send(MessageBuilder.withPayload("hi") + this.tracedChannel.send(MessageBuilder.withPayload("hi") .setHeader(Span.SAMPLED_NAME, Span.SPAN_NOT_SAMPLED).build()); assertNotNull("message was null", this.message); @@ -112,7 +120,7 @@ public class TraceChannelInterceptorTests implements MessageHandler { @Test public void parentSpanIncluded() { - this.channel.send(MessageBuilder.withPayload("hi") + 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(); @@ -129,7 +137,7 @@ public class TraceChannelInterceptorTests implements MessageHandler { // #332 @Test public void shouldSendNewAndOldHeadersWhenNewHeadersWerePassed() { - this.channel.send(MessageBuilder.withPayload("hi") + 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(); @@ -141,7 +149,8 @@ public class TraceChannelInterceptorTests implements MessageHandler { } private String thenNewSpanIdEqualsOldSpanId() { - String newSpanId = this.message.getHeaders().get(TraceMessageHeaders.SPAN_ID_NAME, String.class); + 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); @@ -151,7 +160,7 @@ public class TraceChannelInterceptorTests implements MessageHandler { // #332 @Test public void shouldSendNewAndOldHeadersWhenOldHeadersWerePassed() { - this.channel.send(MessageBuilder.withPayload("hi") + 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(); @@ -163,8 +172,8 @@ public class TraceChannelInterceptorTests implements MessageHandler { } private void thenNewTraceIdEqualsOldTraceId() { - long traceId = Span - .hexToId(this.message.getHeaders().get(TraceMessageHeaders.TRACE_ID_NAME, String.class)); + 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)); @@ -173,7 +182,7 @@ public class TraceChannelInterceptorTests implements MessageHandler { @Test public void spanCreation() { - this.channel.send(MessageBuilder.withPayload("hi").build()); + this.tracedChannel.send(MessageBuilder.withPayload("hi").build()); then(this.message).isNotNull(); String spanId = this.message.getHeaders().get(Span.SPAN_ID_NAME, String.class); @@ -186,22 +195,25 @@ public class TraceChannelInterceptorTests implements MessageHandler { @Test public void shouldLogClientReceivedClientSentEventWhenTheMessageIsSentAndReceived() { - this.channel.send(MessageBuilder.withPayload("hi").build()); + this.tracedChannel.send(MessageBuilder.withPayload("hi").build()); - then(this.span.logs()).extracting("event").contains(Span.CLIENT_SEND, Span.CLIENT_RECV); + then(this.span.logs()).extracting("event").contains(Span.CLIENT_SEND, + Span.CLIENT_RECV); } @Test public void shouldLogServerReceivedServerSentEventWhenTheMessageIsPropagatedToTheNextListener() { - this.channel.send(MessageBuilder.withPayload("hi").setHeader("X-Message-Sent", true).build()); + this.tracedChannel.send(MessageBuilder.withPayload("hi") + .setHeader("X-Message-Sent", true).build()); - then(this.span.logs()).extracting("event").contains(Span.SERVER_RECV, Span.SERVER_SEND); + then(this.span.logs()).extracting("event").contains(Span.SERVER_RECV, + Span.SERVER_SEND); } @Test public void headerCreation() { Span span = this.tracer.createSpan("http:testSendMessage", new AlwaysSampler()); - this.channel.send(MessageBuilder.withPayload("hi").build()); + this.tracedChannel.send(MessageBuilder.withPayload("hi").build()); this.tracer.close(span); then(this.message).isNotNull(); @@ -237,15 +249,33 @@ public class TraceChannelInterceptorTests implements MessageHandler { errorHeaders.put("THROW_EXCEPTION", "TRUE"); try { - this.messagingTemplate.send(MessageBuilder.withPayload("hi").copyHeaders(errorHeaders).build()); + this.messagingTemplate.send( + MessageBuilder.withPayload("hi").copyHeaders(errorHeaders).build()); SleuthAssertions.fail("Exception should occur"); - } catch (RuntimeException e) {} + } + catch (RuntimeException e) { + } then(this.message).isNotNull(); this.tracer.close(span); then(TestSpanContextHolder.getCurrentSpan()).isNull(); } + @Test + public void shouldNotTraceIgnoredChannel() { + this.ignoredChannel.send(MessageBuilder.withPayload("hi").build()); + then(this.message).isNotNull(); + + String spanId = this.message.getHeaders().get(Span.SPAN_ID_NAME, String.class); + then(spanId).isNull(); + + String traceId = this.message.getHeaders().get(Span.TRACE_ID_NAME, String.class); + then(traceId).isNull(); + + then(accumulator.getSpans()).isEmpty(); + then(TestSpanContextHolder.getCurrentSpan()).isNull(); + } + @Configuration @EnableAutoConfiguration static class App { @@ -256,13 +286,18 @@ public class TraceChannelInterceptorTests implements MessageHandler { } @Bean - public DirectChannel channel() { + public DirectChannel tracedChannel() { + return new DirectChannel(); + } + + @Bean + public DirectChannel ignoredChannel() { return new DirectChannel(); } @Bean public MessagingTemplate messagingTemplate() { - return new MessagingTemplate(channel()); + return new MessagingTemplate(tracedChannel()); } @Bean From 7d0b8f62c279b7b73d998cb3d6255a0876194384 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Fri, 15 Jul 2016 08:26:36 +0200 Subject: [PATCH 019/235] Updated docs (cherry picked from commit 48efc5980a9138c821f5b1feef1b6727ca20f1ff) --- docs/src/main/asciidoc/spring-cloud-sleuth.adoc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc index 351d35172..edfa905e9 100644 --- a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc +++ b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc @@ -520,6 +520,10 @@ the same as the ones sent in HTTP (they contain a `-`) in its name. For the sake 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. +Since 1.0.4 you can provide the `spring.sleuth.integration.patterns` pattern to explicitly +provide the names of channels that you want to include for tracing. By default all channels +are included. + === Zuul We're registering Zuul filters to propagate the tracing information (the request header is enriched with tracing data). From 6484013bbfba55d3791ab09b1ed9388dd3ecaae2 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Fri, 15 Jul 2016 08:55:15 +0200 Subject: [PATCH 020/235] Revert "Added patterns to @GlobalChannelInterceptor (#337)" This reverts commit 0e06e4dc92f8ab27ce7061d060aaf43c0fb25b6b. --- ...aceSpringIntegrationAutoConfiguration.java | 2 +- ...itional-spring-configuration-metadata.json | 14 --- .../TraceChannelInterceptorTests.java | 91 ++++++------------- 3 files changed, 29 insertions(+), 78 deletions(-) delete mode 100644 spring-cloud-sleuth-core/src/main/resources/META-INF/additional-spring-configuration-metadata.json diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceSpringIntegrationAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceSpringIntegrationAutoConfiguration.java index d9eb1b008..b7d1271b5 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceSpringIntegrationAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceSpringIntegrationAutoConfiguration.java @@ -54,7 +54,7 @@ import org.springframework.messaging.support.MessageBuilder; public class TraceSpringIntegrationAutoConfiguration { @Bean - @GlobalChannelInterceptor(patterns = "${spring.sleuth.integration.patterns:*}") + @GlobalChannelInterceptor public TraceChannelInterceptor traceChannelInterceptor(Tracer tracer, TraceKeys traceKeys, Random random, SpanExtractor> spanExtractor, SpanInjector> spanInjector) { diff --git a/spring-cloud-sleuth-core/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-cloud-sleuth-core/src/main/resources/META-INF/additional-spring-configuration-metadata.json deleted file mode 100644 index 3dfc54046..000000000 --- a/spring-cloud-sleuth-core/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ /dev/null @@ -1,14 +0,0 @@ -{"properties": [ - { - "name": "spring.sleuth.integration.patterns", - "type": "java.lang.String[]", - "description": "An array of simple patterns against which channel names will be matched. Default is * (all channels). See org.springframework.util.PatternMatchUtils.simpleMatch(String, String).", - "defaultValue": "*" - }, - { - "name": "spring.sleuth.integration.enabled", - "type": "java.lang.Boolean", - "description": "Enable Spring Integration sleuth instrumentation.", - "defaultValue": true - } -]} \ No newline at end of file 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 2a8bb6589..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 @@ -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; @@ -30,8 +26,8 @@ import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.boot.test.IntegrationTest; +import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.cloud.sleuth.Sampler; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.Tracer; @@ -49,25 +45,23 @@ import org.springframework.messaging.Message; import org.springframework.messaging.MessageHandler; import org.springframework.messaging.MessagingException; import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import static org.junit.Assert.assertNotNull; +import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; + /** * @author Dave Syer */ @RunWith(SpringJUnit4ClassRunner.class) -@SpringBootTest(classes = App.class, webEnvironment = WebEnvironment.NONE) +@SpringApplicationConfiguration(classes = App.class) +@IntegrationTest @DirtiesContext -@TestPropertySource(properties = "spring.sleuth.integration.patterns=traced*") public class TraceChannelInterceptorTests implements MessageHandler { @Autowired - @Qualifier("tracedChannel") - private DirectChannel tracedChannel; - - @Autowired - @Qualifier("ignoredChannel") - private DirectChannel ignoredChannel; + @Qualifier("channel") + private DirectChannel channel; @Autowired private Tracer tracer; @@ -93,22 +87,20 @@ public class TraceChannelInterceptorTests implements MessageHandler { @Before public void init() { - this.tracedChannel.subscribe(this); - this.ignoredChannel.subscribe(this); + this.channel.subscribe(this); this.accumulator.getSpans().clear(); } @After public void close() { TestSpanContextHolder.removeCurrentSpan(); - this.tracedChannel.unsubscribe(this); - this.ignoredChannel.unsubscribe(this); + this.channel.unsubscribe(this); this.accumulator.getSpans().clear(); } @Test public void nonExportableSpanCreation() { - this.tracedChannel.send(MessageBuilder.withPayload("hi") + this.channel.send(MessageBuilder.withPayload("hi") .setHeader(Span.SAMPLED_NAME, Span.SPAN_NOT_SAMPLED).build()); assertNotNull("message was null", this.message); @@ -120,7 +112,7 @@ public class TraceChannelInterceptorTests implements MessageHandler { @Test public void parentSpanIncluded() { - this.tracedChannel.send(MessageBuilder.withPayload("hi") + 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(); @@ -137,7 +129,7 @@ public class TraceChannelInterceptorTests implements MessageHandler { // #332 @Test public void shouldSendNewAndOldHeadersWhenNewHeadersWerePassed() { - this.tracedChannel.send(MessageBuilder.withPayload("hi") + 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(); @@ -149,8 +141,7 @@ public class TraceChannelInterceptorTests implements MessageHandler { } private String thenNewSpanIdEqualsOldSpanId() { - String newSpanId = this.message.getHeaders().get(TraceMessageHeaders.SPAN_ID_NAME, - String.class); + 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); @@ -160,7 +151,7 @@ public class TraceChannelInterceptorTests implements MessageHandler { // #332 @Test public void shouldSendNewAndOldHeadersWhenOldHeadersWerePassed() { - this.tracedChannel.send(MessageBuilder.withPayload("hi") + 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(); @@ -172,8 +163,8 @@ public class TraceChannelInterceptorTests implements MessageHandler { } private void thenNewTraceIdEqualsOldTraceId() { - long traceId = Span.hexToId(this.message.getHeaders() - .get(TraceMessageHeaders.TRACE_ID_NAME, String.class)); + 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)); @@ -182,7 +173,7 @@ public class TraceChannelInterceptorTests implements MessageHandler { @Test public void spanCreation() { - this.tracedChannel.send(MessageBuilder.withPayload("hi").build()); + this.channel.send(MessageBuilder.withPayload("hi").build()); then(this.message).isNotNull(); String spanId = this.message.getHeaders().get(Span.SPAN_ID_NAME, String.class); @@ -195,25 +186,22 @@ public class TraceChannelInterceptorTests implements MessageHandler { @Test public void shouldLogClientReceivedClientSentEventWhenTheMessageIsSentAndReceived() { - this.tracedChannel.send(MessageBuilder.withPayload("hi").build()); + this.channel.send(MessageBuilder.withPayload("hi").build()); - then(this.span.logs()).extracting("event").contains(Span.CLIENT_SEND, - Span.CLIENT_RECV); + then(this.span.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()); + this.channel.send(MessageBuilder.withPayload("hi").setHeader("X-Message-Sent", true).build()); - then(this.span.logs()).extracting("event").contains(Span.SERVER_RECV, - Span.SERVER_SEND); + then(this.span.logs()).extracting("event").contains(Span.SERVER_RECV, Span.SERVER_SEND); } @Test public void headerCreation() { Span span = this.tracer.createSpan("http:testSendMessage", new AlwaysSampler()); - this.tracedChannel.send(MessageBuilder.withPayload("hi").build()); + this.channel.send(MessageBuilder.withPayload("hi").build()); this.tracer.close(span); then(this.message).isNotNull(); @@ -249,33 +237,15 @@ public class TraceChannelInterceptorTests implements MessageHandler { errorHeaders.put("THROW_EXCEPTION", "TRUE"); try { - this.messagingTemplate.send( - MessageBuilder.withPayload("hi").copyHeaders(errorHeaders).build()); + this.messagingTemplate.send(MessageBuilder.withPayload("hi").copyHeaders(errorHeaders).build()); SleuthAssertions.fail("Exception should occur"); - } - catch (RuntimeException e) { - } + } catch (RuntimeException e) {} then(this.message).isNotNull(); this.tracer.close(span); then(TestSpanContextHolder.getCurrentSpan()).isNull(); } - @Test - public void shouldNotTraceIgnoredChannel() { - this.ignoredChannel.send(MessageBuilder.withPayload("hi").build()); - then(this.message).isNotNull(); - - String spanId = this.message.getHeaders().get(Span.SPAN_ID_NAME, String.class); - then(spanId).isNull(); - - String traceId = this.message.getHeaders().get(Span.TRACE_ID_NAME, String.class); - then(traceId).isNull(); - - then(accumulator.getSpans()).isEmpty(); - then(TestSpanContextHolder.getCurrentSpan()).isNull(); - } - @Configuration @EnableAutoConfiguration static class App { @@ -286,18 +256,13 @@ public class TraceChannelInterceptorTests implements MessageHandler { } @Bean - public DirectChannel tracedChannel() { - return new DirectChannel(); - } - - @Bean - public DirectChannel ignoredChannel() { + public DirectChannel channel() { return new DirectChannel(); } @Bean public MessagingTemplate messagingTemplate() { - return new MessagingTemplate(tracedChannel()); + return new MessagingTemplate(channel()); } @Bean From a4328325c9a71ca5c8836a5012e12bdbd8c48461 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Fri, 15 Jul 2016 08:55:28 +0200 Subject: [PATCH 021/235] Revert "Updated docs" This reverts commit 7d0b8f62c279b7b73d998cb3d6255a0876194384. --- docs/src/main/asciidoc/spring-cloud-sleuth.adoc | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc index edfa905e9..351d35172 100644 --- a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc +++ b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc @@ -520,10 +520,6 @@ the same as the ones sent in HTTP (they contain a `-`) in its name. For the sake 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. -Since 1.0.4 you can provide the `spring.sleuth.integration.patterns` pattern to explicitly -provide the names of channels that you want to include for tracing. By default all channels -are included. - === Zuul We're registering Zuul filters to propagate the tracing information (the request header is enriched with tracing data). From 1ec2bc78332d0f5dab0e43d03111cbf4aaff3e70 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Fri, 15 Jul 2016 15:47:52 +0200 Subject: [PATCH 022/235] Changed arraylist to linkedlist --- .../cloud/sleuth/stream/StreamSpanReporter.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/StreamSpanReporter.java b/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/StreamSpanReporter.java index 1d41cd6f1..e28491a0f 100644 --- a/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/StreamSpanReporter.java +++ b/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/StreamSpanReporter.java @@ -16,8 +16,8 @@ package org.springframework.cloud.sleuth.stream; -import java.util.ArrayList; import java.util.Iterator; +import java.util.LinkedList; import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; @@ -53,7 +53,7 @@ public class StreamSpanReporter implements SpanReporter { @InboundChannelAdapter(value = SleuthSource.OUTPUT) public Spans poll() { - List result = new ArrayList<>(); + List result = new LinkedList<>(); this.queue.drainTo(result); for (Iterator iterator = result.iterator(); iterator.hasNext();) { Span span = iterator.next(); From b4bcd531d5d89cb58c2323757380f385f7c7c62f Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Fri, 15 Jul 2016 15:48:36 +0200 Subject: [PATCH 023/235] Added patterns to @GlobalChannelInterceptor (#337) --- .../main/asciidoc/spring-cloud-sleuth.adoc | 4 + ...aceSpringIntegrationAutoConfiguration.java | 2 +- ...itional-spring-configuration-metadata.json | 14 ++++ .../TraceChannelInterceptorTests.java | 78 +++++++++++++------ 4 files changed, 75 insertions(+), 23 deletions(-) create mode 100644 spring-cloud-sleuth-core/src/main/resources/META-INF/additional-spring-configuration-metadata.json diff --git a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc index 351d35172..edfa905e9 100644 --- a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc +++ b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc @@ -520,6 +520,10 @@ the same as the ones sent in HTTP (they contain a `-`) in its name. For the sake 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. +Since 1.0.4 you can provide the `spring.sleuth.integration.patterns` pattern to explicitly +provide the names of channels that you want to include for tracing. By default all channels +are included. + === Zuul We're registering Zuul filters to propagate the tracing information (the request header is enriched with tracing data). diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceSpringIntegrationAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceSpringIntegrationAutoConfiguration.java index b7d1271b5..b14f16605 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceSpringIntegrationAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceSpringIntegrationAutoConfiguration.java @@ -54,7 +54,7 @@ import org.springframework.messaging.support.MessageBuilder; public class TraceSpringIntegrationAutoConfiguration { @Bean - @GlobalChannelInterceptor + @GlobalChannelInterceptor(patterns = "#{environment.getProperty('spring.sleuth.integration.patterns') ?: '*'}") public TraceChannelInterceptor traceChannelInterceptor(Tracer tracer, TraceKeys traceKeys, Random random, SpanExtractor> spanExtractor, SpanInjector> spanInjector) { diff --git a/spring-cloud-sleuth-core/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-cloud-sleuth-core/src/main/resources/META-INF/additional-spring-configuration-metadata.json new file mode 100644 index 000000000..3dfc54046 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -0,0 +1,14 @@ +{"properties": [ + { + "name": "spring.sleuth.integration.patterns", + "type": "java.lang.String[]", + "description": "An array of simple patterns against which channel names will be matched. Default is * (all channels). See org.springframework.util.PatternMatchUtils.simpleMatch(String, String).", + "defaultValue": "*" + }, + { + "name": "spring.sleuth.integration.enabled", + "type": "java.lang.Boolean", + "description": "Enable Spring Integration sleuth instrumentation.", + "defaultValue": true + } +]} \ No newline at end of file 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 6702bfcbb..37e173ebc 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 @@ -47,6 +47,7 @@ import org.springframework.messaging.MessagingException; import org.springframework.test.annotation.DirtiesContext; 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; @@ -55,13 +56,17 @@ import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; */ @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = App.class) -@IntegrationTest +@IntegrationTest("spring.sleuth.integration.patterns=traced*") @DirtiesContext public class TraceChannelInterceptorTests implements MessageHandler { @Autowired - @Qualifier("channel") - private DirectChannel channel; + @Qualifier("tracedChannel") + private DirectChannel tracedChannel; + + @Autowired + @Qualifier("ignoredChannel") + private DirectChannel ignoredChannel; @Autowired private Tracer tracer; @@ -87,20 +92,22 @@ public class TraceChannelInterceptorTests implements MessageHandler { @Before public void init() { - this.channel.subscribe(this); + this.tracedChannel.subscribe(this); + this.ignoredChannel.subscribe(this); this.accumulator.getSpans().clear(); } @After public void close() { TestSpanContextHolder.removeCurrentSpan(); - this.channel.unsubscribe(this); + this.tracedChannel.unsubscribe(this); + this.ignoredChannel.unsubscribe(this); this.accumulator.getSpans().clear(); } @Test public void nonExportableSpanCreation() { - this.channel.send(MessageBuilder.withPayload("hi") + this.tracedChannel.send(MessageBuilder.withPayload("hi") .setHeader(Span.SAMPLED_NAME, Span.SPAN_NOT_SAMPLED).build()); assertNotNull("message was null", this.message); @@ -112,7 +119,7 @@ public class TraceChannelInterceptorTests implements MessageHandler { @Test public void parentSpanIncluded() { - this.channel.send(MessageBuilder.withPayload("hi") + 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(); @@ -129,7 +136,7 @@ public class TraceChannelInterceptorTests implements MessageHandler { // #332 @Test public void shouldSendNewAndOldHeadersWhenNewHeadersWerePassed() { - this.channel.send(MessageBuilder.withPayload("hi") + 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(); @@ -141,7 +148,8 @@ public class TraceChannelInterceptorTests implements MessageHandler { } private String thenNewSpanIdEqualsOldSpanId() { - String newSpanId = this.message.getHeaders().get(TraceMessageHeaders.SPAN_ID_NAME, String.class); + 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); @@ -151,7 +159,7 @@ public class TraceChannelInterceptorTests implements MessageHandler { // #332 @Test public void shouldSendNewAndOldHeadersWhenOldHeadersWerePassed() { - this.channel.send(MessageBuilder.withPayload("hi") + 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(); @@ -163,8 +171,8 @@ public class TraceChannelInterceptorTests implements MessageHandler { } private void thenNewTraceIdEqualsOldTraceId() { - long traceId = Span - .hexToId(this.message.getHeaders().get(TraceMessageHeaders.TRACE_ID_NAME, String.class)); + 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)); @@ -173,7 +181,7 @@ public class TraceChannelInterceptorTests implements MessageHandler { @Test public void spanCreation() { - this.channel.send(MessageBuilder.withPayload("hi").build()); + this.tracedChannel.send(MessageBuilder.withPayload("hi").build()); then(this.message).isNotNull(); String spanId = this.message.getHeaders().get(Span.SPAN_ID_NAME, String.class); @@ -186,22 +194,25 @@ public class TraceChannelInterceptorTests implements MessageHandler { @Test public void shouldLogClientReceivedClientSentEventWhenTheMessageIsSentAndReceived() { - this.channel.send(MessageBuilder.withPayload("hi").build()); + this.tracedChannel.send(MessageBuilder.withPayload("hi").build()); - then(this.span.logs()).extracting("event").contains(Span.CLIENT_SEND, Span.CLIENT_RECV); + then(this.span.logs()).extracting("event").contains(Span.CLIENT_SEND, + Span.CLIENT_RECV); } @Test public void shouldLogServerReceivedServerSentEventWhenTheMessageIsPropagatedToTheNextListener() { - this.channel.send(MessageBuilder.withPayload("hi").setHeader("X-Message-Sent", true).build()); + this.tracedChannel.send(MessageBuilder.withPayload("hi") + .setHeader("X-Message-Sent", true).build()); - then(this.span.logs()).extracting("event").contains(Span.SERVER_RECV, Span.SERVER_SEND); + then(this.span.logs()).extracting("event").contains(Span.SERVER_RECV, + Span.SERVER_SEND); } @Test public void headerCreation() { Span span = this.tracer.createSpan("http:testSendMessage", new AlwaysSampler()); - this.channel.send(MessageBuilder.withPayload("hi").build()); + this.tracedChannel.send(MessageBuilder.withPayload("hi").build()); this.tracer.close(span); then(this.message).isNotNull(); @@ -237,15 +248,33 @@ public class TraceChannelInterceptorTests implements MessageHandler { errorHeaders.put("THROW_EXCEPTION", "TRUE"); try { - this.messagingTemplate.send(MessageBuilder.withPayload("hi").copyHeaders(errorHeaders).build()); + this.messagingTemplate.send( + MessageBuilder.withPayload("hi").copyHeaders(errorHeaders).build()); SleuthAssertions.fail("Exception should occur"); - } catch (RuntimeException e) {} + } + catch (RuntimeException e) { + } then(this.message).isNotNull(); this.tracer.close(span); then(TestSpanContextHolder.getCurrentSpan()).isNull(); } + @Test + public void shouldNotTraceIgnoredChannel() { + this.ignoredChannel.send(MessageBuilder.withPayload("hi").build()); + then(this.message).isNotNull(); + + String spanId = this.message.getHeaders().get(Span.SPAN_ID_NAME, String.class); + then(spanId).isNull(); + + String traceId = this.message.getHeaders().get(Span.TRACE_ID_NAME, String.class); + then(traceId).isNull(); + + then(accumulator.getSpans()).isEmpty(); + then(TestSpanContextHolder.getCurrentSpan()).isNull(); + } + @Configuration @EnableAutoConfiguration static class App { @@ -256,13 +285,18 @@ public class TraceChannelInterceptorTests implements MessageHandler { } @Bean - public DirectChannel channel() { + public DirectChannel tracedChannel() { + return new DirectChannel(); + } + + @Bean + public DirectChannel ignoredChannel() { return new DirectChannel(); } @Bean public MessagingTemplate messagingTemplate() { - return new MessagingTemplate(channel()); + return new MessagingTemplate(tracedChannel()); } @Bean From c22e72e4678a6d5fce0b92bff84e973a56216237 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Fri, 15 Jul 2016 16:26:21 +0200 Subject: [PATCH 024/235] Added tests and fix docs for AsyncRestTemplate fixes #334 --- .../main/asciidoc/spring-cloud-sleuth.adoc | 7 +- ...eAsyncClientHttpRequestFactoryWrapper.java | 4 +- ...ceWebAsyncClientAutoConfigurationTest.java | 105 ++++++++++++++++++ .../instrument/web/client/WebClientTests.java | 22 ++-- 4 files changed, 128 insertions(+), 10 deletions(-) create mode 100644 spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfigurationTest.java diff --git a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc index edfa905e9..86af5dae7 100644 --- a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc +++ b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc @@ -456,7 +456,12 @@ If you create a `RestTemplate` instance with a `new` keyword then the instrument Custom instrumentation is set to create and close Spans upon sending and receiving requests. You can customize the `ClientHttpRequestFactory` and the `AsyncClientHttpRequestFactory` by registering your beans. Remember to use tracing compatible implementations (e.g. don't forget to -wrap `ThreadPoolTaskScheduler` in a `TraceAsyncListenableTaskExecutor`). +wrap `ThreadPoolTaskScheduler` in a `TraceAsyncListenableTaskExecutor`). Example of custom request factories: + +[source,java] +---- +include::../../../../spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfigurationTest.java[tags=async_template_factories,indent=0] +---- To block the `AsyncRestTemplate` features set `spring.sleuth.web.async.client.enabled` to `false`. To disable creation of the default `TraceAsyncClientHttpRequestFactoryWrapper` set `spring.sleuth.web.async.client.factory.enabled` diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncClientHttpRequestFactoryWrapper.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncClientHttpRequestFactoryWrapper.java index 96be114cc..49e26f6c4 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncClientHttpRequestFactoryWrapper.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncClientHttpRequestFactoryWrapper.java @@ -43,8 +43,8 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; public class TraceAsyncClientHttpRequestFactoryWrapper extends AbstractTraceHttpRequestInterceptor implements ClientHttpRequestFactory, AsyncClientHttpRequestFactory { - private final AsyncClientHttpRequestFactory asyncDelegate; - private final ClientHttpRequestFactory syncDelegate; + final AsyncClientHttpRequestFactory asyncDelegate; + final ClientHttpRequestFactory syncDelegate; /** * According to the JavaDocs all Spring {@link AsyncClientHttpRequestFactory} implement diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfigurationTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfigurationTest.java new file mode 100644 index 000000000..6f3c01626 --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfigurationTest.java @@ -0,0 +1,105 @@ +/* + * 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.io.IOException; +import java.net.URI; + +import org.junit.After; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.boot.test.WebIntegrationTest; +import org.springframework.cloud.sleuth.trace.TestSpanContextHolder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; +import org.springframework.http.client.AsyncClientHttpRequest; +import org.springframework.http.client.AsyncClientHttpRequestFactory; +import org.springframework.http.client.ClientHttpRequest; +import org.springframework.http.client.ClientHttpRequestFactory; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.web.client.AsyncRestTemplate; + +import com.netflix.hystrix.strategy.HystrixPlugins; + +import static org.assertj.core.api.BDDAssertions.then; + +/** + * @author Marcin Grzejszczak + */ +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(classes = { + TraceWebAsyncClientAutoConfigurationTest.TestConfiguration.class }) +@WebIntegrationTest(randomPort = true) +@DirtiesContext +public class TraceWebAsyncClientAutoConfigurationTest { + + @Autowired AsyncRestTemplate asyncRestTemplate; + @Autowired MySyncClientHttpRequestFactory mySyncClientHttpRequestFactory; + @Autowired MyAsyncClientHttpRequestFactory myAsyncClientHttpRequestFactory; + + @After + public void close() { + TestSpanContextHolder.removeCurrentSpan(); + HystrixPlugins.reset(); + } + + @Test + public void should_inject_to_async_rest_template_custom_client_http_request_factory() { + then(this.asyncRestTemplate.getAsyncRequestFactory()).isInstanceOf(TraceAsyncClientHttpRequestFactoryWrapper.class); + TraceAsyncClientHttpRequestFactoryWrapper wrapper = (TraceAsyncClientHttpRequestFactoryWrapper) this.asyncRestTemplate.getAsyncRequestFactory(); + then(wrapper.syncDelegate).isSameAs(this.mySyncClientHttpRequestFactory); + then(wrapper.asyncDelegate).isSameAs(this.myAsyncClientHttpRequestFactory); + then(this.asyncRestTemplate).isInstanceOf(TraceAsyncRestTemplate.class); + } + + // tag::async_template_factories[] + @EnableAutoConfiguration + @Configuration + public static class TestConfiguration { + + @Bean + ClientHttpRequestFactory mySyncClientFactory() { + return new MySyncClientHttpRequestFactory(); + } + + @Bean + AsyncClientHttpRequestFactory myAsyncClientFactory() { + return new MyAsyncClientHttpRequestFactory(); + } + } + // end::async_template_factories[] + + private static class MySyncClientHttpRequestFactory implements ClientHttpRequestFactory { + @Override public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) + throws IOException { + return null; + } + } + private static class MyAsyncClientHttpRequestFactory implements AsyncClientHttpRequestFactory { + @Override + public AsyncClientHttpRequest createAsyncRequest(URI uri, HttpMethod httpMethod) + throws IOException { + return null; + } + } + +} \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/WebClientTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/WebClientTests.java index 3dbae387a..b98735da3 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/WebClientTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/WebClientTests.java @@ -25,8 +25,10 @@ import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Random; +import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; +import com.jayway.awaitility.Awaitility; import com.netflix.loadbalancer.BaseLoadBalancer; import com.netflix.loadbalancer.ILoadBalancer; import com.netflix.loadbalancer.Server; @@ -113,13 +115,19 @@ public class WebClientTests { then(getHeader(response, Span.TRACE_ID_NAME)).isNotNull(); then(getHeader(response, Span.SPAN_ID_NAME)).isNotNull(); then(this.listener.getSpans()).isNotEmpty(); - Optional noTraceSpan = this.listener.getSpans().stream().filter(span -> - "http:/notrace".equals(span.getName()) && !span.tags().isEmpty()).findFirst(); - then(noTraceSpan.isPresent()).isTrue(); - // TODO: matches cause there is an issue with Feign not providing the full URL at the interceptor level - then(noTraceSpan.get()).matchesATag("http.url", ".*/notrace") - .hasATag("http.path", "/notrace") - .hasATag("http.method", "GET"); + Awaitility.await().atMost(3, TimeUnit.SECONDS).until(() -> { + log.info("Seraching for a notrace span in " + this.listener.getSpans()); + Optional noTraceSpan = this.listener.getSpans().stream().filter(span -> + "http:/notrace".equals(span.getName()) && !span.tags().isEmpty()).findFirst(); + then(noTraceSpan.isPresent()).isTrue(); + log.info("No trace span found"); + // TODO: matches cause there is an issue with Feign not providing the full URL at the interceptor level + then(noTraceSpan.get()).matchesATag("http.url", ".*/notrace") + .hasATag("http.path", "/notrace") + .hasATag("http.method", "GET"); + log.info("Notrace has all the necessary tags"); + }); + } Object[] parametersForShouldCreateANewSpanWithClientSideTagsWhenNoPreviousTracingWasPresent() { From 36ed8326842743346a6837e513638bd63a993ff7 Mon Sep 17 00:00:00 2001 From: Premanand C Date: Sat, 16 Jul 2016 01:13:51 -0400 Subject: [PATCH 025/235] Fix for inability to provide custom implementation of AsyncClientHttpRequestFactory --- .../TraceWebAsyncClientAutoConfiguration.java | 20 +-- ...ceWebAsyncClientAutoConfigurationTest.java | 105 ------------ ...eWebAsyncClientAutoConfigurationTests.java | 150 ++++++++++++++++++ .../instrument/web/client/WebClientTests.java | 2 +- 4 files changed, 161 insertions(+), 116 deletions(-) delete mode 100644 spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfigurationTest.java create mode 100644 spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfigurationTests.java diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfiguration.java index 9e7ae1289..67c32afc2 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfiguration.java @@ -5,7 +5,7 @@ * 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 + * 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, @@ -29,7 +29,6 @@ import org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration; import org.springframework.cloud.sleuth.instrument.web.HttpTraceKeysInjector; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Primary; import org.springframework.core.task.AsyncListenableTaskExecutor; import org.springframework.http.HttpRequest; import org.springframework.http.client.AsyncClientHttpRequestFactory; @@ -54,15 +53,16 @@ import org.springframework.web.client.AsyncRestTemplate; public class TraceWebAsyncClientAutoConfiguration { @Autowired Tracer tracer; - @Autowired HttpTraceKeysInjector httpTraceKeysInjector; - @Autowired SpanInjector spanInjector; - @Autowired(required = false) ClientHttpRequestFactory clientHttpRequestFactory; - @Autowired(required = false) AsyncClientHttpRequestFactory asyncClientHttpRequestFactory; + @Autowired + private HttpTraceKeysInjector httpTraceKeysInjector; + @Autowired + private SpanInjector spanInjector; + @Autowired(required = false) + private ClientHttpRequestFactory clientHttpRequestFactory; + @Autowired(required = false) + private AsyncClientHttpRequestFactory asyncClientHttpRequestFactory; - @Bean - @Primary - @ConditionalOnProperty(value = "spring.sleuth.web.async.client.factory.enabled", matchIfMissing = true) - public TraceAsyncClientHttpRequestFactoryWrapper traceAsyncClientHttpRequestFactory() { + private TraceAsyncClientHttpRequestFactoryWrapper traceAsyncClientHttpRequestFactory() { ClientHttpRequestFactory clientFactory = this.clientHttpRequestFactory; AsyncClientHttpRequestFactory asyncClientFactory = this.asyncClientHttpRequestFactory; if (clientFactory == null) { diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfigurationTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfigurationTest.java deleted file mode 100644 index 6f3c01626..000000000 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfigurationTest.java +++ /dev/null @@ -1,105 +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 java.io.IOException; -import java.net.URI; - -import org.junit.After; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.SpringApplicationConfiguration; -import org.springframework.boot.test.WebIntegrationTest; -import org.springframework.cloud.sleuth.trace.TestSpanContextHolder; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.http.HttpMethod; -import org.springframework.http.client.AsyncClientHttpRequest; -import org.springframework.http.client.AsyncClientHttpRequestFactory; -import org.springframework.http.client.ClientHttpRequest; -import org.springframework.http.client.ClientHttpRequestFactory; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.web.client.AsyncRestTemplate; - -import com.netflix.hystrix.strategy.HystrixPlugins; - -import static org.assertj.core.api.BDDAssertions.then; - -/** - * @author Marcin Grzejszczak - */ -@RunWith(SpringJUnit4ClassRunner.class) -@SpringApplicationConfiguration(classes = { - TraceWebAsyncClientAutoConfigurationTest.TestConfiguration.class }) -@WebIntegrationTest(randomPort = true) -@DirtiesContext -public class TraceWebAsyncClientAutoConfigurationTest { - - @Autowired AsyncRestTemplate asyncRestTemplate; - @Autowired MySyncClientHttpRequestFactory mySyncClientHttpRequestFactory; - @Autowired MyAsyncClientHttpRequestFactory myAsyncClientHttpRequestFactory; - - @After - public void close() { - TestSpanContextHolder.removeCurrentSpan(); - HystrixPlugins.reset(); - } - - @Test - public void should_inject_to_async_rest_template_custom_client_http_request_factory() { - then(this.asyncRestTemplate.getAsyncRequestFactory()).isInstanceOf(TraceAsyncClientHttpRequestFactoryWrapper.class); - TraceAsyncClientHttpRequestFactoryWrapper wrapper = (TraceAsyncClientHttpRequestFactoryWrapper) this.asyncRestTemplate.getAsyncRequestFactory(); - then(wrapper.syncDelegate).isSameAs(this.mySyncClientHttpRequestFactory); - then(wrapper.asyncDelegate).isSameAs(this.myAsyncClientHttpRequestFactory); - then(this.asyncRestTemplate).isInstanceOf(TraceAsyncRestTemplate.class); - } - - // tag::async_template_factories[] - @EnableAutoConfiguration - @Configuration - public static class TestConfiguration { - - @Bean - ClientHttpRequestFactory mySyncClientFactory() { - return new MySyncClientHttpRequestFactory(); - } - - @Bean - AsyncClientHttpRequestFactory myAsyncClientFactory() { - return new MyAsyncClientHttpRequestFactory(); - } - } - // end::async_template_factories[] - - private static class MySyncClientHttpRequestFactory implements ClientHttpRequestFactory { - @Override public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) - throws IOException { - return null; - } - } - private static class MyAsyncClientHttpRequestFactory implements AsyncClientHttpRequestFactory { - @Override - public AsyncClientHttpRequest createAsyncRequest(URI uri, HttpMethod httpMethod) - throws IOException { - return null; - } - } - -} \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfigurationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfigurationTests.java new file mode 100644 index 000000000..afbc9bee0 --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfigurationTests.java @@ -0,0 +1,150 @@ +/* + * 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.io.IOException; +import java.net.URI; + +import org.junit.Test; +import org.junit.experimental.runners.Enclosed; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.boot.test.WebIntegrationTest; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; +import org.springframework.http.client.AsyncClientHttpRequest; +import org.springframework.http.client.AsyncClientHttpRequestFactory; +import org.springframework.http.client.ClientHttpRequest; +import org.springframework.http.client.ClientHttpRequestFactory; +import org.springframework.http.client.SimpleClientHttpRequestFactory; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.web.client.AsyncRestTemplate; + +import static org.assertj.core.api.BDDAssertions.then; + +/** + * @author Marcin Grzejszczak + */ +@RunWith(Enclosed.class) +public class TraceWebAsyncClientAutoConfigurationTests { + + @RunWith(SpringJUnit4ClassRunner.class) + @SpringApplicationConfiguration(classes = { CustomSyncAndAsyncClientFactory.TestConfiguration.class }) + @WebIntegrationTest(randomPort = true) + public static class CustomSyncAndAsyncClientFactory { + @Autowired AsyncRestTemplate asyncRestTemplate; + + @Test + public void should_inject_to_async_rest_template_custom_client_http_request_factory() { + then(this.asyncRestTemplate.getAsyncRequestFactory()).isInstanceOf(TraceAsyncClientHttpRequestFactoryWrapper.class); + TraceAsyncClientHttpRequestFactoryWrapper wrapper = (TraceAsyncClientHttpRequestFactoryWrapper) this.asyncRestTemplate.getAsyncRequestFactory(); + then(wrapper.syncDelegate).isInstanceOf(MySyncClientHttpRequestFactory.class); + then(wrapper.asyncDelegate).isInstanceOf(MyAsyncClientHttpRequestFactory.class); + then(this.asyncRestTemplate).isInstanceOf(TraceAsyncRestTemplate.class); + } + + // tag::async_template_factories[] + @EnableAutoConfiguration + @Configuration + public static class TestConfiguration { + + @Bean + ClientHttpRequestFactory mySyncClientFactory() { + return new MySyncClientHttpRequestFactory(); + } + + @Bean + AsyncClientHttpRequestFactory myAsyncClientFactory() { + return new MyAsyncClientHttpRequestFactory(); + } + } + // end::async_template_factories[] + + } + + @RunWith(SpringJUnit4ClassRunner.class) + @SpringApplicationConfiguration(classes = { CustomSyncClientFactory.TestConfiguration.class }) + @WebIntegrationTest(randomPort = true) + public static class CustomSyncClientFactory { + @Autowired AsyncRestTemplate asyncRestTemplate; + + @Test + public void should_inject_to_async_rest_template_custom_client_http_request_factory() { + then(this.asyncRestTemplate.getAsyncRequestFactory()).isInstanceOf(TraceAsyncClientHttpRequestFactoryWrapper.class); + TraceAsyncClientHttpRequestFactoryWrapper wrapper = (TraceAsyncClientHttpRequestFactoryWrapper) this.asyncRestTemplate.getAsyncRequestFactory(); + then(wrapper.syncDelegate).isInstanceOf(MySyncClientHttpRequestFactory.class); + then(wrapper.asyncDelegate).isInstanceOf(SimpleClientHttpRequestFactory.class); + then(this.asyncRestTemplate).isInstanceOf(TraceAsyncRestTemplate.class); + } + + @EnableAutoConfiguration + @Configuration + public static class TestConfiguration { + + @Bean + ClientHttpRequestFactory mySyncClientFactory() { + return new MySyncClientHttpRequestFactory(); + } + } + + } + + @RunWith(SpringJUnit4ClassRunner.class) + @SpringApplicationConfiguration(classes = { CustomASyncClientFactory.TestConfiguration.class }) + @WebIntegrationTest(randomPort = true) + public static class CustomASyncClientFactory { + @Autowired AsyncRestTemplate asyncRestTemplate; + + @Test + public void should_inject_to_async_rest_template_custom_client_http_request_factory() { + then(this.asyncRestTemplate.getAsyncRequestFactory()).isInstanceOf(TraceAsyncClientHttpRequestFactoryWrapper.class); + TraceAsyncClientHttpRequestFactoryWrapper wrapper = (TraceAsyncClientHttpRequestFactoryWrapper) this.asyncRestTemplate.getAsyncRequestFactory(); + then(wrapper.syncDelegate).isInstanceOf(SimpleClientHttpRequestFactory.class); + then(wrapper.asyncDelegate).isInstanceOf(AsyncClientHttpRequestFactory.class); + then(this.asyncRestTemplate).isInstanceOf(TraceAsyncRestTemplate.class); + } + + @EnableAutoConfiguration + @Configuration + public static class TestConfiguration { + + @Bean + AsyncClientHttpRequestFactory myAsyncClientFactory() { + return new MyAsyncClientHttpRequestFactory(); + } + } + + } + + private static class MySyncClientHttpRequestFactory implements ClientHttpRequestFactory { + @Override public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) + throws IOException { + return null; + } + } + private static class MyAsyncClientHttpRequestFactory implements AsyncClientHttpRequestFactory { + @Override + public AsyncClientHttpRequest createAsyncRequest(URI uri, HttpMethod httpMethod) + throws IOException { + return null; + } + } + +} \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/WebClientTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/WebClientTests.java index b98735da3..d33726a99 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/WebClientTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/WebClientTests.java @@ -83,7 +83,7 @@ import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; @RunWith(JUnitParamsRunner.class) @SpringApplicationConfiguration(classes = { WebClientTests.TestConfiguration.class }) -@WebIntegrationTest(value = { "spring.application.name=fooservice" }, randomPort = true) +@WebIntegrationTest(value = { "spring.application.name=fooservice", "feign.hystrix.enabled=false" }, randomPort = true) @DirtiesContext public class WebClientTests { From 1deea897ad1fbe97a7de8ba1eadaa960e874b03d Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Fri, 22 Jul 2016 18:37:48 +0200 Subject: [PATCH 026/235] Fixed missing default Feign.Builder with this fix if someone is not using Feign he gets the default traced Feign.Builder. Thanks to this no more exceptions should be seen in terms of closing the wrong span. fixes #350 --- .../web/client/feign/SleuthFeignBuilder.java | 6 +- .../feign/SleuthHystrixFeignBuilder.java | 44 ++++++ .../TraceFeignClientAutoConfiguration.java | 9 ++ .../client/feign/issues/Issue307Tests.java | 6 +- .../client/feign/issues/Issue350Tests.java | 141 ++++++++++++++++++ 5 files changed, 200 insertions(+), 6 deletions(-) create mode 100644 spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/SleuthHystrixFeignBuilder.java create mode 100644 spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/Issue350Tests.java diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/SleuthFeignBuilder.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/SleuthFeignBuilder.java index 27c608602..cf4513062 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/SleuthFeignBuilder.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/SleuthFeignBuilder.java @@ -19,11 +19,9 @@ package org.springframework.cloud.sleuth.instrument.web.client.feign; import org.springframework.beans.factory.BeanFactory; import feign.Feign; -import feign.hystrix.HystrixFeign; /** - * Contains {@link feign.Feign.Builder} implementation that delegates execution - * {@link feign.hystrix.HystrixFeign} with tracing components + * Contains {@link feign.Feign.Builder} implementation with tracing components * that close spans on exceptions / success and continues them on retries. * * @author Marcin Grzejszczak @@ -35,7 +33,7 @@ final class SleuthFeignBuilder { private SleuthFeignBuilder() {} static Feign.Builder builder(BeanFactory beanFactory) { - return HystrixFeign.builder() + return Feign.builder() .client(new TraceFeignClient(beanFactory)) .retryer(new TraceFeignRetryer(beanFactory)) .decoder(new TraceFeignDecoder(beanFactory)) diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/SleuthHystrixFeignBuilder.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/SleuthHystrixFeignBuilder.java new file mode 100644 index 000000000..e7f9c59f1 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/SleuthHystrixFeignBuilder.java @@ -0,0 +1,44 @@ +/* + * Copyright 2013-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.sleuth.instrument.web.client.feign; + +import org.springframework.beans.factory.BeanFactory; + +import feign.Feign; +import feign.hystrix.HystrixFeign; + +/** + * Contains {@link Feign.Builder} implementation that delegates execution + * {@link HystrixFeign} with tracing components + * that close spans on exceptions / success and continues them on retries. + * + * @author Marcin Grzejszczak + * + * @since 1.0.4 + */ +final class SleuthHystrixFeignBuilder { + + private SleuthHystrixFeignBuilder() {} + + static Feign.Builder builder(BeanFactory beanFactory) { + return HystrixFeign.builder() + .client(new TraceFeignClient(beanFactory)) + .retryer(new TraceFeignRetryer(beanFactory)) + .decoder(new TraceFeignDecoder(beanFactory)) + .errorDecoder(new TraceFeignErrorDecoder(beanFactory)); + } +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientAutoConfiguration.java index 504af1167..8b7afb920 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientAutoConfiguration.java @@ -28,6 +28,7 @@ import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureBefore; 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.boot.autoconfigure.web.HttpMessageConverters; import org.springframework.cloud.netflix.feign.FeignAutoConfiguration; @@ -72,6 +73,14 @@ public class TraceFeignClientAutoConfiguration { @ConditionalOnClass(HystrixCommand.class) @ConditionalOnProperty(name = "feign.hystrix.enabled", matchIfMissing = true) Feign.Builder feignHystrixBuilder(BeanFactory beanFactory) { + return SleuthHystrixFeignBuilder.builder(beanFactory); + } + + @Bean + @ConditionalOnMissingBean + @Scope("prototype") + @ConditionalOnProperty(name = "feign.hystrix.enabled", havingValue = "false", matchIfMissing = false) + Feign.Builder feignBuilder(BeanFactory beanFactory) { return SleuthFeignBuilder.builder(beanFactory); } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/Issue307Tests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/Issue307Tests.java index 42d58f80e..c0d14ae8c 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/Issue307Tests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/Issue307Tests.java @@ -25,7 +25,7 @@ import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker; import org.springframework.cloud.netflix.feign.EnableFeignClients; import org.springframework.cloud.netflix.feign.FeignClient; @@ -34,6 +34,7 @@ import org.springframework.cloud.sleuth.trace.TestSpanContextHolder; import org.springframework.cloud.sleuth.util.ExceptionUtils; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; import org.springframework.core.env.Environment; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.PathVariable; @@ -62,7 +63,8 @@ public class Issue307Tests { } } -@SpringBootApplication +@EnableAutoConfiguration +@Import({ParticipantsBean.class, ParticipantsClient.class}) @RestController @EnableFeignClients @EnableCircuitBreaker diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/Issue350Tests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/Issue350Tests.java new file mode 100644 index 000000000..fd26a1b00 --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/Issue350Tests.java @@ -0,0 +1,141 @@ +/* + * 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.issues; + +import java.util.concurrent.ExecutionException; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.boot.test.TestRestTemplate; +import org.springframework.boot.test.WebIntegrationTest; +import org.springframework.cloud.netflix.feign.EnableFeignClients; +import org.springframework.cloud.netflix.feign.FeignClient; +import org.springframework.cloud.sleuth.sampler.AlwaysSampler; +import org.springframework.cloud.sleuth.util.ExceptionUtils; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpStatus; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.client.RestTemplate; + +import feign.Logger; + +import static org.assertj.core.api.BDDAssertions.then; + +/** + * @author Marcin Grzejszczak + */ +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(classes = Application.class) +@WebIntegrationTest +@TestPropertySource(properties = {"ribbon.eureka.enabled=false", "feign.hystrix.enabled=false", "server.port=9988"}) +public class Issue350Tests { + + RestTemplate template = new TestRestTemplate(); + + @Before + public void setup() { + ExceptionUtils.setFail(true); + } + + @Test + public void should_successfully_work_without_hystrix() { + this.template.getForEntity("http://localhost:9988/sleuth/test-not-ok", String.class); + then(ExceptionUtils.getLastException()).isNull(); + } +} + +@Configuration +@EnableAutoConfiguration +@EnableFeignClients(basePackageClasses = {SleuthTestController.class}) +class Application { + + @Bean + public ServiceTestController serviceTestController() { + return new ServiceTestController(); + } + + @Bean + public SleuthTestController sleuthTestController() { + return new SleuthTestController(); + } + + @Bean + public Logger.Level feignLoggerLevel() { + return feign.Logger.Level.FULL; + } + + @Bean + public AlwaysSampler defaultSampler() { + return new AlwaysSampler(); + } +} + +@RestController +@RequestMapping(path = "/service") +class ServiceTestController { + + @RequestMapping("/ok") + public String ok() throws InterruptedException, ExecutionException { + String result = "I'm OK"; + return result; + } + + @RequestMapping("/not-ok") + @ResponseStatus(HttpStatus.NOT_ACCEPTABLE) + public String notOk() throws InterruptedException, ExecutionException { + return "Not OK"; + } +} + +@FeignClient(name="myFeignClient", url="localhost:9988") +interface MyFeignClient { + + @RequestMapping("/service/ok") + String ok(); + + @RequestMapping("/service/not-ok") + String exp(); +} + + +@RestController +@RequestMapping(path = "/sleuth") +class SleuthTestController { + + @Autowired + private MyFeignClient myFeignClient; + + @RequestMapping("/test-ok") + public String ok() throws InterruptedException, ExecutionException { + return myFeignClient.ok(); + } + + @RequestMapping("/test-not-ok") + public String notOk() throws InterruptedException, ExecutionException { + return myFeignClient.exp(); + } +} + From b73786f95a0fc0a514b3aa1342623ef921f0ef76 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Mon, 25 Jul 2016 10:39:44 +0200 Subject: [PATCH 027/235] Bumping versions before release --- benchmarks/pom.xml | 2 +- docs/pom.xml | 2 +- pom.xml | 6 +++--- spring-cloud-sleuth-core/pom.xml | 2 +- spring-cloud-sleuth-dependencies/pom.xml | 6 +++--- spring-cloud-sleuth-samples/pom.xml | 2 +- .../spring-cloud-sleuth-sample-feign/pom.xml | 2 +- .../spring-cloud-sleuth-sample-messaging/pom.xml | 2 +- .../spring-cloud-sleuth-sample-ribbon/pom.xml | 2 +- .../spring-cloud-sleuth-sample-stream/pom.xml | 2 +- .../spring-cloud-sleuth-sample-test-core/pom.xml | 2 +- .../spring-cloud-sleuth-sample-websocket/pom.xml | 2 +- .../spring-cloud-sleuth-sample-zipkin-stream/pom.xml | 2 +- .../spring-cloud-sleuth-sample-zipkin/pom.xml | 2 +- .../spring-cloud-sleuth-sample/pom.xml | 2 +- spring-cloud-sleuth-stream/pom.xml | 2 +- spring-cloud-sleuth-zipkin-stream/pom.xml | 2 +- spring-cloud-sleuth-zipkin/pom.xml | 2 +- spring-cloud-starter-sleuth/pom.xml | 2 +- spring-cloud-starter-zipkin/pom.xml | 2 +- 20 files changed, 24 insertions(+), 24 deletions(-) diff --git a/benchmarks/pom.xml b/benchmarks/pom.xml index d1db34781..67e796a6d 100644 --- a/benchmarks/pom.xml +++ b/benchmarks/pom.xml @@ -17,7 +17,7 @@ Benchmarks Benchmarks (JMH) org.springframework.cloud - 1.0.4.BUILD-SNAPSHOT + 1.0.4.RELEASE benchmarks diff --git a/docs/pom.xml b/docs/pom.xml index 2e58efa82..671687830 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.4.BUILD-SNAPSHOT + 1.0.4.RELEASE spring-cloud-sleuth-docs pom diff --git a/pom.xml b/pom.xml index f39ca2411..1358ec5bc 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 spring-cloud-sleuth - 1.0.4.BUILD-SNAPSHOT + 1.0.4.RELEASE pom Spring Cloud Sleuth Spring Cloud Sleuth @@ -13,7 +13,7 @@ org.springframework.cloud spring-cloud-build - 1.1.2.BUILD-SNAPSHOT + 1.1.1.RELEASE @@ -208,7 +208,7 @@ 1.8 2.19.1 2.17 - 1.1.2.BUILD-SNAPSHOT + 1.1.1.RELEASE diff --git a/spring-cloud-sleuth-core/pom.xml b/spring-cloud-sleuth-core/pom.xml index eb88bdfb2..dbf7baa87 100644 --- a/spring-cloud-sleuth-core/pom.xml +++ b/spring-cloud-sleuth-core/pom.xml @@ -11,7 +11,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.4.BUILD-SNAPSHOT + 1.0.4.RELEASE .. diff --git a/spring-cloud-sleuth-dependencies/pom.xml b/spring-cloud-sleuth-dependencies/pom.xml index 448e923cc..6321f5f11 100644 --- a/spring-cloud-sleuth-dependencies/pom.xml +++ b/spring-cloud-sleuth-dependencies/pom.xml @@ -5,16 +5,16 @@ spring-cloud-dependencies-parent org.springframework.cloud - 1.1.2.BUILD-SNAPSHOT + 1.1.1.RELEASE spring-cloud-sleuth-dependencies - 1.0.4.BUILD-SNAPSHOT + 1.0.4.RELEASE pom spring-cloud-sleuth-dependencies Spring Cloud Sleuth Dependencies - 1.1.4.BUILD-SNAPSHOT + 1.1.5.RELEASE 1.8.4 1.1.5 diff --git a/spring-cloud-sleuth-samples/pom.xml b/spring-cloud-sleuth-samples/pom.xml index b8fe37876..57d97c463 100644 --- a/spring-cloud-sleuth-samples/pom.xml +++ b/spring-cloud-sleuth-samples/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.4.BUILD-SNAPSHOT + 1.0.4.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml index ad7362362..a83d69154 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml @@ -20,7 +20,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.4.BUILD-SNAPSHOT + 1.0.4.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml index 48ed16f29..9d0fa250d 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.4.BUILD-SNAPSHOT + 1.0.4.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml index 4b25877d7..20c91b83b 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.4.BUILD-SNAPSHOT + 1.0.4.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml index f651f500c..49440a3e2 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.4.BUILD-SNAPSHOT + 1.0.4.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml index de89d7bb1..18cb76765 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.4.BUILD-SNAPSHOT + 1.0.4.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml index 5c26d0fb3..a3b841565 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.4.BUILD-SNAPSHOT + 1.0.4.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml index 6f3a647f7..c2f867c2a 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.4.BUILD-SNAPSHOT + 1.0.4.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml index 5f8f0ff7f..6abfbde3e 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml @@ -27,7 +27,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.4.BUILD-SNAPSHOT + 1.0.4.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml index e7348b3de..ea24171fc 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.4.BUILD-SNAPSHOT + 1.0.4.RELEASE .. diff --git a/spring-cloud-sleuth-stream/pom.xml b/spring-cloud-sleuth-stream/pom.xml index 15b44239a..723231d2d 100644 --- a/spring-cloud-sleuth-stream/pom.xml +++ b/spring-cloud-sleuth-stream/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.4.BUILD-SNAPSHOT + 1.0.4.RELEASE diff --git a/spring-cloud-sleuth-zipkin-stream/pom.xml b/spring-cloud-sleuth-zipkin-stream/pom.xml index 32842df5e..558941116 100644 --- a/spring-cloud-sleuth-zipkin-stream/pom.xml +++ b/spring-cloud-sleuth-zipkin-stream/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.4.BUILD-SNAPSHOT + 1.0.4.RELEASE diff --git a/spring-cloud-sleuth-zipkin/pom.xml b/spring-cloud-sleuth-zipkin/pom.xml index 9e425eaa1..c6a80f27e 100644 --- a/spring-cloud-sleuth-zipkin/pom.xml +++ b/spring-cloud-sleuth-zipkin/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.4.BUILD-SNAPSHOT + 1.0.4.RELEASE .. diff --git a/spring-cloud-starter-sleuth/pom.xml b/spring-cloud-starter-sleuth/pom.xml index 57a364f61..71741e1ba 100644 --- a/spring-cloud-starter-sleuth/pom.xml +++ b/spring-cloud-starter-sleuth/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.4.BUILD-SNAPSHOT + 1.0.4.RELEASE .. spring-cloud-starter-sleuth diff --git a/spring-cloud-starter-zipkin/pom.xml b/spring-cloud-starter-zipkin/pom.xml index 2191ec89b..43d5a56e0 100644 --- a/spring-cloud-starter-zipkin/pom.xml +++ b/spring-cloud-starter-zipkin/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.4.BUILD-SNAPSHOT + 1.0.4.RELEASE .. spring-cloud-starter-zipkin From 84723e8ad7761b7b58822e5ffc0cda7a72191093 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Mon, 25 Jul 2016 11:44:50 +0200 Subject: [PATCH 028/235] Going back to Snapshots --- benchmarks/pom.xml | 2 +- docs/pom.xml | 2 +- pom.xml | 6 +++--- spring-cloud-sleuth-core/pom.xml | 2 +- spring-cloud-sleuth-dependencies/pom.xml | 6 +++--- spring-cloud-sleuth-samples/pom.xml | 2 +- .../spring-cloud-sleuth-sample-feign/pom.xml | 2 +- .../spring-cloud-sleuth-sample-messaging/pom.xml | 2 +- .../spring-cloud-sleuth-sample-ribbon/pom.xml | 2 +- .../spring-cloud-sleuth-sample-stream/pom.xml | 2 +- .../spring-cloud-sleuth-sample-test-core/pom.xml | 2 +- .../spring-cloud-sleuth-sample-websocket/pom.xml | 2 +- .../spring-cloud-sleuth-sample-zipkin-stream/pom.xml | 2 +- .../spring-cloud-sleuth-sample-zipkin/pom.xml | 2 +- .../spring-cloud-sleuth-sample/pom.xml | 2 +- spring-cloud-sleuth-stream/pom.xml | 2 +- spring-cloud-sleuth-zipkin-stream/pom.xml | 2 +- spring-cloud-sleuth-zipkin/pom.xml | 2 +- spring-cloud-starter-sleuth/pom.xml | 2 +- spring-cloud-starter-zipkin/pom.xml | 2 +- 20 files changed, 24 insertions(+), 24 deletions(-) diff --git a/benchmarks/pom.xml b/benchmarks/pom.xml index 67e796a6d..d0287a4bb 100644 --- a/benchmarks/pom.xml +++ b/benchmarks/pom.xml @@ -17,7 +17,7 @@ Benchmarks Benchmarks (JMH) org.springframework.cloud - 1.0.4.RELEASE + 1.0.5.BUILD-SNAPSHOT benchmarks diff --git a/docs/pom.xml b/docs/pom.xml index 671687830..0b06c760e 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.4.RELEASE + 1.0.5.BUILD-SNAPSHOT spring-cloud-sleuth-docs pom diff --git a/pom.xml b/pom.xml index 1358ec5bc..dd612e174 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 spring-cloud-sleuth - 1.0.4.RELEASE + 1.0.5.BUILD-SNAPSHOT pom Spring Cloud Sleuth Spring Cloud Sleuth @@ -13,7 +13,7 @@ org.springframework.cloud spring-cloud-build - 1.1.1.RELEASE + 1.1.2.BUILD-SNAPSHOT @@ -208,7 +208,7 @@ 1.8 2.19.1 2.17 - 1.1.1.RELEASE + 1.1.2.BUILD-SNAPSHOT diff --git a/spring-cloud-sleuth-core/pom.xml b/spring-cloud-sleuth-core/pom.xml index dbf7baa87..88741d86e 100644 --- a/spring-cloud-sleuth-core/pom.xml +++ b/spring-cloud-sleuth-core/pom.xml @@ -11,7 +11,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.4.RELEASE + 1.0.5.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-dependencies/pom.xml b/spring-cloud-sleuth-dependencies/pom.xml index 6321f5f11..5ba2f53ab 100644 --- a/spring-cloud-sleuth-dependencies/pom.xml +++ b/spring-cloud-sleuth-dependencies/pom.xml @@ -5,16 +5,16 @@ spring-cloud-dependencies-parent org.springframework.cloud - 1.1.1.RELEASE + 1.1.2.BUILD-SNAPSHOT spring-cloud-sleuth-dependencies - 1.0.4.RELEASE + 1.0.5.BUILD-SNAPSHOT pom spring-cloud-sleuth-dependencies Spring Cloud Sleuth Dependencies - 1.1.5.RELEASE + 1.1.6.BUILD-SNAPSHOT 1.8.4 1.1.5 diff --git a/spring-cloud-sleuth-samples/pom.xml b/spring-cloud-sleuth-samples/pom.xml index 57d97c463..8c69bc237 100644 --- a/spring-cloud-sleuth-samples/pom.xml +++ b/spring-cloud-sleuth-samples/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.4.RELEASE + 1.0.5.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml index a83d69154..c5e540069 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml @@ -20,7 +20,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.4.RELEASE + 1.0.5.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml index 9d0fa250d..b4ea43b89 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.4.RELEASE + 1.0.5.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml index 20c91b83b..53c1d4d94 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.4.RELEASE + 1.0.5.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml index 49440a3e2..c4f2b187b 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.4.RELEASE + 1.0.5.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml index 18cb76765..6ed31bd65 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.4.RELEASE + 1.0.5.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml index a3b841565..5d77dd761 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.4.RELEASE + 1.0.5.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml index c2f867c2a..78f4ab0e5 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.4.RELEASE + 1.0.5.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml index 6abfbde3e..af1c2b245 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml @@ -27,7 +27,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.4.RELEASE + 1.0.5.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml index ea24171fc..e67cf7e06 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.4.RELEASE + 1.0.5.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-stream/pom.xml b/spring-cloud-sleuth-stream/pom.xml index 723231d2d..3b8630558 100644 --- a/spring-cloud-sleuth-stream/pom.xml +++ b/spring-cloud-sleuth-stream/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.4.RELEASE + 1.0.5.BUILD-SNAPSHOT diff --git a/spring-cloud-sleuth-zipkin-stream/pom.xml b/spring-cloud-sleuth-zipkin-stream/pom.xml index 558941116..31911a7c8 100644 --- a/spring-cloud-sleuth-zipkin-stream/pom.xml +++ b/spring-cloud-sleuth-zipkin-stream/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.4.RELEASE + 1.0.5.BUILD-SNAPSHOT diff --git a/spring-cloud-sleuth-zipkin/pom.xml b/spring-cloud-sleuth-zipkin/pom.xml index c6a80f27e..aea595d9d 100644 --- a/spring-cloud-sleuth-zipkin/pom.xml +++ b/spring-cloud-sleuth-zipkin/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.4.RELEASE + 1.0.5.BUILD-SNAPSHOT .. diff --git a/spring-cloud-starter-sleuth/pom.xml b/spring-cloud-starter-sleuth/pom.xml index 71741e1ba..1cf8d94d6 100644 --- a/spring-cloud-starter-sleuth/pom.xml +++ b/spring-cloud-starter-sleuth/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.4.RELEASE + 1.0.5.BUILD-SNAPSHOT .. spring-cloud-starter-sleuth diff --git a/spring-cloud-starter-zipkin/pom.xml b/spring-cloud-starter-zipkin/pom.xml index 43d5a56e0..d86002f47 100644 --- a/spring-cloud-starter-zipkin/pom.xml +++ b/spring-cloud-starter-zipkin/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.4.RELEASE + 1.0.5.BUILD-SNAPSHOT .. spring-cloud-starter-zipkin From e0e6e8482ed81b4631c967848e658c7949aafed4 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Thu, 28 Jul 2016 11:04:28 +0200 Subject: [PATCH 029/235] Returning a copy instead of a view (#356) * Returning a copy instead of a view * Making logs and tags thread-safe fixed #355 * Changes following code review --- .../org/springframework/cloud/sleuth/Span.java | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java index 29b8a0d59..b7ed540eb 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java @@ -26,14 +26,16 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; + +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - /** * Class for gathering and reporting statistics about a block of execution. *

@@ -142,7 +144,7 @@ public class Span { private boolean exportable = true; private final Map tags; private final String processId; - private final List logs; + private final Collection logs; private final Span savedSpan; // Null means we don't know the start tick, so fallback to time @@ -205,8 +207,8 @@ public class Span { this.exportable = exportable; this.processId = processId; this.savedSpan = savedSpan; - this.tags = new LinkedHashMap<>(); - this.logs = new ArrayList<>(); + this.tags = new ConcurrentHashMap<>(); + this.logs = new ConcurrentLinkedQueue<>(); } public static SpanBuilder builder() { @@ -297,7 +299,7 @@ public class Span { * Will never be null. */ public Map tags() { - return Collections.unmodifiableMap(this.tags); + return Collections.unmodifiableMap(new LinkedHashMap<>(this.tags)); } /** @@ -307,7 +309,7 @@ public class Span { * Will never be null. */ public List logs() { - return Collections.unmodifiableList(this.logs); + return Collections.unmodifiableList(new ArrayList<>(this.logs)); } /** From 230456e0939937da38f2b917538a73470213778f Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Thu, 28 Jul 2016 11:14:11 +0200 Subject: [PATCH 030/235] Updated CircleCI --- circle.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/circle.yml b/circle.yml index 67412025d..9567e34c7 100644 --- a/circle.yml +++ b/circle.yml @@ -1,3 +1,7 @@ +general: + branches: + ignore: + - gh-pages # list of branches to ignore machine: java: version: oraclejdk8 From 8a5aabea842f5782b1312ef5bcc0e683bf20d619 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Fri, 29 Jul 2016 12:43:15 +0200 Subject: [PATCH 031/235] Unified tag names with this change the tags related to method and class name will not be hyphen delimited. fixes #359 --- .../sleuth/instrument/web/TraceHandlerInterceptor.java | 10 ++++------ .../instrument/web/TraceFilterIntegrationTests.java | 10 +++++----- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptor.java index c9d87e7bc..f23cbd952 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptor.java @@ -16,9 +16,9 @@ package org.springframework.cloud.sleuth.instrument.web; +import java.lang.invoke.MethodHandles; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import java.lang.invoke.MethodHandles; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -87,8 +87,7 @@ public class TraceHandlerInterceptor extends HandlerInterceptorAdapter { private void addClassMethodTag(Object handler, Span span) { if (handler instanceof HandlerMethod) { - String methodName = SpanNameUtil.toLowerHyphen( - ((HandlerMethod) handler).getMethod().getName()); + String methodName = ((HandlerMethod) handler).getMethod().getName(); getTracer().addTag(getTraceKeys().getMvc().getControllerMethod(), methodName); if (log.isDebugEnabled()) { log.debug("Adding a method tag with value [" + methodName + "] to a span " + span); @@ -99,10 +98,9 @@ public class TraceHandlerInterceptor extends HandlerInterceptorAdapter { private void addClassNameTag(Object handler, Span span) { String className; if (handler instanceof HandlerMethod) { - className = SpanNameUtil.toLowerHyphen( - ((HandlerMethod) handler).getBeanType().getSimpleName()); + className = ((HandlerMethod) handler).getBeanType().getSimpleName(); } else { - className = SpanNameUtil.toLowerHyphen(handler.getClass().getSimpleName()); + className = handler.getClass().getSimpleName(); } if (log.isDebugEnabled()) { log.debug("Adding a class tag with value [" + className + "] to a span " + span); diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterIntegrationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterIntegrationTests.java index 05ee3bb3a..4b645eea6 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterIntegrationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterIntegrationTests.java @@ -105,7 +105,7 @@ public class TraceFilterIntegrationTests extends AbstractMvcIntegrationTest { Long expectedTraceId = new Random().nextLong(); MvcResult mvcResult = whenSentFutureWithTraceId(expectedTraceId); - mvcResult = this.mockMvc.perform(asyncDispatch(mvcResult)) + this.mockMvc.perform(asyncDispatch(mvcResult)) .andExpect(status().isOk()).andReturn(); then(tracingHeaderFrom(mvcResult)).isEqualTo(expectedTraceId); @@ -118,7 +118,7 @@ public class TraceFilterIntegrationTests extends AbstractMvcIntegrationTest { Long expectedTraceId = new Random().nextLong(); MvcResult mvcResult = whenSentDeferredWithTraceId(expectedTraceId); - mvcResult = this.mockMvc.perform(asyncDispatch(mvcResult)) + this.mockMvc.perform(asyncDispatch(mvcResult)) .andExpect(status().isOk()).andReturn(); then(tracingHeaderFrom(mvcResult)).isEqualTo(expectedTraceId); @@ -126,8 +126,8 @@ public class TraceFilterIntegrationTests extends AbstractMvcIntegrationTest { .filter(span -> span.tags().containsKey("tag")).findFirst(); then(taggedSpan.isPresent()).isTrue(); then(taggedSpan.get()).hasATag("tag", "value"); - then(taggedSpan.get()).hasATag("mvc.controller.method", "deferred"); - then(taggedSpan.get()).hasATag("mvc.controller.class", "test-controller"); + then(taggedSpan.get()).hasATag("mvc.controller.method", "deferredMethod"); + then(taggedSpan.get()).hasATag("mvc.controller.class", "TestController"); then(ExceptionUtils.getLastException()).isNull(); } @@ -257,7 +257,7 @@ public class TraceFilterIntegrationTests extends AbstractMvcIntegrationTest { } @RequestMapping("/deferred") - public DeferredResult deferred() { + public DeferredResult deferredMethod() { logger.info("deferred"); this.tracer.addTag("tag", "value"); span = this.tracer.getCurrentSpan(); From c8992a65b329aa5b6f23d0f670c59ceb2c3f05ec Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Fri, 29 Jul 2016 15:07:17 +0200 Subject: [PATCH 032/235] Adding tags for Hystrix spans with this change if a span hasnt set any Hystrix related spans then we're setting the tags even if the span gets continued (which is the case when you're using Javanica fixes #352 --- .../SleuthHystrixConcurrencyStrategy.java | 13 ++-- .../SleuthHystrixConcurrencyStrategyTest.java | 74 +++++++++++++++++-- 2 files changed, 75 insertions(+), 12 deletions(-) diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/hystrix/SleuthHystrixConcurrencyStrategy.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/hystrix/SleuthHystrixConcurrencyStrategy.java index 1f2f41f15..1d6fe2838 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/hystrix/SleuthHystrixConcurrencyStrategy.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/hystrix/SleuthHystrixConcurrencyStrategy.java @@ -129,13 +129,16 @@ public class SleuthHystrixConcurrencyStrategy extends HystrixConcurrencyStrategy } else { span = this.tracer.createSpan(HYSTRIX_COMPONENT); - this.tracer.addTag(Span.SPAN_LOCAL_COMPONENT_TAG_NAME, HYSTRIX_COMPONENT); - this.tracer.addTag( - this.traceKeys.getAsync().getPrefix() - + this.traceKeys.getAsync().getThreadNameKey(), - Thread.currentThread().getName()); created = true; } + if (!span.tags().containsKey(Span.SPAN_LOCAL_COMPONENT_TAG_NAME)) { + this.tracer.addTag(Span.SPAN_LOCAL_COMPONENT_TAG_NAME, HYSTRIX_COMPONENT); + } + String asyncKey = this.traceKeys.getAsync().getPrefix() + + this.traceKeys.getAsync().getThreadNameKey(); + if (!span.tags().containsKey(asyncKey)) { + this.tracer.addTag(asyncKey, Thread.currentThread().getName()); + } try { return this.callable.call(); } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/hystrix/SleuthHystrixConcurrencyStrategyTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/hystrix/SleuthHystrixConcurrencyStrategyTest.java index 087187403..6f9d831be 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/hystrix/SleuthHystrixConcurrencyStrategyTest.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/hystrix/SleuthHystrixConcurrencyStrategyTest.java @@ -16,16 +16,22 @@ package org.springframework.cloud.sleuth.instrument.hystrix; +import java.util.Random; import java.util.concurrent.Callable; import org.junit.After; import org.junit.Before; import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.cloud.sleuth.DefaultSpanNamer; +import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.TraceKeys; import org.springframework.cloud.sleuth.Tracer; +import org.springframework.cloud.sleuth.assertions.ListOfSpans; +import org.springframework.cloud.sleuth.log.NoOpSpanLogger; +import org.springframework.cloud.sleuth.sampler.AlwaysSampler; +import org.springframework.cloud.sleuth.trace.DefaultTracer; +import org.springframework.cloud.sleuth.util.ArrayListSpanAccumulator; +import org.springframework.cloud.sleuth.util.ExceptionUtils; import com.netflix.hystrix.strategy.HystrixPlugins; import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy; @@ -34,21 +40,23 @@ import com.netflix.hystrix.strategy.executionhook.HystrixCommandExecutionHook; import com.netflix.hystrix.strategy.metrics.HystrixMetricsPublisher; import com.netflix.hystrix.strategy.properties.HystrixPropertiesStrategy; -import static org.assertj.core.api.BDDAssertions.then; - +import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; /** * @author Marcin Grzejszczak */ -@RunWith(MockitoJUnitRunner.class) public class SleuthHystrixConcurrencyStrategyTest { - @Mock Tracer tracer; + ArrayListSpanAccumulator spanReporter = new ArrayListSpanAccumulator(); + Tracer tracer = new DefaultTracer(new AlwaysSampler(), new Random(), + new DefaultSpanNamer(), new NoOpSpanLogger(), this.spanReporter); TraceKeys traceKeys = new TraceKeys(); @Before @After public void setup() { + ExceptionUtils.setFail(true); HystrixPlugins.reset(); + this.spanReporter.getSpans().clear(); } @Test @@ -94,6 +102,58 @@ public class SleuthHystrixConcurrencyStrategyTest { then(callable).isInstanceOf(SleuthHystrixConcurrencyStrategy.HystrixTraceCallable.class); } + @Test + public void should_add_trace_keys_when_span_is_created() + throws Exception { + SleuthHystrixConcurrencyStrategy strategy = new SleuthHystrixConcurrencyStrategy( + this.tracer, this.traceKeys); + Callable callable = strategy.wrapCallable(() -> "hello"); + + callable.call(); + + String asyncKey = this.traceKeys.getAsync().getPrefix() + + this.traceKeys.getAsync().getThreadNameKey(); + then(new ListOfSpans(this.spanReporter.getSpans())) + .hasASpanWithTagEqualTo(Span.SPAN_LOCAL_COMPONENT_TAG_NAME, "hystrix") + .hasASpanWithTagKeyEqualTo(asyncKey); + } + + @Test + public void should_add_trace_keys_when_span_is_continued() + throws Exception { + Span span = this.tracer.createSpan("new_span"); + SleuthHystrixConcurrencyStrategy strategy = new SleuthHystrixConcurrencyStrategy( + this.tracer, this.traceKeys); + Callable callable = strategy.wrapCallable(() -> "hello"); + + callable.call(); + + String asyncKey = this.traceKeys.getAsync().getPrefix() + + this.traceKeys.getAsync().getThreadNameKey(); + then(span) + .hasATag(Span.SPAN_LOCAL_COMPONENT_TAG_NAME, "hystrix") + .hasATagWithKey(asyncKey); + } + + @Test + public void should_not_override_trace_keys_when_span_is_continued() + throws Exception { + Span span = this.tracer.createSpan("new_span"); + String asyncKey = this.traceKeys.getAsync().getPrefix() + + this.traceKeys.getAsync().getThreadNameKey(); + this.tracer.addTag(Span.SPAN_LOCAL_COMPONENT_TAG_NAME, "foo"); + this.tracer.addTag(asyncKey, "bar"); + SleuthHystrixConcurrencyStrategy strategy = new SleuthHystrixConcurrencyStrategy( + this.tracer, this.traceKeys); + Callable callable = strategy.wrapCallable(() -> "hello"); + + callable.call(); + + then(span) + .hasATag(Span.SPAN_LOCAL_COMPONENT_TAG_NAME, "foo") + .hasATag(asyncKey, "bar"); + } + static class MyHystrixCommandExecutionHook extends HystrixCommandExecutionHook {} @SuppressWarnings("unchecked") static class MyHystrixConcurrencyStrategy extends HystrixConcurrencyStrategy { From cf9e379ca1942bab55fe98ca8448cf0f24ff38d6 Mon Sep 17 00:00:00 2001 From: Steve Oakey Date: Fri, 29 Jul 2016 09:40:46 -0400 Subject: [PATCH 033/235] Added PollerMetadata for StreamSpanReporter (#343) Added configurable PollerMetadata bean to be used with the Inbound Channel Adapter in the StreamSpanReporter. Fixed delay and max messages per poll are configurable via configuration properties. Implementors can provide a bean of type PollerMetadata and name StreamSpanReporter.POLLER to take full control of the poller. Fixes: gh-338 --- .../stream/SleuthStreamAutoConfiguration.java | 24 ++- .../sleuth/stream/SleuthStreamProperties.java | 33 ++++ .../sleuth/stream/StreamSpanReporter.java | 11 +- .../SleuthStreamAutoConfigurationTest.java | 142 ++++++++++++++++++ .../stream/StreamSpanListenerTests.java | 30 ++-- 5 files changed, 221 insertions(+), 19 deletions(-) create mode 100644 spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/SleuthStreamAutoConfigurationTest.java diff --git a/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/SleuthStreamAutoConfiguration.java b/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/SleuthStreamAutoConfiguration.java index c5e748e9b..e69f04564 100644 --- a/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/SleuthStreamAutoConfiguration.java +++ b/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/SleuthStreamAutoConfiguration.java @@ -38,20 +38,23 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.integration.config.GlobalChannelInterceptor; +import org.springframework.integration.scheduling.PollerMetadata; import org.springframework.messaging.support.ChannelInterceptor; +import org.springframework.scheduling.support.PeriodicTrigger; /** - * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration Auto-configuration} - * for sending spans over Spring Cloud Stream. This is for the producer - * (via {@link SleuthSource}). A consumer can enable binding to {@link SleuthSink} and - * receive the messages coming from the source (they have the same channel name so there - * is no additional configuration to do by default). + * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration + * Auto-configuration} for sending spans over Spring Cloud Stream. This is for + * the producer (via {@link SleuthSource}). A consumer can enable binding to + * {@link SleuthSink} and receive the messages coming from the source (they have + * the same channel name so there is no additional configuration to do by + * default). * * @author Dave Syer * @since 1.0.0 */ @Configuration -@EnableConfigurationProperties({SleuthStreamProperties.class, SamplerProperties.class}) +@EnableConfigurationProperties({ SleuthStreamProperties.class, SamplerProperties.class }) @AutoConfigureAfter(TraceMetricsAutoConfiguration.class) @AutoConfigureBefore(ChannelBindingAutoConfiguration.class) @EnableBinding(SleuthSource.class) @@ -77,6 +80,15 @@ public class SleuthStreamAutoConfiguration { return new StreamSpanReporter(endpointLocator, spanMetricReporter); } + @Bean(name = StreamSpanReporter.POLLER) + @ConditionalOnMissingBean(name = StreamSpanReporter.POLLER) + public PollerMetadata defaultStreamSpanReporterPoller(SleuthStreamProperties sleuth) { + PollerMetadata poller = new PollerMetadata(); + poller.setTrigger(new PeriodicTrigger(sleuth.getPoller().getFixedDelay())); + poller.setMaxMessagesPerPoll(sleuth.getPoller().getMaxMessagesPerPoll()); + return poller; + } + @Configuration @ConditionalOnMissingClass("org.springframework.cloud.client.discovery.DiscoveryClient") protected static class DefaultEndpointLocatorConfiguration { diff --git a/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/SleuthStreamProperties.java b/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/SleuthStreamProperties.java index 415f75dd8..81c7959d9 100644 --- a/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/SleuthStreamProperties.java +++ b/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/SleuthStreamProperties.java @@ -28,6 +28,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties; public class SleuthStreamProperties { private boolean enabled = true; private String group = SleuthSink.INPUT; + private Poller poller = new Poller(); public boolean isEnabled() { return this.enabled; @@ -44,4 +45,36 @@ public class SleuthStreamProperties { public void setGroup(String group) { this.group = group; } + + public Poller getPoller() { + return this.poller; + } + + public static class Poller { + /** + * Fixed delay (ms). Default: 1000 + */ + private long fixedDelay = 1000L; + + /** + * Max messages per poll. Default: -1 (unbounded) + */ + private int maxMessagesPerPoll = -1; + + public long getFixedDelay() { + return this.fixedDelay; + } + + public int getMaxMessagesPerPoll() { + return this.maxMessagesPerPoll; + } + + public void setFixedDelay(long fixedDelay) { + this.fixedDelay = fixedDelay; + } + + public void setMaxMessagesPerPoll(int maxMessagesPerPoll) { + this.maxMessagesPerPoll = maxMessagesPerPoll; + } + } } diff --git a/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/StreamSpanReporter.java b/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/StreamSpanReporter.java index e28491a0f..7ecbd8c9a 100644 --- a/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/StreamSpanReporter.java +++ b/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/StreamSpanReporter.java @@ -27,7 +27,7 @@ import org.springframework.cloud.sleuth.SpanReporter; import org.springframework.cloud.sleuth.metric.SpanMetricReporter; import org.springframework.integration.annotation.InboundChannelAdapter; import org.springframework.integration.annotation.MessageEndpoint; - +import org.springframework.integration.annotation.Poller; /** * A message source for spans. Also handles RPC flavoured annotations. @@ -38,6 +38,13 @@ import org.springframework.integration.annotation.MessageEndpoint; @MessageEndpoint public class StreamSpanReporter implements SpanReporter { + /** + * Bean name for the + * {@link org.springframework.integration.scheduling.PollerMetadata + * PollerMetadata} + */ + public static final String POLLER = "streamSpanReporterPoller"; + private BlockingQueue queue = new LinkedBlockingQueue<>(); private final HostLocator endpointLocator; private final SpanMetricReporter spanMetricReporter; @@ -51,7 +58,7 @@ public class StreamSpanReporter implements SpanReporter { this.queue = queue; } - @InboundChannelAdapter(value = SleuthSource.OUTPUT) + @InboundChannelAdapter(value = SleuthSource.OUTPUT, poller = @Poller(POLLER)) public Spans poll() { List result = new LinkedList<>(); this.queue.drainTo(result); diff --git a/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/SleuthStreamAutoConfigurationTest.java b/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/SleuthStreamAutoConfigurationTest.java new file mode 100644 index 000000000..3e0d02bf1 --- /dev/null +++ b/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/SleuthStreamAutoConfigurationTest.java @@ -0,0 +1,142 @@ +package org.springframework.cloud.sleuth.stream; + +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; + +import org.junit.After; +import org.junit.BeforeClass; +import org.junit.Test; +import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; +import org.springframework.boot.test.EnvironmentTestUtils; +import org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration; +import org.springframework.cloud.sleuth.log.NoOpSpanLogger; +import org.springframework.cloud.sleuth.log.SpanLogger; +import org.springframework.cloud.sleuth.metric.TraceMetricsAutoConfiguration; +import org.springframework.cloud.stream.config.ChannelBindingAutoConfiguration; +import org.springframework.cloud.stream.test.binder.TestSupportBinderAutoConfiguration; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.integration.scheduling.PollerMetadata; +import org.springframework.scheduling.Trigger; +import org.springframework.scheduling.TriggerContext; +import org.springframework.scheduling.support.PeriodicTrigger; +import org.springframework.scheduling.support.SimpleTriggerContext; + +import static org.assertj.core.api.Assertions.assertThat; + +public class SleuthStreamAutoConfigurationTest { + + private AnnotationConfigApplicationContext ctx; + private static DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + private static final String TEST_SCHEDULED = "2016-01-01 12:00:00"; + private static final String TEST_COMPLETION = "2016-01-01 12:00:02"; + private static Date LAST_SCHEDULED_DATE; + private static Date LAST_EXECUTED_DATE; + private static Date LAST_COMPLETED_DATE; + + @BeforeClass + public static void setupTests() throws ParseException { + LAST_SCHEDULED_DATE = dateFormat.parse(TEST_SCHEDULED); + LAST_EXECUTED_DATE = new Date(LAST_SCHEDULED_DATE.getTime()); + LAST_COMPLETED_DATE = dateFormat.parse(TEST_COMPLETION); + } + + @After + public void cleanup() { + if (ctx != null) { + ctx.close(); + } + } + + @Test + public void shouldUseDefaultPollerConfiguration() { + ctx = createContext(); + ctx.refresh(); + + PollerMetadata poller = ctx.getBean(StreamSpanReporter.POLLER, + PollerMetadata.class); + assertThat(poller).isNotNull(); + assertPollerConfigurationUsingConfigurationProperties(poller); + } + + @Test + public void shouldUseCustomPollerConfiguration() { + ctx = createContext(); + EnvironmentTestUtils.addEnvironment(ctx, + "spring.sleuth.stream.poller.fixed-delay=5000", + "spring.sleuth.stream.poller.max-messages-per-poll=100"); + ctx.refresh(); + + PollerMetadata poller = ctx.getBean(StreamSpanReporter.POLLER, + PollerMetadata.class); + assertThat(poller).isNotNull(); + assertPollerConfigurationUsingConfigurationProperties(poller); + } + + @Test + public void shouldUseCustomPollerBean() { + ctx = createContext(CustomPollerConfiguration.class); + ctx.refresh(); + + PollerMetadata poller = ctx.getBean(StreamSpanReporter.POLLER, + PollerMetadata.class); + assertThat(poller).isNotNull(); + assertPollerConfiguration(poller, 500L, 5000L); + } + + private void assertPollerConfigurationUsingConfigurationProperties( + PollerMetadata poller) { + SleuthStreamProperties sleuth = ctx.getBean(SleuthStreamProperties.class); + assertPollerConfiguration(poller, sleuth.getPoller().getMaxMessagesPerPoll(), + sleuth.getPoller().getFixedDelay()); + } + + private void assertPollerConfiguration(PollerMetadata poller, + long expectedMaxMessages, long expectedFixedDelay) { + assertThat(poller.getMaxMessagesPerPoll()).isEqualTo(expectedMaxMessages); + Trigger trigger = poller.getTrigger(); + assertThat(trigger).isInstanceOf(PeriodicTrigger.class); + TriggerContext triggerContext = new SimpleTriggerContext(LAST_SCHEDULED_DATE, + LAST_EXECUTED_DATE, LAST_COMPLETED_DATE); + Date nextExecution = trigger.nextExecutionTime(triggerContext); + assertThat(nextExecution.getTime()) + .isEqualTo(LAST_COMPLETED_DATE.getTime() + expectedFixedDelay); + } + + public AnnotationConfigApplicationContext createContext(Class... classes) { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + if (classes != null && classes.length > 0) { + context.register(classes); + } + context.register(BaseConfiguration.class); + return context; + } + + @Configuration + @Import({ SleuthStreamAutoConfiguration.class, TraceMetricsAutoConfiguration.class, + TestSupportBinderAutoConfiguration.class, + ChannelBindingAutoConfiguration.class, TraceAutoConfiguration.class, + PropertyPlaceholderAutoConfiguration.class }) + public static class BaseConfiguration { + @Bean + SpanLogger spanLogger() { + return new NoOpSpanLogger(); + } + } + + @Configuration + public static class CustomPollerConfiguration { + + @Bean(name = StreamSpanReporter.POLLER) + PollerMetadata customPoller() { + PollerMetadata poller = new PollerMetadata(); + poller.setMaxMessagesPerPoll(500); + poller.setTrigger(new PeriodicTrigger(5000L)); + return poller; + } + } +} diff --git a/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/StreamSpanListenerTests.java b/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/StreamSpanListenerTests.java index 1ad85c568..cf41a4c9c 100644 --- a/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/StreamSpanListenerTests.java +++ b/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/StreamSpanListenerTests.java @@ -33,7 +33,6 @@ import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.actuate.metrics.CounterService; import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; -import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.cloud.sleuth.Sampler; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.SpanReporter; @@ -53,22 +52,29 @@ import org.springframework.context.annotation.Import; import org.springframework.integration.annotation.MessageEndpoint; import org.springframework.integration.annotation.ServiceActivator; import org.springframework.messaging.Message; +import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; /** * @author Dave Syer * */ -@SpringApplicationConfiguration(classes = TestConfiguration.class) +@ContextConfiguration(classes = TestConfiguration.class) @RunWith(SpringJUnit4ClassRunner.class) public class StreamSpanListenerTests { - @Autowired Tracer tracer; - @Autowired ApplicationContext application; - @Autowired ZipkinTestConfiguration test; - @Autowired StreamSpanReporter listener; - @Autowired CounterService counterService; - @Autowired SpanReporter spanReporter; + @Autowired + Tracer tracer; + @Autowired + ApplicationContext application; + @Autowired + ZipkinTestConfiguration test; + @Autowired + StreamSpanReporter listener; + @Autowired + CounterService counterService; + @Autowired + SpanReporter spanReporter; @PostConstruct public void init() { @@ -149,7 +155,8 @@ public class StreamSpanListenerTests { TraceMetricsAutoConfiguration.class, TestSupportBinderAutoConfiguration.class, ChannelBindingAutoConfiguration.class, TraceAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class }) - protected static class TestConfiguration {} + protected static class TestConfiguration { + } @Configuration @MessageEndpoint @@ -160,7 +167,7 @@ public class StreamSpanListenerTests { @Autowired StreamSpanReporter listener; - @ServiceActivator(inputChannel=SleuthSource.OUTPUT) + @ServiceActivator(inputChannel = SleuthSource.OUTPUT) public void handle(Message msg) { } @@ -174,7 +181,8 @@ public class StreamSpanListenerTests { return new AlwaysSampler(); } - @Bean CounterService counterService() { + @Bean + CounterService counterService() { return Mockito.mock(CounterService.class); } From 9c0fe65649294a64e0076ac80fef6cc6788b7396 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Fri, 29 Jul 2016 23:29:31 +0200 Subject: [PATCH 034/235] Updating the duration setting (#360) * Updating the duration setting fixes #349 --- .../springframework/cloud/sleuth/Span.java | 18 ++++++++++-- .../stream/ConvertToZipkinSpanList.java | 28 +++++++++++++++++- .../stream/ConvertToZipkinSpanListTests.java | 24 +++++++++++++++ .../sleuth/zipkin/ZipkinSpanListener.java | 29 ++++++++++++++++++- .../zipkin/ZipkinSpanListenerTests.java | 23 ++++++++++++++- 5 files changed, 116 insertions(+), 6 deletions(-) diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java index b7ed540eb..250f7a11d 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java @@ -192,7 +192,7 @@ public class Span { this.startNanos = null; // don't know the start tick this.begin = begin; } else { - this.startNanos = System.nanoTime(); + this.startNanos = nanoTime(); this.begin = System.currentTimeMillis(); } if (end > 0) { @@ -228,7 +228,7 @@ public class Span { this.end = System.currentTimeMillis(); } if (this.startNanos != null) { // set a precise duration - this.durationMicros = (System.nanoTime() - this.startNanos) / 1000; + this.durationMicros = Math.max(1, (nanoTime() - this.startNanos) / 1000); } else { this.durationMicros = (this.end - this.begin) * 1000; } @@ -250,6 +250,12 @@ public class Span { /** * Return the total amount of time elapsed since start was called, if running, or * difference between stop and start, in microseconds. + * + * Note that in case of the spans that have CS / CR events we will not + * send to Zipkin the accumulated microseconds but will calculate the + * duration basing on the timestamps of the CS / CR events. + * + * @return zero if not running, or a positive number of microseconds. */ @JsonIgnore public synchronized long getAccumulatedMicros() { @@ -260,13 +266,19 @@ public class Span { return 0; } if (this.startNanos != null) { - return (System.nanoTime() - this.startNanos) / 1000; + return Math.max(1, (nanoTime() - this.startNanos) / 1000); } else { return (System.currentTimeMillis() - this.begin) * 1000; } } } + // Visible for testing + @JsonIgnore + long nanoTime() { + return System.nanoTime(); + } + /** * Has the span been started and not yet stopped? */ diff --git a/spring-cloud-sleuth-zipkin-stream/src/main/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanList.java b/spring-cloud-sleuth-zipkin-stream/src/main/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanList.java index b086c876f..930f5a5ef 100644 --- a/spring-cloud-sleuth-zipkin-stream/src/main/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanList.java +++ b/spring-cloud-sleuth-zipkin-stream/src/main/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanList.java @@ -91,7 +91,7 @@ final class ConvertToZipkinSpanList { } zipkinSpan.timestamp(span.getBegin() * 1000); if (!span.isRunning()) { // duration is authoritative, only write when the span stopped - zipkinSpan.duration(span.getAccumulatedMicros()); + zipkinSpan.duration(calculateDurationInMicros(span)); } zipkinSpan.traceId(span.getTraceId()); if (span.getParents().size() > 0) { @@ -146,4 +146,30 @@ final class ConvertToZipkinSpanList { return false; } + /** + * There could be instrumentation delay between span creation and the + * semantic start of the span (client send). When there's a difference, + * spans look confusing. Ex users expect duration to be client + * receive - send, but it is a little more than that. Rather than have + * to teach each user about the possibility of instrumentation overhead, + * we truncate absolute duration (span finish - create) to semantic + * duration (client receive - send) + */ + private static long calculateDurationInMicros(Span span) { + org.springframework.cloud.sleuth.Log clientSend = hasLog(Span.CLIENT_SEND, span); + org.springframework.cloud.sleuth.Log clientReceived = hasLog(Span.CLIENT_RECV, span); + if (clientSend != null && clientReceived != null) { + return (clientReceived.getTimestamp() - clientSend.getTimestamp()) * 1000; + } + return span.getAccumulatedMicros(); + } + + private static org.springframework.cloud.sleuth.Log hasLog(String logName, Span span) { + for (org.springframework.cloud.sleuth.Log log : span.logs()) { + if (logName.equals(log.getEvent())) { + return log; + } + } + return null; + } } \ No newline at end of file diff --git a/spring-cloud-sleuth-zipkin-stream/src/test/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanListTests.java b/spring-cloud-sleuth-zipkin-stream/src/test/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanListTests.java index ff282ff70..09635824d 100644 --- a/spring-cloud-sleuth-zipkin-stream/src/test/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanListTests.java +++ b/spring-cloud-sleuth-zipkin-stream/src/test/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanListTests.java @@ -128,6 +128,30 @@ public class ConvertToZipkinSpanListTests { .isLessThanOrEqualTo(System.currentTimeMillis() * 1000); } + @Test + public void setsTheDurationToTheDifferenceBetweenCRandCS() + throws InterruptedException { + Span span = span("foo"); + span.logEvent(Span.CLIENT_SEND); + Thread.sleep(10); + span.logEvent(Span.CLIENT_RECV); + Thread.sleep(20); + span.stop(); + + Spans spans = new Spans(this.host, Collections.singletonList(span)); + zipkin.Span result = ConvertToZipkinSpanList.convert(spans).get(0); + + assertThat(result.timestamp) + .isEqualTo(span.getBegin() * 1000); + long clientSendTimestamp = span.logs().stream().filter(log -> Span.CLIENT_SEND.equals(log.getEvent())) + .findFirst().get().getTimestamp(); + long clientRecvTimestamp = span.logs().stream().filter(log -> Span.CLIENT_RECV.equals(log.getEvent())) + .findFirst().get().getTimestamp(); + assertThat(result.duration) + .isNotEqualTo(span.getAccumulatedMicros()) + .isEqualTo((clientRecvTimestamp - clientSendTimestamp) * 1000); + } + /** Zipkin's duration should only be set when the span is finished. */ @Test public void doesntSetDurationWhenStillRunning() { diff --git a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListener.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListener.java index 36858de6a..ad63f007b 100644 --- a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListener.java +++ b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListener.java @@ -87,7 +87,7 @@ public class ZipkinSpanListener implements SpanReporter { } zipkinSpan.timestamp(span.getBegin() * 1000L); if (!span.isRunning()) { // duration is authoritative, only write when the span stopped - zipkinSpan.duration(span.getAccumulatedMicros()); + zipkinSpan.duration(calculateDurationInMicros(span)); } zipkinSpan.traceId(span.getTraceId()); if (span.getParents().size() > 0) { @@ -173,6 +173,33 @@ public class ZipkinSpanListener implements SpanReporter { } } + /** + * There could be instrumentation delay between span creation and the + * semantic start of the span (client send). When there's a difference, + * spans look confusing. Ex users expect duration to be client + * receive - send, but it is a little more than that. Rather than have + * to teach each user about the possibility of instrumentation overhead, + * we truncate absolute duration (span finish - create) to semantic + * duration (client receive - send) + */ + private long calculateDurationInMicros(Span span) { + org.springframework.cloud.sleuth.Log clientSend = hasLog(Span.CLIENT_SEND, span); + org.springframework.cloud.sleuth.Log clientReceived = hasLog(Span.CLIENT_RECV, span); + if (clientSend != null && clientReceived != null) { + return (clientReceived.getTimestamp() - clientSend.getTimestamp()) * 1000; + } + return span.getAccumulatedMicros(); + } + + private org.springframework.cloud.sleuth.Log hasLog(String logName, Span span) { + for (org.springframework.cloud.sleuth.Log log : span.logs()) { + if (logName.equals(log.getEvent())) { + return log; + } + } + return null; + } + @Override public void report(Span span) { if (span.isExportable()) { diff --git a/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListenerTests.java b/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListenerTests.java index 1b9a2cb4a..58f016e97 100644 --- a/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListenerTests.java +++ b/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListenerTests.java @@ -18,7 +18,6 @@ package org.springframework.cloud.sleuth.zipkin; import java.util.ArrayList; import java.util.List; - import javax.annotation.PostConstruct; import org.junit.Test; @@ -80,6 +79,28 @@ public class ZipkinSpanListenerTests { .isLessThanOrEqualTo(System.currentTimeMillis() * 1000); } + @Test + public void setsTheDurationToTheDifferenceBetweenCRandCS() + throws InterruptedException { + this.parent.logEvent(Span.CLIENT_SEND); + Thread.sleep(10); + this.parent.logEvent(Span.CLIENT_RECV); + Thread.sleep(20); + this.parent.stop(); + + zipkin.Span result = this.spanReporter.convert(this.parent); + + assertThat(result.timestamp) + .isEqualTo(this.parent.getBegin() * 1000); + long clientSendTimestamp = this.parent.logs().stream().filter(log -> Span.CLIENT_SEND.equals(log.getEvent())) + .findFirst().get().getTimestamp(); + long clientRecvTimestamp = this.parent.logs().stream().filter(log -> Span.CLIENT_RECV.equals(log.getEvent())) + .findFirst().get().getTimestamp(); + assertThat(result.duration) + .isNotEqualTo(this.parent.getAccumulatedMicros()) + .isEqualTo((clientRecvTimestamp - clientSendTimestamp) * 1000); + } + /** Zipkin's duration should only be set when the span is finished. */ @Test public void doesntSetDurationWhenStillRunning() { From 511d83afde053cee77974b0cd8be9ca80d6c865e Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Mon, 1 Aug 2016 17:52:17 -0700 Subject: [PATCH 035/235] Fixed circular dependency fixes #354 --- .../cloud/sleuth/instrument/web/TraceWebMvcConfigurer.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebMvcConfigurer.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebMvcConfigurer.java index 35935a39b..c4b1861c3 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebMvcConfigurer.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebMvcConfigurer.java @@ -32,7 +32,7 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter */ @Configuration class TraceWebMvcConfigurer extends WebMvcConfigurerAdapter { - @Autowired TraceHandlerInterceptor traceHandlerInterceptor; + @Autowired BeanFactory beanFactory; @Bean public TraceHandlerInterceptor traceHandlerInterceptor(BeanFactory beanFactory) { @@ -41,6 +41,6 @@ class TraceWebMvcConfigurer extends WebMvcConfigurerAdapter { @Override public void addInterceptors(InterceptorRegistry registry) { - registry.addInterceptor(this.traceHandlerInterceptor); + registry.addInterceptor(this.beanFactory.getBean(TraceHandlerInterceptor.class)); } } From 0781157f103332d406dae0b1236ffe02a848f61e Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Tue, 2 Aug 2016 23:28:26 -0700 Subject: [PATCH 036/235] Fixed wrong ConditionalOnClass with this fix there no longer should be bugs related to a missing class. fixes #363 --- .../scheduling/TraceSchedulingAutoConfiguration.java | 3 +-- .../web/client/feign/TraceFeignClientAutoConfiguration.java | 4 +--- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/scheduling/TraceSchedulingAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/scheduling/TraceSchedulingAutoConfiguration.java index 10ca458bb..40da08e1a 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/scheduling/TraceSchedulingAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/scheduling/TraceSchedulingAutoConfiguration.java @@ -18,7 +18,6 @@ package org.springframework.cloud.sleuth.instrument.scheduling; import java.util.regex.Pattern; -import org.aspectj.lang.ProceedingJoinPoint; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; @@ -52,7 +51,7 @@ public class TraceSchedulingAutoConfiguration { */ private @Value("${spring.sleuth.scheduled.skipPattern:}") String skipPattern; - @ConditionalOnClass(ProceedingJoinPoint.class) + @ConditionalOnClass(name = "org.aspectj.lang.ProceedingJoinPoint") @Bean public TraceSchedulingAspect traceSchedulingAspect(Tracer tracer, TraceKeys traceKeys) { return new TraceSchedulingAspect(tracer, traceKeys, Pattern.compile(this.skipPattern)); diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientAutoConfiguration.java index 8b7afb920..4f74db019 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientAutoConfiguration.java @@ -19,8 +19,6 @@ package org.springframework.cloud.sleuth.instrument.web.client.feign; import java.io.IOException; import java.lang.reflect.Type; -import com.netflix.hystrix.HystrixCommand; - import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.ObjectFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -70,7 +68,7 @@ public class TraceFeignClientAutoConfiguration { @Bean @Scope("prototype") - @ConditionalOnClass(HystrixCommand.class) + @ConditionalOnClass(name = "com.netflix.hystrix.HystrixCommand") @ConditionalOnProperty(name = "feign.hystrix.enabled", matchIfMissing = true) Feign.Builder feignHystrixBuilder(BeanFactory beanFactory) { return SleuthHystrixFeignBuilder.builder(beanFactory); From e408fd41900a847bb77eecd7eb238e69856af87a Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Sat, 6 Aug 2016 15:35:48 -0700 Subject: [PATCH 037/235] Added NPE gueard for TraceFeignContext --- .../sleuth/instrument/web/client/feign/TraceFeignContext.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignContext.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignContext.java index 86f977737..a7735e1b6 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignContext.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignContext.java @@ -32,6 +32,9 @@ class TraceFeignContext extends FeignContext { @Override public Map getInstances(String name, Class type) { Map instances = this.delegate.getInstances(name, type); + if (instances == null) { + return null; + } Map convertedInstances = new HashMap<>(); for (Map.Entry entry : instances.entrySet()) { convertedInstances.put(entry.getKey(), (T) this.traceFeignObjectWrapper.wrap(entry.getValue())); From ee23ea646523c2bb6b5d800a3192b61450d85e90 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Tue, 9 Aug 2016 18:54:28 +0200 Subject: [PATCH 038/235] Simplified Feign (#372) with this change we no longer treat retries as a continuation of a previous span. That way the Feign code simplifies a lot. RequestInterceptor starts a span and the TraceFeignClient will always close it no matter what's happening. fixes #202 --- .../feign/FeignContextBeanPostProcessor.java | 7 +- .../web/client/feign/FeignEventPublisher.java | 56 ----- .../web/client/feign/FeignRequestContext.java | 75 ------- .../feign/FeignResponseHeadersInjector.java | 73 ------- .../web/client/feign/SleuthFeignBuilder.java | 7 +- .../feign/SleuthHystrixFeignBuilder.java | 7 +- .../web/client/feign/TraceFeignClient.java | 73 +++++-- .../TraceFeignClientAutoConfiguration.java | 51 +---- .../web/client/feign/TraceFeignDecoder.java | 58 ----- .../client/feign/TraceFeignErrorDecoder.java | 52 ----- .../client/feign/TraceFeignObjectWrapper.java | 11 +- .../feign/TraceFeignRequestInterceptor.java | 25 +-- .../web/client/feign/TraceFeignRetryer.java | 76 ------- .../cloud/sleuth/assertions/SpanAssert.java | 12 ++ .../web/client/feign/FeignRetriesTests.java | 150 +++++++++++++ .../client/feign/TraceFeignClientTests.java | 105 +++++++++ .../feign/TraceFeignObjectWrapperTests.java | 21 +- .../issues/{ => issue350}/Issue350Tests.java | 2 +- .../feign/issues/issue362/Issue362Tests.java | 202 ++++++++++++++++++ 19 files changed, 536 insertions(+), 527 deletions(-) delete mode 100644 spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignEventPublisher.java delete mode 100644 spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignRequestContext.java delete mode 100644 spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignResponseHeadersInjector.java delete mode 100644 spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignDecoder.java delete mode 100644 spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignErrorDecoder.java delete mode 100644 spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignRetryer.java create mode 100644 spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignRetriesTests.java create mode 100644 spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientTests.java rename spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/{ => issue350}/Issue350Tests.java (99%) create mode 100644 spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue362/Issue362Tests.java diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignContextBeanPostProcessor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignContextBeanPostProcessor.java index 632a6d306..d616696ac 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignContextBeanPostProcessor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignContextBeanPostProcessor.java @@ -40,9 +40,8 @@ final class FeignContextBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { - if (bean instanceof FeignContext) { - return new TraceFeignContext(getTraceFeignObjectWrapper(), - (FeignContext) bean); + if (bean instanceof FeignContext && !(bean instanceof TraceFeignContext)) { + return new TraceFeignContext(getTraceFeignObjectWrapper(), (FeignContext) bean); } return bean; } @@ -53,7 +52,7 @@ final class FeignContextBeanPostProcessor implements BeanPostProcessor { return bean; } - TraceFeignObjectWrapper getTraceFeignObjectWrapper() { + private TraceFeignObjectWrapper getTraceFeignObjectWrapper() { if (this.traceFeignObjectWrapper == null) { this.traceFeignObjectWrapper = this.beanFactory.getBean(TraceFeignObjectWrapper.class); } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignEventPublisher.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignEventPublisher.java deleted file mode 100644 index d66ccba03..000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignEventPublisher.java +++ /dev/null @@ -1,56 +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 org.springframework.beans.factory.BeanFactory; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.Tracer; - -/** - * Abstract class for logging the client received event - * - * @author Marcin Grzejszczak - * - * @since 1.0.0 - */ -abstract class FeignEventPublisher { - - private final FeignRequestContext feignRequestContext = FeignRequestContext.getInstance(); - - protected final BeanFactory beanFactory; - private Tracer tracer; - - protected FeignEventPublisher(BeanFactory beanFactory) { - this.beanFactory = beanFactory; - } - - protected void finish() { - Span span = this.feignRequestContext.getCurrentSpan(); - if (span != null) { - span.logEvent(Span.CLIENT_RECV); - getTracer().close(span); - this.feignRequestContext.clearContext(); - } - } - - Tracer getTracer() { - if (this.tracer == null) { - this.tracer = this.beanFactory.getBean(Tracer.class); - } - return this.tracer; - } -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignRequestContext.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignRequestContext.java deleted file mode 100644 index 93bfdc694..000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignRequestContext.java +++ /dev/null @@ -1,75 +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 org.springframework.cloud.sleuth.Span; - -/** - * Class that holds the information for the span processed by the current - * request. It also knows whether the request has already been retried. - * - * The implementation works on a {@link ThreadLocal} thus is thread-safe. - * - * @author Marcin Grzejszczak - * - * @since 1.0.0 - */ -final class FeignRequestContext { - - private static final FeignRequestContext INSTANCE = new FeignRequestContext(); - - private FeignRequestContext() {} - - private static final ThreadLocal THREAD_LOCAL = new ThreadLocal<>(); - - private static final class SpanHolder { - final Span span; - final boolean retried; - - private SpanHolder(Span span, boolean retried) { - this.span = span; - this.retried = retried; - } - } - - boolean hasSpanInProcess() { - return THREAD_LOCAL.get() != null; - } - - Span getCurrentSpan() { - if (hasSpanInProcess()) { - return THREAD_LOCAL.get().span; - } - return null; - } - - boolean wasSpanRetried() { - return hasSpanInProcess() && THREAD_LOCAL.get().retried; - } - - void putSpan(Span span, boolean retried) { - THREAD_LOCAL.set(new SpanHolder(span, retried)); - } - - void clearContext() { - THREAD_LOCAL.remove(); - } - - static FeignRequestContext getInstance() { - return INSTANCE; - } -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignResponseHeadersInjector.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignResponseHeadersInjector.java deleted file mode 100644 index 9040570bb..000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignResponseHeadersInjector.java +++ /dev/null @@ -1,73 +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.util.Collection; -import java.util.HashMap; -import java.util.Map; - -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.SpanInjector; -import org.springframework.util.StringUtils; - -import static java.util.Collections.singletonList; - -/** - * Span injector that injects tracing info to - * {@link FeignResponseHeadersHolder#responseHeaders} - * - * @author Marcin Grzejszczak - * - * @since 1.0.0 - */ -class FeignResponseHeadersInjector implements SpanInjector { - - @Override - public void inject(Span span, FeignResponseHeadersHolder carrier) { - Map> headers = carrier.responseHeaders; - headersWithTraceId(span, headers); - } - - private Map> headersWithTraceId(Span span, - Map> headers) { - Map> newHeaders = new HashMap<>(); - newHeaders.putAll(headers); - if (span == null) { - setHeader(newHeaders, Span.SAMPLED_NAME, Span.SPAN_NOT_SAMPLED); - return newHeaders; - } - setHeader(newHeaders, Span.SAMPLED_NAME, span.isExportable() ? - Span.SPAN_SAMPLED : Span.SPAN_NOT_SAMPLED); - setHeader(newHeaders, Span.TRACE_ID_NAME, span.getTraceId()); - setHeader(newHeaders, Span.SPAN_ID_NAME, span.getSpanId()); - return newHeaders; - } - - void setHeader(Map> headers, String name, - String value) { - if (StringUtils.hasText(value) && !headers.containsKey(name)) { - headers.put(name, singletonList(value)); - } - } - - void setHeader(Map> headers, String name, - Long value) { - if (value != null) { - setHeader(headers, name, Span.idToHex(value)); - } - } -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/SleuthFeignBuilder.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/SleuthFeignBuilder.java index cf4513062..b8486118e 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/SleuthFeignBuilder.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/SleuthFeignBuilder.java @@ -22,7 +22,7 @@ import feign.Feign; /** * Contains {@link feign.Feign.Builder} implementation with tracing components - * that close spans on exceptions / success and continues them on retries. + * that close spans on completion of request processing. * * @author Marcin Grzejszczak * @@ -34,9 +34,6 @@ final class SleuthFeignBuilder { static Feign.Builder builder(BeanFactory beanFactory) { return Feign.builder() - .client(new TraceFeignClient(beanFactory)) - .retryer(new TraceFeignRetryer(beanFactory)) - .decoder(new TraceFeignDecoder(beanFactory)) - .errorDecoder(new TraceFeignErrorDecoder(beanFactory)); + .client(new TraceFeignClient(beanFactory)); } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/SleuthHystrixFeignBuilder.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/SleuthHystrixFeignBuilder.java index e7f9c59f1..8b69adae8 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/SleuthHystrixFeignBuilder.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/SleuthHystrixFeignBuilder.java @@ -24,7 +24,7 @@ import feign.hystrix.HystrixFeign; /** * Contains {@link Feign.Builder} implementation that delegates execution * {@link HystrixFeign} with tracing components - * that close spans on exceptions / success and continues them on retries. + * that close spans upon completion of request processing. * * @author Marcin Grzejszczak * @@ -36,9 +36,6 @@ final class SleuthHystrixFeignBuilder { static Feign.Builder builder(BeanFactory beanFactory) { return HystrixFeign.builder() - .client(new TraceFeignClient(beanFactory)) - .retryer(new TraceFeignRetryer(beanFactory)) - .decoder(new TraceFeignDecoder(beanFactory)) - .errorDecoder(new TraceFeignErrorDecoder(beanFactory)); + .client(new TraceFeignClient(beanFactory)); } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClient.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClient.java index d0807d197..a29623769 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClient.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClient.java @@ -17,16 +17,19 @@ package org.springframework.cloud.sleuth.instrument.web.client.feign; import java.io.IOException; +import java.lang.invoke.MethodHandles; import java.net.URI; -import java.util.Objects; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.BeanFactory; +import org.springframework.cloud.sleuth.Span; +import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.instrument.web.HttpTraceKeysInjector; import feign.Client; import feign.Request; import feign.Response; -import feign.RetryableException; /** * A Feign Client that closes a Span if there is no response body. In other cases Span @@ -36,42 +39,38 @@ import feign.RetryableException; * * @since 1.0.0 */ -final class TraceFeignClient extends FeignEventPublisher implements Client { +final class TraceFeignClient implements Client { + + private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); private final Client delegate; private HttpTraceKeysInjector keysInjector; + private final BeanFactory beanFactory; + private Tracer tracer; TraceFeignClient(BeanFactory beanFactory) { - super(beanFactory); + this.beanFactory = beanFactory; this.delegate = new Client.Default(null, null); } TraceFeignClient(BeanFactory beanFactory, Client delegate) { - super(beanFactory); this.delegate = delegate; + this.beanFactory = beanFactory; } @Override public Response execute(Request request, Request.Options options) throws IOException { - Response response; try { addRequestTags(request); - response = this.delegate.execute(request, options); - } - catch (RetryableException | IOException e) { - // IOException will be wrapped into a RetryableException in the caller + Response response = this.delegate.execute(request, options); + logCr(); + return response; + } catch (RuntimeException | IOException e) { + logError(e); throw e; + } finally { + closeSpan(); } - catch (RuntimeException e) { - // Any other exception is going to be propagated so we need to tidy up - finish(); - throw e; - } - if (response != null && response.body() == null || (response.body() != null - && Objects.equals(response.body().length(), 0))) { - finish(); - } - return response; } /** @@ -83,10 +82,42 @@ final class TraceFeignClient extends FeignEventPublisher implements Client { request.method(), request.headers()); } - HttpTraceKeysInjector getKeysInjector() { + private HttpTraceKeysInjector getKeysInjector() { if (this.keysInjector == null) { this.keysInjector = this.beanFactory.getBean(HttpTraceKeysInjector.class); } return this.keysInjector; } + + private void closeSpan() { + Span span = getTracer().getCurrentSpan(); + if (span != null) { + log.debug("Closing Feign span " + span); + getTracer().close(span); + } + } + + private void logCr() { + Span span = getTracer().getCurrentSpan(); + if (span != null) { + log.debug("Closing Feign span and logging CR" + span); + span.logEvent(Span.CLIENT_RECV); + } + } + + private void logError(Exception e) { + Span span = getTracer().getCurrentSpan(); + if (span != null) { + String message = e.getMessage() != null ? e.getMessage() : e.toString(); + log.debug("Appending exception [" + message + "] to span " + span); + getTracer().addTag("error", message); + } + } + + private Tracer getTracer() { + if (this.tracer == null) { + this.tracer = this.beanFactory.getBean(Tracer.class); + } + return this.tracer; + } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientAutoConfiguration.java index 4f74db019..10da31b1b 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientAutoConfiguration.java @@ -16,37 +16,23 @@ package org.springframework.cloud.sleuth.instrument.web.client.feign; -import java.io.IOException; -import java.lang.reflect.Type; - import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.ObjectFactory; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureBefore; 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.boot.autoconfigure.web.HttpMessageConverters; import org.springframework.cloud.netflix.feign.FeignAutoConfiguration; -import org.springframework.cloud.netflix.feign.support.ResponseEntityDecoder; -import org.springframework.cloud.netflix.feign.support.SpringDecoder; -import org.springframework.cloud.sleuth.SpanInjector; import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.instrument.hystrix.SleuthHystrixAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.Scope; import feign.Client; import feign.Feign; -import feign.FeignException; import feign.RequestInterceptor; -import feign.RequestTemplate; -import feign.Response; -import feign.codec.Decoder; /** * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration @@ -63,9 +49,6 @@ import feign.codec.Decoder; @AutoConfigureAfter(SleuthHystrixAutoConfiguration.class) public class TraceFeignClientAutoConfiguration { - @Autowired - private ObjectFactory messageConverters; - @Bean @Scope("prototype") @ConditionalOnClass(name = "com.netflix.hystrix.HystrixCommand") @@ -102,44 +85,12 @@ public class TraceFeignClientAutoConfiguration { return new TraceFeignObjectWrapper(beanFactory); } - @Bean - @Primary - Decoder feignDecoder(BeanFactory beanFactory) { - return new TraceFeignDecoder(beanFactory, - new ResponseEntityDecoder(new SpringDecoder(this.messageConverters)) { - @Override - public Object decode(Response response, Type type) - throws IOException, FeignException { - FeignRequestContext feignRequestContext = FeignRequestContext - .getInstance(); - FeignResponseHeadersHolder feignResponseHeadersHolder = new FeignResponseHeadersHolder( - response.headers()); - feignResponseHeadersInjector().inject( - feignRequestContext.getCurrentSpan(), - feignResponseHeadersHolder); - return super.decode( - Response.create(response.status(), response.reason(), - feignResponseHeadersHolder.responseHeaders, - response.body()), - type); - } - }); - } - /** * Sleuth {@link feign.RequestInterceptor} that either starts a new Span or continues * an existing one if a retry takes place. */ @Bean RequestInterceptor traceIdRequestInterceptor(Tracer tracer) { - return new TraceFeignRequestInterceptor(tracer, feignRequestTemplateInjector()); - } - - private SpanInjector feignRequestTemplateInjector() { - return new FeignRequestTemplateInjector(); - } - - private SpanInjector feignResponseHeadersInjector() { - return new FeignResponseHeadersInjector(); + return new TraceFeignRequestInterceptor(tracer, new FeignRequestTemplateInjector()); } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignDecoder.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignDecoder.java deleted file mode 100644 index e4d130154..000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignDecoder.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2013-2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth.instrument.web.client.feign; - -import java.io.IOException; -import java.lang.reflect.Type; - -import org.springframework.beans.factory.BeanFactory; - -import feign.FeignException; -import feign.Response; -import feign.codec.Decoder; - -/** - * A decoder that closes a span upon decoding the response. - * - * @author Marcin Grzejszczak - * - * @since 1.0.0 - */ -final class TraceFeignDecoder extends FeignEventPublisher implements Decoder { - - private final Decoder delegate; - - TraceFeignDecoder(BeanFactory beanFactory) { - super(beanFactory); - this.delegate = new Decoder.Default(); - } - - TraceFeignDecoder(BeanFactory beanFactory, Decoder delegate) { - super(beanFactory); - this.delegate = delegate; - } - - @Override - public Object decode(Response response, Type type) - throws IOException, FeignException { - try { - return this.delegate.decode(response, type); - } finally { - finish(); - } - } -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignErrorDecoder.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignErrorDecoder.java deleted file mode 100644 index ec634fe8d..000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignErrorDecoder.java +++ /dev/null @@ -1,52 +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 org.springframework.beans.factory.BeanFactory; - -import feign.Response; -import feign.codec.ErrorDecoder; - -/** - * An {@link ErrorDecoder} that closes a span before returning the exception type. - * - * @author Marcin Grzejszczak - * - * @since 1.0.0 - */ -final class TraceFeignErrorDecoder extends FeignEventPublisher implements ErrorDecoder { - - private final ErrorDecoder delegate; - - TraceFeignErrorDecoder(BeanFactory beanFactory) { - super(beanFactory); - this.delegate = new ErrorDecoder.Default(); - } - - TraceFeignErrorDecoder(BeanFactory beanFactory, ErrorDecoder delegate) { - super(beanFactory); - this.delegate = delegate; - } - - @Override public Exception decode(String methodKey, Response response) { - try { - return this.delegate.decode(methodKey, response); - } finally { - finish(); - } - } -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignObjectWrapper.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignObjectWrapper.java index be7bc6a48..dae2e6b06 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignObjectWrapper.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignObjectWrapper.java @@ -3,9 +3,6 @@ package org.springframework.cloud.sleuth.instrument.web.client.feign; import org.springframework.beans.factory.BeanFactory; import feign.Client; -import feign.Retryer; -import feign.codec.Decoder; -import feign.codec.ErrorDecoder; /** * Class that wraps Feign related classes into their Trace representative @@ -22,14 +19,8 @@ final class TraceFeignObjectWrapper { } Object wrap(Object bean) { - if (bean instanceof Decoder && !(bean instanceof TraceFeignDecoder)) { - return new TraceFeignDecoder(this.beanFactory, (Decoder) bean); - } else if (bean instanceof Retryer && !(bean instanceof TraceFeignRetryer)) { - return new TraceFeignRetryer(this.beanFactory, (Retryer) bean); - } else if (bean instanceof Client && !(bean instanceof TraceFeignClient)) { + if (bean instanceof Client && !(bean instanceof TraceFeignClient)) { return new TraceFeignClient(this.beanFactory, (Client) bean); - } else if (bean instanceof ErrorDecoder && !(bean instanceof TraceFeignErrorDecoder)) { - return new TraceFeignErrorDecoder(this.beanFactory, (ErrorDecoder) bean); } return bean; } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignRequestInterceptor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignRequestInterceptor.java index b48fa9f2e..425e5bda6 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignRequestInterceptor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignRequestInterceptor.java @@ -26,8 +26,7 @@ import feign.RequestInterceptor; import feign.RequestTemplate; /** - * A request interceptor that sets tracing information in the headers - * and retrieves the span from the current {@link FeignRequestContext}. + * A request interceptor that sets tracing information in the headers. * * @author Marcin Grzejszczak * @@ -37,7 +36,6 @@ final class TraceFeignRequestInterceptor implements RequestInterceptor { private final Tracer tracer; private final SpanInjector spanInjector; - private final FeignRequestContext feignRequestContext = FeignRequestContext.getInstance(); TraceFeignRequestInterceptor(Tracer tracer, SpanInjector spanInjector) { @@ -48,33 +46,16 @@ final class TraceFeignRequestInterceptor implements RequestInterceptor { @Override public void apply(RequestTemplate template) { String spanName = getSpanName(template); - Span span = getSpan(spanName); + Span span = this.tracer.createSpan(spanName); this.spanInjector.inject(span, template); span.logEvent(Span.CLIENT_SEND); } - protected String getSpanName(RequestTemplate template) { + private String getSpanName(RequestTemplate template) { URI uri = URI.create(template.url()); return uriScheme(uri) + ":" + uri.getPath(); } - /** - * Depending on the presence of a Span in context, either starts a new Span - * or continues an existing one. - */ - protected Span getSpan(String spanName) { - if (!this.feignRequestContext.hasSpanInProcess()) { - Span span = this.tracer.createSpan(spanName); - this.feignRequestContext.putSpan(span, false); - return span; - } else { - if (this.feignRequestContext.wasSpanRetried()) { - return this.tracer.continueSpan(this.feignRequestContext.getCurrentSpan()); - } - } - return this.tracer.createSpan(spanName); - } - private String uriScheme(URI uri) { return uri.getScheme() == null ? "http" : uri.getScheme(); } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignRetryer.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignRetryer.java deleted file mode 100644 index 4fa8cbe4a..000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignRetryer.java +++ /dev/null @@ -1,76 +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 org.springframework.beans.factory.BeanFactory; -import org.springframework.cloud.sleuth.Tracer; - -import feign.RetryableException; -import feign.Retryer; - -/** - * Execution of this retryer means that an exception occurred while trying to send the - * request. In that case we need to put information about this span into the - * {@link FeignRequestContext} in order for the {@link feign.RequestInterceptor} to know - * that it should be continued or a new one should be created. - * - * @author Marcin Grzejszczak - * - * @since 1.0.0 - */ -final class TraceFeignRetryer implements Retryer { - - private final BeanFactory beanFactory; - private Tracer tracer; - private final FeignRequestContext feignRequestContext = FeignRequestContext - .getInstance(); - private final Retryer delegate; - - TraceFeignRetryer(BeanFactory beanFactory) { - this(beanFactory, new Retryer.Default()); - } - - TraceFeignRetryer(BeanFactory beanFactory, Retryer delegate) { - this.beanFactory = beanFactory; - this.delegate = delegate; - } - - @Override - public void continueOrPropagate(RetryableException e) { - try { - this.feignRequestContext.putSpan(getTracer().getCurrentSpan(), true); - getTracer().getCurrentSpan().logEvent("feign.retry"); - this.delegate.continueOrPropagate(e); - } - catch (RetryableException e2) { - getTracer().close(getTracer().getCurrentSpan()); - throw e2; - } - } - - @Override - public Retryer clone() { - return new TraceFeignRetryer(this.beanFactory, this.delegate.clone()); - } - - Tracer getTracer() { - if (this.tracer == null) { - this.tracer = this.beanFactory.getBean(Tracer.class); - } - return this.tracer; - } -} \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/SpanAssert.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/SpanAssert.java index 3503e64df..f0f5bd5ef 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/SpanAssert.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/SpanAssert.java @@ -146,6 +146,18 @@ public class SpanAssert extends AbstractAssert { return this; } + public SpanAssert hasNotLoggedAnEvent(String event) { + isNotNull(); + if (this.actual.logs().stream().map(org.springframework.cloud.sleuth.Log::getEvent) + .filter(s -> s.equals(event)).findAny().isPresent()) { + String message = String.format("Expected span NOT to have the event with event value <%s>. " + + "Found logs are <%s>", event, this.actual.logs()); + log.error(message); + failWithMessage(message); + } + return this; + } + public SpanAssert isExportable() { isNotNull(); if (!this.actual.isExportable()) { diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignRetriesTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignRetriesTests.java new file mode 100644 index 000000000..05365dab8 --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignRetriesTests.java @@ -0,0 +1,150 @@ +/* + * 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.io.IOException; +import java.nio.charset.Charset; +import java.util.HashMap; +import java.util.Random; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.BDDMockito; +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.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.log.NoOpSpanLogger; +import org.springframework.cloud.sleuth.sampler.AlwaysSampler; +import org.springframework.cloud.sleuth.trace.DefaultTracer; +import org.springframework.cloud.sleuth.trace.TestSpanContextHolder; +import org.springframework.cloud.sleuth.util.ArrayListSpanAccumulator; +import org.springframework.cloud.sleuth.util.ExceptionUtils; + +import feign.Client; +import feign.Feign; +import feign.FeignException; +import feign.RequestInterceptor; +import feign.RequestLine; +import feign.Response; +import okhttp3.mockwebserver.MockWebServer; + +import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown; +import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; + +/** + * @author Marcin Grzejszczak + */ +@RunWith(MockitoJUnitRunner.class) +public class FeignRetriesTests { + + @Rule + public final MockWebServer server = new MockWebServer(); + + @Mock BeanFactory beanFactory; + + ArrayListSpanAccumulator spanAccumulator = new ArrayListSpanAccumulator(); + Tracer tracer = new DefaultTracer(new AlwaysSampler(), new Random(), new DefaultSpanNamer(), + new NoOpSpanLogger(), this.spanAccumulator); + FeignRequestTemplateInjector injector = new FeignRequestTemplateInjector(); + TraceFeignRequestInterceptor interceptor = new TraceFeignRequestInterceptor(tracer, injector); + + @Before + @After + public void setup() { + ExceptionUtils.setFail(true); + TestSpanContextHolder.removeCurrentSpan(); + BDDMockito.given(this.beanFactory.getBean(HttpTraceKeysInjector.class)) + .willReturn(new HttpTraceKeysInjector(this.tracer, new TraceKeys())); + BDDMockito.given(this.beanFactory.getBean(Tracer.class)).willReturn(this.tracer); + } + + @Test + public void testRetriedWhenExceededNumberOfRetries() throws Exception { + Client client = (request, options) -> { + throw new IOException(); + }; + String url = "http://localhost:" + server.getPort(); + + TestInterface api = + Feign.builder() + .client(new TraceFeignClient(beanFactory, client)) + .requestInterceptor(interceptor) + .target(TestInterface.class, url); + + try { + api.decodedPost(); + failBecauseExceptionWasNotThrown(FeignException.class); + } catch (FeignException e) { } + + then(this.tracer.getCurrentSpan()).isNull(); + then(ExceptionUtils.getLastException()).isNull(); + } + + @Test + public void testRetriedWhenRequestEventuallyIsSent() throws Exception { + String url = "http://localhost:" + server.getPort(); + final AtomicInteger atomicInteger = new AtomicInteger(); + // Client to simulate a retry scenario + Client client = (request, options) -> { + // we simulate an exception only for the first request + if (atomicInteger.get() == 1) { + throw new IOException(); + } else { + // with the second retry (first retry) we send back good result + return Response.create(200, "OK", new HashMap<>(), + "OK", Charset.defaultCharset()); + } + }; + RequestInterceptor requestInterceptor = template -> { + atomicInteger.incrementAndGet(); + interceptor.apply(template); + }; + TestInterface api = + Feign.builder() + .client(new TraceFeignClient(beanFactory, client)) + .requestInterceptor(requestInterceptor) + .target(TestInterface.class, url); + + then(api.decodedPost()).isEqualTo("OK"); + // request interception should take place only twice (1st request & 2nd retry) + then(atomicInteger.get()).isEqualTo(2); + then(this.tracer.getCurrentSpan()).isNull(); + then(ExceptionUtils.getLastException()).isNull(); + then(this.spanAccumulator.getSpans().get(0)) + .hasNotLoggedAnEvent(Span.CLIENT_RECV) + .hasATag("error", "java.io.IOException"); + then(this.spanAccumulator.getSpans().get(1)) + .hasLoggedAnEvent(Span.CLIENT_RECV); + } + + interface TestInterface { + + @RequestLine("POST /") + String decodedPost(); + } + + +} diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientTests.java new file mode 100644 index 000000000..ba6905f22 --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientTests.java @@ -0,0 +1,105 @@ +/* + * 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.io.IOException; +import java.nio.charset.Charset; +import java.util.HashMap; +import java.util.Random; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.BDDMockito; +import org.mockito.InjectMocks; +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.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.log.NoOpSpanLogger; +import org.springframework.cloud.sleuth.sampler.AlwaysSampler; +import org.springframework.cloud.sleuth.trace.DefaultTracer; +import org.springframework.cloud.sleuth.trace.TestSpanContextHolder; +import org.springframework.cloud.sleuth.util.ArrayListSpanAccumulator; +import org.springframework.cloud.sleuth.util.ExceptionUtils; + +import feign.Client; +import feign.Request; +import feign.Response; + +import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; + +/** + * @author Marcin Grzejszczak + */ +@RunWith(MockitoJUnitRunner.class) +public class TraceFeignClientTests { + + ArrayListSpanAccumulator spanAccumulator = new ArrayListSpanAccumulator(); + @Mock BeanFactory beanFactory; + Tracer tracer = new DefaultTracer(new AlwaysSampler(), new Random(), new DefaultSpanNamer(), + new NoOpSpanLogger(), this.spanAccumulator); + @Mock Client client; + @InjectMocks TraceFeignClient traceFeignClient; + + @Before + @After + public void setup() { + TestSpanContextHolder.removeCurrentSpan(); + ExceptionUtils.setFail(true); + BDDMockito.given(this.beanFactory.getBean(HttpTraceKeysInjector.class)) + .willReturn(new HttpTraceKeysInjector(this.tracer, new TraceKeys())); + BDDMockito.given(this.beanFactory.getBean(Tracer.class)).willReturn(this.tracer); + } + + @Test + public void should_log_cr_when_response_successful() throws IOException { + this.tracer.createSpan("foo"); + Response response = this.traceFeignClient.execute( + Request.create("GET", "http://foo", new HashMap<>(), "".getBytes(), + Charset.defaultCharset()), new Request.Options()); + + then(this.tracer.getCurrentSpan()).isNull(); + then(this.spanAccumulator.getSpans().get(0)).hasLoggedAnEvent(Span.CLIENT_RECV); + } + + @Test + public void should_log_error_when_exception_thrown() throws IOException { + this.tracer.createSpan("foo"); + BDDMockito.given(this.client.execute(BDDMockito.any(), BDDMockito.any())) + .willThrow(new RuntimeException("exception has occurred")); + + try { + this.traceFeignClient.execute( + Request.create("GET", "http://foo", new HashMap<>(), "".getBytes(), + Charset.defaultCharset()), new Request.Options()); + SleuthAssertions.fail("Exception should have been thrown"); + } catch (Exception e) {} + + then(this.tracer.getCurrentSpan()).isNull(); + then(this.spanAccumulator.getSpans().get(0)) + .hasNotLoggedAnEvent(Span.CLIENT_RECV) + .hasATag("error", "exception has occurred"); + } + +} \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignObjectWrapperTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignObjectWrapperTests.java index 383bf71f3..946ac1201 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignObjectWrapperTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignObjectWrapperTests.java @@ -1,9 +1,5 @@ package org.springframework.cloud.sleuth.instrument.web.client.feign; -import feign.Client; -import feign.Retryer; -import feign.codec.Decoder; -import feign.codec.ErrorDecoder; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -13,6 +9,8 @@ import org.mockito.runners.MockitoJUnitRunner; import org.springframework.beans.factory.BeanFactory; import org.springframework.cloud.sleuth.Tracer; +import feign.Client; + import static org.assertj.core.api.BDDAssertions.then; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; @@ -32,26 +30,11 @@ public class TraceFeignObjectWrapperTests { given(this.beanFactory.getBean(Tracer.class)).willReturn(this.tracer); } - @Test - public void should_wrap_a_decoder_into_trace_decoder() throws Exception { - then(this.traceFeignObjectWrapper.wrap(mock(Decoder.class))).isExactlyInstanceOf(TraceFeignDecoder.class); - } - - @Test - public void should_wrap_a_retryer_into_trace_retryer() throws Exception { - then(this.traceFeignObjectWrapper.wrap(mock(Retryer.class))).isExactlyInstanceOf(TraceFeignRetryer.class); - } - @Test public void should_wrap_a_client_into_trace_client() throws Exception { then(this.traceFeignObjectWrapper.wrap(mock(Client.class))).isExactlyInstanceOf(TraceFeignClient.class); } - @Test - public void should_wrap_a_error_decoder_into_trace_error_decoder() throws Exception { - then(this.traceFeignObjectWrapper.wrap(mock(ErrorDecoder.class))).isExactlyInstanceOf(TraceFeignErrorDecoder.class); - } - @Test public void should_not_wrap_a_bean_that_is_not_feign_related() throws Exception { String notFeignRelatedObject = "object"; diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/Issue350Tests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue350/Issue350Tests.java similarity index 99% rename from spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/Issue350Tests.java rename to spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue350/Issue350Tests.java index fd26a1b00..da379bd46 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/Issue350Tests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue350/Issue350Tests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.cloud.sleuth.instrument.web.client.feign.issues; +package org.springframework.cloud.sleuth.instrument.web.client.feign.issues.issue350; import java.util.concurrent.ExecutionException; diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue362/Issue362Tests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue362/Issue362Tests.java new file mode 100644 index 000000000..ef8f23bcc --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue362/Issue362Tests.java @@ -0,0 +1,202 @@ +/* + * 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.issues.issue362; + +import java.util.Date; +import java.util.concurrent.ExecutionException; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.boot.test.WebIntegrationTest; +import org.springframework.cloud.netflix.feign.EnableFeignClients; +import org.springframework.cloud.netflix.feign.FeignClient; +import org.springframework.cloud.sleuth.assertions.SleuthAssertions; +import org.springframework.cloud.sleuth.sampler.AlwaysSampler; +import org.springframework.cloud.sleuth.util.ExceptionUtils; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.client.RestTemplate; + +import feign.Client; +import feign.Logger; +import feign.Response; +import feign.RetryableException; +import feign.Retryer; +import feign.codec.ErrorDecoder; + +import static org.assertj.core.api.Assertions.fail; +import static org.assertj.core.api.BDDAssertions.then; + +/** + * @author Marcin Grzejszczak + */ +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(Application.class) +@WebIntegrationTest +@TestPropertySource(properties = {"ribbon.eureka.enabled=false", "feign.hystrix.enabled=false", "server.port=9998"}) +public class Issue362Tests { + + RestTemplate template = new RestTemplate(); + + @Before + public void setup() { + ExceptionUtils.setFail(true); + } + + @Test + public void should_successfully_work_with_custom_error_decoder_when_sending_successful_request() { + String securedURl = "http://localhost:9998/sleuth/test-ok"; + + ResponseEntity response = this.template.getForEntity(securedURl, String.class); + + SleuthAssertions.then(response.getBody()).isEqualTo("I'm OK"); + then(ExceptionUtils.getLastException()).isNull(); + } + + @Test + public void should_successfully_work_with_custom_error_decoder_when_sending_failing_request() { + String securedURl = "http://localhost:9998/sleuth/test-not-ok"; + + try { + this.template.getForEntity(securedURl, String.class); + fail("should propagate an exception"); + } catch (Exception e) { } + + then(ExceptionUtils.getLastException()).isNull(); + } +} + +@Configuration +@EnableAutoConfiguration +@EnableFeignClients(basePackageClasses = { + SleuthTestController.class}) +class Application { + + @Bean + public ServiceTestController serviceTestController() { + return new ServiceTestController(); + } + + @Bean + public SleuthTestController sleuthTestController() { + return new SleuthTestController(); + } + + @Bean + public Logger.Level feignLoggerLevel() { + return feign.Logger.Level.FULL; + } + + @Bean + public AlwaysSampler defaultSampler() { + return new AlwaysSampler(); + } + + @Bean + public Client client() { + return new Client.Default(null, null); + } +} + +@Configuration +class CustomConfig { + + @Bean + public ErrorDecoder errorDecoder() { + return new CustomErrorDecoder(); + } + + @Bean + public Retryer retryer() { + return new Retryer.Default(); + } + + public static class CustomErrorDecoder extends ErrorDecoder.Default { + + public CustomErrorDecoder() { + } + + @Override + public Exception decode(String methodKey, Response response) { + if (response.status() == 409) { + return new RetryableException("Article not Ready", new Date()); + } else { + return super.decode(methodKey, response); + } + } + + } +} + +@FeignClient(value="myFeignClient", url="http://localhost:9998", + configuration = CustomConfig.class) +interface MyFeignClient { + + @RequestMapping("/service/ok") + String ok(); + + @RequestMapping("/service/not-ok") + String exp(); +} + +@RestController +@RequestMapping(path = "/service") +class ServiceTestController { + + @RequestMapping("/ok") + public String ok() throws InterruptedException, ExecutionException { + String result = "I'm OK"; + return result; + } + + @RequestMapping("/not-ok") + @ResponseStatus(HttpStatus.CONFLICT) + public String notOk() throws InterruptedException, ExecutionException { + return "Not OK"; + } +} + +@RestController +@RequestMapping(path = "/sleuth") +class SleuthTestController { + + @Autowired + private MyFeignClient myFeignClient; + + @RequestMapping("/test-ok") + public String ok() throws InterruptedException, ExecutionException { + String result = myFeignClient.ok(); + return result; + } + + @RequestMapping("/test-not-ok") + public String notOk() throws InterruptedException, ExecutionException { + String result = myFeignClient.exp(); + return result; + } +} \ No newline at end of file From 35652a43d983e1f4c22b4b5eaa3a9b01958cb17a Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Mon, 11 Jul 2016 13:21:31 +0200 Subject: [PATCH 039/235] Removed sending the tracing information back in the response (#329) fixes #327 --- .../web/HttpServletResponseInjector.java | 44 ------------------- .../sleuth/instrument/web/TraceFilter.java | 21 +-------- .../web/TraceWebAutoConfiguration.java | 11 +---- ...ceFilterAlwaysSamplerIntegrationTests.java | 8 +--- .../web/TraceFilterCustomExtractorTests.java | 9 +--- .../web/TraceFilterIntegrationTests.java | 19 +++----- .../TraceFilterMockChainIntegrationTests.java | 4 +- .../instrument/web/TraceFilterTests.java | 33 +++++++------- .../instrument/web/client/WebClientTests.java | 11 ++--- .../common/AbstractMvcIntegrationTest.java | 3 -- 10 files changed, 33 insertions(+), 130 deletions(-) delete mode 100644 spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/HttpServletResponseInjector.java diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/HttpServletResponseInjector.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/HttpServletResponseInjector.java deleted file mode 100644 index e0f7a92fe..000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/HttpServletResponseInjector.java +++ /dev/null @@ -1,44 +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 javax.servlet.http.HttpServletResponse; - -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.SpanInjector; - -/** - * Span injector that injects tracing info to {@link HttpServletResponse} - * - * @author Marcin Grzejszczak - * - * @since 1.0.0 - */ -class HttpServletResponseInjector implements SpanInjector { - - @Override - public void inject(Span span, HttpServletResponse carrier) { - if (span == null) { - return; - } - if (!carrier.containsHeader(Span.SPAN_ID_NAME)) { - carrier.addHeader(Span.SPAN_ID_NAME, Span.idToHex(span.getSpanId())); - carrier.addHeader(Span.TRACE_ID_NAME, Span.idToHex(span.getTraceId())); - } - } - -} 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 30efa50c4..a593922a4 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 @@ -32,7 +32,6 @@ 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.SpanReporter; import org.springframework.cloud.sleuth.TraceKeys; import org.springframework.cloud.sleuth.Tracer; @@ -45,8 +44,6 @@ import org.springframework.web.context.request.async.WebAsyncUtils; import org.springframework.web.filter.GenericFilterBean; import org.springframework.web.util.UrlPathHelper; -import static org.springframework.util.StringUtils.hasText; - /** * Filter that takes the value of the {@link Span#SPAN_ID_NAME} and * {@link Span#TRACE_ID_NAME} header from either request or response and uses them to @@ -89,29 +86,25 @@ public class TraceFilter extends GenericFilterBean { private final Pattern skipPattern; private final SpanReporter spanReporter; private final SpanExtractor spanExtractor; - private final SpanInjector spanInjector; private final HttpTraceKeysInjector httpTraceKeysInjector; private UrlPathHelper urlPathHelper = new UrlPathHelper(); public TraceFilter(Tracer tracer, TraceKeys traceKeys, SpanReporter spanReporter, SpanExtractor spanExtractor, - SpanInjector spanInjector, HttpTraceKeysInjector httpTraceKeysInjector) { this(tracer, traceKeys, Pattern.compile(DEFAULT_SKIP_PATTERN), spanReporter, - spanExtractor, spanInjector, httpTraceKeysInjector); + spanExtractor, httpTraceKeysInjector); } public TraceFilter(Tracer tracer, TraceKeys traceKeys, Pattern skipPattern, SpanReporter spanReporter, SpanExtractor spanExtractor, - SpanInjector spanInjector, HttpTraceKeysInjector httpTraceKeysInjector) { this.tracer = tracer; this.traceKeys = traceKeys; this.skipPattern = skipPattern; this.spanReporter = spanReporter; this.spanExtractor = spanExtractor; - this.spanInjector = spanInjector; this.httpTraceKeysInjector = httpTraceKeysInjector; } @@ -138,7 +131,6 @@ public class TraceFilter extends GenericFilterBean { processErrorRequest(filterChain, request, response, spanFromRequest); return; } - addToResponseIfNotPresent(response, Span.SAMPLED_NAME, skip ? Span.SPAN_NOT_SAMPLED : Span.SPAN_SAMPLED); String name = HTTP_COMPONENT + ":" + uri; try { spanFromRequest = createSpan(request, skip, spanFromRequest, name); @@ -150,9 +142,6 @@ public class TraceFilter extends GenericFilterBean { } Throwable exception = null; try { - this.spanInjector.inject(spanFromRequest, response); - // Add headers before filter chain in case one of the filters flushes the - // response... filterChain.doFilter(request, response); } catch (Throwable e) { exception = e; @@ -166,7 +155,6 @@ public class TraceFilter extends GenericFilterBean { return; } spanFromRequest = createSpanIfRequestNotHandled(request, spanFromRequest, name, skip); - addToResponseIfNotPresent(response, Span.SAMPLED_NAME, skip ? Span.SPAN_NOT_SAMPLED : Span.SPAN_SAMPLED); detachOrCloseSpans(request, response, spanFromRequest, exception); } } @@ -364,13 +352,6 @@ public class TraceFilter extends GenericFilterBean { } } - private void addToResponseIfNotPresent(HttpServletResponse response, String name, - String value) { - if (!hasText(response.getHeader(name))) { - response.addHeader(name, value); - } - } - protected boolean isAsyncStarted(HttpServletRequest request) { return WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted(); } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java index 64bb9819a..e2b00eeaf 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java @@ -16,7 +16,6 @@ package org.springframework.cloud.sleuth.instrument.web; import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import java.util.regex.Pattern; import org.springframework.beans.factory.BeanFactory; @@ -32,7 +31,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplicat 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.SpanInjector; import org.springframework.cloud.sleuth.SpanNamer; import org.springframework.cloud.sleuth.SpanReporter; import org.springframework.cloud.sleuth.TraceKeys; @@ -89,7 +87,6 @@ public class TraceWebAutoConfiguration { public FilterRegistrationBean traceWebFilter(Tracer tracer, TraceKeys traceKeys, SkipPatternProvider skipPatternProvider, SpanReporter spanReporter, SpanExtractor spanExtractor, - SpanInjector spanInjector, HttpTraceKeysInjector httpTraceKeysInjector, TraceFilter traceFilter) { FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(traceFilter); filterRegistrationBean.setDispatcherTypes(ASYNC, ERROR, FORWARD, INCLUDE, REQUEST); @@ -100,10 +97,9 @@ public class TraceWebAutoConfiguration { public TraceFilter traceFilter(Tracer tracer, TraceKeys traceKeys, SkipPatternProvider skipPatternProvider, SpanReporter spanReporter, SpanExtractor spanExtractor, - SpanInjector spanInjector, HttpTraceKeysInjector httpTraceKeysInjector) { return new TraceFilter(tracer, traceKeys, skipPatternProvider.skipPattern(), - spanReporter, spanExtractor, spanInjector, httpTraceKeysInjector); + spanReporter, spanExtractor, httpTraceKeysInjector); } @Bean @@ -112,11 +108,6 @@ public class TraceWebAutoConfiguration { return new HttpServletRequestExtractor(skipPatternProvider.skipPattern()); } - @Bean - public SpanInjector httpServletResponseSpanInjector() { - return new HttpServletResponseInjector(); - } - @Configuration @ConditionalOnClass(ManagementServerProperties.class) @ConditionalOnMissingBean(SkipPatternProvider.class) 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 69de76f39..3e799e838 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 @@ -42,7 +42,6 @@ public class TraceFilterAlwaysSamplerIntegrationTests extends AbstractMvcIntegra MvcResult mvcResult = whenSentPingWithTraceId(expectedTraceId); - then(tracingHeaderFrom(mvcResult)).isEqualTo(expectedTraceId); then(span.isExportable()); } @@ -52,14 +51,13 @@ public class TraceFilterAlwaysSamplerIntegrationTests extends AbstractMvcIntegra MvcResult mvcResult = whenSentPingWithTraceIdAndNotSampling(expectedTraceId); - then(tracingHeaderFrom(mvcResult)).isEqualTo(expectedTraceId); then(span.isExportable()).isFalse(); } @Override protected void configureMockMvcBuilder(DefaultMockMvcBuilder mockMvcBuilder) { mockMvcBuilder.addFilters(new TraceFilter(this.tracer, this.traceKeys, - new NoOpSpanReporter(), this.spanExtractor, this.spanInjector, + new NoOpSpanReporter(), this.spanExtractor, this.httpTraceKeysInjector)); } @@ -87,10 +85,6 @@ public class TraceFilterAlwaysSamplerIntegrationTests extends AbstractMvcIntegra return this.mockMvc.perform(request).andReturn(); } - private Long tracingHeaderFrom(MvcResult mvcResult) { - return Span.hexToId(mvcResult.getResponse().getHeader(Span.TRACE_ID_NAME)); - } - @DefaultTestAutoConfiguration @RestController @Configuration diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterCustomExtractorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterCustomExtractorTests.java index 400aa722c..652d961fa 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterCustomExtractorTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterCustomExtractorTests.java @@ -19,7 +19,6 @@ package org.springframework.cloud.sleuth.instrument.web; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.net.URI; -import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Random; @@ -84,20 +83,16 @@ public class TraceFilterCustomExtractorTests { .header("mySpanId", Span.idToHex(spanId)).build(); @SuppressWarnings("rawtypes") - ResponseEntity requestHeaders = this.restTemplate.exchange(requestEntity, + ResponseEntity responseHeaders = this.restTemplate.exchange(requestEntity, Map.class); await().until(() -> then(this.accumulator.getSpans().stream().filter( span -> span.getSpanId() == spanId).findFirst().get()) .hasTraceIdEqualTo(traceId)); - then(requestHeaders.getBody()) + then(responseHeaders.getBody()) .containsEntry("correlationid", Span.idToHex(traceId)) .containsEntry("myspanid", Span.idToHex(spanId)) .as("input request headers"); - then(requestHeaders.getHeaders()) - .containsEntry("correlationId", - Collections.singletonList(Span.idToHex(traceId))) - .containsKey("mySpanId").as("response headers"); } @Configuration diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterIntegrationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterIntegrationTests.java index 4b645eea6..ef32f43db 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterIntegrationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterIntegrationTests.java @@ -59,9 +59,8 @@ public class TraceFilterIntegrationTests extends AbstractMvcIntegrationTest { @Test public void should_create_and_return_trace_in_HTTP_header() throws Exception { - MvcResult mvcResult = whenSentPingWithoutTracingData(); + whenSentPingWithoutTracingData(); - then(tracingHeaderFrom(mvcResult)).isNotNull(); Span parentSpan = this.spanAccumulator.getSpans().stream().filter( span -> span.getSpanId() == TraceFilterIntegrationTests.span.getParents().get(0)) .findFirst().get(); @@ -75,7 +74,9 @@ public class TraceFilterIntegrationTests extends AbstractMvcIntegrationTest { throws Exception { MvcResult mvcResult = whenSentInfoWithTraceId(new Random().nextLong()); - then(notSampledHeaderIsPresent(mvcResult)).isEqualTo(true); + // https://github.com/spring-cloud/spring-cloud-sleuth/issues/327 + // we don't want to respond with any tracing data + then(notSampledHeaderIsPresent(mvcResult)).isEqualTo(false); then(ExceptionUtils.getLastException()).isNull(); } @@ -86,7 +87,6 @@ public class TraceFilterIntegrationTests extends AbstractMvcIntegrationTest { MvcResult mvcResult = whenSentPingWithTraceId(expectedTraceId); - then(tracingHeaderFrom(mvcResult)).isEqualTo(expectedTraceId); then(ExceptionUtils.getLastException()).isNull(); } @@ -108,7 +108,6 @@ public class TraceFilterIntegrationTests extends AbstractMvcIntegrationTest { this.mockMvc.perform(asyncDispatch(mvcResult)) .andExpect(status().isOk()).andReturn(); - then(tracingHeaderFrom(mvcResult)).isEqualTo(expectedTraceId); then(this.tracer.getCurrentSpan()).isNull(); then(ExceptionUtils.getLastException()).isNull(); } @@ -121,7 +120,6 @@ public class TraceFilterIntegrationTests extends AbstractMvcIntegrationTest { this.mockMvc.perform(asyncDispatch(mvcResult)) .andExpect(status().isOk()).andReturn(); - then(tracingHeaderFrom(mvcResult)).isEqualTo(expectedTraceId); Optional taggedSpan = this.spanAccumulator.getSpans().stream() .filter(span -> span.tags().containsKey("tag")).findFirst(); then(taggedSpan.isPresent()).isTrue(); @@ -137,7 +135,6 @@ public class TraceFilterIntegrationTests extends AbstractMvcIntegrationTest { MvcResult mvcResult = whenSentToNonExistentEndpointWithTraceId(expectedTraceId); - then(tracingHeaderFrom(mvcResult)).isEqualTo(expectedTraceId); then(this.tracer.getCurrentSpan()).isNull(); then(ExceptionUtils.getLastException()).isNull(); } @@ -208,12 +205,10 @@ public class TraceFilterIntegrationTests extends AbstractMvcIntegrationTest { private MvcResult whenSentRequestWithTraceIdAndNoSpanId(Long traceId) throws Exception { - MvcResult mvcResult = this.mockMvc + return this.mockMvc .perform(MockMvcRequestBuilders.get("/ping").accept(MediaType.TEXT_PLAIN) .header(Span.TRACE_ID_NAME, Span.idToHex(traceId))) .andReturn(); - then(tracingHeaderFrom(mvcResult)).isEqualTo(traceId); - return mvcResult; } private MvcResult sendRequestWithTraceId(String path, String headerName, Long traceId, HttpStatus status) @@ -226,10 +221,6 @@ public class TraceFilterIntegrationTests extends AbstractMvcIntegrationTest { .andReturn(); } - private Long tracingHeaderFrom(MvcResult mvcResult) { - return Span.hexToId(mvcResult.getResponse().getHeader(Span.TRACE_ID_NAME)); - } - private boolean notSampledHeaderIsPresent(MvcResult mvcResult) { return Span.SPAN_NOT_SAMPLED .equals(mvcResult.getResponse().getHeader(Span.SAMPLED_NAME)); 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 f8dbb0134..5f29beea6 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 @@ -74,7 +74,7 @@ public class TraceFilterMockChainIntegrationTests { public void startsNewTrace() throws Exception { TraceFilter filter = new TraceFilter(this.tracer, this.traceKeys, new NoOpSpanReporter(), new HttpServletRequestExtractor(Pattern.compile(TraceFilter.DEFAULT_SKIP_PATTERN)), - new HttpServletResponseInjector(), keysInjector); + keysInjector); filter.doFilter(this.request, this.response, this.filterChain); assertNull(TestSpanContextHolder.getCurrentSpan()); } @@ -86,7 +86,7 @@ public class TraceFilterMockChainIntegrationTests { .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 HttpServletResponseInjector(), keysInjector); + keysInjector); filter.doFilter(this.request, this.response, this.filterChain); assertNull(TestSpanContextHolder.getCurrentSpan()); } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterTests.java index 28c80214e..4787beef3 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 @@ -17,7 +17,6 @@ package org.springframework.cloud.sleuth.instrument.web; import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import java.util.Optional; import java.util.Random; import java.util.regex.Pattern; @@ -30,7 +29,6 @@ import org.springframework.cloud.sleuth.DefaultSpanNamer; import org.springframework.cloud.sleuth.Sampler; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.SpanExtractor; -import org.springframework.cloud.sleuth.SpanInjector; import org.springframework.cloud.sleuth.SpanReporter; import org.springframework.cloud.sleuth.TraceKeys; import org.springframework.cloud.sleuth.Tracer; @@ -51,8 +49,8 @@ import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilde import static org.junit.Assert.assertEquals; import static org.mockito.MockitoAnnotations.initMocks; import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.assertThat; -import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.entry; +import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; /** @@ -67,7 +65,6 @@ public class TraceFilterTests { ArrayListSpanAccumulator spanReporter = new ArrayListSpanAccumulator(); SpanExtractor spanExtractor = new HttpServletRequestExtractor(Pattern .compile(TraceFilter.DEFAULT_SKIP_PATTERN)); - SpanInjector spanInjector = new HttpServletResponseInjector(); private Tracer tracer; private TraceKeys traceKeys = new TraceKeys(); @@ -112,7 +109,7 @@ public class TraceFilterTests { public void notTraced() throws Exception { this.sampler = NeverSampler.INSTANCE; TraceFilter filter = new TraceFilter(this.tracer, this.traceKeys, this.spanReporter, - this.spanExtractor, this.spanInjector, this.httpTraceKeysInjector); + this.spanExtractor, this.httpTraceKeysInjector); this.request = get("/favicon.ico").accept(MediaType.ALL) .buildRequest(new MockServletContext()); @@ -126,7 +123,7 @@ public class TraceFilterTests { @Test public void startsNewTrace() throws Exception { TraceFilter filter = new TraceFilter(this.tracer, this.traceKeys, this.spanReporter, - this.spanExtractor, this.spanInjector, this.httpTraceKeysInjector); + this.spanExtractor, this.httpTraceKeysInjector); filter.doFilter(this.request, this.response, this.filterChain); verifyCurrentSpanStatusCode(HttpStatus.OK); @@ -137,7 +134,7 @@ public class TraceFilterTests { @Test public void shouldNotStoreHttpStatusCodeWhenResponseCodeHasNotYetBeenSet() throws Exception { TraceFilter filter = new TraceFilter(this.tracer, this.traceKeys, this.spanReporter, - this.spanExtractor, this.spanInjector, this.httpTraceKeysInjector); + this.spanExtractor, this.httpTraceKeysInjector); this.response.setStatus(0); filter.doFilter(this.request, this.response, this.filterChain); @@ -154,7 +151,7 @@ public class TraceFilterTests { .header(Span.PARENT_ID_NAME, Span.idToHex(3L)) .buildRequest(new MockServletContext()); TraceFilter filter = new TraceFilter(this.tracer, this.traceKeys, this.spanReporter, - this.spanExtractor, this.spanInjector, this.httpTraceKeysInjector); + this.spanExtractor, this.httpTraceKeysInjector); filter.doFilter(this.request, this.response, this.filterChain); @@ -187,7 +184,7 @@ public class TraceFilterTests { TestSpanContextHolder.removeCurrentSpan(); TraceFilter filter = new TraceFilter(this.tracer, this.traceKeys, this.spanReporter, - this.spanExtractor, this.spanInjector, this.httpTraceKeysInjector); + this.spanExtractor, this.httpTraceKeysInjector); filter.doFilter(this.request, this.response, this.filterChain); then(TestSpanContextHolder.getCurrentSpan()).isNull(); @@ -203,7 +200,7 @@ public class TraceFilterTests { TestSpanContextHolder.removeCurrentSpan(); TraceFilter filter = new TraceFilter(this.tracer, this.traceKeys, this.spanReporter, - this.spanExtractor, this.spanInjector, this.httpTraceKeysInjector); + this.spanExtractor, this.httpTraceKeysInjector); filter.doFilter(this.request, this.response, this.filterChain); then(TestSpanContextHolder.getCurrentSpan()).isNull(); @@ -220,7 +217,7 @@ public class TraceFilterTests { TestSpanContextHolder.removeCurrentSpan(); TraceFilter filter = new TraceFilter(this.tracer, this.traceKeys, this.spanReporter, - this.spanExtractor, this.spanInjector, this.httpTraceKeysInjector); + this.spanExtractor, this.httpTraceKeysInjector); filter.doFilter(this.request, this.response, this.filterChain); then(TestSpanContextHolder.getCurrentSpan()).isNull(); @@ -232,7 +229,7 @@ public class TraceFilterTests { .header(Span.TRACE_ID_NAME, 20L).buildRequest(new MockServletContext()); TraceFilter filter = new TraceFilter(this.tracer, this.traceKeys, this.spanReporter, - this.spanExtractor, this.spanInjector, this.httpTraceKeysInjector); + this.spanExtractor, this.httpTraceKeysInjector); filter.doFilter(this.request, this.response, this.filterChain); verifyParentSpanHttpTags(); @@ -247,7 +244,7 @@ public class TraceFilterTests { this.traceKeys.getHttp().getHeaders().add("x-foo"); TraceFilter filter = new TraceFilter(this.tracer, this.traceKeys, this.spanReporter, - this.spanExtractor, this.spanInjector, this.httpTraceKeysInjector); + this.spanExtractor, this.httpTraceKeysInjector); this.request.addHeader("X-Foo", "bar"); filter.doFilter(this.request, this.response, this.filterChain); @@ -261,7 +258,7 @@ public class TraceFilterTests { this.request = builder().header(Span.SPAN_ID_NAME, PARENT_ID) .header(Span.TRACE_ID_NAME, 20L).buildRequest(new MockServletContext()); TraceFilter filter = new TraceFilter(this.tracer, this.traceKeys, spanIsStoppedVeryfingReporter(), - this.spanExtractor, this.spanInjector, this.httpTraceKeysInjector); + this.spanExtractor, this.httpTraceKeysInjector); filter.doFilter(this.request, this.response, this.filterChain); } @@ -277,7 +274,7 @@ public class TraceFilterTests { this.traceKeys.getHttp().getHeaders().add("x-foo"); TraceFilter filter = new TraceFilter(this.tracer, this.traceKeys, this.spanReporter, - this.spanExtractor, this.spanInjector, this.httpTraceKeysInjector); + this.spanExtractor, this.httpTraceKeysInjector); this.request.addHeader("X-Foo", "bar"); this.request.addHeader("X-Foo", "spam"); filter.doFilter(this.request, this.response, this.filterChain); @@ -292,7 +289,7 @@ public class TraceFilterTests { this.request = builder().header(Span.SPAN_ID_NAME, PARENT_ID) .header(Span.TRACE_ID_NAME, 20L).buildRequest(new MockServletContext()); TraceFilter filter = new TraceFilter(this.tracer, this.traceKeys, this.spanReporter, - this.spanExtractor, this.spanInjector, this.httpTraceKeysInjector); + this.spanExtractor, this.httpTraceKeysInjector); this.filterChain = new MockFilterChain() { @Override public void doFilter(javax.servlet.ServletRequest request, @@ -317,7 +314,7 @@ public class TraceFilterTests { this.request = builder().header(Span.SPAN_ID_NAME, PARENT_ID) .header(Span.TRACE_ID_NAME, 20L).buildRequest(new MockServletContext()); TraceFilter filter = new TraceFilter(this.tracer, this.traceKeys, this.spanReporter, - this.spanExtractor, this.spanInjector, this.httpTraceKeysInjector); + this.spanExtractor, this.httpTraceKeysInjector); this.response.setStatus(404); filter.doFilter(this.request, this.response, this.filterChain); @@ -330,7 +327,7 @@ public class TraceFilterTests { this.request = builder().header(Span.SPAN_ID_NAME, "asd") .header(Span.TRACE_ID_NAME, 20L).buildRequest(new MockServletContext()); TraceFilter filter = new TraceFilter(this.tracer, this.traceKeys, this.spanReporter, - this.spanExtractor, this.spanInjector, this.httpTraceKeysInjector); + this.spanExtractor, this.httpTraceKeysInjector); filter.doFilter(this.request, this.response, this.filterChain); diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/WebClientTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/WebClientTests.java index d33726a99..5e4a02512 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/WebClientTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/WebClientTests.java @@ -112,8 +112,8 @@ public class WebClientTests { ResponseEntityProvider provider) { ResponseEntity response = provider.get(this); - then(getHeader(response, Span.TRACE_ID_NAME)).isNotNull(); - then(getHeader(response, Span.SPAN_ID_NAME)).isNotNull(); + then(getHeader(response, Span.TRACE_ID_NAME)).isNull(); + then(getHeader(response, Span.SPAN_ID_NAME)).isNull(); then(this.listener.getSpans()).isNotEmpty(); Awaitility.await().atMost(3, TimeUnit.SECONDS).until(() -> { log.info("Seraching for a notrace span in " + this.listener.getSpans()); @@ -172,9 +172,10 @@ public class WebClientTests { ResponseEntity response = provider.get(this); - then(getHeader(response, Span.SAMPLED_NAME)).isEqualTo(Span.SPAN_SAMPLED); - then(Span.hexToId(getHeader(response, Span.TRACE_ID_NAME))) - .isEqualTo(currentTraceId); + // https://github.com/spring-cloud/spring-cloud-sleuth/issues/327 + // we don't want to respond with any tracing data + then(getHeader(response, Span.SAMPLED_NAME)).isNull(); + then(getHeader(response, Span.TRACE_ID_NAME)).isNull(); thenRegisteredClientSentAndReceivedEvents(spanWithClientEvents()); } 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 af3f80282..1ad11b4d3 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/common/AbstractMvcIntegrationTest.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/common/AbstractMvcIntegrationTest.java @@ -1,12 +1,10 @@ package org.springframework.cloud.sleuth.instrument.web.common; import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import org.junit.Before; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.sleuth.SpanExtractor; -import org.springframework.cloud.sleuth.SpanInjector; import org.springframework.cloud.sleuth.TraceKeys; import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.instrument.web.HttpTraceKeysInjector; @@ -37,7 +35,6 @@ public abstract class AbstractMvcIntegrationTest { @Autowired protected Tracer tracer; @Autowired protected TraceKeys traceKeys; @Autowired protected SpanExtractor spanExtractor; - @Autowired protected SpanInjector spanInjector; @Autowired protected HttpTraceKeysInjector httpTraceKeysInjector; @Before From e0fbcfe7c6cb0d6e0055ffdc18c2a8073293d6fd Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Wed, 10 Aug 2016 17:18:20 +0200 Subject: [PATCH 040/235] Polish --- .../springframework/cloud/sleuth/Span.java | 4 +-- .../sleuth/instrument/web/TraceFilter.java | 33 ++++++++++--------- .../sleuth/assertions/ListOfSpansAssert.java | 9 +++-- .../instrument/hystrix/TraceCommandTests.java | 4 +-- .../instrument/web/client/WebClientTests.java | 32 +++++++----------- .../stream/ConvertToZipkinSpanList.java | 2 +- .../sleuth/zipkin/ZipkinSpanListener.java | 10 +++--- 7 files changed, 41 insertions(+), 53 deletions(-) diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java index 250f7a11d..3cbc67531 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java @@ -462,9 +462,7 @@ public class Span { Span other = (Span) obj; if (this.spanId != other.spanId) return false; - if (this.traceId != other.traceId) - return false; - return true; + return this.traceId == other.traceId; } public static class SpanBuilder { 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 a593922a4..d2806fdc2 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceFilter.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceFilter.java @@ -15,18 +15,18 @@ */ package org.springframework.cloud.sleuth.instrument.web; -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 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 org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -204,30 +204,31 @@ public class TraceFilter extends GenericFilterBean { private void detachOrCloseSpans(HttpServletRequest request, HttpServletResponse response, Span spanFromRequest, Throwable exception) { - if (spanFromRequest != null) { + Span span = spanFromRequest; + if (span != null) { addResponseTags(response, exception); - if (spanFromRequest.hasSavedSpan() && requestHasAlreadyBeenHandled(request)) { - recordParentSpan(spanFromRequest.getSavedSpan()); + if (span.hasSavedSpan() && requestHasAlreadyBeenHandled(request)) { + recordParentSpan(span.getSavedSpan()); } else if (!requestHasAlreadyBeenHandled(request)) { - spanFromRequest = this.tracer.close(spanFromRequest); + span = this.tracer.close(span); } - recordParentSpan(spanFromRequest); + recordParentSpan(span); // in case of a response with exception status will close the span when exception dispatch is handled if (httpStatusSuccessful(response)) { if (log.isDebugEnabled()) { - log.debug("Closing the span " + spanFromRequest + " since the response was successful"); + log.debug("Closing the span " + span + " since the response was successful"); } - this.tracer.close(spanFromRequest); + this.tracer.close(span); } else if (errorAlreadyHandled(request)) { if (log.isDebugEnabled()) { log.debug( - "Won't detach the span " + spanFromRequest + " since error has already been handled"); + "Won't detach the span " + span + " since error has already been handled"); } } else { if (log.isDebugEnabled()) { - log.debug("Detaching the span " + spanFromRequest + " since the response was unsuccessful"); + log.debug("Detaching the span " + span + " since the response was unsuccessful"); } - this.tracer.detach(spanFromRequest); + this.tracer.detach(span); } } } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/ListOfSpansAssert.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/ListOfSpansAssert.java index 9f460d24f..06fb23942 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/ListOfSpansAssert.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/ListOfSpansAssert.java @@ -20,16 +20,15 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.assertj.core.api.AbstractAssert; import org.springframework.cloud.sleuth.Span; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.toList; import static org.assertj.core.api.Assertions.assertThat; @@ -114,7 +113,7 @@ public class ListOfSpansAssert extends AbstractAssert "\nSPAN: " + span.toString() + " with name [" + span.getName() + "] " + - "\nwith tags " + span.tags() + "\nwith logs " + span.logs()).collect(Collectors.joining("\n")); + "\nwith tags " + span.tags() + "\nwith logs " + span.logs()).collect(joining("\n")); } public ListOfSpansAssert doesNotHaveASpanWithName(String name) { diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/hystrix/TraceCommandTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/hystrix/TraceCommandTests.java index 45254cada..1ab54bf9f 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/hystrix/TraceCommandTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/hystrix/TraceCommandTests.java @@ -32,7 +32,6 @@ import org.springframework.cloud.sleuth.trace.DefaultTracer; import org.springframework.cloud.sleuth.trace.TestSpanContextHolder; import com.netflix.hystrix.HystrixCommand; -import com.netflix.hystrix.HystrixCommandGroupKey; import com.netflix.hystrix.HystrixCommandKey; import com.netflix.hystrix.HystrixCommandProperties; import com.netflix.hystrix.HystrixThreadPoolProperties; @@ -103,8 +102,7 @@ public class TraceCommandTests { Tracer tracer = new DefaultTracer(new AlwaysSampler(), new Random(), new DefaultSpanNamer(), new NoOpSpanLogger(), new NoOpSpanReporter()); TraceKeys traceKeys = new TraceKeys(); - HystrixCommand.Setter setter = HystrixCommand.Setter - .withGroupKey(HystrixCommandGroupKey.Factory.asKey("group")) + HystrixCommand.Setter setter = withGroupKey(asKey("group")) .andCommandKey(HystrixCommandKey.Factory.asKey("command")); // tag::hystrix_command[] HystrixCommand hystrixCommand = new HystrixCommand(setter) { diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/WebClientTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/WebClientTests.java index 5e4a02512..adef26999 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/WebClientTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/WebClientTests.java @@ -16,7 +16,6 @@ package org.springframework.cloud.sleuth.instrument.web.client; -import javax.servlet.http.HttpServletRequest; import java.lang.invoke.MethodHandles; import java.util.ArrayList; import java.util.Collections; @@ -25,13 +24,8 @@ import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Random; -import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; - -import com.jayway.awaitility.Awaitility; -import com.netflix.loadbalancer.BaseLoadBalancer; -import com.netflix.loadbalancer.ILoadBalancer; -import com.netflix.loadbalancer.Server; +import javax.servlet.http.HttpServletRequest; import org.apache.commons.logging.LogFactory; import org.junit.After; @@ -73,6 +67,10 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.RestTemplate; +import com.netflix.loadbalancer.BaseLoadBalancer; +import com.netflix.loadbalancer.ILoadBalancer; +import com.netflix.loadbalancer.Server; + import junitparams.JUnitParamsRunner; import junitparams.Parameters; @@ -115,19 +113,13 @@ public class WebClientTests { then(getHeader(response, Span.TRACE_ID_NAME)).isNull(); then(getHeader(response, Span.SPAN_ID_NAME)).isNull(); then(this.listener.getSpans()).isNotEmpty(); - Awaitility.await().atMost(3, TimeUnit.SECONDS).until(() -> { - log.info("Seraching for a notrace span in " + this.listener.getSpans()); - Optional noTraceSpan = this.listener.getSpans().stream().filter(span -> - "http:/notrace".equals(span.getName()) && !span.tags().isEmpty()).findFirst(); - then(noTraceSpan.isPresent()).isTrue(); - log.info("No trace span found"); - // TODO: matches cause there is an issue with Feign not providing the full URL at the interceptor level - then(noTraceSpan.get()).matchesATag("http.url", ".*/notrace") - .hasATag("http.path", "/notrace") - .hasATag("http.method", "GET"); - log.info("Notrace has all the necessary tags"); - }); - + Optional noTraceSpan = this.listener.getSpans().stream().filter(span -> + "http:/notrace".equals(span.getName()) && !span.tags().isEmpty()).findFirst(); + then(noTraceSpan.isPresent()).isTrue(); + // TODO: matches cause there is an issue with Feign not providing the full URL at the interceptor level + then(noTraceSpan.get()).matchesATag("http.url", ".*/notrace") + .hasATag("http.path", "/notrace") + .hasATag("http.method", "GET"); } Object[] parametersForShouldCreateANewSpanWithClientSideTagsWhenNoPreviousTracingWasPresent() { diff --git a/spring-cloud-sleuth-zipkin-stream/src/main/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanList.java b/spring-cloud-sleuth-zipkin-stream/src/main/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanList.java index 930f5a5ef..1eac99aec 100644 --- a/spring-cloud-sleuth-zipkin-stream/src/main/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanList.java +++ b/spring-cloud-sleuth-zipkin-stream/src/main/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanList.java @@ -118,7 +118,7 @@ final class ConvertToZipkinSpanList { BinaryAnnotation.create(Constants.LOCAL_COMPONENT, processId, ep)); } - private static void ensureServerAddr(Span span, zipkin.Span.Builder zipkinSpan, + private static void ensureServerAddr(Span span, Builder zipkinSpan, Endpoint ep) { String serviceName = span.tags().containsKey(Span.SPAN_PEER_SERVICE_TAG_NAME) ? span.tags().get(Span.SPAN_PEER_SERVICE_TAG_NAME) : ep.serviceName; diff --git a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListener.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListener.java index ad63f007b..636b88a6b 100644 --- a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListener.java +++ b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListener.java @@ -136,7 +136,7 @@ public class ZipkinSpanListener implements SpanReporter { } private boolean hasClientSend(Span span) { - for (org.springframework.cloud.sleuth.Log log : span.logs()) { + for (Log log : span.logs()) { if (Constants.CLIENT_SEND.equals(log.getEvent())) { return !span.tags().containsKey(Constants.SERVER_ADDR); } @@ -183,16 +183,16 @@ public class ZipkinSpanListener implements SpanReporter { * duration (client receive - send) */ private long calculateDurationInMicros(Span span) { - org.springframework.cloud.sleuth.Log clientSend = hasLog(Span.CLIENT_SEND, span); - org.springframework.cloud.sleuth.Log clientReceived = hasLog(Span.CLIENT_RECV, span); + Log clientSend = hasLog(Span.CLIENT_SEND, span); + Log clientReceived = hasLog(Span.CLIENT_RECV, span); if (clientSend != null && clientReceived != null) { return (clientReceived.getTimestamp() - clientSend.getTimestamp()) * 1000; } return span.getAccumulatedMicros(); } - private org.springframework.cloud.sleuth.Log hasLog(String logName, Span span) { - for (org.springframework.cloud.sleuth.Log log : span.logs()) { + private Log hasLog(String logName, Span span) { + for (Log log : span.logs()) { if (logName.equals(log.getEvent())) { return log; } From f677be026e0aa4dd41187cc9408421ba670463ba Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Tue, 16 Aug 2016 10:39:03 +0200 Subject: [PATCH 041/235] Ensured reusability of Feign components Tests have been refactored to ensure that the custom components registered as beans are working properly fixes #374 --- .../cloud/sleuth/AdhocTestSuite.java | 2 +- .../WebClientDiscoveryExceptionTests.java | 2 +- .../WebClientExceptionTests.java | 2 +- .../issues/{ => issue307}/Issue307Tests.java | 2 +- .../feign/issues/issue362/Issue362Tests.java | 29 +++++++++++++------ .../FeignClientServerErrorTests.java | 2 +- .../{ => integration}/WebClientTests.java | 2 +- 7 files changed, 26 insertions(+), 15 deletions(-) rename spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/{ => discoveryexception}/WebClientDiscoveryExceptionTests.java (98%) rename spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/{ => exception}/WebClientExceptionTests.java (98%) rename spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/{ => issue307}/Issue307Tests.java (99%) rename spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/{ => servererrors}/FeignClientServerErrorTests.java (99%) rename spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/{ => integration}/WebClientTests.java (99%) diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/AdhocTestSuite.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/AdhocTestSuite.java index 7ffdbed5b..b2bbc570c 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/AdhocTestSuite.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/AdhocTestSuite.java @@ -21,7 +21,7 @@ import org.junit.runner.RunWith; import org.junit.runners.Suite; import org.junit.runners.Suite.SuiteClasses; import org.springframework.cloud.sleuth.instrument.web.RestTemplateTraceAspectIntegrationTests; -import org.springframework.cloud.sleuth.instrument.web.client.WebClientDiscoveryExceptionTests; +import org.springframework.cloud.sleuth.instrument.web.client.discoveryexception.WebClientDiscoveryExceptionTests; /** * A test suite for probing weird ordering problems in the tests. diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/WebClientDiscoveryExceptionTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/discoveryexception/WebClientDiscoveryExceptionTests.java similarity index 98% rename from spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/WebClientDiscoveryExceptionTests.java rename to spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/discoveryexception/WebClientDiscoveryExceptionTests.java index 73ff1f476..1bf42d1a3 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/WebClientDiscoveryExceptionTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/discoveryexception/WebClientDiscoveryExceptionTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.cloud.sleuth.instrument.web.client; +package org.springframework.cloud.sleuth.instrument.web.client.discoveryexception; import java.io.IOException; import java.util.Map; diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/WebClientExceptionTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/exception/WebClientExceptionTests.java similarity index 98% rename from spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/WebClientExceptionTests.java rename to spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/exception/WebClientExceptionTests.java index 9af225c0d..2d4fce19b 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/WebClientExceptionTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/exception/WebClientExceptionTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.cloud.sleuth.instrument.web.client; +package org.springframework.cloud.sleuth.instrument.web.client.exception; import java.io.IOException; import java.util.Collections; diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/Issue307Tests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue307/Issue307Tests.java similarity index 99% rename from spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/Issue307Tests.java rename to spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue307/Issue307Tests.java index c0d14ae8c..bd3eb5856 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/Issue307Tests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue307/Issue307Tests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.cloud.sleuth.instrument.web.client.feign.issues; +package org.springframework.cloud.sleuth.instrument.web.client.feign.issues.issue307; import java.util.ArrayList; import java.util.List; diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue362/Issue362Tests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue362/Issue362Tests.java index ef8f23bcc..806b6c65f 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue362/Issue362Tests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue362/Issue362Tests.java @@ -17,6 +17,8 @@ package org.springframework.cloud.sleuth.instrument.web.client.feign.issues.issue362; import java.util.Date; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import org.junit.Before; @@ -42,7 +44,6 @@ import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; -import feign.Client; import feign.Logger; import feign.Response; import feign.RetryableException; @@ -62,9 +63,11 @@ import static org.assertj.core.api.BDDAssertions.then; public class Issue362Tests { RestTemplate template = new RestTemplate(); + @Autowired FeignComponentAsserter feignComponentAsserter; @Before public void setup() { + this.feignComponentAsserter.executedComponents.clear(); ExceptionUtils.setFail(true); } @@ -88,6 +91,8 @@ public class Issue362Tests { } catch (Exception e) { } then(ExceptionUtils.getLastException()).isNull(); + then(this.feignComponentAsserter.executedComponents) + .containsEntry(ErrorDecoder.class, true); } } @@ -118,17 +123,20 @@ class Application { } @Bean - public Client client() { - return new Client.Default(null, null); - } + public FeignComponentAsserter testHolder() { return new FeignComponentAsserter(); } + +} + +class FeignComponentAsserter { + Map executedComponents = new ConcurrentHashMap<>(); } @Configuration class CustomConfig { @Bean - public ErrorDecoder errorDecoder() { - return new CustomErrorDecoder(); + public ErrorDecoder errorDecoder(FeignComponentAsserter feignComponentAsserter) { + return new CustomErrorDecoder(feignComponentAsserter); } @Bean @@ -138,22 +146,25 @@ class CustomConfig { public static class CustomErrorDecoder extends ErrorDecoder.Default { - public CustomErrorDecoder() { + private final FeignComponentAsserter feignComponentAsserter; + + public CustomErrorDecoder(FeignComponentAsserter feignComponentAsserter) { + this.feignComponentAsserter = feignComponentAsserter; } @Override public Exception decode(String methodKey, Response response) { + this.feignComponentAsserter.executedComponents.put(ErrorDecoder.class, true); if (response.status() == 409) { return new RetryableException("Article not Ready", new Date()); } else { return super.decode(methodKey, response); } } - } } -@FeignClient(value="myFeignClient", url="http://localhost:9998", +@FeignClient(name="myFeignClient", url="http://localhost:9998", configuration = CustomConfig.class) interface MyFeignClient { diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignClientServerErrorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/servererrors/FeignClientServerErrorTests.java similarity index 99% rename from spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignClientServerErrorTests.java rename to spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/servererrors/FeignClientServerErrorTests.java index b5277b03a..67512f8cb 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignClientServerErrorTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/servererrors/FeignClientServerErrorTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.cloud.sleuth.instrument.web.client.feign; +package org.springframework.cloud.sleuth.instrument.web.client.feign.servererrors; import java.util.ArrayList; import java.util.Collections; diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/WebClientTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/integration/WebClientTests.java similarity index 99% rename from spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/WebClientTests.java rename to spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/integration/WebClientTests.java index adef26999..347db6be4 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/WebClientTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/integration/WebClientTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.cloud.sleuth.instrument.web.client; +package org.springframework.cloud.sleuth.instrument.web.client.integration; import java.lang.invoke.MethodHandles; import java.util.ArrayList; From 9e10796b0ad9b2d5e5f74f81c79745107ecd8530 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Tue, 16 Aug 2016 14:04:32 +0200 Subject: [PATCH 042/235] Updated Zipkin UI docs fixes #375 --- docs/src/main/asciidoc/spring-cloud-sleuth.adoc | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc index 86af5dae7..b217e26b0 100644 --- a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc +++ b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc @@ -293,10 +293,19 @@ include::../../../../spring-cloud-sleuth-zipkin-stream/src/test/java/org/springf will listen for the Span data on whatever transport you provide via a Spring Cloud Stream `Binder` (e.g. include `spring-cloud-starter-stream-rabbit` for RabbitMQ, and similar -starters exist for Redis and Kafka). The app will also be a -https://github.com/openzipkin/zipkin-java[Zipkin server], which hosts +starters exist for Redis and Kafka). If you add the following UI dependency + +[source,xml] +---- +io.zipkin.java +zipkin-autoconfigure-ui +---- + +Then you'll have your app a +https://github.com/openzipkin/zipkin[Zipkin server], which hosts the UI and api on port 9411. + The default `SpanStore` is in-memory (good for demos and getting started quickly). For a more robust solution you can add MySQL and `spring-boot-starter-jdbc` to your classpath and enable the JDBC From 3f96b210d9a87cfff72490fbee1a093c4d8592ac Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Tue, 16 Aug 2016 14:16:25 +0200 Subject: [PATCH 043/235] Deploying documentation to proper folder (#373) What we're missing ATM is different documentation versions for different application versions. What this change does is that it's: - finding out what is the current branch (e.g. 1.0.x) - finding out out what is the name of the main adoc file (e.g. spring-cloud-sleuth) - pulling the changes from gh-pages after checkout - finding out what is the list of comma separated whitelisted branches (via the `docs.whitelisted.branches` prop) - in gh-pages creating a folder with name of the branch (e.g. /1.0.x) copying all the docs/target/generated-docs/ to that folder - if the branch from which we're calling the script is NOT master then we're changing the ${main.adoc}.html to index.html so that it's easier to access the docs (e.g. http://cloud.spring.io/spring-cloud-sleuth/1.0.x/) --- docs/pom.xml | 2 + docs/src/main/asciidoc/ghpages.sh | 109 +++++++++++++++--- .../main/asciidoc/spring-cloud-sleuth.adoc | 3 + 3 files changed, 98 insertions(+), 16 deletions(-) diff --git a/docs/pom.xml b/docs/pom.xml index 0b06c760e..276af00bb 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -13,6 +13,8 @@ Spring Sleuth Docs spring-cloud-sleuth + + 1.0.x ${basedir}/.. diff --git a/docs/src/main/asciidoc/ghpages.sh b/docs/src/main/asciidoc/ghpages.sh index 919057572..fc8dfb20c 100755 --- a/docs/src/main/asciidoc/ghpages.sh +++ b/docs/src/main/asciidoc/ghpages.sh @@ -12,6 +12,47 @@ if ! [ -d docs/target/generated-docs ]; then exit 0 fi +# The script should be executed from the root folder + +ROOT_FOLDER=`pwd` +echo "Current folder is ${ROOT_FOLDER}" + +if [[ ! -e "${ROOT_FOLDER}/.git" ]]; then + echo "You're not in the root folder of the project!" + exit 1 +fi + +# Retrieve properties +################################################################### + +# Prop that will let commit the changes +COMMIT_CHANGES="no" + +# Get the name of the `docs.main` property +MAIN_ADOC_VALUE=$(mvn -q \ + -Dexec.executable="echo" \ + -Dexec.args='${docs.main}' \ + --non-recursive \ + org.codehaus.mojo:exec-maven-plugin:1.3.1:exec) +echo "Extracted 'main.adoc' from Maven build [${MAIN_ADOC_VALUE}]" + +# Get whitelisted branches - assumes that a `docs` module is available under `docs` profile +WHITELIST_PROPERTY="docs.whitelisted.branches" +WHITELISTED_BRANCHES_VALUE=$(mvn -q \ + -Dexec.executable="echo" \ + -Dexec.args="\${${WHITELIST_PROPERTY}}" \ + org.codehaus.mojo:exec-maven-plugin:1.3.1:exec \ + -P docs \ + -pl docs) +echo "Extracted '${WHITELIST_PROPERTY}' from Maven build [${WHITELISTED_BRANCHES_VALUE}]" + +# Code getting the name of the current branch. For master we want to publish as we did until now +# http://stackoverflow.com/questions/1593051/how-to-programmatically-determine-the-current-checked-out-git-branch +CURRENT_BRANCH=$(git symbolic-ref -q HEAD) +CURRENT_BRANCH=${CURRENT_BRANCH##refs/heads/} +CURRENT_BRANCH=${CURRENT_BRANCH:-HEAD} +echo "Current branch is [${CURRENT_BRANCH}]" + # Stash any outstanding changes ################################################################### git diff-index --quiet HEAD @@ -21,26 +62,62 @@ if [ "$dirty" != "0" ]; then git stash; fi # Switch to gh-pages branch to sync it with master ################################################################### git checkout gh-pages +git pull origin gh-pages -for f in docs/target/generated-docs/*; do - file=${f#docs/target/generated-docs/*} - if ! git ls-files -i -o --exclude-standard --directory | grep -q ^$file$; then - # Not ignored... - cp -rf $f . - git add -A $file - fi -done - -git commit -a -m "Sync docs from master to gh-pages" - -# Uncomment the following push if you want to auto push to -# the gh-pages branch whenever you commit to master locally. -# This is a little extreme. Use with care! +# Add git branches ################################################################### -git push origin gh-pages +mkdir -p ${ROOT_FOLDER}/${CURRENT_BRANCH} +if [[ "${CURRENT_BRANCH}" == "master" ]] ; then + echo -e "Current branch is master - will copy the current docs only to the root folder" + for f in docs/target/generated-docs/*; do + file=${f#docs/target/generated-docs/*} + if ! git ls-files -i -o --exclude-standard --directory | grep -q ^$file$; then + # Not ignored... + cp -rf $f ${ROOT_FOLDER}/ + git add -A ${ROOT_FOLDER}/$file + fi + done + COMMIT_CHANGES="yes" +else + echo -e "Current branch is [${CURRENT_BRANCH}]" + # http://stackoverflow.com/questions/29300806/a-bash-script-to-check-if-a-string-is-present-in-a-comma-separated-list-of-strin + if [[ ",${WHITELISTED_BRANCHES_VALUE}," = *",${CURRENT_BRANCH},"* ]] ; then + echo -e "Branch [${CURRENT_BRANCH}] is whitelisted! Will copy the current docs to the [${CURRENT_BRANCH}] folder" + for f in docs/target/generated-docs/*; do + file=${f#docs/target/generated-docs/*} + if ! git ls-files -i -o --exclude-standard --directory | grep -q ^$file$; then + # Not ignored... + # We want users to access 1.0.0.RELEASE/ instead of 1.0.0.RELEASE/spring-cloud.sleuth.html + if [[ "${file}" == "${MAIN_ADOC_VALUE}.html" ]] ; then + # We don't want to copy the spring-cloud-sleuth.html + # we want it to be converted to index.html + cp -rf $f ${ROOT_FOLDER}/${CURRENT_BRANCH}/index.html + git add -A ${ROOT_FOLDER}/${CURRENT_BRANCH}/index.html + else + cp -rf $f ${ROOT_FOLDER}/${CURRENT_BRANCH} + git add -A ${ROOT_FOLDER}/${CURRENT_BRANCH}/$file + fi + fi + done + COMMIT_CHANGES="yes" + else + echo -e "Branch [${CURRENT_BRANCH}] is not on the white list! Check out the Maven [${WHITELIST_PROPERTY}] property in + [docs] module available under [docs] profile. Won't commit any changes to gh-pages for this branch." + fi +fi + +if [[ "${COMMIT_CHANGES}" == "yes" ]] ; then + git commit -a -m "Sync docs from ${CURRENT_BRANCH} to gh-pages" + + # Uncomment the following push if you want to auto push to + # the gh-pages branch whenever you commit to master locally. + # This is a little extreme. Use with care! + ################################################################### + git push origin gh-pages +fi # Finally, switch back to the master branch and exit block -git checkout master +git checkout ${CURRENT_BRANCH} if [ "$dirty" != "0" ]; then git stash pop; fi exit 0 diff --git a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc index b217e26b0..330c4dfc5 100644 --- a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc +++ b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc @@ -7,6 +7,9 @@ Spring Cloud Sleuth ==================== Adrian Cole, Spencer Gibb, Marcin Grzejszczak, Dave Syer + +*{spring-cloud-version}* + :doctype: book include::intro.adoc[] From 109bc02d1d2270cc6b701c348f8818610f9ad222 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Tue, 16 Aug 2016 14:27:22 +0200 Subject: [PATCH 044/235] Updated docs --- README.adoc | 5 +++-- docs/src/main/asciidoc/README.adoc | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/README.adoc b/README.adoc index 401323638..ffbb1f0ea 100644 --- a/README.adoc +++ b/README.adoc @@ -2,7 +2,8 @@ :jdkversion: 1.8 -image::https://circleci.com/gh/spring-cloud/spring-cloud-sleuth.svg?style=svg["CircleCI", link="https://circleci.com/gh/spring-cloud/spring-cloud-sleuth"] +image::https://circleci.com/gh/spring-cloud/spring-cloud-sleuth/tree/1.0.x.svg?style=svg["CircleCI", link="https://circleci.com/gh/spring-cloud/spring-cloud-sleuth/tree/1.0.x"] +image::https://codecov.io/gh/spring-cloud/spring-cloud-sleuth/branch/1.0.x/graph/badge.svg["codecov", link="https://codecov.io/gh/spring-cloud/spring-cloud-sleuth"] image::https://badges.gitter.im/spring-cloud/spring-cloud-sleuth.svg[Gitter, link="https://gitter.im/spring-cloud/spring-cloud-sleuth?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge"] == Spring Cloud Sleuth @@ -636,7 +637,7 @@ follow the guidelines below. === Sign the Contributor License Agreement Before we accept a non-trivial patch or pull request we will need you to sign the -https://support.springsource.com/spring_committer_signup[contributor's agreement]. +https://cla.pivotal.io/sign/spring[Contributor License Agreement]. Signing the contributor's agreement does not grant anyone commit rights to the main repository, but it does mean that we can accept your contributions, and you will get an author credit if we do. Active contributors might be asked to join the core team, and diff --git a/docs/src/main/asciidoc/README.adoc b/docs/src/main/asciidoc/README.adoc index 2e418d33f..59f73ccdd 100644 --- a/docs/src/main/asciidoc/README.adoc +++ b/docs/src/main/asciidoc/README.adoc @@ -1,7 +1,7 @@ :jdkversion: 1.8 -image::https://circleci.com/gh/spring-cloud/spring-cloud-sleuth.svg?style=svg["CircleCI", link="https://circleci.com/gh/spring-cloud/spring-cloud-sleuth"] -image::https://codecov.io/gh/spring-cloud/spring-cloud-sleuth/branch/master/graph/badge.svg["codecov", link="https://codecov.io/gh/spring-cloud/spring-cloud-sleuth"] +image::https://circleci.com/gh/spring-cloud/spring-cloud-sleuth/tree/1.0.x.svg?style=svg["CircleCI", link="https://circleci.com/gh/spring-cloud/spring-cloud-sleuth/tree/1.0.x"] +image::https://codecov.io/gh/spring-cloud/spring-cloud-sleuth/branch/1.0.x/graph/badge.svg["codecov", link="https://codecov.io/gh/spring-cloud/spring-cloud-sleuth"] image::https://badges.gitter.im/spring-cloud/spring-cloud-sleuth.svg[Gitter, link="https://gitter.im/spring-cloud/spring-cloud-sleuth?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge"] == Spring Cloud Sleuth From 9e900569a944c64d50d2aae89d06e256c56bca85 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Wed, 17 Aug 2016 12:45:56 +0200 Subject: [PATCH 045/235] Updating ghpages for all projects --- docs/src/main/asciidoc/ghpages.sh | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/docs/src/main/asciidoc/ghpages.sh b/docs/src/main/asciidoc/ghpages.sh index fc8dfb20c..a5d1acd53 100755 --- a/docs/src/main/asciidoc/ghpages.sh +++ b/docs/src/main/asciidoc/ghpages.sh @@ -27,9 +27,23 @@ fi # Prop that will let commit the changes COMMIT_CHANGES="no" +MAVEN_PATH=${MAVEN_PATH:-} +echo "Path to Maven is [${MAVEN_PATH}]" + +# Code getting the name of the current branch. For master we want to publish as we did until now +# http://stackoverflow.com/questions/1593051/how-to-programmatically-determine-the-current-checked-out-git-branch +# If there is a branch already passed will reuse it - otherwise will try to find it +CURRENT_BRANCH=${BRANCH} +if [[ -z "${CURRENT_BRANCH}" ]] ; then + CURRENT_BRANCH=$(git symbolic-ref -q HEAD) + CURRENT_BRANCH=${CURRENT_BRANCH##refs/heads/} + CURRENT_BRANCH=${CURRENT_BRANCH:-HEAD} +fi +echo "Current branch is [${CURRENT_BRANCH}]" +git checkout ${CURRENT_BRANCH} # Get the name of the `docs.main` property -MAIN_ADOC_VALUE=$(mvn -q \ +MAIN_ADOC_VALUE=$("${MAVEN_PATH}"mvn -q \ -Dexec.executable="echo" \ -Dexec.args='${docs.main}' \ --non-recursive \ @@ -38,7 +52,7 @@ echo "Extracted 'main.adoc' from Maven build [${MAIN_ADOC_VALUE}]" # Get whitelisted branches - assumes that a `docs` module is available under `docs` profile WHITELIST_PROPERTY="docs.whitelisted.branches" -WHITELISTED_BRANCHES_VALUE=$(mvn -q \ +WHITELISTED_BRANCHES_VALUE=$("${MAVEN_PATH}"mvn -q \ -Dexec.executable="echo" \ -Dexec.args="\${${WHITELIST_PROPERTY}}" \ org.codehaus.mojo:exec-maven-plugin:1.3.1:exec \ @@ -46,17 +60,9 @@ WHITELISTED_BRANCHES_VALUE=$(mvn -q \ -pl docs) echo "Extracted '${WHITELIST_PROPERTY}' from Maven build [${WHITELISTED_BRANCHES_VALUE}]" -# Code getting the name of the current branch. For master we want to publish as we did until now -# http://stackoverflow.com/questions/1593051/how-to-programmatically-determine-the-current-checked-out-git-branch -CURRENT_BRANCH=$(git symbolic-ref -q HEAD) -CURRENT_BRANCH=${CURRENT_BRANCH##refs/heads/} -CURRENT_BRANCH=${CURRENT_BRANCH:-HEAD} -echo "Current branch is [${CURRENT_BRANCH}]" - # Stash any outstanding changes ################################################################### -git diff-index --quiet HEAD -dirty=$? +git diff-index --quiet HEAD && dirty=$? || (echo "Failed to check if the current repo is dirty. Assuming that it is." && dirty="1") if [ "$dirty" != "0" ]; then git stash; fi # Switch to gh-pages branch to sync it with master @@ -120,4 +126,4 @@ fi git checkout ${CURRENT_BRANCH} if [ "$dirty" != "0" ]; then git stash pop; fi -exit 0 +exit 0 \ No newline at end of file From fdea5d6bac21e2d5f490624a80a1ae4d90631540 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Wed, 17 Aug 2016 13:54:12 +0200 Subject: [PATCH 046/235] Wraped debug logs with conditional --- .../SleuthHystrixConcurrencyStrategy.java | 12 +++++++----- .../web/HttpServletRequestExtractor.java | 8 +++++--- .../cloud/sleuth/instrument/web/TraceFilter.java | 4 +++- .../instrument/web/TraceHandlerInterceptor.java | 16 ++++++++++++---- .../sleuth/instrument/web/TraceWebAspect.java | 11 +++++++---- .../web/client/feign/TraceFeignClient.java | 12 +++++++++--- .../zipkin/stream/ConvertToZipkinSpanList.java | 7 +++++-- .../sleuth/zipkin/HttpZipkinSpanReporter.java | 4 +++- 8 files changed, 51 insertions(+), 23 deletions(-) diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/hystrix/SleuthHystrixConcurrencyStrategy.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/hystrix/SleuthHystrixConcurrencyStrategy.java index 1d6fe2838..9878b44fa 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/hystrix/SleuthHystrixConcurrencyStrategy.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/hystrix/SleuthHystrixConcurrencyStrategy.java @@ -84,11 +84,13 @@ public class SleuthHystrixConcurrencyStrategy extends HystrixConcurrencyStrategy private void logCurrentStateOfHysrixPlugins(HystrixEventNotifier eventNotifier, HystrixMetricsPublisher metricsPublisher, HystrixPropertiesStrategy propertiesStrategy) { - log.debug("Current Hystrix plugins configuration is [" + "concurrencyStrategy [" - + this.delegate + "]," + "eventNotifier [" + eventNotifier + "]," - + "metricPublisher [" + metricsPublisher + "]," + "propertiesStrategy [" - + propertiesStrategy + "]," + "]"); - log.debug("Registering Sleuth Hystrix Concurrency Strategy."); + if (log.isDebugEnabled()) { + log.debug("Current Hystrix plugins configuration is [" + "concurrencyStrategy [" + + this.delegate + "]," + "eventNotifier [" + eventNotifier + "]," + + "metricPublisher [" + metricsPublisher + "]," + "propertiesStrategy [" + + propertiesStrategy + "]," + "]"); + log.debug("Registering Sleuth Hystrix Concurrency Strategy."); + } } @Override diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/HttpServletRequestExtractor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/HttpServletRequestExtractor.java index d0d0f9f21..8081b1104 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/HttpServletRequestExtractor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/HttpServletRequestExtractor.java @@ -16,9 +16,9 @@ package org.springframework.cloud.sleuth.instrument.web; -import javax.servlet.http.HttpServletRequest; 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; @@ -66,8 +66,10 @@ class HttpServletRequestExtractor implements SpanExtractor { private long spanId(HttpServletRequest carrier, long traceId) { String spanId = carrier.getHeader(Span.SPAN_ID_NAME); if (spanId == null) { - 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"); + 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); 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 d2806fdc2..50faa46a6 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 @@ -315,7 +315,9 @@ public class TraceFilter extends GenericFilterBean { } spanFromRequest.logEvent(Span.SERVER_RECV); request.setAttribute(TRACE_REQUEST_ATTR, spanFromRequest); - log.debug("No parent span present - creating a new span"); + if (log.isDebugEnabled()) { + log.debug("No parent span present - creating a new span"); + } } return spanFromRequest; } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptor.java index f23cbd952..58bc9a4b1 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptor.java @@ -62,11 +62,15 @@ public class TraceHandlerInterceptor extends HandlerInterceptorAdapter { public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (isErrorControllerRelated(request)) { - log.debug("Skipping creation of a span for error controller processing"); + if (log.isDebugEnabled()) { + log.debug("Skipping creation of a span for error controller processing"); + } return true; } if (isSpanContinued(request)) { - log.debug("Skipping creation of a span since the span is continued"); + if (log.isDebugEnabled()) { + log.debug("Skipping creation of a span since the span is continued"); + } return true; } String spanName = spanName(handler); @@ -131,11 +135,15 @@ public class TraceHandlerInterceptor extends HandlerInterceptorAdapter { public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { if (isErrorControllerRelated(request)) { - log.debug("Skipping closing of a span for error controller processing"); + if (log.isDebugEnabled()) { + log.debug("Skipping closing of a span for error controller processing"); + } return; } if (isSpanContinued(request)) { - log.debug("Skipping closing of a span since it's been continued"); + if (log.isDebugEnabled()) { + log.debug("Skipping closing of a span since it's been continued"); + } return; } Span span = getSpanFromAttribute(request); diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAspect.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAspect.java index a56ac27a3..7e7ce9182 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAspect.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAspect.java @@ -100,8 +100,9 @@ public class TraceWebAspect { public Object wrapWithCorrelationId(ProceedingJoinPoint pjp) throws Throwable { Callable callable = (Callable) pjp.proceed(); if (this.tracer.isTracing()) { - log.debug("Wrapping callable with span [" - + this.tracer.getCurrentSpan() + "]"); + if (log.isDebugEnabled()) { + log.debug("Wrapping callable with span [" + this.tracer.getCurrentSpan() + "]"); + } return new TraceContinuingCallable<>(this.tracer, this.spanNamer, callable); } else { @@ -114,8 +115,10 @@ public class TraceWebAspect { final WebAsyncTask webAsyncTask = (WebAsyncTask) pjp.proceed(); if (this.tracer.isTracing()) { try { - log.debug("Wrapping callable with span [" - + this.tracer.getCurrentSpan() + "]"); + if (log.isDebugEnabled()) { + log.debug("Wrapping callable with span [" + this.tracer.getCurrentSpan() + + "]"); + } Field callableField = WebAsyncTask.class.getDeclaredField("callable"); callableField.setAccessible(true); callableField.set(webAsyncTask, new TraceContinuingCallable<>(this.tracer, diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClient.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClient.java index a29623769..fa9e85df3 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClient.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClient.java @@ -92,7 +92,9 @@ final class TraceFeignClient implements Client { private void closeSpan() { Span span = getTracer().getCurrentSpan(); if (span != null) { - log.debug("Closing Feign span " + span); + if (log.isDebugEnabled()) { + log.debug("Closing Feign span " + span); + } getTracer().close(span); } } @@ -100,7 +102,9 @@ final class TraceFeignClient implements Client { private void logCr() { Span span = getTracer().getCurrentSpan(); if (span != null) { - log.debug("Closing Feign span and logging CR" + span); + if (log.isDebugEnabled()) { + log.debug("Closing Feign span and logging CR" + span); + } span.logEvent(Span.CLIENT_RECV); } } @@ -109,7 +113,9 @@ final class TraceFeignClient implements Client { Span span = getTracer().getCurrentSpan(); if (span != null) { String message = e.getMessage() != null ? e.getMessage() : e.toString(); - log.debug("Appending exception [" + message + "] to span " + span); + if (log.isDebugEnabled()) { + log.debug("Appending exception [" + message + "] to span " + span); + } getTracer().addTag("error", message); } } diff --git a/spring-cloud-sleuth-zipkin-stream/src/main/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanList.java b/spring-cloud-sleuth-zipkin-stream/src/main/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanList.java index 1eac99aec..c94420b43 100644 --- a/spring-cloud-sleuth-zipkin-stream/src/main/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanList.java +++ b/spring-cloud-sleuth-zipkin-stream/src/main/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanList.java @@ -96,8 +96,11 @@ final class ConvertToZipkinSpanList { zipkinSpan.traceId(span.getTraceId()); if (span.getParents().size() > 0) { if (span.getParents().size() > 1) { - log.debug("zipkin doesn't support spans with multiple parents. Omitting " - + "other parents for " + span); + if (log.isDebugEnabled()) { + log.debug( + "zipkin doesn't support spans with multiple parents. Omitting " + + "other parents for " + span); + } } zipkinSpan.parentId(span.getParents().get(0)); } diff --git a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/HttpZipkinSpanReporter.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/HttpZipkinSpanReporter.java index 17fa56b06..917109248 100644 --- a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/HttpZipkinSpanReporter.java +++ b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/HttpZipkinSpanReporter.java @@ -86,7 +86,9 @@ public final class HttpZipkinSpanReporter byte[] json = Codec.JSON.writeSpans(drained); // NOTE: https://github.com/openzipkin/zipkin-java/issues/66 will throw instead of return null. if (json == null) { - log.debug("failed to encode spans, dropping them: " + drained); + if (log.isDebugEnabled()) { + log.debug("failed to encode spans, dropping them: " + drained); + } this.spanMetricReporter.incrementDroppedSpans(drained.size()); return; } From ef1cd65f0c6594941c5446f04b0422fe89772ce6 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Thu, 18 Aug 2016 12:20:01 +0200 Subject: [PATCH 047/235] Bounding the queue for StreamSpanReporter without this change the limit is MAX_INTEGER. With this change we set it to 1000 fixes #378 --- .../springframework/cloud/sleuth/stream/StreamSpanReporter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/StreamSpanReporter.java b/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/StreamSpanReporter.java index 7ecbd8c9a..575f06515 100644 --- a/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/StreamSpanReporter.java +++ b/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/StreamSpanReporter.java @@ -45,7 +45,7 @@ public class StreamSpanReporter implements SpanReporter { */ public static final String POLLER = "streamSpanReporterPoller"; - private BlockingQueue queue = new LinkedBlockingQueue<>(); + private BlockingQueue queue = new LinkedBlockingQueue<>(1000); private final HostLocator endpointLocator; private final SpanMetricReporter spanMetricReporter; From 0c9955c761133937cf01f1c60eb79c5e6577f160 Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Wed, 17 Aug 2016 17:31:26 +0100 Subject: [PATCH 048/235] Change repo URLs to https --- benchmarks/pom.xml | 8 ++++---- pom.xml | 14 +++++++------- spring-cloud-sleuth-dependencies/pom.xml | 10 +++++----- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/benchmarks/pom.xml b/benchmarks/pom.xml index d0287a4bb..36dead8a4 100644 --- a/benchmarks/pom.xml +++ b/benchmarks/pom.xml @@ -159,7 +159,7 @@ spring-snapshots Spring Snapshots - http://repo.spring.io/libs-snapshot-local + https://repo.spring.io/libs-snapshot-local true @@ -167,7 +167,7 @@ spring-milestones Spring Milestones - http://repo.spring.io/libs-milestone-local + https://repo.spring.io/libs-milestone-local false @@ -175,7 +175,7 @@ spring-staging Spring Staging - http://repo.spring.io/libs-staging-local/ + https://repo.spring.io/libs-staging-local/ false @@ -183,7 +183,7 @@ spring-releases Spring Releases - http://repo.spring.io/release + https://repo.spring.io/release false diff --git a/pom.xml b/pom.xml index dd612e174..09fe00956 100644 --- a/pom.xml +++ b/pom.xml @@ -218,7 +218,7 @@ spring-snapshots Spring Snapshots - http://repo.spring.io/libs-snapshot-local + https://repo.spring.io/libs-snapshot-local true @@ -226,7 +226,7 @@ spring-milestones Spring Milestones - http://repo.spring.io/libs-milestone-local + https://repo.spring.io/libs-milestone-local false @@ -237,7 +237,7 @@ spring-staging Spring Staging - http://repo.spring.io/libs-staging-local/ + https://repo.spring.io/libs-staging-local/ false @@ -245,7 +245,7 @@ spring-releases Spring Releases - http://repo.spring.io/release + https://repo.spring.io/release false @@ -255,7 +255,7 @@ spring-snapshots Spring Snapshots - http://repo.spring.io/libs-snapshot-local + https://repo.spring.io/libs-snapshot-local true @@ -266,7 +266,7 @@ spring-milestones Spring Milestones - http://repo.spring.io/libs-milestone-local + https://repo.spring.io/libs-milestone-local false @@ -274,7 +274,7 @@ spring-releases Spring Releases - http://repo.spring.io/libs-release-local + https://repo.spring.io/libs-release-local false diff --git a/spring-cloud-sleuth-dependencies/pom.xml b/spring-cloud-sleuth-dependencies/pom.xml index 5ba2f53ab..f040b3909 100644 --- a/spring-cloud-sleuth-dependencies/pom.xml +++ b/spring-cloud-sleuth-dependencies/pom.xml @@ -101,7 +101,7 @@ spring-snapshots Spring Snapshots - http://repo.spring.io/libs-snapshot-local + https://repo.spring.io/libs-snapshot-local true @@ -112,7 +112,7 @@ spring-milestones Spring Milestones - http://repo.spring.io/libs-milestone-local + https://repo.spring.io/libs-milestone-local false @@ -120,7 +120,7 @@ spring-releases Spring Releases - http://repo.spring.io/release + https://repo.spring.io/release false @@ -130,7 +130,7 @@ spring-snapshots Spring Snapshots - http://repo.spring.io/libs-snapshot-local + https://repo.spring.io/libs-snapshot-local true @@ -141,7 +141,7 @@ spring-milestones Spring Milestones - http://repo.spring.io/libs-milestone-local + https://repo.spring.io/libs-milestone-local false From ab2b4576e5e72905dca10ca25eccfb4c2b7e7312 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Thu, 18 Aug 2016 13:15:51 +0200 Subject: [PATCH 049/235] Updating ghpages for all projects --- docs/src/main/asciidoc/ghpages.sh | 368 +++++++++++++++++++++--------- 1 file changed, 266 insertions(+), 102 deletions(-) diff --git a/docs/src/main/asciidoc/ghpages.sh b/docs/src/main/asciidoc/ghpages.sh index a5d1acd53..c43f19044 100755 --- a/docs/src/main/asciidoc/ghpages.sh +++ b/docs/src/main/asciidoc/ghpages.sh @@ -1,129 +1,293 @@ #!/bin/bash -x -git remote set-url --push origin `git config remote.origin.url | sed -e 's/^git:/https:/'` +set -e -if ! (git remote set-branches --add origin gh-pages && git fetch -q); then - echo "No gh-pages, so not syncing" - exit 0 -fi +# Set default props like MAVEN_PATH, ROOT_FOLDER etc. +function set_default_props() { + # The script should be executed from the root folder + ROOT_FOLDER=`pwd` + echo "Current folder is ${ROOT_FOLDER}" -if ! [ -d docs/target/generated-docs ]; then - echo "No gh-pages sources in docs/target/generated-docs, so not syncing" - exit 0 -fi + if [[ ! -e "${ROOT_FOLDER}/.git" ]]; then + echo "You're not in the root folder of the project!" + exit 1 + fi -# The script should be executed from the root folder + # Prop that will let commit the changes + COMMIT_CHANGES="no" + MAVEN_PATH=${MAVEN_PATH:-} + echo "Path to Maven is [${MAVEN_PATH}]" + REPO_NAME=${PWD##*/} + echo "Repo name is [${REPO_NAME}]" +} -ROOT_FOLDER=`pwd` -echo "Current folder is ${ROOT_FOLDER}" +# Check if gh-pages exists and docs have been built +function check_if_anything_to_sync() { + git remote set-url --push origin `git config remote.origin.url | sed -e 's/^git:/https:/'` -if [[ ! -e "${ROOT_FOLDER}/.git" ]]; then - echo "You're not in the root folder of the project!" - exit 1 -fi + if ! (git remote set-branches --add origin gh-pages && git fetch -q); then + echo "No gh-pages, so not syncing" + exit 0 + fi -# Retrieve properties -################################################################### + if ! [ -d docs/target/generated-docs ]; then + echo "No gh-pages sources in docs/target/generated-docs, so not syncing" + exit 0 + fi +} -# Prop that will let commit the changes -COMMIT_CHANGES="no" -MAVEN_PATH=${MAVEN_PATH:-} -echo "Path to Maven is [${MAVEN_PATH}]" +function retrieve_current_branch() { + # Code getting the name of the current branch. For master we want to publish as we did until now + # http://stackoverflow.com/questions/1593051/how-to-programmatically-determine-the-current-checked-out-git-branch + # If there is a branch already passed will reuse it - otherwise will try to find it + CURRENT_BRANCH=${BRANCH} + if [[ -z "${CURRENT_BRANCH}" ]] ; then + CURRENT_BRANCH=$(git symbolic-ref -q HEAD) + CURRENT_BRANCH=${CURRENT_BRANCH##refs/heads/} + CURRENT_BRANCH=${CURRENT_BRANCH:-HEAD} + fi + echo "Current branch is [${CURRENT_BRANCH}]" + git checkout ${CURRENT_BRANCH} -# Code getting the name of the current branch. For master we want to publish as we did until now -# http://stackoverflow.com/questions/1593051/how-to-programmatically-determine-the-current-checked-out-git-branch -# If there is a branch already passed will reuse it - otherwise will try to find it -CURRENT_BRANCH=${BRANCH} -if [[ -z "${CURRENT_BRANCH}" ]] ; then - CURRENT_BRANCH=$(git symbolic-ref -q HEAD) - CURRENT_BRANCH=${CURRENT_BRANCH##refs/heads/} - CURRENT_BRANCH=${CURRENT_BRANCH:-HEAD} -fi -echo "Current branch is [${CURRENT_BRANCH}]" -git checkout ${CURRENT_BRANCH} +} + +# Switches to the provided value of the release version. We always prefix it with `v` +function switch_to_tag() { + git checkout v${VERSION} +} # Get the name of the `docs.main` property -MAIN_ADOC_VALUE=$("${MAVEN_PATH}"mvn -q \ - -Dexec.executable="echo" \ - -Dexec.args='${docs.main}' \ - --non-recursive \ - org.codehaus.mojo:exec-maven-plugin:1.3.1:exec) -echo "Extracted 'main.adoc' from Maven build [${MAIN_ADOC_VALUE}]" - # Get whitelisted branches - assumes that a `docs` module is available under `docs` profile -WHITELIST_PROPERTY="docs.whitelisted.branches" -WHITELISTED_BRANCHES_VALUE=$("${MAVEN_PATH}"mvn -q \ - -Dexec.executable="echo" \ - -Dexec.args="\${${WHITELIST_PROPERTY}}" \ - org.codehaus.mojo:exec-maven-plugin:1.3.1:exec \ - -P docs \ - -pl docs) -echo "Extracted '${WHITELIST_PROPERTY}' from Maven build [${WHITELISTED_BRANCHES_VALUE}]" +function retrieve_doc_properties() { + MAIN_ADOC_VALUE=$("${MAVEN_PATH}"mvn -q \ + -Dexec.executable="echo" \ + -Dexec.args='${docs.main}' \ + --non-recursive \ + org.codehaus.mojo:exec-maven-plugin:1.3.1:exec) + echo "Extracted 'main.adoc' from Maven build [${MAIN_ADOC_VALUE}]" + + + WHITELIST_PROPERTY=${WHITELIST_PROPERTY:-"docs.whitelisted.branches"} + WHITELISTED_BRANCHES_VALUE=$("${MAVEN_PATH}"mvn -q \ + -Dexec.executable="echo" \ + -Dexec.args="\${${WHITELIST_PROPERTY}}" \ + org.codehaus.mojo:exec-maven-plugin:1.3.1:exec \ + -P docs \ + -pl docs) + echo "Extracted '${WHITELIST_PROPERTY}' from Maven build [${WHITELISTED_BRANCHES_VALUE}]" +} # Stash any outstanding changes -################################################################### -git diff-index --quiet HEAD && dirty=$? || (echo "Failed to check if the current repo is dirty. Assuming that it is." && dirty="1") -if [ "$dirty" != "0" ]; then git stash; fi +function stash_changes() { + git diff-index --quiet HEAD && dirty=$? || (echo "Failed to check if the current repo is dirty. Assuming that it is." && dirty="1") + if [ "$dirty" != "0" ]; then git stash; fi +} -# Switch to gh-pages branch to sync it with master -################################################################### -git checkout gh-pages -git pull origin gh-pages - -# Add git branches -################################################################### -mkdir -p ${ROOT_FOLDER}/${CURRENT_BRANCH} -if [[ "${CURRENT_BRANCH}" == "master" ]] ; then - echo -e "Current branch is master - will copy the current docs only to the root folder" - for f in docs/target/generated-docs/*; do - file=${f#docs/target/generated-docs/*} - if ! git ls-files -i -o --exclude-standard --directory | grep -q ^$file$; then - # Not ignored... - cp -rf $f ${ROOT_FOLDER}/ - git add -A ${ROOT_FOLDER}/$file +# Switch to gh-pages branch to sync it with current branch +function add_docs_from_target() { + local DESTINATION_REPO_FOLDER + if [[ -z "${DESTINATION}" ]] ; then + DESTINATION_REPO_FOLDER=${ROOT_FOLDER} + else + if [[ ! -e "${DESTINATION}/.git" ]]; then + echo "[${DESTINATION}] is not a git repository" + exit 1 fi - done - COMMIT_CHANGES="yes" -else - echo -e "Current branch is [${CURRENT_BRANCH}]" - # http://stackoverflow.com/questions/29300806/a-bash-script-to-check-if-a-string-is-present-in-a-comma-separated-list-of-strin - if [[ ",${WHITELISTED_BRANCHES_VALUE}," = *",${CURRENT_BRANCH},"* ]] ; then - echo -e "Branch [${CURRENT_BRANCH}] is whitelisted! Will copy the current docs to the [${CURRENT_BRANCH}] folder" + DESTINATION_REPO_FOLDER=${DESTINATION}/${REPO_NAME} + mkdir -p ${DESTINATION_REPO_FOLDER} + echo "Destination was provided [${DESTINATION}]" + fi + cd ${DESTINATION_REPO_FOLDER} + git checkout gh-pages + git pull origin gh-pages + + # Add git branches + ################################################################### + if [[ -z "${VERSION}" ]] ; then + copy_docs_for_current_version + else + copy_docs_for_provided_version + fi + commit_changes_if_applicable +} + + +# Copies the docs by using the retrieved properties from Maven build +function copy_docs_for_current_version() { + if [[ "${CURRENT_BRANCH}" == "master" ]] ; then + echo -e "Current branch is master - will copy the current docs only to the root folder" for f in docs/target/generated-docs/*; do file=${f#docs/target/generated-docs/*} if ! git ls-files -i -o --exclude-standard --directory | grep -q ^$file$; then # Not ignored... - # We want users to access 1.0.0.RELEASE/ instead of 1.0.0.RELEASE/spring-cloud.sleuth.html - if [[ "${file}" == "${MAIN_ADOC_VALUE}.html" ]] ; then - # We don't want to copy the spring-cloud-sleuth.html - # we want it to be converted to index.html - cp -rf $f ${ROOT_FOLDER}/${CURRENT_BRANCH}/index.html - git add -A ${ROOT_FOLDER}/${CURRENT_BRANCH}/index.html - else - cp -rf $f ${ROOT_FOLDER}/${CURRENT_BRANCH} - git add -A ${ROOT_FOLDER}/${CURRENT_BRANCH}/$file - fi + cp -rf $f ${ROOT_FOLDER}/ + git add -A ${ROOT_FOLDER}/$file fi done COMMIT_CHANGES="yes" else - echo -e "Branch [${CURRENT_BRANCH}] is not on the white list! Check out the Maven [${WHITELIST_PROPERTY}] property in - [docs] module available under [docs] profile. Won't commit any changes to gh-pages for this branch." + echo -e "Current branch is [${CURRENT_BRANCH}]" + # http://stackoverflow.com/questions/29300806/a-bash-script-to-check-if-a-string-is-present-in-a-comma-separated-list-of-strin + if [[ ",${WHITELISTED_BRANCHES_VALUE}," = *",${CURRENT_BRANCH},"* ]] ; then + mkdir -p ${ROOT_FOLDER}/${CURRENT_BRANCH} + echo -e "Branch [${CURRENT_BRANCH}] is whitelisted! Will copy the current docs to the [${CURRENT_BRANCH}] folder" + for f in docs/target/generated-docs/*; do + file=${f#docs/target/generated-docs/*} + if ! git ls-files -i -o --exclude-standard --directory | grep -q ^$file$; then + # Not ignored... + # We want users to access 1.0.0.RELEASE/ instead of 1.0.0.RELEASE/spring-cloud.sleuth.html + if [[ "${file}" == "${MAIN_ADOC_VALUE}.html" ]] ; then + # We don't want to copy the spring-cloud-sleuth.html + # we want it to be converted to index.html + cp -rf $f ${ROOT_FOLDER}/${CURRENT_BRANCH}/index.html + git add -A ${ROOT_FOLDER}/${CURRENT_BRANCH}/index.html + else + cp -rf $f ${ROOT_FOLDER}/${CURRENT_BRANCH} + git add -A ${ROOT_FOLDER}/${CURRENT_BRANCH}/$file + fi + fi + done + COMMIT_CHANGES="yes" + else + echo -e "Branch [${CURRENT_BRANCH}] is not on the white list! Check out the Maven [${WHITELIST_PROPERTY}] property in + [docs] module available under [docs] profile. Won't commit any changes to gh-pages for this branch." + fi fi +} + +# Copies the docs by using the explicitly provided version +function copy_docs_for_provided_version() { + local FOLDER=${DESTINATION_REPO_FOLDER}/${VERSION} + mkdir -p ${FOLDER} + echo -e "Current tag is [v${VERSION}] Will copy the current docs to the [${FOLDER}] folder" + for f in ${ROOT_FOLDER}/docs/target/generated-docs/*; do + file=${f#${ROOT_FOLDER}/docs/target/generated-docs/*} + copy_docs_for_branch ${file} ${FOLDER} + done + COMMIT_CHANGES="yes" + CURRENT_BRANCH="v${VERSION}" +} + +# Copies the docs from target to the provided destination +# Params: +# $1 - file from target +# $2 - destination to which copy the files +function copy_docs_for_branch() { + local file=$1 + local destination=$2 + if ! git ls-files -i -o --exclude-standard --directory | grep -q ^${file}$; then + # Not ignored... + # We want users to access 1.0.0.RELEASE/ instead of 1.0.0.RELEASE/spring-cloud.sleuth.html + if [[ "${file}" == "${MAIN_ADOC_VALUE}.html" ]] ; then + # We don't want to copy the spring-cloud-sleuth.html + # we want it to be converted to index.html + cp -rf $f ${destination}/index.html + git add -A ${destination}/index.html + else + cp -rf $f ${destination} + git add -A ${destination}/$file + fi + fi +} + +function commit_changes_if_applicable() { + if [[ "${COMMIT_CHANGES}" == "yes" ]] ; then + COMMIT_SUCCESSFUL="no" + git commit -a -m "Sync docs from ${CURRENT_BRANCH} to gh-pages" && COMMIT_SUCCESSFUL="yes" || echo "Failed to commit changes" + + # Uncomment the following push if you want to auto push to + # the gh-pages branch whenever you commit to master locally. + # This is a little extreme. Use with care! + ################################################################### + if [[ "${COMMIT_SUCCESSFUL}" == "yes" ]] ; then + git push origin gh-pages + fi + fi +} + +# Switch back to the previous branch and exit block +function checkout_previous_branch() { + git checkout ${CURRENT_BRANCH} + if [ "$dirty" != "0" ]; then git stash pop; fi + exit 0 +} + +# Assert if properties have been properly passed +function assert_properties() { +if [[ "${VERSION}" != "" && -z "${DESTINATION}" ]] ; then echo "Version was set but destination was not!"; exit 1;fi +if [[ "${DESTINATION}" != "" && -z "${VERSION}" ]] ; then echo "Destination was set but version was not!"; exit 1;fi +} + +# Prints the usage +function print_usage() { +cat </` + +USAGE: + +You can use the following options: + +-v|--version - the script will apply the whole procedure for a particular library version +-d|--destination - the root of destination folder where the docs should be copied. E.g. point to spring-cloud-static folder + +EOF +} + + +# ========================================== +# ____ ____ _____ _____ _____ _______ +# / ____|/ ____| __ \|_ _| __ \__ __| +# | (___ | | | |__) | | | | |__) | | | +# \___ \| | | _ / | | | ___/ | | +# ____) | |____| | \ \ _| |_| | | | +# |_____/ \_____|_| \_\_____|_| |_| +# +# ========================================== + +while [[ $# > 0 ]] +do +key="$1" +case ${key} in + -v|--version) + VERSION="$2" + shift # past argument + ;; + -d|--destination) + DESTINATION="$2" + shift # past argument + ;; + -h|--help) + print_usage + exit 0 + ;; + *) + echo "Invalid option: [$1]" + print_usage + exit 1 + ;; +esac +shift # past argument or value +done + +assert_properties +set_default_props +check_if_anything_to_sync +if [[ -z "${VERSION}" ]] ; then + retrieve_current_branch +else + switch_to_tag fi - -if [[ "${COMMIT_CHANGES}" == "yes" ]] ; then - git commit -a -m "Sync docs from ${CURRENT_BRANCH} to gh-pages" - - # Uncomment the following push if you want to auto push to - # the gh-pages branch whenever you commit to master locally. - # This is a little extreme. Use with care! - ################################################################### - git push origin gh-pages -fi - -# Finally, switch back to the master branch and exit block -git checkout ${CURRENT_BRANCH} -if [ "$dirty" != "0" ]; then git stash pop; fi - -exit 0 \ No newline at end of file +retrieve_doc_properties +stash_changes +add_docs_from_target +checkout_previous_branch \ No newline at end of file From af7593eaf57196feed860c9cd4046cabc844704a Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Thu, 18 Aug 2016 15:44:04 +0200 Subject: [PATCH 050/235] Added docs about logback-spring and props we had a couple of questions related to missing attributes in the logback file or the logs as such. It's related to the fact that once you provide a custom logback file you have to place the propery of spring.application.name in the bootstrap file fixes #376 --- docs/src/main/asciidoc/intro.adoc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/src/main/asciidoc/intro.adoc b/docs/src/main/asciidoc/intro.adoc index c4d7f6217..2123efc36 100644 --- a/docs/src/main/asciidoc/intro.adoc +++ b/docs/src/main/asciidoc/intro.adoc @@ -158,6 +158,9 @@ Below you can find an example of a Logback configuration (file named `https://gi include::https://raw.githubusercontent.com/spring-cloud-samples/sleuth-documentation-apps/master/service1/src/main/resources/logback-spring.xml[] ----- +NOTE: If you're using a custom `logback-spring.xml` then you have to pass the `spring.application.name` in +`bootstrap` instead of `application` property file. Otherwise your custom logback file won't read the property properly. + === Adding to the project ==== Only Sleuth (log correlation) From 8e01425eed522edfef0751ed946873d0febb85bb Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Thu, 18 Aug 2016 16:11:46 +0200 Subject: [PATCH 051/235] Synchronized ArrayListSpanAccumulator spans without this change we could have ConcurrentModificationExceptions --- .../cloud/sleuth/util/ArrayListSpanAccumulator.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/util/ArrayListSpanAccumulator.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/util/ArrayListSpanAccumulator.java index daf1f7bc4..bfaeac69e 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/util/ArrayListSpanAccumulator.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/util/ArrayListSpanAccumulator.java @@ -17,6 +17,7 @@ package org.springframework.cloud.sleuth.util; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import org.springframework.cloud.sleuth.Span; @@ -30,7 +31,7 @@ import org.springframework.cloud.sleuth.SpanReporter; * @since 1.0.0 */ public class ArrayListSpanAccumulator implements SpanReporter { - private final List spans = new ArrayList<>(); + private final List spans = Collections.synchronizedList(new ArrayList()); public List getSpans() { return this.spans; From 08e8e87ba2b5437728666ea863ecf046cb4dff95 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Fri, 19 Aug 2016 11:46:57 +0200 Subject: [PATCH 052/235] Updating ghpages for all projects --- docs/src/main/asciidoc/ghpages.sh | 52 ++++++++++++++++++++++++++----- 1 file changed, 44 insertions(+), 8 deletions(-) diff --git a/docs/src/main/asciidoc/ghpages.sh b/docs/src/main/asciidoc/ghpages.sh index c43f19044..6a6363ba5 100755 --- a/docs/src/main/asciidoc/ghpages.sh +++ b/docs/src/main/asciidoc/ghpages.sh @@ -19,6 +19,8 @@ function set_default_props() { echo "Path to Maven is [${MAVEN_PATH}]" REPO_NAME=${PWD##*/} echo "Repo name is [${REPO_NAME}]" + SPRING_CLOUD_STATIC_REPO=${SPRING_CLOUD_STATIC_REPO:-git@github.com:spring-cloud/spring-cloud-static.git} + echo "Spring Cloud Static repo is [${SPRING_CLOUD_STATIC_REPO}" } # Check if gh-pages exists and docs have been built @@ -47,8 +49,7 @@ function retrieve_current_branch() { CURRENT_BRANCH=${CURRENT_BRANCH:-HEAD} fi echo "Current branch is [${CURRENT_BRANCH}]" - git checkout ${CURRENT_BRANCH} - + git checkout ${CURRENT_BRANCH} || echo "Failed to check the branch... continuing with the script" } # Switches to the provided value of the release version. We always prefix it with `v` @@ -56,6 +57,13 @@ function switch_to_tag() { git checkout v${VERSION} } +# Build the docs if switch is on +function build_docs_if_applicable() { + if [[ "${BUILD}" == "yes" ]] ; then + ./mvnw clean install -P docs -pl docs -DskipTests + fi +} + # Get the name of the `docs.main` property # Get whitelisted branches - assumes that a `docs` module is available under `docs` profile function retrieve_doc_properties() { @@ -86,8 +94,19 @@ function stash_changes() { # Switch to gh-pages branch to sync it with current branch function add_docs_from_target() { local DESTINATION_REPO_FOLDER - if [[ -z "${DESTINATION}" ]] ; then + if [[ -z "${DESTINATION}" && -z "${CLONE}" ]] ; then DESTINATION_REPO_FOLDER=${ROOT_FOLDER} + elif [[ "${CLONE}" == "yes" ]]; then + mkdir -p ${ROOT_FOLDER}/target + local clonedStatic=${ROOT_FOLDER}/target/spring-cloud-static + if [[ ! -e "${clonedStatic}/.git" ]]; then + echo "Cloning Spring Cloud Static to target" + git clone ${SPRING_CLOUD_STATIC_REPO} ${clonedStatic} + else + echo "Spring Cloud Static already cloned - will pull changes" + cd ${clonedStatic} && git checkout gh-pages && git pull origin gh-pages + fi + DESTINATION_REPO_FOLDER=${clonedStatic}/${REPO_NAME} else if [[ ! -e "${DESTINATION}/.git" ]]; then echo "[${DESTINATION}] is not a git repository" @@ -178,7 +197,7 @@ function copy_docs_for_branch() { if ! git ls-files -i -o --exclude-standard --directory | grep -q ^${file}$; then # Not ignored... # We want users to access 1.0.0.RELEASE/ instead of 1.0.0.RELEASE/spring-cloud.sleuth.html - if [[ "${file}" == "${MAIN_ADOC_VALUE}.html" ]] ; then + if [[ ("${file}" == "${MAIN_ADOC_VALUE}.html") || ("${file}" == "${REPO_NAME}.html") ]] ; then # We don't want to copy the spring-cloud-sleuth.html # we want it to be converted to index.html cp -rf $f ${destination}/index.html @@ -207,15 +226,19 @@ function commit_changes_if_applicable() { # Switch back to the previous branch and exit block function checkout_previous_branch() { - git checkout ${CURRENT_BRANCH} + # If -version was provided we need to come back to root project + cd ${ROOT_FOLDER} + git checkout ${CURRENT_BRANCH} || echo "Failed to check the branch... continuing with the script" if [ "$dirty" != "0" ]; then git stash pop; fi exit 0 } # Assert if properties have been properly passed function assert_properties() { -if [[ "${VERSION}" != "" && -z "${DESTINATION}" ]] ; then echo "Version was set but destination was not!"; exit 1;fi -if [[ "${DESTINATION}" != "" && -z "${VERSION}" ]] ; then echo "Destination was set but version was not!"; exit 1;fi +echo "VERSION [${VERSION}], DESTINATION [${DESTINATION}], CLONE [${CLONE}]" +if [[ "${VERSION}" != "" && (-z "${DESTINATION}" && -z "${CLONE}") ]] ; then echo "Version was set but destination / clone was not!"; exit 1;fi +if [[ ("${DESTINATION}" != "" && "${CLONE}" != "") && -z "${VERSION}" ]] ; then echo "Destination / clone was set but version was not!"; exit 1;fi +if [[ "${DESTINATION}" != "" && "${CLONE}" == "yes" ]] ; then echo "Destination and clone was set. Pick one!"; exit 1;fi } # Prints the usage @@ -232,13 +255,19 @@ the script will work in the following manner: with that version number will be created in the gh-pages branch. WARNING! No whitelist verification will take place - if the destination switch is passed (-d) then the script will check if the provided dir is a git repo and then will switch to gh-pages of that repo and copy the generated docs to `docs//` +- if the destination switch is passed (-d) then the script will check if the provided dir is a git repo and then will + switch to gh-pages of that repo and copy the generated docs to `docs//` USAGE: You can use the following options: -v|--version - the script will apply the whole procedure for a particular library version --d|--destination - the root of destination folder where the docs should be copied. E.g. point to spring-cloud-static folder +-d|--destination - the root of destination folder where the docs should be copied. You have to use the full path. + E.g. point to spring-cloud-static folder. Can't be used with (-c) +-b|--build - will run the standard build process after checking out the branch +-c|--clone - will automatically clone the spring-cloud-static repo instead of providing the destination. + Obviously can't be used with (-d) EOF } @@ -266,6 +295,12 @@ case ${key} in DESTINATION="$2" shift # past argument ;; + -b|--build) + BUILD="yes" + ;; + -c|--clone) + CLONE="yes" + ;; -h|--help) print_usage exit 0 @@ -287,6 +322,7 @@ if [[ -z "${VERSION}" ]] ; then else switch_to_tag fi +build_docs_if_applicable retrieve_doc_properties stash_changes add_docs_from_target From 1b348abbdeaeba89c1e3de7318b49cfba8fd61ef Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Fri, 19 Aug 2016 13:25:12 +0200 Subject: [PATCH 053/235] Added docs on how to provide custom serviceName without this entry in the docs people got confused on how to do this. fixes #346 --- benchmarks/pom.xml | 2 +- .../main/asciidoc/spring-cloud-sleuth.adoc | 13 ++++++ pom.xml | 2 +- .../zipkin/HttpZipkinSpanReporterTest.java | 43 ++++++++++++++++++- 4 files changed, 57 insertions(+), 3 deletions(-) diff --git a/benchmarks/pom.xml b/benchmarks/pom.xml index 36dead8a4..37b90d0df 100644 --- a/benchmarks/pom.xml +++ b/benchmarks/pom.xml @@ -103,7 +103,7 @@ org.assertj assertj-core - 2.1.0 + 3.5.2 compile diff --git a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc index 330c4dfc5..f0ccc7bbe 100644 --- a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc +++ b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc @@ -272,6 +272,19 @@ And you could register them like this: include::../../../..//spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterCustomExtractorTests.java[tags=configuration,indent=0] ---- +=== Custom SA tag in Zipkin + +Sometimes you want to create a manual Span that will wrap a call to an external service which is not instrumented. +What you can do is to create a span with the `peer.service` tag that will contain a value of the service that you want to call. +Below you can see an example of a call to Redis that is wrapped in such a span. + +[source,java] +---- +include::../../../..//spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/HttpZipkinSpanReporterTest.java[tags=service_name,indent=0] +---- + +IMPORTANT: Remember not to add both `peer.service` tag and the `SA` tag! You have to add only `peer.service`. + == Span Data as Messages You can accumulate and send span data over diff --git a/pom.xml b/pom.xml index 09fe00956..7e0194875 100644 --- a/pom.xml +++ b/pom.xml @@ -195,7 +195,7 @@ org.assertj assertj-core - 3.5.1 + 3.5.2 test diff --git a/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/HttpZipkinSpanReporterTest.java b/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/HttpZipkinSpanReporterTest.java index 02ace6de6..c30ce440e 100644 --- a/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/HttpZipkinSpanReporterTest.java +++ b/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/HttpZipkinSpanReporterTest.java @@ -1,16 +1,27 @@ package org.springframework.cloud.sleuth.zipkin; +import java.util.Random; +import java.util.concurrent.atomic.AtomicReference; + import org.junit.Rule; import org.junit.Test; +import org.springframework.boot.autoconfigure.web.ServerProperties; +import org.springframework.cloud.sleuth.DefaultSpanNamer; +import org.springframework.cloud.sleuth.Tracer; +import org.springframework.cloud.sleuth.log.NoOpSpanLogger; import org.springframework.cloud.sleuth.metric.CounterServiceBasedSpanMetricReporter; import org.springframework.cloud.sleuth.metric.SpanMetricReporter; -import zipkin.Span; +import org.springframework.cloud.sleuth.sampler.AlwaysSampler; +import org.springframework.cloud.sleuth.trace.DefaultTracer; +import org.springframework.cloud.sleuth.util.ExceptionUtils; +import zipkin.Span; import zipkin.junit.HttpFailure; import zipkin.junit.ZipkinRule; import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.BDDAssertions.then; public class HttpZipkinSpanReporterTest { @@ -113,6 +124,36 @@ public class HttpZipkinSpanReporterTest { assertThat(this.inMemorySpanCounter.getDroppedSpans()).isEqualTo(2); } + @Test + public void should_change_the_service_name_in_zipkin_to_the_manually_provided_one() { + AtomicReference receivedSpan = new AtomicReference<>(); + Tracer tracer = new DefaultTracer(new AlwaysSampler(), new Random(), new DefaultSpanNamer(), + new NoOpSpanLogger(), new ZipkinSpanListener(receivedSpan::set, + new ServerPropertiesEndpointLocator(new ServerProperties(), "foo"))); + // tag::service_name[] + org.springframework.cloud.sleuth.Span newSpan = tracer.createSpan("redis"); + try { + newSpan.tag("redis.op", "get"); + newSpan.tag("lc", "redis"); + newSpan.logEvent(org.springframework.cloud.sleuth.Span.CLIENT_SEND); + // call redis service e.g + // return (SomeObj) redisTemplate.opsForHash().get("MYHASH", someObjKey); + } finally { + newSpan.tag("peer.service", "redisService"); + newSpan.tag("peer.ipv4", "1.2.3.4"); + newSpan.tag("peer.port", "1234"); + newSpan.logEvent(org.springframework.cloud.sleuth.Span.CLIENT_RECV); + tracer.close(newSpan); + } + // end::service_name[] + + then(tracer.getCurrentSpan()).isNull(); + then(ExceptionUtils.getLastException()).isNull(); + then(receivedSpan.get().binaryAnnotations) + .flatExtracting(input -> input.key, input -> new String(input.value)) + .contains("peer.service", "redisService"); + } + static Span span(long traceId, String spanName) { return Span.builder().traceId(traceId).id(traceId).name(spanName).build(); } From f56779e2554460f7dc7315516f0f00231ec99e7d Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Fri, 19 Aug 2016 14:26:49 +0200 Subject: [PATCH 054/235] Bumping versions before release --- README.adoc | 3 +++ benchmarks/pom.xml | 2 +- docs/pom.xml | 2 +- pom.xml | 6 +++--- spring-cloud-sleuth-core/pom.xml | 2 +- spring-cloud-sleuth-dependencies/pom.xml | 6 +++--- spring-cloud-sleuth-samples/pom.xml | 2 +- .../spring-cloud-sleuth-sample-feign/pom.xml | 2 +- .../spring-cloud-sleuth-sample-messaging/pom.xml | 2 +- .../spring-cloud-sleuth-sample-ribbon/pom.xml | 2 +- .../spring-cloud-sleuth-sample-stream/pom.xml | 2 +- .../spring-cloud-sleuth-sample-test-core/pom.xml | 2 +- .../spring-cloud-sleuth-sample-websocket/pom.xml | 2 +- .../spring-cloud-sleuth-sample-zipkin-stream/README.md | 2 +- .../spring-cloud-sleuth-sample-zipkin-stream/pom.xml | 2 +- .../spring-cloud-sleuth-sample-zipkin/pom.xml | 2 +- .../spring-cloud-sleuth-sample/pom.xml | 2 +- spring-cloud-sleuth-stream/pom.xml | 2 +- spring-cloud-sleuth-zipkin-stream/pom.xml | 2 +- spring-cloud-sleuth-zipkin/pom.xml | 2 +- spring-cloud-starter-sleuth/pom.xml | 2 +- spring-cloud-starter-zipkin/pom.xml | 2 +- 22 files changed, 28 insertions(+), 25 deletions(-) diff --git a/README.adoc b/README.adoc index ffbb1f0ea..85f99be14 100644 --- a/README.adoc +++ b/README.adoc @@ -239,6 +239,9 @@ Below you can find an example of a Logback configuration (file named `https://gi ----- +NOTE: If you're using a custom `logback-spring.xml` then you have to pass the `spring.application.name` in +`bootstrap` instead of `application` property file. Otherwise your custom logback file won't read the property properly. + === Adding to the project ==== Only Sleuth (log correlation) diff --git a/benchmarks/pom.xml b/benchmarks/pom.xml index 37b90d0df..25ecab6a3 100644 --- a/benchmarks/pom.xml +++ b/benchmarks/pom.xml @@ -17,7 +17,7 @@ Benchmarks Benchmarks (JMH) org.springframework.cloud - 1.0.5.BUILD-SNAPSHOT + 1.0.5.RELEASE benchmarks diff --git a/docs/pom.xml b/docs/pom.xml index 276af00bb..e129eb669 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.5.BUILD-SNAPSHOT + 1.0.5.RELEASE spring-cloud-sleuth-docs pom diff --git a/pom.xml b/pom.xml index 7e0194875..dd0582622 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 spring-cloud-sleuth - 1.0.5.BUILD-SNAPSHOT + 1.0.5.RELEASE pom Spring Cloud Sleuth Spring Cloud Sleuth @@ -13,7 +13,7 @@ org.springframework.cloud spring-cloud-build - 1.1.2.BUILD-SNAPSHOT + 1.1.2.RELEASE @@ -208,7 +208,7 @@ 1.8 2.19.1 2.17 - 1.1.2.BUILD-SNAPSHOT + 1.1.2.RELEASE diff --git a/spring-cloud-sleuth-core/pom.xml b/spring-cloud-sleuth-core/pom.xml index 88741d86e..0f627df08 100644 --- a/spring-cloud-sleuth-core/pom.xml +++ b/spring-cloud-sleuth-core/pom.xml @@ -11,7 +11,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.5.BUILD-SNAPSHOT + 1.0.5.RELEASE .. diff --git a/spring-cloud-sleuth-dependencies/pom.xml b/spring-cloud-sleuth-dependencies/pom.xml index f040b3909..5dc053e86 100644 --- a/spring-cloud-sleuth-dependencies/pom.xml +++ b/spring-cloud-sleuth-dependencies/pom.xml @@ -5,16 +5,16 @@ spring-cloud-dependencies-parent org.springframework.cloud - 1.1.2.BUILD-SNAPSHOT + 1.1.2.RELEASE spring-cloud-sleuth-dependencies - 1.0.5.BUILD-SNAPSHOT + 1.0.5.RELEASE pom spring-cloud-sleuth-dependencies Spring Cloud Sleuth Dependencies - 1.1.6.BUILD-SNAPSHOT + 1.1.5.RELEASE 1.8.4 1.1.5 diff --git a/spring-cloud-sleuth-samples/pom.xml b/spring-cloud-sleuth-samples/pom.xml index 8c69bc237..8947a9ca0 100644 --- a/spring-cloud-sleuth-samples/pom.xml +++ b/spring-cloud-sleuth-samples/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.5.BUILD-SNAPSHOT + 1.0.5.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml index c5e540069..94bd9beeb 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml @@ -20,7 +20,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.5.BUILD-SNAPSHOT + 1.0.5.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml index b4ea43b89..de701e4f9 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.5.BUILD-SNAPSHOT + 1.0.5.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml index 53c1d4d94..636634e04 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.5.BUILD-SNAPSHOT + 1.0.5.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml index c4f2b187b..58f4757f4 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.5.BUILD-SNAPSHOT + 1.0.5.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml index 6ed31bd65..b2c8affc4 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.5.BUILD-SNAPSHOT + 1.0.5.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml index 5d77dd761..a00a29d0c 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.5.BUILD-SNAPSHOT + 1.0.5.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/README.md b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/README.md index 033f555b7..b5d0a6960 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/README.md +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/README.md @@ -20,7 +20,7 @@ or running the packaged app from the root: ``` ./mvnw package --projects spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream -java -jar spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/target/spring-cloud-sleuth-sample-zipkin-stream-1.0.0.BUILD-SNAPSHOT-exec.jar +java -jar spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/target/spring-cloud-sleuth-sample-zipkin-stream-1.0.0.RELEASE-exec.jar ``` and test it diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml index 78f4ab0e5..40b0d0aad 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.5.BUILD-SNAPSHOT + 1.0.5.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml index af1c2b245..fc1cc0f42 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml @@ -27,7 +27,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.5.BUILD-SNAPSHOT + 1.0.5.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml index e67cf7e06..a4b265d5a 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.5.BUILD-SNAPSHOT + 1.0.5.RELEASE .. diff --git a/spring-cloud-sleuth-stream/pom.xml b/spring-cloud-sleuth-stream/pom.xml index 3b8630558..47b9b728a 100644 --- a/spring-cloud-sleuth-stream/pom.xml +++ b/spring-cloud-sleuth-stream/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.5.BUILD-SNAPSHOT + 1.0.5.RELEASE diff --git a/spring-cloud-sleuth-zipkin-stream/pom.xml b/spring-cloud-sleuth-zipkin-stream/pom.xml index 31911a7c8..c4baa28e5 100644 --- a/spring-cloud-sleuth-zipkin-stream/pom.xml +++ b/spring-cloud-sleuth-zipkin-stream/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.5.BUILD-SNAPSHOT + 1.0.5.RELEASE diff --git a/spring-cloud-sleuth-zipkin/pom.xml b/spring-cloud-sleuth-zipkin/pom.xml index aea595d9d..5bd3e008b 100644 --- a/spring-cloud-sleuth-zipkin/pom.xml +++ b/spring-cloud-sleuth-zipkin/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.5.BUILD-SNAPSHOT + 1.0.5.RELEASE .. diff --git a/spring-cloud-starter-sleuth/pom.xml b/spring-cloud-starter-sleuth/pom.xml index 1cf8d94d6..075767889 100644 --- a/spring-cloud-starter-sleuth/pom.xml +++ b/spring-cloud-starter-sleuth/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.5.BUILD-SNAPSHOT + 1.0.5.RELEASE .. spring-cloud-starter-sleuth diff --git a/spring-cloud-starter-zipkin/pom.xml b/spring-cloud-starter-zipkin/pom.xml index d86002f47..770302667 100644 --- a/spring-cloud-starter-zipkin/pom.xml +++ b/spring-cloud-starter-zipkin/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.5.BUILD-SNAPSHOT + 1.0.5.RELEASE .. spring-cloud-starter-zipkin From 2bad78ee4b02184e336e160483487319c80c6497 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Fri, 19 Aug 2016 14:56:28 +0200 Subject: [PATCH 055/235] Going back to snapshots after release --- benchmarks/pom.xml | 2 +- docs/pom.xml | 2 +- pom.xml | 6 +++--- spring-cloud-sleuth-core/pom.xml | 2 +- spring-cloud-sleuth-dependencies/pom.xml | 6 +++--- spring-cloud-sleuth-samples/pom.xml | 2 +- .../spring-cloud-sleuth-sample-feign/pom.xml | 2 +- .../spring-cloud-sleuth-sample-messaging/pom.xml | 2 +- .../spring-cloud-sleuth-sample-ribbon/pom.xml | 2 +- .../spring-cloud-sleuth-sample-stream/pom.xml | 2 +- .../spring-cloud-sleuth-sample-test-core/pom.xml | 2 +- .../spring-cloud-sleuth-sample-websocket/pom.xml | 2 +- .../spring-cloud-sleuth-sample-zipkin-stream/README.md | 2 +- .../spring-cloud-sleuth-sample-zipkin-stream/pom.xml | 2 +- .../spring-cloud-sleuth-sample-zipkin/pom.xml | 2 +- .../spring-cloud-sleuth-sample/pom.xml | 2 +- spring-cloud-sleuth-stream/pom.xml | 2 +- spring-cloud-sleuth-zipkin-stream/pom.xml | 2 +- spring-cloud-sleuth-zipkin/pom.xml | 2 +- spring-cloud-starter-sleuth/pom.xml | 2 +- spring-cloud-starter-zipkin/pom.xml | 2 +- 21 files changed, 25 insertions(+), 25 deletions(-) diff --git a/benchmarks/pom.xml b/benchmarks/pom.xml index 25ecab6a3..6e545f9c6 100644 --- a/benchmarks/pom.xml +++ b/benchmarks/pom.xml @@ -17,7 +17,7 @@ Benchmarks Benchmarks (JMH) org.springframework.cloud - 1.0.5.RELEASE + 1.0.6.BUILD-SNAPSHOT benchmarks diff --git a/docs/pom.xml b/docs/pom.xml index e129eb669..68936e70a 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.5.RELEASE + 1.0.6.BUILD-SNAPSHOT spring-cloud-sleuth-docs pom diff --git a/pom.xml b/pom.xml index dd0582622..b8347a37a 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 spring-cloud-sleuth - 1.0.5.RELEASE + 1.0.6.BUILD-SNAPSHOT pom Spring Cloud Sleuth Spring Cloud Sleuth @@ -13,7 +13,7 @@ org.springframework.cloud spring-cloud-build - 1.1.2.RELEASE + 1.1.2.BUILD-SNAPSHOT @@ -208,7 +208,7 @@ 1.8 2.19.1 2.17 - 1.1.2.RELEASE + 1.1.2.BUILD-SNAPSHOT diff --git a/spring-cloud-sleuth-core/pom.xml b/spring-cloud-sleuth-core/pom.xml index 0f627df08..ce077fa22 100644 --- a/spring-cloud-sleuth-core/pom.xml +++ b/spring-cloud-sleuth-core/pom.xml @@ -11,7 +11,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.5.RELEASE + 1.0.6.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-dependencies/pom.xml b/spring-cloud-sleuth-dependencies/pom.xml index 5dc053e86..68c299cc3 100644 --- a/spring-cloud-sleuth-dependencies/pom.xml +++ b/spring-cloud-sleuth-dependencies/pom.xml @@ -5,16 +5,16 @@ spring-cloud-dependencies-parent org.springframework.cloud - 1.1.2.RELEASE + 1.1.2.BUILD-SNAPSHOT spring-cloud-sleuth-dependencies - 1.0.5.RELEASE + 1.0.6.BUILD-SNAPSHOT pom spring-cloud-sleuth-dependencies Spring Cloud Sleuth Dependencies - 1.1.5.RELEASE + 1.1.6.BUILD-SNAPSHOT 1.8.4 1.1.5 diff --git a/spring-cloud-sleuth-samples/pom.xml b/spring-cloud-sleuth-samples/pom.xml index 8947a9ca0..19a8634d0 100644 --- a/spring-cloud-sleuth-samples/pom.xml +++ b/spring-cloud-sleuth-samples/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.5.RELEASE + 1.0.6.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml index 94bd9beeb..c9a875ed2 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml @@ -20,7 +20,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.5.RELEASE + 1.0.6.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml index de701e4f9..c7b3b613a 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.5.RELEASE + 1.0.6.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml index 636634e04..de4fa1214 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.5.RELEASE + 1.0.6.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml index 58f4757f4..bb1856df4 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.5.RELEASE + 1.0.6.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml index b2c8affc4..20981f345 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.5.RELEASE + 1.0.6.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml index a00a29d0c..0e67da0d0 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.5.RELEASE + 1.0.6.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/README.md b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/README.md index b5d0a6960..033f555b7 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/README.md +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/README.md @@ -20,7 +20,7 @@ or running the packaged app from the root: ``` ./mvnw package --projects spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream -java -jar spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/target/spring-cloud-sleuth-sample-zipkin-stream-1.0.0.RELEASE-exec.jar +java -jar spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/target/spring-cloud-sleuth-sample-zipkin-stream-1.0.0.BUILD-SNAPSHOT-exec.jar ``` and test it diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml index 40b0d0aad..4bb412e2d 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.5.RELEASE + 1.0.6.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml index fc1cc0f42..ac9baa8d8 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml @@ -27,7 +27,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.5.RELEASE + 1.0.6.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml index a4b265d5a..ec38203de 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.5.RELEASE + 1.0.6.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-stream/pom.xml b/spring-cloud-sleuth-stream/pom.xml index 47b9b728a..2ca457587 100644 --- a/spring-cloud-sleuth-stream/pom.xml +++ b/spring-cloud-sleuth-stream/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.5.RELEASE + 1.0.6.BUILD-SNAPSHOT diff --git a/spring-cloud-sleuth-zipkin-stream/pom.xml b/spring-cloud-sleuth-zipkin-stream/pom.xml index c4baa28e5..d23a3f443 100644 --- a/spring-cloud-sleuth-zipkin-stream/pom.xml +++ b/spring-cloud-sleuth-zipkin-stream/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.5.RELEASE + 1.0.6.BUILD-SNAPSHOT diff --git a/spring-cloud-sleuth-zipkin/pom.xml b/spring-cloud-sleuth-zipkin/pom.xml index 5bd3e008b..ca554f030 100644 --- a/spring-cloud-sleuth-zipkin/pom.xml +++ b/spring-cloud-sleuth-zipkin/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.5.RELEASE + 1.0.6.BUILD-SNAPSHOT .. diff --git a/spring-cloud-starter-sleuth/pom.xml b/spring-cloud-starter-sleuth/pom.xml index 075767889..3eee8d8a6 100644 --- a/spring-cloud-starter-sleuth/pom.xml +++ b/spring-cloud-starter-sleuth/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.5.RELEASE + 1.0.6.BUILD-SNAPSHOT .. spring-cloud-starter-sleuth diff --git a/spring-cloud-starter-zipkin/pom.xml b/spring-cloud-starter-zipkin/pom.xml index 770302667..6804a68a9 100644 --- a/spring-cloud-starter-zipkin/pom.xml +++ b/spring-cloud-starter-zipkin/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.5.RELEASE + 1.0.6.BUILD-SNAPSHOT .. spring-cloud-starter-zipkin From ef655267a86102c84d89b2b05be7d0cbce71ae9b Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Fri, 19 Aug 2016 15:07:06 +0200 Subject: [PATCH 056/235] Fixed a typo in docs --- docs/src/main/asciidoc/spring-cloud-sleuth.adoc | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc index f0ccc7bbe..d27edffc1 100644 --- a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc +++ b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc @@ -34,8 +34,8 @@ to Zipkin or Spring Cloud Stream, there is also an `AlwaysSampler` that exports everything and a `PercentageBasedSampler` that samples a fixed fraction of spans. -NOTE: the `PercentageBasedSampler` is the default if you are using -`spring-cloud-sleuth-zipkin` or `spring-cloud-sleuth-stream`. You can +NOTE: the `PercentageBasedSampler` is the default if you are using +`spring-cloud-sleuth-zipkin` or `spring-cloud-sleuth-stream`. You can configure the exports using `spring.sleuth.sampler.percentage`. A sampler can be installed just by creating a bean definition, e.g: @@ -298,7 +298,7 @@ type `Spans`. === Zipkin Consumer -There is a special convenience annotation for setting up a message consumer +There is a special convenience annotation for setting up a message consumer for the Span data and pushing it into a Zipkin `SpanStore`. This application [source,java] @@ -485,7 +485,7 @@ wrap `ThreadPoolTaskScheduler` in a `TraceAsyncListenableTaskExecutor`). Example [source,java] ---- -include::../../../../spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfigurationTest.java[tags=async_template_factories,indent=0] +include::../../../../spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfigurationTests.java[tags=async_template_factories,indent=0] ---- To block the `AsyncRestTemplate` features set `spring.sleuth.web.async.client.enabled` to `false`. @@ -564,4 +564,4 @@ To disable Zuul support set the `spring.sleuth.zuul.enabled` property to `false` You can find the running examples deployed in the https://run.pivotal.io/[Pivotal Web Services]. Check them out in the following links: - http://docssleuth-zipkin-server.cfapps.io/[Zipkin for apps presented in the samples to the top] -- http://docsbrewing-zipkin-web.cfapps.io/[Zipkin for Brewery on PWS], its https://github.com/spring-cloud-samples/brewery[Github Code] \ No newline at end of file +- http://docsbrewing-zipkin-web.cfapps.io/[Zipkin for Brewery on PWS], its https://github.com/spring-cloud-samples/brewery[Github Code] From b7f0c4e7c5c3142800074e676fa2955135544142 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Mon, 22 Aug 2016 12:22:26 +0200 Subject: [PATCH 057/235] Fixing TraceFilter order (#382) after making TraceFilter process different dispatch types we've introduced a bug related to filter ordering. TraceFilter was registered with a default ordering which is of lowest precedence. With this change we ensure that the ordering of TraceFilter is fixed. Fixes #380 --- .../sleuth/instrument/web/TraceFilter.java | 4 +- .../web/TraceWebAutoConfiguration.java | 3 +- ...terWebIntegrationMultipleFiltersTests.java | 136 ++++++++++++++++++ 3 files changed, 141 insertions(+), 2 deletions(-) create mode 100644 spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterWebIntegrationMultipleFiltersTests.java 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 50faa46a6..cdf25664c 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 @@ -65,13 +65,15 @@ import org.springframework.web.util.UrlPathHelper; * @see TraceKeys * @see TraceWebAutoConfiguration#traceFilter */ -@Order(Ordered.HIGHEST_PRECEDENCE + 5) +@Order(TraceFilter.ORDER) public class TraceFilter extends GenericFilterBean { private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); private static final String HTTP_COMPONENT = "http"; + protected static final int ORDER = Ordered.HIGHEST_PRECEDENCE + 5; + protected static final String TRACE_REQUEST_ATTR = TraceFilter.class.getName() + ".TRACE"; diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java index e2b00eeaf..39cd582f2 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java @@ -15,8 +15,8 @@ */ package org.springframework.cloud.sleuth.instrument.web; -import javax.servlet.http.HttpServletRequest; import java.util.regex.Pattern; +import javax.servlet.http.HttpServletRequest; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.annotation.Value; @@ -90,6 +90,7 @@ public class TraceWebAutoConfiguration { HttpTraceKeysInjector httpTraceKeysInjector, TraceFilter traceFilter) { FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(traceFilter); filterRegistrationBean.setDispatcherTypes(ASYNC, ERROR, FORWARD, INCLUDE, REQUEST); + filterRegistrationBean.setOrder(TraceFilter.ORDER); return filterRegistrationBean; } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterWebIntegrationMultipleFiltersTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterWebIntegrationMultipleFiltersTests.java new file mode 100644 index 000000000..e51800a45 --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterWebIntegrationMultipleFiltersTests.java @@ -0,0 +1,136 @@ +/* + * 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.io.IOException; +import java.util.concurrent.atomic.AtomicReference; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.context.embedded.FilterRegistrationBean; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.boot.test.WebIntegrationTest; +import org.springframework.cloud.sleuth.Sampler; +import org.springframework.cloud.sleuth.Span; +import org.springframework.cloud.sleuth.Tracer; +import org.springframework.cloud.sleuth.sampler.AlwaysSampler; +import org.springframework.cloud.sleuth.trace.TestSpanContextHolder; +import org.springframework.cloud.sleuth.util.ExceptionUtils; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.web.client.DefaultResponseErrorHandler; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.filter.GenericFilterBean; + +import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; + +/** + * @author Marcin Grzejszczak + */ +@RunWith(SpringJUnit4ClassRunner.class) +@WebIntegrationTest({ "server.port=0" }) +@SpringApplicationConfiguration(classes = { TraceFilterWebIntegrationMultipleFiltersTests.Config.class }) +public class TraceFilterWebIntegrationMultipleFiltersTests { + + @Autowired Tracer tracer; + @Autowired RestTemplate restTemplate; + @Autowired Environment environment; + @Autowired MyFilter myFilter; + + @Before + @After + public void cleanup() { + ExceptionUtils.setFail(true); + TestSpanContextHolder.removeCurrentSpan(); + } + + @Test + public void should_register_trace_filter_before_the_custom_filter() { + this.restTemplate.getForObject("http://localhost:" + port() + "/", String.class); + + then(this.tracer.getCurrentSpan()).isNull(); + then(this.myFilter.getSpan().get()).isNotNull(); + then(ExceptionUtils.getLastException()).isNull(); + } + + private int port() { + return this.environment.getProperty("local.server.port", Integer.class); + } + + @EnableAutoConfiguration + @Configuration + public static class Config { + + @Bean Sampler alwaysSampler() { + return new AlwaysSampler(); + } + + + @Bean RestTemplate restTemplate() { + RestTemplate restTemplate = new RestTemplate(); + restTemplate.setErrorHandler(new DefaultResponseErrorHandler() { + @Override public void handleError(ClientHttpResponse response) + throws IOException { + } + }); + return restTemplate; + } + + @Bean MyFilter myFilter(Tracer tracer) { + return new MyFilter(tracer); + } + + @Bean FilterRegistrationBean registrationBean(MyFilter myFilter) { + FilterRegistrationBean bean = new FilterRegistrationBean(); + bean.setFilter(myFilter); + bean.setOrder(0); + return bean; + } + } + + static class MyFilter extends GenericFilterBean { + + AtomicReference span = new AtomicReference<>(); + + private final Tracer tracer; + + MyFilter(Tracer tracer) { + this.tracer = tracer; + } + + @Override public void doFilter(ServletRequest request, ServletResponse response, + FilterChain chain) throws IOException, ServletException { + Span currentSpan = tracer.getCurrentSpan(); + this.span.set(currentSpan); + } + + public AtomicReference getSpan() { + return span; + } + } +} From 995371fc0d5cebd8d76784080fb387f21b1ed12c Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Mon, 22 Aug 2016 12:39:30 +0200 Subject: [PATCH 058/235] Bumping versions before release --- benchmarks/pom.xml | 2 +- docs/pom.xml | 2 +- pom.xml | 6 +++--- spring-cloud-sleuth-core/pom.xml | 2 +- spring-cloud-sleuth-dependencies/pom.xml | 6 +++--- spring-cloud-sleuth-samples/pom.xml | 2 +- .../spring-cloud-sleuth-sample-feign/pom.xml | 2 +- .../spring-cloud-sleuth-sample-messaging/pom.xml | 2 +- .../spring-cloud-sleuth-sample-ribbon/pom.xml | 2 +- .../spring-cloud-sleuth-sample-stream/pom.xml | 2 +- .../spring-cloud-sleuth-sample-test-core/pom.xml | 2 +- .../spring-cloud-sleuth-sample-websocket/pom.xml | 2 +- .../spring-cloud-sleuth-sample-zipkin-stream/pom.xml | 2 +- .../spring-cloud-sleuth-sample-zipkin/pom.xml | 2 +- .../spring-cloud-sleuth-sample/pom.xml | 2 +- spring-cloud-sleuth-stream/pom.xml | 2 +- spring-cloud-sleuth-zipkin-stream/pom.xml | 2 +- spring-cloud-sleuth-zipkin/pom.xml | 2 +- spring-cloud-starter-sleuth/pom.xml | 2 +- spring-cloud-starter-zipkin/pom.xml | 2 +- 20 files changed, 24 insertions(+), 24 deletions(-) diff --git a/benchmarks/pom.xml b/benchmarks/pom.xml index 6e545f9c6..0f018bcb2 100644 --- a/benchmarks/pom.xml +++ b/benchmarks/pom.xml @@ -17,7 +17,7 @@ Benchmarks Benchmarks (JMH) org.springframework.cloud - 1.0.6.BUILD-SNAPSHOT + 1.0.6.RELEASE benchmarks diff --git a/docs/pom.xml b/docs/pom.xml index 68936e70a..afdb4af08 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.6.BUILD-SNAPSHOT + 1.0.6.RELEASE spring-cloud-sleuth-docs pom diff --git a/pom.xml b/pom.xml index b8347a37a..ee31c2abd 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 spring-cloud-sleuth - 1.0.6.BUILD-SNAPSHOT + 1.0.6.RELEASE pom Spring Cloud Sleuth Spring Cloud Sleuth @@ -13,7 +13,7 @@ org.springframework.cloud spring-cloud-build - 1.1.2.BUILD-SNAPSHOT + 1.1.2.RELEASE @@ -208,7 +208,7 @@ 1.8 2.19.1 2.17 - 1.1.2.BUILD-SNAPSHOT + 1.1.2.RELEASE diff --git a/spring-cloud-sleuth-core/pom.xml b/spring-cloud-sleuth-core/pom.xml index ce077fa22..f8d8301df 100644 --- a/spring-cloud-sleuth-core/pom.xml +++ b/spring-cloud-sleuth-core/pom.xml @@ -11,7 +11,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.6.BUILD-SNAPSHOT + 1.0.6.RELEASE .. diff --git a/spring-cloud-sleuth-dependencies/pom.xml b/spring-cloud-sleuth-dependencies/pom.xml index 68c299cc3..fb7b01bf6 100644 --- a/spring-cloud-sleuth-dependencies/pom.xml +++ b/spring-cloud-sleuth-dependencies/pom.xml @@ -5,16 +5,16 @@ spring-cloud-dependencies-parent org.springframework.cloud - 1.1.2.BUILD-SNAPSHOT + 1.1.2.RELEASE spring-cloud-sleuth-dependencies - 1.0.6.BUILD-SNAPSHOT + 1.0.6.RELEASE pom spring-cloud-sleuth-dependencies Spring Cloud Sleuth Dependencies - 1.1.6.BUILD-SNAPSHOT + 1.1.5.RELEASE 1.8.4 1.1.5 diff --git a/spring-cloud-sleuth-samples/pom.xml b/spring-cloud-sleuth-samples/pom.xml index 19a8634d0..2ae452907 100644 --- a/spring-cloud-sleuth-samples/pom.xml +++ b/spring-cloud-sleuth-samples/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.6.BUILD-SNAPSHOT + 1.0.6.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml index c9a875ed2..dfeb8ea06 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml @@ -20,7 +20,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.6.BUILD-SNAPSHOT + 1.0.6.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml index c7b3b613a..0a301c63d 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.6.BUILD-SNAPSHOT + 1.0.6.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml index de4fa1214..4e9f72318 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.6.BUILD-SNAPSHOT + 1.0.6.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml index bb1856df4..79f9a6886 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.6.BUILD-SNAPSHOT + 1.0.6.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml index 20981f345..5c9e4ab1e 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.6.BUILD-SNAPSHOT + 1.0.6.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml index 0e67da0d0..56904c147 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.6.BUILD-SNAPSHOT + 1.0.6.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml index 4bb412e2d..bcc358d33 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.6.BUILD-SNAPSHOT + 1.0.6.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml index ac9baa8d8..8ebd68b53 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml @@ -27,7 +27,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.6.BUILD-SNAPSHOT + 1.0.6.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml index ec38203de..aafa84889 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.6.BUILD-SNAPSHOT + 1.0.6.RELEASE .. diff --git a/spring-cloud-sleuth-stream/pom.xml b/spring-cloud-sleuth-stream/pom.xml index 2ca457587..4b4b38d2d 100644 --- a/spring-cloud-sleuth-stream/pom.xml +++ b/spring-cloud-sleuth-stream/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.6.BUILD-SNAPSHOT + 1.0.6.RELEASE diff --git a/spring-cloud-sleuth-zipkin-stream/pom.xml b/spring-cloud-sleuth-zipkin-stream/pom.xml index d23a3f443..d7e241da6 100644 --- a/spring-cloud-sleuth-zipkin-stream/pom.xml +++ b/spring-cloud-sleuth-zipkin-stream/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.6.BUILD-SNAPSHOT + 1.0.6.RELEASE diff --git a/spring-cloud-sleuth-zipkin/pom.xml b/spring-cloud-sleuth-zipkin/pom.xml index ca554f030..0b370592c 100644 --- a/spring-cloud-sleuth-zipkin/pom.xml +++ b/spring-cloud-sleuth-zipkin/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.6.BUILD-SNAPSHOT + 1.0.6.RELEASE .. diff --git a/spring-cloud-starter-sleuth/pom.xml b/spring-cloud-starter-sleuth/pom.xml index 3eee8d8a6..61a07cfc3 100644 --- a/spring-cloud-starter-sleuth/pom.xml +++ b/spring-cloud-starter-sleuth/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.6.BUILD-SNAPSHOT + 1.0.6.RELEASE .. spring-cloud-starter-sleuth diff --git a/spring-cloud-starter-zipkin/pom.xml b/spring-cloud-starter-zipkin/pom.xml index 6804a68a9..dc9bd0d36 100644 --- a/spring-cloud-starter-zipkin/pom.xml +++ b/spring-cloud-starter-zipkin/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.6.BUILD-SNAPSHOT + 1.0.6.RELEASE .. spring-cloud-starter-zipkin From cd17b35ea3261c3f30bd1bf23e5e59dfe8b37953 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Mon, 22 Aug 2016 13:14:13 +0200 Subject: [PATCH 059/235] Going back to Snapshots --- benchmarks/pom.xml | 2 +- docs/pom.xml | 2 +- pom.xml | 6 +++--- spring-cloud-sleuth-core/pom.xml | 2 +- spring-cloud-sleuth-dependencies/pom.xml | 6 +++--- spring-cloud-sleuth-samples/pom.xml | 2 +- .../spring-cloud-sleuth-sample-feign/pom.xml | 2 +- .../spring-cloud-sleuth-sample-messaging/pom.xml | 2 +- .../spring-cloud-sleuth-sample-ribbon/pom.xml | 2 +- .../spring-cloud-sleuth-sample-stream/pom.xml | 2 +- .../spring-cloud-sleuth-sample-test-core/pom.xml | 2 +- .../spring-cloud-sleuth-sample-websocket/pom.xml | 2 +- .../spring-cloud-sleuth-sample-zipkin-stream/pom.xml | 2 +- .../spring-cloud-sleuth-sample-zipkin/pom.xml | 2 +- .../spring-cloud-sleuth-sample/pom.xml | 2 +- spring-cloud-sleuth-stream/pom.xml | 2 +- spring-cloud-sleuth-zipkin-stream/pom.xml | 2 +- spring-cloud-sleuth-zipkin/pom.xml | 2 +- spring-cloud-starter-sleuth/pom.xml | 2 +- spring-cloud-starter-zipkin/pom.xml | 2 +- 20 files changed, 24 insertions(+), 24 deletions(-) diff --git a/benchmarks/pom.xml b/benchmarks/pom.xml index 0f018bcb2..d2125385c 100644 --- a/benchmarks/pom.xml +++ b/benchmarks/pom.xml @@ -17,7 +17,7 @@ Benchmarks Benchmarks (JMH) org.springframework.cloud - 1.0.6.RELEASE + 1.0.7.BUILD-SNAPSHOT benchmarks diff --git a/docs/pom.xml b/docs/pom.xml index afdb4af08..8e3b03e75 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.6.RELEASE + 1.0.7.BUILD-SNAPSHOT spring-cloud-sleuth-docs pom diff --git a/pom.xml b/pom.xml index ee31c2abd..a145a57db 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 spring-cloud-sleuth - 1.0.6.RELEASE + 1.0.7.BUILD-SNAPSHOT pom Spring Cloud Sleuth Spring Cloud Sleuth @@ -13,7 +13,7 @@ org.springframework.cloud spring-cloud-build - 1.1.2.RELEASE + 1.1.2.BUILD-SNAPSHOT @@ -208,7 +208,7 @@ 1.8 2.19.1 2.17 - 1.1.2.RELEASE + 1.1.2.BUILD-SNAPSHOT diff --git a/spring-cloud-sleuth-core/pom.xml b/spring-cloud-sleuth-core/pom.xml index f8d8301df..c83c0d53f 100644 --- a/spring-cloud-sleuth-core/pom.xml +++ b/spring-cloud-sleuth-core/pom.xml @@ -11,7 +11,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.6.RELEASE + 1.0.7.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-dependencies/pom.xml b/spring-cloud-sleuth-dependencies/pom.xml index fb7b01bf6..2f2179322 100644 --- a/spring-cloud-sleuth-dependencies/pom.xml +++ b/spring-cloud-sleuth-dependencies/pom.xml @@ -5,16 +5,16 @@ spring-cloud-dependencies-parent org.springframework.cloud - 1.1.2.RELEASE + 1.1.2.BUILD-SNAPSHOT spring-cloud-sleuth-dependencies - 1.0.6.RELEASE + 1.0.7.BUILD-SNAPSHOT pom spring-cloud-sleuth-dependencies Spring Cloud Sleuth Dependencies - 1.1.5.RELEASE + 1.1.6.BUILD-SNAPSHOT 1.8.4 1.1.5 diff --git a/spring-cloud-sleuth-samples/pom.xml b/spring-cloud-sleuth-samples/pom.xml index 2ae452907..8d2695be5 100644 --- a/spring-cloud-sleuth-samples/pom.xml +++ b/spring-cloud-sleuth-samples/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.6.RELEASE + 1.0.7.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml index dfeb8ea06..543ab0b21 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml @@ -20,7 +20,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.6.RELEASE + 1.0.7.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml index 0a301c63d..b7302789b 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.6.RELEASE + 1.0.7.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml index 4e9f72318..1777d4efa 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.6.RELEASE + 1.0.7.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml index 79f9a6886..ec4ed17f7 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.6.RELEASE + 1.0.7.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml index 5c9e4ab1e..2986953a3 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.6.RELEASE + 1.0.7.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml index 56904c147..1676764da 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.6.RELEASE + 1.0.7.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml index bcc358d33..39dae832b 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.6.RELEASE + 1.0.7.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml index 8ebd68b53..eca2b3689 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml @@ -27,7 +27,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.6.RELEASE + 1.0.7.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml index aafa84889..cfe1c4419 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.6.RELEASE + 1.0.7.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-stream/pom.xml b/spring-cloud-sleuth-stream/pom.xml index 4b4b38d2d..b8a19df3c 100644 --- a/spring-cloud-sleuth-stream/pom.xml +++ b/spring-cloud-sleuth-stream/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.6.RELEASE + 1.0.7.BUILD-SNAPSHOT diff --git a/spring-cloud-sleuth-zipkin-stream/pom.xml b/spring-cloud-sleuth-zipkin-stream/pom.xml index d7e241da6..3814bde7b 100644 --- a/spring-cloud-sleuth-zipkin-stream/pom.xml +++ b/spring-cloud-sleuth-zipkin-stream/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.6.RELEASE + 1.0.7.BUILD-SNAPSHOT diff --git a/spring-cloud-sleuth-zipkin/pom.xml b/spring-cloud-sleuth-zipkin/pom.xml index 0b370592c..d7c7c1f75 100644 --- a/spring-cloud-sleuth-zipkin/pom.xml +++ b/spring-cloud-sleuth-zipkin/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.6.RELEASE + 1.0.7.BUILD-SNAPSHOT .. diff --git a/spring-cloud-starter-sleuth/pom.xml b/spring-cloud-starter-sleuth/pom.xml index 61a07cfc3..2fec8da05 100644 --- a/spring-cloud-starter-sleuth/pom.xml +++ b/spring-cloud-starter-sleuth/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.6.RELEASE + 1.0.7.BUILD-SNAPSHOT .. spring-cloud-starter-sleuth diff --git a/spring-cloud-starter-zipkin/pom.xml b/spring-cloud-starter-zipkin/pom.xml index dc9bd0d36..bfc7152ff 100644 --- a/spring-cloud-starter-zipkin/pom.xml +++ b/spring-cloud-starter-zipkin/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.6.RELEASE + 1.0.7.BUILD-SNAPSHOT .. spring-cloud-starter-zipkin From b47ad1613dcdfec73321064944caed6077052a36 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Mon, 22 Aug 2016 13:32:40 +0200 Subject: [PATCH 060/235] Updating ghpages for all projects --- docs/src/main/asciidoc/ghpages.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/src/main/asciidoc/ghpages.sh b/docs/src/main/asciidoc/ghpages.sh index 6a6363ba5..57c5da3a3 100755 --- a/docs/src/main/asciidoc/ghpages.sh +++ b/docs/src/main/asciidoc/ghpages.sh @@ -32,7 +32,7 @@ function check_if_anything_to_sync() { exit 0 fi - if ! [ -d docs/target/generated-docs ]; then + if ! [ -d docs/target/generated-docs ] && ! [ "${BUILD}" == "yes" ]; then echo "No gh-pages sources in docs/target/generated-docs, so not syncing" exit 0 fi @@ -101,12 +101,13 @@ function add_docs_from_target() { local clonedStatic=${ROOT_FOLDER}/target/spring-cloud-static if [[ ! -e "${clonedStatic}/.git" ]]; then echo "Cloning Spring Cloud Static to target" - git clone ${SPRING_CLOUD_STATIC_REPO} ${clonedStatic} + git clone ${SPRING_CLOUD_STATIC_REPO} ${clonedStatic} && git checkout gh-pages else echo "Spring Cloud Static already cloned - will pull changes" cd ${clonedStatic} && git checkout gh-pages && git pull origin gh-pages fi DESTINATION_REPO_FOLDER=${clonedStatic}/${REPO_NAME} + mkdir -p ${DESTINATION_REPO_FOLDER} else if [[ ! -e "${DESTINATION}/.git" ]]; then echo "[${DESTINATION}] is not a git repository" From 634020eea68d1f6664aae62e8eec8cfce75b2a74 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Tue, 23 Aug 2016 15:34:44 +0200 Subject: [PATCH 061/235] Synchronizing on spans in ArrayListSpanAccumulator without this change tests happen to have ConcurrentModificationException --- .../cloud/sleuth/util/ArrayListSpanAccumulator.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/util/ArrayListSpanAccumulator.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/util/ArrayListSpanAccumulator.java index bfaeac69e..2a412d167 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/util/ArrayListSpanAccumulator.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/util/ArrayListSpanAccumulator.java @@ -17,7 +17,6 @@ package org.springframework.cloud.sleuth.util; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import org.springframework.cloud.sleuth.Span; @@ -31,21 +30,25 @@ import org.springframework.cloud.sleuth.SpanReporter; * @since 1.0.0 */ public class ArrayListSpanAccumulator implements SpanReporter { - private final List spans = Collections.synchronizedList(new ArrayList()); + private final List spans = new ArrayList<>(); public List getSpans() { - return this.spans; + synchronized (this.spans) { + return this.spans; + } } @Override public String toString() { return "ArrayListSpanAccumulator{" + - "spans=" + this.spans + + "spans=" + getSpans() + '}'; } @Override public void report(Span span) { - this.spans.add(span); + synchronized (this.spans) { + this.spans.add(span); + } } } From 641af5e2729363172041690285651f1f55269bdd Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Wed, 24 Aug 2016 17:36:41 +0200 Subject: [PATCH 062/235] Bumping sc-build to 1.1.3 --- pom.xml | 4 ++-- spring-cloud-sleuth-dependencies/pom.xml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index a145a57db..733f676aa 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ org.springframework.cloud spring-cloud-build - 1.1.2.BUILD-SNAPSHOT + 1.1.3.BUILD-SNAPSHOT @@ -208,7 +208,7 @@ 1.8 2.19.1 2.17 - 1.1.2.BUILD-SNAPSHOT + 1.1.3.BUILD-SNAPSHOT diff --git a/spring-cloud-sleuth-dependencies/pom.xml b/spring-cloud-sleuth-dependencies/pom.xml index 2f2179322..7706f75a1 100644 --- a/spring-cloud-sleuth-dependencies/pom.xml +++ b/spring-cloud-sleuth-dependencies/pom.xml @@ -5,7 +5,7 @@ spring-cloud-dependencies-parent org.springframework.cloud - 1.1.2.BUILD-SNAPSHOT + 1.1.3.BUILD-SNAPSHOT spring-cloud-sleuth-dependencies From f92950b474b5e6e0881de5ef74c175021736b37a Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Mon, 29 Aug 2016 15:08:41 +0200 Subject: [PATCH 063/235] Added info about root span --- README.adoc | 3 +++ docs/src/main/asciidoc/intro.adoc | 3 +++ 2 files changed, 6 insertions(+) diff --git a/README.adoc b/README.adoc index 85f99be14..c4f9b28fb 100644 --- a/README.adoc +++ b/README.adoc @@ -21,6 +21,9 @@ annotations (tags), the ID of the span that caused them, and process ID's (norma Spans are started and stopped, and they keep track of their timing information. Once you create a span, you must stop it at some point in the future. +TIP: The initial span that starts a trace is called a `root span`. The value of span id +of that span is equal to trace id. + *Trace:* A set of spans forming a tree-like structure. For example, if you are running a distributed big-data store, a trace might be formed by a put request. diff --git a/docs/src/main/asciidoc/intro.adoc b/docs/src/main/asciidoc/intro.adoc index 2123efc36..3e93ec633 100644 --- a/docs/src/main/asciidoc/intro.adoc +++ b/docs/src/main/asciidoc/intro.adoc @@ -12,6 +12,9 @@ annotations (tags), the ID of the span that caused them, and process ID's (norma Spans are started and stopped, and they keep track of their timing information. Once you create a span, you must stop it at some point in the future. +TIP: The initial span that starts a trace is called a `root span`. The value of span id +of that span is equal to trace id. + *Trace:* A set of spans forming a tree-like structure. For example, if you are running a distributed big-data store, a trace might be formed by a put request. From fd543298a05427f3bc3c2cde410669d9ad9165a1 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Tue, 30 Aug 2016 13:07:34 +0200 Subject: [PATCH 064/235] Fixed duplicate stream env post processor entries (#388) when the stream env post processor is executed headers are added endlessly - there is no check for the presence of the tracing headers. with this change a check is added so the tracing headers are added only once. fixes #387 --- .../StreamEnvironmentPostProcessor.java | 20 +++++++-- .../StreamEnvironmentPostProcessorTests.java | 41 +++++++++++++++---- 2 files changed, 50 insertions(+), 11 deletions(-) diff --git a/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/StreamEnvironmentPostProcessor.java b/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/StreamEnvironmentPostProcessor.java index a7cca416e..f93595695 100644 --- a/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/StreamEnvironmentPostProcessor.java +++ b/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/StreamEnvironmentPostProcessor.java @@ -46,7 +46,7 @@ import org.springframework.core.io.support.PropertiesLoaderUtils; public class StreamEnvironmentPostProcessor implements EnvironmentPostProcessor { private static final String PROPERTY_SOURCE_NAME = "defaultProperties"; - private static String[] headers = new String[] { Span.SPAN_ID_NAME, + static String[] headers = new String[] { Span.SPAN_ID_NAME, Span.TRACE_ID_NAME, Span.PARENT_ID_NAME, Span.PROCESS_ID_NAME, Span.SAMPLED_NAME, Span.SPAN_NAME_NAME }; @@ -64,7 +64,7 @@ public class StreamEnvironmentPostProcessor implements EnvironmentPostProcessor .getResources("classpath*:META-INF/spring.binders")) { for (String binderType : parseBinderConfigurations(resource)) { int startIndex = findStartIndex(environment, binderType); - addHeaders(map, binderType, startIndex); + addHeaders(map, environment.getPropertySources(), binderType, startIndex); } } } @@ -125,11 +125,23 @@ public class StreamEnvironmentPostProcessor implements EnvironmentPostProcessor } } - private void addHeaders(Map map, String binder, int startIndex) { + private void addHeaders(Map map, MutablePropertySources propertySources, + String binder, int startIndex) { String stem = "spring.cloud.stream." + binder + ".binder.headers"; for (int i = 0; i < headers.length; i++) { - map.put(stem + "[" + (i + startIndex) + "]", headers[i]); + if (!hasTracingHeadersValue(propertySources, headers[i])) { + map.put(stem + "[" + (i + startIndex) + "]", headers[i]); + } } } + private boolean hasTracingHeadersValue(MutablePropertySources propertySources, String header) { + PropertySource source = propertySources.get(PROPERTY_SOURCE_NAME); + if (source instanceof MapPropertySource) { + Collection values = ((MapPropertySource) source).getSource().values(); + return values.contains(header); + } + return false; + } + } diff --git a/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/StreamEnvironmentPostProcessorTests.java b/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/StreamEnvironmentPostProcessorTests.java index d7e9031be..c90c78591 100644 --- a/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/StreamEnvironmentPostProcessorTests.java +++ b/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/StreamEnvironmentPostProcessorTests.java @@ -16,7 +16,9 @@ package org.springframework.cloud.sleuth.stream; -import static org.assertj.core.api.Assertions.assertThat; +import java.util.Collection; +import java.util.Map; +import java.util.stream.Collectors; import org.junit.Test; import org.springframework.boot.SpringApplication; @@ -25,6 +27,8 @@ import org.springframework.cloud.sleuth.Span; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.StandardEnvironment; +import static org.assertj.core.api.Assertions.assertThat; + /** * @author Dave Syer * @@ -35,21 +39,44 @@ public class StreamEnvironmentPostProcessorTests { private ConfigurableEnvironment environment = new StandardEnvironment(); @Test - public void testHeadersAdded() { - this.processor.postProcessEnvironment(this.environment, - new SpringApplication(StreamEnvironmentPostProcessorTests.class)); + public void should_append_tracing_headers() { + postProcess(); assertThat(this.environment.getProperty("spring.cloud.stream.test.binder.headers[0]")) .isEqualTo(Span.SPAN_ID_NAME); } @Test - public void headersAppendedIfNecessary() { + public void should_append_tracing_headers_to_existing_ones() { EnvironmentTestUtils.addEnvironment(this.environment, "spring.cloud.stream.test.binder.headers[0]=X-Custom", "spring.cloud.stream.test.binder.headers[1]=X-Mine"); - this.processor.postProcessEnvironment(this.environment, - new SpringApplication(StreamEnvironmentPostProcessorTests.class)); + postProcess(); assertThat(this.environment.getProperty("spring.cloud.stream.test.binder.headers[2]")) .isEqualTo(Span.SPAN_ID_NAME); } + @Test + public void should_not_append_tracing_headers_if_they_are_already_appended() { + postProcess(); + postProcess(); + postProcess(); + + Collection headerValues = defaultPropertiesSource().values(); + + Collection traceIds = headerValues.stream() + .filter(input -> input.contains(Span.TRACE_ID_NAME)).collect(Collectors.toList()); + assertThat(traceIds).hasSize(1); + assertThat(defaultPropertiesSource().keySet().stream() + .filter(input -> input.startsWith("spring.cloud.stream.test.binder.headers")) + .collect(Collectors.toList())).hasSize(StreamEnvironmentPostProcessor.headers.length); + } + + private void postProcess() { + this.processor.postProcessEnvironment(this.environment, + new SpringApplication(StreamEnvironmentPostProcessorTests.class)); + } + + private Map defaultPropertiesSource() { + return (Map) this.environment.getPropertySources().get("defaultProperties").getSource(); + } + } From 31585fea2fae2641536ce761824b2150412b286c Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Wed, 7 Sep 2016 17:30:00 +0200 Subject: [PATCH 065/235] Updating mvnw for all projects --- mvnw | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/mvnw b/mvnw index 53c0d7219..fe1957e36 100755 --- a/mvnw +++ b/mvnw @@ -46,15 +46,6 @@ if [ -z "$MAVEN_SKIP_RC" ] ; then fi -VERSION=$(awk '/ 0) {$0=$0} 1' `dirname $0`/pom.xml| grep '\(.*\)<.*/\1/') -if echo $VERSION | egrep -q 'M|RC'; then - echo Activating \"milestone\" profile for version=\"$VERSION\" - echo $MAVEN_ARGS | grep -q milestone || MAVEN_ARGS="$MAVEN_ARGS -Pmilestone" -else - echo Deactivating \"milestone\" profile for version=\"$VERSION\" - echo $MAVEN_ARGS | grep -q milestone && MAVEN_ARGS=$(echo $MAVEN_ARGS | sed -e 's/-Pmilestone//') -fi - # OS specific support. $var _must_ be set to either true or false. cygwin=false; darwin=false; @@ -66,27 +57,27 @@ case "`uname`" in # # Look for the Apple JDKs first to preserve the existing behaviour, and then look # for the new JDKs provided by Oracle. - # + # if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK ] ; then # # Apple JDKs # export JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home fi - + if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Java/JavaVirtualMachines/CurrentJDK ] ; then # # Apple JDKs # export JAVA_HOME=/System/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home fi - + if [ -z "$JAVA_HOME" ] && [ -L "/Library/Java/JavaVirtualMachines/CurrentJDK" ] ; then # # Oracle JDKs # export JAVA_HOME=/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home - fi + fi if [ -z "$JAVA_HOME" ] && [ -x "/usr/libexec/java_home" ]; then # @@ -228,16 +219,30 @@ concat_lines() { export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-$(find_maven_basedir)} MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" -# Provide a "standardized" way to retrieve the CLI args that will +# Provide a "standardized" way to retrieve the CLI args that will # work with both Windows and non-Windows executions. MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" export MAVEN_CMD_LINE_ARGS WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain +VERSION=$(exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + -Dexec.executable="echo" -Dexec.args='${project.version}' ${WRAPPER_LAUNCHER} -q --non-recursive org.codehaus.mojo:exec-maven-plugin:1.3.1:exec ) + +if echo $VERSION | egrep -q 'M|RC'; then + echo Activating \"milestone\" profile for version=\"$VERSION\" + echo $MAVEN_ARGS | grep -q milestone || MAVEN_ARGS="$MAVEN_ARGS -Pmilestone" +else + echo Deactivating \"milestone\" profile for version=\"$VERSION\" + echo $MAVEN_ARGS | grep -q milestone && MAVEN_ARGS=$(echo $MAVEN_ARGS | sed -e 's/-Pmilestone//') +fi + + exec "$JAVACMD" \ $MAVEN_OPTS \ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ ${WRAPPER_LAUNCHER} ${MAVEN_ARGS} "$@" - From bc532c4f02a46ed7fbc563490c28acb96a499357 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Wed, 7 Sep 2016 17:48:10 +0200 Subject: [PATCH 066/235] Removed adoc footer --- docs/src/main/asciidoc/spring-cloud-sleuth.adoc | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc index d27edffc1..08b7da2a0 100644 --- a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc +++ b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc @@ -3,6 +3,7 @@ :github-raw: http://raw.github.com/{github-repo}/{github-tag} :github-code: http://github.com/{github-repo}/tree/{github-tag} :toc: left +:nofooter: Spring Cloud Sleuth ==================== From 3ebcd1e693bbc89026fd260b9ad25ddddda67419 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Thu, 8 Sep 2016 10:53:27 +0200 Subject: [PATCH 067/235] Wrapping LoadBalancerFeignClient in tracing representation without this change when using SC-Netflix 1.2.0 Feign can't call an external URL fixes #393 --- spring-cloud-sleuth-core/pom.xml | 5 + .../sleuth/instrument/web/TraceFilter.java | 14 +- .../client/feign/TraceFeignObjectWrapper.java | 28 ++++ .../feign/TraceLoadBalancerFeignClient.java | 28 ++++ .../feign/issues/issue350/Issue350Tests.java | 3 + .../feign/issues/issue362/Issue362Tests.java | 6 +- .../feign/issues/issue393/Issue393Tests.java | 154 ++++++++++++++++++ 7 files changed, 230 insertions(+), 8 deletions(-) create mode 100644 spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceLoadBalancerFeignClient.java create mode 100644 spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue393/Issue393Tests.java diff --git a/spring-cloud-sleuth-core/pom.xml b/spring-cloud-sleuth-core/pom.xml index c83c0d53f..3cdd7b410 100644 --- a/spring-cloud-sleuth-core/pom.xml +++ b/spring-cloud-sleuth-core/pom.xml @@ -141,6 +141,11 @@ h2 test + + org.springframework.cloud + spring-cloud-starter-eureka + test + 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 cdf25664c..cf32f75de 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceFilter.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceFilter.java @@ -15,18 +15,18 @@ */ 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; @@ -126,7 +126,7 @@ public class TraceFilter extends GenericFilterBean { continueSpan(request, spanFromRequest); } if (log.isDebugEnabled()) { - log.debug("Received a request to uri [" + uri + "] that should be skipped [" + skip + "]"); + log.debug("Received a request to uri [" + uri + "] that should not be sampled [" + skip + "]"); } // in case of a response with exception status a exception controller will close the span if (!httpStatusSuccessful(response) && isSpanContinued(request)) { diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignObjectWrapper.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignObjectWrapper.java index dae2e6b06..4d5b4f54e 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignObjectWrapper.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignObjectWrapper.java @@ -1,6 +1,9 @@ package org.springframework.cloud.sleuth.instrument.web.client.feign; import org.springframework.beans.factory.BeanFactory; +import org.springframework.cloud.netflix.feign.ribbon.CachingSpringLoadBalancerFactory; +import org.springframework.cloud.netflix.feign.ribbon.LoadBalancerFeignClient; +import org.springframework.cloud.netflix.ribbon.SpringClientFactory; import feign.Client; @@ -14,14 +17,39 @@ final class TraceFeignObjectWrapper { private final BeanFactory beanFactory; + private CachingSpringLoadBalancerFactory cachingSpringLoadBalancerFactory; + private SpringClientFactory springClientFactory; + TraceFeignObjectWrapper(BeanFactory beanFactory) { this.beanFactory = beanFactory; } Object wrap(Object bean) { if (bean instanceof Client && !(bean instanceof TraceFeignClient)) { + if (bean instanceof LoadBalancerFeignClient) { + LoadBalancerFeignClient client = ((LoadBalancerFeignClient) bean); + return new TraceLoadBalancerFeignClient( + client.getDelegate(), factory(), + clientFactory(), this.beanFactory); + } return new TraceFeignClient(this.beanFactory, (Client) bean); } return bean; } + + CachingSpringLoadBalancerFactory factory() { + if (this.cachingSpringLoadBalancerFactory == null) { + this.cachingSpringLoadBalancerFactory = this.beanFactory + .getBean(CachingSpringLoadBalancerFactory.class); + } + return this.cachingSpringLoadBalancerFactory; + } + + SpringClientFactory clientFactory() { + if (this.springClientFactory == null) { + this.springClientFactory = this.beanFactory + .getBean(SpringClientFactory.class); + } + return this.springClientFactory; + } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceLoadBalancerFeignClient.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceLoadBalancerFeignClient.java new file mode 100644 index 000000000..ec4ab9129 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceLoadBalancerFeignClient.java @@ -0,0 +1,28 @@ +package org.springframework.cloud.sleuth.instrument.web.client.feign; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.cloud.netflix.feign.ribbon.CachingSpringLoadBalancerFactory; +import org.springframework.cloud.netflix.feign.ribbon.LoadBalancerFeignClient; +import org.springframework.cloud.netflix.ribbon.SpringClientFactory; + +import feign.Client; + +/** + * We need to wrap the {@link LoadBalancerFeignClient} into a trace representation + * due to casts in {@link org.springframework.cloud.netflix.feign.FeignClientFactoryBean}. + * + * @author Marcin Grzejszczak + * @since 1.0.7 + */ +class TraceLoadBalancerFeignClient extends LoadBalancerFeignClient { + + public TraceLoadBalancerFeignClient(Client delegate, + CachingSpringLoadBalancerFactory lbClientFactory, + SpringClientFactory clientFactory, BeanFactory beanFactory) { + super(wrap(delegate, beanFactory), lbClientFactory, clientFactory); + } + + private static Client wrap(Client delegate, BeanFactory beanFactory) { + return (Client) new TraceFeignObjectWrapper(beanFactory).wrap(delegate); + } +} diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue350/Issue350Tests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue350/Issue350Tests.java index da379bd46..8c0c277ea 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue350/Issue350Tests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue350/Issue350Tests.java @@ -28,6 +28,7 @@ import org.springframework.boot.test.TestRestTemplate; import org.springframework.boot.test.WebIntegrationTest; import org.springframework.cloud.netflix.feign.EnableFeignClients; import org.springframework.cloud.netflix.feign.FeignClient; +import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.sampler.AlwaysSampler; import org.springframework.cloud.sleuth.util.ExceptionUtils; import org.springframework.context.annotation.Bean; @@ -54,6 +55,7 @@ import static org.assertj.core.api.BDDAssertions.then; public class Issue350Tests { RestTemplate template = new TestRestTemplate(); + @Autowired Tracer tracer; @Before public void setup() { @@ -64,6 +66,7 @@ public class Issue350Tests { public void should_successfully_work_without_hystrix() { this.template.getForEntity("http://localhost:9988/sleuth/test-not-ok", String.class); then(ExceptionUtils.getLastException()).isNull(); + then(this.tracer.getCurrentSpan()).isNull(); } } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue362/Issue362Tests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue362/Issue362Tests.java index 806b6c65f..dcfd729fa 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue362/Issue362Tests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue362/Issue362Tests.java @@ -30,6 +30,7 @@ import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.boot.test.WebIntegrationTest; import org.springframework.cloud.netflix.feign.EnableFeignClients; import org.springframework.cloud.netflix.feign.FeignClient; +import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.assertions.SleuthAssertions; import org.springframework.cloud.sleuth.sampler.AlwaysSampler; import org.springframework.cloud.sleuth.util.ExceptionUtils; @@ -64,6 +65,7 @@ public class Issue362Tests { RestTemplate template = new RestTemplate(); @Autowired FeignComponentAsserter feignComponentAsserter; + @Autowired Tracer tracer; @Before public void setup() { @@ -77,8 +79,9 @@ public class Issue362Tests { ResponseEntity response = this.template.getForEntity(securedURl, String.class); - SleuthAssertions.then(response.getBody()).isEqualTo("I'm OK"); + then(response.getBody()).isEqualTo("I'm OK"); then(ExceptionUtils.getLastException()).isNull(); + then(this.tracer.getCurrentSpan()).isNull(); } @Test @@ -93,6 +96,7 @@ public class Issue362Tests { then(ExceptionUtils.getLastException()).isNull(); then(this.feignComponentAsserter.executedComponents) .containsEntry(ErrorDecoder.class, true); + then(this.tracer.getCurrentSpan()).isNull(); } } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue393/Issue393Tests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue393/Issue393Tests.java new file mode 100644 index 000000000..ac03cb6aa --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue393/Issue393Tests.java @@ -0,0 +1,154 @@ +/* + * 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.issues.issue393; + +import java.util.Date; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutionException; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; + +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.boot.test.WebIntegrationTest; +import org.springframework.cloud.client.discovery.EnableDiscoveryClient; +import org.springframework.cloud.netflix.feign.EnableFeignClients; +import org.springframework.cloud.netflix.feign.FeignClient; +import org.springframework.cloud.sleuth.Tracer; +import org.springframework.cloud.sleuth.assertions.SleuthAssertions; +import org.springframework.cloud.sleuth.sampler.AlwaysSampler; +import org.springframework.cloud.sleuth.trace.TestSpanContextHolder; +import org.springframework.cloud.sleuth.util.ExceptionUtils; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.client.RestTemplate; + +import feign.Logger; +import feign.Response; +import feign.RetryableException; +import feign.Retryer; +import feign.codec.ErrorDecoder; + +import static org.assertj.core.api.Assertions.fail; +import static org.assertj.core.api.Assertions.registerCustomDateFormat; +import static org.assertj.core.api.Assertions.registerFormatterForType; +import static org.assertj.core.api.BDDAssertions.then; + +/** + * @author Marcin Grzejszczak + */ +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(Application.class) +@WebIntegrationTest +@TestPropertySource(properties = {"spring.application.name=demo-feign-uri", + "server.port=9978"}) +public class Issue393Tests { + + RestTemplate template = new RestTemplate(); + @Autowired Tracer tracer; + + @Before + public void open() { + TestSpanContextHolder.removeCurrentSpan(); + ExceptionUtils.setFail(true); + } + + @After + public void close() { + TestSpanContextHolder.removeCurrentSpan(); + } + + @Test + public void should_successfully_work_when_service_discovery_is_on_classpath_and_feign_uses_url() { + String url = "http://localhost:9978/hello/mikesarver"; + + ResponseEntity response = this.template.getForEntity(url, String.class); + + then(response.getBody()).isEqualTo("mikesarver foo"); + then(ExceptionUtils.getLastException()).isNull(); + then(this.tracer.getCurrentSpan()).isNull(); + } +} + +@Configuration +@EnableAutoConfiguration +@EnableFeignClients +@EnableDiscoveryClient +class Application { + + @Bean + public DemoController demoController(MyNameRemote myNameRemote) { + return new DemoController(myNameRemote); + } + + @Bean + public feign.Logger.Level feignLoggerLevel() { + return feign.Logger.Level.BASIC; + } + + @Bean + public AlwaysSampler defaultSampler() { + return new AlwaysSampler(); + } + +} + +@FeignClient(name="no-name", + url="http://localhost:9978") +interface MyNameRemote { + + @RequestMapping(value = "/name/{id}", method = RequestMethod.GET) + String getName(@PathVariable("id") String id); +} + +@RestController +class DemoController { + + private final MyNameRemote myNameRemote; + + public DemoController(MyNameRemote myNameRemote) { + this.myNameRemote = myNameRemote; + } + + @RequestMapping(value = "/hello/{name}") + public String getHello(@PathVariable("name") String name) { + return myNameRemote.getName(name) + " foo"; + } + + @RequestMapping(value = "/name/{name}") + public String getName(@PathVariable("name") String name) { + return name; + } +} From 10a169ed26c0419706ad8c2b2ffc72f141ccab26 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Thu, 8 Sep 2016 15:08:01 +0200 Subject: [PATCH 068/235] TraceLoadBalancerFeignClient not closing span when TLBFC is throwing an exception the span wasn't closed. Throwing exception can occurr when IOExcepiton is thrown. Then the span wouldn't be closed and the whole series of problems occur. fixes #393 --- .../feign/TraceLoadBalancerFeignClient.java | 46 ++++++++++++++++++- .../WebClientDiscoveryExceptionTests.java | 10 ++-- .../feign/issues/issue393/Issue393Tests.java | 24 +--------- .../src/test/resources/application.yml | 3 ++ 4 files changed, 55 insertions(+), 28 deletions(-) diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceLoadBalancerFeignClient.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceLoadBalancerFeignClient.java index ec4ab9129..d667f325e 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceLoadBalancerFeignClient.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceLoadBalancerFeignClient.java @@ -1,11 +1,21 @@ package org.springframework.cloud.sleuth.instrument.web.client.feign; +import java.io.IOException; +import java.lang.invoke.MethodHandles; +import java.util.Objects; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.BeanFactory; import org.springframework.cloud.netflix.feign.ribbon.CachingSpringLoadBalancerFactory; import org.springframework.cloud.netflix.feign.ribbon.LoadBalancerFeignClient; import org.springframework.cloud.netflix.ribbon.SpringClientFactory; +import org.springframework.cloud.sleuth.Span; +import org.springframework.cloud.sleuth.Tracer; import feign.Client; +import feign.Request; +import feign.Response; /** * We need to wrap the {@link LoadBalancerFeignClient} into a trace representation @@ -16,13 +26,47 @@ import feign.Client; */ class TraceLoadBalancerFeignClient extends LoadBalancerFeignClient { - public TraceLoadBalancerFeignClient(Client delegate, + private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); + + private final BeanFactory beanFactory; + private Tracer tracer; + + TraceLoadBalancerFeignClient(Client delegate, CachingSpringLoadBalancerFactory lbClientFactory, SpringClientFactory clientFactory, BeanFactory beanFactory) { super(wrap(delegate, beanFactory), lbClientFactory, clientFactory); + this.beanFactory = beanFactory; + } + + @Override public Response execute(Request request, Request.Options options) + throws IOException { + Span currentSpan = tracer().getCurrentSpan(); + if (log.isDebugEnabled()) { + log.debug("Current span is " + currentSpan); + } + try { + return super.execute(request, options); + } catch (Exception e) { + if (Objects.equals(currentSpan, tracer().getCurrentSpan())) { + if (log.isDebugEnabled()) { + log.debug("Closing span " + currentSpan + " due to exception which is " + + "not handled by Feign. This can happen when the load balancer " + + "threw exception before Feign even managed to do sth about it"); + } + tracer().close(currentSpan); + } + throw e; + } } private static Client wrap(Client delegate, BeanFactory beanFactory) { return (Client) new TraceFeignObjectWrapper(beanFactory).wrap(delegate); } + + private Tracer tracer() { + if (this.tracer == null) { + this.tracer = this.beanFactory.getBean(Tracer.class); + } + return this.tracer; + } } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/discoveryexception/WebClientDiscoveryExceptionTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/discoveryexception/WebClientDiscoveryExceptionTests.java index 1bf42d1a3..eeeea91c6 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/discoveryexception/WebClientDiscoveryExceptionTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/discoveryexception/WebClientDiscoveryExceptionTests.java @@ -30,13 +30,13 @@ import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.boot.test.WebIntegrationTest; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.client.loadbalancer.LoadBalanced; +import org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration; import org.springframework.cloud.netflix.feign.EnableFeignClients; import org.springframework.cloud.netflix.feign.FeignClient; import org.springframework.cloud.netflix.ribbon.RibbonClient; import org.springframework.cloud.sleuth.Sampler; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.assertions.SleuthAssertions; import org.springframework.cloud.sleuth.sampler.AlwaysSampler; import org.springframework.cloud.sleuth.trace.TestSpanContextHolder; import org.springframework.cloud.sleuth.util.ExceptionUtils; @@ -77,7 +77,7 @@ public class WebClientDiscoveryExceptionTests { // issue #240 private void shouldCloseSpanUponException(ResponseEntityProvider provider) - throws IOException { + throws IOException, InterruptedException { Span span = this.tracer.createSpan("new trace"); try { @@ -89,8 +89,10 @@ public class WebClientDiscoveryExceptionTests { assertThat(ExceptionUtils.getLastException()).isNull(); - SleuthAssertions.then(this.tracer.getCurrentSpan()).isEqualTo(span); + then(this.tracer.getCurrentSpan()).isEqualTo(span); this.tracer.close(span); + // hystrix commands should finish at this point + Thread.sleep(200); then(ExceptionUtils.getLastException()).isNull(); } @@ -114,7 +116,7 @@ public class WebClientDiscoveryExceptionTests { } @Configuration - @EnableAutoConfiguration + @EnableAutoConfiguration(exclude = EurekaClientAutoConfiguration.class) @EnableDiscoveryClient @EnableFeignClients @RibbonClient("exceptionservice") diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue393/Issue393Tests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue393/Issue393Tests.java index ac03cb6aa..10278508c 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue393/Issue393Tests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue393/Issue393Tests.java @@ -16,18 +16,8 @@ package org.springframework.cloud.sleuth.instrument.web.client.feign.issues.issue393; -import java.util.Date; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutionException; - -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; - import org.junit.After; import org.junit.Before; -import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; @@ -38,32 +28,20 @@ import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.netflix.feign.EnableFeignClients; import org.springframework.cloud.netflix.feign.FeignClient; import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.assertions.SleuthAssertions; import org.springframework.cloud.sleuth.sampler.AlwaysSampler; import org.springframework.cloud.sleuth.trace.TestSpanContextHolder; import org.springframework.cloud.sleuth.util.ExceptionUtils; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; -import feign.Logger; -import feign.Response; -import feign.RetryableException; -import feign.Retryer; -import feign.codec.ErrorDecoder; - -import static org.assertj.core.api.Assertions.fail; -import static org.assertj.core.api.Assertions.registerCustomDateFormat; -import static org.assertj.core.api.Assertions.registerFormatterForType; import static org.assertj.core.api.BDDAssertions.then; /** @@ -73,7 +51,7 @@ import static org.assertj.core.api.BDDAssertions.then; @SpringApplicationConfiguration(Application.class) @WebIntegrationTest @TestPropertySource(properties = {"spring.application.name=demo-feign-uri", - "server.port=9978"}) + "server.port=9978", "eureka.client.enabled=true", "ribbon.eureka.enabled=true"}) public class Issue393Tests { RestTemplate template = new RestTemplate(); diff --git a/spring-cloud-sleuth-core/src/test/resources/application.yml b/spring-cloud-sleuth-core/src/test/resources/application.yml index d081e7e83..5b9e8f669 100644 --- a/spring-cloud-sleuth-core/src/test/resources/application.yml +++ b/spring-cloud-sleuth-core/src/test/resources/application.yml @@ -9,6 +9,9 @@ exceptionService.ribbon: ConnectTimeout: 1 ReadTimeout: 1 +eureka.client.enabled: false +ribbon.eureka.enabled: false + spring.sleuth.scheduled.skipPattern: "^org.*TestBeanWithScheduledMethodToBeIgnored$" # comma separated list of matchers spring.sleuth.rxjava.schedulers.ignoredthreads: HystixMetricPoller,^MyCustomThread.*$,^RxComputation.*$ \ No newline at end of file From d331f5a212874b633a2c731acdeb9b5ccd186f51 Mon Sep 17 00:00:00 2001 From: Marius Bogoevici Date: Thu, 8 Sep 2016 09:56:29 -0400 Subject: [PATCH 069/235] Use StreamListener for coercing messages from JSON `Span` (#386) * Use StreamListener for coercing messages from JSON `Span` Rely on the `contentType` header of the transported message to tell Spring Cloud Stream how to coerce the message to `Span`. --- .../sleuth/stream/StreamEnvironmentPostProcessor.java | 4 ++-- .../zipkin/stream/StreamEnvironmentPostProcessor.java | 10 +++++++--- .../sleuth/zipkin/stream/ZipkinMessageListener.java | 4 ++-- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/StreamEnvironmentPostProcessor.java b/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/StreamEnvironmentPostProcessor.java index f93595695..d735ac7ad 100644 --- a/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/StreamEnvironmentPostProcessor.java +++ b/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/StreamEnvironmentPostProcessor.java @@ -74,9 +74,9 @@ public class StreamEnvironmentPostProcessor implements EnvironmentPostProcessor // Technically this is only needed on the consumer, but it's fine to be explicit // on producers as well. It puts all consumers in the same "group", meaning they // compete with each other and only one gets each message. - map.put("spring.cloud.stream.bindings." + SleuthSink.INPUT + ".group", + map.put("spring.cloud.stream.bindings." + SleuthSource.OUTPUT + ".group", environment.getProperty("spring.sleuth.stream.group", SleuthSink.INPUT)); - map.put("spring.cloud.stream.bindings." + SleuthSink.INPUT + ".content-type", + map.put("spring.cloud.stream.bindings." + SleuthSource.OUTPUT + ".content-type", environment.getProperty("spring.sleuth.stream.content-type", "application/json")); addOrReplace(environment.getPropertySources(), map); } diff --git a/spring-cloud-sleuth-zipkin-stream/src/main/java/org/springframework/cloud/sleuth/zipkin/stream/StreamEnvironmentPostProcessor.java b/spring-cloud-sleuth-zipkin-stream/src/main/java/org/springframework/cloud/sleuth/zipkin/stream/StreamEnvironmentPostProcessor.java index 2c67e524a..18d299085 100644 --- a/spring-cloud-sleuth-zipkin-stream/src/main/java/org/springframework/cloud/sleuth/zipkin/stream/StreamEnvironmentPostProcessor.java +++ b/spring-cloud-sleuth-zipkin-stream/src/main/java/org/springframework/cloud/sleuth/zipkin/stream/StreamEnvironmentPostProcessor.java @@ -22,7 +22,6 @@ import java.util.Map; import org.springframework.boot.SpringApplication; import org.springframework.boot.env.EnvironmentPostProcessor; import org.springframework.cloud.sleuth.stream.SleuthSink; -import org.springframework.cloud.sleuth.stream.Spans; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.MapPropertySource; import org.springframework.core.env.MutablePropertySources; @@ -43,9 +42,14 @@ public class StreamEnvironmentPostProcessor implements EnvironmentPostProcessor public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { Map map = new HashMap(); + // Clearing the content type on the inbound channel means that the payload + // of inbound messages can be coerced by a `@StreamListener` method to + // its argument type based on the 'contentType' header of the inbound message. + // Necessary to be done explicitly because the property is set by by + // org.springframework.cloud.sleuth.stream.StreamEnvironmentPostProcessor to + // 'application/json' for outbound channels map.put("spring.cloud.stream.bindings." + SleuthSink.INPUT + ".content-type", - environment.getProperty("spring.sleuth.stream.content-type", - "application/x-java-object;type=" + Spans.class.getName())); + ""); addOrReplace(environment.getPropertySources(), map); } diff --git a/spring-cloud-sleuth-zipkin-stream/src/main/java/org/springframework/cloud/sleuth/zipkin/stream/ZipkinMessageListener.java b/spring-cloud-sleuth-zipkin-stream/src/main/java/org/springframework/cloud/sleuth/zipkin/stream/ZipkinMessageListener.java index c46053682..c5ce55651 100644 --- a/spring-cloud-sleuth-zipkin-stream/src/main/java/org/springframework/cloud/sleuth/zipkin/stream/ZipkinMessageListener.java +++ b/spring-cloud-sleuth-zipkin-stream/src/main/java/org/springframework/cloud/sleuth/zipkin/stream/ZipkinMessageListener.java @@ -8,6 +8,7 @@ import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.stream.SleuthSink; import org.springframework.cloud.sleuth.stream.Spans; import org.springframework.cloud.sleuth.zipkin.stream.ZipkinMessageListener.NotSleuthStreamClient; +import org.springframework.cloud.stream.annotation.StreamListener; import org.springframework.context.annotation.ConditionContext; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Lazy; @@ -16,7 +17,6 @@ import org.springframework.core.env.Environment; import org.springframework.core.env.MapPropertySource; import org.springframework.core.type.AnnotatedTypeMetadata; import org.springframework.integration.annotation.MessageEndpoint; -import org.springframework.integration.annotation.ServiceActivator; import zipkin.Annotation; import zipkin.BinaryAnnotation; import zipkin.BinaryAnnotation.Type; @@ -62,7 +62,7 @@ public class ZipkinMessageListener { .metrics(metrics.forTransport("stream")).build(); } - @ServiceActivator(inputChannel = SleuthSink.INPUT) + @StreamListener(SleuthSink.INPUT) public void sink(Spans input) { List converted = ConvertToZipkinSpanList.convert(input); this.collector.accept(converted, Callback.NOOP); From 877b80b70efebd0600a8853e11220795c402d0c9 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Thu, 8 Sep 2016 17:37:08 +0200 Subject: [PATCH 070/235] Bumping versions before release --- benchmarks/pom.xml | 2 +- docs/pom.xml | 2 +- pom.xml | 6 +++--- spring-cloud-sleuth-core/pom.xml | 2 +- spring-cloud-sleuth-dependencies/pom.xml | 6 +++--- spring-cloud-sleuth-samples/pom.xml | 2 +- .../spring-cloud-sleuth-sample-feign/pom.xml | 2 +- .../spring-cloud-sleuth-sample-messaging/pom.xml | 2 +- .../spring-cloud-sleuth-sample-ribbon/pom.xml | 2 +- .../spring-cloud-sleuth-sample-stream/pom.xml | 2 +- .../spring-cloud-sleuth-sample-test-core/pom.xml | 2 +- .../spring-cloud-sleuth-sample-websocket/pom.xml | 2 +- .../spring-cloud-sleuth-sample-zipkin-stream/pom.xml | 2 +- .../spring-cloud-sleuth-sample-zipkin/pom.xml | 2 +- .../spring-cloud-sleuth-sample/pom.xml | 2 +- spring-cloud-sleuth-stream/pom.xml | 2 +- spring-cloud-sleuth-zipkin-stream/pom.xml | 2 +- spring-cloud-sleuth-zipkin/pom.xml | 2 +- spring-cloud-starter-sleuth/pom.xml | 2 +- spring-cloud-starter-zipkin/pom.xml | 2 +- 20 files changed, 24 insertions(+), 24 deletions(-) diff --git a/benchmarks/pom.xml b/benchmarks/pom.xml index d2125385c..92215c148 100644 --- a/benchmarks/pom.xml +++ b/benchmarks/pom.xml @@ -17,7 +17,7 @@ Benchmarks Benchmarks (JMH) org.springframework.cloud - 1.0.7.BUILD-SNAPSHOT + 1.0.7.RELEASE benchmarks diff --git a/docs/pom.xml b/docs/pom.xml index 8e3b03e75..dc4a7382f 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.7.BUILD-SNAPSHOT + 1.0.7.RELEASE spring-cloud-sleuth-docs pom diff --git a/pom.xml b/pom.xml index 733f676aa..a9479e2c6 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 spring-cloud-sleuth - 1.0.7.BUILD-SNAPSHOT + 1.0.7.RELEASE pom Spring Cloud Sleuth Spring Cloud Sleuth @@ -13,7 +13,7 @@ org.springframework.cloud spring-cloud-build - 1.1.3.BUILD-SNAPSHOT + 1.1.2.RELEASE @@ -208,7 +208,7 @@ 1.8 2.19.1 2.17 - 1.1.3.BUILD-SNAPSHOT + 1.1.2.RELEASE diff --git a/spring-cloud-sleuth-core/pom.xml b/spring-cloud-sleuth-core/pom.xml index 3cdd7b410..a5049a8c7 100644 --- a/spring-cloud-sleuth-core/pom.xml +++ b/spring-cloud-sleuth-core/pom.xml @@ -11,7 +11,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.7.BUILD-SNAPSHOT + 1.0.7.RELEASE .. diff --git a/spring-cloud-sleuth-dependencies/pom.xml b/spring-cloud-sleuth-dependencies/pom.xml index 7706f75a1..8f233e498 100644 --- a/spring-cloud-sleuth-dependencies/pom.xml +++ b/spring-cloud-sleuth-dependencies/pom.xml @@ -5,16 +5,16 @@ spring-cloud-dependencies-parent org.springframework.cloud - 1.1.3.BUILD-SNAPSHOT + 1.1.2.RELEASE spring-cloud-sleuth-dependencies - 1.0.7.BUILD-SNAPSHOT + 1.0.7.RELEASE pom spring-cloud-sleuth-dependencies Spring Cloud Sleuth Dependencies - 1.1.6.BUILD-SNAPSHOT + 1.1.5.RELEASE 1.8.4 1.1.5 diff --git a/spring-cloud-sleuth-samples/pom.xml b/spring-cloud-sleuth-samples/pom.xml index 8d2695be5..a8be15a09 100644 --- a/spring-cloud-sleuth-samples/pom.xml +++ b/spring-cloud-sleuth-samples/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.7.BUILD-SNAPSHOT + 1.0.7.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml index 543ab0b21..502d2317b 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml @@ -20,7 +20,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.7.BUILD-SNAPSHOT + 1.0.7.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml index b7302789b..5068e86d8 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.7.BUILD-SNAPSHOT + 1.0.7.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml index 1777d4efa..4e3d395cb 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.7.BUILD-SNAPSHOT + 1.0.7.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml index ec4ed17f7..309f9c2f4 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.7.BUILD-SNAPSHOT + 1.0.7.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml index 2986953a3..be0a458ce 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.7.BUILD-SNAPSHOT + 1.0.7.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml index 1676764da..28086956b 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.7.BUILD-SNAPSHOT + 1.0.7.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml index 39dae832b..532d6c177 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.7.BUILD-SNAPSHOT + 1.0.7.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml index eca2b3689..177db5a96 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml @@ -27,7 +27,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.7.BUILD-SNAPSHOT + 1.0.7.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml index cfe1c4419..d6c83e8ee 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.7.BUILD-SNAPSHOT + 1.0.7.RELEASE .. diff --git a/spring-cloud-sleuth-stream/pom.xml b/spring-cloud-sleuth-stream/pom.xml index b8a19df3c..94886ba12 100644 --- a/spring-cloud-sleuth-stream/pom.xml +++ b/spring-cloud-sleuth-stream/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.7.BUILD-SNAPSHOT + 1.0.7.RELEASE diff --git a/spring-cloud-sleuth-zipkin-stream/pom.xml b/spring-cloud-sleuth-zipkin-stream/pom.xml index 3814bde7b..e8b274303 100644 --- a/spring-cloud-sleuth-zipkin-stream/pom.xml +++ b/spring-cloud-sleuth-zipkin-stream/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.7.BUILD-SNAPSHOT + 1.0.7.RELEASE diff --git a/spring-cloud-sleuth-zipkin/pom.xml b/spring-cloud-sleuth-zipkin/pom.xml index d7c7c1f75..58e502c44 100644 --- a/spring-cloud-sleuth-zipkin/pom.xml +++ b/spring-cloud-sleuth-zipkin/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.7.BUILD-SNAPSHOT + 1.0.7.RELEASE .. diff --git a/spring-cloud-starter-sleuth/pom.xml b/spring-cloud-starter-sleuth/pom.xml index 2fec8da05..fd50e2b58 100644 --- a/spring-cloud-starter-sleuth/pom.xml +++ b/spring-cloud-starter-sleuth/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.7.BUILD-SNAPSHOT + 1.0.7.RELEASE .. spring-cloud-starter-sleuth diff --git a/spring-cloud-starter-zipkin/pom.xml b/spring-cloud-starter-zipkin/pom.xml index bfc7152ff..a16d98204 100644 --- a/spring-cloud-starter-zipkin/pom.xml +++ b/spring-cloud-starter-zipkin/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.7.BUILD-SNAPSHOT + 1.0.7.RELEASE .. spring-cloud-starter-zipkin From ef2445090d28e9050680982217b128bcd7f9f080 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Thu, 8 Sep 2016 18:20:05 +0200 Subject: [PATCH 071/235] Going back to SNAPSHOTs --- benchmarks/pom.xml | 2 +- docs/pom.xml | 2 +- pom.xml | 6 +++--- spring-cloud-sleuth-core/pom.xml | 2 +- spring-cloud-sleuth-dependencies/pom.xml | 6 +++--- spring-cloud-sleuth-samples/pom.xml | 2 +- .../spring-cloud-sleuth-sample-feign/pom.xml | 2 +- .../spring-cloud-sleuth-sample-messaging/pom.xml | 2 +- .../spring-cloud-sleuth-sample-ribbon/pom.xml | 2 +- .../spring-cloud-sleuth-sample-stream/pom.xml | 2 +- .../spring-cloud-sleuth-sample-test-core/pom.xml | 2 +- .../spring-cloud-sleuth-sample-websocket/pom.xml | 2 +- .../spring-cloud-sleuth-sample-zipkin-stream/pom.xml | 2 +- .../spring-cloud-sleuth-sample-zipkin/pom.xml | 2 +- .../spring-cloud-sleuth-sample/pom.xml | 2 +- spring-cloud-sleuth-stream/pom.xml | 2 +- spring-cloud-sleuth-zipkin-stream/pom.xml | 2 +- spring-cloud-sleuth-zipkin/pom.xml | 2 +- spring-cloud-starter-sleuth/pom.xml | 2 +- spring-cloud-starter-zipkin/pom.xml | 2 +- 20 files changed, 24 insertions(+), 24 deletions(-) diff --git a/benchmarks/pom.xml b/benchmarks/pom.xml index 92215c148..f8386837c 100644 --- a/benchmarks/pom.xml +++ b/benchmarks/pom.xml @@ -17,7 +17,7 @@ Benchmarks Benchmarks (JMH) org.springframework.cloud - 1.0.7.RELEASE + 1.0.8.BUILD-SNAPSHOT benchmarks diff --git a/docs/pom.xml b/docs/pom.xml index dc4a7382f..fad55a1b6 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.7.RELEASE + 1.0.8.BUILD-SNAPSHOT spring-cloud-sleuth-docs pom diff --git a/pom.xml b/pom.xml index a9479e2c6..5ee2511be 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 spring-cloud-sleuth - 1.0.7.RELEASE + 1.0.8.BUILD-SNAPSHOT pom Spring Cloud Sleuth Spring Cloud Sleuth @@ -13,7 +13,7 @@ org.springframework.cloud spring-cloud-build - 1.1.2.RELEASE + 1.1.3.BUILD-SNAPSHOT @@ -208,7 +208,7 @@ 1.8 2.19.1 2.17 - 1.1.2.RELEASE + 1.1.3.BUILD-SNAPSHOT diff --git a/spring-cloud-sleuth-core/pom.xml b/spring-cloud-sleuth-core/pom.xml index a5049a8c7..178facd82 100644 --- a/spring-cloud-sleuth-core/pom.xml +++ b/spring-cloud-sleuth-core/pom.xml @@ -11,7 +11,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.7.RELEASE + 1.0.8.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-dependencies/pom.xml b/spring-cloud-sleuth-dependencies/pom.xml index 8f233e498..57adecd81 100644 --- a/spring-cloud-sleuth-dependencies/pom.xml +++ b/spring-cloud-sleuth-dependencies/pom.xml @@ -5,16 +5,16 @@ spring-cloud-dependencies-parent org.springframework.cloud - 1.1.2.RELEASE + 1.1.3.BUILD-SNAPSHOT spring-cloud-sleuth-dependencies - 1.0.7.RELEASE + 1.0.8.BUILD-SNAPSHOT pom spring-cloud-sleuth-dependencies Spring Cloud Sleuth Dependencies - 1.1.5.RELEASE + 1.1.6.BUILD-SNAPSHOT 1.8.4 1.1.5 diff --git a/spring-cloud-sleuth-samples/pom.xml b/spring-cloud-sleuth-samples/pom.xml index a8be15a09..8d69d04ce 100644 --- a/spring-cloud-sleuth-samples/pom.xml +++ b/spring-cloud-sleuth-samples/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.7.RELEASE + 1.0.8.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml index 502d2317b..3fc733f4d 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml @@ -20,7 +20,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.7.RELEASE + 1.0.8.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml index 5068e86d8..b33468046 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.7.RELEASE + 1.0.8.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml index 4e3d395cb..18b9c0f63 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.7.RELEASE + 1.0.8.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml index 309f9c2f4..6e4f38d21 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.7.RELEASE + 1.0.8.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml index be0a458ce..51435355d 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.7.RELEASE + 1.0.8.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml index 28086956b..1a90dd8e4 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.7.RELEASE + 1.0.8.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml index 532d6c177..aa2f92895 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.7.RELEASE + 1.0.8.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml index 177db5a96..8224b23f7 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml @@ -27,7 +27,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.7.RELEASE + 1.0.8.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml index d6c83e8ee..d73d8c7ea 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.7.RELEASE + 1.0.8.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-stream/pom.xml b/spring-cloud-sleuth-stream/pom.xml index 94886ba12..dd1dca251 100644 --- a/spring-cloud-sleuth-stream/pom.xml +++ b/spring-cloud-sleuth-stream/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.7.RELEASE + 1.0.8.BUILD-SNAPSHOT diff --git a/spring-cloud-sleuth-zipkin-stream/pom.xml b/spring-cloud-sleuth-zipkin-stream/pom.xml index e8b274303..baf07e8aa 100644 --- a/spring-cloud-sleuth-zipkin-stream/pom.xml +++ b/spring-cloud-sleuth-zipkin-stream/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.7.RELEASE + 1.0.8.BUILD-SNAPSHOT diff --git a/spring-cloud-sleuth-zipkin/pom.xml b/spring-cloud-sleuth-zipkin/pom.xml index 58e502c44..66bdbb699 100644 --- a/spring-cloud-sleuth-zipkin/pom.xml +++ b/spring-cloud-sleuth-zipkin/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.7.RELEASE + 1.0.8.BUILD-SNAPSHOT .. diff --git a/spring-cloud-starter-sleuth/pom.xml b/spring-cloud-starter-sleuth/pom.xml index fd50e2b58..7095829c2 100644 --- a/spring-cloud-starter-sleuth/pom.xml +++ b/spring-cloud-starter-sleuth/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.7.RELEASE + 1.0.8.BUILD-SNAPSHOT .. spring-cloud-starter-sleuth diff --git a/spring-cloud-starter-zipkin/pom.xml b/spring-cloud-starter-zipkin/pom.xml index a16d98204..8f47fa8ea 100644 --- a/spring-cloud-starter-zipkin/pom.xml +++ b/spring-cloud-starter-zipkin/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.7.RELEASE + 1.0.8.BUILD-SNAPSHOT .. spring-cloud-starter-zipkin From 9e01d774314f947d37a4cd5dcf4ddabe6f70a157 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Thu, 8 Sep 2016 22:06:44 +0200 Subject: [PATCH 072/235] Feign fixes it turned out that some of the tests were leaky and didn't catch that ExceptionUtils were throwing an exception (race condition with Hystrix). That was due to the fact that When Hystrix with Feign were doing retries the RequestInterceptor wasn't called. That means that a new span wasn't created but a parent span was closed. With this change the only place where the span creation and closing takes place is TraceFeignClient. I removed the Feign RequestInterceptor. Now whenever there is a retry - a new span is created and closed after getting a response. There are no exceptions, special cases etc. In addition to that since Feign is fully immutable and SpanInjector is by design made to mutate objects I had to wrap the immutable Request in an AtomicReference in order to change the contents of the Request. I'm ashamed but didn't have a better idea. Since that is packaged scope nobody should every see that (outside the package of course) --- spring-cloud-sleuth-core/pom.xml | 6 ++ .../SleuthHystrixConcurrencyStrategy.java | 27 +++++-- .../sleuth/instrument/web/TraceFilter.java | 2 +- .../client/feign/FeignRequestInjector.java | 79 +++++++++++++++++++ .../feign/FeignRequestTemplateInjector.java | 61 -------------- .../web/client/feign/TraceFeignClient.java | 34 ++++++-- .../TraceFeignClientAutoConfiguration.java | 10 --- .../feign/TraceFeignRequestInterceptor.java | 63 --------------- .../feign/TraceLoadBalancerFeignClient.java | 20 +---- .../exception/WebClientExceptionTests.java | 16 +++- .../web/client/feign/FeignRetriesTests.java | 18 ++--- .../client/feign/TraceFeignClientTests.java | 8 +- .../sleuth/stream/StreamSpanReporter.java | 9 +++ .../sleuth/zipkin/ZipkinSpanListener.java | 4 + 14 files changed, 176 insertions(+), 181 deletions(-) create mode 100644 spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignRequestInjector.java delete mode 100644 spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignRequestTemplateInjector.java delete mode 100644 spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignRequestInterceptor.java diff --git a/spring-cloud-sleuth-core/pom.xml b/spring-cloud-sleuth-core/pom.xml index 178facd82..b08f90051 100644 --- a/spring-cloud-sleuth-core/pom.xml +++ b/spring-cloud-sleuth-core/pom.xml @@ -146,6 +146,12 @@ spring-cloud-starter-eureka test + + com.github.stefanbirkner + system-rules + 1.16.0 + test + diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/hystrix/SleuthHystrixConcurrencyStrategy.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/hystrix/SleuthHystrixConcurrencyStrategy.java index 9878b44fa..544f43e0b 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/hystrix/SleuthHystrixConcurrencyStrategy.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/hystrix/SleuthHystrixConcurrencyStrategy.java @@ -16,14 +16,9 @@ package org.springframework.cloud.sleuth.instrument.hystrix; +import java.lang.invoke.MethodHandles; import java.util.concurrent.Callable; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.TraceKeys; -import org.springframework.cloud.sleuth.Tracer; - import com.netflix.hystrix.strategy.HystrixPlugins; import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy; import com.netflix.hystrix.strategy.eventnotifier.HystrixEventNotifier; @@ -31,6 +26,12 @@ import com.netflix.hystrix.strategy.executionhook.HystrixCommandExecutionHook; import com.netflix.hystrix.strategy.metrics.HystrixMetricsPublisher; import com.netflix.hystrix.strategy.properties.HystrixPropertiesStrategy; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.cloud.sleuth.Span; +import org.springframework.cloud.sleuth.TraceKeys; +import org.springframework.cloud.sleuth.Tracer; + /** * A {@link HystrixConcurrencyStrategy} that wraps a {@link Callable} in a * {@link Callable} that either starts a new span or continues one if the tracing was @@ -109,6 +110,8 @@ public class SleuthHystrixConcurrencyStrategy extends HystrixConcurrencyStrategy // Visible for testing static class HystrixTraceCallable implements Callable { + private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); + private Tracer tracer; private TraceKeys traceKeys; private Callable callable; @@ -128,10 +131,16 @@ public class SleuthHystrixConcurrencyStrategy extends HystrixConcurrencyStrategy boolean created = false; if (span != null) { span = this.tracer.continueSpan(span); + if (log.isDebugEnabled()) { + log.debug("Continuing span " + span); + } } else { span = this.tracer.createSpan(HYSTRIX_COMPONENT); created = true; + if (log.isDebugEnabled()) { + log.debug("Creating new span " + span); + } } if (!span.tags().containsKey(Span.SPAN_LOCAL_COMPONENT_TAG_NAME)) { this.tracer.addTag(Span.SPAN_LOCAL_COMPONENT_TAG_NAME, HYSTRIX_COMPONENT); @@ -146,9 +155,15 @@ public class SleuthHystrixConcurrencyStrategy extends HystrixConcurrencyStrategy } finally { if (created) { + if (log.isDebugEnabled()) { + log.debug("Closing span since it was created" + span); + } this.tracer.close(span); } else { + if (log.isDebugEnabled()) { + log.debug("Detaching span since it was continued " + span); + } this.tracer.detach(span); } } 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 cf32f75de..e5222e2cc 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 @@ -241,7 +241,7 @@ public class TraceFilter extends GenericFilterBean { } if (parent.isRemote()) { if (log.isDebugEnabled()) { - log.debug("Sending the parent span " + parent + " to Zipkin"); + log.debug("Trying to send the parent span " + parent + " to Zipkin"); } parent.stop(); parent.logEvent(Span.SERVER_SEND); diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignRequestInjector.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignRequestInjector.java new file mode 100644 index 000000000..f1323a35e --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignRequestInjector.java @@ -0,0 +1,79 @@ +/* + * Copyright 2013-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.sleuth.instrument.web.client.feign; + +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; + +import org.springframework.cloud.sleuth.Span; +import org.springframework.cloud.sleuth.SpanInjector; +import org.springframework.util.StringUtils; + +import feign.Request; + +/** + * Span injector that injects tracing info to {@link Request} via {@link AtomicReference} + * since {@link Request} is immutable. + * + * @author Marcin Grzejszczak + * + * @since 1.0.0 + */ +class FeignRequestInjector implements SpanInjector> { + + @Override + public void inject(Span span, AtomicReference carrier) { + String method = carrier.get().method(); + String url = carrier.get().url(); + Map> headers = new HashMap<>(carrier.get().headers()); + byte[] body = carrier.get().body(); + Charset charset = carrier.get().charset(); + if (span == null) { + setHeader(headers, Span.SAMPLED_NAME, Span.SPAN_NOT_SAMPLED); + carrier.set(Request.create(method, url, headers, body, charset)); + return; + } + setHeader(headers, Span.TRACE_ID_NAME, Span.idToHex(span.getTraceId())); + setHeader(headers, Span.SPAN_NAME_NAME, span.getName()); + setHeader(headers, Span.SPAN_ID_NAME, Span.idToHex(span.getSpanId())); + setHeader(headers, Span.SAMPLED_NAME, span.isExportable() ? + Span.SPAN_SAMPLED : Span.SPAN_NOT_SAMPLED); + Long parentId = getParentId(span); + if (parentId != null) { + setHeader(headers, Span.PARENT_ID_NAME, Span.idToHex(parentId)); + } + setHeader(headers, Span.PROCESS_ID_NAME, span.getProcessId()); + carrier.set(Request.create(method, url, headers, body, charset)); + } + + private Long getParentId(Span span) { + return !span.getParents().isEmpty() ? span.getParents().get(0) : null; + } + + protected void setHeader(Map> headers, String name, String value) { + if (StringUtils.hasText(value) && !headers.containsKey(name)) { + List list = new ArrayList<>(); + list.add(value); + headers.put(name, list); + } + } +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignRequestTemplateInjector.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignRequestTemplateInjector.java deleted file mode 100644 index 118927f5b..000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignRequestTemplateInjector.java +++ /dev/null @@ -1,61 +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 org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.SpanInjector; -import org.springframework.util.StringUtils; - -import feign.RequestTemplate; - -/** - * Span injector that injects tracing info to {@link RequestTemplate} - * - * @author Marcin Grzejszczak - * - * @since 1.0.0 - */ -class FeignRequestTemplateInjector implements SpanInjector { - - @Override - public void inject(Span span, RequestTemplate carrier) { - if (span == null) { - setHeader(carrier, Span.SAMPLED_NAME, Span.SPAN_NOT_SAMPLED); - return; - } - carrier.header(Span.TRACE_ID_NAME, Span.idToHex(span.getTraceId())); - setHeader(carrier, Span.SPAN_NAME_NAME, span.getName()); - setHeader(carrier, Span.SPAN_ID_NAME, Span.idToHex(span.getSpanId())); - setHeader(carrier, Span.SAMPLED_NAME, span.isExportable() ? - Span.SPAN_SAMPLED : Span.SPAN_NOT_SAMPLED); - Long parentId = getParentId(span); - if (parentId != null) { - setHeader(carrier, Span.PARENT_ID_NAME, Span.idToHex(parentId)); - } - setHeader(carrier, Span.PROCESS_ID_NAME, span.getProcessId()); - } - - private Long getParentId(Span span) { - return !span.getParents().isEmpty() ? span.getParents().get(0) : null; - } - - protected void setHeader(RequestTemplate request, String name, String value) { - if (StringUtils.hasText(value) && !request.headers().containsKey(name)) { - request.header(name, value); - } - } -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClient.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClient.java index fa9e85df3..1c66c0108 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClient.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClient.java @@ -19,6 +19,7 @@ package org.springframework.cloud.sleuth.instrument.web.client.feign; import java.io.IOException; import java.lang.invoke.MethodHandles; import java.net.URI; +import java.util.concurrent.atomic.AtomicReference; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -39,7 +40,7 @@ import feign.Response; * * @since 1.0.0 */ -final class TraceFeignClient implements Client { +class TraceFeignClient implements Client { private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); @@ -47,6 +48,7 @@ final class TraceFeignClient implements Client { private HttpTraceKeysInjector keysInjector; private final BeanFactory beanFactory; private Tracer tracer; + private final FeignRequestInjector spanInjector = new FeignRequestInjector(); TraceFeignClient(BeanFactory beanFactory) { this.beanFactory = beanFactory; @@ -60,19 +62,40 @@ final class TraceFeignClient implements Client { @Override public Response execute(Request request, Request.Options options) throws IOException { + String spanName = getSpanName(request); + Span span = getTracer().createSpan(spanName); + if (log.isDebugEnabled()) { + log.debug("Created new Feign span " + span); + } try { + AtomicReference feignRequest = new AtomicReference<>(request); + this.spanInjector.inject(span, feignRequest); + span.logEvent(Span.CLIENT_SEND); addRequestTags(request); - Response response = this.delegate.execute(request, options); + Request modifiedRequest = feignRequest.get(); + if (log.isDebugEnabled()) { + log.debug("The modified request equals " + modifiedRequest); + } + Response response = this.delegate.execute(modifiedRequest, options); logCr(); return response; } catch (RuntimeException | IOException e) { logError(e); throw e; } finally { - closeSpan(); + closeSpan(span); } } + private String getSpanName(Request request) { + URI uri = URI.create(request.url()); + return uriScheme(uri) + ":" + uri.getPath(); + } + + private String uriScheme(URI uri) { + return uri.getScheme() == null ? "http" : uri.getScheme(); + } + /** * Adds HTTP tags to the client side span */ @@ -89,8 +112,7 @@ final class TraceFeignClient implements Client { return this.keysInjector; } - private void closeSpan() { - Span span = getTracer().getCurrentSpan(); + private void closeSpan(Span span) { if (span != null) { if (log.isDebugEnabled()) { log.debug("Closing Feign span " + span); @@ -103,7 +125,7 @@ final class TraceFeignClient implements Client { Span span = getTracer().getCurrentSpan(); if (span != null) { if (log.isDebugEnabled()) { - log.debug("Closing Feign span and logging CR" + span); + log.debug("Closing Feign span and logging CR " + span); } span.logEvent(Span.CLIENT_RECV); } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientAutoConfiguration.java index 10da31b1b..6595fefa2 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientAutoConfiguration.java @@ -32,7 +32,6 @@ import org.springframework.context.annotation.Scope; import feign.Client; import feign.Feign; -import feign.RequestInterceptor; /** * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration @@ -84,13 +83,4 @@ public class TraceFeignClientAutoConfiguration { TraceFeignObjectWrapper traceFeignObjectWrapper(BeanFactory beanFactory) { return new TraceFeignObjectWrapper(beanFactory); } - - /** - * Sleuth {@link feign.RequestInterceptor} that either starts a new Span or continues - * an existing one if a retry takes place. - */ - @Bean - RequestInterceptor traceIdRequestInterceptor(Tracer tracer) { - return new TraceFeignRequestInterceptor(tracer, new FeignRequestTemplateInjector()); - } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignRequestInterceptor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignRequestInterceptor.java deleted file mode 100644 index 425e5bda6..000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignRequestInterceptor.java +++ /dev/null @@ -1,63 +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.net.URI; - -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.SpanInjector; -import org.springframework.cloud.sleuth.Tracer; - -import feign.RequestInterceptor; -import feign.RequestTemplate; - -/** - * A request interceptor that sets tracing information in the headers. - * - * @author Marcin Grzejszczak - * - * @since 1.0.0 - */ -final class TraceFeignRequestInterceptor implements RequestInterceptor { - - private final Tracer tracer; - private final SpanInjector spanInjector; - - TraceFeignRequestInterceptor(Tracer tracer, - SpanInjector spanInjector) { - this.tracer = tracer; - this.spanInjector = spanInjector; - } - - @Override - public void apply(RequestTemplate template) { - String spanName = getSpanName(template); - Span span = this.tracer.createSpan(spanName); - this.spanInjector.inject(span, template); - span.logEvent(Span.CLIENT_SEND); - } - - private String getSpanName(RequestTemplate template) { - URI uri = URI.create(template.url()); - return uriScheme(uri) + ":" + uri.getPath(); - } - - private String uriScheme(URI uri) { - return uri.getScheme() == null ? "http" : uri.getScheme(); - } - -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceLoadBalancerFeignClient.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceLoadBalancerFeignClient.java index d667f325e..fd4655e72 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceLoadBalancerFeignClient.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceLoadBalancerFeignClient.java @@ -2,7 +2,6 @@ package org.springframework.cloud.sleuth.instrument.web.client.feign; import java.io.IOException; import java.lang.invoke.MethodHandles; -import java.util.Objects; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -10,7 +9,6 @@ import org.springframework.beans.factory.BeanFactory; import org.springframework.cloud.netflix.feign.ribbon.CachingSpringLoadBalancerFactory; import org.springframework.cloud.netflix.feign.ribbon.LoadBalancerFeignClient; import org.springframework.cloud.netflix.ribbon.SpringClientFactory; -import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.Tracer; import feign.Client; @@ -40,23 +38,7 @@ class TraceLoadBalancerFeignClient extends LoadBalancerFeignClient { @Override public Response execute(Request request, Request.Options options) throws IOException { - Span currentSpan = tracer().getCurrentSpan(); - if (log.isDebugEnabled()) { - log.debug("Current span is " + currentSpan); - } - try { - return super.execute(request, options); - } catch (Exception e) { - if (Objects.equals(currentSpan, tracer().getCurrentSpan())) { - if (log.isDebugEnabled()) { - log.debug("Closing span " + currentSpan + " due to exception which is " - + "not handled by Feign. This can happen when the load balancer " - + "threw exception before Feign even managed to do sth about it"); - } - tracer().close(currentSpan); - } - throw e; - } + return super.execute(request, options); } private static Client wrap(Client delegate, BeanFactory beanFactory) { diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/exception/WebClientExceptionTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/exception/WebClientExceptionTests.java index 2d4fce19b..73d167bec 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/exception/WebClientExceptionTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/exception/WebClientExceptionTests.java @@ -17,6 +17,7 @@ package org.springframework.cloud.sleuth.instrument.web.client.exception; import java.io.IOException; +import java.lang.invoke.MethodHandles; import java.util.Collections; import java.util.Map; @@ -24,12 +25,16 @@ import com.netflix.loadbalancer.BaseLoadBalancer; import com.netflix.loadbalancer.ILoadBalancer; import com.netflix.loadbalancer.Server; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; +import org.junit.contrib.java.lang.system.SystemErrRule; +import org.junit.contrib.java.lang.system.SystemOutRule; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; @@ -68,10 +73,16 @@ import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; "spring.application.name=exceptionservice" }, randomPort = true) public class WebClientExceptionTests { + private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); + @ClassRule public static final SpringClassRule SCR = new SpringClassRule(); @Rule public final SpringMethodRule springMethodRule = new SpringMethodRule(); + @Rule + public final SystemErrRule systemErrRule = new SystemErrRule().enableLog(); + @Rule + public final SystemOutRule systemOutRule = new SystemOutRule().enableLog(); @Autowired TestFeignInterfaceWithException testFeignInterfaceWithException; @Autowired @LoadBalanced RestTemplate template; @@ -85,7 +96,6 @@ public class WebClientExceptionTests { @After public void close() { - ExceptionUtils.setFail(false); TestSpanContextHolder.removeCurrentSpan(); } @@ -95,6 +105,7 @@ public class WebClientExceptionTests { public void shouldCloseSpanUponException(ResponseEntityProvider provider) throws IOException { Span span = this.tracer.createSpan("new trace"); + log.info("Started new span " + span); try { provider.get(this); @@ -107,6 +118,9 @@ public class WebClientExceptionTests { then(ExceptionUtils.getLastException()).isNull(); then(this.tracer.getCurrentSpan()).isEqualTo(span); this.tracer.close(span); + then(ExceptionUtils.getLastException()).isNull(); + then(this.systemErrRule.getLog()).doesNotContain("Tried to detach trace span but it is not the current span"); + then(this.systemOutRule.getLog()).doesNotContain("Tried to detach trace span but it is not the current span"); } Object[] parametersForShouldCloseSpanUponException() { diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignRetriesTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignRetriesTests.java index 05365dab8..daaf6fba4 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignRetriesTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignRetriesTests.java @@ -46,7 +46,7 @@ import org.springframework.cloud.sleuth.util.ExceptionUtils; import feign.Client; import feign.Feign; import feign.FeignException; -import feign.RequestInterceptor; +import feign.Request; import feign.RequestLine; import feign.Response; import okhttp3.mockwebserver.MockWebServer; @@ -68,8 +68,6 @@ public class FeignRetriesTests { ArrayListSpanAccumulator spanAccumulator = new ArrayListSpanAccumulator(); Tracer tracer = new DefaultTracer(new AlwaysSampler(), new Random(), new DefaultSpanNamer(), new NoOpSpanLogger(), this.spanAccumulator); - FeignRequestTemplateInjector injector = new FeignRequestTemplateInjector(); - TraceFeignRequestInterceptor interceptor = new TraceFeignRequestInterceptor(tracer, injector); @Before @After @@ -91,7 +89,6 @@ public class FeignRetriesTests { TestInterface api = Feign.builder() .client(new TraceFeignClient(beanFactory, client)) - .requestInterceptor(interceptor) .target(TestInterface.class, url); try { @@ -118,14 +115,15 @@ public class FeignRetriesTests { "OK", Charset.defaultCharset()); } }; - RequestInterceptor requestInterceptor = template -> { - atomicInteger.incrementAndGet(); - interceptor.apply(template); - }; TestInterface api = Feign.builder() - .client(new TraceFeignClient(beanFactory, client)) - .requestInterceptor(requestInterceptor) + .client(new TraceFeignClient(beanFactory, client) { + @Override public Response execute(Request request, + Request.Options options) throws IOException { + atomicInteger.incrementAndGet(); + return super.execute(request, options); + } + }) .target(TestInterface.class, url); then(api.decodedPost()).isEqualTo("OK"); diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientTests.java index ba6905f22..1477bd2ce 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientTests.java @@ -74,18 +74,18 @@ public class TraceFeignClientTests { @Test public void should_log_cr_when_response_successful() throws IOException { - this.tracer.createSpan("foo"); + Span span = this.tracer.createSpan("foo"); Response response = this.traceFeignClient.execute( Request.create("GET", "http://foo", new HashMap<>(), "".getBytes(), Charset.defaultCharset()), new Request.Options()); - then(this.tracer.getCurrentSpan()).isNull(); + then(this.tracer.getCurrentSpan()).isEqualTo(span); then(this.spanAccumulator.getSpans().get(0)).hasLoggedAnEvent(Span.CLIENT_RECV); } @Test public void should_log_error_when_exception_thrown() throws IOException { - this.tracer.createSpan("foo"); + Span span = this.tracer.createSpan("foo"); BDDMockito.given(this.client.execute(BDDMockito.any(), BDDMockito.any())) .willThrow(new RuntimeException("exception has occurred")); @@ -96,7 +96,7 @@ public class TraceFeignClientTests { SleuthAssertions.fail("Exception should have been thrown"); } catch (Exception e) {} - then(this.tracer.getCurrentSpan()).isNull(); + then(this.tracer.getCurrentSpan()).isEqualTo(span); then(this.spanAccumulator.getSpans().get(0)) .hasNotLoggedAnEvent(Span.CLIENT_RECV) .hasATag("error", "exception has occurred"); diff --git a/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/StreamSpanReporter.java b/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/StreamSpanReporter.java index 575f06515..935b789be 100644 --- a/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/StreamSpanReporter.java +++ b/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/StreamSpanReporter.java @@ -16,12 +16,15 @@ package org.springframework.cloud.sleuth.stream; +import java.lang.invoke.MethodHandles; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.SpanReporter; import org.springframework.cloud.sleuth.metric.SpanMetricReporter; @@ -38,6 +41,8 @@ import org.springframework.integration.annotation.Poller; @MessageEndpoint public class StreamSpanReporter implements SpanReporter { + private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); + /** * Bean name for the * {@link org.springframework.integration.scheduling.PollerMetadata @@ -79,6 +84,10 @@ public class StreamSpanReporter implements SpanReporter { public void report(Span span) { if (span.isExportable()) { this.queue.add(span); + } else { + if (log.isDebugEnabled()) { + log.debug("The span " + span + " will not be sent to Zipkin due to sampling"); + } } } } diff --git a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListener.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListener.java index 636b88a6b..07815baab 100644 --- a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListener.java +++ b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListener.java @@ -204,6 +204,10 @@ public class ZipkinSpanListener implements SpanReporter { public void report(Span span) { if (span.isExportable()) { this.reporter.report(convert(span)); + } else { + if (log.isDebugEnabled()) { + log.debug("The span " + span + " will not be sent to Zipkin due to sampling"); + } } } } From 2e4c59b3c69c942b234af276719f874e7c14cebf Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Thu, 8 Sep 2016 22:45:02 +0200 Subject: [PATCH 073/235] Bumping versions before release --- benchmarks/pom.xml | 2 +- docs/pom.xml | 2 +- pom.xml | 6 +++--- spring-cloud-sleuth-core/pom.xml | 2 +- spring-cloud-sleuth-dependencies/pom.xml | 6 +++--- spring-cloud-sleuth-samples/pom.xml | 2 +- .../spring-cloud-sleuth-sample-feign/pom.xml | 2 +- .../spring-cloud-sleuth-sample-messaging/pom.xml | 2 +- .../spring-cloud-sleuth-sample-ribbon/pom.xml | 2 +- .../spring-cloud-sleuth-sample-stream/pom.xml | 2 +- .../spring-cloud-sleuth-sample-test-core/pom.xml | 2 +- .../spring-cloud-sleuth-sample-websocket/pom.xml | 2 +- .../spring-cloud-sleuth-sample-zipkin-stream/pom.xml | 2 +- .../spring-cloud-sleuth-sample-zipkin/pom.xml | 2 +- .../spring-cloud-sleuth-sample/pom.xml | 2 +- spring-cloud-sleuth-stream/pom.xml | 2 +- spring-cloud-sleuth-zipkin-stream/pom.xml | 2 +- spring-cloud-sleuth-zipkin/pom.xml | 2 +- spring-cloud-starter-sleuth/pom.xml | 2 +- spring-cloud-starter-zipkin/pom.xml | 2 +- 20 files changed, 24 insertions(+), 24 deletions(-) diff --git a/benchmarks/pom.xml b/benchmarks/pom.xml index f8386837c..95de7becc 100644 --- a/benchmarks/pom.xml +++ b/benchmarks/pom.xml @@ -17,7 +17,7 @@ Benchmarks Benchmarks (JMH) org.springframework.cloud - 1.0.8.BUILD-SNAPSHOT + 1.0.8.RELEASE benchmarks diff --git a/docs/pom.xml b/docs/pom.xml index fad55a1b6..2dcc5796a 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.8.BUILD-SNAPSHOT + 1.0.8.RELEASE spring-cloud-sleuth-docs pom diff --git a/pom.xml b/pom.xml index 5ee2511be..85355e3fb 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 spring-cloud-sleuth - 1.0.8.BUILD-SNAPSHOT + 1.0.8.RELEASE pom Spring Cloud Sleuth Spring Cloud Sleuth @@ -13,7 +13,7 @@ org.springframework.cloud spring-cloud-build - 1.1.3.BUILD-SNAPSHOT + 1.1.2.RELEASE @@ -208,7 +208,7 @@ 1.8 2.19.1 2.17 - 1.1.3.BUILD-SNAPSHOT + 1.1.2.RELEASE diff --git a/spring-cloud-sleuth-core/pom.xml b/spring-cloud-sleuth-core/pom.xml index b08f90051..1894e6836 100644 --- a/spring-cloud-sleuth-core/pom.xml +++ b/spring-cloud-sleuth-core/pom.xml @@ -11,7 +11,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.8.BUILD-SNAPSHOT + 1.0.8.RELEASE .. diff --git a/spring-cloud-sleuth-dependencies/pom.xml b/spring-cloud-sleuth-dependencies/pom.xml index 57adecd81..89b9d6235 100644 --- a/spring-cloud-sleuth-dependencies/pom.xml +++ b/spring-cloud-sleuth-dependencies/pom.xml @@ -5,16 +5,16 @@ spring-cloud-dependencies-parent org.springframework.cloud - 1.1.3.BUILD-SNAPSHOT + 1.1.2.RELEASE spring-cloud-sleuth-dependencies - 1.0.8.BUILD-SNAPSHOT + 1.0.8.RELEASE pom spring-cloud-sleuth-dependencies Spring Cloud Sleuth Dependencies - 1.1.6.BUILD-SNAPSHOT + 1.1.6.RELEASE 1.8.4 1.1.5 diff --git a/spring-cloud-sleuth-samples/pom.xml b/spring-cloud-sleuth-samples/pom.xml index 8d69d04ce..8ee4d59e1 100644 --- a/spring-cloud-sleuth-samples/pom.xml +++ b/spring-cloud-sleuth-samples/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.8.BUILD-SNAPSHOT + 1.0.8.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml index 3fc733f4d..8b5713636 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml @@ -20,7 +20,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.8.BUILD-SNAPSHOT + 1.0.8.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml index b33468046..2c56f5246 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.8.BUILD-SNAPSHOT + 1.0.8.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml index 18b9c0f63..9366841a2 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.8.BUILD-SNAPSHOT + 1.0.8.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml index 6e4f38d21..beed80cf2 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.8.BUILD-SNAPSHOT + 1.0.8.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml index 51435355d..ec03ec186 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.8.BUILD-SNAPSHOT + 1.0.8.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml index 1a90dd8e4..4593e411e 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.8.BUILD-SNAPSHOT + 1.0.8.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml index aa2f92895..06c4ec65c 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.8.BUILD-SNAPSHOT + 1.0.8.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml index 8224b23f7..8ef0f7063 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml @@ -27,7 +27,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.8.BUILD-SNAPSHOT + 1.0.8.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml index d73d8c7ea..d710e93b4 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.8.BUILD-SNAPSHOT + 1.0.8.RELEASE .. diff --git a/spring-cloud-sleuth-stream/pom.xml b/spring-cloud-sleuth-stream/pom.xml index dd1dca251..e3742aaea 100644 --- a/spring-cloud-sleuth-stream/pom.xml +++ b/spring-cloud-sleuth-stream/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.8.BUILD-SNAPSHOT + 1.0.8.RELEASE diff --git a/spring-cloud-sleuth-zipkin-stream/pom.xml b/spring-cloud-sleuth-zipkin-stream/pom.xml index baf07e8aa..567a9efa5 100644 --- a/spring-cloud-sleuth-zipkin-stream/pom.xml +++ b/spring-cloud-sleuth-zipkin-stream/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.8.BUILD-SNAPSHOT + 1.0.8.RELEASE diff --git a/spring-cloud-sleuth-zipkin/pom.xml b/spring-cloud-sleuth-zipkin/pom.xml index 66bdbb699..07ee06afc 100644 --- a/spring-cloud-sleuth-zipkin/pom.xml +++ b/spring-cloud-sleuth-zipkin/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.8.BUILD-SNAPSHOT + 1.0.8.RELEASE .. diff --git a/spring-cloud-starter-sleuth/pom.xml b/spring-cloud-starter-sleuth/pom.xml index 7095829c2..3e768d69f 100644 --- a/spring-cloud-starter-sleuth/pom.xml +++ b/spring-cloud-starter-sleuth/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.8.BUILD-SNAPSHOT + 1.0.8.RELEASE .. spring-cloud-starter-sleuth diff --git a/spring-cloud-starter-zipkin/pom.xml b/spring-cloud-starter-zipkin/pom.xml index 8f47fa8ea..f7d6cc859 100644 --- a/spring-cloud-starter-zipkin/pom.xml +++ b/spring-cloud-starter-zipkin/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.8.BUILD-SNAPSHOT + 1.0.8.RELEASE .. spring-cloud-starter-zipkin From 8a32f02f9f30664d72cf613363b019ca14555c3f Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Thu, 8 Sep 2016 23:23:24 +0200 Subject: [PATCH 074/235] Going back to SNAPSHOTs --- benchmarks/pom.xml | 2 +- docs/pom.xml | 2 +- pom.xml | 6 +++--- spring-cloud-sleuth-core/pom.xml | 2 +- spring-cloud-sleuth-dependencies/pom.xml | 6 +++--- spring-cloud-sleuth-samples/pom.xml | 2 +- .../spring-cloud-sleuth-sample-feign/pom.xml | 2 +- .../spring-cloud-sleuth-sample-messaging/pom.xml | 2 +- .../spring-cloud-sleuth-sample-ribbon/pom.xml | 2 +- .../spring-cloud-sleuth-sample-stream/pom.xml | 2 +- .../spring-cloud-sleuth-sample-test-core/pom.xml | 2 +- .../spring-cloud-sleuth-sample-websocket/pom.xml | 2 +- .../spring-cloud-sleuth-sample-zipkin-stream/pom.xml | 2 +- .../spring-cloud-sleuth-sample-zipkin/pom.xml | 2 +- .../spring-cloud-sleuth-sample/pom.xml | 2 +- spring-cloud-sleuth-stream/pom.xml | 2 +- spring-cloud-sleuth-zipkin-stream/pom.xml | 2 +- spring-cloud-sleuth-zipkin/pom.xml | 2 +- spring-cloud-starter-sleuth/pom.xml | 2 +- spring-cloud-starter-zipkin/pom.xml | 2 +- 20 files changed, 24 insertions(+), 24 deletions(-) diff --git a/benchmarks/pom.xml b/benchmarks/pom.xml index 95de7becc..e50b4d101 100644 --- a/benchmarks/pom.xml +++ b/benchmarks/pom.xml @@ -17,7 +17,7 @@ Benchmarks Benchmarks (JMH) org.springframework.cloud - 1.0.8.RELEASE + 1.0.9.BUILD-SNAPSHOT benchmarks diff --git a/docs/pom.xml b/docs/pom.xml index 2dcc5796a..08aa8fe9d 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.8.RELEASE + 1.0.9.BUILD-SNAPSHOT spring-cloud-sleuth-docs pom diff --git a/pom.xml b/pom.xml index 85355e3fb..ebcac002a 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 spring-cloud-sleuth - 1.0.8.RELEASE + 1.0.9.BUILD-SNAPSHOT pom Spring Cloud Sleuth Spring Cloud Sleuth @@ -13,7 +13,7 @@ org.springframework.cloud spring-cloud-build - 1.1.2.RELEASE + 1.1.3.BUILD-SNAPSHOT @@ -208,7 +208,7 @@ 1.8 2.19.1 2.17 - 1.1.2.RELEASE + 1.1.3.BUILD-SNAPSHOT diff --git a/spring-cloud-sleuth-core/pom.xml b/spring-cloud-sleuth-core/pom.xml index 1894e6836..8c964bde4 100644 --- a/spring-cloud-sleuth-core/pom.xml +++ b/spring-cloud-sleuth-core/pom.xml @@ -11,7 +11,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.8.RELEASE + 1.0.9.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-dependencies/pom.xml b/spring-cloud-sleuth-dependencies/pom.xml index 89b9d6235..0f62d7ca8 100644 --- a/spring-cloud-sleuth-dependencies/pom.xml +++ b/spring-cloud-sleuth-dependencies/pom.xml @@ -5,16 +5,16 @@ spring-cloud-dependencies-parent org.springframework.cloud - 1.1.2.RELEASE + 1.1.3.BUILD-SNAPSHOT spring-cloud-sleuth-dependencies - 1.0.8.RELEASE + 1.0.9.BUILD-SNAPSHOT pom spring-cloud-sleuth-dependencies Spring Cloud Sleuth Dependencies - 1.1.6.RELEASE + 1.1.7.BUILD-SNAPSHOT 1.8.4 1.1.5 diff --git a/spring-cloud-sleuth-samples/pom.xml b/spring-cloud-sleuth-samples/pom.xml index 8ee4d59e1..4c5f15eaa 100644 --- a/spring-cloud-sleuth-samples/pom.xml +++ b/spring-cloud-sleuth-samples/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.8.RELEASE + 1.0.9.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml index 8b5713636..72c751464 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml @@ -20,7 +20,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.8.RELEASE + 1.0.9.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml index 2c56f5246..22fca2edf 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.8.RELEASE + 1.0.9.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml index 9366841a2..ec770366d 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.8.RELEASE + 1.0.9.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml index beed80cf2..2beda5aee 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.8.RELEASE + 1.0.9.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml index ec03ec186..6d3bc2beb 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.8.RELEASE + 1.0.9.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml index 4593e411e..e583060ad 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.8.RELEASE + 1.0.9.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml index 06c4ec65c..d5721d34c 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.8.RELEASE + 1.0.9.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml index 8ef0f7063..227df1f16 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml @@ -27,7 +27,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.8.RELEASE + 1.0.9.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml index d710e93b4..9bf911ca6 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.8.RELEASE + 1.0.9.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-stream/pom.xml b/spring-cloud-sleuth-stream/pom.xml index e3742aaea..a00600007 100644 --- a/spring-cloud-sleuth-stream/pom.xml +++ b/spring-cloud-sleuth-stream/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.8.RELEASE + 1.0.9.BUILD-SNAPSHOT diff --git a/spring-cloud-sleuth-zipkin-stream/pom.xml b/spring-cloud-sleuth-zipkin-stream/pom.xml index 567a9efa5..586148b9a 100644 --- a/spring-cloud-sleuth-zipkin-stream/pom.xml +++ b/spring-cloud-sleuth-zipkin-stream/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.8.RELEASE + 1.0.9.BUILD-SNAPSHOT diff --git a/spring-cloud-sleuth-zipkin/pom.xml b/spring-cloud-sleuth-zipkin/pom.xml index 07ee06afc..2d8c740c8 100644 --- a/spring-cloud-sleuth-zipkin/pom.xml +++ b/spring-cloud-sleuth-zipkin/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.8.RELEASE + 1.0.9.BUILD-SNAPSHOT .. diff --git a/spring-cloud-starter-sleuth/pom.xml b/spring-cloud-starter-sleuth/pom.xml index 3e768d69f..b94c13a40 100644 --- a/spring-cloud-starter-sleuth/pom.xml +++ b/spring-cloud-starter-sleuth/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.8.RELEASE + 1.0.9.BUILD-SNAPSHOT .. spring-cloud-starter-sleuth diff --git a/spring-cloud-starter-zipkin/pom.xml b/spring-cloud-starter-zipkin/pom.xml index f7d6cc859..218e633f6 100644 --- a/spring-cloud-starter-zipkin/pom.xml +++ b/spring-cloud-starter-zipkin/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.8.RELEASE + 1.0.9.BUILD-SNAPSHOT .. spring-cloud-starter-zipkin From 4490b1b81e141b0594357b217fb66a8c27cb85da Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Tue, 13 Sep 2016 10:16:13 +0200 Subject: [PATCH 075/235] Simplified TraceZuulHandlerMapping without this change we were wrapping the ZuulHandlerMapping in its tracing representation. with this change we are simplifing that by adding interceptors fixes #399 --- ...ceZuulHandlerMappingBeanPostProcessor.java | 59 ++----------------- 1 file changed, 5 insertions(+), 54 deletions(-) diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulHandlerMappingBeanPostProcessor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulHandlerMappingBeanPostProcessor.java index 14eed9913..d6076e8c5 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulHandlerMappingBeanPostProcessor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulHandlerMappingBeanPostProcessor.java @@ -17,16 +17,12 @@ package org.springframework.cloud.sleuth.instrument.zuul; import java.lang.invoke.MethodHandles; -import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.config.BeanPostProcessor; -import org.springframework.boot.autoconfigure.web.ErrorController; -import org.springframework.cloud.netflix.zuul.filters.RouteLocator; -import org.springframework.cloud.netflix.zuul.web.ZuulController; import org.springframework.cloud.netflix.zuul.web.ZuulHandlerMapping; import org.springframework.cloud.sleuth.instrument.web.TraceHandlerInterceptor; @@ -42,9 +38,6 @@ class TraceZuulHandlerMappingBeanPostProcessor implements BeanPostProcessor { private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); private final BeanFactory beanFactory; - private RouteLocator routeLocator; - private ZuulController zuul; - private ErrorController errorController; public TraceZuulHandlerMappingBeanPostProcessor(BeanFactory beanFactory) { this.beanFactory = beanFactory; @@ -53,13 +46,13 @@ class TraceZuulHandlerMappingBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { - if (bean instanceof ZuulHandlerMapping && !(bean instanceof TraceZuulHandlerMapping)) { + if (bean instanceof ZuulHandlerMapping) { if (log.isDebugEnabled()) { - log.debug("Wrapping bean [" + beanName + "] of type [" + bean.getClass().getSimpleName() + - "] in its trace representation"); + log.debug("Attaching trace interceptor to bean [" + beanName + "] of type [" + bean.getClass().getSimpleName() + "]"); } - return new TraceZuulHandlerMapping(this.beanFactory, routeLocator(), zuulController(), - errorController()); + ZuulHandlerMapping zuulHandlerMapping = (ZuulHandlerMapping) bean; + zuulHandlerMapping.setInterceptors( + new Object[] { new TraceHandlerInterceptor(this.beanFactory) }); } return bean; } @@ -69,46 +62,4 @@ class TraceZuulHandlerMappingBeanPostProcessor implements BeanPostProcessor { throws BeansException { return bean; } - - private static class TraceZuulHandlerMapping extends ZuulHandlerMapping { - - private final BeanFactory beanFactory; - - public TraceZuulHandlerMapping(BeanFactory beanFactory, RouteLocator routeLocator, - ZuulController zuulController, ErrorController errorController) { - super(routeLocator, zuulController); - this.beanFactory = beanFactory; - setErrorController(errorController); - } - - @Override - protected void extendInterceptors(List interceptors) { - interceptors.add(new TraceHandlerInterceptor(this.beanFactory)); - } - } - - private RouteLocator routeLocator() { - if (this.routeLocator == null) { - this.routeLocator = this.beanFactory.getBean(RouteLocator.class); - } - return this.routeLocator; - } - - private ZuulController zuulController() { - if (this.zuul == null) { - this.zuul = this.beanFactory.getBean(ZuulController.class); - } - return this.zuul; - } - - private ErrorController errorController() { - if (this.errorController == null) { - try { - this.errorController = this.beanFactory.getBean(ErrorController.class); - } catch (BeansException b) { - return null; - } - } - return this.errorController; - } } From 00fe8e0f5ad663238cfe30d6179b3bc6b2d38f9f Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Tue, 13 Sep 2016 14:34:48 +0200 Subject: [PATCH 076/235] Added more explicit Tracer explanation without this change it could be nonclear where Tracer comes from and how you can use it. With this change hopefully it get properly explained fixes #402 --- docs/src/main/asciidoc/spring-cloud-sleuth.adoc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc index 08b7da2a0..1f59207b8 100644 --- a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc +++ b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc @@ -73,7 +73,7 @@ This approach may change in the future towards being lazy on this matter. == Span lifecycle -You can do the following operations on the Span by means of *Tracer* interface: +You can do the following operations on the Span by means of *org.springframework.cloud.sleuth.Tracer* interface: - <> - when you start a span its name is assigned and start timestamp is recorded. - <> - the span gets finished (the end time of the span is recorded) and if @@ -84,6 +84,8 @@ one that it continues. - <> - the span doesn't get stopped or closed. It only gets removed from the current thread. - <> - you can create a new span and set an explicit parent to it +TIP: Spring creates the instance of `Tracer` for you. In order to use it all you need is to just autowire it. + === Creating and closing spans [[creating-and-closing-spans]] You can manually create spans by using the *Tracer* interface. From 02473f7c1876a5a0e887987c87d88fac2b39e158 Mon Sep 17 00:00:00 2001 From: Adrian Cole Date: Thu, 15 Sep 2016 21:12:33 +0800 Subject: [PATCH 077/235] Tolerate reads of 128 bit X-B3-TraceId (#408) The first step of transitioning to 128bit `X-B3-TraceId` is tolerantly reading 32 character long ids by throwing away the high bits (any characters left of 16 characters). This allows the tracing system to more flexibly introduce 128bit trace id support in the future. Ex. when `X-B3-TraceId: 463ac35c9f6413ad48485a3953bb6124` is received, parse the lower 64 bits (right most 16 characters ex48485a3953bb6124) as the trace id. --- .../springframework/cloud/sleuth/Span.java | 26 ++++++++++++++----- .../cloud/sleuth/SpanTests.java | 10 +++++++ .../TraceChannelInterceptorTests.java | 14 ++++++++++ .../web/HttpServletRequestExtractorTests.java | 17 +++++++++++- 4 files changed, 60 insertions(+), 7 deletions(-) diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java index 3cbc67531..4a152355d 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java @@ -16,7 +16,6 @@ package org.springframework.cloud.sleuth; -import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -421,15 +420,30 @@ public class Span { } /** - * Represents hex string as long + * Parses a 1 to 32 character lower-hex string with no prefix into an unsigned long, tossing any + * bits higher than 64. */ public static long hexToId(String hexString) { Assert.hasText(hexString, "Can't convert empty hex string to long"); - try { - return new BigInteger(hexString, 16).longValue(); - } catch (NumberFormatException e) { - throw new IllegalArgumentException("Malformed id [" + hexString + "]", e); + int length = hexString.length(); + if (length < 1 || length > 32) throw new IllegalArgumentException("Malformed id"); + + // trim off any high bits + int i = length > 16 ? length - 16 : 0; + + long result = 0; + for (; i < length; i++) { + char c = hexString.charAt(i); + result <<= 4; + if (c >= '0' && c <= '9') { + result |= c - '0'; + } else if (c >= 'a' && c <= 'f') { + result |= c - 'a' + 10; + } else { + throw new IllegalArgumentException("Malformed id"); + } } + return result; } @Override diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/SpanTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/SpanTests.java index 1021a6213..bed930500 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/SpanTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/SpanTests.java @@ -52,6 +52,16 @@ public class SpanTests { then(someLong).isEqualTo(123123L); } + @Test + public void should_convert_lower_64bits_of_hex_string_to_long() throws Exception { + String hex128Bits = "463ac35c9f6413ad48485a3953bb6124"; + String lower64Bits = "48485a3953bb6124"; + + long someLong = Span.hexToId(hex128Bits); + + then(someLong).isEqualTo(Span.hexToId(lower64Bits)); + } + @Test(expected = IllegalArgumentException.class) public void should_throw_exception_when_null_string_is_to_be_converted_to_long() throws Exception { Span.hexToId(null); 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 37e173ebc..2ab5c63af 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 @@ -275,6 +275,20 @@ public class TraceChannelInterceptorTests implements MessageHandler { then(TestSpanContextHolder.getCurrentSpan()).isNull(); } + @Test + public void downgrades128bitIdsByDroppingHighBits() { + 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()); + then(this.message).isNotNull(); + + long traceId = Span.hexToId(this.message.getHeaders() + .get(TraceMessageHeaders.TRACE_ID_NAME, String.class)); + then(traceId).isEqualTo(Span.hexToId(lower64Bits)); + } + @Configuration @EnableAutoConfiguration static class App { diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/HttpServletRequestExtractorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/HttpServletRequestExtractorTests.java index fc660f2ad..fe1afc5df 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/HttpServletRequestExtractorTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/HttpServletRequestExtractorTests.java @@ -93,4 +93,19 @@ public class HttpServletRequestExtractorTests { then(e).hasMessageContaining("Malformed id"); } } -} \ No newline at end of file + + @Test + public void should_downgrade_128bit_trace_id_by_dropping_high_bits() { + String hex128Bits = "463ac35c9f6413ad48485a3953bb6124"; + String lower64Bits = "48485a3953bb6124"; + + 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); + + then(span.getTraceId()).isEqualTo(Span.hexToId(lower64Bits)); + } +} From e6d29898ddea922e5cb357705943cb5a3bbecf45 Mon Sep 17 00:00:00 2001 From: Adrian Cole Date: Thu, 15 Sep 2016 22:10:32 +0800 Subject: [PATCH 078/235] Reroutes HttpZipkinSpanReporter to use zipkin.reporter.AsyncReporter (#409) AsyncReporter is a more robust version of what we were doing before. Notably, it can give a memory threshold instead of span count for the backlog. This change ports to use AsyncReporter internally. See https://github.com/openzipkin/zipkin-reporter-java#asyncreporter --- benchmarks/pom.xml | 11 +- pom.xml | 11 +- spring-cloud-sleuth-dependencies/pom.xml | 13 +- spring-cloud-sleuth-samples/pom.xml | 4 +- spring-cloud-sleuth-zipkin/pom.xml | 8 + .../sleuth/zipkin/HttpZipkinSpanReporter.java | 146 +++--------------- .../sleuth/zipkin/ReporterMetricsAdapter.java | 43 ++++++ 7 files changed, 91 insertions(+), 145 deletions(-) create mode 100644 spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ReporterMetricsAdapter.java diff --git a/benchmarks/pom.xml b/benchmarks/pom.xml index e50b4d101..546f6a989 100644 --- a/benchmarks/pom.xml +++ b/benchmarks/pom.xml @@ -163,6 +163,9 @@ true + + false + spring-milestones @@ -172,14 +175,6 @@ false - - spring-staging - Spring Staging - https://repo.spring.io/libs-staging-local/ - - false - - spring-releases Spring Releases diff --git a/pom.xml b/pom.xml index ebcac002a..dc4b2e792 100644 --- a/pom.xml +++ b/pom.xml @@ -222,6 +222,9 @@ true + + false + spring-milestones @@ -234,14 +237,6 @@ false - - spring-staging - Spring Staging - https://repo.spring.io/libs-staging-local/ - - false - - spring-releases Spring Releases diff --git a/spring-cloud-sleuth-dependencies/pom.xml b/spring-cloud-sleuth-dependencies/pom.xml index 0f62d7ca8..3f2e86c29 100644 --- a/spring-cloud-sleuth-dependencies/pom.xml +++ b/spring-cloud-sleuth-dependencies/pom.xml @@ -16,7 +16,8 @@ 1.1.7.BUILD-SNAPSHOT 1.8.4 - 1.1.5 + 1.11.1 + 0.4.3 @@ -92,6 +93,16 @@ zipkin-junit ${zipkin.version} + + io.zipkin.reporter + zipkin-reporter + ${zipkin-reporter.version} + + + io.zipkin.reporter + zipkin-sender-urlconnection + ${zipkin-reporter.version} + diff --git a/spring-cloud-sleuth-samples/pom.xml b/spring-cloud-sleuth-samples/pom.xml index 4c5f15eaa..f0f558db9 100644 --- a/spring-cloud-sleuth-samples/pom.xml +++ b/spring-cloud-sleuth-samples/pom.xml @@ -59,12 +59,12 @@ io.zipkin.java zipkin - 1.1.5 + 1.11.1 io.zipkin.java zipkin-server - 1.1.5 + 1.11.1 diff --git a/spring-cloud-sleuth-zipkin/pom.xml b/spring-cloud-sleuth-zipkin/pom.xml index 2d8c740c8..f7e7484a5 100644 --- a/spring-cloud-sleuth-zipkin/pom.xml +++ b/spring-cloud-sleuth-zipkin/pom.xml @@ -61,6 +61,14 @@ io.zipkin.java zipkin + + io.zipkin.reporter + zipkin-reporter + + + io.zipkin.reporter + zipkin-sender-urlconnection + org.springframework spring-messaging diff --git a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/HttpZipkinSpanReporter.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/HttpZipkinSpanReporter.java index 917109248..98fb93bba 100644 --- a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/HttpZipkinSpanReporter.java +++ b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/HttpZipkinSpanReporter.java @@ -1,29 +1,15 @@ package org.springframework.cloud.sleuth.zipkin; -import java.io.ByteArrayOutputStream; import java.io.Closeable; import java.io.Flushable; -import java.io.IOException; -import java.io.InputStream; -import java.net.HttpURLConnection; -import java.net.URL; -import java.nio.charset.Charset; -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.List; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.Executors; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; -import java.util.zip.GZIPOutputStream; -import org.apache.commons.logging.Log; import org.springframework.cloud.sleuth.metric.SpanMetricReporter; -import zipkin.Codec; import zipkin.Span; +import zipkin.reporter.AsyncReporter; +import zipkin.reporter.urlconnection.URLConnectionSender; -import static java.util.concurrent.TimeUnit.SECONDS; /** * Submits spans using Zipkin's {@code POST /spans} endpoint. @@ -31,17 +17,9 @@ import static java.util.concurrent.TimeUnit.SECONDS; * @author Adrian Cole * @since 1.0.0 */ -public final class HttpZipkinSpanReporter - implements ZipkinSpanReporter, Flushable, Closeable { - private static final Log log = org.apache.commons.logging.LogFactory - .getLog(HttpZipkinSpanReporter.class); - private static final Charset UTF_8 = Charset.forName("UTF-8"); - - private final String url; - private final BlockingQueue pending = new LinkedBlockingQueue<>(1000); - private final Flusher flusher; // Nullable for testing - private final boolean compressionEnabled; - private final SpanMetricReporter spanMetricReporter; +public final class HttpZipkinSpanReporter implements ZipkinSpanReporter, Flushable, Closeable { + private final URLConnectionSender sender; + private final AsyncReporter delegate; /** * @param baseUrl URL of the zipkin query server instance. Like: http://localhost:9411/ @@ -51,10 +29,15 @@ public final class HttpZipkinSpanReporter */ public HttpZipkinSpanReporter(String baseUrl, int flushInterval, boolean compressionEnabled, SpanMetricReporter spanMetricReporter) { - this.url = baseUrl + (baseUrl.endsWith("/") ? "" : "/") + "api/v1/spans"; - this.flusher = flushInterval > 0 ? new Flusher(this, flushInterval) : null; - this.compressionEnabled = compressionEnabled; - this.spanMetricReporter = spanMetricReporter; + this.sender = URLConnectionSender.builder() + .endpoint(baseUrl + (baseUrl.endsWith("/") ? "" : "/") + "api/v1/spans") + .compressionEnabled(compressionEnabled) + .build(); + this.delegate = AsyncReporter.builder(this.sender) + .queuedMaxSpans(1000) // historical constraint. Note: AsyncReporter supports memory bounds + .messageTimeout(flushInterval, TimeUnit.SECONDS) + .metrics(new ReporterMetricsAdapter(spanMetricReporter)) + .build(); } /** @@ -64,10 +47,7 @@ public final class HttpZipkinSpanReporter */ @Override public void report(Span span) { - this.spanMetricReporter.incrementAcceptedSpans(1); - if (!this.pending.offer(span)) { - this.spanMetricReporter.incrementDroppedSpans(1); - } + this.delegate.report(span); } /** @@ -75,101 +55,15 @@ public final class HttpZipkinSpanReporter */ @Override public void flush() { - if (this.pending.isEmpty()) - return; - List drained = new ArrayList<>(this.pending.size()); - this.pending.drainTo(drained); - if (drained.isEmpty()) - return; - - // json-encode the spans for transport - byte[] json = Codec.JSON.writeSpans(drained); - // NOTE: https://github.com/openzipkin/zipkin-java/issues/66 will throw instead of return null. - if (json == null) { - if (log.isDebugEnabled()) { - log.debug("failed to encode spans, dropping them: " + drained); - } - this.spanMetricReporter.incrementDroppedSpans(drained.size()); - return; - } - - // Send the json to the zipkin endpoint - try { - postSpans(json); - } - catch (IOException e) { - if (log.isDebugEnabled()) { // don't pollute logs unless debug is on. - // TODO: logger test - log.debug( - "error POSTing spans to " + this.url + ": as json: " + new String(json, - UTF_8), e); - } - this.spanMetricReporter.incrementDroppedSpans(drained.size()); - } + this.delegate.flush(); } /** - * Calls flush on a fixed interval - */ - static final class Flusher implements Runnable { - final Flushable flushable; - final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); - - Flusher(Flushable flushable, int flushInterval) { - this.flushable = flushable; - this.scheduler.scheduleWithFixedDelay(this, 0, flushInterval, SECONDS); - } - - @Override - public void run() { - try { - this.flushable.flush(); - } - catch (IOException ignored) { - } - } - } - - void postSpans(byte[] json) throws IOException { - // intentionally not closing the connection, so as to use keep-alives - HttpURLConnection connection = (HttpURLConnection) new URL(this.url).openConnection(); - connection.setRequestMethod("POST"); - connection.addRequestProperty("Content-Type", "application/json"); - if (this.compressionEnabled) { - connection.addRequestProperty("Content-Encoding", "gzip"); - ByteArrayOutputStream gzipped = new ByteArrayOutputStream(); - try (GZIPOutputStream compressor = new GZIPOutputStream(gzipped)) { - compressor.write(json); - } - json = gzipped.toByteArray(); - } - connection.setDoOutput(true); - connection.setFixedLengthStreamingMode(json.length); - connection.getOutputStream().write(json); - - try (InputStream in = connection.getInputStream()) { - while (in.read() != -1); // skip - } - catch (IOException e) { - try (InputStream err = connection.getErrorStream()) { - if (err != null) { // possible, if the connection was dropped - while (err.read() != -1); // skip - } - } - throw e; - } - } - - /** - * Requests a cease of delivery. There will be at most one in-flight request processing after this - * call returns. + * Blocks until in-flight spans are sent and drops any that are left pending. */ @Override public void close() { - if (this.flusher != null) - this.flusher.scheduler.shutdown(); - // throw any outstanding spans on the floor - int dropped = this.pending.drainTo(new LinkedList<>()); - this.spanMetricReporter.incrementDroppedSpans(dropped); + this.delegate.close(); + this.sender.close(); } } diff --git a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ReporterMetricsAdapter.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ReporterMetricsAdapter.java new file mode 100644 index 000000000..b1772ff64 --- /dev/null +++ b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ReporterMetricsAdapter.java @@ -0,0 +1,43 @@ +package org.springframework.cloud.sleuth.zipkin; + +import org.springframework.cloud.sleuth.metric.SpanMetricReporter; + +import zipkin.reporter.ReporterMetrics; + +final class ReporterMetricsAdapter implements ReporterMetrics { + private final SpanMetricReporter spanMetricReporter; + + public ReporterMetricsAdapter(SpanMetricReporter spanMetricReporter) { + this.spanMetricReporter = spanMetricReporter; + } + + @Override + public ReporterMetrics forTransport(String transport) { + return this; + } + + @Override + public void incrementMessages() { + } + + @Override + public void incrementMessagesDropped() { + } + + @Override + public void incrementSpans(int i) { + this.spanMetricReporter.incrementAcceptedSpans(i); + } + + @Override + public void incrementSpanBytes(int i) { + } + + @Override + public void incrementMessageBytes(int i) { + } + + @Override public void incrementSpansDropped(int i) { + this.spanMetricReporter.incrementDroppedSpans(i); + } +} From 633631b91da381d6f8b76cc3c0e273d6dd4d895a Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Thu, 15 Sep 2016 16:13:35 +0200 Subject: [PATCH 079/235] Updating mvnw for all projects --- mvnw | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/mvnw b/mvnw index fe1957e36..a1af4c42a 100755 --- a/mvnw +++ b/mvnw @@ -226,12 +226,21 @@ export MAVEN_CMD_LINE_ARGS WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain +echo "Running version check for the first time to ensure all jars are downloaded. Without this wrong version might be chosen" VERSION=$(exec "$JAVACMD" \ $MAVEN_OPTS \ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ -Dexec.executable="echo" -Dexec.args='${project.version}' ${WRAPPER_LAUNCHER} -q --non-recursive org.codehaus.mojo:exec-maven-plugin:1.3.1:exec ) +echo "Running version check for the second time in order to output only the version" +VERSION=$(exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + -Dexec.executable="echo" -Dexec.args='${project.version}' ${WRAPPER_LAUNCHER} -q --non-recursive org.codehaus.mojo:exec-maven-plugin:1.3.1:exec ) +echo "The found version is [${VERSION}]" + if echo $VERSION | egrep -q 'M|RC'; then echo Activating \"milestone\" profile for version=\"$VERSION\" echo $MAVEN_ARGS | grep -q milestone || MAVEN_ARGS="$MAVEN_ARGS -Pmilestone" From beee1d2a3e374eb4ba06d64d4e49061033b4b6ab Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Mon, 19 Sep 2016 12:47:20 +0200 Subject: [PATCH 080/235] Updating mvnw for all projects --- mvnw | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mvnw b/mvnw index a1af4c42a..dbabd636a 100755 --- a/mvnw +++ b/mvnw @@ -231,14 +231,14 @@ VERSION=$(exec "$JAVACMD" \ $MAVEN_OPTS \ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ - -Dexec.executable="echo" -Dexec.args='${project.version}' ${WRAPPER_LAUNCHER} -q --non-recursive org.codehaus.mojo:exec-maven-plugin:1.3.1:exec ) + -Dexec.executable="echo" -Dexec.args='${project.version}' ${WRAPPER_LAUNCHER} -q -o --non-recursive org.codehaus.mojo:exec-maven-plugin:1.3.1:exec ) echo "Running version check for the second time in order to output only the version" VERSION=$(exec "$JAVACMD" \ $MAVEN_OPTS \ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ - -Dexec.executable="echo" -Dexec.args='${project.version}' ${WRAPPER_LAUNCHER} -q --non-recursive org.codehaus.mojo:exec-maven-plugin:1.3.1:exec ) + -Dexec.executable="echo" -Dexec.args='${project.version}' ${WRAPPER_LAUNCHER} -q -o --non-recursive org.codehaus.mojo:exec-maven-plugin:1.3.1:exec ) echo "The found version is [${VERSION}]" if echo $VERSION | egrep -q 'M|RC'; then From 18dab000ef965577a7be51aa337e6c9d6c4e5775 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Mon, 19 Sep 2016 14:18:54 +0200 Subject: [PATCH 081/235] Updating mvnw for all projects --- mvnw | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/mvnw b/mvnw index dbabd636a..2e3905698 100755 --- a/mvnw +++ b/mvnw @@ -226,19 +226,8 @@ export MAVEN_CMD_LINE_ARGS WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain -echo "Running version check for the first time to ensure all jars are downloaded. Without this wrong version might be chosen" -VERSION=$(exec "$JAVACMD" \ - $MAVEN_OPTS \ - -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ - "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ - -Dexec.executable="echo" -Dexec.args='${project.version}' ${WRAPPER_LAUNCHER} -q -o --non-recursive org.codehaus.mojo:exec-maven-plugin:1.3.1:exec ) - -echo "Running version check for the second time in order to output only the version" -VERSION=$(exec "$JAVACMD" \ - $MAVEN_OPTS \ - -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ - "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ - -Dexec.executable="echo" -Dexec.args='${project.version}' ${WRAPPER_LAUNCHER} -q -o --non-recursive org.codehaus.mojo:exec-maven-plugin:1.3.1:exec ) +eecho "Running version check" +VERSION=$( sed '\!//' -e 's!.*$!!' ) echo "The found version is [${VERSION}]" if echo $VERSION | egrep -q 'M|RC'; then @@ -249,7 +238,6 @@ else echo $MAVEN_ARGS | grep -q milestone && MAVEN_ARGS=$(echo $MAVEN_ARGS | sed -e 's/-Pmilestone//') fi - exec "$JAVACMD" \ $MAVEN_OPTS \ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ From 0a61f85e400dc7572d067b2b79c19c33bce7fd61 Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Fri, 16 Sep 2016 16:44:58 +0100 Subject: [PATCH 082/235] Move unnecessary dependencies up a level --- pom.xml | 24 ++++++++++++++++++++++++ spring-cloud-sleuth-dependencies/pom.xml | 19 ------------------- 2 files changed, 24 insertions(+), 19 deletions(-) diff --git a/pom.xml b/pom.xml index dc4b2e792..7b088e792 100644 --- a/pom.xml +++ b/pom.xml @@ -145,6 +145,27 @@ pom import + + org.springframework.cloud + spring-cloud-netflix-dependencies + ${spring-cloud-netflix.version} + pom + import + + + org.springframework.cloud + spring-cloud-commons-dependencies + ${spring-cloud-commons.version} + pom + import + + + org.springframework.cloud + spring-cloud-stream-dependencies + ${spring-cloud-stream.version} + pom + import + org.spockframework @@ -209,6 +230,9 @@ 2.19.1 2.17 1.1.3.BUILD-SNAPSHOT + 1.1.3.BUILD-SNAPSHOT + Brooklyn.BUILD-SNAPSHOT + 1.1.6.BUILD-SNAPSHOT diff --git a/spring-cloud-sleuth-dependencies/pom.xml b/spring-cloud-sleuth-dependencies/pom.xml index 3f2e86c29..995d3f6ce 100644 --- a/spring-cloud-sleuth-dependencies/pom.xml +++ b/spring-cloud-sleuth-dependencies/pom.xml @@ -14,20 +14,11 @@ spring-cloud-sleuth-dependencies Spring Cloud Sleuth Dependencies - 1.1.7.BUILD-SNAPSHOT - 1.8.4 1.11.1 0.4.3 - - org.springframework.cloud - spring-cloud-netflix-dependencies - ${spring-cloud-netflix.version} - pom - import - org.springframework.cloud spring-cloud-sleuth-core @@ -58,16 +49,6 @@ spring-cloud-starter-sleuth ${project.version} - - org.aspectj - aspectjrt - ${aspectj.version} - - - org.aspectj - aspectjweaver - ${aspectj.version} - io.zipkin.java zipkin From 6e61c21728ef8daecd44fe5debf5c2277c619a26 Mon Sep 17 00:00:00 2001 From: Adrian Cole Date: Mon, 19 Sep 2016 22:14:43 +0800 Subject: [PATCH 083/235] Updates to adjust for reporter metrics drift (#411) --- spring-cloud-sleuth-dependencies/pom.xml | 2 +- .../sleuth/zipkin/ReporterMetricsAdapter.java | 18 +++++++++++------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/spring-cloud-sleuth-dependencies/pom.xml b/spring-cloud-sleuth-dependencies/pom.xml index 995d3f6ce..63ecc4f89 100644 --- a/spring-cloud-sleuth-dependencies/pom.xml +++ b/spring-cloud-sleuth-dependencies/pom.xml @@ -15,7 +15,7 @@ Spring Cloud Sleuth Dependencies 1.11.1 - 0.4.3 + 0.5.0 diff --git a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ReporterMetricsAdapter.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ReporterMetricsAdapter.java index b1772ff64..61dd77e70 100644 --- a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ReporterMetricsAdapter.java +++ b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ReporterMetricsAdapter.java @@ -11,17 +11,12 @@ final class ReporterMetricsAdapter implements ReporterMetrics { this.spanMetricReporter = spanMetricReporter; } - @Override - public ReporterMetrics forTransport(String transport) { - return this; - } - @Override public void incrementMessages() { } @Override - public void incrementMessagesDropped() { + public void incrementMessagesDropped(Throwable throwable) { } @Override @@ -37,7 +32,16 @@ final class ReporterMetricsAdapter implements ReporterMetrics { public void incrementMessageBytes(int i) { } - @Override public void incrementSpansDropped(int i) { + @Override + public void incrementSpansDropped(int i) { this.spanMetricReporter.incrementDroppedSpans(i); } + + @Override + public void updateQueuedSpans(int i) { + } + + @Override + public void updateQueuedBytes(int i) { + } } From a293af7e37d776d00e343180bda17cd6c8c1d8b9 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Mon, 19 Sep 2016 19:31:49 +0200 Subject: [PATCH 084/235] Updating mvnw for all projects --- mvnw | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mvnw b/mvnw index 2e3905698..ee316a275 100755 --- a/mvnw +++ b/mvnw @@ -226,7 +226,7 @@ export MAVEN_CMD_LINE_ARGS WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain -eecho "Running version check" +echo "Running version check" VERSION=$( sed '\!//' -e 's!.*$!!' ) echo "The found version is [${VERSION}]" From b882749044e8245c7d7ba5e22d3ae52c5e95b04d Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Tue, 20 Sep 2016 13:48:13 +0100 Subject: [PATCH 085/235] Ensure version is scraped from parent pom --- mvnw | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mvnw b/mvnw index ee316a275..0a7dac221 100755 --- a/mvnw +++ b/mvnw @@ -227,7 +227,7 @@ export MAVEN_CMD_LINE_ARGS WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain echo "Running version check" -VERSION=$( sed '\!//' -e 's!.*$!!' ) +VERSION=$( sed '\!//' -e 's!.*$!!' ) echo "The found version is [${VERSION}]" if echo $VERSION | egrep -q 'M|RC'; then From 755e01d6464aa28854817b81d5b839ebb766075c Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Wed, 21 Sep 2016 12:55:19 +0200 Subject: [PATCH 086/235] Bumping versions before release --- benchmarks/pom.xml | 2 +- docs/pom.xml | 2 +- pom.xml | 12 ++++++------ spring-cloud-sleuth-core/pom.xml | 2 +- spring-cloud-sleuth-dependencies/pom.xml | 4 ++-- spring-cloud-sleuth-samples/pom.xml | 2 +- .../spring-cloud-sleuth-sample-feign/pom.xml | 2 +- .../spring-cloud-sleuth-sample-messaging/pom.xml | 2 +- .../spring-cloud-sleuth-sample-ribbon/pom.xml | 2 +- .../spring-cloud-sleuth-sample-stream/pom.xml | 2 +- .../spring-cloud-sleuth-sample-test-core/pom.xml | 2 +- .../spring-cloud-sleuth-sample-websocket/pom.xml | 2 +- .../spring-cloud-sleuth-sample-zipkin-stream/pom.xml | 2 +- .../spring-cloud-sleuth-sample-zipkin/pom.xml | 2 +- .../spring-cloud-sleuth-sample/pom.xml | 2 +- spring-cloud-sleuth-stream/pom.xml | 2 +- spring-cloud-sleuth-zipkin-stream/pom.xml | 2 +- spring-cloud-sleuth-zipkin/pom.xml | 2 +- spring-cloud-starter-sleuth/pom.xml | 2 +- spring-cloud-starter-zipkin/pom.xml | 2 +- 20 files changed, 26 insertions(+), 26 deletions(-) diff --git a/benchmarks/pom.xml b/benchmarks/pom.xml index 546f6a989..310d94d19 100644 --- a/benchmarks/pom.xml +++ b/benchmarks/pom.xml @@ -17,7 +17,7 @@ Benchmarks Benchmarks (JMH) org.springframework.cloud - 1.0.9.BUILD-SNAPSHOT + 1.0.9.RELEASE benchmarks diff --git a/docs/pom.xml b/docs/pom.xml index 08aa8fe9d..49d4f6e86 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.9.BUILD-SNAPSHOT + 1.0.9.RELEASE spring-cloud-sleuth-docs pom diff --git a/pom.xml b/pom.xml index 7b088e792..20e21b93a 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 spring-cloud-sleuth - 1.0.9.BUILD-SNAPSHOT + 1.0.9.RELEASE pom Spring Cloud Sleuth Spring Cloud Sleuth @@ -13,7 +13,7 @@ org.springframework.cloud spring-cloud-build - 1.1.3.BUILD-SNAPSHOT + 1.1.2.RELEASE @@ -229,10 +229,10 @@ 1.8 2.19.1 2.17 - 1.1.3.BUILD-SNAPSHOT - 1.1.3.BUILD-SNAPSHOT - Brooklyn.BUILD-SNAPSHOT - 1.1.6.BUILD-SNAPSHOT + 1.1.2.RELEASE + 1.1.3.RELEASE + 1.0.2.RELEASE + 1.1.6.RELEASE diff --git a/spring-cloud-sleuth-core/pom.xml b/spring-cloud-sleuth-core/pom.xml index 8c964bde4..0aa588a56 100644 --- a/spring-cloud-sleuth-core/pom.xml +++ b/spring-cloud-sleuth-core/pom.xml @@ -11,7 +11,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.9.BUILD-SNAPSHOT + 1.0.9.RELEASE .. diff --git a/spring-cloud-sleuth-dependencies/pom.xml b/spring-cloud-sleuth-dependencies/pom.xml index 63ecc4f89..93bf36515 100644 --- a/spring-cloud-sleuth-dependencies/pom.xml +++ b/spring-cloud-sleuth-dependencies/pom.xml @@ -5,11 +5,11 @@ spring-cloud-dependencies-parent org.springframework.cloud - 1.1.3.BUILD-SNAPSHOT + 1.1.2.RELEASE spring-cloud-sleuth-dependencies - 1.0.9.BUILD-SNAPSHOT + 1.0.9.RELEASE pom spring-cloud-sleuth-dependencies Spring Cloud Sleuth Dependencies diff --git a/spring-cloud-sleuth-samples/pom.xml b/spring-cloud-sleuth-samples/pom.xml index f0f558db9..2cfb3d430 100644 --- a/spring-cloud-sleuth-samples/pom.xml +++ b/spring-cloud-sleuth-samples/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.9.BUILD-SNAPSHOT + 1.0.9.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml index 72c751464..49a5643ba 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml @@ -20,7 +20,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.9.BUILD-SNAPSHOT + 1.0.9.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml index 22fca2edf..fe9a0df89 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.9.BUILD-SNAPSHOT + 1.0.9.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml index ec770366d..a133ed1e6 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.9.BUILD-SNAPSHOT + 1.0.9.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml index 2beda5aee..60c367a33 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.9.BUILD-SNAPSHOT + 1.0.9.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml index 6d3bc2beb..4663e7cc6 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.9.BUILD-SNAPSHOT + 1.0.9.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml index e583060ad..d419e274a 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.9.BUILD-SNAPSHOT + 1.0.9.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml index d5721d34c..aa800a02e 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.9.BUILD-SNAPSHOT + 1.0.9.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml index 227df1f16..dc3a61417 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml @@ -27,7 +27,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.9.BUILD-SNAPSHOT + 1.0.9.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml index 9bf911ca6..e9ae2ebff 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.9.BUILD-SNAPSHOT + 1.0.9.RELEASE .. diff --git a/spring-cloud-sleuth-stream/pom.xml b/spring-cloud-sleuth-stream/pom.xml index a00600007..bc9fe276e 100644 --- a/spring-cloud-sleuth-stream/pom.xml +++ b/spring-cloud-sleuth-stream/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.9.BUILD-SNAPSHOT + 1.0.9.RELEASE diff --git a/spring-cloud-sleuth-zipkin-stream/pom.xml b/spring-cloud-sleuth-zipkin-stream/pom.xml index 586148b9a..c6b055f6d 100644 --- a/spring-cloud-sleuth-zipkin-stream/pom.xml +++ b/spring-cloud-sleuth-zipkin-stream/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.9.BUILD-SNAPSHOT + 1.0.9.RELEASE diff --git a/spring-cloud-sleuth-zipkin/pom.xml b/spring-cloud-sleuth-zipkin/pom.xml index f7e7484a5..b43fcdb6d 100644 --- a/spring-cloud-sleuth-zipkin/pom.xml +++ b/spring-cloud-sleuth-zipkin/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.9.BUILD-SNAPSHOT + 1.0.9.RELEASE .. diff --git a/spring-cloud-starter-sleuth/pom.xml b/spring-cloud-starter-sleuth/pom.xml index b94c13a40..2bdc38c6c 100644 --- a/spring-cloud-starter-sleuth/pom.xml +++ b/spring-cloud-starter-sleuth/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.9.BUILD-SNAPSHOT + 1.0.9.RELEASE .. spring-cloud-starter-sleuth diff --git a/spring-cloud-starter-zipkin/pom.xml b/spring-cloud-starter-zipkin/pom.xml index 218e633f6..6e56e6aab 100644 --- a/spring-cloud-starter-zipkin/pom.xml +++ b/spring-cloud-starter-zipkin/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.9.BUILD-SNAPSHOT + 1.0.9.RELEASE .. spring-cloud-starter-zipkin From 72d003285968c8eb4792d743c11cd9c8f2a643b1 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Wed, 21 Sep 2016 13:12:18 +0200 Subject: [PATCH 087/235] Going back to snapshots --- benchmarks/pom.xml | 2 +- docs/pom.xml | 2 +- pom.xml | 12 ++++++------ spring-cloud-sleuth-core/pom.xml | 2 +- spring-cloud-sleuth-dependencies/pom.xml | 4 ++-- spring-cloud-sleuth-samples/pom.xml | 2 +- .../spring-cloud-sleuth-sample-feign/pom.xml | 2 +- .../spring-cloud-sleuth-sample-messaging/pom.xml | 2 +- .../spring-cloud-sleuth-sample-ribbon/pom.xml | 2 +- .../spring-cloud-sleuth-sample-stream/pom.xml | 2 +- .../spring-cloud-sleuth-sample-test-core/pom.xml | 2 +- .../spring-cloud-sleuth-sample-websocket/pom.xml | 2 +- .../spring-cloud-sleuth-sample-zipkin-stream/pom.xml | 2 +- .../spring-cloud-sleuth-sample-zipkin/pom.xml | 2 +- .../spring-cloud-sleuth-sample/pom.xml | 2 +- spring-cloud-sleuth-stream/pom.xml | 2 +- spring-cloud-sleuth-zipkin-stream/pom.xml | 2 +- spring-cloud-sleuth-zipkin/pom.xml | 2 +- spring-cloud-starter-sleuth/pom.xml | 2 +- spring-cloud-starter-zipkin/pom.xml | 2 +- 20 files changed, 26 insertions(+), 26 deletions(-) diff --git a/benchmarks/pom.xml b/benchmarks/pom.xml index 310d94d19..3c6bd6c2d 100644 --- a/benchmarks/pom.xml +++ b/benchmarks/pom.xml @@ -17,7 +17,7 @@ Benchmarks Benchmarks (JMH) org.springframework.cloud - 1.0.9.RELEASE + 1.0.10.BUILD-SNAPSHOT benchmarks diff --git a/docs/pom.xml b/docs/pom.xml index 49d4f6e86..f46f8eac7 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.9.RELEASE + 1.0.10.BUILD-SNAPSHOT spring-cloud-sleuth-docs pom diff --git a/pom.xml b/pom.xml index 20e21b93a..2c07235b6 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 spring-cloud-sleuth - 1.0.9.RELEASE + 1.0.10.BUILD-SNAPSHOT pom Spring Cloud Sleuth Spring Cloud Sleuth @@ -13,7 +13,7 @@ org.springframework.cloud spring-cloud-build - 1.1.2.RELEASE + 1.1.3.BUILD-SNAPSHOT @@ -229,10 +229,10 @@ 1.8 2.19.1 2.17 - 1.1.2.RELEASE - 1.1.3.RELEASE - 1.0.2.RELEASE - 1.1.6.RELEASE + 1.1.3.BUILD-SNAPSHOT + 1.1.3.BUILD-SNAPSHOT + Brooklyn.BUILD-SNAPSHOT + 1.1.6.BUILD-SNAPSHOT diff --git a/spring-cloud-sleuth-core/pom.xml b/spring-cloud-sleuth-core/pom.xml index 0aa588a56..e58f7e09b 100644 --- a/spring-cloud-sleuth-core/pom.xml +++ b/spring-cloud-sleuth-core/pom.xml @@ -11,7 +11,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.9.RELEASE + 1.0.10.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-dependencies/pom.xml b/spring-cloud-sleuth-dependencies/pom.xml index 93bf36515..a733317f2 100644 --- a/spring-cloud-sleuth-dependencies/pom.xml +++ b/spring-cloud-sleuth-dependencies/pom.xml @@ -5,11 +5,11 @@ spring-cloud-dependencies-parent org.springframework.cloud - 1.1.2.RELEASE + 1.1.3.BUILD-SNAPSHOT spring-cloud-sleuth-dependencies - 1.0.9.RELEASE + 1.0.10.BUILD-SNAPSHOT pom spring-cloud-sleuth-dependencies Spring Cloud Sleuth Dependencies diff --git a/spring-cloud-sleuth-samples/pom.xml b/spring-cloud-sleuth-samples/pom.xml index 2cfb3d430..1a2c1409d 100644 --- a/spring-cloud-sleuth-samples/pom.xml +++ b/spring-cloud-sleuth-samples/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.9.RELEASE + 1.0.10.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml index 49a5643ba..213148634 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml @@ -20,7 +20,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.9.RELEASE + 1.0.10.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml index fe9a0df89..3683d93be 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.9.RELEASE + 1.0.10.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml index a133ed1e6..21887c6c2 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.9.RELEASE + 1.0.10.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml index 60c367a33..d7b593c57 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.9.RELEASE + 1.0.10.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml index 4663e7cc6..4502c5635 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.9.RELEASE + 1.0.10.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml index d419e274a..b39ee5f9d 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.9.RELEASE + 1.0.10.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml index aa800a02e..2d08fe3a6 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.9.RELEASE + 1.0.10.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml index dc3a61417..be217be4f 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml @@ -27,7 +27,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.9.RELEASE + 1.0.10.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml index e9ae2ebff..b041f3406 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.9.RELEASE + 1.0.10.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-stream/pom.xml b/spring-cloud-sleuth-stream/pom.xml index bc9fe276e..6fe1dce35 100644 --- a/spring-cloud-sleuth-stream/pom.xml +++ b/spring-cloud-sleuth-stream/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.9.RELEASE + 1.0.10.BUILD-SNAPSHOT diff --git a/spring-cloud-sleuth-zipkin-stream/pom.xml b/spring-cloud-sleuth-zipkin-stream/pom.xml index c6b055f6d..4c1832b9c 100644 --- a/spring-cloud-sleuth-zipkin-stream/pom.xml +++ b/spring-cloud-sleuth-zipkin-stream/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.9.RELEASE + 1.0.10.BUILD-SNAPSHOT diff --git a/spring-cloud-sleuth-zipkin/pom.xml b/spring-cloud-sleuth-zipkin/pom.xml index b43fcdb6d..1a6f7676c 100644 --- a/spring-cloud-sleuth-zipkin/pom.xml +++ b/spring-cloud-sleuth-zipkin/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.9.RELEASE + 1.0.10.BUILD-SNAPSHOT .. diff --git a/spring-cloud-starter-sleuth/pom.xml b/spring-cloud-starter-sleuth/pom.xml index 2bdc38c6c..7a71b4433 100644 --- a/spring-cloud-starter-sleuth/pom.xml +++ b/spring-cloud-starter-sleuth/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.9.RELEASE + 1.0.10.BUILD-SNAPSHOT .. spring-cloud-starter-sleuth diff --git a/spring-cloud-starter-zipkin/pom.xml b/spring-cloud-starter-zipkin/pom.xml index 6e56e6aab..fb72cfe01 100644 --- a/spring-cloud-starter-zipkin/pom.xml +++ b/spring-cloud-starter-zipkin/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.9.RELEASE + 1.0.10.BUILD-SNAPSHOT .. spring-cloud-starter-zipkin From d0bedb22cc04360c5e0b3695556fec73d1bbbc4b Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Tue, 4 Oct 2016 13:42:22 +0200 Subject: [PATCH 088/235] Wrapping all Executors and ThreadPoolTaskExecutors without this change there was a gap in passing tracing info to executors. With this change the Executors are wrapped in LazyTraceExecutor and ThreadPoolTaskExecutors are wrapped in their tracing representation too fixes #410 --- .../cloud/sleuth/TraceCallable.java | 8 + .../cloud/sleuth/TraceRunnable.java | 8 + .../async/AsyncDefaultAutoConfiguration.java | 7 +- .../LazyTraceThreadPoolTaskExecutor.java | 118 +++++++++++++ .../async/SpanContinuingTraceCallable.java | 77 +++++++++ .../async/SpanContinuingTraceRunnable.java | 75 ++++++++ .../async/TraceExecutorBeanPostProcessor.java | 62 +++++++ .../async/issues/issue410/Issue410Tests.java | 162 ++++++++++++++++++ 8 files changed, 516 insertions(+), 1 deletion(-) create mode 100644 spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/LazyTraceThreadPoolTaskExecutor.java create mode 100644 spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/SpanContinuingTraceCallable.java create mode 100644 spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/SpanContinuingTraceRunnable.java create mode 100644 spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceExecutorBeanPostProcessor.java create mode 100644 spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue410/Issue410Tests.java diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/TraceCallable.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/TraceCallable.java index 25a9dceb7..44bc0e1e7 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/TraceCallable.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/TraceCallable.java @@ -73,6 +73,14 @@ public class TraceCallable implements Callable { this.tracer.close(span); } + protected Span continueSpan(Span span) { + return this.tracer.continueSpan(span); + } + + protected Span detachSpan(Span span) { + return this.tracer.detach(span); + } + public Tracer getTracer() { return this.tracer; } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/TraceRunnable.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/TraceRunnable.java index 7c0d01545..cbf838713 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/TraceRunnable.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/TraceRunnable.java @@ -77,6 +77,14 @@ public class TraceRunnable implements Runnable { this.tracer.close(span); } + protected Span continueSpan(Span span) { + return this.tracer.continueSpan(span); + } + + protected Span detachSpan(Span span) { + return this.tracer.detach(span); + } + public Tracer getTracer() { return this.tracer; } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/AsyncDefaultAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/AsyncDefaultAutoConfiguration.java index 25764982a..38b5d9b65 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/AsyncDefaultAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/AsyncDefaultAutoConfiguration.java @@ -24,8 +24,8 @@ import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.TraceKeys; +import org.springframework.cloud.sleuth.Tracer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.task.SimpleAsyncTaskExecutor; @@ -69,4 +69,9 @@ public class AsyncDefaultAutoConfiguration { return new TraceAsyncAspect(tracer, traceKeys); } + @Bean + public TraceExecutorBeanPostProcessor traceExecutorBeanPostProcessor(BeanFactory beanFactory) { + return new TraceExecutorBeanPostProcessor(beanFactory); + } + } \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/LazyTraceThreadPoolTaskExecutor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/LazyTraceThreadPoolTaskExecutor.java new file mode 100644 index 000000000..292a97702 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/LazyTraceThreadPoolTaskExecutor.java @@ -0,0 +1,118 @@ +/* + * 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.async; + +import java.lang.invoke.MethodHandles; +import java.util.concurrent.Callable; +import java.util.concurrent.Future; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.cloud.sleuth.DefaultSpanNamer; +import org.springframework.cloud.sleuth.SpanNamer; +import org.springframework.cloud.sleuth.TraceKeys; +import org.springframework.cloud.sleuth.Tracer; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.util.concurrent.ListenableFuture; + +/** + * {@link ThreadPoolTaskExecutor} that delegates execution to {@link LazyTraceExecutor} + * + * @author Marcin Grzejszczak + * @since 1.0.10 + */ +public class LazyTraceThreadPoolTaskExecutor extends ThreadPoolTaskExecutor { + + private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); + + private Tracer tracer; + private final BeanFactory beanFactory; + private final ThreadPoolTaskExecutor delegate; + private TraceKeys traceKeys; + private SpanNamer spanNamer; + + public LazyTraceThreadPoolTaskExecutor(BeanFactory beanFactory, + ThreadPoolTaskExecutor delegate) { + this.beanFactory = beanFactory; + this.delegate = delegate; + } + + @Override + public void execute(Runnable task) { + this.delegate.execute(new SpanContinuingTraceRunnable(tracer(), traceKeys(), spanNamer(), task)); + } + + @Override + public void execute(Runnable task, long startTimeout) { + this.delegate.execute(new SpanContinuingTraceRunnable(tracer(), traceKeys(), spanNamer(), task), startTimeout); + } + + @Override + public Future submit(Runnable task) { + return this.delegate.submit(new SpanContinuingTraceRunnable(tracer(), traceKeys(), spanNamer(), task)); + } + + @Override + public Future submit(Callable task) { + return this.delegate.submit(new SpanContinuingTraceCallable<>(tracer(), traceKeys(), spanNamer(), task)); + } + + @Override + public ListenableFuture submitListenable(Runnable task) { + return this.delegate.submitListenable(new SpanContinuingTraceRunnable(tracer(), traceKeys(), spanNamer(), task)); + } + + @Override + public ListenableFuture submitListenable(Callable task) { + return this.delegate.submitListenable(new SpanContinuingTraceCallable<>(tracer(), traceKeys(), spanNamer(), task)); + } + + private Tracer tracer() { + if (this.tracer == null) { + this.tracer = this.beanFactory.getBean(Tracer.class); + } + return this.tracer; + } + + private TraceKeys traceKeys() { + if (this.traceKeys == null) { + try { + this.traceKeys = this.beanFactory.getBean(TraceKeys.class); + } + catch (NoSuchBeanDefinitionException e) { + log.warn("TraceKeys bean not found - will provide a manually created instance"); + return new TraceKeys(); + } + } + return this.traceKeys; + } + + private SpanNamer spanNamer() { + if (this.spanNamer == null) { + try { + this.spanNamer = this.beanFactory.getBean(SpanNamer.class); + } + catch (NoSuchBeanDefinitionException e) { + log.warn("SpanNamer bean not found - will provide a manually created instance"); + return new DefaultSpanNamer(); + } + } + return this.spanNamer; + } +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/SpanContinuingTraceCallable.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/SpanContinuingTraceCallable.java new file mode 100644 index 000000000..1846a8f21 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/SpanContinuingTraceCallable.java @@ -0,0 +1,77 @@ +/* + * 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.async; + +import java.util.concurrent.Callable; + +import org.springframework.cloud.sleuth.Span; +import org.springframework.cloud.sleuth.SpanNamer; +import org.springframework.cloud.sleuth.TraceCallable; +import org.springframework.cloud.sleuth.TraceKeys; +import org.springframework.cloud.sleuth.Tracer; + +/** + * Runnable that continues a span if there is one and creates new that is a + * local component span if there was no tracing present. + * + * @author Marcin Grzejszczak + * @since 1.0.10 + */ +class SpanContinuingTraceCallable extends TraceCallable { + + private final LocalComponentTraceCallable traceCallable; + + public SpanContinuingTraceCallable(Tracer tracer, TraceKeys traceKeys, + SpanNamer spanNamer, Callable delegate) { + super(tracer, spanNamer, delegate); + this.traceCallable = new LocalComponentTraceCallable<>(tracer, traceKeys, spanNamer, delegate); + } + + public SpanContinuingTraceCallable(Tracer tracer, TraceKeys traceKeys, + SpanNamer spanNamer, Callable delegate, String name) { + super(tracer, spanNamer, delegate, name); + this.traceCallable = new LocalComponentTraceCallable<>(tracer, traceKeys, spanNamer, name, delegate); + } + + @Override + public V call() throws Exception { + Span span = startSpan(); + try { + return this.getDelegate().call(); + } + finally { + close(span); + } + } + + @Override + protected Span startSpan() { + Span span = this.getParent(); + if (span == null) { + return this.traceCallable.startSpan(); + } + return continueSpan(span); + } + + @Override protected void close(Span span) { + if (this.getParent() == null) { + super.close(span); + } else { + super.detachSpan(span); + } + } +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/SpanContinuingTraceRunnable.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/SpanContinuingTraceRunnable.java new file mode 100644 index 000000000..fd2401d91 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/SpanContinuingTraceRunnable.java @@ -0,0 +1,75 @@ +/* + * 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.async; + +import org.springframework.cloud.sleuth.Span; +import org.springframework.cloud.sleuth.SpanNamer; +import org.springframework.cloud.sleuth.TraceKeys; +import org.springframework.cloud.sleuth.TraceRunnable; +import org.springframework.cloud.sleuth.Tracer; + +/** + * Runnable that continues a span if there is one and creates new that is a + * local component span if there was no tracing present. + * + * @author Marcin Grzejszczak + * @since 1.0.10 + */ +class SpanContinuingTraceRunnable extends TraceRunnable { + + private final LocalComponentTraceRunnable traceRunnable; + + public SpanContinuingTraceRunnable(Tracer tracer, TraceKeys traceKeys, + SpanNamer spanNamer, Runnable delegate) { + super(tracer, spanNamer, delegate); + this.traceRunnable = new LocalComponentTraceRunnable(tracer, traceKeys, spanNamer, delegate); + } + + public SpanContinuingTraceRunnable(Tracer tracer, TraceKeys traceKeys, + SpanNamer spanNamer, Runnable delegate, String name) { + super(tracer, spanNamer, delegate, name); + this.traceRunnable = new LocalComponentTraceRunnable(tracer, traceKeys, spanNamer, delegate, name); + } + + @Override + public void run() { + Span span = startSpan(); + try { + this.getDelegate().run(); + } + finally { + close(span); + } + } + + @Override + protected Span startSpan() { + Span span = this.getParent(); + if (span == null) { + return this.traceRunnable.startSpan(); + } + return continueSpan(span); + } + + @Override protected void close(Span span) { + if (this.getParent() == null) { + super.close(span); + } else { + super.detachSpan(span); + } + } +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceExecutorBeanPostProcessor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceExecutorBeanPostProcessor.java new file mode 100644 index 000000000..060bf34e8 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceExecutorBeanPostProcessor.java @@ -0,0 +1,62 @@ +/* + * Copyright 2013-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.sleuth.instrument.async; + +import java.lang.invoke.MethodHandles; +import java.util.concurrent.Executor; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.scheduling.TaskScheduler; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +/** + * Bean post processor that wraps {@link Executor} in its Trace representation + * + * @author Marcin Grzejszczak + * @since 1.0.10 + */ +class TraceExecutorBeanPostProcessor implements BeanPostProcessor { + + private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); + + private final BeanFactory beanFactory; + + public TraceExecutorBeanPostProcessor(BeanFactory beanFactory) { + this.beanFactory = beanFactory; + } + + @Override public Object postProcessBeforeInitialization(Object bean, String beanName) + throws BeansException { + if (bean instanceof ThreadPoolTaskExecutor && !(bean instanceof TaskScheduler) && + !(bean instanceof LazyTraceThreadPoolTaskExecutor)) { + return new LazyTraceThreadPoolTaskExecutor(this.beanFactory, (ThreadPoolTaskExecutor) bean); + } + if (bean instanceof Executor && !(bean instanceof TaskScheduler) && !(bean instanceof LazyTraceExecutor)) { + return new LazyTraceExecutor(this.beanFactory, (Executor) bean); + } + return bean; + } + + @Override public Object postProcessAfterInitialization(Object bean, String beanName) + throws BeansException { + return bean; + } +} diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue410/Issue410Tests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue410/Issue410Tests.java new file mode 100644 index 000000000..48926859a --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue410/Issue410Tests.java @@ -0,0 +1,162 @@ +/* + * 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.async.issues.issue410; + +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicReference; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.boot.test.WebIntegrationTest; +import org.springframework.cloud.sleuth.Sampler; +import org.springframework.cloud.sleuth.Span; +import org.springframework.cloud.sleuth.Tracer; +import org.springframework.cloud.sleuth.sampler.AlwaysSampler; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; +import org.springframework.scheduling.annotation.Async; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.stereotype.Component; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.client.RestTemplate; + +import static org.assertj.core.api.BDDAssertions.then; + +/** + * @author Marcin Grzejszczak + */ +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(classes = Application.class) +@WebIntegrationTest +@TestPropertySource(properties = {"ribbon.eureka.enabled=false", "feign.hystrix.enabled=false", "server.port=0"}) +public class Issue410Tests { + + @Autowired Environment environment; + @Autowired Tracer tracer; + @Autowired AsyncTask asyncTask; + @Autowired RestTemplate restTemplate; + + @Test + public void should_pass_tracing_info_for_tasks_running_without_a_pool() { + Span span = this.tracer.createSpan("foo"); + + String response = this.restTemplate.getForObject("http://localhost:" + port() + "/without_pool", String.class); + + then(response).isEqualTo(Span.idToHex(span.getTraceId())); + then(this.asyncTask.getSpan().get()).isNotNull(); + then(this.asyncTask.getSpan().get().getTraceId()).isEqualTo(span.getTraceId()); + } + + @Test + public void should_pass_tracing_info_for_tasks_running_with_a_pool() { + Span span = this.tracer.createSpan("foo"); + + String response = this.restTemplate.getForObject("http://localhost:" + port() + "/with_pool", String.class); + + then(response).isEqualTo(Span.idToHex(span.getTraceId())); + then(this.asyncTask.getSpan().get()).isNotNull(); + then(this.asyncTask.getSpan().get().getTraceId()).isEqualTo(span.getTraceId()); + } + + private int port() { + return this.environment.getProperty("local.server.port", Integer.class); + } +} + + +@Configuration +@EnableAsync +class AppConfig { + + @Bean Sampler testSampler() { + return new AlwaysSampler(); + } + + @Bean RestTemplate restTemplate() { + return new RestTemplate(); + } + + @Bean public Executor poolTaskExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.initialize(); + return executor; + } + +} + +@Component +class AsyncTask { + + private static final Log log = LogFactory.getLog(AsyncTask.class); + + private AtomicReference span = new AtomicReference<>(); + + @Autowired Tracer tracer; + + @Async("poolTaskExecutor") + public void runWithPool() { + log.info("This task is running with a pool."); + this.span.set(this.tracer.getCurrentSpan()); + } + + @Async + public void runWithoutPool() { + log.info("This task is running without a pool."); + this.span.set(this.tracer.getCurrentSpan()); + } + + public AtomicReference getSpan() { + return span; + } +} + +@SpringBootApplication(exclude = SpringDataWebAutoConfiguration.class) +@RestController +class Application { + + private static final Log log = LogFactory.getLog(Application.class); + + @Autowired AsyncTask asyncTask; + @Autowired Tracer tracer; + + @RequestMapping("/with_pool") + public String withPool() { + log.info("Executing with pool."); + this.asyncTask.runWithPool(); + return Span.idToHex(this.tracer.getCurrentSpan().getTraceId()); + + } + + @RequestMapping("/without_pool") + public String withoutPool() { + log.info("Executing without pool."); + this.asyncTask.runWithoutPool(); + return Span.idToHex(this.tracer.getCurrentSpan().getTraceId()); + } + +} From 842f8a1bb4aeff4a133c8af775029ad82eb059a1 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Tue, 4 Oct 2016 13:51:44 +0200 Subject: [PATCH 089/235] Fixed javadocs --- .../instrument/async/LazyTraceThreadPoolTaskExecutor.java | 2 +- .../instrument/async/TraceExecutorBeanPostProcessor.java | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/LazyTraceThreadPoolTaskExecutor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/LazyTraceThreadPoolTaskExecutor.java index 292a97702..721b839e9 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/LazyTraceThreadPoolTaskExecutor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/LazyTraceThreadPoolTaskExecutor.java @@ -32,7 +32,7 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.util.concurrent.ListenableFuture; /** - * {@link ThreadPoolTaskExecutor} that delegates execution to {@link LazyTraceExecutor} + * {@link ThreadPoolTaskExecutor} that continues a span if one was passed or creates a new one * * @author Marcin Grzejszczak * @since 1.0.10 diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceExecutorBeanPostProcessor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceExecutorBeanPostProcessor.java index 060bf34e8..66f97a16b 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceExecutorBeanPostProcessor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceExecutorBeanPostProcessor.java @@ -16,11 +16,8 @@ package org.springframework.cloud.sleuth.instrument.async; -import java.lang.invoke.MethodHandles; import java.util.concurrent.Executor; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.config.BeanPostProcessor; @@ -35,8 +32,6 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; */ class TraceExecutorBeanPostProcessor implements BeanPostProcessor { - private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); - private final BeanFactory beanFactory; public TraceExecutorBeanPostProcessor(BeanFactory beanFactory) { From d1cb4b2990d5b06531be57c7c697a1d58e9d05b0 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Tue, 4 Oct 2016 13:53:33 +0200 Subject: [PATCH 090/235] Returning a copy of spans from ArrayListSpanAccumulator --- .../cloud/sleuth/util/ArrayListSpanAccumulator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/util/ArrayListSpanAccumulator.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/util/ArrayListSpanAccumulator.java index 2a412d167..774313124 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/util/ArrayListSpanAccumulator.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/util/ArrayListSpanAccumulator.java @@ -34,7 +34,7 @@ public class ArrayListSpanAccumulator implements SpanReporter { public List getSpans() { synchronized (this.spans) { - return this.spans; + return new ArrayList<>(this.spans); } } From 7289212868a4462f63a4314b29aea9354584b4b6 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Tue, 4 Oct 2016 14:02:00 +0200 Subject: [PATCH 091/235] Fixing ConcurrentModificationException --- .../cloud/sleuth/util/ArrayListSpanAccumulator.java | 2 +- .../instrument/web/client/integration/WebClientTests.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/util/ArrayListSpanAccumulator.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/util/ArrayListSpanAccumulator.java index 774313124..2a412d167 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/util/ArrayListSpanAccumulator.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/util/ArrayListSpanAccumulator.java @@ -34,7 +34,7 @@ public class ArrayListSpanAccumulator implements SpanReporter { public List getSpans() { synchronized (this.spans) { - return new ArrayList<>(this.spans); + return this.spans; } } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/integration/WebClientTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/integration/WebClientTests.java index 347db6be4..14c7c8ba5 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/integration/WebClientTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/integration/WebClientTests.java @@ -113,7 +113,7 @@ public class WebClientTests { then(getHeader(response, Span.TRACE_ID_NAME)).isNull(); then(getHeader(response, Span.SPAN_ID_NAME)).isNull(); then(this.listener.getSpans()).isNotEmpty(); - Optional noTraceSpan = this.listener.getSpans().stream().filter(span -> + Optional noTraceSpan = new ArrayList<>(this.listener.getSpans()).stream().filter(span -> "http:/notrace".equals(span.getName()) && !span.tags().isEmpty()).findFirst(); then(noTraceSpan.isPresent()).isTrue(); // TODO: matches cause there is an issue with Feign not providing the full URL at the interceptor level From 8bd8e3db1fc9a1994bfbcf5a6e4611f7f5a558d4 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Tue, 4 Oct 2016 14:30:56 +0200 Subject: [PATCH 092/235] Fixed race condition in tests --- .../async/issues/issue410/Issue410Tests.java | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue410/Issue410Tests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue410/Issue410Tests.java index 48926859a..982e11b00 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue410/Issue410Tests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue410/Issue410Tests.java @@ -45,6 +45,8 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; +import com.jayway.awaitility.Awaitility; + import static org.assertj.core.api.BDDAssertions.then; /** @@ -68,8 +70,10 @@ public class Issue410Tests { String response = this.restTemplate.getForObject("http://localhost:" + port() + "/without_pool", String.class); then(response).isEqualTo(Span.idToHex(span.getTraceId())); - then(this.asyncTask.getSpan().get()).isNotNull(); - then(this.asyncTask.getSpan().get().getTraceId()).isEqualTo(span.getTraceId()); + Awaitility.await().until(() -> { + then(this.asyncTask.getSpan().get()).isNotNull(); + then(this.asyncTask.getSpan().get().getTraceId()).isEqualTo(span.getTraceId()); + }); } @Test @@ -79,8 +83,10 @@ public class Issue410Tests { String response = this.restTemplate.getForObject("http://localhost:" + port() + "/with_pool", String.class); then(response).isEqualTo(Span.idToHex(span.getTraceId())); - then(this.asyncTask.getSpan().get()).isNotNull(); - then(this.asyncTask.getSpan().get().getTraceId()).isEqualTo(span.getTraceId()); + Awaitility.await().until(() -> { + then(this.asyncTask.getSpan().get()).isNotNull(); + then(this.asyncTask.getSpan().get().getTraceId()).isEqualTo(span.getTraceId()); + }); } private int port() { From 5080ee3e24f8d92f9d557b7a268c27b8412d6235 Mon Sep 17 00:00:00 2001 From: Adrian Cole Date: Thu, 6 Oct 2016 19:05:20 +0800 Subject: [PATCH 093/235] Updates to latest Zipkin and avoids Endpoint factory method (#417) Now that Endpoint has ipv6, the factory method is even worse than it was before. This switches to a builder instead. Note: Endpoint now has a nice toString which should help debugging. --- spring-cloud-sleuth-dependencies/pom.xml | 4 ++-- spring-cloud-sleuth-samples/pom.xml | 4 ++-- .../sleuth/zipkin/stream/ConvertToZipkinSpanList.java | 11 +++++------ .../zipkin/stream/ZipkinMessageListenerTests.java | 6 ++++-- .../sleuth/zipkin/DiscoveryClientEndpointLocator.java | 6 ++++-- .../zipkin/ServerPropertiesEndpointLocator.java | 8 +++++--- .../zipkin/FallbackHavingEndpointLocatorTests.java | 3 ++- 7 files changed, 24 insertions(+), 18 deletions(-) diff --git a/spring-cloud-sleuth-dependencies/pom.xml b/spring-cloud-sleuth-dependencies/pom.xml index a733317f2..ae173aca4 100644 --- a/spring-cloud-sleuth-dependencies/pom.xml +++ b/spring-cloud-sleuth-dependencies/pom.xml @@ -14,8 +14,8 @@ spring-cloud-sleuth-dependencies Spring Cloud Sleuth Dependencies - 1.11.1 - 0.5.0 + 1.13.1 + 0.6.1 diff --git a/spring-cloud-sleuth-samples/pom.xml b/spring-cloud-sleuth-samples/pom.xml index 1a2c1409d..afe391ff7 100644 --- a/spring-cloud-sleuth-samples/pom.xml +++ b/spring-cloud-sleuth-samples/pom.xml @@ -59,12 +59,12 @@ io.zipkin.java zipkin - 1.11.1 + 1.13.1 io.zipkin.java zipkin-server - 1.11.1 + 1.13.1 diff --git a/spring-cloud-sleuth-zipkin-stream/src/main/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanList.java b/spring-cloud-sleuth-zipkin-stream/src/main/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanList.java index c94420b43..16fc039d8 100644 --- a/spring-cloud-sleuth-zipkin-stream/src/main/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanList.java +++ b/spring-cloud-sleuth-zipkin-stream/src/main/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanList.java @@ -18,14 +18,12 @@ package org.springframework.cloud.sleuth.zipkin.stream; import java.util.ArrayList; import java.util.Arrays; import java.util.List; - import org.apache.commons.logging.Log; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.stream.Host; import org.springframework.cloud.sleuth.stream.SleuthSink; import org.springframework.cloud.sleuth.stream.Spans; import org.springframework.util.StringUtils; - import zipkin.BinaryAnnotation; import zipkin.Constants; import zipkin.Endpoint; @@ -76,8 +74,10 @@ final class ConvertToZipkinSpanList { static zipkin.Span convert(Span span, Host host) { Builder zipkinSpan = zipkin.Span.builder(); - Endpoint ep = Endpoint.create(host.getServiceName(), host.getIpv4(), - host.getPort().shortValue()); + Endpoint ep = Endpoint.builder() + .serviceName(host.getServiceName()) + .ipv4(host.getIpv4()) + .port(host.getPort() != null ? host.getPort() : 0).build(); // A zipkin span without any annotations cannot be queried, add special "lc" to // avoid that. @@ -125,8 +125,7 @@ final class ConvertToZipkinSpanList { Endpoint ep) { String serviceName = span.tags().containsKey(Span.SPAN_PEER_SERVICE_TAG_NAME) ? span.tags().get(Span.SPAN_PEER_SERVICE_TAG_NAME) : ep.serviceName; - Endpoint endpoint = ep.port == null ? Endpoint.create(serviceName, ep.ipv4) - : Endpoint.create(serviceName, ep.ipv4, ep.port); + Endpoint endpoint = ep.toBuilder().serviceName(serviceName).build(); zipkinSpan.addBinaryAnnotation( BinaryAnnotation.address(Constants.SERVER_ADDR, endpoint)); } diff --git a/spring-cloud-sleuth-zipkin-stream/src/test/java/org/springframework/cloud/sleuth/zipkin/stream/ZipkinMessageListenerTests.java b/spring-cloud-sleuth-zipkin-stream/src/test/java/org/springframework/cloud/sleuth/zipkin/stream/ZipkinMessageListenerTests.java index 2047024bf..cab8523df 100644 --- a/spring-cloud-sleuth-zipkin-stream/src/test/java/org/springframework/cloud/sleuth/zipkin/stream/ZipkinMessageListenerTests.java +++ b/spring-cloud-sleuth-zipkin-stream/src/test/java/org/springframework/cloud/sleuth/zipkin/stream/ZipkinMessageListenerTests.java @@ -17,7 +17,6 @@ package org.springframework.cloud.sleuth.zipkin.stream; import java.util.Collections; - import org.junit.Test; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.stream.Host; @@ -30,7 +29,10 @@ public class ZipkinMessageListenerTests { Span span = new Span(1, 3, "http:name", 1L, Collections.emptyList(), 2L, true, true, "process"); Host host = new Host("myservice", "1.2.3.4", 8080); - Endpoint endpoint = Endpoint.create("myservice", 1 << 24 | 2 << 16 | 3 << 8 | 4, 8080); + Endpoint endpoint = Endpoint.builder() + .serviceName("myservice") + .ipv4(1 << 24 | 2 << 16 | 3 << 8 | 4) + .port(8080).build(); /** Sleuth timestamps are millisecond granularity while zipkin is microsecond. */ @Test diff --git a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/DiscoveryClientEndpointLocator.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/DiscoveryClientEndpointLocator.java index 329bb7c58..41de557a8 100644 --- a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/DiscoveryClientEndpointLocator.java +++ b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/DiscoveryClientEndpointLocator.java @@ -19,7 +19,6 @@ package org.springframework.cloud.sleuth.zipkin; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.cloud.commons.util.InetUtils; - import zipkin.Endpoint; /** @@ -43,7 +42,10 @@ public class DiscoveryClientEndpointLocator implements EndpointLocator { if (instance == null) { throw new NoServiceInstanceAvailableException(); } - return Endpoint.create(instance.getServiceId(), getIpAddress(instance), instance.getPort()); + return Endpoint.builder() + .serviceName(instance.getServiceId()) + .ipv4(getIpAddress(instance)) + .port(instance.getPort()).build(); } private int getIpAddress(ServiceInstance instance) { diff --git a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ServerPropertiesEndpointLocator.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ServerPropertiesEndpointLocator.java index f626066f4..472dc70de 100644 --- a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ServerPropertiesEndpointLocator.java +++ b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ServerPropertiesEndpointLocator.java @@ -48,9 +48,11 @@ public class ServerPropertiesEndpointLocator implements EndpointLocator { @Override public Endpoint local() { - int address = getAddress(); - Integer port = getPort(); - return Endpoint.create(this.appName, address, port); + return Endpoint.builder() + .serviceName(this.appName) + .ipv4(getAddress()) + .port(getPort()) + .build(); } @EventListener(EmbeddedServletContainerInitializedEvent.class) diff --git a/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/FallbackHavingEndpointLocatorTests.java b/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/FallbackHavingEndpointLocatorTests.java index dc2a64407..3d4dd9ae5 100644 --- a/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/FallbackHavingEndpointLocatorTests.java +++ b/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/FallbackHavingEndpointLocatorTests.java @@ -15,7 +15,8 @@ public class FallbackHavingEndpointLocatorTests { @Mock DiscoveryClientEndpointLocator discoveryClientEndpointLocator; @Mock ServerPropertiesEndpointLocator serverPropertiesEndpointLocator; - Endpoint expectedEndpoint = Endpoint.create("my-tomcat", 127 << 24 | 1, 8080); + Endpoint expectedEndpoint = Endpoint.builder() + .serviceName("my-tomcat").ipv4(127 << 24 | 1).port(8080).build(); @Test public void should_use_system_property_locator_if_discovery_client_locator_is_not_present() { From 1d1f28d971fe81f651db956820e7e2f78f965ded Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Mon, 10 Oct 2016 13:27:53 +0100 Subject: [PATCH 094/235] Ensure message headers are still mutable after interceptor is called Some components in Spring assume (perhaps wrongly) that a channel interceptor will not convert a mutable message into an immutable one, and they continue to modify the headers dowstream. We can dosge the issue of whether this is right or wrong by keeping the message headers mutable, just in case. It would probably be better to refactor the SpanInjector so that it works with something other than the MessageBuilder, but we can defer doing that in favour of this smaller change that works. Adds a test for mutability. Also tested with the gs-messaging-stomp-websocket guide from spring.io. Fixes gh-276 --- .../instrument/messaging/TraceChannelInterceptor.java | 6 +++++- .../messaging/TraceChannelInterceptorTests.java | 10 ++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) 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 98358be53..a290b5a27 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 @@ -26,7 +26,9 @@ import org.springframework.cloud.sleuth.sampler.NeverSampler; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.MessageHandler; +import org.springframework.messaging.support.GenericMessage; import org.springframework.messaging.support.MessageBuilder; +import org.springframework.messaging.support.MessageHeaderAccessor; /** * A channel interceptor that automatically starts / continues / closes and detaches @@ -84,7 +86,9 @@ public class TraceChannelInterceptor extends AbstractTraceChannelInterceptor { messageBuilder.setHeader(TraceMessageHeaders.MESSAGE_SENT_FROM_CLIENT, true); } getSpanInjector().inject(span, messageBuilder); - return messageBuilder.build(); + MessageHeaderAccessor headers = MessageHeaderAccessor.getMutableAccessor(message); + headers.copyHeaders(messageBuilder.build().getHeaders()); + return new GenericMessage(message.getPayload(), headers.getMessageHeaders()); } private Span startSpan(Span span, String name, Message message) { 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 2ab5c63af..567f90e39 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 @@ -44,6 +44,7 @@ import org.springframework.integration.support.MessageBuilder; import org.springframework.messaging.Message; import org.springframework.messaging.MessageHandler; import org.springframework.messaging.MessagingException; +import org.springframework.messaging.support.MessageHeaderAccessor; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @@ -117,6 +118,15 @@ public class TraceChannelInterceptorTests implements MessageHandler { then(this.span.isExportable()).isFalse(); } + @Test + public void messageHeadersStillMutable() { + this.tracedChannel.send(MessageBuilder.withPayload("hi") + .setHeader(Span.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); + } + @Test public void parentSpanIncluded() { this.tracedChannel.send(MessageBuilder.withPayload("hi") From d092d62397052c2a6a607defaa6e60c360833168 Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Mon, 10 Oct 2016 13:32:03 +0100 Subject: [PATCH 095/235] Ensure lifecycle callbacks are applied to delegate When wrapping a ThreadPoolTaskExecutor in a bean postprocessor we should take care that the delegate gets the lifecycle callbacks from the container. Otherwise when it is first used, the thread pool will not have been initialized, resulting in an exception. (Can't believe this ever actually worked) --- .../async/LazyTraceThreadPoolTaskExecutor.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/LazyTraceThreadPoolTaskExecutor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/LazyTraceThreadPoolTaskExecutor.java index 721b839e9..b09925bab 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/LazyTraceThreadPoolTaskExecutor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/LazyTraceThreadPoolTaskExecutor.java @@ -37,6 +37,7 @@ import org.springframework.util.concurrent.ListenableFuture; * @author Marcin Grzejszczak * @since 1.0.10 */ +@SuppressWarnings("serial") public class LazyTraceThreadPoolTaskExecutor extends ThreadPoolTaskExecutor { private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); @@ -83,6 +84,17 @@ public class LazyTraceThreadPoolTaskExecutor extends ThreadPoolTaskExecutor { return this.delegate.submitListenable(new SpanContinuingTraceCallable<>(tracer(), traceKeys(), spanNamer(), task)); } + public void destroy() { + this.delegate.destroy(); + super.destroy(); + } + + @Override + public void afterPropertiesSet() { + this.delegate.afterPropertiesSet(); + super.afterPropertiesSet(); + } + private Tracer tracer() { if (this.tracer == null) { this.tracer = this.beanFactory.getBean(Tracer.class); From b8db95bc78c4ddfd8419acb3eb48c9d3070e725f Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Tue, 11 Oct 2016 15:15:11 +0200 Subject: [PATCH 096/235] Not throwing an exception when queue size is exceeded without this change when queue size of spans is exceeded for Stream span propagation, an exception is thrown that terminates business logic processing with this change we're not propagating the exception - we're incrementing the dropped spans counter fixes #421 --- .../sleuth/stream/StreamSpanReporter.java | 9 +++- .../stream/StreamSpanReporterTests.java | 52 +++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/StreamSpanReporterTests.java diff --git a/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/StreamSpanReporter.java b/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/StreamSpanReporter.java index 935b789be..3ddcbeade 100644 --- a/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/StreamSpanReporter.java +++ b/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/StreamSpanReporter.java @@ -83,7 +83,14 @@ public class StreamSpanReporter implements SpanReporter { @Override public void report(Span span) { if (span.isExportable()) { - this.queue.add(span); + try { + this.queue.add(span); + } catch (Exception e) { + this.spanMetricReporter.incrementDroppedSpans(1); + if (log.isDebugEnabled()) { + log.debug("The span " + span + " will not be sent to Zipkin due to [" + e + "]"); + } + } } else { if (log.isDebugEnabled()) { log.debug("The span " + span + " will not be sent to Zipkin due to sampling"); diff --git a/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/StreamSpanReporterTests.java b/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/StreamSpanReporterTests.java new file mode 100644 index 000000000..49c5402bf --- /dev/null +++ b/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/StreamSpanReporterTests.java @@ -0,0 +1,52 @@ +/* + * 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.stream; + +import java.util.concurrent.ArrayBlockingQueue; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.cloud.sleuth.Span; +import org.springframework.cloud.sleuth.metric.SpanMetricReporter; + +import static org.mockito.BDDMockito.then; + +/** + * @author Marcin Grzejszczak + */ +@RunWith(MockitoJUnitRunner.class) +public class StreamSpanReporterTests { + + @Mock HostLocator endpointLocator; + @Mock SpanMetricReporter spanMetricReporter; + @InjectMocks StreamSpanReporter reporter; + + @Test + public void should_not_throw_an_exception_when_queue_size_is_exceeded() throws Exception { + ArrayBlockingQueue queue = new ArrayBlockingQueue<>(1); + queue.add(Span.builder().name("foo").build()); + this.reporter.setQueue(queue); + + this.reporter.report(Span.builder().name("bar").exportable(true).build()); + + then(spanMetricReporter).should().incrementDroppedSpans(1); + } + +} \ No newline at end of file From 9240fdbd9caef6de0a02aae85e34bb51250a9ad9 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Tue, 18 Oct 2016 14:36:41 +0200 Subject: [PATCH 097/235] Failure in extracting headers results in new span without this change if the users sends invalid headers then exceptions are thrown. with this change extractors catch the exception, log it and then a new span is created. That of course will lead to an invalid trace graph cause a new trace will be created but at least business apps will not be broken due to an issue in instrumentation. fixes #425 --- .../AbstractTraceChannelInterceptor.java | 13 +++++++++- .../web/HttpServletRequestExtractor.java | 19 +++++++++----- .../TraceChannelInterceptorTests.java | 14 +++++++++- .../web/HttpServletRequestExtractorTests.java | 24 +++-------------- .../instrument/web/TraceFilterTests.java | 26 ++++++++++++++++--- 5 files changed, 64 insertions(+), 32 deletions(-) diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/AbstractTraceChannelInterceptor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/AbstractTraceChannelInterceptor.java index 6f594ea4b..df22ff6e8 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/AbstractTraceChannelInterceptor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/AbstractTraceChannelInterceptor.java @@ -1,5 +1,9 @@ package org.springframework.cloud.sleuth.instrument.messaging; +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; @@ -22,6 +26,8 @@ import org.springframework.util.ClassUtils; abstract class AbstractTraceChannelInterceptor extends ChannelInterceptorAdapter implements ExecutorChannelInterceptor { + private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); + /** * If a span comes from messaging components then it will have this value as a prefix * to its name. @@ -63,7 +69,12 @@ abstract class AbstractTraceChannelInterceptor extends ChannelInterceptorAdapter * missing. */ protected Span buildSpan(Message message) { - return this.spanExtractor.joinTrace(message); + try { + return this.spanExtractor.joinTrace(message); + } catch (Exception e) { + log.error("Exception occurred while trying to extract span from carrier", e); + return null; + } } String getChannelName(MessageChannel channel) { diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/HttpServletRequestExtractor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/HttpServletRequestExtractor.java index 8081b1104..2a682bfe6 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/HttpServletRequestExtractor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/HttpServletRequestExtractor.java @@ -54,13 +54,18 @@ class HttpServletRequestExtractor implements SpanExtractor { // can't build a Span without trace id return null; } - 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); + 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) { 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 567f90e39..c5a570de8 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 @@ -281,7 +281,7 @@ public class TraceChannelInterceptorTests implements MessageHandler { String traceId = this.message.getHeaders().get(Span.TRACE_ID_NAME, String.class); then(traceId).isNull(); - then(accumulator.getSpans()).isEmpty(); + then(this.accumulator.getSpans()).isEmpty(); then(TestSpanContextHolder.getCurrentSpan()).isNull(); } @@ -299,6 +299,18 @@ public class TraceChannelInterceptorTests implements MessageHandler { then(traceId).isEqualTo(Span.hexToId(lower64Bits)); } + @Test + public void shouldNotBreakWhenInvalidHeadersAreSent() { + this.tracedChannel.send(MessageBuilder.withPayload("hi") + .setHeader(TraceMessageHeaders.PARENT_ID_NAME, "-") + .setHeader(TraceMessageHeaders.TRACE_ID_NAME, Span.idToHex(10L)) + .setHeader(TraceMessageHeaders.SPAN_ID_NAME, Span.idToHex(20L)).build()); + + then(this.message).isNotNull(); + then(this.accumulator.getSpans()).isNotEmpty(); + then(TestSpanContextHolder.getCurrentSpan()).isNull(); + } + @Configuration @EnableAutoConfiguration static class App { diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/HttpServletRequestExtractorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/HttpServletRequestExtractorTests.java index fe1afc5df..6cc85427f 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/HttpServletRequestExtractorTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/HttpServletRequestExtractorTests.java @@ -16,9 +16,9 @@ package org.springframework.cloud.sleuth.instrument.web; -import javax.servlet.http.HttpServletRequest; import java.util.Random; import java.util.regex.Pattern; +import javax.servlet.http.HttpServletRequest; import org.junit.Before; import org.junit.Test; @@ -28,7 +28,6 @@ import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; import org.springframework.cloud.sleuth.Span; -import static org.assertj.core.api.Assertions.fail; import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; @RunWith(MockitoJUnitRunner.class) @@ -54,12 +53,7 @@ public class HttpServletRequestExtractorTests { BDDMockito.given(this.request.getHeader(Span.TRACE_ID_NAME)) .willReturn("invalid"); - try { - this.extractor.joinTrace(this.request); - fail("should throw an exception"); - } catch (IllegalArgumentException e) { - then(e).hasMessageContaining("Malformed id"); - } + then(this.extractor.joinTrace(this.request)).isNull(); } @Test @@ -69,12 +63,7 @@ public class HttpServletRequestExtractorTests { BDDMockito.given(this.request.getHeader(Span.SPAN_ID_NAME)) .willReturn("invalid"); - try { - this.extractor.joinTrace(this.request); - fail("should throw an exception"); - } catch (IllegalArgumentException e) { - then(e).hasMessageContaining("Malformed id"); - } + then(this.extractor.joinTrace(this.request)).isNull(); } @Test @@ -86,12 +75,7 @@ public class HttpServletRequestExtractorTests { BDDMockito.given(this.request.getHeader(Span.PARENT_ID_NAME)) .willReturn("invalid"); - try { - this.extractor.joinTrace(this.request); - fail("should throw an exception"); - } catch (IllegalArgumentException e) { - then(e).hasMessageContaining("Malformed id"); - } + then(this.extractor.joinTrace(this.request)).isNull(); } @Test 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 4787beef3..a06f90198 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 @@ -16,10 +16,11 @@ package org.springframework.cloud.sleuth.instrument.web; -import javax.servlet.http.HttpServletRequest; +import java.util.ArrayList; import java.util.Optional; import java.util.Random; import java.util.regex.Pattern; +import javax.servlet.http.HttpServletRequest; import org.junit.After; import org.junit.Before; @@ -38,6 +39,7 @@ import org.springframework.cloud.sleuth.sampler.NeverSampler; import org.springframework.cloud.sleuth.trace.DefaultTracer; import org.springframework.cloud.sleuth.trace.TestSpanContextHolder; import org.springframework.cloud.sleuth.util.ArrayListSpanAccumulator; +import org.springframework.cloud.sleuth.util.ExceptionUtils; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.mock.web.MockFilterChain; @@ -323,7 +325,7 @@ public class TraceFilterTests { } @Test - public void returns400IfSpanIsMalformed() throws Exception { + public void returns400IfSpanIsMalformedAndCreatesANewSpan() throws Exception { this.request = builder().header(Span.SPAN_ID_NAME, "asd") .header(Span.TRACE_ID_NAME, 20L).buildRequest(new MockServletContext()); TraceFilter filter = new TraceFilter(this.tracer, this.traceKeys, this.spanReporter, @@ -331,8 +333,26 @@ public class TraceFilterTests { filter.doFilter(this.request, this.response, this.filterChain); + then(new ArrayList<>(this.spanReporter.getSpans())).isNotEmpty(); then(TestSpanContextHolder.getCurrentSpan()).isNull(); - then(this.response.getStatus()).isEqualTo(HttpStatus.BAD_REQUEST.value()); + then(ExceptionUtils.getLastException()).isNull(); + then(this.response.getStatus()).isEqualTo(HttpStatus.OK.value()); + } + + @Test + public void returns200IfSpanParentIsMalformedAndCreatesANewSpan() throws Exception { + this.request = builder().header(Span.SPAN_ID_NAME, PARENT_ID) + .header(Span.PARENT_ID_NAME, "-") + .header(Span.TRACE_ID_NAME, 20L).buildRequest(new MockServletContext()); + TraceFilter filter = new TraceFilter(this.tracer, this.traceKeys, this.spanReporter, + this.spanExtractor, this.httpTraceKeysInjector); + + filter.doFilter(this.request, this.response, this.filterChain); + + then(new ArrayList<>(this.spanReporter.getSpans())).isNotEmpty(); + then(TestSpanContextHolder.getCurrentSpan()).isNull(); + then(ExceptionUtils.getLastException()).isNull(); + then(this.response.getStatus()).isEqualTo(HttpStatus.OK.value()); } public void verifyParentSpanHttpTags() { From b505e4a1f93fc9e8d37631ff6bf02064a10860a9 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Tue, 18 Oct 2016 16:21:04 +0200 Subject: [PATCH 098/235] Propagating exceptions in trace filter without this change any exception occurring while creating a span will be swallowed. with this change we're propagating the exception so that it gets handled properly. fixes #426 --- .../sleuth/instrument/web/TraceFilter.java | 21 +++++++------------ 1 file changed, 7 insertions(+), 14 deletions(-) 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 e5222e2cc..f8ba27379 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceFilter.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceFilter.java @@ -15,18 +15,18 @@ */ package org.springframework.cloud.sleuth.instrument.web; -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 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 org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -134,16 +134,9 @@ public class TraceFilter extends GenericFilterBean { return; } String name = HTTP_COMPONENT + ":" + uri; - try { - spanFromRequest = createSpan(request, skip, spanFromRequest, name); - } catch (IllegalArgumentException e) { - filterChain.doFilter(request, response); - response.sendError(HttpStatus.BAD_REQUEST.value(), - "Exception tracing request [" + e.getMessage() + "]"); - return; - } Throwable exception = null; try { + spanFromRequest = createSpan(request, skip, spanFromRequest, name); filterChain.doFilter(request, response); } catch (Throwable e) { exception = e; From 66c6dd30103f9bea3806dc3a7b52e4224909e6b5 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Tue, 18 Oct 2016 17:05:11 +0200 Subject: [PATCH 099/235] Bumping versions before release --- benchmarks/pom.xml | 2 +- docs/pom.xml | 2 +- pom.xml | 12 ++++++------ spring-cloud-sleuth-core/pom.xml | 2 +- spring-cloud-sleuth-dependencies/pom.xml | 4 ++-- spring-cloud-sleuth-samples/pom.xml | 2 +- .../spring-cloud-sleuth-sample-feign/pom.xml | 2 +- .../spring-cloud-sleuth-sample-messaging/pom.xml | 2 +- .../spring-cloud-sleuth-sample-ribbon/pom.xml | 2 +- .../spring-cloud-sleuth-sample-stream/pom.xml | 2 +- .../spring-cloud-sleuth-sample-test-core/pom.xml | 2 +- .../spring-cloud-sleuth-sample-websocket/pom.xml | 2 +- .../spring-cloud-sleuth-sample-zipkin-stream/pom.xml | 2 +- .../spring-cloud-sleuth-sample-zipkin/pom.xml | 2 +- .../spring-cloud-sleuth-sample/pom.xml | 2 +- spring-cloud-sleuth-stream/pom.xml | 2 +- spring-cloud-sleuth-zipkin-stream/pom.xml | 2 +- spring-cloud-sleuth-zipkin/pom.xml | 2 +- spring-cloud-starter-sleuth/pom.xml | 2 +- spring-cloud-starter-zipkin/pom.xml | 2 +- 20 files changed, 26 insertions(+), 26 deletions(-) diff --git a/benchmarks/pom.xml b/benchmarks/pom.xml index 3c6bd6c2d..39de11f8a 100644 --- a/benchmarks/pom.xml +++ b/benchmarks/pom.xml @@ -17,7 +17,7 @@ Benchmarks Benchmarks (JMH) org.springframework.cloud - 1.0.10.BUILD-SNAPSHOT + 1.0.10.RELEASE benchmarks diff --git a/docs/pom.xml b/docs/pom.xml index f46f8eac7..46f77400b 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.10.BUILD-SNAPSHOT + 1.0.10.RELEASE spring-cloud-sleuth-docs pom diff --git a/pom.xml b/pom.xml index 2c07235b6..7ab974c2a 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 spring-cloud-sleuth - 1.0.10.BUILD-SNAPSHOT + 1.0.10.RELEASE pom Spring Cloud Sleuth Spring Cloud Sleuth @@ -13,7 +13,7 @@ org.springframework.cloud spring-cloud-build - 1.1.3.BUILD-SNAPSHOT + 1.1.2.RELEASE @@ -229,10 +229,10 @@ 1.8 2.19.1 2.17 - 1.1.3.BUILD-SNAPSHOT - 1.1.3.BUILD-SNAPSHOT - Brooklyn.BUILD-SNAPSHOT - 1.1.6.BUILD-SNAPSHOT + 1.1.2.RELEASE + 1.1.3.RELEASE + Brooklyn.RELEASE + 1.1.6.RELEASE diff --git a/spring-cloud-sleuth-core/pom.xml b/spring-cloud-sleuth-core/pom.xml index e58f7e09b..56474e19f 100644 --- a/spring-cloud-sleuth-core/pom.xml +++ b/spring-cloud-sleuth-core/pom.xml @@ -11,7 +11,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.10.BUILD-SNAPSHOT + 1.0.10.RELEASE .. diff --git a/spring-cloud-sleuth-dependencies/pom.xml b/spring-cloud-sleuth-dependencies/pom.xml index ae173aca4..cf2455d23 100644 --- a/spring-cloud-sleuth-dependencies/pom.xml +++ b/spring-cloud-sleuth-dependencies/pom.xml @@ -5,11 +5,11 @@ spring-cloud-dependencies-parent org.springframework.cloud - 1.1.3.BUILD-SNAPSHOT + 1.1.2.RELEASE spring-cloud-sleuth-dependencies - 1.0.10.BUILD-SNAPSHOT + 1.0.10.RELEASE pom spring-cloud-sleuth-dependencies Spring Cloud Sleuth Dependencies diff --git a/spring-cloud-sleuth-samples/pom.xml b/spring-cloud-sleuth-samples/pom.xml index afe391ff7..b5c02e4e9 100644 --- a/spring-cloud-sleuth-samples/pom.xml +++ b/spring-cloud-sleuth-samples/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.10.BUILD-SNAPSHOT + 1.0.10.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml index 213148634..66ffbd196 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml @@ -20,7 +20,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.10.BUILD-SNAPSHOT + 1.0.10.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml index 3683d93be..ef1c40ed7 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.10.BUILD-SNAPSHOT + 1.0.10.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml index 21887c6c2..35c0aba75 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.10.BUILD-SNAPSHOT + 1.0.10.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml index d7b593c57..d25487b84 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.10.BUILD-SNAPSHOT + 1.0.10.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml index 4502c5635..e088aba10 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.10.BUILD-SNAPSHOT + 1.0.10.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml index b39ee5f9d..216b98192 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.10.BUILD-SNAPSHOT + 1.0.10.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml index 2d08fe3a6..b4212d669 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.10.BUILD-SNAPSHOT + 1.0.10.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml index be217be4f..01a1d9b21 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml @@ -27,7 +27,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.10.BUILD-SNAPSHOT + 1.0.10.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml index b041f3406..b4ac23de8 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.10.BUILD-SNAPSHOT + 1.0.10.RELEASE .. diff --git a/spring-cloud-sleuth-stream/pom.xml b/spring-cloud-sleuth-stream/pom.xml index 6fe1dce35..4b2101108 100644 --- a/spring-cloud-sleuth-stream/pom.xml +++ b/spring-cloud-sleuth-stream/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.10.BUILD-SNAPSHOT + 1.0.10.RELEASE diff --git a/spring-cloud-sleuth-zipkin-stream/pom.xml b/spring-cloud-sleuth-zipkin-stream/pom.xml index 4c1832b9c..bed38d8e4 100644 --- a/spring-cloud-sleuth-zipkin-stream/pom.xml +++ b/spring-cloud-sleuth-zipkin-stream/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.10.BUILD-SNAPSHOT + 1.0.10.RELEASE diff --git a/spring-cloud-sleuth-zipkin/pom.xml b/spring-cloud-sleuth-zipkin/pom.xml index 1a6f7676c..8695f2c41 100644 --- a/spring-cloud-sleuth-zipkin/pom.xml +++ b/spring-cloud-sleuth-zipkin/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.10.BUILD-SNAPSHOT + 1.0.10.RELEASE .. diff --git a/spring-cloud-starter-sleuth/pom.xml b/spring-cloud-starter-sleuth/pom.xml index 7a71b4433..3eed8511f 100644 --- a/spring-cloud-starter-sleuth/pom.xml +++ b/spring-cloud-starter-sleuth/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.10.BUILD-SNAPSHOT + 1.0.10.RELEASE .. spring-cloud-starter-sleuth diff --git a/spring-cloud-starter-zipkin/pom.xml b/spring-cloud-starter-zipkin/pom.xml index fb72cfe01..24b960987 100644 --- a/spring-cloud-starter-zipkin/pom.xml +++ b/spring-cloud-starter-zipkin/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.10.BUILD-SNAPSHOT + 1.0.10.RELEASE .. spring-cloud-starter-zipkin From a2449ddc591dee3f186cc8db5e8c6fec02eb3c74 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Tue, 18 Oct 2016 17:16:36 +0200 Subject: [PATCH 100/235] Going back to snapshots --- benchmarks/pom.xml | 2 +- docs/pom.xml | 2 +- pom.xml | 12 ++++++------ spring-cloud-sleuth-core/pom.xml | 2 +- spring-cloud-sleuth-dependencies/pom.xml | 4 ++-- spring-cloud-sleuth-samples/pom.xml | 2 +- .../spring-cloud-sleuth-sample-feign/pom.xml | 2 +- .../spring-cloud-sleuth-sample-messaging/pom.xml | 2 +- .../spring-cloud-sleuth-sample-ribbon/pom.xml | 2 +- .../spring-cloud-sleuth-sample-stream/pom.xml | 2 +- .../spring-cloud-sleuth-sample-test-core/pom.xml | 2 +- .../spring-cloud-sleuth-sample-websocket/pom.xml | 2 +- .../spring-cloud-sleuth-sample-zipkin-stream/pom.xml | 2 +- .../spring-cloud-sleuth-sample-zipkin/pom.xml | 2 +- .../spring-cloud-sleuth-sample/pom.xml | 2 +- spring-cloud-sleuth-stream/pom.xml | 2 +- spring-cloud-sleuth-zipkin-stream/pom.xml | 2 +- spring-cloud-sleuth-zipkin/pom.xml | 2 +- spring-cloud-starter-sleuth/pom.xml | 2 +- spring-cloud-starter-zipkin/pom.xml | 2 +- 20 files changed, 26 insertions(+), 26 deletions(-) diff --git a/benchmarks/pom.xml b/benchmarks/pom.xml index 39de11f8a..a0c924bd2 100644 --- a/benchmarks/pom.xml +++ b/benchmarks/pom.xml @@ -17,7 +17,7 @@ Benchmarks Benchmarks (JMH) org.springframework.cloud - 1.0.10.RELEASE + 1.0.11.BUILD-SNAPSHOT benchmarks diff --git a/docs/pom.xml b/docs/pom.xml index 46f77400b..f5d10ed17 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.10.RELEASE + 1.0.11.BUILD-SNAPSHOT spring-cloud-sleuth-docs pom diff --git a/pom.xml b/pom.xml index 7ab974c2a..529abe4d1 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 spring-cloud-sleuth - 1.0.10.RELEASE + 1.0.11.BUILD-SNAPSHOT pom Spring Cloud Sleuth Spring Cloud Sleuth @@ -13,7 +13,7 @@ org.springframework.cloud spring-cloud-build - 1.1.2.RELEASE + 1.1.3.BUILD-SNAPSHOT @@ -229,10 +229,10 @@ 1.8 2.19.1 2.17 - 1.1.2.RELEASE - 1.1.3.RELEASE - Brooklyn.RELEASE - 1.1.6.RELEASE + 1.1.3.BUILD-SNAPSHOT + 1.1.3.BUILD-SNAPSHOT + Brooklyn.BUILD-SNAPSHOT + 1.1.6.BUILD-SNAPSHOT diff --git a/spring-cloud-sleuth-core/pom.xml b/spring-cloud-sleuth-core/pom.xml index 56474e19f..9b3aba544 100644 --- a/spring-cloud-sleuth-core/pom.xml +++ b/spring-cloud-sleuth-core/pom.xml @@ -11,7 +11,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.10.RELEASE + 1.0.11.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-dependencies/pom.xml b/spring-cloud-sleuth-dependencies/pom.xml index cf2455d23..7bf2ee6e7 100644 --- a/spring-cloud-sleuth-dependencies/pom.xml +++ b/spring-cloud-sleuth-dependencies/pom.xml @@ -5,11 +5,11 @@ spring-cloud-dependencies-parent org.springframework.cloud - 1.1.2.RELEASE + 1.1.3.BUILD-SNAPSHOT spring-cloud-sleuth-dependencies - 1.0.10.RELEASE + 1.0.11.BUILD-SNAPSHOT pom spring-cloud-sleuth-dependencies Spring Cloud Sleuth Dependencies diff --git a/spring-cloud-sleuth-samples/pom.xml b/spring-cloud-sleuth-samples/pom.xml index b5c02e4e9..d87a6b0e9 100644 --- a/spring-cloud-sleuth-samples/pom.xml +++ b/spring-cloud-sleuth-samples/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.10.RELEASE + 1.0.11.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml index 66ffbd196..bcb5206e4 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml @@ -20,7 +20,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.10.RELEASE + 1.0.11.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml index ef1c40ed7..2cbd44cbe 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.10.RELEASE + 1.0.11.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml index 35c0aba75..71fd2e1df 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.10.RELEASE + 1.0.11.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml index d25487b84..da1c6d34a 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.10.RELEASE + 1.0.11.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml index e088aba10..47c6dfe61 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.10.RELEASE + 1.0.11.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml index 216b98192..117a3a912 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.10.RELEASE + 1.0.11.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml index b4212d669..67fb0764b 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.10.RELEASE + 1.0.11.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml index 01a1d9b21..202c6c482 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml @@ -27,7 +27,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.10.RELEASE + 1.0.11.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml index b4ac23de8..6d0a2a375 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.10.RELEASE + 1.0.11.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-stream/pom.xml b/spring-cloud-sleuth-stream/pom.xml index 4b2101108..9a756a4ca 100644 --- a/spring-cloud-sleuth-stream/pom.xml +++ b/spring-cloud-sleuth-stream/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.10.RELEASE + 1.0.11.BUILD-SNAPSHOT diff --git a/spring-cloud-sleuth-zipkin-stream/pom.xml b/spring-cloud-sleuth-zipkin-stream/pom.xml index bed38d8e4..26a937f06 100644 --- a/spring-cloud-sleuth-zipkin-stream/pom.xml +++ b/spring-cloud-sleuth-zipkin-stream/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.10.RELEASE + 1.0.11.BUILD-SNAPSHOT diff --git a/spring-cloud-sleuth-zipkin/pom.xml b/spring-cloud-sleuth-zipkin/pom.xml index 8695f2c41..82afa1f08 100644 --- a/spring-cloud-sleuth-zipkin/pom.xml +++ b/spring-cloud-sleuth-zipkin/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.10.RELEASE + 1.0.11.BUILD-SNAPSHOT .. diff --git a/spring-cloud-starter-sleuth/pom.xml b/spring-cloud-starter-sleuth/pom.xml index 3eed8511f..5b6d58794 100644 --- a/spring-cloud-starter-sleuth/pom.xml +++ b/spring-cloud-starter-sleuth/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.10.RELEASE + 1.0.11.BUILD-SNAPSHOT .. spring-cloud-starter-sleuth diff --git a/spring-cloud-starter-zipkin/pom.xml b/spring-cloud-starter-zipkin/pom.xml index 24b960987..eb9236a63 100644 --- a/spring-cloud-starter-zipkin/pom.xml +++ b/spring-cloud-starter-zipkin/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.10.RELEASE + 1.0.11.BUILD-SNAPSHOT .. spring-cloud-starter-zipkin From 56048b185233ccba271b97f95bc3f69b21510888 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Thu, 20 Oct 2016 12:20:45 +0200 Subject: [PATCH 101/235] Increased level of TOC --- docs/src/main/asciidoc/spring-cloud-sleuth.adoc | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc index 1f59207b8..cd0962524 100644 --- a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc +++ b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc @@ -3,6 +3,7 @@ :github-raw: http://raw.github.com/{github-repo}/{github-tag} :github-code: http://github.com/{github-repo}/tree/{github-tag} :toc: left +:toclevels: 8 :nofooter: Spring Cloud Sleuth From 8437bef6a99840762b6087a3af7ca4a27495ddde Mon Sep 17 00:00:00 2001 From: Biju Kunjummen Date: Thu, 27 Oct 2016 04:42:48 -0700 Subject: [PATCH 102/235] Issue 424 - Documentation for adding trace/span headers to http response (#429) fixes #424 --- .../main/asciidoc/spring-cloud-sleuth.adoc | 31 ++-- ...raceCustomFilterResponseInjectorTests.java | 159 ++++++++++++++++++ .../web/TraceFilterCustomExtractorTests.java | 27 +-- 3 files changed, 178 insertions(+), 39 deletions(-) create mode 100644 spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceCustomFilterResponseInjectorTests.java diff --git a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc index cd0962524..ee6255fe7 100644 --- a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc +++ b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc @@ -228,8 +228,7 @@ to your bean definition. === HTTP -For HTTP these are the beans responsible for creation of a Span from a `HttpServletRequest` - and filling in the `HttpServletResponse` with tracing information. +For HTTP these are the beans responsible for creation of a Span from a `HttpServletRequest`. [source,java] ---- @@ -237,11 +236,6 @@ For HTTP these are the beans responsible for creation of a Span from a `HttpServ public SpanExtractor httpServletRequestSpanExtractor() { ... } - -@Bean -public SpanInjector httpServletResponseSpanInjector() { - ... -} ---- You can override them by providing your own implementation and by adding a `@Primary` annotation @@ -262,20 +256,27 @@ This is a an example of a `SpanExtractor` include::../../../..//spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterCustomExtractorTests.java[tags=extractor,indent=0] ---- -The following `SpanInjector` could be created - -[source,java] ----- -include::../../../..//spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterCustomExtractorTests.java[tags=injector,indent=0] ----- - -And you could register them like this: +And you could register it like this: [source,java] ---- include::../../../..//spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterCustomExtractorTests.java[tags=configuration,indent=0] ---- +Spring Cloud Sleuth does not add trace/span related headers to the Http Response for security reasons. If you need the headers then a custom `SpanInjector` +that injects the headers into the Http Response and a Servlet filter which makes use of this can be added the following way: + +[source,java] +---- +include::../../../..//spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceCustomFilterResponseInjectorTests.java[tags=injector,indent=0] +---- + +And you could register them like this: +[source,java] +---- +include::../../../..//spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceCustomFilterResponseInjectorTests.java[tags=configuration,indent=0] +---- + === Custom SA tag in Zipkin Sometimes you want to create a manual Span that will wrap a call to an external service which is not instrumented. diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceCustomFilterResponseInjectorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceCustomFilterResponseInjectorTests.java new file mode 100644 index 000000000..1783bbfce --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceCustomFilterResponseInjectorTests.java @@ -0,0 +1,159 @@ +/* + * 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.io.IOException; +import java.net.URI; +import java.util.HashMap; +import java.util.Map; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletResponse; + +import org.junit.Test; +import org.junit.runner.RunWith; +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.SpringApplicationConfiguration; +import org.springframework.boot.test.WebIntegrationTest; +import org.springframework.cloud.sleuth.Span; +import org.springframework.cloud.sleuth.SpanInjector; +import org.springframework.cloud.sleuth.Tracer; +import org.springframework.context.ApplicationListener; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpHeaders; +import org.springframework.http.RequestEntity; +import org.springframework.http.ResponseEntity; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.filter.GenericFilterBean; + +import static org.assertj.core.api.BDDAssertions.then; + +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(TraceCustomFilterResponseInjectorTests.Config.class) +@WebIntegrationTest(randomPort = true) +@DirtiesContext +public class TraceCustomFilterResponseInjectorTests { + @Autowired RestTemplate restTemplate; + @Autowired Config config; + @Autowired CustomRestController customRestController; + + + @Test + @SuppressWarnings("unchecked") + public void should_inject_trace_and_span_ids_in_response_headers() { + RequestEntity requestEntity = RequestEntity + .get(URI.create("http://localhost:" + this.config.port + "/headers")) + .build(); + + @SuppressWarnings("rawtypes") + ResponseEntity responseEntity = this.restTemplate.exchange(requestEntity, Map.class); + + then(responseEntity.getHeaders()) + .containsKeys(Span.TRACE_ID_NAME, Span.SPAN_ID_NAME) + .as("Trace headers must be present in response headers"); + } + + @Configuration + @EnableAutoConfiguration + static class Config + implements ApplicationListener { + int port; + + // tag::configuration[] + @Bean + SpanInjector customHttpServletResponseSpanInjector() { + return new CustomHttpServletResponseSpanInjector(); + } + + @Bean + HttpResponseInjectingTraceFilter responseInjectingTraceFilter(Tracer tracer) { + return new HttpResponseInjectingTraceFilter(tracer, customHttpServletResponseSpanInjector()); + } + // end::configuration[] + + @Override + public void onApplicationEvent(EmbeddedServletContainerInitializedEvent event) { + this.port = event.getEmbeddedServletContainer().getPort(); + } + + @Bean + public RestTemplate restTemplate() { + return new RestTemplate(); + } + + @Bean + CustomRestController customRestController() { + return new CustomRestController(); + } + + + } + + // tag::injector[] + static class CustomHttpServletResponseSpanInjector + implements SpanInjector { + + @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())); + } + } + + static class HttpResponseInjectingTraceFilter extends GenericFilterBean { + + private final Tracer tracer; + private final SpanInjector spanInjector; + + public HttpResponseInjectingTraceFilter(Tracer tracer, SpanInjector spanInjector) { + this.tracer = tracer; + this.spanInjector = spanInjector; + } + + @Override + 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); + filterChain.doFilter(request, response); + } + } + // end::injector[] + + @RestController + static class CustomRestController { + + @RequestMapping("/headers") + public Map headers(@RequestHeader HttpHeaders headers) { + Map map = new HashMap<>(); + for (String key : headers.keySet()) { + map.put(key, headers.getFirst(key)); + } + return map; + } + } +} diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterCustomExtractorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterCustomExtractorTests.java index 652d961fa..708a5f4a0 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterCustomExtractorTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterCustomExtractorTests.java @@ -16,12 +16,11 @@ package org.springframework.cloud.sleuth.instrument.web; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import java.net.URI; import java.util.HashMap; import java.util.Map; import java.util.Random; +import javax.servlet.http.HttpServletRequest; import org.junit.Before; import org.junit.Test; @@ -34,7 +33,6 @@ import org.springframework.boot.test.WebIntegrationTest; import org.springframework.cloud.sleuth.Sampler; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.SpanExtractor; -import org.springframework.cloud.sleuth.SpanInjector; import org.springframework.cloud.sleuth.SpanReporter; import org.springframework.cloud.sleuth.sampler.AlwaysSampler; import org.springframework.cloud.sleuth.util.ArrayListSpanAccumulator; @@ -83,13 +81,13 @@ public class TraceFilterCustomExtractorTests { .header("mySpanId", Span.idToHex(spanId)).build(); @SuppressWarnings("rawtypes") - ResponseEntity responseHeaders = this.restTemplate.exchange(requestEntity, + ResponseEntity responseEntity = this.restTemplate.exchange(requestEntity, Map.class); await().until(() -> then(this.accumulator.getSpans().stream().filter( span -> span.getSpanId() == spanId).findFirst().get()) .hasTraceIdEqualTo(traceId)); - then(responseHeaders.getBody()) + then(responseEntity.getBody()) .containsEntry("correlationid", Span.idToHex(traceId)) .containsEntry("myspanid", Span.idToHex(spanId)) .as("input request headers"); @@ -107,12 +105,6 @@ public class TraceFilterCustomExtractorTests { SpanExtractor customHttpServletRequestSpanExtractor() { return new CustomHttpServletRequestSpanExtractor(); } - - @Bean - @Primary - SpanInjector customHttpServletResponseSpanInjector() { - return new CustomHttpServletResponseSpanInjector(); - } // end::configuration[] @Override @@ -157,19 +149,6 @@ public class TraceFilterCustomExtractorTests { } // end::extractor[] - // tag::injector[] - static class CustomHttpServletResponseSpanInjector - implements SpanInjector { - - @Override - public void inject(Span span, HttpServletResponse carrier) { - carrier.addHeader("correlationId", Span.idToHex(span.getTraceId())); - carrier.addHeader("mySpanId", Span.idToHex(span.getSpanId())); - // inject the rest of Span values to the header - } - } - // end::injector[] - @RestController static class CustomRestController { From e5181441264a88b38574da11b9cf8da2c388ce8c Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Thu, 27 Oct 2016 14:06:28 +0200 Subject: [PATCH 103/235] Updated docs with percentage information without this change the percentage value might have been set to over 1.0 with this change we explain what are the valid values and what are the reasons for keeping the value as it is fixes #397 --- docs/src/main/asciidoc/spring-cloud-sleuth.adoc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc index ee6255fe7..984ed90bf 100644 --- a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc +++ b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc @@ -38,7 +38,9 @@ fixed fraction of spans. NOTE: the `PercentageBasedSampler` is the default if you are using `spring-cloud-sleuth-zipkin` or `spring-cloud-sleuth-stream`. You can -configure the exports using `spring.sleuth.sampler.percentage`. +configure the exports using `spring.sleuth.sampler.percentage`. The passed +value needs to be a double from `0.0` to `1.0` so it's not a percentage. +For backwards compatibility reasons we're not changing the property name. A sampler can be installed just by creating a bean definition, e.g: From faa3d4ecf958ce62ac4924fcd7d646552664244d Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Fri, 28 Oct 2016 09:30:01 +0200 Subject: [PATCH 104/235] Providing examples of how to work with executors without this example it might be misleading for users how to work with callables and custom executors. with this example we're showing both the case when you're using a custom executor (in this case you just have to register it as a bean and then use that bean in your callable); and also we show an example of how to reuse the taskScheduler one (it's enough to wrap it with LazyTraceExecutor). fixes #423 --- .../async/TraceExecutorBeanPostProcessor.java | 3 +- .../async/issues/issue410/Issue410Tests.java | 106 +++++++++++++++++- 2 files changed, 106 insertions(+), 3 deletions(-) diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceExecutorBeanPostProcessor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceExecutorBeanPostProcessor.java index 66f97a16b..04e8ea0ef 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceExecutorBeanPostProcessor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceExecutorBeanPostProcessor.java @@ -43,8 +43,7 @@ class TraceExecutorBeanPostProcessor implements BeanPostProcessor { if (bean instanceof ThreadPoolTaskExecutor && !(bean instanceof TaskScheduler) && !(bean instanceof LazyTraceThreadPoolTaskExecutor)) { return new LazyTraceThreadPoolTaskExecutor(this.beanFactory, (ThreadPoolTaskExecutor) bean); - } - if (bean instanceof Executor && !(bean instanceof TaskScheduler) && !(bean instanceof LazyTraceExecutor)) { + } else if (bean instanceof Executor && !(bean instanceof TaskScheduler) && !(bean instanceof LazyTraceExecutor)) { return new LazyTraceExecutor(this.beanFactory, (Executor) bean); } return bean; diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue410/Issue410Tests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue410/Issue410Tests.java index 982e11b00..cf180ca1b 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue410/Issue410Tests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue410/Issue410Tests.java @@ -16,6 +16,8 @@ package org.springframework.cloud.sleuth.instrument.async.issues.issue410; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicReference; @@ -23,7 +25,9 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.junit.Test; import org.junit.runner.RunWith; +import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration; import org.springframework.boot.test.SpringApplicationConfiguration; @@ -31,6 +35,7 @@ import org.springframework.boot.test.WebIntegrationTest; import org.springframework.cloud.sleuth.Sampler; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.Tracer; +import org.springframework.cloud.sleuth.instrument.async.LazyTraceExecutor; import org.springframework.cloud.sleuth.sampler.AlwaysSampler; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -47,7 +52,7 @@ import org.springframework.web.client.RestTemplate; import com.jayway.awaitility.Awaitility; -import static org.assertj.core.api.BDDAssertions.then; +import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; /** * @author Marcin Grzejszczak @@ -89,6 +94,38 @@ public class Issue410Tests { }); } + /** + * Related to issue #423 + */ + @Test + public void should_pass_tracing_info_for_completable_futures_with_executor() { + Span span = this.tracer.createSpan("foo"); + + String response = this.restTemplate.getForObject("http://localhost:" + port() + "/completable", String.class); + + then(response).isEqualTo(Span.idToHex(span.getTraceId())); + Awaitility.await().until(() -> { + then(this.asyncTask.getSpan().get()).isNotNull(); + then(this.asyncTask.getSpan().get().getTraceId()).isEqualTo(span.getTraceId()); + }); + } + + /** + * Related to issue #423 + */ + @Test + public void should_pass_tracing_info_for_completable_futures_with_task_scheduler() { + Span span = this.tracer.createSpan("foo"); + + String response = this.restTemplate.getForObject("http://localhost:" + port() + "/taskScheduler", String.class); + + then(response).isEqualTo(Span.idToHex(span.getTraceId())); + Awaitility.await().until(() -> { + then(this.asyncTask.getSpan().get()).isNotNull(); + then(this.asyncTask.getSpan().get().getTraceId()).isEqualTo(span.getTraceId()); + }); + } + private int port() { return this.environment.getProperty("local.server.port", Integer.class); } @@ -123,6 +160,9 @@ class AsyncTask { private AtomicReference span = new AtomicReference<>(); @Autowired Tracer tracer; + @Autowired @Qualifier("poolTaskExecutor") Executor executor; + @Autowired @Qualifier("taskScheduler") Executor taskScheduler; + @Autowired BeanFactory beanFactory; @Async("poolTaskExecutor") public void runWithPool() { @@ -136,6 +176,58 @@ class AsyncTask { this.span.set(this.tracer.getCurrentSpan()); } + public Span completableFutures() throws ExecutionException, InterruptedException { + log.info("This task is running with completable future"); + CompletableFuture span1 = CompletableFuture + .supplyAsync(() -> { + AsyncTask.log.info("First completable future"); + return AsyncTask.this.tracer.getCurrentSpan(); + }, AsyncTask.this.executor); + CompletableFuture span2 = CompletableFuture + .supplyAsync(() -> { + AsyncTask.log.info("Second completable future"); + return AsyncTask.this.tracer.getCurrentSpan(); + }, AsyncTask.this.executor); + CompletableFuture response = CompletableFuture.allOf(span1, span2) + .thenApply(ignoredVoid -> { + AsyncTask.log.info("Third completable future"); + Span joinedSpan1 = span1.join(); + Span joinedSpan2 = span2.join(); + then(joinedSpan2).isNotNull(); + then(joinedSpan1).hasTraceIdEqualTo(joinedSpan2.getTraceId()); + AsyncTask.log.info("TraceIds are correct"); + return joinedSpan2; + }); + this.span.set(response.get()); + return this.span.get(); + } + + public Span taskScheduler() throws ExecutionException, InterruptedException { + log.info("This task is running with completable future"); + CompletableFuture span1 = CompletableFuture + .supplyAsync(() -> { + AsyncTask.log.info("First completable future"); + return AsyncTask.this.tracer.getCurrentSpan(); + }, new LazyTraceExecutor(AsyncTask.this.beanFactory, AsyncTask.this.taskScheduler)); + CompletableFuture span2 = CompletableFuture + .supplyAsync(() -> { + AsyncTask.log.info("Second completable future"); + return AsyncTask.this.tracer.getCurrentSpan(); + }, new LazyTraceExecutor(AsyncTask.this.beanFactory, AsyncTask.this.taskScheduler)); + CompletableFuture response = CompletableFuture.allOf(span1, span2) + .thenApply(ignoredVoid -> { + AsyncTask.log.info("Third completable future"); + Span joinedSpan1 = span1.join(); + Span joinedSpan2 = span2.join(); + then(joinedSpan2).isNotNull(); + then(joinedSpan1).hasTraceIdEqualTo(joinedSpan2.getTraceId()); + AsyncTask.log.info("TraceIds are correct"); + return joinedSpan2; + }); + this.span.set(response.get()); + return this.span.get(); + } + public AtomicReference getSpan() { return span; } @@ -165,4 +257,16 @@ class Application { return Span.idToHex(this.tracer.getCurrentSpan().getTraceId()); } + @RequestMapping("/completable") + public String completable() throws ExecutionException, InterruptedException { + log.info("Executing completable"); + return Span.idToHex(this.asyncTask.completableFutures().getTraceId()); + } + + @RequestMapping("/taskScheduler") + public String taskScheduler() throws ExecutionException, InterruptedException { + log.info("Executing completable via task scheduler"); + return Span.idToHex(this.asyncTask.taskScheduler().getTraceId()); + } + } From 538b5c956ecae9486ad72a535e8c5a672886dc20 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Mon, 31 Oct 2016 13:12:25 +0100 Subject: [PATCH 105/235] Added error tag upon exceptions (#435) without this change there's no error colouring on Zipkin side with this change we set error tags - whenever there is an exception thrown on the server side (5xx) - wheneber on the client side it's impossible to send a message fixes #384 --- .../springframework/cloud/sleuth/Span.java | 1 + .../messaging/TraceChannelInterceptor.java | 9 +++ .../sleuth/instrument/web/TraceFilter.java | 2 + .../web/TraceHandlerInterceptor.java | 4 + .../client/TraceRestTemplateInterceptor.java | 3 + .../web/client/feign/TraceFeignClient.java | 7 +- .../cloud/sleuth/util/ExceptionUtils.java | 4 + .../async/issues/issue410/Issue410Tests.java | 68 +++++++++------- .../TraceChannelInterceptorTests.java | 6 +- .../instrument/web/TraceFilterTests.java | 7 +- .../web/TraceFilterWebIntegrationTests.java | 4 + ...stTemplateInterceptorIntegrationTests.java | 10 ++- .../client/feign/TraceFeignClientTests.java | 2 +- .../FeignClientServerErrorTests.java | 78 +++++++++++-------- .../cloud/sleuth/util/ExceptionUtilsTest.java | 18 +++++ 15 files changed, 150 insertions(+), 73 deletions(-) diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java index 4a152355d..e44e6d44a 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java @@ -90,6 +90,7 @@ public class Span { public static final String SPAN_NOT_SAMPLED = "0"; public static final String SPAN_LOCAL_COMPONENT_TAG_NAME = "lc"; + public static final String SPAN_ERROR_TAG_NAME = "error"; /** * cr - Client Receive. Signifies the end of the span. The client has 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 a290b5a27..171ab01ad 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 @@ -23,6 +23,7 @@ import org.springframework.cloud.sleuth.SpanInjector; import org.springframework.cloud.sleuth.TraceKeys; import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.sampler.NeverSampler; +import org.springframework.cloud.sleuth.util.ExceptionUtils; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.MessageHandler; @@ -53,6 +54,7 @@ public class TraceChannelInterceptor extends AbstractTraceChannelInterceptor { } else if (spanFromHeader != null) { spanFromHeader.logEvent(Span.CLIENT_RECV); } + addErrorTag(ex); getTracer().close(spanFromHeader); } @@ -120,10 +122,17 @@ public class TraceChannelInterceptor extends AbstractTraceChannelInterceptor { Span spanFromHeader = getSpanFromHeader(message); if (spanFromHeader!= null) { spanFromHeader.logEvent(Span.SERVER_SEND); + addErrorTag(ex); } getTracer().detach(spanFromHeader); } + private void addErrorTag(Exception ex) { + if (ex != null) { + getTracer().addTag(Span.SPAN_ERROR_TAG_NAME, ExceptionUtils.getExceptionMessage(ex)); + } + } + private Span getSpanFromHeader(Message message) { if (message == null) { return null; 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 f8ba27379..96ed118a7 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 @@ -36,6 +36,7 @@ import org.springframework.cloud.sleuth.SpanReporter; import org.springframework.cloud.sleuth.TraceKeys; import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.sampler.NeverSampler; +import org.springframework.cloud.sleuth.util.ExceptionUtils; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.http.HttpStatus; @@ -140,6 +141,7 @@ public class TraceFilter extends GenericFilterBean { filterChain.doFilter(request, response); } catch (Throwable e) { exception = e; + this.tracer.addTag(Span.SPAN_ERROR_TAG_NAME, ExceptionUtils.getExceptionMessage(e)); throw e; } finally { if (isAsyncStarted(request) || request.isAsyncStarted()) { diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptor.java index 58bc9a4b1..538195de3 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptor.java @@ -27,6 +27,7 @@ import org.springframework.boot.autoconfigure.web.ErrorController; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.TraceKeys; import org.springframework.cloud.sleuth.Tracer; +import org.springframework.cloud.sleuth.util.ExceptionUtils; import org.springframework.cloud.sleuth.util.SpanNameUtil; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; @@ -150,6 +151,9 @@ public class TraceHandlerInterceptor extends HandlerInterceptorAdapter { if (log.isDebugEnabled()) { log.debug("Closing span " + span); } + if (ex != null) { + getTracer().addTag(Span.SPAN_ERROR_TAG_NAME, ExceptionUtils.getExceptionMessage(ex)); + } getTracer().close(span); } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptor.java index 8eda2fbc4..ce71beb84 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptor.java @@ -18,9 +18,11 @@ package org.springframework.cloud.sleuth.instrument.web.client; import java.io.IOException; +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; import org.springframework.http.HttpRequest; import org.springframework.http.client.ClientHttpRequestExecution; import org.springframework.http.client.ClientHttpRequestInterceptor; @@ -59,6 +61,7 @@ public class TraceRestTemplateInterceptor extends AbstractTraceHttpRequestInterc if (log.isDebugEnabled()) { log.debug("Exception occurred while trying to execute the request. Will close the span [" + currentSpan() + "]", e); } + this.tracer.addTag(Span.SPAN_ERROR_TAG_NAME, ExceptionUtils.getExceptionMessage(e)); this.tracer.close(currentSpan()); throw e; } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClient.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClient.java index 1c66c0108..33acce88b 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClient.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClient.java @@ -31,6 +31,7 @@ import org.springframework.cloud.sleuth.instrument.web.HttpTraceKeysInjector; 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 @@ -134,11 +135,7 @@ class TraceFeignClient implements Client { private void logError(Exception e) { Span span = getTracer().getCurrentSpan(); if (span != null) { - String message = e.getMessage() != null ? e.getMessage() : e.toString(); - if (log.isDebugEnabled()) { - log.debug("Appending exception [" + message + "] to span " + span); - } - getTracer().addTag("error", message); + getTracer().addTag(Span.SPAN_ERROR_TAG_NAME, ExceptionUtils.getExceptionMessage(e)); } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/util/ExceptionUtils.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/util/ExceptionUtils.java index 7cabe94a8..2c0182a20 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/util/ExceptionUtils.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/util/ExceptionUtils.java @@ -55,4 +55,8 @@ public final class ExceptionUtils { ExceptionUtils.fail = fail; ExceptionUtils.lastException = null; } + + public static String getExceptionMessage(Throwable e) { + return e.getMessage() != null ? e.getMessage() : e.toString(); + } } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue410/Issue410Tests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue410/Issue410Tests.java index cf180ca1b..dd3c17a08 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue410/Issue410Tests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue410/Issue410Tests.java @@ -71,27 +71,33 @@ public class Issue410Tests { @Test public void should_pass_tracing_info_for_tasks_running_without_a_pool() { Span span = this.tracer.createSpan("foo"); + try { + String response = this.restTemplate.getForObject("http://localhost:" + port() + "/without_pool", String.class); - String response = this.restTemplate.getForObject("http://localhost:" + port() + "/without_pool", String.class); - - then(response).isEqualTo(Span.idToHex(span.getTraceId())); - Awaitility.await().until(() -> { - then(this.asyncTask.getSpan().get()).isNotNull(); - then(this.asyncTask.getSpan().get().getTraceId()).isEqualTo(span.getTraceId()); - }); + then(response).isEqualTo(Span.idToHex(span.getTraceId())); + Awaitility.await().until(() -> { + then(this.asyncTask.getSpan().get()).isNotNull(); + then(this.asyncTask.getSpan().get().getTraceId()).isEqualTo(span.getTraceId()); + }); + } finally { + this.tracer.close(span); + } } @Test public void should_pass_tracing_info_for_tasks_running_with_a_pool() { Span span = this.tracer.createSpan("foo"); + try { + String response = this.restTemplate.getForObject("http://localhost:" + port() + "/with_pool", String.class); - String response = this.restTemplate.getForObject("http://localhost:" + port() + "/with_pool", String.class); - - then(response).isEqualTo(Span.idToHex(span.getTraceId())); - Awaitility.await().until(() -> { - then(this.asyncTask.getSpan().get()).isNotNull(); - then(this.asyncTask.getSpan().get().getTraceId()).isEqualTo(span.getTraceId()); - }); + then(response).isEqualTo(Span.idToHex(span.getTraceId())); + Awaitility.await().until(() -> { + then(this.asyncTask.getSpan().get()).isNotNull(); + then(this.asyncTask.getSpan().get().getTraceId()).isEqualTo(span.getTraceId()); + }); + } finally { + this.tracer.close(span); + } } /** @@ -100,14 +106,17 @@ public class Issue410Tests { @Test public void should_pass_tracing_info_for_completable_futures_with_executor() { Span span = this.tracer.createSpan("foo"); + try { + String response = this.restTemplate.getForObject("http://localhost:" + port() + "/completable", String.class); - String response = this.restTemplate.getForObject("http://localhost:" + port() + "/completable", String.class); - - then(response).isEqualTo(Span.idToHex(span.getTraceId())); - Awaitility.await().until(() -> { - then(this.asyncTask.getSpan().get()).isNotNull(); - then(this.asyncTask.getSpan().get().getTraceId()).isEqualTo(span.getTraceId()); - }); + then(response).isEqualTo(Span.idToHex(span.getTraceId())); + Awaitility.await().until(() -> { + then(this.asyncTask.getSpan().get()).isNotNull(); + then(this.asyncTask.getSpan().get().getTraceId()).isEqualTo(span.getTraceId()); + }); + } finally { + this.tracer.close(span); + } } /** @@ -116,14 +125,17 @@ public class Issue410Tests { @Test public void should_pass_tracing_info_for_completable_futures_with_task_scheduler() { Span span = this.tracer.createSpan("foo"); + try { + String response = this.restTemplate.getForObject("http://localhost:" + port() + "/taskScheduler", String.class); - String response = this.restTemplate.getForObject("http://localhost:" + port() + "/taskScheduler", String.class); - - then(response).isEqualTo(Span.idToHex(span.getTraceId())); - Awaitility.await().until(() -> { - then(this.asyncTask.getSpan().get()).isNotNull(); - then(this.asyncTask.getSpan().get().getTraceId()).isEqualTo(span.getTraceId()); - }); + then(response).isEqualTo(Span.idToHex(span.getTraceId())); + Awaitility.await().until(() -> { + then(this.asyncTask.getSpan().get()).isNotNull(); + then(this.asyncTask.getSpan().get().getTraceId()).isEqualTo(span.getTraceId()); + }); + } finally { + this.tracer.close(span); + } } private int port() { 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 c5a570de8..d86eab7fd 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 @@ -31,6 +31,7 @@ import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.cloud.sleuth.Sampler; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.Tracer; +import org.springframework.cloud.sleuth.assertions.ListOfSpans; import org.springframework.cloud.sleuth.assertions.SleuthAssertions; import org.springframework.cloud.sleuth.instrument.messaging.TraceChannelInterceptorTests.App; import org.springframework.cloud.sleuth.sampler.AlwaysSampler; @@ -87,7 +88,7 @@ public class TraceChannelInterceptorTests implements MessageHandler { this.message = message; this.span = TestSpanContextHolder.getCurrentSpan(); if (message.getHeaders().containsKey("THROW_EXCEPTION")) { - throw new RuntimeException(); + throw new RuntimeException("A terrible exception has occurred"); } } @@ -268,6 +269,9 @@ public class TraceChannelInterceptorTests implements MessageHandler { then(this.message).isNotNull(); this.tracer.close(span); then(TestSpanContextHolder.getCurrentSpan()).isNull(); + then(new ListOfSpans(this.accumulator.getSpans())) + .hasASpanWithTagEqualTo(Span.SPAN_ERROR_TAG_NAME, + "A terrible exception has occurred"); } @Test 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 a06f90198..db7a6a213 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 @@ -33,6 +33,7 @@ import org.springframework.cloud.sleuth.SpanExtractor; 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.log.SpanLogger; import org.springframework.cloud.sleuth.sampler.AlwaysSampler; import org.springframework.cloud.sleuth.sampler.NeverSampler; @@ -287,7 +288,7 @@ public class TraceFilterTests { } @Test - public void catchesException() throws Exception { + public void shouldAnnotateSpanWithErrorWhenExceptionIsThrown() throws Exception { this.request = builder().header(Span.SPAN_ID_NAME, PARENT_ID) .header(Span.TRACE_ID_NAME, 20L).buildRequest(new MockServletContext()); TraceFilter filter = new TraceFilter(this.tracer, this.traceKeys, this.spanReporter, @@ -298,7 +299,7 @@ public class TraceFilterTests { javax.servlet.ServletResponse response) throws java.io.IOException, javax.servlet.ServletException { throw new RuntimeException("Planned"); - }; + } }; try { filter.doFilter(this.request, this.response, this.filterChain); @@ -309,6 +310,8 @@ public class TraceFilterTests { verifyParentSpanHttpTags(HttpStatus.INTERNAL_SERVER_ERROR); then(TestSpanContextHolder.getCurrentSpan()).isNull(); + then(new ListOfSpans(this.spanReporter.getSpans())) + .hasASpanWithTagEqualTo(Span.SPAN_ERROR_TAG_NAME, "Planned"); } @Test diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterWebIntegrationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterWebIntegrationTests.java index 8ed1a8ad1..662f1567a 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterWebIntegrationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterWebIntegrationTests.java @@ -27,6 +27,7 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.boot.test.WebIntegrationTest; import org.springframework.cloud.sleuth.Sampler; +import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.assertions.ListOfSpans; import org.springframework.cloud.sleuth.sampler.AlwaysSampler; @@ -74,6 +75,9 @@ public class TraceFilterWebIntegrationTests { .doesNotHaveASpanWithName("error") .hasASpanWithTagEqualTo("http.status_code", "500"); then(ExceptionUtils.getLastException()).isNull(); + then(new ListOfSpans(this.accumulator.getSpans())) + .hasASpanWithTagEqualTo(Span.SPAN_ERROR_TAG_NAME, + "Request processing failed; nested exception is java.lang.RuntimeException: Throwing exception"); } private int port() { diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptorIntegrationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptorIntegrationTests.java index 48c4e662e..73a363ecc 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptorIntegrationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptorIntegrationTests.java @@ -27,15 +27,16 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.springframework.cloud.sleuth.DefaultSpanNamer; -import org.springframework.cloud.sleuth.NoOpSpanReporter; import org.springframework.cloud.sleuth.Span; 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.log.NoOpSpanLogger; import org.springframework.cloud.sleuth.sampler.AlwaysSampler; import org.springframework.cloud.sleuth.trace.DefaultTracer; import org.springframework.cloud.sleuth.trace.TestSpanContextHolder; +import org.springframework.cloud.sleuth.util.ArrayListSpanAccumulator; import org.springframework.cloud.sleuth.util.ExceptionUtils; import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.http.client.ClientHttpRequestInterceptor; @@ -59,10 +60,12 @@ public class TraceRestTemplateInterceptorIntegrationTests { private DefaultTracer tracer; + private ArrayListSpanAccumulator spanAccumulator = new ArrayListSpanAccumulator(); + @Before public void setup() { this.tracer = new DefaultTracer(new AlwaysSampler(), new Random(), - new DefaultSpanNamer(), new NoOpSpanLogger(), new NoOpSpanReporter()); + new DefaultSpanNamer(), new NoOpSpanLogger(), this.spanAccumulator); this.template.setInterceptors(Arrays.asList( new TraceRestTemplateInterceptor(this.tracer, new HttpRequestInjector(), new HttpTraceKeysInjector(this.tracer, new TraceKeys())))); @@ -91,6 +94,9 @@ public class TraceRestTemplateInterceptorIntegrationTests { SleuthAssertions.then(this.tracer.getCurrentSpan()).isEqualTo(span); this.tracer.close(span); + SleuthAssertions.then(new ListOfSpans(this.spanAccumulator.getSpans())) + .hasASpanWithTagEqualTo(Span.SPAN_ERROR_TAG_NAME, + "Read timed out"); then(ExceptionUtils.getLastException()).isNull(); } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientTests.java index 1477bd2ce..ce8f7ea21 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientTests.java @@ -99,7 +99,7 @@ public class TraceFeignClientTests { then(this.tracer.getCurrentSpan()).isEqualTo(span); then(this.spanAccumulator.getSpans().get(0)) .hasNotLoggedAnEvent(Span.CLIENT_RECV) - .hasATag("error", "exception has occurred"); + .hasATag(Span.SPAN_ERROR_TAG_NAME, "exception has occurred"); } } \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/servererrors/FeignClientServerErrorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/servererrors/FeignClientServerErrorTests.java index 67512f8cb..5cd501607 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/servererrors/FeignClientServerErrorTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/servererrors/FeignClientServerErrorTests.java @@ -20,11 +20,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import com.netflix.hystrix.exception.HystrixRuntimeException; -import com.netflix.loadbalancer.BaseLoadBalancer; -import com.netflix.loadbalancer.ILoadBalancer; -import com.netflix.loadbalancer.Server; - import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -44,6 +39,7 @@ import org.springframework.cloud.sleuth.Sampler; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.SpanReporter; import org.springframework.cloud.sleuth.Tracer; +import org.springframework.cloud.sleuth.assertions.ListOfSpans; import org.springframework.cloud.sleuth.sampler.AlwaysSampler; import org.springframework.cloud.sleuth.util.ExceptionUtils; import org.springframework.context.annotation.Bean; @@ -58,10 +54,16 @@ import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; +import com.jayway.awaitility.Awaitility; +import com.netflix.hystrix.exception.HystrixRuntimeException; +import com.netflix.loadbalancer.BaseLoadBalancer; +import com.netflix.loadbalancer.ILoadBalancer; +import com.netflix.loadbalancer.Server; + import feign.codec.Decoder; import feign.codec.ErrorDecoder; -import static org.assertj.core.api.BDDAssertions.then; +import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; /** * Related to https://github.com/spring-cloud/spring-cloud-sleuth/issues/257 @@ -76,10 +78,12 @@ public class FeignClientServerErrorTests { @Autowired TestFeignInterface feignInterface; @Autowired TestFeignWithCustomConfInterface customConfFeignInterface; + @Autowired Listener listener; @Rule public OutputCapture capture = new OutputCapture(); @Before public void setup() { + this.listener.clear(); ExceptionUtils.setFail(true); } @@ -90,11 +94,17 @@ public class FeignClientServerErrorTests { } catch (HystrixRuntimeException e) { } - // ugly :/ waiting for rx thread to complete - Thread.sleep(100); - then(this.capture.toString()) - .doesNotContain("Tried to close span but it is not the current span"); - then(ExceptionUtils.getLastException()).isNull(); + Awaitility.await().until(() -> { + then(this.capture.toString()) + .doesNotContain("Tried to close span but it is not the current span"); + then(ExceptionUtils.getLastException()).isNull(); + then(new ListOfSpans(this.listener.getEvents())) + .hasASpanWithTagEqualTo(Span.SPAN_ERROR_TAG_NAME, + "Request processing failed; nested exception is java.lang.RuntimeException: Internal Error"); + then(new ListOfSpans(this.listener.getEvents())) + .hasASpanWithTagEqualTo(Span.SPAN_ERROR_TAG_NAME, + "Internal Error"); + }); } @Test @@ -104,11 +114,11 @@ public class FeignClientServerErrorTests { } catch (HystrixRuntimeException e) { } - // ugly :/ waiting for rx thread to complete - Thread.sleep(100); - then(this.capture.toString()) - .doesNotContain("Tried to close span but it is not the current span"); - then(ExceptionUtils.getLastException()).isNull(); + Awaitility.await().until(() -> { + then(this.capture.toString()) + .doesNotContain("Tried to close span but it is not the current span"); + then(ExceptionUtils.getLastException()).isNull(); + }); } @Test @@ -118,11 +128,10 @@ public class FeignClientServerErrorTests { } catch (HystrixRuntimeException e) { } - // ugly :/ waiting for rx thread to complete - Thread.sleep(100); - then(this.capture.toString()) - .doesNotContain("Tried to close span but it is not the current span"); - then(ExceptionUtils.getLastException()).isNull(); + Awaitility.await().until(() -> { + then(this.capture.toString()).doesNotContain("Tried to close span but it is not the current span"); + then(ExceptionUtils.getLastException()).isNull(); + }); } @Test @@ -132,11 +141,10 @@ public class FeignClientServerErrorTests { } catch (HystrixRuntimeException e) { } - // ugly :/ waiting for rx thread to complete - Thread.sleep(100); - then(this.capture.toString()) - .doesNotContain("Tried to close span but it is not the current span"); - then(ExceptionUtils.getLastException()).isNull(); + Awaitility.await().until(() -> { + then(this.capture.toString()).doesNotContain("Tried to close span but it is not the current span"); + then(ExceptionUtils.getLastException()).isNull(); + }); } @Test @@ -146,11 +154,10 @@ public class FeignClientServerErrorTests { } catch (HystrixRuntimeException e) { } - // ugly :/ waiting for rx thread to complete - Thread.sleep(100); - then(this.capture.toString()) - .doesNotContain("Tried to close span but it is not the current span"); - then(ExceptionUtils.getLastException()).isNull(); + Awaitility.await().until(() -> { + then(this.capture.toString()).doesNotContain("Tried to close span but it is not the current span"); + then(ExceptionUtils.getLastException()).isNull(); + }); } @Configuration @@ -226,7 +233,11 @@ public class FeignClientServerErrorTests { private List events = new ArrayList<>(); public List getEvents() { - return this.events; + return new ArrayList<>(this.events); + } + + public void clear() { + this.events.clear(); } @Override @@ -246,8 +257,7 @@ public class FeignClientServerErrorTests { @RequestHeader(Span.TRACE_ID_NAME) String traceId, @RequestHeader(Span.SPAN_ID_NAME) String spanId, @RequestHeader(Span.PARENT_ID_NAME) String parentId) { - return new ResponseEntity<>("internal error", - HttpStatus.INTERNAL_SERVER_ERROR); + throw new RuntimeException("Internal Error"); } @RequestMapping("/notfound") diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/util/ExceptionUtilsTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/util/ExceptionUtilsTest.java index 7820dd83f..13da88cc6 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/util/ExceptionUtilsTest.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/util/ExceptionUtilsTest.java @@ -50,4 +50,22 @@ public class ExceptionUtilsTest { then(e).isInstanceOf(IllegalStateException.class); } } + + @Test + public void should_print_error_message_when_there_is_one() throws Exception { + Throwable e = new RuntimeException("Foo"); + + String message = ExceptionUtils.getExceptionMessage(e); + + then(message).isEqualTo("Foo"); + } + + @Test + public void should_print_to_string_when_there_is_no_error() throws Exception { + Throwable e = new RuntimeException(); + + String message = ExceptionUtils.getExceptionMessage(e); + + then(message).isEqualTo("java.lang.RuntimeException"); + } } \ No newline at end of file From 7a042af1777531b052649d9aae8f6a26e4a009f9 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Wed, 2 Nov 2016 11:49:23 +0100 Subject: [PATCH 106/235] Fixing spring.sleuth.web.client.enabled (#437) Ensuring that disabling of modules work without this change when you disable Sleuth web client the context fails to load with this change it's working fine. We've moved beans around + the async web client will be turned off automatically if the sync one is also disabled fixes #433 --- .../web/TraceWebAutoConfiguration.java | 8 +++++- .../web/client/SleuthWebClientEnabled.java | 18 +++++++++++++ .../TraceWebAsyncClientAutoConfiguration.java | 20 ++++++-------- .../TraceWebClientAutoConfiguration.java | 16 +++-------- .../TraceFeignClientAutoConfiguration.java | 3 ++- .../instrument/zuul/TracePreZuulFilter.java | 7 +++-- .../zuul/TraceZuulAutoConfiguration.java | 12 ++++----- .../instrument/web/TraceWebDisabledTests.java | 27 +++++++++++++++++++ .../src/test/resources/logback.xml | 1 + 9 files changed, 76 insertions(+), 36 deletions(-) create mode 100644 spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/SleuthWebClientEnabled.java create mode 100644 spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceWebDisabledTests.java diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java index 39cd582f2..43117dae3 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java @@ -15,8 +15,8 @@ */ package org.springframework.cloud.sleuth.instrument.web; -import java.util.regex.Pattern; import javax.servlet.http.HttpServletRequest; +import java.util.regex.Pattern; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.annotation.Value; @@ -83,6 +83,12 @@ public class TraceWebAutoConfiguration { return new TraceSpringDataBeanPostProcessor(beanFactory); } + @Bean + @ConditionalOnMissingBean + public HttpTraceKeysInjector httpTraceKeysInjector(Tracer tracer, TraceKeys traceKeys) { + return new HttpTraceKeysInjector(tracer, traceKeys); + } + @Bean public FilterRegistrationBean traceWebFilter(Tracer tracer, TraceKeys traceKeys, SkipPatternProvider skipPatternProvider, SpanReporter spanReporter, diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/SleuthWebClientEnabled.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/SleuthWebClientEnabled.java new file mode 100644 index 000000000..3c127edee --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/SleuthWebClientEnabled.java @@ -0,0 +1,18 @@ +package org.springframework.cloud.sleuth.instrument.web.client; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; + +import java.lang.annotation.*; + +/** + * Helper annotation to enable Sleuth web client + * + * @author Marcin Grzejszczak + * @since 1.0.11 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE, ElementType.METHOD}) +@Documented +@ConditionalOnProperty(value = "spring.sleuth.web.client.enabled", matchIfMissing = true) +@interface SleuthWebClientEnabled { +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfiguration.java index 67c32afc2..d5fb8f2e6 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfiguration.java @@ -22,11 +22,10 @@ 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.SpanAccessor; import org.springframework.cloud.sleuth.SpanInjector; import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration; 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; @@ -46,21 +45,18 @@ import org.springframework.web.client.AsyncRestTemplate; * @since 1.0.0 */ @Configuration +@SleuthWebClientEnabled @ConditionalOnProperty(value = "spring.sleuth.web.async.client.enabled", matchIfMissing = true) @ConditionalOnClass(AsyncRestTemplate.class) -@ConditionalOnBean(SpanAccessor.class) -@AutoConfigureAfter(TraceAutoConfiguration.class) +@ConditionalOnBean(HttpTraceKeysInjector.class) +@AutoConfigureAfter(TraceWebAutoConfiguration.class) public class TraceWebAsyncClientAutoConfiguration { @Autowired Tracer tracer; - @Autowired - private HttpTraceKeysInjector httpTraceKeysInjector; - @Autowired - private SpanInjector spanInjector; - @Autowired(required = false) - private ClientHttpRequestFactory clientHttpRequestFactory; - @Autowired(required = false) - private AsyncClientHttpRequestFactory asyncClientHttpRequestFactory; + @Autowired private HttpTraceKeysInjector httpTraceKeysInjector; + @Autowired private SpanInjector spanInjector; + @Autowired(required = false) private ClientHttpRequestFactory clientHttpRequestFactory; + @Autowired(required = false) private AsyncClientHttpRequestFactory asyncClientHttpRequestFactory; private TraceAsyncClientHttpRequestFactoryWrapper traceAsyncClientHttpRequestFactory() { ClientHttpRequestFactory clientFactory = this.clientHttpRequestFactory; diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebClientAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebClientAutoConfiguration.java index d07efb11c..6585b0b13 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebClientAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebClientAutoConfiguration.java @@ -26,12 +26,10 @@ 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.boot.autoconfigure.condition.ConditionalOnProperty; 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.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; @@ -47,10 +45,10 @@ import org.springframework.web.client.RestTemplate; * @since 1.0.0 */ @Configuration -@ConditionalOnProperty(value = "spring.sleuth.web.client.enabled", matchIfMissing = true) +@SleuthWebClientEnabled @ConditionalOnClass(RestTemplate.class) -@ConditionalOnBean(Tracer.class) -@AutoConfigureAfter(TraceAutoConfiguration.class) +@ConditionalOnBean(HttpTraceKeysInjector.class) +@AutoConfigureAfter(TraceWebAutoConfiguration.class) public class TraceWebClientAutoConfiguration { @Bean @@ -66,12 +64,6 @@ public class TraceWebClientAutoConfiguration { return new HttpRequestInjector(); } - @Bean - @ConditionalOnMissingBean - public HttpTraceKeysInjector httpTraceKeysInjector(Tracer tracer, TraceKeys traceKeys) { - return new HttpTraceKeysInjector(tracer, traceKeys); - } - @Configuration protected static class TraceInterceptorConfiguration { diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientAutoConfiguration.java index 6595fefa2..664f57bc8 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientAutoConfiguration.java @@ -26,6 +26,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.cloud.netflix.feign.FeignAutoConfiguration; import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.instrument.hystrix.SleuthHystrixAutoConfiguration; +import org.springframework.cloud.sleuth.instrument.web.TraceWebAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Scope; @@ -45,7 +46,7 @@ import feign.Feign; @ConditionalOnClass(Client.class) @ConditionalOnBean(Tracer.class) @AutoConfigureBefore(FeignAutoConfiguration.class) -@AutoConfigureAfter(SleuthHystrixAutoConfiguration.class) +@AutoConfigureAfter({SleuthHystrixAutoConfiguration.class, TraceWebAutoConfiguration.class}) public class TraceFeignClientAutoConfiguration { @Bean diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TracePreZuulFilter.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TracePreZuulFilter.java index efe3c9113..f940093ed 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TracePreZuulFilter.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TracePreZuulFilter.java @@ -16,14 +16,10 @@ 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.Span; @@ -32,6 +28,9 @@ 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. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulAutoConfiguration.java index 65dc9a89e..546d24596 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulAutoConfiguration.java @@ -15,6 +15,10 @@ */ package org.springframework.cloud.sleuth.instrument.zuul; +import com.netflix.client.http.HttpRequest; +import com.netflix.zuul.ZuulFilter; +import com.netflix.zuul.context.RequestContext; + import org.springframework.beans.factory.BeanFactory; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; @@ -26,15 +30,11 @@ import org.springframework.cloud.netflix.ribbon.SpringClientFactory; 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.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 com.netflix.client.http.HttpRequest; -import com.netflix.zuul.ZuulFilter; -import com.netflix.zuul.context.RequestContext; - /** * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration Auto-configuration} * enables span information propagation when using Zuul. @@ -47,7 +47,7 @@ import com.netflix.zuul.context.RequestContext; @ConditionalOnWebApplication @ConditionalOnClass(ZuulFilter.class) @ConditionalOnBean(Tracer.class) -@AutoConfigureAfter(TraceAutoConfiguration.class) +@AutoConfigureAfter(TraceWebAutoConfiguration.class) public class TraceZuulAutoConfiguration { @Bean diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceWebDisabledTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceWebDisabledTests.java new file mode 100644 index 000000000..73919b566 --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceWebDisabledTests.java @@ -0,0 +1,27 @@ +package org.springframework.cloud.sleuth.instrument.web; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.IntegrationTest; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * @author Marcin Grzejszczak + */ +@RunWith(SpringJUnit4ClassRunner.class) +@IntegrationTest({ "spring.sleuth.web.enabled=true", "spring.sleuth.web.client.enabled=false"}) +@SpringApplicationConfiguration(classes = { TraceWebDisabledTests.Config.class }) +public class TraceWebDisabledTests { + + @Test + public void should_load_context() { + + } + + @Configuration + @EnableAutoConfiguration + public static class Config {} +} diff --git a/spring-cloud-sleuth-core/src/test/resources/logback.xml b/spring-cloud-sleuth-core/src/test/resources/logback.xml index fd0c9a35a..4d584ab3f 100644 --- a/spring-cloud-sleuth-core/src/test/resources/logback.xml +++ b/spring-cloud-sleuth-core/src/test/resources/logback.xml @@ -2,6 +2,7 @@ + From 7e84459f648c504b0ab3b0eca00d16c7defffd8c Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Wed, 2 Nov 2016 14:11:05 +0100 Subject: [PATCH 107/235] Added 1.1.x version --- benchmarks/pom.xml | 2 +- docs/pom.xml | 2 +- pom.xml | 2 +- spring-cloud-sleuth-core/pom.xml | 2 +- spring-cloud-sleuth-dependencies/pom.xml | 2 +- spring-cloud-sleuth-samples/pom.xml | 2 +- .../spring-cloud-sleuth-sample-feign/pom.xml | 2 +- .../spring-cloud-sleuth-sample-messaging/pom.xml | 2 +- .../spring-cloud-sleuth-sample-ribbon/pom.xml | 2 +- .../spring-cloud-sleuth-sample-stream/pom.xml | 2 +- .../spring-cloud-sleuth-sample-test-core/pom.xml | 2 +- .../spring-cloud-sleuth-sample-websocket/pom.xml | 2 +- .../spring-cloud-sleuth-sample-zipkin-stream/pom.xml | 2 +- .../spring-cloud-sleuth-sample-zipkin/pom.xml | 2 +- spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml | 2 +- spring-cloud-sleuth-stream/pom.xml | 2 +- spring-cloud-sleuth-zipkin-stream/pom.xml | 2 +- spring-cloud-sleuth-zipkin/pom.xml | 2 +- spring-cloud-starter-sleuth/pom.xml | 2 +- spring-cloud-starter-zipkin/pom.xml | 2 +- 20 files changed, 20 insertions(+), 20 deletions(-) diff --git a/benchmarks/pom.xml b/benchmarks/pom.xml index a0c924bd2..09c4438dc 100644 --- a/benchmarks/pom.xml +++ b/benchmarks/pom.xml @@ -17,7 +17,7 @@ Benchmarks Benchmarks (JMH) org.springframework.cloud - 1.0.11.BUILD-SNAPSHOT + 1.1.0.BUILD-SNAPSHOT benchmarks diff --git a/docs/pom.xml b/docs/pom.xml index f5d10ed17..28b5fd137 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.11.BUILD-SNAPSHOT + 1.1.0.BUILD-SNAPSHOT spring-cloud-sleuth-docs pom diff --git a/pom.xml b/pom.xml index 529abe4d1..7aa189141 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 spring-cloud-sleuth - 1.0.11.BUILD-SNAPSHOT + 1.1.0.BUILD-SNAPSHOT pom Spring Cloud Sleuth Spring Cloud Sleuth diff --git a/spring-cloud-sleuth-core/pom.xml b/spring-cloud-sleuth-core/pom.xml index 9b3aba544..9457b8e2c 100644 --- a/spring-cloud-sleuth-core/pom.xml +++ b/spring-cloud-sleuth-core/pom.xml @@ -11,7 +11,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.11.BUILD-SNAPSHOT + 1.1.0.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-dependencies/pom.xml b/spring-cloud-sleuth-dependencies/pom.xml index 7bf2ee6e7..1bd064da3 100644 --- a/spring-cloud-sleuth-dependencies/pom.xml +++ b/spring-cloud-sleuth-dependencies/pom.xml @@ -9,7 +9,7 @@ spring-cloud-sleuth-dependencies - 1.0.11.BUILD-SNAPSHOT + 1.1.0.BUILD-SNAPSHOT pom spring-cloud-sleuth-dependencies Spring Cloud Sleuth Dependencies diff --git a/spring-cloud-sleuth-samples/pom.xml b/spring-cloud-sleuth-samples/pom.xml index d87a6b0e9..9799a2c2e 100644 --- a/spring-cloud-sleuth-samples/pom.xml +++ b/spring-cloud-sleuth-samples/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.11.BUILD-SNAPSHOT + 1.1.0.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml index bcb5206e4..1158e08dd 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml @@ -20,7 +20,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.11.BUILD-SNAPSHOT + 1.1.0.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml index 2cbd44cbe..123a51d43 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.11.BUILD-SNAPSHOT + 1.1.0.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml index 71fd2e1df..26614f659 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.11.BUILD-SNAPSHOT + 1.1.0.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml index da1c6d34a..7edd1f78e 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.11.BUILD-SNAPSHOT + 1.1.0.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml index 47c6dfe61..113ef2e0b 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.11.BUILD-SNAPSHOT + 1.1.0.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml index 117a3a912..bb0b4d4ce 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.11.BUILD-SNAPSHOT + 1.1.0.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml index 67fb0764b..a21476c86 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.11.BUILD-SNAPSHOT + 1.1.0.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml index 202c6c482..19098d337 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml @@ -27,7 +27,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.11.BUILD-SNAPSHOT + 1.1.0.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml index 6d0a2a375..38062baf7 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.11.BUILD-SNAPSHOT + 1.1.0.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-stream/pom.xml b/spring-cloud-sleuth-stream/pom.xml index 9a756a4ca..68647ec05 100644 --- a/spring-cloud-sleuth-stream/pom.xml +++ b/spring-cloud-sleuth-stream/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.11.BUILD-SNAPSHOT + 1.1.0.BUILD-SNAPSHOT diff --git a/spring-cloud-sleuth-zipkin-stream/pom.xml b/spring-cloud-sleuth-zipkin-stream/pom.xml index 26a937f06..eaccc27c1 100644 --- a/spring-cloud-sleuth-zipkin-stream/pom.xml +++ b/spring-cloud-sleuth-zipkin-stream/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.11.BUILD-SNAPSHOT + 1.1.0.BUILD-SNAPSHOT diff --git a/spring-cloud-sleuth-zipkin/pom.xml b/spring-cloud-sleuth-zipkin/pom.xml index 82afa1f08..a5273fa3a 100644 --- a/spring-cloud-sleuth-zipkin/pom.xml +++ b/spring-cloud-sleuth-zipkin/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.11.BUILD-SNAPSHOT + 1.1.0.BUILD-SNAPSHOT .. diff --git a/spring-cloud-starter-sleuth/pom.xml b/spring-cloud-starter-sleuth/pom.xml index 5b6d58794..eceef2eb1 100644 --- a/spring-cloud-starter-sleuth/pom.xml +++ b/spring-cloud-starter-sleuth/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.11.BUILD-SNAPSHOT + 1.1.0.BUILD-SNAPSHOT .. spring-cloud-starter-sleuth diff --git a/spring-cloud-starter-zipkin/pom.xml b/spring-cloud-starter-zipkin/pom.xml index eb9236a63..092f00cf9 100644 --- a/spring-cloud-starter-zipkin/pom.xml +++ b/spring-cloud-starter-zipkin/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.11.BUILD-SNAPSHOT + 1.1.0.BUILD-SNAPSHOT .. spring-cloud-starter-zipkin From c655c3e4b2dfaf234b9607e130f33f322b234650 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Wed, 2 Nov 2016 14:14:07 +0100 Subject: [PATCH 108/235] Updated docs whitelisted branches --- docs/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/pom.xml b/docs/pom.xml index f5d10ed17..0927eea06 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -14,7 +14,7 @@ spring-cloud-sleuth - 1.0.x + 1.0.x,1.1.x ${basedir}/.. From bda706e67e5a45b9ada94ac7b688d76f6268ba94 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Wed, 2 Nov 2016 14:14:21 +0100 Subject: [PATCH 109/235] Updated docs whitelisted branches --- docs/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/pom.xml b/docs/pom.xml index 28b5fd137..44ae5a86b 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -14,7 +14,7 @@ spring-cloud-sleuth - 1.0.x + 1.0.x,1.1.x ${basedir}/.. From bb7430453e0f8e2ec580a5f3bb6b1abbf2907e25 Mon Sep 17 00:00:00 2001 From: Ryan Baxter Date: Thu, 3 Nov 2016 15:42:43 -0400 Subject: [PATCH 110/235] Make sure Feign does not retry when Sleuth is present --- .../web/client/feign/NeverRetry.java | 23 +++++++++++++++++++ .../web/client/feign/SleuthFeignBuilder.java | 5 ++-- .../feign/SleuthHystrixFeignBuilder.java | 5 ++-- 3 files changed, 27 insertions(+), 6 deletions(-) create mode 100644 spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/NeverRetry.java diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/NeverRetry.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/NeverRetry.java new file mode 100644 index 000000000..72753c713 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/NeverRetry.java @@ -0,0 +1,23 @@ +package org.springframework.cloud.sleuth.instrument.web.client.feign; + +import feign.RetryableException; +import feign.Retryer; + +/** + * This is essentially the same implementation of a Retryer that is in newer versions of + * Feign. For the 1.0.x stream we add it here. + * @author Ryan Baxter + */ +public class NeverRetry implements Retryer { + @Override + public void continueOrPropagate(RetryableException e) { + throw e; + } + + @Override + public Retryer clone() { + return this; + } + + public static final NeverRetry INSTANCE = new NeverRetry(); +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/SleuthFeignBuilder.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/SleuthFeignBuilder.java index b8486118e..6b34ab0c4 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/SleuthFeignBuilder.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/SleuthFeignBuilder.java @@ -16,9 +16,8 @@ package org.springframework.cloud.sleuth.instrument.web.client.feign; -import org.springframework.beans.factory.BeanFactory; - import feign.Feign; +import org.springframework.beans.factory.BeanFactory; /** * Contains {@link feign.Feign.Builder} implementation with tracing components @@ -33,7 +32,7 @@ final class SleuthFeignBuilder { private SleuthFeignBuilder() {} static Feign.Builder builder(BeanFactory beanFactory) { - return Feign.builder() + return Feign.builder().retryer(NeverRetry.INSTANCE) .client(new TraceFeignClient(beanFactory)); } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/SleuthHystrixFeignBuilder.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/SleuthHystrixFeignBuilder.java index 8b69adae8..69096e0f2 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/SleuthHystrixFeignBuilder.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/SleuthHystrixFeignBuilder.java @@ -16,10 +16,9 @@ package org.springframework.cloud.sleuth.instrument.web.client.feign; -import org.springframework.beans.factory.BeanFactory; - import feign.Feign; import feign.hystrix.HystrixFeign; +import org.springframework.beans.factory.BeanFactory; /** * Contains {@link Feign.Builder} implementation that delegates execution @@ -35,7 +34,7 @@ final class SleuthHystrixFeignBuilder { private SleuthHystrixFeignBuilder() {} static Feign.Builder builder(BeanFactory beanFactory) { - return HystrixFeign.builder() + return HystrixFeign.builder().retryer(NeverRetry.INSTANCE) .client(new TraceFeignClient(beanFactory)); } } From b47bfe7fcb31343bb1fe9e3bd79c8f2be0fa35e1 Mon Sep 17 00:00:00 2001 From: Adrian Cole Date: Sun, 6 Nov 2016 19:38:46 +0800 Subject: [PATCH 111/235] Updates to zipkin 1.14.4 (#441) * Support for MySQL 5.7 with SQL_MODE=ONLY_FULL_GROUP_BY * Includes support to store 128-bit trace ID (not yet implemented here) See https://github.com/openzipkin/b3-propagation/issues/6 --- spring-cloud-sleuth-dependencies/pom.xml | 4 ++-- spring-cloud-sleuth-samples/pom.xml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/spring-cloud-sleuth-dependencies/pom.xml b/spring-cloud-sleuth-dependencies/pom.xml index 7bf2ee6e7..fe99ab3a7 100644 --- a/spring-cloud-sleuth-dependencies/pom.xml +++ b/spring-cloud-sleuth-dependencies/pom.xml @@ -14,8 +14,8 @@ spring-cloud-sleuth-dependencies Spring Cloud Sleuth Dependencies - 1.13.1 - 0.6.1 + 1.14.4 + 0.6.6 diff --git a/spring-cloud-sleuth-samples/pom.xml b/spring-cloud-sleuth-samples/pom.xml index d87a6b0e9..0960508e2 100644 --- a/spring-cloud-sleuth-samples/pom.xml +++ b/spring-cloud-sleuth-samples/pom.xml @@ -59,12 +59,12 @@ io.zipkin.java zipkin - 1.13.1 + 1.14.4 io.zipkin.java zipkin-server - 1.13.1 + 1.14.4 From 6391bdaaec5ea3e5de0e538f5b877ac8a3fcaf8c Mon Sep 17 00:00:00 2001 From: Adrian Cole Date: Sun, 6 Nov 2016 19:38:46 +0800 Subject: [PATCH 112/235] Updates to zipkin 1.14.4 (#441) * Support for MySQL 5.7 with SQL_MODE=ONLY_FULL_GROUP_BY * Includes support to store 128-bit trace ID (not yet implemented here) See https://github.com/openzipkin/b3-propagation/issues/6 --- spring-cloud-sleuth-dependencies/pom.xml | 4 ++-- spring-cloud-sleuth-samples/pom.xml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/spring-cloud-sleuth-dependencies/pom.xml b/spring-cloud-sleuth-dependencies/pom.xml index 1bd064da3..d2f77d9c0 100644 --- a/spring-cloud-sleuth-dependencies/pom.xml +++ b/spring-cloud-sleuth-dependencies/pom.xml @@ -14,8 +14,8 @@ spring-cloud-sleuth-dependencies Spring Cloud Sleuth Dependencies - 1.13.1 - 0.6.1 + 1.14.4 + 0.6.6 diff --git a/spring-cloud-sleuth-samples/pom.xml b/spring-cloud-sleuth-samples/pom.xml index 9799a2c2e..5c41b2df4 100644 --- a/spring-cloud-sleuth-samples/pom.xml +++ b/spring-cloud-sleuth-samples/pom.xml @@ -59,12 +59,12 @@ io.zipkin.java zipkin - 1.13.1 + 1.14.4 io.zipkin.java zipkin-server - 1.13.1 + 1.14.4 From cd2b0fd0f4be38387cd53534dc9591b1917eb74c Mon Sep 17 00:00:00 2001 From: Christoffer Sawicki Date: Sun, 6 Nov 2016 20:03:34 +0100 Subject: [PATCH 113/235] Allow TraceWebAutoConfiguration to work without Web MVC Fixes #436 --- .../instrument/web/TraceWebAutoConfiguration.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java index 43117dae3..0c5e0a590 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java @@ -40,6 +40,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.util.StringUtils; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import static javax.servlet.DispatcherType.ASYNC; import static javax.servlet.DispatcherType.ERROR; @@ -63,7 +64,6 @@ import static javax.servlet.DispatcherType.REQUEST; @ConditionalOnBean(Tracer.class) @AutoConfigureAfter(TraceAutoConfiguration.class) @EnableConfigurationProperties(TraceKeys.class) -@Import(TraceWebMvcConfigurer.class) public class TraceWebAutoConfiguration { /** @@ -72,6 +72,16 @@ public class TraceWebAutoConfiguration { @Value("${spring.sleuth.web.skipPattern:}") private String skipPattern; + /** + * Nested config that configures Web MVC if it's present + * (without adding a runtime dependency to it) + */ + @Configuration + @ConditionalOnClass(WebMvcConfigurerAdapter.class) + @Import(TraceWebMvcConfigurer.class) + protected static class TraceWebMvcAutoConfiguration { + } + @Bean public TraceWebAspect traceWebAspect(Tracer tracer, SpanNamer spanNamer) { return new TraceWebAspect(tracer, spanNamer); From 0609396ea1343e64e464d48631a2095cece3b26e Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Thu, 3 Nov 2016 10:08:18 +0100 Subject: [PATCH 114/235] Updated libs --- pom.xml | 8 ++++---- spring-cloud-sleuth-core/pom.xml | 2 +- .../zuul/TraceRestClientRibbonCommandFactoryTest.java | 3 ++- spring-cloud-sleuth-dependencies/pom.xml | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/pom.xml b/pom.xml index 7aa189141..c87327474 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ org.springframework.cloud spring-cloud-build - 1.1.3.BUILD-SNAPSHOT + 1.2.2.BUILD-SNAPSHOT @@ -229,10 +229,10 @@ 1.8 2.19.1 2.17 - 1.1.3.BUILD-SNAPSHOT - 1.1.3.BUILD-SNAPSHOT + 1.2.2.BUILD-SNAPSHOT + 1.1.6.BUILD-SNAPSHOT Brooklyn.BUILD-SNAPSHOT - 1.1.6.BUILD-SNAPSHOT + 1.2.3.BUILD-SNAPSHOT diff --git a/spring-cloud-sleuth-core/pom.xml b/spring-cloud-sleuth-core/pom.xml index 9457b8e2c..0d99a353b 100644 --- a/spring-cloud-sleuth-core/pom.xml +++ b/spring-cloud-sleuth-core/pom.xml @@ -61,7 +61,7 @@ true - com.netflix.feign + io.github.openfeign feign-core true diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRestClientRibbonCommandFactoryTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRestClientRibbonCommandFactoryTest.java index 26ebf11fb..f85932401 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRestClientRibbonCommandFactoryTest.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRestClientRibbonCommandFactoryTest.java @@ -34,6 +34,7 @@ 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.trace.TestSpanContextHolder; +import org.springframework.http.HttpHeaders; import static org.assertj.core.api.BDDAssertions.then; import static org.mockito.BDDMockito.given; @@ -105,6 +106,6 @@ public class TraceRestClientRibbonCommandFactoryTest { private RibbonCommandContext ribbonCommandContext() { return new RibbonCommandContext("serviceId", "GET", "http://localhost:1234/foo", - false, null, null, null); + false, new HttpHeaders(), new HttpHeaders(), null); } } \ No newline at end of file diff --git a/spring-cloud-sleuth-dependencies/pom.xml b/spring-cloud-sleuth-dependencies/pom.xml index d2f77d9c0..25666c929 100644 --- a/spring-cloud-sleuth-dependencies/pom.xml +++ b/spring-cloud-sleuth-dependencies/pom.xml @@ -5,7 +5,7 @@ spring-cloud-dependencies-parent org.springframework.cloud - 1.1.3.BUILD-SNAPSHOT + 1.2.2.BUILD-SNAPSHOT spring-cloud-sleuth-dependencies From e0a20b205fc8499bf5924837b00817761587cab0 Mon Sep 17 00:00:00 2001 From: Christoffer Sawicki Date: Sun, 6 Nov 2016 20:03:34 +0100 Subject: [PATCH 115/235] Allow TraceWebAutoConfiguration to work without Web MVC Fixes #436 --- .../instrument/web/TraceWebAutoConfiguration.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java index 43117dae3..0c5e0a590 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java @@ -40,6 +40,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.util.StringUtils; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import static javax.servlet.DispatcherType.ASYNC; import static javax.servlet.DispatcherType.ERROR; @@ -63,7 +64,6 @@ import static javax.servlet.DispatcherType.REQUEST; @ConditionalOnBean(Tracer.class) @AutoConfigureAfter(TraceAutoConfiguration.class) @EnableConfigurationProperties(TraceKeys.class) -@Import(TraceWebMvcConfigurer.class) public class TraceWebAutoConfiguration { /** @@ -72,6 +72,16 @@ public class TraceWebAutoConfiguration { @Value("${spring.sleuth.web.skipPattern:}") private String skipPattern; + /** + * Nested config that configures Web MVC if it's present + * (without adding a runtime dependency to it) + */ + @Configuration + @ConditionalOnClass(WebMvcConfigurerAdapter.class) + @Import(TraceWebMvcConfigurer.class) + protected static class TraceWebMvcAutoConfiguration { + } + @Bean public TraceWebAspect traceWebAspect(Tracer tracer, SpanNamer spanNamer) { return new TraceWebAspect(tracer, spanNamer); From 44cc2b5b01eefcecd0f0ca13808a8bbf58877ec8 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Sun, 6 Nov 2016 10:06:17 +0100 Subject: [PATCH 116/235] Added a test for tracing the tracer --- spring-cloud-sleuth-stream/pom.xml | 5 + .../stream/StreamSpanListenerTests.java | 8 +- .../TraceIgnoringChannelInterceptorTests.java | 105 ++++++++++++++++++ 3 files changed, 116 insertions(+), 2 deletions(-) create mode 100644 spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/TraceIgnoringChannelInterceptorTests.java diff --git a/spring-cloud-sleuth-stream/pom.xml b/spring-cloud-sleuth-stream/pom.xml index 68647ec05..403e28622 100644 --- a/spring-cloud-sleuth-stream/pom.xml +++ b/spring-cloud-sleuth-stream/pom.xml @@ -59,6 +59,11 @@ spring-boot-starter-test test + + org.aspectj + aspectjweaver + test + diff --git a/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/StreamSpanListenerTests.java b/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/StreamSpanListenerTests.java index cf41a4c9c..c99df54f0 100644 --- a/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/StreamSpanListenerTests.java +++ b/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/StreamSpanListenerTests.java @@ -84,8 +84,10 @@ public class StreamSpanListenerTests { @Test public void acquireAndRelease() { Span context = this.tracer.createSpan("http:foo"); + this.tracer.close(context); - assertEquals(1, this.test.spans.size()); + + assertThat(this.test.spans).hasSize(1); } @Test @@ -96,8 +98,10 @@ public class StreamSpanListenerTests { context.logEvent(Span.CLIENT_SEND); logServerReceived(parent); logServerSent(this.spanReporter, parent); + this.tracer.close(context); - assertEquals(2, this.test.spans.size()); + + assertThat(this.test.spans).hasSize(2); } void logServerReceived(Span parent) { diff --git a/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/TraceIgnoringChannelInterceptorTests.java b/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/TraceIgnoringChannelInterceptorTests.java new file mode 100644 index 000000000..3d7e395ea --- /dev/null +++ b/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/TraceIgnoringChannelInterceptorTests.java @@ -0,0 +1,105 @@ +/* + * Copyright 2013-2015 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.stream; + +import javax.annotation.PostConstruct; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.cloud.sleuth.Sampler; +import org.springframework.cloud.sleuth.Span; +import org.springframework.cloud.sleuth.Tracer; +import org.springframework.cloud.sleuth.sampler.AlwaysSampler; +import org.springframework.cloud.sleuth.util.ExceptionUtils; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.integration.core.MessagingTemplate; +import org.springframework.integration.support.MessageBuilder; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import static org.assertj.core.api.BDDAssertions.then; + +/** + * @author Dave Syer + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = TraceIgnoringChannelInterceptorTests.App.class) +@DirtiesContext +public class TraceIgnoringChannelInterceptorTests { + + @Autowired Tracer tracer; + @Autowired App app; + @Autowired MessagingTemplate messagingTemplate; + + @Before + public void init() { + this.app.clear(); + } + + @After + public void close() { + then(ExceptionUtils.getLastException()).isNull(); + this.app.clear(); + } + + @Test + public void shouldNotTraceTheTracer() { + this.messagingTemplate.send(MessageBuilder.withPayload("hi").build()); + + Spans spans = this.app.listener.poll(); + + then(spans).isNull(); + then(this.tracer.getCurrentSpan()).isNull(); + } + + @Configuration + @EnableAutoConfiguration + static class App { + + private final BlockingQueue spans = new LinkedBlockingQueue<>(); + + @Autowired StreamSpanReporter listener; + + @Bean MessagingTemplate messagingTemplate(SleuthSource sleuthSource) { + return new MessagingTemplate(sleuthSource.output()); + } + + @Bean Sampler alwaysSampler() { + return new AlwaysSampler(); + } + + + @PostConstruct + public void init() { + this.listener.setQueue(this.spans); + } + + public void clear() { + this.spans.clear(); + } + } +} From 57f95aac3e26c97e996686cacc516afe11f59633 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Sun, 6 Nov 2016 10:06:17 +0100 Subject: [PATCH 117/235] Added a test for tracing the tracer --- spring-cloud-sleuth-stream/pom.xml | 5 + .../stream/StreamSpanListenerTests.java | 8 +- .../TraceIgnoringChannelInterceptorTests.java | 105 ++++++++++++++++++ 3 files changed, 116 insertions(+), 2 deletions(-) create mode 100644 spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/TraceIgnoringChannelInterceptorTests.java diff --git a/spring-cloud-sleuth-stream/pom.xml b/spring-cloud-sleuth-stream/pom.xml index 9a756a4ca..a781bd524 100644 --- a/spring-cloud-sleuth-stream/pom.xml +++ b/spring-cloud-sleuth-stream/pom.xml @@ -59,6 +59,11 @@ spring-boot-starter-test test + + org.aspectj + aspectjweaver + test + diff --git a/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/StreamSpanListenerTests.java b/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/StreamSpanListenerTests.java index cf41a4c9c..c99df54f0 100644 --- a/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/StreamSpanListenerTests.java +++ b/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/StreamSpanListenerTests.java @@ -84,8 +84,10 @@ public class StreamSpanListenerTests { @Test public void acquireAndRelease() { Span context = this.tracer.createSpan("http:foo"); + this.tracer.close(context); - assertEquals(1, this.test.spans.size()); + + assertThat(this.test.spans).hasSize(1); } @Test @@ -96,8 +98,10 @@ public class StreamSpanListenerTests { context.logEvent(Span.CLIENT_SEND); logServerReceived(parent); logServerSent(this.spanReporter, parent); + this.tracer.close(context); - assertEquals(2, this.test.spans.size()); + + assertThat(this.test.spans).hasSize(2); } void logServerReceived(Span parent) { diff --git a/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/TraceIgnoringChannelInterceptorTests.java b/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/TraceIgnoringChannelInterceptorTests.java new file mode 100644 index 000000000..3d7e395ea --- /dev/null +++ b/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/TraceIgnoringChannelInterceptorTests.java @@ -0,0 +1,105 @@ +/* + * Copyright 2013-2015 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.stream; + +import javax.annotation.PostConstruct; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.cloud.sleuth.Sampler; +import org.springframework.cloud.sleuth.Span; +import org.springframework.cloud.sleuth.Tracer; +import org.springframework.cloud.sleuth.sampler.AlwaysSampler; +import org.springframework.cloud.sleuth.util.ExceptionUtils; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.integration.core.MessagingTemplate; +import org.springframework.integration.support.MessageBuilder; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import static org.assertj.core.api.BDDAssertions.then; + +/** + * @author Dave Syer + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = TraceIgnoringChannelInterceptorTests.App.class) +@DirtiesContext +public class TraceIgnoringChannelInterceptorTests { + + @Autowired Tracer tracer; + @Autowired App app; + @Autowired MessagingTemplate messagingTemplate; + + @Before + public void init() { + this.app.clear(); + } + + @After + public void close() { + then(ExceptionUtils.getLastException()).isNull(); + this.app.clear(); + } + + @Test + public void shouldNotTraceTheTracer() { + this.messagingTemplate.send(MessageBuilder.withPayload("hi").build()); + + Spans spans = this.app.listener.poll(); + + then(spans).isNull(); + then(this.tracer.getCurrentSpan()).isNull(); + } + + @Configuration + @EnableAutoConfiguration + static class App { + + private final BlockingQueue spans = new LinkedBlockingQueue<>(); + + @Autowired StreamSpanReporter listener; + + @Bean MessagingTemplate messagingTemplate(SleuthSource sleuthSource) { + return new MessagingTemplate(sleuthSource.output()); + } + + @Bean Sampler alwaysSampler() { + return new AlwaysSampler(); + } + + + @PostConstruct + public void init() { + this.listener.setQueue(this.spans); + } + + public void clear() { + this.spans.clear(); + } + } +} From cec661e8c674e867aaf871ab3b5a347186549f19 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Sun, 6 Nov 2016 22:45:06 +0100 Subject: [PATCH 118/235] Removed wrong imports --- .../sleuth/stream/TraceIgnoringChannelInterceptorTests.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/TraceIgnoringChannelInterceptorTests.java b/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/TraceIgnoringChannelInterceptorTests.java index 3d7e395ea..01485ff4d 100644 --- a/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/TraceIgnoringChannelInterceptorTests.java +++ b/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/TraceIgnoringChannelInterceptorTests.java @@ -26,8 +26,6 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.cloud.sleuth.Sampler; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.Tracer; From 3ad22cd16364c9d5f97b2f0723b6508c6f996d10 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Sun, 6 Nov 2016 22:45:29 +0100 Subject: [PATCH 119/235] Removed wrong imports --- .../sleuth/stream/TraceIgnoringChannelInterceptorTests.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/TraceIgnoringChannelInterceptorTests.java b/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/TraceIgnoringChannelInterceptorTests.java index 3d7e395ea..01485ff4d 100644 --- a/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/TraceIgnoringChannelInterceptorTests.java +++ b/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/TraceIgnoringChannelInterceptorTests.java @@ -26,8 +26,6 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.cloud.sleuth.Sampler; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.Tracer; From 81d5361561b76d4472ae84bf3246a27b4f2d6e11 Mon Sep 17 00:00:00 2001 From: Adrian Cole Date: Mon, 7 Nov 2016 06:58:08 -0800 Subject: [PATCH 120/235] Ensures Span.timestamp/duration are not reported on remote spans (#443) This ensures spans that have remote set to true don't send Span.timestamp/duration. In the RPC span model, the client owns the timestamp and duration of the span. If we were propagated an id, we can assume that we shouldn't report timestamp or duration, rather let the client do that. Worst case we were propagated an unreported ID and Zipkin backfills timestamp and duration. --- .../MessagingApplicationTests.java | 2 +- .../stream/ConvertToZipkinSpanList.java | 14 ++++-- .../stream/ConvertToZipkinSpanListTests.java | 28 +++++++++-- .../stream/ZipkinMessageListenerTests.java | 33 ++++++++++--- .../sleuth/zipkin/ZipkinSpanListener.java | 12 +++-- .../zipkin/ZipkinSpanListenerTests.java | 49 +++++++++++++------ 6 files changed, 106 insertions(+), 32 deletions(-) diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/src/test/java/integration/MessagingApplicationTests.java b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/src/test/java/integration/MessagingApplicationTests.java index 81f5174e0..b8ccb3123 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/src/test/java/integration/MessagingApplicationTests.java +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/src/test/java/integration/MessagingApplicationTests.java @@ -114,7 +114,7 @@ public class MessagingApplicationTests extends AbstractIntegrationTest { Optional eventReceivedSpan = findSpanWithAnnotation(Constants.CLIENT_RECV); Optional lastHttpSpansParent = findLastHttpSpansParent(); // "http:/parent/" -> "home" -> "message:messages" -> "http:/foo" (CS + CR) -> "http:/foo" (SS) -> "foo" - Collections.sort(this.integrationTestSpanCollector.hashedSpans, (s1, s2) -> s1.timestamp.compareTo(s2.timestamp)); + Collections.sort(this.integrationTestSpanCollector.hashedSpans); thenAllSpansArePresent(firstHttpSpan, eventSpans, lastHttpSpansParent, eventSentSpan, eventReceivedSpan); then(this.integrationTestSpanCollector.hashedSpans).as("There were 6 spans").hasSize(6); log.info("Checking the parent child structure"); diff --git a/spring-cloud-sleuth-zipkin-stream/src/main/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanList.java b/spring-cloud-sleuth-zipkin-stream/src/main/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanList.java index 16fc039d8..7a1e2bd47 100644 --- a/spring-cloud-sleuth-zipkin-stream/src/main/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanList.java +++ b/spring-cloud-sleuth-zipkin-stream/src/main/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanList.java @@ -89,9 +89,15 @@ final class ConvertToZipkinSpanList { if (hasClientSend(span)) { ensureServerAddr(span, zipkinSpan, ep); } - zipkinSpan.timestamp(span.getBegin() * 1000); - if (!span.isRunning()) { // duration is authoritative, only write when the span stopped - zipkinSpan.duration(calculateDurationInMicros(span)); + // In the RPC span model, the client owns the timestamp and duration of the span. If we + // were propagated an id, we can assume that we shouldn't report timestamp or duration, + // rather let the client do that. Worst case we were propagated an unreported ID and + // Zipkin backfills timestamp and duration. + if (!span.isRemote()) { + zipkinSpan.timestamp(span.getBegin() * 1000); + if (!span.isRunning()) { // duration is authoritative, only write when the span stopped + zipkinSpan.duration(calculateDurationInMicros(span)); + } } zipkinSpan.traceId(span.getTraceId()); if (span.getParents().size() > 0) { @@ -174,4 +180,4 @@ final class ConvertToZipkinSpanList { } return null; } -} \ No newline at end of file +} diff --git a/spring-cloud-sleuth-zipkin-stream/src/test/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanListTests.java b/spring-cloud-sleuth-zipkin-stream/src/test/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanListTests.java index 09635824d..4aa3cac91 100644 --- a/spring-cloud-sleuth-zipkin-stream/src/test/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanListTests.java +++ b/spring-cloud-sleuth-zipkin-stream/src/test/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanListTests.java @@ -155,7 +155,7 @@ public class ConvertToZipkinSpanListTests { /** Zipkin's duration should only be set when the span is finished. */ @Test public void doesntSetDurationWhenStillRunning() { - Span running = Span.builder().traceId(1L).name("http:parent").remote(true).build(); + Span running = Span.builder().traceId(1L).name("http:child").build(); Spans spans = new Spans(this.host, Collections.singletonList(running)); zipkin.Span result = ConvertToZipkinSpanList.convert(spans).get(0); @@ -165,9 +165,31 @@ public class ConvertToZipkinSpanListTests { .isNull(); } + /** + * In the RPC span model, the client owns the timestamp and duration of the span. If we + * were propagated an id, we can assume that we shouldn't report timestamp or duration, + * rather let the client do that. Worst case we were propagated an unreported ID and + * Zipkin backfills timestamp and duration. + */ + @Test + public void doesntSetTimestampOrDurationWhenRemote() { + Span span = span("foo", true); + Spans spans = new Spans(this.host, Collections.singletonList(span)); + zipkin.Span result = ConvertToZipkinSpanList.convert(spans).get(0); + + assertThat(result.timestamp) + .isNull(); + assertThat(result.duration) + .isNull(); + } + Span span(String name) { + return span(name, false); + } + + Span span(String name, boolean remote) { Long id = new Random().nextLong(); - return new Span(1, 3, "message:" + name, id, Collections.emptyList(), id, true, true, + return new Span(1, 3, "message:" + name, id, Collections.emptyList(), id, remote, true, "process"); } -} \ No newline at end of file +} diff --git a/spring-cloud-sleuth-zipkin-stream/src/test/java/org/springframework/cloud/sleuth/zipkin/stream/ZipkinMessageListenerTests.java b/spring-cloud-sleuth-zipkin-stream/src/test/java/org/springframework/cloud/sleuth/zipkin/stream/ZipkinMessageListenerTests.java index cab8523df..31d94b3ec 100644 --- a/spring-cloud-sleuth-zipkin-stream/src/test/java/org/springframework/cloud/sleuth/zipkin/stream/ZipkinMessageListenerTests.java +++ b/spring-cloud-sleuth-zipkin-stream/src/test/java/org/springframework/cloud/sleuth/zipkin/stream/ZipkinMessageListenerTests.java @@ -34,18 +34,37 @@ public class ZipkinMessageListenerTests { .ipv4(1 << 24 | 2 << 16 | 3 << 8 | 4) .port(8080).build(); - /** Sleuth timestamps are millisecond granularity while zipkin is microsecond. */ + /** + * In the RPC span model, the client owns the timestamp and duration of the span. If we + * were propagated an id, we can assume that we shouldn't report timestamp or duration, + * rather let the client do that. Worst case we were propagated an unreported ID and + * Zipkin backfills timestamp and duration. + */ @Test - public void convertsTimestampAndDurationToMicroseconds() { - long start = System.currentTimeMillis(); - this.span.logEvent("hystrix/retry"); // System.currentTimeMillis - + public void doesntSetTimestampOrDurationWhenRemote() { + this.span.stop(); zipkin.Span result = ConvertToZipkinSpanList.convert(this.span, this.host); assertThat(result.timestamp) - .isEqualTo(this.span.getBegin() * 1000); + .isNull(); assertThat(result.duration) - .isEqualTo((this.span.getEnd() - this.span.getBegin()) * 1000); + .isNull(); + } + + /** Sleuth timestamps are millisecond granularity while zipkin is microsecond. */ + @Test + public void convertsTimestampAndDurationToMicroseconds() { + Span span = new Span(1, 3, "http:name", 1L, Collections.emptyList(), 2L, false, true, + "process"); + long start = System.currentTimeMillis(); + span.logEvent("hystrix/retry"); // System.currentTimeMillis + + zipkin.Span result = ConvertToZipkinSpanList.convert(span, this.host); + + assertThat(result.timestamp) + .isEqualTo(span.getBegin() * 1000); + assertThat(result.duration) + .isEqualTo((span.getEnd() - span.getBegin()) * 1000); assertThat(result.annotations.get(0).timestamp) .isGreaterThanOrEqualTo(start * 1000) .isLessThanOrEqualTo(System.currentTimeMillis() * 1000); diff --git a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListener.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListener.java index 07815baab..116a876e4 100644 --- a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListener.java +++ b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListener.java @@ -85,9 +85,15 @@ public class ZipkinSpanListener implements SpanReporter { if (hasClientSend(span)) { ensureServerAddr(span, zipkinSpan); } - zipkinSpan.timestamp(span.getBegin() * 1000L); - if (!span.isRunning()) { // duration is authoritative, only write when the span stopped - zipkinSpan.duration(calculateDurationInMicros(span)); + // In the RPC span model, the client owns the timestamp and duration of the span. If we + // were propagated an id, we can assume that we shouldn't report timestamp or duration, + // rather let the client do that. Worst case we were propagated an unreported ID and + // Zipkin backfills timestamp and duration. + if (!span.isRemote()) { + zipkinSpan.timestamp(span.getBegin() * 1000L); + if (!span.isRunning()) { // duration is authoritative, only write when the span stopped + zipkinSpan.duration(calculateDurationInMicros(span)); + } } zipkinSpan.traceId(span.getTraceId()); if (span.getParents().size() > 0) { diff --git a/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListenerTests.java b/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListenerTests.java index 58f016e97..a99e4d267 100644 --- a/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListenerTests.java +++ b/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListenerTests.java @@ -17,6 +17,7 @@ package org.springframework.cloud.sleuth.zipkin; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import javax.annotation.PostConstruct; @@ -64,16 +65,17 @@ public class ZipkinSpanListenerTests { /** Sleuth timestamps are millisecond granularity while zipkin is microsecond. */ @Test public void convertsTimestampToMicrosecondsAndSetsDurationToAccumulatedMicros() { + Span span = Span.builder().traceId(1L).name("http:api").build(); long start = System.currentTimeMillis(); - this.parent.logEvent("hystrix/retry"); // System.currentTimeMillis - this.parent.stop(); + span.logEvent("hystrix/retry"); // System.currentTimeMillis + span.stop(); - zipkin.Span result = this.spanReporter.convert(this.parent); + zipkin.Span result = this.spanReporter.convert(span); assertThat(result.timestamp) - .isEqualTo(this.parent.getBegin() * 1000); + .isEqualTo(span.getBegin() * 1000); assertThat(result.duration) - .isEqualTo(this.parent.getAccumulatedMicros()); + .isEqualTo(span.getAccumulatedMicros()); assertThat(result.annotations.get(0).timestamp) .isGreaterThanOrEqualTo(start * 1000) .isLessThanOrEqualTo(System.currentTimeMillis() * 1000); @@ -82,29 +84,31 @@ public class ZipkinSpanListenerTests { @Test public void setsTheDurationToTheDifferenceBetweenCRandCS() throws InterruptedException { - this.parent.logEvent(Span.CLIENT_SEND); + Span span = Span.builder().traceId(1L).name("http:api").build(); + span.logEvent(Span.CLIENT_SEND); Thread.sleep(10); - this.parent.logEvent(Span.CLIENT_RECV); + span.logEvent(Span.CLIENT_RECV); Thread.sleep(20); - this.parent.stop(); + span.stop(); - zipkin.Span result = this.spanReporter.convert(this.parent); + zipkin.Span result = this.spanReporter.convert(span); assertThat(result.timestamp) - .isEqualTo(this.parent.getBegin() * 1000); - long clientSendTimestamp = this.parent.logs().stream().filter(log -> Span.CLIENT_SEND.equals(log.getEvent())) + .isEqualTo(span.getBegin() * 1000); + long clientSendTimestamp = span.logs().stream().filter(log -> Span.CLIENT_SEND.equals(log.getEvent())) .findFirst().get().getTimestamp(); - long clientRecvTimestamp = this.parent.logs().stream().filter(log -> Span.CLIENT_RECV.equals(log.getEvent())) + long clientRecvTimestamp = span.logs().stream().filter(log -> Span.CLIENT_RECV.equals(log.getEvent())) .findFirst().get().getTimestamp(); assertThat(result.duration) - .isNotEqualTo(this.parent.getAccumulatedMicros()) + .isNotEqualTo(span.getAccumulatedMicros()) .isEqualTo((clientRecvTimestamp - clientSendTimestamp) * 1000); } /** Zipkin's duration should only be set when the span is finished. */ @Test public void doesntSetDurationWhenStillRunning() { - zipkin.Span result = this.spanReporter.convert(this.parent); + Span span = Span.builder().traceId(1L).name("http:api").build(); + zipkin.Span result = this.spanReporter.convert(span); assertThat(result.timestamp) .isGreaterThan(0); // sanity check it did start @@ -112,6 +116,23 @@ public class ZipkinSpanListenerTests { .isNull(); } + /** + * In the RPC span model, the client owns the timestamp and duration of the span. If we + * were propagated an id, we can assume that we shouldn't report timestamp or duration, + * rather let the client do that. Worst case we were propagated an unreported ID and + * Zipkin backfills timestamp and duration. + */ + @Test + public void doesntSetTimestampOrDurationWhenRemote() { + this.parent.stop(); + zipkin.Span result = this.spanReporter.convert(this.parent); + + assertThat(result.timestamp) + .isNull(); + assertThat(result.duration) + .isNull(); + } + /** Sleuth host corresponds to annotation/binaryAnnotation.host in zipkin. */ @Test public void annotationsIncludeHost() { From 903a9da226a8218e2baa4b0b89298affb6f2d4ef Mon Sep 17 00:00:00 2001 From: Adrian Cole Date: Sun, 6 Nov 2016 21:35:20 +0800 Subject: [PATCH 121/235] Ensures Span.timestamp/duration are not reported on remote spans This ensures spans that have remote set to true don't send Span.timestamp/duration. In the RPC span model, the client owns the timestamp and duration of the span. If we were propagated an id, we can assume that we shouldn't report timestamp or duration, rather let the client do that. Worst case we were propagated an unreported ID and Zipkin backfills timestamp and duration. See https://github.com/openzipkin/openzipkin.github.io/issues/49 --- .../stream/ConvertToZipkinSpanList.java | 14 ++++-- .../stream/ConvertToZipkinSpanListTests.java | 28 +++++++++-- .../sleuth/zipkin/ZipkinSpanListener.java | 12 +++-- .../zipkin/ZipkinSpanListenerTests.java | 49 +++++++++++++------ 4 files changed, 79 insertions(+), 24 deletions(-) diff --git a/spring-cloud-sleuth-zipkin-stream/src/main/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanList.java b/spring-cloud-sleuth-zipkin-stream/src/main/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanList.java index 16fc039d8..7a1e2bd47 100644 --- a/spring-cloud-sleuth-zipkin-stream/src/main/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanList.java +++ b/spring-cloud-sleuth-zipkin-stream/src/main/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanList.java @@ -89,9 +89,15 @@ final class ConvertToZipkinSpanList { if (hasClientSend(span)) { ensureServerAddr(span, zipkinSpan, ep); } - zipkinSpan.timestamp(span.getBegin() * 1000); - if (!span.isRunning()) { // duration is authoritative, only write when the span stopped - zipkinSpan.duration(calculateDurationInMicros(span)); + // In the RPC span model, the client owns the timestamp and duration of the span. If we + // were propagated an id, we can assume that we shouldn't report timestamp or duration, + // rather let the client do that. Worst case we were propagated an unreported ID and + // Zipkin backfills timestamp and duration. + if (!span.isRemote()) { + zipkinSpan.timestamp(span.getBegin() * 1000); + if (!span.isRunning()) { // duration is authoritative, only write when the span stopped + zipkinSpan.duration(calculateDurationInMicros(span)); + } } zipkinSpan.traceId(span.getTraceId()); if (span.getParents().size() > 0) { @@ -174,4 +180,4 @@ final class ConvertToZipkinSpanList { } return null; } -} \ No newline at end of file +} diff --git a/spring-cloud-sleuth-zipkin-stream/src/test/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanListTests.java b/spring-cloud-sleuth-zipkin-stream/src/test/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanListTests.java index 09635824d..4aa3cac91 100644 --- a/spring-cloud-sleuth-zipkin-stream/src/test/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanListTests.java +++ b/spring-cloud-sleuth-zipkin-stream/src/test/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanListTests.java @@ -155,7 +155,7 @@ public class ConvertToZipkinSpanListTests { /** Zipkin's duration should only be set when the span is finished. */ @Test public void doesntSetDurationWhenStillRunning() { - Span running = Span.builder().traceId(1L).name("http:parent").remote(true).build(); + Span running = Span.builder().traceId(1L).name("http:child").build(); Spans spans = new Spans(this.host, Collections.singletonList(running)); zipkin.Span result = ConvertToZipkinSpanList.convert(spans).get(0); @@ -165,9 +165,31 @@ public class ConvertToZipkinSpanListTests { .isNull(); } + /** + * In the RPC span model, the client owns the timestamp and duration of the span. If we + * were propagated an id, we can assume that we shouldn't report timestamp or duration, + * rather let the client do that. Worst case we were propagated an unreported ID and + * Zipkin backfills timestamp and duration. + */ + @Test + public void doesntSetTimestampOrDurationWhenRemote() { + Span span = span("foo", true); + Spans spans = new Spans(this.host, Collections.singletonList(span)); + zipkin.Span result = ConvertToZipkinSpanList.convert(spans).get(0); + + assertThat(result.timestamp) + .isNull(); + assertThat(result.duration) + .isNull(); + } + Span span(String name) { + return span(name, false); + } + + Span span(String name, boolean remote) { Long id = new Random().nextLong(); - return new Span(1, 3, "message:" + name, id, Collections.emptyList(), id, true, true, + return new Span(1, 3, "message:" + name, id, Collections.emptyList(), id, remote, true, "process"); } -} \ No newline at end of file +} diff --git a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListener.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListener.java index 07815baab..116a876e4 100644 --- a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListener.java +++ b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListener.java @@ -85,9 +85,15 @@ public class ZipkinSpanListener implements SpanReporter { if (hasClientSend(span)) { ensureServerAddr(span, zipkinSpan); } - zipkinSpan.timestamp(span.getBegin() * 1000L); - if (!span.isRunning()) { // duration is authoritative, only write when the span stopped - zipkinSpan.duration(calculateDurationInMicros(span)); + // In the RPC span model, the client owns the timestamp and duration of the span. If we + // were propagated an id, we can assume that we shouldn't report timestamp or duration, + // rather let the client do that. Worst case we were propagated an unreported ID and + // Zipkin backfills timestamp and duration. + if (!span.isRemote()) { + zipkinSpan.timestamp(span.getBegin() * 1000L); + if (!span.isRunning()) { // duration is authoritative, only write when the span stopped + zipkinSpan.duration(calculateDurationInMicros(span)); + } } zipkinSpan.traceId(span.getTraceId()); if (span.getParents().size() > 0) { diff --git a/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListenerTests.java b/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListenerTests.java index 58f016e97..a99e4d267 100644 --- a/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListenerTests.java +++ b/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListenerTests.java @@ -17,6 +17,7 @@ package org.springframework.cloud.sleuth.zipkin; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import javax.annotation.PostConstruct; @@ -64,16 +65,17 @@ public class ZipkinSpanListenerTests { /** Sleuth timestamps are millisecond granularity while zipkin is microsecond. */ @Test public void convertsTimestampToMicrosecondsAndSetsDurationToAccumulatedMicros() { + Span span = Span.builder().traceId(1L).name("http:api").build(); long start = System.currentTimeMillis(); - this.parent.logEvent("hystrix/retry"); // System.currentTimeMillis - this.parent.stop(); + span.logEvent("hystrix/retry"); // System.currentTimeMillis + span.stop(); - zipkin.Span result = this.spanReporter.convert(this.parent); + zipkin.Span result = this.spanReporter.convert(span); assertThat(result.timestamp) - .isEqualTo(this.parent.getBegin() * 1000); + .isEqualTo(span.getBegin() * 1000); assertThat(result.duration) - .isEqualTo(this.parent.getAccumulatedMicros()); + .isEqualTo(span.getAccumulatedMicros()); assertThat(result.annotations.get(0).timestamp) .isGreaterThanOrEqualTo(start * 1000) .isLessThanOrEqualTo(System.currentTimeMillis() * 1000); @@ -82,29 +84,31 @@ public class ZipkinSpanListenerTests { @Test public void setsTheDurationToTheDifferenceBetweenCRandCS() throws InterruptedException { - this.parent.logEvent(Span.CLIENT_SEND); + Span span = Span.builder().traceId(1L).name("http:api").build(); + span.logEvent(Span.CLIENT_SEND); Thread.sleep(10); - this.parent.logEvent(Span.CLIENT_RECV); + span.logEvent(Span.CLIENT_RECV); Thread.sleep(20); - this.parent.stop(); + span.stop(); - zipkin.Span result = this.spanReporter.convert(this.parent); + zipkin.Span result = this.spanReporter.convert(span); assertThat(result.timestamp) - .isEqualTo(this.parent.getBegin() * 1000); - long clientSendTimestamp = this.parent.logs().stream().filter(log -> Span.CLIENT_SEND.equals(log.getEvent())) + .isEqualTo(span.getBegin() * 1000); + long clientSendTimestamp = span.logs().stream().filter(log -> Span.CLIENT_SEND.equals(log.getEvent())) .findFirst().get().getTimestamp(); - long clientRecvTimestamp = this.parent.logs().stream().filter(log -> Span.CLIENT_RECV.equals(log.getEvent())) + long clientRecvTimestamp = span.logs().stream().filter(log -> Span.CLIENT_RECV.equals(log.getEvent())) .findFirst().get().getTimestamp(); assertThat(result.duration) - .isNotEqualTo(this.parent.getAccumulatedMicros()) + .isNotEqualTo(span.getAccumulatedMicros()) .isEqualTo((clientRecvTimestamp - clientSendTimestamp) * 1000); } /** Zipkin's duration should only be set when the span is finished. */ @Test public void doesntSetDurationWhenStillRunning() { - zipkin.Span result = this.spanReporter.convert(this.parent); + Span span = Span.builder().traceId(1L).name("http:api").build(); + zipkin.Span result = this.spanReporter.convert(span); assertThat(result.timestamp) .isGreaterThan(0); // sanity check it did start @@ -112,6 +116,23 @@ public class ZipkinSpanListenerTests { .isNull(); } + /** + * In the RPC span model, the client owns the timestamp and duration of the span. If we + * were propagated an id, we can assume that we shouldn't report timestamp or duration, + * rather let the client do that. Worst case we were propagated an unreported ID and + * Zipkin backfills timestamp and duration. + */ + @Test + public void doesntSetTimestampOrDurationWhenRemote() { + this.parent.stop(); + zipkin.Span result = this.spanReporter.convert(this.parent); + + assertThat(result.timestamp) + .isNull(); + assertThat(result.duration) + .isNull(); + } + /** Sleuth host corresponds to annotation/binaryAnnotation.host in zipkin. */ @Test public void annotationsIncludeHost() { From fb18d4631156902ac6cfe4fe66073b8152b929cf Mon Sep 17 00:00:00 2001 From: Adrian Cole Date: Mon, 7 Nov 2016 07:10:18 -0800 Subject: [PATCH 122/235] Fixes ZipkinMessageListenerTests --- .../stream/ZipkinMessageListenerTests.java | 33 +++++++++++++++---- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/spring-cloud-sleuth-zipkin-stream/src/test/java/org/springframework/cloud/sleuth/zipkin/stream/ZipkinMessageListenerTests.java b/spring-cloud-sleuth-zipkin-stream/src/test/java/org/springframework/cloud/sleuth/zipkin/stream/ZipkinMessageListenerTests.java index cab8523df..31d94b3ec 100644 --- a/spring-cloud-sleuth-zipkin-stream/src/test/java/org/springframework/cloud/sleuth/zipkin/stream/ZipkinMessageListenerTests.java +++ b/spring-cloud-sleuth-zipkin-stream/src/test/java/org/springframework/cloud/sleuth/zipkin/stream/ZipkinMessageListenerTests.java @@ -34,18 +34,37 @@ public class ZipkinMessageListenerTests { .ipv4(1 << 24 | 2 << 16 | 3 << 8 | 4) .port(8080).build(); - /** Sleuth timestamps are millisecond granularity while zipkin is microsecond. */ + /** + * In the RPC span model, the client owns the timestamp and duration of the span. If we + * were propagated an id, we can assume that we shouldn't report timestamp or duration, + * rather let the client do that. Worst case we were propagated an unreported ID and + * Zipkin backfills timestamp and duration. + */ @Test - public void convertsTimestampAndDurationToMicroseconds() { - long start = System.currentTimeMillis(); - this.span.logEvent("hystrix/retry"); // System.currentTimeMillis - + public void doesntSetTimestampOrDurationWhenRemote() { + this.span.stop(); zipkin.Span result = ConvertToZipkinSpanList.convert(this.span, this.host); assertThat(result.timestamp) - .isEqualTo(this.span.getBegin() * 1000); + .isNull(); assertThat(result.duration) - .isEqualTo((this.span.getEnd() - this.span.getBegin()) * 1000); + .isNull(); + } + + /** Sleuth timestamps are millisecond granularity while zipkin is microsecond. */ + @Test + public void convertsTimestampAndDurationToMicroseconds() { + Span span = new Span(1, 3, "http:name", 1L, Collections.emptyList(), 2L, false, true, + "process"); + long start = System.currentTimeMillis(); + span.logEvent("hystrix/retry"); // System.currentTimeMillis + + zipkin.Span result = ConvertToZipkinSpanList.convert(span, this.host); + + assertThat(result.timestamp) + .isEqualTo(span.getBegin() * 1000); + assertThat(result.duration) + .isEqualTo((span.getEnd() - span.getBegin()) * 1000); assertThat(result.annotations.get(0).timestamp) .isGreaterThanOrEqualTo(start * 1000) .isLessThanOrEqualTo(System.currentTimeMillis() * 1000); From 31e02f54e2be9127651baac7cc9da01e37f2e229 Mon Sep 17 00:00:00 2001 From: Adrian Cole Date: Mon, 7 Nov 2016 07:29:31 -0800 Subject: [PATCH 123/235] Fixes MessagingApplicationTests --- .../src/test/java/integration/MessagingApplicationTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/src/test/java/integration/MessagingApplicationTests.java b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/src/test/java/integration/MessagingApplicationTests.java index 81f5174e0..b8ccb3123 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/src/test/java/integration/MessagingApplicationTests.java +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/src/test/java/integration/MessagingApplicationTests.java @@ -114,7 +114,7 @@ public class MessagingApplicationTests extends AbstractIntegrationTest { Optional eventReceivedSpan = findSpanWithAnnotation(Constants.CLIENT_RECV); Optional lastHttpSpansParent = findLastHttpSpansParent(); // "http:/parent/" -> "home" -> "message:messages" -> "http:/foo" (CS + CR) -> "http:/foo" (SS) -> "foo" - Collections.sort(this.integrationTestSpanCollector.hashedSpans, (s1, s2) -> s1.timestamp.compareTo(s2.timestamp)); + Collections.sort(this.integrationTestSpanCollector.hashedSpans); thenAllSpansArePresent(firstHttpSpan, eventSpans, lastHttpSpansParent, eventSentSpan, eventReceivedSpan); then(this.integrationTestSpanCollector.hashedSpans).as("There were 6 spans").hasSize(6); log.info("Checking the parent child structure"); From 54932c567501cf277b3363d93e07691f2c0e85c8 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Tue, 8 Nov 2016 15:05:06 +0100 Subject: [PATCH 124/235] Wrapping ExecutorService in its own representation without this change ExecutorService was treated as an Executor and wrapped in the Executor bean. Due to this the bean was missing / bean of invalid type was registered. with this change we do not wrap ExecutorService with a Executor bean, instead we wrap it in a TraceableExecutorService representation. fixes #445 --- README.adoc | 4 +- .../async/TraceExecutorBeanPostProcessor.java | 6 +- .../async/TraceableExecutorService.java | 57 ++++++++++++++----- .../async/issues/issue410/Issue410Tests.java | 26 ++++++++- 4 files changed, 74 insertions(+), 19 deletions(-) diff --git a/README.adoc b/README.adoc index c4f9b28fb..7fa260c97 100644 --- a/README.adoc +++ b/README.adoc @@ -598,11 +598,11 @@ a modified file in the correct place. Just commit it and push the change. If you don't have an IDE preference we would recommend that you use http://www.springsource.com/developer/sts[Spring Tools Suite] or http://eclipse.org[Eclipse] when working with the code. We use the -http://eclipse.org/m2e/[m2eclipe] eclipse plugin for maven support. Other IDEs and tools +http://eclipse.org/m2e/[m2eclipse] eclipse plugin for maven support. Other IDEs and tools should also work without issue as long as they use Maven 3.3.3 or better. ==== Importing into eclipse with m2eclipse -We recommend the http://eclipse.org/m2e/[m2eclipe] eclipse plugin when working with +We recommend the http://eclipse.org/m2e/[m2eclipse] eclipse plugin when working with eclipse. If you don't already have m2eclipse installed it is available from the "eclipse marketplace". diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceExecutorBeanPostProcessor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceExecutorBeanPostProcessor.java index 04e8ea0ef..3d59e9e48 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceExecutorBeanPostProcessor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceExecutorBeanPostProcessor.java @@ -17,6 +17,7 @@ package org.springframework.cloud.sleuth.instrument.async; import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; @@ -43,8 +44,11 @@ class TraceExecutorBeanPostProcessor implements BeanPostProcessor { if (bean instanceof ThreadPoolTaskExecutor && !(bean instanceof TaskScheduler) && !(bean instanceof LazyTraceThreadPoolTaskExecutor)) { return new LazyTraceThreadPoolTaskExecutor(this.beanFactory, (ThreadPoolTaskExecutor) bean); - } else if (bean instanceof Executor && !(bean instanceof TaskScheduler) && !(bean instanceof LazyTraceExecutor)) { + } else if (bean instanceof Executor && !(bean instanceof ExecutorService) && + !(bean instanceof TaskScheduler) && !(bean instanceof LazyTraceExecutor)) { return new LazyTraceExecutor(this.beanFactory, (Executor) bean); + } else if (bean instanceof ExecutorService) { + return new TraceableExecutorService(this.beanFactory, (ExecutorService) bean); } return bean; } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceableExecutorService.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceableExecutorService.java index 4bd0d578e..fbf547f28 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceableExecutorService.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceableExecutorService.java @@ -25,6 +25,7 @@ import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import org.springframework.beans.factory.BeanFactory; import org.springframework.cloud.sleuth.SpanNamer; import org.springframework.cloud.sleuth.TraceKeys; import org.springframework.cloud.sleuth.Tracer; @@ -36,17 +37,24 @@ import org.springframework.cloud.sleuth.Tracer; * @since 1.0.0 */ public class TraceableExecutorService implements ExecutorService { - final ExecutorService delegate; - final Tracer tracer; + ExecutorService delegate; + Tracer tracer; private final String spanName; - final TraceKeys traceKeys; - final SpanNamer spanNamer; + TraceKeys traceKeys; + SpanNamer spanNamer; + BeanFactory beanFactory; public TraceableExecutorService(final ExecutorService delegate, final Tracer tracer, TraceKeys traceKeys, SpanNamer spanNamer) { this(delegate, tracer, traceKeys, spanNamer, null); } + public TraceableExecutorService(BeanFactory beanFactory, final ExecutorService delegate) { + this.delegate = delegate; + this.beanFactory = beanFactory; + this.spanName = null; + } + public TraceableExecutorService(final ExecutorService delegate, final Tracer tracer, TraceKeys traceKeys, SpanNamer spanNamer, String spanName) { this.delegate = delegate; @@ -58,8 +66,8 @@ public class TraceableExecutorService implements ExecutorService { @Override public void execute(Runnable command) { - final Runnable r = new LocalComponentTraceRunnable(this.tracer, this.traceKeys, - this.spanNamer, command, this.spanName); + final Runnable r = new LocalComponentTraceRunnable(tracer(), traceKeys(), + spanNamer(), command, this.spanName); this.delegate.execute(r); } @@ -90,22 +98,22 @@ public class TraceableExecutorService implements ExecutorService { @Override public Future submit(Callable task) { - Callable c = new LocalComponentTraceCallable<>(this.tracer, this.traceKeys, - this.spanNamer, this.spanName, task); + Callable c = new LocalComponentTraceCallable<>(tracer(), traceKeys(), + spanNamer(), this.spanName, task); return this.delegate.submit(c); } @Override public Future submit(Runnable task, T result) { - Runnable r = new LocalComponentTraceRunnable(this.tracer, this.traceKeys, - this.spanNamer, task, this.spanName); + Runnable r = new LocalComponentTraceRunnable(tracer(), traceKeys(), + spanNamer(), task, this.spanName); return this.delegate.submit(r, result); } @Override public Future submit(Runnable task) { - Runnable r = new LocalComponentTraceRunnable(this.tracer, this.traceKeys, - this.spanNamer, task, this.spanName); + Runnable r = new LocalComponentTraceRunnable(tracer(), traceKeys(), + spanNamer(), task, this.spanName); return this.delegate.submit(r); } @@ -135,11 +143,32 @@ public class TraceableExecutorService implements ExecutorService { List> ts = new ArrayList<>(); for (Callable task : tasks) { if (!(task instanceof LocalComponentTraceCallable)) { - ts.add(new LocalComponentTraceCallable<>(this.tracer, this.traceKeys, - this.spanNamer, this.spanName, task)); + ts.add(new LocalComponentTraceCallable<>(tracer(), traceKeys(), + spanNamer(), this.spanName, task)); } } return ts; } + Tracer tracer() { + if (this.tracer == null && this.beanFactory != null) { + this.tracer = this.beanFactory.getBean(Tracer.class); + } + return this.tracer; + } + + TraceKeys traceKeys() { + if (this.traceKeys == null && this.beanFactory != null) { + this.traceKeys = this.beanFactory.getBean(TraceKeys.class); + } + return this.traceKeys; + } + + SpanNamer spanNamer() { + if (this.spanNamer == null && this.beanFactory != null) { + this.spanNamer = this.beanFactory.getBean(SpanNamer.class); + } + return this.spanNamer; + } + } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue410/Issue410Tests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue410/Issue410Tests.java index dd3c17a08..1c8e3b283 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue410/Issue410Tests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue410/Issue410Tests.java @@ -19,6 +19,8 @@ package org.springframework.cloud.sleuth.instrument.async.issues.issue410; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicReference; import org.apache.commons.logging.Log; @@ -36,6 +38,7 @@ import org.springframework.cloud.sleuth.Sampler; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.instrument.async.LazyTraceExecutor; +import org.springframework.cloud.sleuth.instrument.async.TraceableExecutorService; import org.springframework.cloud.sleuth.sampler.AlwaysSampler; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -67,6 +70,10 @@ public class Issue410Tests { @Autowired Tracer tracer; @Autowired AsyncTask asyncTask; @Autowired RestTemplate restTemplate; + /** + * Related to issue #445 + */ + @Autowired ExecutorService executorService; @Test public void should_pass_tracing_info_for_tasks_running_without_a_pool() { @@ -138,6 +145,14 @@ public class Issue410Tests { } } + /** + * Related to issue #445 + */ + @Test + public void should_wrap_executor_service_in_trace_representation() { + then(this.executorService).isInstanceOf(TraceableExecutorService.class); + } + private int port() { return this.environment.getProperty("local.server.port", Integer.class); } @@ -148,11 +163,11 @@ public class Issue410Tests { @EnableAsync class AppConfig { - @Bean Sampler testSampler() { + @Bean public Sampler testSampler() { return new AlwaysSampler(); } - @Bean RestTemplate restTemplate() { + @Bean public RestTemplate restTemplate() { return new RestTemplate(); } @@ -281,4 +296,11 @@ class Application { return Span.idToHex(this.asyncTask.taskScheduler().getTraceId()); } + /** + * Related to issue #445 + */ + @Bean public ExecutorService executorService() { + return Executors.newSingleThreadExecutor(); + } + } From 2a740db1d26402d0927bccfd585afa969e5e2bb8 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Tue, 8 Nov 2016 15:05:06 +0100 Subject: [PATCH 125/235] Wrapping ExecutorService in its own representation without this change ExecutorService was treated as an Executor and wrapped in the Executor bean. Due to this the bean was missing / bean of invalid type was registered. with this change we do not wrap ExecutorService with a Executor bean, instead we wrap it in a TraceableExecutorService representation. fixes #445 --- README.adoc | 4 +- .../async/TraceExecutorBeanPostProcessor.java | 6 +- .../async/TraceableExecutorService.java | 57 ++++++++++++++----- .../async/issues/issue410/Issue410Tests.java | 26 ++++++++- 4 files changed, 74 insertions(+), 19 deletions(-) diff --git a/README.adoc b/README.adoc index c4f9b28fb..7fa260c97 100644 --- a/README.adoc +++ b/README.adoc @@ -598,11 +598,11 @@ a modified file in the correct place. Just commit it and push the change. If you don't have an IDE preference we would recommend that you use http://www.springsource.com/developer/sts[Spring Tools Suite] or http://eclipse.org[Eclipse] when working with the code. We use the -http://eclipse.org/m2e/[m2eclipe] eclipse plugin for maven support. Other IDEs and tools +http://eclipse.org/m2e/[m2eclipse] eclipse plugin for maven support. Other IDEs and tools should also work without issue as long as they use Maven 3.3.3 or better. ==== Importing into eclipse with m2eclipse -We recommend the http://eclipse.org/m2e/[m2eclipe] eclipse plugin when working with +We recommend the http://eclipse.org/m2e/[m2eclipse] eclipse plugin when working with eclipse. If you don't already have m2eclipse installed it is available from the "eclipse marketplace". diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceExecutorBeanPostProcessor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceExecutorBeanPostProcessor.java index 04e8ea0ef..3d59e9e48 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceExecutorBeanPostProcessor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceExecutorBeanPostProcessor.java @@ -17,6 +17,7 @@ package org.springframework.cloud.sleuth.instrument.async; import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; @@ -43,8 +44,11 @@ class TraceExecutorBeanPostProcessor implements BeanPostProcessor { if (bean instanceof ThreadPoolTaskExecutor && !(bean instanceof TaskScheduler) && !(bean instanceof LazyTraceThreadPoolTaskExecutor)) { return new LazyTraceThreadPoolTaskExecutor(this.beanFactory, (ThreadPoolTaskExecutor) bean); - } else if (bean instanceof Executor && !(bean instanceof TaskScheduler) && !(bean instanceof LazyTraceExecutor)) { + } else if (bean instanceof Executor && !(bean instanceof ExecutorService) && + !(bean instanceof TaskScheduler) && !(bean instanceof LazyTraceExecutor)) { return new LazyTraceExecutor(this.beanFactory, (Executor) bean); + } else if (bean instanceof ExecutorService) { + return new TraceableExecutorService(this.beanFactory, (ExecutorService) bean); } return bean; } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceableExecutorService.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceableExecutorService.java index 4bd0d578e..fbf547f28 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceableExecutorService.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceableExecutorService.java @@ -25,6 +25,7 @@ import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import org.springframework.beans.factory.BeanFactory; import org.springframework.cloud.sleuth.SpanNamer; import org.springframework.cloud.sleuth.TraceKeys; import org.springframework.cloud.sleuth.Tracer; @@ -36,17 +37,24 @@ import org.springframework.cloud.sleuth.Tracer; * @since 1.0.0 */ public class TraceableExecutorService implements ExecutorService { - final ExecutorService delegate; - final Tracer tracer; + ExecutorService delegate; + Tracer tracer; private final String spanName; - final TraceKeys traceKeys; - final SpanNamer spanNamer; + TraceKeys traceKeys; + SpanNamer spanNamer; + BeanFactory beanFactory; public TraceableExecutorService(final ExecutorService delegate, final Tracer tracer, TraceKeys traceKeys, SpanNamer spanNamer) { this(delegate, tracer, traceKeys, spanNamer, null); } + public TraceableExecutorService(BeanFactory beanFactory, final ExecutorService delegate) { + this.delegate = delegate; + this.beanFactory = beanFactory; + this.spanName = null; + } + public TraceableExecutorService(final ExecutorService delegate, final Tracer tracer, TraceKeys traceKeys, SpanNamer spanNamer, String spanName) { this.delegate = delegate; @@ -58,8 +66,8 @@ public class TraceableExecutorService implements ExecutorService { @Override public void execute(Runnable command) { - final Runnable r = new LocalComponentTraceRunnable(this.tracer, this.traceKeys, - this.spanNamer, command, this.spanName); + final Runnable r = new LocalComponentTraceRunnable(tracer(), traceKeys(), + spanNamer(), command, this.spanName); this.delegate.execute(r); } @@ -90,22 +98,22 @@ public class TraceableExecutorService implements ExecutorService { @Override public Future submit(Callable task) { - Callable c = new LocalComponentTraceCallable<>(this.tracer, this.traceKeys, - this.spanNamer, this.spanName, task); + Callable c = new LocalComponentTraceCallable<>(tracer(), traceKeys(), + spanNamer(), this.spanName, task); return this.delegate.submit(c); } @Override public Future submit(Runnable task, T result) { - Runnable r = new LocalComponentTraceRunnable(this.tracer, this.traceKeys, - this.spanNamer, task, this.spanName); + Runnable r = new LocalComponentTraceRunnable(tracer(), traceKeys(), + spanNamer(), task, this.spanName); return this.delegate.submit(r, result); } @Override public Future submit(Runnable task) { - Runnable r = new LocalComponentTraceRunnable(this.tracer, this.traceKeys, - this.spanNamer, task, this.spanName); + Runnable r = new LocalComponentTraceRunnable(tracer(), traceKeys(), + spanNamer(), task, this.spanName); return this.delegate.submit(r); } @@ -135,11 +143,32 @@ public class TraceableExecutorService implements ExecutorService { List> ts = new ArrayList<>(); for (Callable task : tasks) { if (!(task instanceof LocalComponentTraceCallable)) { - ts.add(new LocalComponentTraceCallable<>(this.tracer, this.traceKeys, - this.spanNamer, this.spanName, task)); + ts.add(new LocalComponentTraceCallable<>(tracer(), traceKeys(), + spanNamer(), this.spanName, task)); } } return ts; } + Tracer tracer() { + if (this.tracer == null && this.beanFactory != null) { + this.tracer = this.beanFactory.getBean(Tracer.class); + } + return this.tracer; + } + + TraceKeys traceKeys() { + if (this.traceKeys == null && this.beanFactory != null) { + this.traceKeys = this.beanFactory.getBean(TraceKeys.class); + } + return this.traceKeys; + } + + SpanNamer spanNamer() { + if (this.spanNamer == null && this.beanFactory != null) { + this.spanNamer = this.beanFactory.getBean(SpanNamer.class); + } + return this.spanNamer; + } + } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue410/Issue410Tests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue410/Issue410Tests.java index dd3c17a08..1c8e3b283 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue410/Issue410Tests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue410/Issue410Tests.java @@ -19,6 +19,8 @@ package org.springframework.cloud.sleuth.instrument.async.issues.issue410; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicReference; import org.apache.commons.logging.Log; @@ -36,6 +38,7 @@ import org.springframework.cloud.sleuth.Sampler; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.instrument.async.LazyTraceExecutor; +import org.springframework.cloud.sleuth.instrument.async.TraceableExecutorService; import org.springframework.cloud.sleuth.sampler.AlwaysSampler; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -67,6 +70,10 @@ public class Issue410Tests { @Autowired Tracer tracer; @Autowired AsyncTask asyncTask; @Autowired RestTemplate restTemplate; + /** + * Related to issue #445 + */ + @Autowired ExecutorService executorService; @Test public void should_pass_tracing_info_for_tasks_running_without_a_pool() { @@ -138,6 +145,14 @@ public class Issue410Tests { } } + /** + * Related to issue #445 + */ + @Test + public void should_wrap_executor_service_in_trace_representation() { + then(this.executorService).isInstanceOf(TraceableExecutorService.class); + } + private int port() { return this.environment.getProperty("local.server.port", Integer.class); } @@ -148,11 +163,11 @@ public class Issue410Tests { @EnableAsync class AppConfig { - @Bean Sampler testSampler() { + @Bean public Sampler testSampler() { return new AlwaysSampler(); } - @Bean RestTemplate restTemplate() { + @Bean public RestTemplate restTemplate() { return new RestTemplate(); } @@ -281,4 +296,11 @@ class Application { return Span.idToHex(this.asyncTask.taskScheduler().getTraceId()); } + /** + * Related to issue #445 + */ + @Bean public ExecutorService executorService() { + return Executors.newSingleThreadExecutor(); + } + } From 8d48406dfac92a3e00ae003c96c6e3f54b70c812 Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Tue, 15 Nov 2016 11:29:19 +0000 Subject: [PATCH 126/235] Ensure websockets 1xx response is not treated as an error --- .../cloud/sleuth/instrument/web/TraceFilter.java | 2 +- .../org/springframework/cloud/sleuth/trace/DefaultTracer.java | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceFilter.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceFilter.java index 96ed118a7..9380c1673 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 @@ -251,7 +251,7 @@ public class TraceFilter extends GenericFilterBean { return false; } HttpStatus httpStatus = HttpStatus.valueOf(response.getStatus()); - return httpStatus.is2xxSuccessful() || httpStatus.is3xxRedirection(); + return httpStatus.is1xxInformational() || httpStatus.is2xxSuccessful() || httpStatus.is3xxRedirection(); } private Span getSpanFromAttribute(HttpServletRequest request) { 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 e1bc938b6..7cd2ffa78 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 @@ -131,7 +131,9 @@ public class DefaultTracer implements Tracer { } SpanContextHolder.close(new SpanContextHolder.SpanFunction() { @Override public void apply(Span span) { - DefaultTracer.this.spanLogger.logStoppedSpan(savedSpan, span); + if (span!=null) { + DefaultTracer.this.spanLogger.logStoppedSpan(savedSpan, span); + } } }); } From a39371572cc706b77039a1411e880f094660e490 Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Tue, 15 Nov 2016 15:40:06 +0000 Subject: [PATCH 127/235] Fix broken test (duh) --- .../cloud/sleuth/log/Slf4jSpanLogger.java | 10 ++--- .../cloud/sleuth/trace/DefaultTracer.java | 39 ++++++++----------- 2 files changed, 21 insertions(+), 28 deletions(-) diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/Slf4jSpanLogger.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/Slf4jSpanLogger.java index 3df9f1b03..9ba05efc9 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/Slf4jSpanLogger.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/Slf4jSpanLogger.java @@ -23,8 +23,7 @@ import org.slf4j.MDC; import org.springframework.cloud.sleuth.Span; /** - * Span listener that logs to the console when a span got - * started / stopped / continued. + * Span listener that logs to the console when a span got started / stopped / continued. * * @author Spencer Gibb * @since 1.0.0 @@ -36,8 +35,7 @@ public class Slf4jSpanLogger implements SpanLogger { public Slf4jSpanLogger(String nameSkipPattern) { this.nameSkipPattern = Pattern.compile(nameSkipPattern); - this.log = org.slf4j.LoggerFactory - .getLogger(Slf4jSpanLogger.class); + this.log = org.slf4j.LoggerFactory.getLogger(Slf4jSpanLogger.class); } Slf4jSpanLogger(String nameSkipPattern, Logger log) { @@ -66,7 +64,9 @@ public class Slf4jSpanLogger implements SpanLogger { @Override public void logStoppedSpan(Span parent, Span span) { - log("Stopped span: {}", span); + if (span != null) { + log("Stopped span: {}", span); + } if (span != null && parent != null) { log("With parent: {}", parent); MDC.put(Span.SPAN_ID_NAME, Span.idToHex(parent.getSpanId())); 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 7cd2ffa78..360bde3f0 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 @@ -47,8 +47,8 @@ public class DefaultTracer implements Tracer { private final SpanReporter spanReporter; - public DefaultTracer(Sampler defaultSampler, Random random, SpanNamer spanNamer, - SpanLogger spanLogger, SpanReporter spanReporter) { + public DefaultTracer(Sampler defaultSampler, Random random, SpanNamer spanNamer, SpanLogger spanLogger, + SpanReporter spanReporter) { this.defaultSampler = defaultSampler; this.random = random; this.spanNamer = spanNamer; @@ -77,9 +77,8 @@ public class DefaultTracer implements Tracer { } else { long id = createId(); - span = Span.builder().name(name).traceId(id) - .spanId(id).build(); - if (sampler==null) { + span = Span.builder().name(name).traceId(id).spanId(id).build(); + if (sampler == null) { sampler = this.defaultSampler; } span = sampledSpan(name, id, span, sampler); @@ -95,8 +94,7 @@ public class DefaultTracer implements Tracer { } Span cur = SpanContextHolder.getCurrentSpan(); if (!span.equals(cur)) { - ExceptionUtils.warn("Tried to detach trace span but " - + "it is not the current span: " + span + ExceptionUtils.warn("Tried to detach trace span but " + "it is not the current span: " + span + ". You may have forgotten to close or detach " + cur); } else { @@ -113,9 +111,8 @@ public class DefaultTracer implements Tracer { Span cur = SpanContextHolder.getCurrentSpan(); final Span savedSpan = span.getSavedSpan(); if (!span.equals(cur)) { - ExceptionUtils.warn( - "Tried to close span but it is not the current span: " + span - + ". You may have forgotten to close or detach " + cur); + ExceptionUtils.warn("Tried to close span but it is not the current span: " + span + + ". You may have forgotten to close or detach " + cur); } else { span.stop(); @@ -130,10 +127,9 @@ public class DefaultTracer implements Tracer { } } SpanContextHolder.close(new SpanContextHolder.SpanFunction() { - @Override public void apply(Span span) { - if (span!=null) { - DefaultTracer.this.spanLogger.logStoppedSpan(savedSpan, span); - } + @Override + public void apply(Span span) { + DefaultTracer.this.spanLogger.logStoppedSpan(savedSpan, span); } }); } @@ -143,8 +139,7 @@ public class DefaultTracer implements Tracer { protected Span createChild(Span parent, String name) { long id = createId(); if (parent == null) { - Span span = Span.builder().name(name) - .traceId(id).spanId(id).build(); + Span span = Span.builder().name(name).traceId(id).spanId(id).build(); span = sampledSpan(name, id, span, this.defaultSampler); this.spanLogger.logStartedSpan(null, span); return span; @@ -153,10 +148,8 @@ public class DefaultTracer implements Tracer { if (!isTracing()) { SpanContextHolder.push(parent, true); } - Span span = Span.builder().name(name) - .traceId(parent.getTraceId()).parent(parent.getSpanId()).spanId(id) - .processId(parent.getProcessId()).savedSpan(parent) - .exportable(parent.isExportable()).build(); + Span span = Span.builder().name(name).traceId(parent.getTraceId()).parent(parent.getSpanId()).spanId(id) + .processId(parent.getProcessId()).savedSpan(parent).exportable(parent.isExportable()).build(); this.spanLogger.logStartedSpan(parent, span); return span; } @@ -165,8 +158,7 @@ public class DefaultTracer implements Tracer { private Span sampledSpan(String name, long id, Span span, Sampler sampler) { if (!sampler.isSampled(span)) { // Non-exportable so we keep the trace but not other data - return Span.builder().begin(span.getBegin()).name(name).traceId(id) - .spanId(id).exportable(false).build(); + return Span.builder().begin(span.getBegin()).name(name).traceId(id).spanId(id).exportable(false).build(); } return span; } @@ -179,7 +171,8 @@ public class DefaultTracer implements Tracer { public Span continueSpan(Span span) { if (span != null) { this.spanLogger.logContinuedSpan(span); - } else { + } + else { return null; } Span newSpan = createContinuedSpan(span, SpanContextHolder.getCurrentSpan()); From d6bc3529b382f9b8a1d56608c098a006ce8959b9 Mon Sep 17 00:00:00 2001 From: Adrian Cole Date: Wed, 16 Nov 2016 16:49:16 +0800 Subject: [PATCH 128/235] Switches ID encoding to fixed-length (1.0.x backport) (#451) Before, we were using variable encoding for trace and span identifiers. This complicates search for those who are copy/pasting fixed-length IDs provisioned upstream. This moves to standard formatting, while maintaining tolerant reads. The code added will also be used to support 128-bit (32 char) trace IDs. Backport of #450 --- .../springframework/cloud/sleuth/Span.java | 26 +++++++++++++++++-- .../cloud/sleuth/SpanTests.java | 4 +-- ...aceRestClientRibbonCommandFactoryTest.java | 6 ++--- 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java index e44e6d44a..394ce657d 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java @@ -414,12 +414,34 @@ public class Span { } /** - * Represents given long id as hex string + * Represents given long id as 16-character lower-hex string */ public static String idToHex(long id) { - return Long.toHexString(id); + char[] data = new char[16]; + writeHexLong(data, 0, id); + return new String(data); } + /** Inspired by {@code okio.Buffer.writeLong} */ + static void writeHexLong(char[] data, int pos, long v) { + writeHexByte(data, pos + 0, (byte) ((v >>> 56L) & 0xff)); + writeHexByte(data, pos + 2, (byte) ((v >>> 48L) & 0xff)); + writeHexByte(data, pos + 4, (byte) ((v >>> 40L) & 0xff)); + writeHexByte(data, pos + 6, (byte) ((v >>> 32L) & 0xff)); + writeHexByte(data, pos + 8, (byte) ((v >>> 24L) & 0xff)); + writeHexByte(data, pos + 10, (byte) ((v >>> 16L) & 0xff)); + writeHexByte(data, pos + 12, (byte) ((v >>> 8L) & 0xff)); + writeHexByte(data, pos + 14, (byte) (v & 0xff)); + } + + static void writeHexByte(char[] data, int pos, byte b) { + data[pos + 0] = HEX_DIGITS[(b >> 4) & 0xf]; + data[pos + 1] = HEX_DIGITS[b & 0xf]; + } + + static final char[] HEX_DIGITS = + {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + /** * Parses a 1 to 32 character lower-hex string with no prefix into an unsigned long, tossing any * bits higher than 64. diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/SpanTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/SpanTests.java index bed930500..e39c07c8c 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/SpanTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/SpanTests.java @@ -35,12 +35,12 @@ import static org.assertj.core.api.BDDAssertions.then; public class SpanTests { @Test - public void should_convert_long_to_hex_string() throws Exception { + public void should_convert_long_to_16_character_hex_string() throws Exception { long someLong = 123123L; String hexString = Span.idToHex(someLong); - then(hexString).isEqualTo("1e0f3"); + then(hexString).isEqualTo("000000000001e0f3"); } @Test diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRestClientRibbonCommandFactoryTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRestClientRibbonCommandFactoryTest.java index 26ebf11fb..1eca7a645 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRestClientRibbonCommandFactoryTest.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRestClientRibbonCommandFactoryTest.java @@ -92,13 +92,13 @@ public class TraceRestClientRibbonCommandFactoryTest { HttpRequest httpRequest = builder.build(); then(httpRequest.getHttpHeaders().getFirstValue(Span.SPAN_ID_NAME)) - .isEqualTo("1"); + .isEqualTo("0000000000000001"); then(httpRequest.getHttpHeaders().getFirstValue(Span.TRACE_ID_NAME)) - .isEqualTo("2"); + .isEqualTo("0000000000000002"); then(httpRequest.getHttpHeaders().getFirstValue(Span.SPAN_NAME_NAME)) .isEqualTo("name"); then(httpRequest.getHttpHeaders().getFirstValue(Span.PARENT_ID_NAME)) - .isEqualTo("3"); + .isEqualTo("0000000000000003"); then(httpRequest.getHttpHeaders().getFirstValue(Span.PROCESS_ID_NAME)) .isEqualTo("processId"); } From 6cb0c21e5a90bf8f90b41260fde11cfd5e5a586b Mon Sep 17 00:00:00 2001 From: Adrian Cole Date: Wed, 16 Nov 2016 16:49:16 +0800 Subject: [PATCH 129/235] Switches ID encoding to fixed-length (1.0.x backport) (#451) Before, we were using variable encoding for trace and span identifiers. This complicates search for those who are copy/pasting fixed-length IDs provisioned upstream. This moves to standard formatting, while maintaining tolerant reads. The code added will also be used to support 128-bit (32 char) trace IDs. Backport of #450 --- .../springframework/cloud/sleuth/Span.java | 26 +++++++++++++++++-- .../cloud/sleuth/SpanTests.java | 4 +-- ...aceRestClientRibbonCommandFactoryTest.java | 6 ++--- 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java index e44e6d44a..394ce657d 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java @@ -414,12 +414,34 @@ public class Span { } /** - * Represents given long id as hex string + * Represents given long id as 16-character lower-hex string */ public static String idToHex(long id) { - return Long.toHexString(id); + char[] data = new char[16]; + writeHexLong(data, 0, id); + return new String(data); } + /** Inspired by {@code okio.Buffer.writeLong} */ + static void writeHexLong(char[] data, int pos, long v) { + writeHexByte(data, pos + 0, (byte) ((v >>> 56L) & 0xff)); + writeHexByte(data, pos + 2, (byte) ((v >>> 48L) & 0xff)); + writeHexByte(data, pos + 4, (byte) ((v >>> 40L) & 0xff)); + writeHexByte(data, pos + 6, (byte) ((v >>> 32L) & 0xff)); + writeHexByte(data, pos + 8, (byte) ((v >>> 24L) & 0xff)); + writeHexByte(data, pos + 10, (byte) ((v >>> 16L) & 0xff)); + writeHexByte(data, pos + 12, (byte) ((v >>> 8L) & 0xff)); + writeHexByte(data, pos + 14, (byte) (v & 0xff)); + } + + static void writeHexByte(char[] data, int pos, byte b) { + data[pos + 0] = HEX_DIGITS[(b >> 4) & 0xf]; + data[pos + 1] = HEX_DIGITS[b & 0xf]; + } + + static final char[] HEX_DIGITS = + {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + /** * Parses a 1 to 32 character lower-hex string with no prefix into an unsigned long, tossing any * bits higher than 64. diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/SpanTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/SpanTests.java index bed930500..e39c07c8c 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/SpanTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/SpanTests.java @@ -35,12 +35,12 @@ import static org.assertj.core.api.BDDAssertions.then; public class SpanTests { @Test - public void should_convert_long_to_hex_string() throws Exception { + public void should_convert_long_to_16_character_hex_string() throws Exception { long someLong = 123123L; String hexString = Span.idToHex(someLong); - then(hexString).isEqualTo("1e0f3"); + then(hexString).isEqualTo("000000000001e0f3"); } @Test diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRestClientRibbonCommandFactoryTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRestClientRibbonCommandFactoryTest.java index f85932401..a87ce8f78 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRestClientRibbonCommandFactoryTest.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRestClientRibbonCommandFactoryTest.java @@ -93,13 +93,13 @@ public class TraceRestClientRibbonCommandFactoryTest { HttpRequest httpRequest = builder.build(); then(httpRequest.getHttpHeaders().getFirstValue(Span.SPAN_ID_NAME)) - .isEqualTo("1"); + .isEqualTo("0000000000000001"); then(httpRequest.getHttpHeaders().getFirstValue(Span.TRACE_ID_NAME)) - .isEqualTo("2"); + .isEqualTo("0000000000000002"); then(httpRequest.getHttpHeaders().getFirstValue(Span.SPAN_NAME_NAME)) .isEqualTo("name"); then(httpRequest.getHttpHeaders().getFirstValue(Span.PARENT_ID_NAME)) - .isEqualTo("3"); + .isEqualTo("0000000000000003"); then(httpRequest.getHttpHeaders().getFirstValue(Span.PROCESS_ID_NAME)) .isEqualTo("processId"); } From 2a1b4befbac1af60104972edf37e5ccb704423ca Mon Sep 17 00:00:00 2001 From: Adrian Cole Date: Fri, 18 Nov 2016 14:40:25 +0800 Subject: [PATCH 130/235] Propagates logs and reports 128-bit trace IDs (1.1.x) (#453) This supports 128-bit traces via a new field traceIdHigh, which matches other zipkin implementations. In encoded form, the trace ID is simply twice as long (32 hex characters). With this change in, a 128-bit trace propagated will not be downgraded to 64-bits when sending downstream, reporting to Zipkin or adding to the logging context. This will be followed by a change to support initiating 128-bit traces. --- .../springframework/cloud/sleuth/Span.java | 174 +++++++++++++----- .../messaging/MessagingSpanExtractor.java | 8 +- .../messaging/MessagingSpanInjector.java | 2 +- .../web/HttpServletRequestExtractor.java | 21 ++- .../web/client/HttpRequestInjector.java | 2 +- .../zuul/RequestContextInjector.java | 2 +- .../cloud/sleuth/log/Slf4jSpanLogger.java | 4 +- .../cloud/sleuth/SpanTests.java | 122 ++++++++++-- .../async/issues/issue410/Issue410Tests.java | 16 +- .../MessagingSpanExtractorTests.java | 13 +- .../web/HttpServletRequestExtractorTests.java | 4 +- ...raceCustomFilterResponseInjectorTests.java | 2 +- .../cloud/sleuth/log/Slf4JSpanLoggerTest.java | 39 ++++ .../stream/ConvertToZipkinSpanList.java | 1 + .../stream/ConvertToZipkinSpanListTests.java | 17 +- .../sleuth/zipkin/ZipkinSpanListener.java | 1 + .../zipkin/ZipkinSpanListenerTests.java | 10 + 17 files changed, 343 insertions(+), 95 deletions(-) diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java index 394ce657d..31574f18e 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java @@ -137,6 +137,7 @@ public class Span { private final long begin; private long end = 0; private final String name; + private final long traceIdHigh; private final long traceId; private List parents = new ArrayList<>(); private final long spanId; @@ -166,6 +167,7 @@ public class Span { this.begin = current.getBegin(); this.end = current.getEnd(); this.name = current.getName(); + this.traceIdHigh = current.getTraceIdHigh(); this.traceId = current.getTraceId(); this.parents = current.getParents(); this.spanId = current.getSpanId(); @@ -179,36 +181,61 @@ public class Span { this.savedSpan = savedSpan; } + /** + * @deprecated please use {@link SpanBuilder} + */ + @Deprecated public Span(long begin, long end, String name, long traceId, List parents, long spanId, boolean remote, boolean exportable, String processId) { this(begin, end, name, traceId, parents, spanId, remote, exportable, processId, null); } + /** + * @deprecated please use {@link SpanBuilder} + */ + @Deprecated public Span(long begin, long end, String name, long traceId, List parents, long spanId, boolean remote, boolean exportable, String processId, Span savedSpan) { - if (begin > 0) { // conventionally, 0 indicates unset + this(new SpanBuilder() + .begin(begin) + .end(end) + .name(name) + .traceId(traceId) + .parents(parents) + .spanId(spanId) + .remote(remote) + .exportable(exportable) + .processId(processId) + .savedSpan(savedSpan)); + } + + Span(SpanBuilder builder) { + if (builder.begin > 0) { // conventionally, 0 indicates unset this.startNanos = null; // don't know the start tick - this.begin = begin; + this.begin = builder.begin; } else { this.startNanos = nanoTime(); this.begin = System.currentTimeMillis(); } - if (end > 0) { - this.end = end; - this.durationMicros = (end - begin) * 1000; + if (builder.end > 0) { + this.end = builder.end; + this.durationMicros = (this.end - this.begin) * 1000; } - this.name = name != null ? name : ""; - this.traceId = traceId; - this.parents = parents; - this.spanId = spanId; - this.remote = remote; - this.exportable = exportable; - this.processId = processId; - this.savedSpan = savedSpan; + this.name = builder.name != null ? builder.name : ""; + this.traceIdHigh = builder.traceIdHigh; + this.traceId = builder.traceId; + this.parents.addAll(builder.parents); + this.spanId = builder.spanId; + this.remote = builder.remote; + this.exportable = builder.exportable; + this.processId = builder.processId; + this.savedSpan = builder.savedSpan; this.tags = new ConcurrentHashMap<>(); + this.tags.putAll(builder.tags); this.logs = new ConcurrentLinkedQueue<>(); + this.logs.addAll(builder.logs); } public static SpanBuilder builder() { @@ -358,7 +385,30 @@ public class Span { } /** - * A pseudo-unique (random) number assigned to the trace associated with this span + * When non-zero, the trace containing this span uses 128-bit trace identifiers. + * + *

{@code traceIdHigh} corresponds to the high bits in big-endian format and + * {@link #getTraceId()} corresponds to the low bits. + * + *

Ex. to convert the two fields to a 128bit opaque id array, you'd use code like below. + *

{@code
+	 * ByteBuffer traceId128 = ByteBuffer.allocate(16);
+	 * traceId128.putLong(span.getTraceIdHigh());
+	 * traceId128.putLong(span.getTraceId());
+	 * traceBytes = traceId128.array();
+	 * }
+ * + * @see #traceIdString() + * @since 1.0.11 + */ + public long getTraceIdHigh() { + return this.traceIdHigh; + } + + /** + * Unique 8-byte identifier for a trace, set on all spans within it. + * + * @see #getTraceIdHigh() for notes about 128-bit trace identifiers */ public long getTraceId() { return this.traceId; @@ -413,8 +463,27 @@ public class Span { return this.exportable; } + /** + * Returns the 16 or 32 character hex representation of the span's trace ID + * + * @since 1.0.11 + */ + public String traceIdString() { + if (this.traceIdHigh != 0) { + char[] result = new char[32]; + writeHexLong(result, 0, this.traceIdHigh); + writeHexLong(result, 16, this.traceId); + return new String(result); + } + char[] result = new char[16]; + writeHexLong(result, 0, this.traceId); + return new String(result); + } + /** * Represents given long id as 16-character lower-hex string + * + * @see #traceIdString() */ public static String idToHex(long id) { char[] data = new char[16]; @@ -449,21 +518,32 @@ public class Span { public static long hexToId(String hexString) { Assert.hasText(hexString, "Can't convert empty hex string to long"); int length = hexString.length(); - if (length < 1 || length > 32) throw new IllegalArgumentException("Malformed id"); + if (length < 1 || length > 32) throw new IllegalArgumentException("Malformed id: " + hexString); // trim off any high bits - int i = length > 16 ? length - 16 : 0; + int beginIndex = length > 16 ? length - 16 : 0; + return hexToId(hexString, beginIndex); + } + + /** + * Parses a 16 character lower-hex string with no prefix into an unsigned long, starting at the + * specified index. + * + * @since 1.0.11 + */ + public static long hexToId(String lowerHex, int index) { + Assert.hasText(lowerHex, "Can't convert empty hex string to long"); long result = 0; - for (; i < length; i++) { - char c = hexString.charAt(i); + for (int endIndex = Math.min(index + 16, lowerHex.length()); index < endIndex; index++) { + char c = lowerHex.charAt(index); result <<= 4; if (c >= '0' && c <= '9') { result |= c - '0'; } else if (c >= 'a' && c <= 'f') { result |= c - 'a' + 10; } else { - throw new IllegalArgumentException("Malformed id"); + throw new IllegalArgumentException("Malformed id: " + lowerHex); } } return result; @@ -471,7 +551,7 @@ public class Span { @Override public String toString() { - return "[Trace: " + idToHex(this.traceId) + ", Span: " + idToHex(this.spanId) + return "[Trace: " + traceIdString() + ", Span: " + idToHex(this.spanId) + ", Parent: " + getParentIdIfPresent() + ", exportable:" + this.exportable + "]"; } @@ -481,31 +561,36 @@ public class Span { @Override public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + (int) (this.spanId ^ (this.spanId >>> 32)); - result = prime * result + (int) (this.traceId ^ (this.traceId >>> 32)); - return result; + int h = 1; + h *= 1000003; + h ^= (this.traceIdHigh >>> 32) ^ this.traceIdHigh; + h *= 1000003; + h ^= (this.traceId >>> 32) ^ this.traceId; + h *= 1000003; + h ^= (this.spanId >>> 32) ^ this.spanId; + h *= 1000003; + return h; } @Override - public boolean equals(Object obj) { - if (this == obj) + public boolean equals(Object o) { + if (o == this) { return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - Span other = (Span) obj; - if (this.spanId != other.spanId) - return false; - return this.traceId == other.traceId; + } + if (o instanceof Span) { + Span that = (Span) o; + return (this.traceIdHigh == that.traceIdHigh) + && (this.traceId == that.traceId) + && (this.spanId == that.spanId); + } + return false; } public static class SpanBuilder { private long begin; private long end; private String name; + private long traceIdHigh; private long traceId; private ArrayList parents = new ArrayList<>(); private long spanId; @@ -541,6 +626,11 @@ public class Span { return this; } + public Span.SpanBuilder traceIdHigh(long traceIdHigh) { + this.traceIdHigh = traceIdHigh; + return this; + } + public Span.SpanBuilder traceId(long traceId) { this.traceId = traceId; return this; @@ -602,22 +692,12 @@ public class Span { } public Span build() { - Span span = new Span(this.begin, this.end, this.name, this.traceId, - this.parents, this.spanId, this.remote, this.exportable, - this.processId, this.savedSpan); - span.logs.addAll(this.logs); - span.tags.putAll(this.tags); - return span; + return new Span(this); } @Override public String toString() { - return "SpanBuilder{" + "begin=" + this.begin + ", end=" + this.end - + ", name=" + this.name + ", traceId=" + this.traceId + ", parents=" - + this.parents + ", spanId=" + this.spanId + ", remote=" + this.remote - + ", exportable=" + this.exportable + ", processId='" + this.processId - + '\'' + ", savedSpan=" + this.savedSpan + ", logs=" + this.logs - + ", tags=" + this.tags + '}'; + return new Span(this).toString(); } } } 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 b7cdc809f..8b14098f6 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 @@ -76,12 +76,14 @@ class MessagingSpanExtractor implements SpanExtractor> { 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)); + String traceId = getHeader(carrier, traceIdHeader); + spanBuilder.traceIdHigh(traceId.length() == 32 ? Span.hexToId(traceId, 0) : 0); + spanBuilder.traceId(Span.hexToId(traceId)); + long spanId = hasHeader(carrier, spanIdHeader) ? Span.hexToId(getHeader(carrier, spanIdHeader)) : this.random.nextLong(); - spanBuilder = spanBuilder.traceId(traceId).spanId(spanId); + spanBuilder = spanBuilder.spanId(spanId); spanBuilder.exportable( Span.SPAN_SAMPLED.equals(getHeader(carrier, spanSampledHeader))); String processId = getHeader(carrier, spanProcessIdHeader); 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 11098b447..f867411ac 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 @@ -95,7 +95,7 @@ class MessagingSpanInjector implements SpanInjector> { 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, traceIdHeader, span.traceIdString()); addHeader(headers, spanIdHeader, Span.idToHex(span.getSpanId())); if (span.isExportable()) { addAnnotations(this.traceKeys, initialMessage, span); diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/HttpServletRequestExtractor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/HttpServletRequestExtractor.java index 2a682bfe6..719fd96b9 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/HttpServletRequestExtractor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/HttpServletRequestExtractor.java @@ -58,32 +58,33 @@ class HttpServletRequestExtractor implements SpanExtractor { 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); + long spanId = spanId(carrier); + return buildParentSpan(carrier, uri, skip, 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) { + private long spanId(HttpServletRequest carrier) { 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"); + + "a root span with span id equal to the lower 64-bits of the trace id"); } - return traceId; + return Span.hexToId(carrier.getHeader(Span.TRACE_ID_NAME)); } 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); + private Span buildParentSpan(HttpServletRequest carrier, String uri, boolean skip, long spanId) { + String traceId = carrier.getHeader(Span.TRACE_ID_NAME); + SpanBuilder span = Span.builder() + .traceIdHigh(traceId.length() == 32 ? Span.hexToId(traceId, 0) : 0) + .traceId(Span.hexToId(traceId)) + .spanId(spanId); String processId = carrier.getHeader(Span.PROCESS_ID_NAME); String parentName = carrier.getHeader(Span.SPAN_NAME_NAME); if (StringUtils.hasText(parentName)) { diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/HttpRequestInjector.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/HttpRequestInjector.java index 8f12f3150..b8185613b 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/HttpRequestInjector.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/HttpRequestInjector.java @@ -32,7 +32,7 @@ class HttpRequestInjector implements SpanInjector { @Override public void inject(Span span, HttpRequest carrier) { - setIdHeader(carrier, Span.TRACE_ID_NAME, span.getTraceId()); + setHeader(carrier, Span.TRACE_ID_NAME, span.traceIdString()); 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()); diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/RequestContextInjector.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/RequestContextInjector.java index 20b0c5d3a..341f036b6 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/RequestContextInjector.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/RequestContextInjector.java @@ -41,7 +41,7 @@ class RequestContextInjector implements SpanInjector { return; } setHeader(requestHeaders, Span.SPAN_ID_NAME, span.getSpanId()); - setHeader(requestHeaders, Span.TRACE_ID_NAME, span.getTraceId()); + setHeader(requestHeaders, Span.TRACE_ID_NAME, span.traceIdString()); setHeader(requestHeaders, Span.SPAN_NAME_NAME, span.getName()); setHeader(requestHeaders, Span.SAMPLED_NAME, span.isExportable() ? Span.SPAN_SAMPLED : Span.SPAN_NOT_SAMPLED); diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/Slf4jSpanLogger.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/Slf4jSpanLogger.java index 3df9f1b03..db1dbe31c 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/Slf4jSpanLogger.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/Slf4jSpanLogger.java @@ -49,7 +49,7 @@ public class Slf4jSpanLogger implements SpanLogger { public void logStartedSpan(Span parent, Span span) { MDC.put(Span.SPAN_ID_NAME, Span.idToHex(span.getSpanId())); MDC.put(Span.SPAN_EXPORT_NAME, String.valueOf(span.isExportable())); - MDC.put(Span.TRACE_ID_NAME, Span.idToHex(span.getTraceId())); + MDC.put(Span.TRACE_ID_NAME, span.traceIdString()); log("Starting span: {}", span); if (parent != null) { log("With parent: {}", parent); @@ -59,7 +59,7 @@ public class Slf4jSpanLogger implements SpanLogger { @Override public void logContinuedSpan(Span span) { MDC.put(Span.SPAN_ID_NAME, Span.idToHex(span.getSpanId())); - MDC.put(Span.TRACE_ID_NAME, Span.idToHex(span.getTraceId())); + MDC.put(Span.TRACE_ID_NAME, span.traceIdString()); MDC.put(Span.SPAN_EXPORT_NAME, String.valueOf(span.isExportable())); log("Continued span: {}", span); } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/SpanTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/SpanTests.java index e39c07c8c..3ab21ee9c 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/SpanTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/SpanTests.java @@ -17,7 +17,7 @@ package org.springframework.cloud.sleuth; import java.io.IOException; -import java.util.Collections; +import java.util.concurrent.atomic.AtomicLong; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; @@ -33,6 +33,45 @@ import static org.assertj.core.api.BDDAssertions.then; * @author Spencer Gibb */ public class SpanTests { + Span span = Span.builder().begin(1).end(2).name("http:name").traceId(1L).spanId(2L) + .remote(true).exportable(true).processId("process").build(); + + @Test + public void should_consider_trace_and_span_id_on_equals_and_hashCode() throws Exception { + Span span = Span.builder().traceId(1L).spanId(2L).build(); + Span differentSpan = Span.builder().traceId(1L).spanId(3L).build(); + Span withParent =Span.builder().traceId(1L).spanId(2L).parent(3L).build(); + + then(span).isEqualTo(withParent); + then(span).isNotEqualTo(differentSpan); + then(span.hashCode()).isNotEqualTo(differentSpan.hashCode()); + } + + @Test + public void should_have_toString_with_identifiers_and_export() throws Exception { + span = Span.builder().traceId(1L).spanId(2L).parent(3L).name("foo").build(); + + then(span).hasToString( + "[Trace: 0000000000000001, Span: 0000000000000002, Parent: 0000000000000003, exportable:true]"); + } + + @Test + public void should_have_toString_with_128bit_trace_id() throws Exception { + span = Span.builder().traceIdHigh(1L).traceId(2L).spanId(3L).parent(4L).build(); + + then(span.toString()).startsWith("[Trace: 00000000000000010000000000000002,"); + } + + @Test + public void should_consider_128bit_trace_and_span_id_on_equals_and_hashCode() throws Exception { + Span span = Span.builder().traceIdHigh(1L).traceId(2L).spanId(3L).build(); + Span differentSpan = Span.builder().traceIdHigh(2L).traceId(2L).spanId(3L).build(); + Span withParent = Span.builder().traceIdHigh(1L).traceId(2L).spanId(3L).parent(4L).build(); + + then(span).isEqualTo(withParent); + then(span).isNotEqualTo(differentSpan); + then(span.hashCode()).isNotEqualTo(differentSpan.hashCode()); + } @Test public void should_convert_long_to_16_character_hex_string() throws Exception { @@ -62,6 +101,36 @@ public class SpanTests { then(someLong).isEqualTo(Span.hexToId(lower64Bits)); } + @Test + public void should_convert_offset_64bits_of_hex_string_to_long() throws Exception { + String hex128Bits = "463ac35c9f6413ad48485a3953bb6124"; + String high64Bits = "463ac35c9f6413ad"; + + long someLong = Span.hexToId(hex128Bits, 0); + + then(someLong).isEqualTo(Span.hexToId(high64Bits)); + } + + @Test + public void should_writeFixedLength64BitTraceId() throws Exception { + String traceId = span.traceIdString(); + + then(traceId).isEqualTo("0000000000000001"); + } + + @Test + public void should_writeFixedLength128BitTraceId() throws Exception { + String high128Bits = "463ac35c9f6413ad"; + String low64Bits = "48485a3953bb6124"; + + span = Span.builder().traceIdHigh(Span.hexToId(high128Bits)).traceId(Span.hexToId(low64Bits)) + .spanId(1L).name("foo").build(); + + String traceId = span.traceIdString(); + + then(traceId).isEqualTo(high128Bits + low64Bits); + } + @Test(expected = IllegalArgumentException.class) public void should_throw_exception_when_null_string_is_to_be_converted_to_long() throws Exception { Span.hexToId(null); @@ -69,23 +138,15 @@ public class SpanTests { @Test(expected = UnsupportedOperationException.class) public void getAnnotationsReadOnly() { - Span span = new Span(1, 2, "http:name", 1L, Collections.emptyList(), 2L, true, - true, "process"); - span.tags().put("a", "b"); } @Test(expected = UnsupportedOperationException.class) public void getTimelineAnnotationsReadOnly() { - Span span = new Span(1, 2, "http:name", 1L, Collections.emptyList(), 2L, true, - true, "process"); - span.logs().add(new Log(1, "1")); } @Test public void should_properly_serialize_object() throws JsonProcessingException { - Span span = new Span(1, 2, "http:name", 1L, - Collections.emptyList(), 2L, true, true, "process"); ObjectMapper objectMapper = new ObjectMapper(); String serializedName = objectMapper.writeValueAsString(span); @@ -94,8 +155,6 @@ public class SpanTests { } @Test public void should_properly_serialize_logs() throws IOException { - Span span = new Span(1, 2, "http:name", 1L, - Collections.emptyList(), 2L, true, true, "process"); span.logEvent("cs"); ObjectMapper objectMapper = new ObjectMapper(); @@ -108,8 +167,6 @@ public class SpanTests { } @Test public void should_properly_serialize_tags() throws IOException { - Span span = new Span(1, 2, "http:name", 1L, - Collections.emptyList(), 2L, true, true, "process"); span.tag("calculatedTax", "100"); ObjectMapper objectMapper = new ObjectMapper(); @@ -128,7 +185,7 @@ public class SpanTests { /** When going over a transport like spring-cloud-stream, we must retain the precise duration. */ @Test public void shouldSerializeDurationMicros() throws IOException { - Span span = Span.builder().traceId(1L).name("http:parent").remote(true).build(); + Span span = Span.builder().traceId(1L).name("http:parent").build(); span.stop(); assertThat(span.getAccumulatedMicros()) @@ -145,4 +202,41 @@ public class SpanTests { assertThat(deserialized.getAccumulatedMicros()) .isEqualTo(span.getAccumulatedMicros()); } + + // Duration of 0 is confusing to plot and can be misinterpreted as null + @Test public void getAccumulatedMicros_roundsUpToOneWhenRunning() throws IOException { + AtomicLong nanoTime = new AtomicLong(); + + // starts the span, recording its initial tick as zero + Span span = new Span(Span.builder().name("http:name").traceId(1L).spanId(2L)) { + @Override long nanoTime() { + return nanoTime.get(); + } + }; + + // When only 100 nanoseconds passed + nanoTime.set(100L); + + // We round so that we don't confuse "not started" with a short span. + assertThat(span.getAccumulatedMicros()).isEqualTo(1L); + } + + // Duration of 0 is confusing to plot and can be misinterpreted as null + @Test public void getAccumulatedMicros_roundsUpToOneWhenStopped() throws IOException { + final AtomicLong nanoTime = new AtomicLong(); + + // starts the span, recording its initial tick as zero + Span span = new Span(Span.builder().name("http:name").traceId(1L).spanId(2L)) { + @Override long nanoTime() { + return nanoTime.get(); + } + }; + + // When only 100 nanoseconds passed + nanoTime.set(100L); + span.stop(); + + // We round so that we don't confuse "not started" with a short span. + assertThat(span.getAccumulatedMicros()).isEqualTo(1L); + } } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue410/Issue410Tests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue410/Issue410Tests.java index 1c8e3b283..272230de7 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue410/Issue410Tests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue410/Issue410Tests.java @@ -81,7 +81,7 @@ public class Issue410Tests { try { String response = this.restTemplate.getForObject("http://localhost:" + port() + "/without_pool", String.class); - then(response).isEqualTo(Span.idToHex(span.getTraceId())); + then(response).isEqualTo(span.traceIdString()); Awaitility.await().until(() -> { then(this.asyncTask.getSpan().get()).isNotNull(); then(this.asyncTask.getSpan().get().getTraceId()).isEqualTo(span.getTraceId()); @@ -97,7 +97,7 @@ public class Issue410Tests { try { String response = this.restTemplate.getForObject("http://localhost:" + port() + "/with_pool", String.class); - then(response).isEqualTo(Span.idToHex(span.getTraceId())); + then(response).isEqualTo(span.traceIdString()); Awaitility.await().until(() -> { then(this.asyncTask.getSpan().get()).isNotNull(); then(this.asyncTask.getSpan().get().getTraceId()).isEqualTo(span.getTraceId()); @@ -116,7 +116,7 @@ public class Issue410Tests { try { String response = this.restTemplate.getForObject("http://localhost:" + port() + "/completable", String.class); - then(response).isEqualTo(Span.idToHex(span.getTraceId())); + then(response).isEqualTo(span.traceIdString()); Awaitility.await().until(() -> { then(this.asyncTask.getSpan().get()).isNotNull(); then(this.asyncTask.getSpan().get().getTraceId()).isEqualTo(span.getTraceId()); @@ -135,7 +135,7 @@ public class Issue410Tests { try { String response = this.restTemplate.getForObject("http://localhost:" + port() + "/taskScheduler", String.class); - then(response).isEqualTo(Span.idToHex(span.getTraceId())); + then(response).isEqualTo(span.traceIdString()); Awaitility.await().until(() -> { then(this.asyncTask.getSpan().get()).isNotNull(); then(this.asyncTask.getSpan().get().getTraceId()).isEqualTo(span.getTraceId()); @@ -273,7 +273,7 @@ class Application { public String withPool() { log.info("Executing with pool."); this.asyncTask.runWithPool(); - return Span.idToHex(this.tracer.getCurrentSpan().getTraceId()); + return this.tracer.getCurrentSpan().traceIdString(); } @@ -281,19 +281,19 @@ class Application { public String withoutPool() { log.info("Executing without pool."); this.asyncTask.runWithoutPool(); - return Span.idToHex(this.tracer.getCurrentSpan().getTraceId()); + return this.tracer.getCurrentSpan().traceIdString(); } @RequestMapping("/completable") public String completable() throws ExecutionException, InterruptedException { log.info("Executing completable"); - return Span.idToHex(this.asyncTask.completableFutures().getTraceId()); + return this.asyncTask.completableFutures().traceIdString(); } @RequestMapping("/taskScheduler") public String taskScheduler() throws ExecutionException, InterruptedException { log.info("Executing completable via task scheduler"); - return Span.idToHex(this.asyncTask.taskScheduler().getTraceId()); + return this.asyncTask.taskScheduler().traceIdString(); } /** diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingSpanExtractorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingSpanExtractorTests.java index acd296951..1070d43f4 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingSpanExtractorTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingSpanExtractorTests.java @@ -19,10 +19,10 @@ package org.springframework.cloud.sleuth.instrument.messaging; import java.util.HashMap; 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; @@ -56,6 +56,17 @@ public class MessagingSpanExtractorTests { } } + @Test + public void should_parse_128bit_trace_id() { + String traceId128 = "463ac35c9f6413ad48485a3953bb6124"; + + Span span = this.extractor.joinTrace( + MessageBuilder.createMessage("", + headers(traceId128, randomId()))); + + then(span.traceIdString()).isEqualTo(traceId128); + } + @Test public void should_set_random_spanid_if_header_value_is_invalid() { Message message = MessageBuilder.createMessage("", diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/HttpServletRequestExtractorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/HttpServletRequestExtractorTests.java index 6cc85427f..04dd26f95 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/HttpServletRequestExtractorTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/HttpServletRequestExtractorTests.java @@ -79,7 +79,7 @@ public class HttpServletRequestExtractorTests { } @Test - public void should_downgrade_128bit_trace_id_by_dropping_high_bits() { + public void should_accept_128bit_trace_id() { String hex128Bits = "463ac35c9f6413ad48485a3953bb6124"; String lower64Bits = "48485a3953bb6124"; @@ -90,6 +90,6 @@ public class HttpServletRequestExtractorTests { Span span = this.extractor.joinTrace(this.request); - then(span.getTraceId()).isEqualTo(Span.hexToId(lower64Bits)); + then(span.traceIdString()).isEqualTo(hex128Bits); } } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceCustomFilterResponseInjectorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceCustomFilterResponseInjectorTests.java index 1783bbfce..a2f65aee1 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceCustomFilterResponseInjectorTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceCustomFilterResponseInjectorTests.java @@ -119,7 +119,7 @@ public class TraceCustomFilterResponseInjectorTests { @Override public void inject(Span span, HttpServletResponse carrier) { - carrier.addHeader(Span.TRACE_ID_NAME, Span.idToHex(span.getTraceId())); + carrier.addHeader(Span.TRACE_ID_NAME, span.traceIdString()); carrier.addHeader(Span.SPAN_ID_NAME, Span.idToHex(span.getSpanId())); } } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/log/Slf4JSpanLoggerTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/log/Slf4JSpanLoggerTest.java index 29b3afea6..649ea1a9d 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/log/Slf4JSpanLoggerTest.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/log/Slf4JSpanLoggerTest.java @@ -20,8 +20,10 @@ import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; import org.slf4j.Logger; +import org.slf4j.MDC; import org.springframework.cloud.sleuth.Span; +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.then; import static org.mockito.Matchers.anyList; @@ -42,9 +44,46 @@ public class Slf4JSpanLoggerTest { @Before public void setup() { + MDC.clear(); given(log.isTraceEnabled()).willReturn(true); } + @Test + public void when_start_event_arrived_should_add_64bit_trace_id_to_MDC() throws Exception { + Span span = Span.builder().traceId(1L).spanId(2L).build(); + + this.slf4JSpanLogger.logStartedSpan(this.spanWithNameNotToBeExcluded, span); + + assertThat(MDC.get("X-B3-TraceId")).isEqualTo("0000000000000001"); + } + + @Test + public void when_start_event_arrived_should_add_128bit_trace_id_to_MDC() throws Exception { + Span span = Span.builder().traceIdHigh(1L).traceId(2L).spanId(3L).build(); + + this.slf4JSpanLogger.logStartedSpan(this.spanWithNameNotToBeExcluded, span); + + assertThat(MDC.get("X-B3-TraceId")).isEqualTo("00000000000000010000000000000002"); + } + + @Test + public void when_continued_event_arrived_should_add_64bit_trace_id_to_MDC() throws Exception { + Span span = Span.builder().traceId(1L).spanId(2L).build(); + + this.slf4JSpanLogger.logContinuedSpan(span); + + assertThat(MDC.get("X-B3-TraceId")).isEqualTo("0000000000000001"); + } + + @Test + public void when_continued_event_arrived_should_add_128bit_trace_id_to_MDC() throws Exception { + Span span = Span.builder().traceIdHigh(1L).traceId(2L).spanId(3L).build(); + + this.slf4JSpanLogger.logContinuedSpan(span); + + assertThat(MDC.get("X-B3-TraceId")).isEqualTo("00000000000000010000000000000002"); + } + @Test public void should_log_when_start_event_arrived_and_pattern_doesnt_match_span_name() throws Exception { this.slf4JSpanLogger.logStartedSpan(this.spanWithNameNotToBeExcluded, diff --git a/spring-cloud-sleuth-zipkin-stream/src/main/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanList.java b/spring-cloud-sleuth-zipkin-stream/src/main/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanList.java index 7a1e2bd47..7bd83c15b 100644 --- a/spring-cloud-sleuth-zipkin-stream/src/main/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanList.java +++ b/spring-cloud-sleuth-zipkin-stream/src/main/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanList.java @@ -99,6 +99,7 @@ final class ConvertToZipkinSpanList { zipkinSpan.duration(calculateDurationInMicros(span)); } } + zipkinSpan.traceIdHigh(span.getTraceIdHigh()); zipkinSpan.traceId(span.getTraceId()); if (span.getParents().size() > 0) { if (span.getParents().size() > 1) { diff --git a/spring-cloud-sleuth-zipkin-stream/src/test/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanListTests.java b/spring-cloud-sleuth-zipkin-stream/src/test/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanListTests.java index 4aa3cac91..26161e080 100644 --- a/spring-cloud-sleuth-zipkin-stream/src/test/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanListTests.java +++ b/spring-cloud-sleuth-zipkin-stream/src/test/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanListTests.java @@ -19,12 +19,10 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Random; - import org.junit.Test; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.stream.Host; import org.springframework.cloud.sleuth.stream.Spans; - import zipkin.Constants; import static org.assertj.core.api.Assertions.assertThat; @@ -183,13 +181,24 @@ public class ConvertToZipkinSpanListTests { .isNull(); } + @Test + public void converts128BitTraceId() { + Span span = Span.builder().traceIdHigh(1L).traceId(2L).spanId(3L).name("foo").build(); + + Spans spans = new Spans(this.host, Collections.singletonList(span)); + zipkin.Span result = ConvertToZipkinSpanList.convert(spans).get(0); + + assertThat(result.traceIdHigh).isEqualTo(span.getTraceIdHigh()); + assertThat(result.traceId).isEqualTo(span.getTraceId()); + } + Span span(String name) { return span(name, false); } Span span(String name, boolean remote) { Long id = new Random().nextLong(); - return new Span(1, 3, "message:" + name, id, Collections.emptyList(), id, remote, true, - "process"); + return Span.builder().begin(1).end(3).name("message:" + name).traceId(id).spanId(id) + .remote(remote).processId("process").build(); } } diff --git a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListener.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListener.java index 116a876e4..46a34f5b5 100644 --- a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListener.java +++ b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListener.java @@ -95,6 +95,7 @@ public class ZipkinSpanListener implements SpanReporter { zipkinSpan.duration(calculateDurationInMicros(span)); } } + zipkinSpan.traceIdHigh(span.getTraceIdHigh()); zipkinSpan.traceId(span.getTraceId()); if (span.getParents().size() > 0) { if (span.getParents().size() > 1) { diff --git a/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListenerTests.java b/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListenerTests.java index a99e4d267..239754a49 100644 --- a/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListenerTests.java +++ b/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListenerTests.java @@ -215,6 +215,16 @@ public class ZipkinSpanListenerTests { .isNotEmpty(); } + @Test + public void converts128BitTraceId() { + Span span = Span.builder().traceIdHigh(1L).traceId(2L).spanId(3L).name("foo").build(); + + zipkin.Span result = this.spanReporter.convert(span); + + assertThat(result.traceIdHigh).isEqualTo(span.getTraceIdHigh()); + assertThat(result.traceId).isEqualTo(span.getTraceId()); + } + @Test public void shouldReuseServerAddressTag() { this.parent.logEvent(Constants.CLIENT_SEND); From 78f6a43f79b99bdd30c270b070192b9f32dcbcf7 Mon Sep 17 00:00:00 2001 From: Adrian Cole Date: Fri, 18 Nov 2016 14:40:25 +0800 Subject: [PATCH 131/235] Propagates logs and reports 128-bit trace IDs (1.1.x) (#453) This supports 128-bit traces via a new field traceIdHigh, which matches other zipkin implementations. In encoded form, the trace ID is simply twice as long (32 hex characters). With this change in, a 128-bit trace propagated will not be downgraded to 64-bits when sending downstream, reporting to Zipkin or adding to the logging context. This will be followed by a change to support initiating 128-bit traces. --- .../springframework/cloud/sleuth/Span.java | 174 +++++++++++++----- .../messaging/MessagingSpanExtractor.java | 8 +- .../messaging/MessagingSpanInjector.java | 2 +- .../web/HttpServletRequestExtractor.java | 21 ++- .../web/client/HttpRequestInjector.java | 2 +- .../zuul/RequestContextInjector.java | 2 +- .../cloud/sleuth/log/Slf4jSpanLogger.java | 4 +- .../cloud/sleuth/SpanTests.java | 122 ++++++++++-- .../async/issues/issue410/Issue410Tests.java | 16 +- .../MessagingSpanExtractorTests.java | 13 +- .../web/HttpServletRequestExtractorTests.java | 4 +- ...raceCustomFilterResponseInjectorTests.java | 2 +- .../cloud/sleuth/log/Slf4JSpanLoggerTest.java | 39 ++++ .../stream/ConvertToZipkinSpanList.java | 1 + .../stream/ConvertToZipkinSpanListTests.java | 17 +- .../sleuth/zipkin/ZipkinSpanListener.java | 1 + .../zipkin/ZipkinSpanListenerTests.java | 10 + 17 files changed, 343 insertions(+), 95 deletions(-) diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java index 394ce657d..31574f18e 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java @@ -137,6 +137,7 @@ public class Span { private final long begin; private long end = 0; private final String name; + private final long traceIdHigh; private final long traceId; private List parents = new ArrayList<>(); private final long spanId; @@ -166,6 +167,7 @@ public class Span { this.begin = current.getBegin(); this.end = current.getEnd(); this.name = current.getName(); + this.traceIdHigh = current.getTraceIdHigh(); this.traceId = current.getTraceId(); this.parents = current.getParents(); this.spanId = current.getSpanId(); @@ -179,36 +181,61 @@ public class Span { this.savedSpan = savedSpan; } + /** + * @deprecated please use {@link SpanBuilder} + */ + @Deprecated public Span(long begin, long end, String name, long traceId, List parents, long spanId, boolean remote, boolean exportable, String processId) { this(begin, end, name, traceId, parents, spanId, remote, exportable, processId, null); } + /** + * @deprecated please use {@link SpanBuilder} + */ + @Deprecated public Span(long begin, long end, String name, long traceId, List parents, long spanId, boolean remote, boolean exportable, String processId, Span savedSpan) { - if (begin > 0) { // conventionally, 0 indicates unset + this(new SpanBuilder() + .begin(begin) + .end(end) + .name(name) + .traceId(traceId) + .parents(parents) + .spanId(spanId) + .remote(remote) + .exportable(exportable) + .processId(processId) + .savedSpan(savedSpan)); + } + + Span(SpanBuilder builder) { + if (builder.begin > 0) { // conventionally, 0 indicates unset this.startNanos = null; // don't know the start tick - this.begin = begin; + this.begin = builder.begin; } else { this.startNanos = nanoTime(); this.begin = System.currentTimeMillis(); } - if (end > 0) { - this.end = end; - this.durationMicros = (end - begin) * 1000; + if (builder.end > 0) { + this.end = builder.end; + this.durationMicros = (this.end - this.begin) * 1000; } - this.name = name != null ? name : ""; - this.traceId = traceId; - this.parents = parents; - this.spanId = spanId; - this.remote = remote; - this.exportable = exportable; - this.processId = processId; - this.savedSpan = savedSpan; + this.name = builder.name != null ? builder.name : ""; + this.traceIdHigh = builder.traceIdHigh; + this.traceId = builder.traceId; + this.parents.addAll(builder.parents); + this.spanId = builder.spanId; + this.remote = builder.remote; + this.exportable = builder.exportable; + this.processId = builder.processId; + this.savedSpan = builder.savedSpan; this.tags = new ConcurrentHashMap<>(); + this.tags.putAll(builder.tags); this.logs = new ConcurrentLinkedQueue<>(); + this.logs.addAll(builder.logs); } public static SpanBuilder builder() { @@ -358,7 +385,30 @@ public class Span { } /** - * A pseudo-unique (random) number assigned to the trace associated with this span + * When non-zero, the trace containing this span uses 128-bit trace identifiers. + * + *

{@code traceIdHigh} corresponds to the high bits in big-endian format and + * {@link #getTraceId()} corresponds to the low bits. + * + *

Ex. to convert the two fields to a 128bit opaque id array, you'd use code like below. + *

{@code
+	 * ByteBuffer traceId128 = ByteBuffer.allocate(16);
+	 * traceId128.putLong(span.getTraceIdHigh());
+	 * traceId128.putLong(span.getTraceId());
+	 * traceBytes = traceId128.array();
+	 * }
+ * + * @see #traceIdString() + * @since 1.0.11 + */ + public long getTraceIdHigh() { + return this.traceIdHigh; + } + + /** + * Unique 8-byte identifier for a trace, set on all spans within it. + * + * @see #getTraceIdHigh() for notes about 128-bit trace identifiers */ public long getTraceId() { return this.traceId; @@ -413,8 +463,27 @@ public class Span { return this.exportable; } + /** + * Returns the 16 or 32 character hex representation of the span's trace ID + * + * @since 1.0.11 + */ + public String traceIdString() { + if (this.traceIdHigh != 0) { + char[] result = new char[32]; + writeHexLong(result, 0, this.traceIdHigh); + writeHexLong(result, 16, this.traceId); + return new String(result); + } + char[] result = new char[16]; + writeHexLong(result, 0, this.traceId); + return new String(result); + } + /** * Represents given long id as 16-character lower-hex string + * + * @see #traceIdString() */ public static String idToHex(long id) { char[] data = new char[16]; @@ -449,21 +518,32 @@ public class Span { public static long hexToId(String hexString) { Assert.hasText(hexString, "Can't convert empty hex string to long"); int length = hexString.length(); - if (length < 1 || length > 32) throw new IllegalArgumentException("Malformed id"); + if (length < 1 || length > 32) throw new IllegalArgumentException("Malformed id: " + hexString); // trim off any high bits - int i = length > 16 ? length - 16 : 0; + int beginIndex = length > 16 ? length - 16 : 0; + return hexToId(hexString, beginIndex); + } + + /** + * Parses a 16 character lower-hex string with no prefix into an unsigned long, starting at the + * specified index. + * + * @since 1.0.11 + */ + public static long hexToId(String lowerHex, int index) { + Assert.hasText(lowerHex, "Can't convert empty hex string to long"); long result = 0; - for (; i < length; i++) { - char c = hexString.charAt(i); + for (int endIndex = Math.min(index + 16, lowerHex.length()); index < endIndex; index++) { + char c = lowerHex.charAt(index); result <<= 4; if (c >= '0' && c <= '9') { result |= c - '0'; } else if (c >= 'a' && c <= 'f') { result |= c - 'a' + 10; } else { - throw new IllegalArgumentException("Malformed id"); + throw new IllegalArgumentException("Malformed id: " + lowerHex); } } return result; @@ -471,7 +551,7 @@ public class Span { @Override public String toString() { - return "[Trace: " + idToHex(this.traceId) + ", Span: " + idToHex(this.spanId) + return "[Trace: " + traceIdString() + ", Span: " + idToHex(this.spanId) + ", Parent: " + getParentIdIfPresent() + ", exportable:" + this.exportable + "]"; } @@ -481,31 +561,36 @@ public class Span { @Override public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + (int) (this.spanId ^ (this.spanId >>> 32)); - result = prime * result + (int) (this.traceId ^ (this.traceId >>> 32)); - return result; + int h = 1; + h *= 1000003; + h ^= (this.traceIdHigh >>> 32) ^ this.traceIdHigh; + h *= 1000003; + h ^= (this.traceId >>> 32) ^ this.traceId; + h *= 1000003; + h ^= (this.spanId >>> 32) ^ this.spanId; + h *= 1000003; + return h; } @Override - public boolean equals(Object obj) { - if (this == obj) + public boolean equals(Object o) { + if (o == this) { return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - Span other = (Span) obj; - if (this.spanId != other.spanId) - return false; - return this.traceId == other.traceId; + } + if (o instanceof Span) { + Span that = (Span) o; + return (this.traceIdHigh == that.traceIdHigh) + && (this.traceId == that.traceId) + && (this.spanId == that.spanId); + } + return false; } public static class SpanBuilder { private long begin; private long end; private String name; + private long traceIdHigh; private long traceId; private ArrayList parents = new ArrayList<>(); private long spanId; @@ -541,6 +626,11 @@ public class Span { return this; } + public Span.SpanBuilder traceIdHigh(long traceIdHigh) { + this.traceIdHigh = traceIdHigh; + return this; + } + public Span.SpanBuilder traceId(long traceId) { this.traceId = traceId; return this; @@ -602,22 +692,12 @@ public class Span { } public Span build() { - Span span = new Span(this.begin, this.end, this.name, this.traceId, - this.parents, this.spanId, this.remote, this.exportable, - this.processId, this.savedSpan); - span.logs.addAll(this.logs); - span.tags.putAll(this.tags); - return span; + return new Span(this); } @Override public String toString() { - return "SpanBuilder{" + "begin=" + this.begin + ", end=" + this.end - + ", name=" + this.name + ", traceId=" + this.traceId + ", parents=" - + this.parents + ", spanId=" + this.spanId + ", remote=" + this.remote - + ", exportable=" + this.exportable + ", processId='" + this.processId - + '\'' + ", savedSpan=" + this.savedSpan + ", logs=" + this.logs - + ", tags=" + this.tags + '}'; + return new Span(this).toString(); } } } 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 b7cdc809f..8b14098f6 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 @@ -76,12 +76,14 @@ class MessagingSpanExtractor implements SpanExtractor> { 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)); + String traceId = getHeader(carrier, traceIdHeader); + spanBuilder.traceIdHigh(traceId.length() == 32 ? Span.hexToId(traceId, 0) : 0); + spanBuilder.traceId(Span.hexToId(traceId)); + long spanId = hasHeader(carrier, spanIdHeader) ? Span.hexToId(getHeader(carrier, spanIdHeader)) : this.random.nextLong(); - spanBuilder = spanBuilder.traceId(traceId).spanId(spanId); + spanBuilder = spanBuilder.spanId(spanId); spanBuilder.exportable( Span.SPAN_SAMPLED.equals(getHeader(carrier, spanSampledHeader))); String processId = getHeader(carrier, spanProcessIdHeader); 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 11098b447..f867411ac 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 @@ -95,7 +95,7 @@ class MessagingSpanInjector implements SpanInjector> { 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, traceIdHeader, span.traceIdString()); addHeader(headers, spanIdHeader, Span.idToHex(span.getSpanId())); if (span.isExportable()) { addAnnotations(this.traceKeys, initialMessage, span); diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/HttpServletRequestExtractor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/HttpServletRequestExtractor.java index 2a682bfe6..719fd96b9 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/HttpServletRequestExtractor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/HttpServletRequestExtractor.java @@ -58,32 +58,33 @@ class HttpServletRequestExtractor implements SpanExtractor { 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); + long spanId = spanId(carrier); + return buildParentSpan(carrier, uri, skip, 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) { + private long spanId(HttpServletRequest carrier) { 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"); + + "a root span with span id equal to the lower 64-bits of the trace id"); } - return traceId; + return Span.hexToId(carrier.getHeader(Span.TRACE_ID_NAME)); } 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); + private Span buildParentSpan(HttpServletRequest carrier, String uri, boolean skip, long spanId) { + String traceId = carrier.getHeader(Span.TRACE_ID_NAME); + SpanBuilder span = Span.builder() + .traceIdHigh(traceId.length() == 32 ? Span.hexToId(traceId, 0) : 0) + .traceId(Span.hexToId(traceId)) + .spanId(spanId); String processId = carrier.getHeader(Span.PROCESS_ID_NAME); String parentName = carrier.getHeader(Span.SPAN_NAME_NAME); if (StringUtils.hasText(parentName)) { diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/HttpRequestInjector.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/HttpRequestInjector.java index 8f12f3150..b8185613b 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/HttpRequestInjector.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/HttpRequestInjector.java @@ -32,7 +32,7 @@ class HttpRequestInjector implements SpanInjector { @Override public void inject(Span span, HttpRequest carrier) { - setIdHeader(carrier, Span.TRACE_ID_NAME, span.getTraceId()); + setHeader(carrier, Span.TRACE_ID_NAME, span.traceIdString()); 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()); diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/RequestContextInjector.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/RequestContextInjector.java index 20b0c5d3a..341f036b6 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/RequestContextInjector.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/RequestContextInjector.java @@ -41,7 +41,7 @@ class RequestContextInjector implements SpanInjector { return; } setHeader(requestHeaders, Span.SPAN_ID_NAME, span.getSpanId()); - setHeader(requestHeaders, Span.TRACE_ID_NAME, span.getTraceId()); + setHeader(requestHeaders, Span.TRACE_ID_NAME, span.traceIdString()); setHeader(requestHeaders, Span.SPAN_NAME_NAME, span.getName()); setHeader(requestHeaders, Span.SAMPLED_NAME, span.isExportable() ? Span.SPAN_SAMPLED : Span.SPAN_NOT_SAMPLED); diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/Slf4jSpanLogger.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/Slf4jSpanLogger.java index 9ba05efc9..08468ffbf 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/Slf4jSpanLogger.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/Slf4jSpanLogger.java @@ -47,7 +47,7 @@ public class Slf4jSpanLogger implements SpanLogger { public void logStartedSpan(Span parent, Span span) { MDC.put(Span.SPAN_ID_NAME, Span.idToHex(span.getSpanId())); MDC.put(Span.SPAN_EXPORT_NAME, String.valueOf(span.isExportable())); - MDC.put(Span.TRACE_ID_NAME, Span.idToHex(span.getTraceId())); + MDC.put(Span.TRACE_ID_NAME, span.traceIdString()); log("Starting span: {}", span); if (parent != null) { log("With parent: {}", parent); @@ -57,7 +57,7 @@ public class Slf4jSpanLogger implements SpanLogger { @Override public void logContinuedSpan(Span span) { MDC.put(Span.SPAN_ID_NAME, Span.idToHex(span.getSpanId())); - MDC.put(Span.TRACE_ID_NAME, Span.idToHex(span.getTraceId())); + MDC.put(Span.TRACE_ID_NAME, span.traceIdString()); MDC.put(Span.SPAN_EXPORT_NAME, String.valueOf(span.isExportable())); log("Continued span: {}", span); } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/SpanTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/SpanTests.java index e39c07c8c..3ab21ee9c 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/SpanTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/SpanTests.java @@ -17,7 +17,7 @@ package org.springframework.cloud.sleuth; import java.io.IOException; -import java.util.Collections; +import java.util.concurrent.atomic.AtomicLong; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; @@ -33,6 +33,45 @@ import static org.assertj.core.api.BDDAssertions.then; * @author Spencer Gibb */ public class SpanTests { + Span span = Span.builder().begin(1).end(2).name("http:name").traceId(1L).spanId(2L) + .remote(true).exportable(true).processId("process").build(); + + @Test + public void should_consider_trace_and_span_id_on_equals_and_hashCode() throws Exception { + Span span = Span.builder().traceId(1L).spanId(2L).build(); + Span differentSpan = Span.builder().traceId(1L).spanId(3L).build(); + Span withParent =Span.builder().traceId(1L).spanId(2L).parent(3L).build(); + + then(span).isEqualTo(withParent); + then(span).isNotEqualTo(differentSpan); + then(span.hashCode()).isNotEqualTo(differentSpan.hashCode()); + } + + @Test + public void should_have_toString_with_identifiers_and_export() throws Exception { + span = Span.builder().traceId(1L).spanId(2L).parent(3L).name("foo").build(); + + then(span).hasToString( + "[Trace: 0000000000000001, Span: 0000000000000002, Parent: 0000000000000003, exportable:true]"); + } + + @Test + public void should_have_toString_with_128bit_trace_id() throws Exception { + span = Span.builder().traceIdHigh(1L).traceId(2L).spanId(3L).parent(4L).build(); + + then(span.toString()).startsWith("[Trace: 00000000000000010000000000000002,"); + } + + @Test + public void should_consider_128bit_trace_and_span_id_on_equals_and_hashCode() throws Exception { + Span span = Span.builder().traceIdHigh(1L).traceId(2L).spanId(3L).build(); + Span differentSpan = Span.builder().traceIdHigh(2L).traceId(2L).spanId(3L).build(); + Span withParent = Span.builder().traceIdHigh(1L).traceId(2L).spanId(3L).parent(4L).build(); + + then(span).isEqualTo(withParent); + then(span).isNotEqualTo(differentSpan); + then(span.hashCode()).isNotEqualTo(differentSpan.hashCode()); + } @Test public void should_convert_long_to_16_character_hex_string() throws Exception { @@ -62,6 +101,36 @@ public class SpanTests { then(someLong).isEqualTo(Span.hexToId(lower64Bits)); } + @Test + public void should_convert_offset_64bits_of_hex_string_to_long() throws Exception { + String hex128Bits = "463ac35c9f6413ad48485a3953bb6124"; + String high64Bits = "463ac35c9f6413ad"; + + long someLong = Span.hexToId(hex128Bits, 0); + + then(someLong).isEqualTo(Span.hexToId(high64Bits)); + } + + @Test + public void should_writeFixedLength64BitTraceId() throws Exception { + String traceId = span.traceIdString(); + + then(traceId).isEqualTo("0000000000000001"); + } + + @Test + public void should_writeFixedLength128BitTraceId() throws Exception { + String high128Bits = "463ac35c9f6413ad"; + String low64Bits = "48485a3953bb6124"; + + span = Span.builder().traceIdHigh(Span.hexToId(high128Bits)).traceId(Span.hexToId(low64Bits)) + .spanId(1L).name("foo").build(); + + String traceId = span.traceIdString(); + + then(traceId).isEqualTo(high128Bits + low64Bits); + } + @Test(expected = IllegalArgumentException.class) public void should_throw_exception_when_null_string_is_to_be_converted_to_long() throws Exception { Span.hexToId(null); @@ -69,23 +138,15 @@ public class SpanTests { @Test(expected = UnsupportedOperationException.class) public void getAnnotationsReadOnly() { - Span span = new Span(1, 2, "http:name", 1L, Collections.emptyList(), 2L, true, - true, "process"); - span.tags().put("a", "b"); } @Test(expected = UnsupportedOperationException.class) public void getTimelineAnnotationsReadOnly() { - Span span = new Span(1, 2, "http:name", 1L, Collections.emptyList(), 2L, true, - true, "process"); - span.logs().add(new Log(1, "1")); } @Test public void should_properly_serialize_object() throws JsonProcessingException { - Span span = new Span(1, 2, "http:name", 1L, - Collections.emptyList(), 2L, true, true, "process"); ObjectMapper objectMapper = new ObjectMapper(); String serializedName = objectMapper.writeValueAsString(span); @@ -94,8 +155,6 @@ public class SpanTests { } @Test public void should_properly_serialize_logs() throws IOException { - Span span = new Span(1, 2, "http:name", 1L, - Collections.emptyList(), 2L, true, true, "process"); span.logEvent("cs"); ObjectMapper objectMapper = new ObjectMapper(); @@ -108,8 +167,6 @@ public class SpanTests { } @Test public void should_properly_serialize_tags() throws IOException { - Span span = new Span(1, 2, "http:name", 1L, - Collections.emptyList(), 2L, true, true, "process"); span.tag("calculatedTax", "100"); ObjectMapper objectMapper = new ObjectMapper(); @@ -128,7 +185,7 @@ public class SpanTests { /** When going over a transport like spring-cloud-stream, we must retain the precise duration. */ @Test public void shouldSerializeDurationMicros() throws IOException { - Span span = Span.builder().traceId(1L).name("http:parent").remote(true).build(); + Span span = Span.builder().traceId(1L).name("http:parent").build(); span.stop(); assertThat(span.getAccumulatedMicros()) @@ -145,4 +202,41 @@ public class SpanTests { assertThat(deserialized.getAccumulatedMicros()) .isEqualTo(span.getAccumulatedMicros()); } + + // Duration of 0 is confusing to plot and can be misinterpreted as null + @Test public void getAccumulatedMicros_roundsUpToOneWhenRunning() throws IOException { + AtomicLong nanoTime = new AtomicLong(); + + // starts the span, recording its initial tick as zero + Span span = new Span(Span.builder().name("http:name").traceId(1L).spanId(2L)) { + @Override long nanoTime() { + return nanoTime.get(); + } + }; + + // When only 100 nanoseconds passed + nanoTime.set(100L); + + // We round so that we don't confuse "not started" with a short span. + assertThat(span.getAccumulatedMicros()).isEqualTo(1L); + } + + // Duration of 0 is confusing to plot and can be misinterpreted as null + @Test public void getAccumulatedMicros_roundsUpToOneWhenStopped() throws IOException { + final AtomicLong nanoTime = new AtomicLong(); + + // starts the span, recording its initial tick as zero + Span span = new Span(Span.builder().name("http:name").traceId(1L).spanId(2L)) { + @Override long nanoTime() { + return nanoTime.get(); + } + }; + + // When only 100 nanoseconds passed + nanoTime.set(100L); + span.stop(); + + // We round so that we don't confuse "not started" with a short span. + assertThat(span.getAccumulatedMicros()).isEqualTo(1L); + } } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue410/Issue410Tests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue410/Issue410Tests.java index 1c8e3b283..272230de7 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue410/Issue410Tests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue410/Issue410Tests.java @@ -81,7 +81,7 @@ public class Issue410Tests { try { String response = this.restTemplate.getForObject("http://localhost:" + port() + "/without_pool", String.class); - then(response).isEqualTo(Span.idToHex(span.getTraceId())); + then(response).isEqualTo(span.traceIdString()); Awaitility.await().until(() -> { then(this.asyncTask.getSpan().get()).isNotNull(); then(this.asyncTask.getSpan().get().getTraceId()).isEqualTo(span.getTraceId()); @@ -97,7 +97,7 @@ public class Issue410Tests { try { String response = this.restTemplate.getForObject("http://localhost:" + port() + "/with_pool", String.class); - then(response).isEqualTo(Span.idToHex(span.getTraceId())); + then(response).isEqualTo(span.traceIdString()); Awaitility.await().until(() -> { then(this.asyncTask.getSpan().get()).isNotNull(); then(this.asyncTask.getSpan().get().getTraceId()).isEqualTo(span.getTraceId()); @@ -116,7 +116,7 @@ public class Issue410Tests { try { String response = this.restTemplate.getForObject("http://localhost:" + port() + "/completable", String.class); - then(response).isEqualTo(Span.idToHex(span.getTraceId())); + then(response).isEqualTo(span.traceIdString()); Awaitility.await().until(() -> { then(this.asyncTask.getSpan().get()).isNotNull(); then(this.asyncTask.getSpan().get().getTraceId()).isEqualTo(span.getTraceId()); @@ -135,7 +135,7 @@ public class Issue410Tests { try { String response = this.restTemplate.getForObject("http://localhost:" + port() + "/taskScheduler", String.class); - then(response).isEqualTo(Span.idToHex(span.getTraceId())); + then(response).isEqualTo(span.traceIdString()); Awaitility.await().until(() -> { then(this.asyncTask.getSpan().get()).isNotNull(); then(this.asyncTask.getSpan().get().getTraceId()).isEqualTo(span.getTraceId()); @@ -273,7 +273,7 @@ class Application { public String withPool() { log.info("Executing with pool."); this.asyncTask.runWithPool(); - return Span.idToHex(this.tracer.getCurrentSpan().getTraceId()); + return this.tracer.getCurrentSpan().traceIdString(); } @@ -281,19 +281,19 @@ class Application { public String withoutPool() { log.info("Executing without pool."); this.asyncTask.runWithoutPool(); - return Span.idToHex(this.tracer.getCurrentSpan().getTraceId()); + return this.tracer.getCurrentSpan().traceIdString(); } @RequestMapping("/completable") public String completable() throws ExecutionException, InterruptedException { log.info("Executing completable"); - return Span.idToHex(this.asyncTask.completableFutures().getTraceId()); + return this.asyncTask.completableFutures().traceIdString(); } @RequestMapping("/taskScheduler") public String taskScheduler() throws ExecutionException, InterruptedException { log.info("Executing completable via task scheduler"); - return Span.idToHex(this.asyncTask.taskScheduler().getTraceId()); + return this.asyncTask.taskScheduler().traceIdString(); } /** diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingSpanExtractorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingSpanExtractorTests.java index acd296951..1070d43f4 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingSpanExtractorTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingSpanExtractorTests.java @@ -19,10 +19,10 @@ package org.springframework.cloud.sleuth.instrument.messaging; import java.util.HashMap; 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; @@ -56,6 +56,17 @@ public class MessagingSpanExtractorTests { } } + @Test + public void should_parse_128bit_trace_id() { + String traceId128 = "463ac35c9f6413ad48485a3953bb6124"; + + Span span = this.extractor.joinTrace( + MessageBuilder.createMessage("", + headers(traceId128, randomId()))); + + then(span.traceIdString()).isEqualTo(traceId128); + } + @Test public void should_set_random_spanid_if_header_value_is_invalid() { Message message = MessageBuilder.createMessage("", diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/HttpServletRequestExtractorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/HttpServletRequestExtractorTests.java index 6cc85427f..04dd26f95 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/HttpServletRequestExtractorTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/HttpServletRequestExtractorTests.java @@ -79,7 +79,7 @@ public class HttpServletRequestExtractorTests { } @Test - public void should_downgrade_128bit_trace_id_by_dropping_high_bits() { + public void should_accept_128bit_trace_id() { String hex128Bits = "463ac35c9f6413ad48485a3953bb6124"; String lower64Bits = "48485a3953bb6124"; @@ -90,6 +90,6 @@ public class HttpServletRequestExtractorTests { Span span = this.extractor.joinTrace(this.request); - then(span.getTraceId()).isEqualTo(Span.hexToId(lower64Bits)); + then(span.traceIdString()).isEqualTo(hex128Bits); } } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceCustomFilterResponseInjectorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceCustomFilterResponseInjectorTests.java index 1783bbfce..a2f65aee1 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceCustomFilterResponseInjectorTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceCustomFilterResponseInjectorTests.java @@ -119,7 +119,7 @@ public class TraceCustomFilterResponseInjectorTests { @Override public void inject(Span span, HttpServletResponse carrier) { - carrier.addHeader(Span.TRACE_ID_NAME, Span.idToHex(span.getTraceId())); + carrier.addHeader(Span.TRACE_ID_NAME, span.traceIdString()); carrier.addHeader(Span.SPAN_ID_NAME, Span.idToHex(span.getSpanId())); } } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/log/Slf4JSpanLoggerTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/log/Slf4JSpanLoggerTest.java index 29b3afea6..649ea1a9d 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/log/Slf4JSpanLoggerTest.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/log/Slf4JSpanLoggerTest.java @@ -20,8 +20,10 @@ import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; import org.slf4j.Logger; +import org.slf4j.MDC; import org.springframework.cloud.sleuth.Span; +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.then; import static org.mockito.Matchers.anyList; @@ -42,9 +44,46 @@ public class Slf4JSpanLoggerTest { @Before public void setup() { + MDC.clear(); given(log.isTraceEnabled()).willReturn(true); } + @Test + public void when_start_event_arrived_should_add_64bit_trace_id_to_MDC() throws Exception { + Span span = Span.builder().traceId(1L).spanId(2L).build(); + + this.slf4JSpanLogger.logStartedSpan(this.spanWithNameNotToBeExcluded, span); + + assertThat(MDC.get("X-B3-TraceId")).isEqualTo("0000000000000001"); + } + + @Test + public void when_start_event_arrived_should_add_128bit_trace_id_to_MDC() throws Exception { + Span span = Span.builder().traceIdHigh(1L).traceId(2L).spanId(3L).build(); + + this.slf4JSpanLogger.logStartedSpan(this.spanWithNameNotToBeExcluded, span); + + assertThat(MDC.get("X-B3-TraceId")).isEqualTo("00000000000000010000000000000002"); + } + + @Test + public void when_continued_event_arrived_should_add_64bit_trace_id_to_MDC() throws Exception { + Span span = Span.builder().traceId(1L).spanId(2L).build(); + + this.slf4JSpanLogger.logContinuedSpan(span); + + assertThat(MDC.get("X-B3-TraceId")).isEqualTo("0000000000000001"); + } + + @Test + public void when_continued_event_arrived_should_add_128bit_trace_id_to_MDC() throws Exception { + Span span = Span.builder().traceIdHigh(1L).traceId(2L).spanId(3L).build(); + + this.slf4JSpanLogger.logContinuedSpan(span); + + assertThat(MDC.get("X-B3-TraceId")).isEqualTo("00000000000000010000000000000002"); + } + @Test public void should_log_when_start_event_arrived_and_pattern_doesnt_match_span_name() throws Exception { this.slf4JSpanLogger.logStartedSpan(this.spanWithNameNotToBeExcluded, diff --git a/spring-cloud-sleuth-zipkin-stream/src/main/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanList.java b/spring-cloud-sleuth-zipkin-stream/src/main/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanList.java index 7a1e2bd47..7bd83c15b 100644 --- a/spring-cloud-sleuth-zipkin-stream/src/main/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanList.java +++ b/spring-cloud-sleuth-zipkin-stream/src/main/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanList.java @@ -99,6 +99,7 @@ final class ConvertToZipkinSpanList { zipkinSpan.duration(calculateDurationInMicros(span)); } } + zipkinSpan.traceIdHigh(span.getTraceIdHigh()); zipkinSpan.traceId(span.getTraceId()); if (span.getParents().size() > 0) { if (span.getParents().size() > 1) { diff --git a/spring-cloud-sleuth-zipkin-stream/src/test/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanListTests.java b/spring-cloud-sleuth-zipkin-stream/src/test/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanListTests.java index 4aa3cac91..26161e080 100644 --- a/spring-cloud-sleuth-zipkin-stream/src/test/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanListTests.java +++ b/spring-cloud-sleuth-zipkin-stream/src/test/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanListTests.java @@ -19,12 +19,10 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Random; - import org.junit.Test; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.stream.Host; import org.springframework.cloud.sleuth.stream.Spans; - import zipkin.Constants; import static org.assertj.core.api.Assertions.assertThat; @@ -183,13 +181,24 @@ public class ConvertToZipkinSpanListTests { .isNull(); } + @Test + public void converts128BitTraceId() { + Span span = Span.builder().traceIdHigh(1L).traceId(2L).spanId(3L).name("foo").build(); + + Spans spans = new Spans(this.host, Collections.singletonList(span)); + zipkin.Span result = ConvertToZipkinSpanList.convert(spans).get(0); + + assertThat(result.traceIdHigh).isEqualTo(span.getTraceIdHigh()); + assertThat(result.traceId).isEqualTo(span.getTraceId()); + } + Span span(String name) { return span(name, false); } Span span(String name, boolean remote) { Long id = new Random().nextLong(); - return new Span(1, 3, "message:" + name, id, Collections.emptyList(), id, remote, true, - "process"); + return Span.builder().begin(1).end(3).name("message:" + name).traceId(id).spanId(id) + .remote(remote).processId("process").build(); } } diff --git a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListener.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListener.java index 116a876e4..46a34f5b5 100644 --- a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListener.java +++ b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListener.java @@ -95,6 +95,7 @@ public class ZipkinSpanListener implements SpanReporter { zipkinSpan.duration(calculateDurationInMicros(span)); } } + zipkinSpan.traceIdHigh(span.getTraceIdHigh()); zipkinSpan.traceId(span.getTraceId()); if (span.getParents().size() > 0) { if (span.getParents().size() > 1) { diff --git a/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListenerTests.java b/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListenerTests.java index a99e4d267..239754a49 100644 --- a/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListenerTests.java +++ b/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListenerTests.java @@ -215,6 +215,16 @@ public class ZipkinSpanListenerTests { .isNotEmpty(); } + @Test + public void converts128BitTraceId() { + Span span = Span.builder().traceIdHigh(1L).traceId(2L).spanId(3L).name("foo").build(); + + zipkin.Span result = this.spanReporter.convert(span); + + assertThat(result.traceIdHigh).isEqualTo(span.getTraceIdHigh()); + assertThat(result.traceId).isEqualTo(span.getTraceId()); + } + @Test public void shouldReuseServerAddressTag() { this.parent.logEvent(Constants.CLIENT_SEND); From 85d4ffef82210649edd26c428c0cab77c1b80b50 Mon Sep 17 00:00:00 2001 From: Adrian Cole Date: Fri, 18 Nov 2016 16:41:00 +0800 Subject: [PATCH 132/235] Updates to latest Zipkin 1.16.2 (#454) Here are the features relevant to sleuth * MySQL support of 128-bit group-by * don't stack overflow on json write bug * more forgiving of IPv4-mapped addresses in json --- spring-cloud-sleuth-dependencies/pom.xml | 4 ++-- spring-cloud-sleuth-samples/pom.xml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/spring-cloud-sleuth-dependencies/pom.xml b/spring-cloud-sleuth-dependencies/pom.xml index 25666c929..0442a162f 100644 --- a/spring-cloud-sleuth-dependencies/pom.xml +++ b/spring-cloud-sleuth-dependencies/pom.xml @@ -14,8 +14,8 @@ spring-cloud-sleuth-dependencies Spring Cloud Sleuth Dependencies - 1.14.4 - 0.6.6 + 1.16.2 + 0.6.9 diff --git a/spring-cloud-sleuth-samples/pom.xml b/spring-cloud-sleuth-samples/pom.xml index 5c41b2df4..83398de89 100644 --- a/spring-cloud-sleuth-samples/pom.xml +++ b/spring-cloud-sleuth-samples/pom.xml @@ -59,12 +59,12 @@ io.zipkin.java zipkin - 1.14.4 + 1.16.2 io.zipkin.java zipkin-server - 1.14.4 + 1.16.2 From b0c8f26d803e842c7d861de81e238aaaa6ca181f Mon Sep 17 00:00:00 2001 From: Adrian Cole Date: Fri, 18 Nov 2016 16:41:00 +0800 Subject: [PATCH 133/235] Updates to latest Zipkin 1.16.2 (#454) Here are the features relevant to sleuth * MySQL support of 128-bit group-by * don't stack overflow on json write bug * more forgiving of IPv4-mapped addresses in json --- spring-cloud-sleuth-dependencies/pom.xml | 4 ++-- spring-cloud-sleuth-samples/pom.xml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/spring-cloud-sleuth-dependencies/pom.xml b/spring-cloud-sleuth-dependencies/pom.xml index fe99ab3a7..b78f329da 100644 --- a/spring-cloud-sleuth-dependencies/pom.xml +++ b/spring-cloud-sleuth-dependencies/pom.xml @@ -14,8 +14,8 @@ spring-cloud-sleuth-dependencies Spring Cloud Sleuth Dependencies - 1.14.4 - 0.6.6 + 1.16.2 + 0.6.9 diff --git a/spring-cloud-sleuth-samples/pom.xml b/spring-cloud-sleuth-samples/pom.xml index 0960508e2..d7317f3d5 100644 --- a/spring-cloud-sleuth-samples/pom.xml +++ b/spring-cloud-sleuth-samples/pom.xml @@ -59,12 +59,12 @@ io.zipkin.java zipkin - 1.14.4 + 1.16.2 io.zipkin.java zipkin-server - 1.14.4 + 1.16.2 From 02e6eb574527ea9726acc0d309b441a88622f13e Mon Sep 17 00:00:00 2001 From: Adrian Cole Date: Fri, 18 Nov 2016 20:10:00 +0800 Subject: [PATCH 134/235] Starts 128-bit traces when spring.sleuth.traceId128=true (#455) This adds autoconfiguration to create 128-bit traces when `spring.sleuth.traceId128=true`. --- .../sleuth/autoconfig/SleuthProperties.java | 38 +++++++++ .../autoconfig/TraceAutoConfiguration.java | 7 +- .../cloud/sleuth/trace/DefaultTracer.java | 37 ++++++--- .../TraceAutoConfigurationTest.java | 82 +++++++++++++++++++ 4 files changed, 152 insertions(+), 12 deletions(-) create mode 100644 spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/SleuthProperties.java create mode 100644 spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfigurationTest.java 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 new file mode 100644 index 000000000..57036095a --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/SleuthProperties.java @@ -0,0 +1,38 @@ +/* + * Copyright 2013-2015 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.autoconfig; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Sleuth settings + * + * @since 1.0.11 + */ +@ConfigurationProperties("spring.sleuth") +public class SleuthProperties { + /** When true, generate 128-bit trace IDs instead of 64-bit ones. */ + private boolean traceId128 = false; + + public boolean isTraceId128() { + return this.traceId128; + } + + public void setTraceId128(boolean traceId128) { + this.traceId128 = traceId128; + } +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfiguration.java index 579f81f5b..e4a5fcf54 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfiguration.java @@ -18,6 +18,7 @@ package org.springframework.cloud.sleuth.autoconfig; import java.util.Random; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; @@ -44,8 +45,10 @@ import org.springframework.context.annotation.Configuration; */ @Configuration @ConditionalOnProperty(value="spring.sleuth.enabled", matchIfMissing=true) -@EnableConfigurationProperties(TraceKeys.class) +@EnableConfigurationProperties({TraceKeys.class, SleuthProperties.class}) public class TraceAutoConfiguration { + @Autowired + SleuthProperties properties; @Bean @ConditionalOnMissingBean @@ -65,7 +68,7 @@ public class TraceAutoConfiguration { SpanNamer spanNamer, SpanLogger spanLogger, SpanReporter spanReporter) { return new DefaultTracer(sampler, random, spanNamer, spanLogger, - spanReporter); + spanReporter, this.properties.isTraceId128()); } @Bean 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 e1bc938b6..48e5a4b60 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 @@ -18,7 +18,6 @@ package org.springframework.cloud.sleuth.trace; import java.util.Random; import java.util.concurrent.Callable; - import org.springframework.cloud.sleuth.Sampler; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.SpanNamer; @@ -47,13 +46,21 @@ public class DefaultTracer implements Tracer { private final SpanReporter spanReporter; + private final boolean traceId128; + public DefaultTracer(Sampler defaultSampler, Random random, SpanNamer spanNamer, SpanLogger spanLogger, SpanReporter spanReporter) { + this(defaultSampler, random, spanNamer, spanLogger, spanReporter, false); + } + + public DefaultTracer(Sampler defaultSampler, Random random, SpanNamer spanNamer, + SpanLogger spanLogger, SpanReporter spanReporter, boolean traceId128) { this.defaultSampler = defaultSampler; this.random = random; this.spanNamer = spanNamer; this.spanLogger = spanLogger; this.spanReporter = spanReporter; + this.traceId128 = traceId128; } @Override @@ -77,12 +84,14 @@ public class DefaultTracer implements Tracer { } else { long id = createId(); - span = Span.builder().name(name).traceId(id) + span = Span.builder().name(name) + .traceIdHigh(this.traceId128 ? createId() : 0L) + .traceId(id) .spanId(id).build(); - if (sampler==null) { + if (sampler == null) { sampler = this.defaultSampler; } - span = sampledSpan(name, id, span, sampler); + span = sampledSpan(span, sampler); this.spanLogger.logStartedSpan(null, span); } return continueSpan(span); @@ -142,8 +151,10 @@ public class DefaultTracer implements Tracer { long id = createId(); if (parent == null) { Span span = Span.builder().name(name) - .traceId(id).spanId(id).build(); - span = sampledSpan(name, id, span, this.defaultSampler); + .traceIdHigh(this.traceId128 ? createId() : 0L) + .traceId(id) + .spanId(id).build(); + span = sampledSpan(span, this.defaultSampler); this.spanLogger.logStartedSpan(null, span); return span; } @@ -152,6 +163,7 @@ public class DefaultTracer implements Tracer { SpanContextHolder.push(parent, true); } Span span = Span.builder().name(name) + .traceIdHigh(parent.getTraceIdHigh()) .traceId(parent.getTraceId()).parent(parent.getSpanId()).spanId(id) .processId(parent.getProcessId()).savedSpan(parent) .exportable(parent.isExportable()).build(); @@ -160,11 +172,16 @@ public class DefaultTracer implements Tracer { } } - private Span sampledSpan(String name, long id, Span span, Sampler sampler) { + private Span sampledSpan(Span span, Sampler sampler) { if (!sampler.isSampled(span)) { - // Non-exportable so we keep the trace but not other data - return Span.builder().begin(span.getBegin()).name(name).traceId(id) - .spanId(id).exportable(false).build(); + // Copy everything, except set exportable to false + return Span.builder() + .begin(span.getBegin()) + .traceIdHigh(span.getTraceIdHigh()) + .traceId(span.getTraceId()) + .spanId(span.getSpanId()) + .name(span.getName()) + .exportable(false).build(); } return span; } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfigurationTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfigurationTest.java new file mode 100644 index 000000000..b0dfe97d2 --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfigurationTest.java @@ -0,0 +1,82 @@ +/** + * Copyright 2015-2016 The OpenZipkin 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.autoconfig; + +import org.junit.After; +import org.junit.Test; +import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; +import org.springframework.cloud.sleuth.Span; +import org.springframework.cloud.sleuth.Tracer; +import org.springframework.cloud.sleuth.log.SleuthLogAutoConfiguration; +import org.springframework.cloud.sleuth.sampler.NeverSampler; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.boot.test.util.EnvironmentTestUtils.addEnvironment; + +public class TraceAutoConfigurationTest { + + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + + @After + public void close() { + context.close(); + } + + @Test + public void defaultsTo64BitTraceId() { + context = new AnnotationConfigApplicationContext(); + context.register( + PropertyPlaceholderAutoConfiguration.class, + SleuthLogAutoConfiguration.class, + TraceAutoConfiguration.class + ); + context.refresh(); + Tracer tracer = context.getBean(Tracer.class); + + Span span = null; + try { + span = tracer.createSpan("foo", NeverSampler.INSTANCE); + assertThat(span.getTraceIdHigh()).isEqualTo(0L); + assertThat(span.getTraceId()).isNotEqualTo(0L); + } finally { + if (span != null){ + tracer.close(span); + } + } + } + + @Test + public void optInto128BitTraceId() { + addEnvironment(context, "spring.sleuth.traceId128:true"); + context.register( + PropertyPlaceholderAutoConfiguration.class, + SleuthLogAutoConfiguration.class, + TraceAutoConfiguration.class + ); + context.refresh(); + Tracer tracer = context.getBean(Tracer.class); + + Span span = null; + try { + span = tracer.createSpan("foo", NeverSampler.INSTANCE); + assertThat(span.getTraceIdHigh()).isNotEqualTo(0L); + assertThat(span.getTraceId()).isNotEqualTo(0L); + } finally { + if (span != null){ + tracer.close(span); + } + } + } +} From 24e52c690d982622944a9ba520f9a6e243b5bf1e Mon Sep 17 00:00:00 2001 From: Adrian Cole Date: Fri, 18 Nov 2016 22:58:34 +0800 Subject: [PATCH 135/235] Starts 128-bit traces when spring.sleuth.traceId128=true (#455) (#456) This adds autoconfiguration to create 128-bit traces when `spring.sleuth.traceId128=true`. --- .../sleuth/autoconfig/SleuthProperties.java | 38 +++++++++ .../autoconfig/TraceAutoConfiguration.java | 7 +- .../cloud/sleuth/trace/DefaultTracer.java | 47 ++++++++--- .../TraceAutoConfigurationTest.java | 82 +++++++++++++++++++ 4 files changed, 160 insertions(+), 14 deletions(-) create mode 100644 spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/SleuthProperties.java create mode 100644 spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfigurationTest.java 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 new file mode 100644 index 000000000..57036095a --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/SleuthProperties.java @@ -0,0 +1,38 @@ +/* + * Copyright 2013-2015 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.autoconfig; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Sleuth settings + * + * @since 1.0.11 + */ +@ConfigurationProperties("spring.sleuth") +public class SleuthProperties { + /** When true, generate 128-bit trace IDs instead of 64-bit ones. */ + private boolean traceId128 = false; + + public boolean isTraceId128() { + return this.traceId128; + } + + public void setTraceId128(boolean traceId128) { + this.traceId128 = traceId128; + } +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfiguration.java index 579f81f5b..e4a5fcf54 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfiguration.java @@ -18,6 +18,7 @@ package org.springframework.cloud.sleuth.autoconfig; import java.util.Random; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; @@ -44,8 +45,10 @@ import org.springframework.context.annotation.Configuration; */ @Configuration @ConditionalOnProperty(value="spring.sleuth.enabled", matchIfMissing=true) -@EnableConfigurationProperties(TraceKeys.class) +@EnableConfigurationProperties({TraceKeys.class, SleuthProperties.class}) public class TraceAutoConfiguration { + @Autowired + SleuthProperties properties; @Bean @ConditionalOnMissingBean @@ -65,7 +68,7 @@ public class TraceAutoConfiguration { SpanNamer spanNamer, SpanLogger spanLogger, SpanReporter spanReporter) { return new DefaultTracer(sampler, random, spanNamer, spanLogger, - spanReporter); + spanReporter, this.properties.isTraceId128()); } @Bean 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 360bde3f0..a1cb5edd0 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 @@ -18,7 +18,6 @@ package org.springframework.cloud.sleuth.trace; import java.util.Random; import java.util.concurrent.Callable; - import org.springframework.cloud.sleuth.Sampler; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.SpanNamer; @@ -47,13 +46,21 @@ public class DefaultTracer implements Tracer { private final SpanReporter spanReporter; - public DefaultTracer(Sampler defaultSampler, Random random, SpanNamer spanNamer, SpanLogger spanLogger, - SpanReporter spanReporter) { + private final boolean traceId128; + + public DefaultTracer(Sampler defaultSampler, Random random, SpanNamer spanNamer, + SpanLogger spanLogger, SpanReporter spanReporter) { + this(defaultSampler, random, spanNamer, spanLogger, spanReporter, false); + } + + public DefaultTracer(Sampler defaultSampler, Random random, SpanNamer spanNamer, + SpanLogger spanLogger, SpanReporter spanReporter, boolean traceId128) { this.defaultSampler = defaultSampler; this.random = random; this.spanNamer = spanNamer; this.spanLogger = spanLogger; this.spanReporter = spanReporter; + this.traceId128 = traceId128; } @Override @@ -77,11 +84,14 @@ public class DefaultTracer implements Tracer { } else { long id = createId(); - span = Span.builder().name(name).traceId(id).spanId(id).build(); + span = Span.builder().name(name) + .traceIdHigh(this.traceId128 ? createId() : 0L) + .traceId(id) + .spanId(id).build(); if (sampler == null) { sampler = this.defaultSampler; } - span = sampledSpan(name, id, span, sampler); + span = sampledSpan(span, sampler); this.spanLogger.logStartedSpan(null, span); } return continueSpan(span); @@ -139,8 +149,11 @@ public class DefaultTracer implements Tracer { protected Span createChild(Span parent, String name) { long id = createId(); if (parent == null) { - Span span = Span.builder().name(name).traceId(id).spanId(id).build(); - span = sampledSpan(name, id, span, this.defaultSampler); + Span span = Span.builder().name(name) + .traceIdHigh(this.traceId128 ? createId() : 0L) + .traceId(id) + .spanId(id).build(); + span = sampledSpan(span, this.defaultSampler); this.spanLogger.logStartedSpan(null, span); return span; } @@ -148,17 +161,27 @@ public class DefaultTracer implements Tracer { if (!isTracing()) { SpanContextHolder.push(parent, true); } - Span span = Span.builder().name(name).traceId(parent.getTraceId()).parent(parent.getSpanId()).spanId(id) - .processId(parent.getProcessId()).savedSpan(parent).exportable(parent.isExportable()).build(); + Span span = Span.builder().name(name) + .traceIdHigh(parent.getTraceIdHigh()) + .traceId(parent.getTraceId()).parent(parent.getSpanId()).spanId(id) + .processId(parent.getProcessId()).savedSpan(parent) + .exportable(parent.isExportable()) + .build(); this.spanLogger.logStartedSpan(parent, span); return span; } } - private Span sampledSpan(String name, long id, Span span, Sampler sampler) { + private Span sampledSpan(Span span, Sampler sampler) { if (!sampler.isSampled(span)) { - // Non-exportable so we keep the trace but not other data - return Span.builder().begin(span.getBegin()).name(name).traceId(id).spanId(id).exportable(false).build(); + // Copy everything, except set exportable to false + return Span.builder() + .begin(span.getBegin()) + .traceIdHigh(span.getTraceIdHigh()) + .traceId(span.getTraceId()) + .spanId(span.getSpanId()) + .name(span.getName()) + .exportable(false).build(); } return span; } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfigurationTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfigurationTest.java new file mode 100644 index 000000000..389760318 --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfigurationTest.java @@ -0,0 +1,82 @@ +/** + * Copyright 2015-2016 The OpenZipkin 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.autoconfig; + +import org.junit.After; +import org.junit.Test; +import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; +import org.springframework.cloud.sleuth.Span; +import org.springframework.cloud.sleuth.Tracer; +import org.springframework.cloud.sleuth.log.SleuthLogAutoConfiguration; +import org.springframework.cloud.sleuth.sampler.NeverSampler; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.boot.test.EnvironmentTestUtils.addEnvironment; + +public class TraceAutoConfigurationTest { + + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + + @After + public void close() { + context.close(); + } + + @Test + public void defaultsTo64BitTraceId() { + context = new AnnotationConfigApplicationContext(); + context.register( + PropertyPlaceholderAutoConfiguration.class, + SleuthLogAutoConfiguration.class, + TraceAutoConfiguration.class + ); + context.refresh(); + Tracer tracer = context.getBean(Tracer.class); + + Span span = null; + try { + span = tracer.createSpan("foo", NeverSampler.INSTANCE); + assertThat(span.getTraceIdHigh()).isEqualTo(0L); + assertThat(span.getTraceId()).isNotEqualTo(0L); + } finally { + if (span != null){ + tracer.close(span); + } + } + } + + @Test + public void optInto128BitTraceId() { + addEnvironment(context, "spring.sleuth.traceId128:true"); + context.register( + PropertyPlaceholderAutoConfiguration.class, + SleuthLogAutoConfiguration.class, + TraceAutoConfiguration.class + ); + context.refresh(); + Tracer tracer = context.getBean(Tracer.class); + + Span span = null; + try { + span = tracer.createSpan("foo", NeverSampler.INSTANCE); + assertThat(span.getTraceIdHigh()).isNotEqualTo(0L); + assertThat(span.getTraceId()).isNotEqualTo(0L); + } finally { + if (span != null){ + tracer.close(span); + } + } + } +} From a3c0f816713f2606acaaa39a51b3ec444c73ceb0 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Mon, 21 Nov 2016 16:41:20 +0100 Subject: [PATCH 136/235] Fixed wrong proxying of executors without this change tracing worked fine but the custom types were not registered as beans. Thus autowiring of them was not possible with this change the bean post processor is removed and an aspect is used - that way tracing is still working fine but we don't interfere in bean registration fixes #445 --- .../async/AsyncDefaultAutoConfiguration.java | 9 +-- .../LazyTraceThreadPoolTaskExecutor.java | 6 ++ .../instrument/async/TraceAsyncAspect.java | 42 ++++++++++++- .../async/TraceExecutorBeanPostProcessor.java | 60 ------------------- .../async/issues/issue410/Issue410Tests.java | 25 ++++---- 5 files changed, 60 insertions(+), 82 deletions(-) delete mode 100644 spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceExecutorBeanPostProcessor.java diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/AsyncDefaultAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/AsyncDefaultAutoConfiguration.java index 38b5d9b65..fc4c7ac26 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/AsyncDefaultAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/AsyncDefaultAutoConfiguration.java @@ -51,6 +51,8 @@ import org.springframework.scheduling.annotation.EnableAsync; @AutoConfigureAfter(AsyncCustomAutoConfiguration.class) public class AsyncDefaultAutoConfiguration { + @Autowired private BeanFactory beanFactory; + @Configuration @ConditionalOnMissingBean(AsyncConfigurer.class) @ConditionalOnProperty(value = "spring.sleuth.async.configurer.enabled", matchIfMissing = true) @@ -66,12 +68,7 @@ public class AsyncDefaultAutoConfiguration { @Bean public TraceAsyncAspect traceAsyncAspect(Tracer tracer, TraceKeys traceKeys) { - return new TraceAsyncAspect(tracer, traceKeys); - } - - @Bean - public TraceExecutorBeanPostProcessor traceExecutorBeanPostProcessor(BeanFactory beanFactory) { - return new TraceExecutorBeanPostProcessor(beanFactory); + return new TraceAsyncAspect(tracer, traceKeys, this.beanFactory); } } \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/LazyTraceThreadPoolTaskExecutor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/LazyTraceThreadPoolTaskExecutor.java index b09925bab..30d83f720 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/LazyTraceThreadPoolTaskExecutor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/LazyTraceThreadPoolTaskExecutor.java @@ -19,6 +19,7 @@ package org.springframework.cloud.sleuth.instrument.async; import java.lang.invoke.MethodHandles; import java.util.concurrent.Callable; import java.util.concurrent.Future; +import java.util.concurrent.ThreadPoolExecutor; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -84,6 +85,11 @@ public class LazyTraceThreadPoolTaskExecutor extends ThreadPoolTaskExecutor { return this.delegate.submitListenable(new SpanContinuingTraceCallable<>(tracer(), traceKeys(), spanNamer(), task)); } + @Override + public ThreadPoolExecutor getThreadPoolExecutor() throws IllegalStateException { + return this.delegate.getThreadPoolExecutor(); + } + public void destroy() { this.delegate.destroy(); super.destroy(); diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceAsyncAspect.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceAsyncAspect.java index 361a5c1f3..76aa0bf4c 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceAsyncAspect.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceAsyncAspect.java @@ -16,13 +16,20 @@ package org.springframework.cloud.sleuth.instrument.async; +import java.lang.reflect.Method; +import java.util.concurrent.Executor; + import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.beans.factory.BeanFactory; import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.TraceKeys; +import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.util.SpanNameUtil; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.util.ReflectionUtils; /** * Aspect that creates a new Span for running threads executing methods annotated with @@ -40,10 +47,12 @@ public class TraceAsyncAspect { private final Tracer tracer; private final TraceKeys traceKeys; + private final BeanFactory beanFactory; - public TraceAsyncAspect(Tracer tracer, TraceKeys traceKeys) { + public TraceAsyncAspect(Tracer tracer, TraceKeys traceKeys, BeanFactory beanFactory) { this.tracer = tracer; this.traceKeys = traceKeys; + this.beanFactory = beanFactory; } @Around("execution (@org.springframework.scheduling.annotation.Async * *.*(..))") @@ -62,4 +71,33 @@ public class TraceAsyncAspect { } } + @Around("execution (* org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor.*(..))") + public Object traceThreadPoolTaskExecutor(final ProceedingJoinPoint pjp) throws Throwable { + LazyTraceThreadPoolTaskExecutor executor = new LazyTraceThreadPoolTaskExecutor(this.beanFactory, + (ThreadPoolTaskExecutor) pjp.getTarget()); + Method methodOnTracedBean = getMethod(pjp, executor); + if (methodOnTracedBean != null) { + return methodOnTracedBean.invoke(executor, pjp.getArgs()); + } + return pjp.proceed(); + } + + @Around("execution (* java.util.concurrent.Executor.*(..))") + public Object traceExecutor(final ProceedingJoinPoint pjp) throws Throwable { + LazyTraceExecutor executor = new LazyTraceExecutor(this.beanFactory, + (Executor) pjp.getTarget()); + Method methodOnTracedBean = getMethod(pjp, executor); + if (methodOnTracedBean != null) { + return methodOnTracedBean.invoke(executor, pjp.getArgs()); + } + return pjp.proceed(); + } + + private Method getMethod(ProceedingJoinPoint pjp, Object object) { + MethodSignature signature = (MethodSignature) pjp.getSignature(); + Method method = signature.getMethod(); + return ReflectionUtils + .findMethod(object.getClass(), method.getName(), method.getParameterTypes()); + } + } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceExecutorBeanPostProcessor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceExecutorBeanPostProcessor.java deleted file mode 100644 index 3d59e9e48..000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceExecutorBeanPostProcessor.java +++ /dev/null @@ -1,60 +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.async; - -import java.util.concurrent.Executor; -import java.util.concurrent.ExecutorService; - -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.config.BeanPostProcessor; -import org.springframework.scheduling.TaskScheduler; -import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; - -/** - * Bean post processor that wraps {@link Executor} in its Trace representation - * - * @author Marcin Grzejszczak - * @since 1.0.10 - */ -class TraceExecutorBeanPostProcessor implements BeanPostProcessor { - - private final BeanFactory beanFactory; - - public TraceExecutorBeanPostProcessor(BeanFactory beanFactory) { - this.beanFactory = beanFactory; - } - - @Override public Object postProcessBeforeInitialization(Object bean, String beanName) - throws BeansException { - if (bean instanceof ThreadPoolTaskExecutor && !(bean instanceof TaskScheduler) && - !(bean instanceof LazyTraceThreadPoolTaskExecutor)) { - return new LazyTraceThreadPoolTaskExecutor(this.beanFactory, (ThreadPoolTaskExecutor) bean); - } else if (bean instanceof Executor && !(bean instanceof ExecutorService) && - !(bean instanceof TaskScheduler) && !(bean instanceof LazyTraceExecutor)) { - return new LazyTraceExecutor(this.beanFactory, (Executor) bean); - } else if (bean instanceof ExecutorService) { - return new TraceableExecutorService(this.beanFactory, (ExecutorService) bean); - } - return bean; - } - - @Override public Object postProcessAfterInitialization(Object bean, String beanName) - throws BeansException { - return bean; - } -} diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue410/Issue410Tests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue410/Issue410Tests.java index 272230de7..c7572f627 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue410/Issue410Tests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue410/Issue410Tests.java @@ -19,8 +19,6 @@ package org.springframework.cloud.sleuth.instrument.async.issues.issue410; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicReference; import org.apache.commons.logging.Log; @@ -38,7 +36,6 @@ import org.springframework.cloud.sleuth.Sampler; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.instrument.async.LazyTraceExecutor; -import org.springframework.cloud.sleuth.instrument.async.TraceableExecutorService; import org.springframework.cloud.sleuth.sampler.AlwaysSampler; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -73,7 +70,7 @@ public class Issue410Tests { /** * Related to issue #445 */ - @Autowired ExecutorService executorService; + @Autowired Application.MyService executorService; @Test public void should_pass_tracing_info_for_tasks_running_without_a_pool() { @@ -145,14 +142,6 @@ public class Issue410Tests { } } - /** - * Related to issue #445 - */ - @Test - public void should_wrap_executor_service_in_trace_representation() { - then(this.executorService).isInstanceOf(TraceableExecutorService.class); - } - private int port() { return this.environment.getProperty("local.server.port", Integer.class); } @@ -299,8 +288,16 @@ class Application { /** * Related to issue #445 */ - @Bean public ExecutorService executorService() { - return Executors.newSingleThreadExecutor(); + @Bean public MyService executorService() { + return new MyService() { + @Override public void execute(Runnable command) { + + } + }; + } + + interface MyService extends Executor { + } } From 06ab758d69142c83b484c7c07a588f3087b23f8e Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Mon, 21 Nov 2016 16:41:20 +0100 Subject: [PATCH 137/235] Fixed wrong proxying of executors without this change tracing worked fine but the custom types were not registered as beans. Thus autowiring of them was not possible with this change the bean post processor is removed and an aspect is used - that way tracing is still working fine but we don't interfere in bean registration fixes #445 --- .../async/AsyncDefaultAutoConfiguration.java | 9 +-- .../LazyTraceThreadPoolTaskExecutor.java | 6 ++ .../instrument/async/TraceAsyncAspect.java | 42 ++++++++++++- .../async/TraceExecutorBeanPostProcessor.java | 60 ------------------- .../async/issues/issue410/Issue410Tests.java | 25 ++++---- 5 files changed, 60 insertions(+), 82 deletions(-) delete mode 100644 spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceExecutorBeanPostProcessor.java diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/AsyncDefaultAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/AsyncDefaultAutoConfiguration.java index 38b5d9b65..fc4c7ac26 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/AsyncDefaultAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/AsyncDefaultAutoConfiguration.java @@ -51,6 +51,8 @@ import org.springframework.scheduling.annotation.EnableAsync; @AutoConfigureAfter(AsyncCustomAutoConfiguration.class) public class AsyncDefaultAutoConfiguration { + @Autowired private BeanFactory beanFactory; + @Configuration @ConditionalOnMissingBean(AsyncConfigurer.class) @ConditionalOnProperty(value = "spring.sleuth.async.configurer.enabled", matchIfMissing = true) @@ -66,12 +68,7 @@ public class AsyncDefaultAutoConfiguration { @Bean public TraceAsyncAspect traceAsyncAspect(Tracer tracer, TraceKeys traceKeys) { - return new TraceAsyncAspect(tracer, traceKeys); - } - - @Bean - public TraceExecutorBeanPostProcessor traceExecutorBeanPostProcessor(BeanFactory beanFactory) { - return new TraceExecutorBeanPostProcessor(beanFactory); + return new TraceAsyncAspect(tracer, traceKeys, this.beanFactory); } } \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/LazyTraceThreadPoolTaskExecutor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/LazyTraceThreadPoolTaskExecutor.java index b09925bab..30d83f720 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/LazyTraceThreadPoolTaskExecutor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/LazyTraceThreadPoolTaskExecutor.java @@ -19,6 +19,7 @@ package org.springframework.cloud.sleuth.instrument.async; import java.lang.invoke.MethodHandles; import java.util.concurrent.Callable; import java.util.concurrent.Future; +import java.util.concurrent.ThreadPoolExecutor; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -84,6 +85,11 @@ public class LazyTraceThreadPoolTaskExecutor extends ThreadPoolTaskExecutor { return this.delegate.submitListenable(new SpanContinuingTraceCallable<>(tracer(), traceKeys(), spanNamer(), task)); } + @Override + public ThreadPoolExecutor getThreadPoolExecutor() throws IllegalStateException { + return this.delegate.getThreadPoolExecutor(); + } + public void destroy() { this.delegate.destroy(); super.destroy(); diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceAsyncAspect.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceAsyncAspect.java index 361a5c1f3..76aa0bf4c 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceAsyncAspect.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceAsyncAspect.java @@ -16,13 +16,20 @@ package org.springframework.cloud.sleuth.instrument.async; +import java.lang.reflect.Method; +import java.util.concurrent.Executor; + import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.beans.factory.BeanFactory; import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.TraceKeys; +import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.util.SpanNameUtil; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.util.ReflectionUtils; /** * Aspect that creates a new Span for running threads executing methods annotated with @@ -40,10 +47,12 @@ public class TraceAsyncAspect { private final Tracer tracer; private final TraceKeys traceKeys; + private final BeanFactory beanFactory; - public TraceAsyncAspect(Tracer tracer, TraceKeys traceKeys) { + public TraceAsyncAspect(Tracer tracer, TraceKeys traceKeys, BeanFactory beanFactory) { this.tracer = tracer; this.traceKeys = traceKeys; + this.beanFactory = beanFactory; } @Around("execution (@org.springframework.scheduling.annotation.Async * *.*(..))") @@ -62,4 +71,33 @@ public class TraceAsyncAspect { } } + @Around("execution (* org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor.*(..))") + public Object traceThreadPoolTaskExecutor(final ProceedingJoinPoint pjp) throws Throwable { + LazyTraceThreadPoolTaskExecutor executor = new LazyTraceThreadPoolTaskExecutor(this.beanFactory, + (ThreadPoolTaskExecutor) pjp.getTarget()); + Method methodOnTracedBean = getMethod(pjp, executor); + if (methodOnTracedBean != null) { + return methodOnTracedBean.invoke(executor, pjp.getArgs()); + } + return pjp.proceed(); + } + + @Around("execution (* java.util.concurrent.Executor.*(..))") + public Object traceExecutor(final ProceedingJoinPoint pjp) throws Throwable { + LazyTraceExecutor executor = new LazyTraceExecutor(this.beanFactory, + (Executor) pjp.getTarget()); + Method methodOnTracedBean = getMethod(pjp, executor); + if (methodOnTracedBean != null) { + return methodOnTracedBean.invoke(executor, pjp.getArgs()); + } + return pjp.proceed(); + } + + private Method getMethod(ProceedingJoinPoint pjp, Object object) { + MethodSignature signature = (MethodSignature) pjp.getSignature(); + Method method = signature.getMethod(); + return ReflectionUtils + .findMethod(object.getClass(), method.getName(), method.getParameterTypes()); + } + } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceExecutorBeanPostProcessor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceExecutorBeanPostProcessor.java deleted file mode 100644 index 3d59e9e48..000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceExecutorBeanPostProcessor.java +++ /dev/null @@ -1,60 +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.async; - -import java.util.concurrent.Executor; -import java.util.concurrent.ExecutorService; - -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.config.BeanPostProcessor; -import org.springframework.scheduling.TaskScheduler; -import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; - -/** - * Bean post processor that wraps {@link Executor} in its Trace representation - * - * @author Marcin Grzejszczak - * @since 1.0.10 - */ -class TraceExecutorBeanPostProcessor implements BeanPostProcessor { - - private final BeanFactory beanFactory; - - public TraceExecutorBeanPostProcessor(BeanFactory beanFactory) { - this.beanFactory = beanFactory; - } - - @Override public Object postProcessBeforeInitialization(Object bean, String beanName) - throws BeansException { - if (bean instanceof ThreadPoolTaskExecutor && !(bean instanceof TaskScheduler) && - !(bean instanceof LazyTraceThreadPoolTaskExecutor)) { - return new LazyTraceThreadPoolTaskExecutor(this.beanFactory, (ThreadPoolTaskExecutor) bean); - } else if (bean instanceof Executor && !(bean instanceof ExecutorService) && - !(bean instanceof TaskScheduler) && !(bean instanceof LazyTraceExecutor)) { - return new LazyTraceExecutor(this.beanFactory, (Executor) bean); - } else if (bean instanceof ExecutorService) { - return new TraceableExecutorService(this.beanFactory, (ExecutorService) bean); - } - return bean; - } - - @Override public Object postProcessAfterInitialization(Object bean, String beanName) - throws BeansException { - return bean; - } -} diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue410/Issue410Tests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue410/Issue410Tests.java index 272230de7..c7572f627 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue410/Issue410Tests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue410/Issue410Tests.java @@ -19,8 +19,6 @@ package org.springframework.cloud.sleuth.instrument.async.issues.issue410; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicReference; import org.apache.commons.logging.Log; @@ -38,7 +36,6 @@ import org.springframework.cloud.sleuth.Sampler; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.instrument.async.LazyTraceExecutor; -import org.springframework.cloud.sleuth.instrument.async.TraceableExecutorService; import org.springframework.cloud.sleuth.sampler.AlwaysSampler; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -73,7 +70,7 @@ public class Issue410Tests { /** * Related to issue #445 */ - @Autowired ExecutorService executorService; + @Autowired Application.MyService executorService; @Test public void should_pass_tracing_info_for_tasks_running_without_a_pool() { @@ -145,14 +142,6 @@ public class Issue410Tests { } } - /** - * Related to issue #445 - */ - @Test - public void should_wrap_executor_service_in_trace_representation() { - then(this.executorService).isInstanceOf(TraceableExecutorService.class); - } - private int port() { return this.environment.getProperty("local.server.port", Integer.class); } @@ -299,8 +288,16 @@ class Application { /** * Related to issue #445 */ - @Bean public ExecutorService executorService() { - return Executors.newSingleThreadExecutor(); + @Bean public MyService executorService() { + return new MyService() { + @Override public void execute(Runnable command) { + + } + }; + } + + interface MyService extends Executor { + } } From c47d0b6c185d00afd7329e538d90cf14bd0c9291 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Thu, 24 Nov 2016 12:08:10 +0100 Subject: [PATCH 138/235] Bumping versions before release --- benchmarks/pom.xml | 2 +- docs/pom.xml | 2 +- pom.xml | 12 ++++++------ spring-cloud-sleuth-core/pom.xml | 2 +- spring-cloud-sleuth-dependencies/pom.xml | 4 ++-- spring-cloud-sleuth-samples/pom.xml | 2 +- .../spring-cloud-sleuth-sample-feign/pom.xml | 2 +- .../spring-cloud-sleuth-sample-messaging/pom.xml | 2 +- .../spring-cloud-sleuth-sample-ribbon/pom.xml | 2 +- .../spring-cloud-sleuth-sample-stream/pom.xml | 2 +- .../spring-cloud-sleuth-sample-test-core/pom.xml | 2 +- .../spring-cloud-sleuth-sample-websocket/pom.xml | 2 +- .../spring-cloud-sleuth-sample-zipkin-stream/pom.xml | 2 +- .../spring-cloud-sleuth-sample-zipkin/pom.xml | 2 +- .../spring-cloud-sleuth-sample/pom.xml | 2 +- spring-cloud-sleuth-stream/pom.xml | 2 +- spring-cloud-sleuth-zipkin-stream/pom.xml | 2 +- spring-cloud-sleuth-zipkin/pom.xml | 2 +- spring-cloud-starter-sleuth/pom.xml | 2 +- spring-cloud-starter-zipkin/pom.xml | 2 +- 20 files changed, 26 insertions(+), 26 deletions(-) diff --git a/benchmarks/pom.xml b/benchmarks/pom.xml index a0c924bd2..90de6025a 100644 --- a/benchmarks/pom.xml +++ b/benchmarks/pom.xml @@ -17,7 +17,7 @@ Benchmarks Benchmarks (JMH) org.springframework.cloud - 1.0.11.BUILD-SNAPSHOT + 1.0.11.RELEASE benchmarks diff --git a/docs/pom.xml b/docs/pom.xml index 0927eea06..550e43741 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.11.BUILD-SNAPSHOT + 1.0.11.RELEASE spring-cloud-sleuth-docs pom diff --git a/pom.xml b/pom.xml index 529abe4d1..2fd266239 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 spring-cloud-sleuth - 1.0.11.BUILD-SNAPSHOT + 1.0.11.RELEASE pom Spring Cloud Sleuth Spring Cloud Sleuth @@ -13,7 +13,7 @@ org.springframework.cloud spring-cloud-build - 1.1.3.BUILD-SNAPSHOT + 1.1.2.RELEASE @@ -229,10 +229,10 @@ 1.8 2.19.1 2.17 - 1.1.3.BUILD-SNAPSHOT - 1.1.3.BUILD-SNAPSHOT - Brooklyn.BUILD-SNAPSHOT - 1.1.6.BUILD-SNAPSHOT + 1.1.2.RELEASE + 1.1.3.RELEASE + Brooklyn.RELEASE + 1.1.7.RELEASE diff --git a/spring-cloud-sleuth-core/pom.xml b/spring-cloud-sleuth-core/pom.xml index 9b3aba544..9b0d57985 100644 --- a/spring-cloud-sleuth-core/pom.xml +++ b/spring-cloud-sleuth-core/pom.xml @@ -11,7 +11,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.11.BUILD-SNAPSHOT + 1.0.11.RELEASE .. diff --git a/spring-cloud-sleuth-dependencies/pom.xml b/spring-cloud-sleuth-dependencies/pom.xml index b78f329da..871640b9b 100644 --- a/spring-cloud-sleuth-dependencies/pom.xml +++ b/spring-cloud-sleuth-dependencies/pom.xml @@ -5,11 +5,11 @@ spring-cloud-dependencies-parent org.springframework.cloud - 1.1.3.BUILD-SNAPSHOT + 1.1.2.RELEASE spring-cloud-sleuth-dependencies - 1.0.11.BUILD-SNAPSHOT + 1.0.11.RELEASE pom spring-cloud-sleuth-dependencies Spring Cloud Sleuth Dependencies diff --git a/spring-cloud-sleuth-samples/pom.xml b/spring-cloud-sleuth-samples/pom.xml index d7317f3d5..23449ce7b 100644 --- a/spring-cloud-sleuth-samples/pom.xml +++ b/spring-cloud-sleuth-samples/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.11.BUILD-SNAPSHOT + 1.0.11.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml index bcb5206e4..420c1bd9c 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml @@ -20,7 +20,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.11.BUILD-SNAPSHOT + 1.0.11.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml index 2cbd44cbe..a5f354719 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.11.BUILD-SNAPSHOT + 1.0.11.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml index 71fd2e1df..2bc2cd4e4 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.11.BUILD-SNAPSHOT + 1.0.11.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml index da1c6d34a..d163cdb4f 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.11.BUILD-SNAPSHOT + 1.0.11.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml index 47c6dfe61..1ac39b600 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.11.BUILD-SNAPSHOT + 1.0.11.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml index 117a3a912..41031adae 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.11.BUILD-SNAPSHOT + 1.0.11.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml index 67fb0764b..3fc00b55a 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.11.BUILD-SNAPSHOT + 1.0.11.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml index 202c6c482..5bf977596 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml @@ -27,7 +27,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.11.BUILD-SNAPSHOT + 1.0.11.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml index 6d0a2a375..fd9c36555 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.11.BUILD-SNAPSHOT + 1.0.11.RELEASE .. diff --git a/spring-cloud-sleuth-stream/pom.xml b/spring-cloud-sleuth-stream/pom.xml index a781bd524..e21e36ba4 100644 --- a/spring-cloud-sleuth-stream/pom.xml +++ b/spring-cloud-sleuth-stream/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.11.BUILD-SNAPSHOT + 1.0.11.RELEASE diff --git a/spring-cloud-sleuth-zipkin-stream/pom.xml b/spring-cloud-sleuth-zipkin-stream/pom.xml index 26a937f06..e78d24564 100644 --- a/spring-cloud-sleuth-zipkin-stream/pom.xml +++ b/spring-cloud-sleuth-zipkin-stream/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.11.BUILD-SNAPSHOT + 1.0.11.RELEASE diff --git a/spring-cloud-sleuth-zipkin/pom.xml b/spring-cloud-sleuth-zipkin/pom.xml index 82afa1f08..2afd5accd 100644 --- a/spring-cloud-sleuth-zipkin/pom.xml +++ b/spring-cloud-sleuth-zipkin/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.11.BUILD-SNAPSHOT + 1.0.11.RELEASE .. diff --git a/spring-cloud-starter-sleuth/pom.xml b/spring-cloud-starter-sleuth/pom.xml index 5b6d58794..e40e9e367 100644 --- a/spring-cloud-starter-sleuth/pom.xml +++ b/spring-cloud-starter-sleuth/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.11.BUILD-SNAPSHOT + 1.0.11.RELEASE .. spring-cloud-starter-sleuth diff --git a/spring-cloud-starter-zipkin/pom.xml b/spring-cloud-starter-zipkin/pom.xml index eb9236a63..5710108d4 100644 --- a/spring-cloud-starter-zipkin/pom.xml +++ b/spring-cloud-starter-zipkin/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.11.BUILD-SNAPSHOT + 1.0.11.RELEASE .. spring-cloud-starter-zipkin From 34c4eacb5e76fa9c172b437ca2a0ada2d8ac94bc Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Thu, 24 Nov 2016 12:33:44 +0100 Subject: [PATCH 139/235] Going back to Snapshots --- benchmarks/pom.xml | 2 +- docs/pom.xml | 2 +- pom.xml | 12 ++++++------ spring-cloud-sleuth-core/pom.xml | 2 +- spring-cloud-sleuth-dependencies/pom.xml | 4 ++-- spring-cloud-sleuth-samples/pom.xml | 2 +- .../spring-cloud-sleuth-sample-feign/pom.xml | 2 +- .../spring-cloud-sleuth-sample-messaging/pom.xml | 2 +- .../spring-cloud-sleuth-sample-ribbon/pom.xml | 2 +- .../spring-cloud-sleuth-sample-stream/pom.xml | 2 +- .../spring-cloud-sleuth-sample-test-core/pom.xml | 2 +- .../spring-cloud-sleuth-sample-websocket/pom.xml | 2 +- .../spring-cloud-sleuth-sample-zipkin-stream/pom.xml | 2 +- .../spring-cloud-sleuth-sample-zipkin/pom.xml | 2 +- .../spring-cloud-sleuth-sample/pom.xml | 2 +- spring-cloud-sleuth-stream/pom.xml | 2 +- spring-cloud-sleuth-zipkin-stream/pom.xml | 2 +- spring-cloud-sleuth-zipkin/pom.xml | 2 +- spring-cloud-starter-sleuth/pom.xml | 2 +- spring-cloud-starter-zipkin/pom.xml | 2 +- 20 files changed, 26 insertions(+), 26 deletions(-) diff --git a/benchmarks/pom.xml b/benchmarks/pom.xml index 90de6025a..5f21d7276 100644 --- a/benchmarks/pom.xml +++ b/benchmarks/pom.xml @@ -17,7 +17,7 @@ Benchmarks Benchmarks (JMH) org.springframework.cloud - 1.0.11.RELEASE + 1.0.12.BUILD-SNAPSHOT benchmarks diff --git a/docs/pom.xml b/docs/pom.xml index 550e43741..65d6b68f9 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.11.RELEASE + 1.0.12.BUILD-SNAPSHOT spring-cloud-sleuth-docs pom diff --git a/pom.xml b/pom.xml index 2fd266239..f34815422 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 spring-cloud-sleuth - 1.0.11.RELEASE + 1.0.12.BUILD-SNAPSHOT pom Spring Cloud Sleuth Spring Cloud Sleuth @@ -13,7 +13,7 @@ org.springframework.cloud spring-cloud-build - 1.1.2.RELEASE + 1.1.3.BUILD-SNAPSHOT @@ -229,10 +229,10 @@ 1.8 2.19.1 2.17 - 1.1.2.RELEASE - 1.1.3.RELEASE - Brooklyn.RELEASE - 1.1.7.RELEASE + 1.1.3.BUILD-SNAPSHOT + 1.1.3.BUILD-SNAPSHOT + Brooklyn.BUILD-SNAPSHOT + 1.1.8.BUILD-SNAPSHOT diff --git a/spring-cloud-sleuth-core/pom.xml b/spring-cloud-sleuth-core/pom.xml index 9b0d57985..cff5f2c94 100644 --- a/spring-cloud-sleuth-core/pom.xml +++ b/spring-cloud-sleuth-core/pom.xml @@ -11,7 +11,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.11.RELEASE + 1.0.12.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-dependencies/pom.xml b/spring-cloud-sleuth-dependencies/pom.xml index 871640b9b..db43c722f 100644 --- a/spring-cloud-sleuth-dependencies/pom.xml +++ b/spring-cloud-sleuth-dependencies/pom.xml @@ -5,11 +5,11 @@ spring-cloud-dependencies-parent org.springframework.cloud - 1.1.2.RELEASE + 1.1.3.BUILD-SNAPSHOT spring-cloud-sleuth-dependencies - 1.0.11.RELEASE + 1.0.12.BUILD-SNAPSHOT pom spring-cloud-sleuth-dependencies Spring Cloud Sleuth Dependencies diff --git a/spring-cloud-sleuth-samples/pom.xml b/spring-cloud-sleuth-samples/pom.xml index 23449ce7b..4564e1882 100644 --- a/spring-cloud-sleuth-samples/pom.xml +++ b/spring-cloud-sleuth-samples/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.11.RELEASE + 1.0.12.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml index 420c1bd9c..b9ed14f34 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml @@ -20,7 +20,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.11.RELEASE + 1.0.12.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml index a5f354719..c5e60be4e 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.11.RELEASE + 1.0.12.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml index 2bc2cd4e4..a3558d75a 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.11.RELEASE + 1.0.12.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml index d163cdb4f..57d65e294 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.11.RELEASE + 1.0.12.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml index 1ac39b600..445b4bcc3 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.11.RELEASE + 1.0.12.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml index 41031adae..9d5247e7d 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.11.RELEASE + 1.0.12.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml index 3fc00b55a..3c3388749 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.11.RELEASE + 1.0.12.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml index 5bf977596..5bf157a98 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml @@ -27,7 +27,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.11.RELEASE + 1.0.12.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml index fd9c36555..8043c7b08 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.11.RELEASE + 1.0.12.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-stream/pom.xml b/spring-cloud-sleuth-stream/pom.xml index e21e36ba4..c9fb5c948 100644 --- a/spring-cloud-sleuth-stream/pom.xml +++ b/spring-cloud-sleuth-stream/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.11.RELEASE + 1.0.12.BUILD-SNAPSHOT diff --git a/spring-cloud-sleuth-zipkin-stream/pom.xml b/spring-cloud-sleuth-zipkin-stream/pom.xml index e78d24564..e2957662e 100644 --- a/spring-cloud-sleuth-zipkin-stream/pom.xml +++ b/spring-cloud-sleuth-zipkin-stream/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.11.RELEASE + 1.0.12.BUILD-SNAPSHOT diff --git a/spring-cloud-sleuth-zipkin/pom.xml b/spring-cloud-sleuth-zipkin/pom.xml index 2afd5accd..bd4dc4be5 100644 --- a/spring-cloud-sleuth-zipkin/pom.xml +++ b/spring-cloud-sleuth-zipkin/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.11.RELEASE + 1.0.12.BUILD-SNAPSHOT .. diff --git a/spring-cloud-starter-sleuth/pom.xml b/spring-cloud-starter-sleuth/pom.xml index e40e9e367..092259366 100644 --- a/spring-cloud-starter-sleuth/pom.xml +++ b/spring-cloud-starter-sleuth/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.11.RELEASE + 1.0.12.BUILD-SNAPSHOT .. spring-cloud-starter-sleuth diff --git a/spring-cloud-starter-zipkin/pom.xml b/spring-cloud-starter-zipkin/pom.xml index 5710108d4..f68cd4295 100644 --- a/spring-cloud-starter-zipkin/pom.xml +++ b/spring-cloud-starter-zipkin/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.11.RELEASE + 1.0.12.BUILD-SNAPSHOT .. spring-cloud-starter-zipkin From ea9553cef74b05cce487ca945dc28cf046d89232 Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Tue, 15 Nov 2016 11:29:19 +0000 Subject: [PATCH 140/235] Ensure websockets 1xx response is not treated as an error --- .../cloud/sleuth/instrument/web/TraceFilter.java | 2 +- .../org/springframework/cloud/sleuth/trace/DefaultTracer.java | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceFilter.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceFilter.java index 96ed118a7..9380c1673 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 @@ -251,7 +251,7 @@ public class TraceFilter extends GenericFilterBean { return false; } HttpStatus httpStatus = HttpStatus.valueOf(response.getStatus()); - return httpStatus.is2xxSuccessful() || httpStatus.is3xxRedirection(); + return httpStatus.is1xxInformational() || httpStatus.is2xxSuccessful() || httpStatus.is3xxRedirection(); } private Span getSpanFromAttribute(HttpServletRequest request) { 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 48e5a4b60..098cd93b1 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 @@ -140,7 +140,9 @@ public class DefaultTracer implements Tracer { } SpanContextHolder.close(new SpanContextHolder.SpanFunction() { @Override public void apply(Span span) { - DefaultTracer.this.spanLogger.logStoppedSpan(savedSpan, span); + if (span!=null) { + DefaultTracer.this.spanLogger.logStoppedSpan(savedSpan, span); + } } }); } From 427df71b76740d90ce3119f7c5d226230c3bef17 Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Tue, 15 Nov 2016 15:40:06 +0000 Subject: [PATCH 141/235] Fix broken test (duh) --- .../cloud/sleuth/log/Slf4jSpanLogger.java | 10 +++++----- .../cloud/sleuth/trace/DefaultTracer.java | 19 +++++++++---------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/Slf4jSpanLogger.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/Slf4jSpanLogger.java index db1dbe31c..08468ffbf 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/Slf4jSpanLogger.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/Slf4jSpanLogger.java @@ -23,8 +23,7 @@ import org.slf4j.MDC; import org.springframework.cloud.sleuth.Span; /** - * Span listener that logs to the console when a span got - * started / stopped / continued. + * Span listener that logs to the console when a span got started / stopped / continued. * * @author Spencer Gibb * @since 1.0.0 @@ -36,8 +35,7 @@ public class Slf4jSpanLogger implements SpanLogger { public Slf4jSpanLogger(String nameSkipPattern) { this.nameSkipPattern = Pattern.compile(nameSkipPattern); - this.log = org.slf4j.LoggerFactory - .getLogger(Slf4jSpanLogger.class); + this.log = org.slf4j.LoggerFactory.getLogger(Slf4jSpanLogger.class); } Slf4jSpanLogger(String nameSkipPattern, Logger log) { @@ -66,7 +64,9 @@ public class Slf4jSpanLogger implements SpanLogger { @Override public void logStoppedSpan(Span parent, Span span) { - log("Stopped span: {}", span); + if (span != null) { + log("Stopped span: {}", span); + } if (span != null && parent != null) { log("With parent: {}", parent); MDC.put(Span.SPAN_ID_NAME, Span.idToHex(parent.getSpanId())); 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 098cd93b1..0f2e6d847 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 @@ -18,6 +18,7 @@ package org.springframework.cloud.sleuth.trace; import java.util.Random; import java.util.concurrent.Callable; + import org.springframework.cloud.sleuth.Sampler; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.SpanNamer; @@ -104,8 +105,7 @@ public class DefaultTracer implements Tracer { } Span cur = SpanContextHolder.getCurrentSpan(); if (!span.equals(cur)) { - ExceptionUtils.warn("Tried to detach trace span but " - + "it is not the current span: " + span + ExceptionUtils.warn("Tried to detach trace span but " + "it is not the current span: " + span + ". You may have forgotten to close or detach " + cur); } else { @@ -122,9 +122,8 @@ public class DefaultTracer implements Tracer { Span cur = SpanContextHolder.getCurrentSpan(); final Span savedSpan = span.getSavedSpan(); if (!span.equals(cur)) { - ExceptionUtils.warn( - "Tried to close span but it is not the current span: " + span - + ". You may have forgotten to close or detach " + cur); + ExceptionUtils.warn("Tried to close span but it is not the current span: " + span + + ". You may have forgotten to close or detach " + cur); } else { span.stop(); @@ -139,10 +138,9 @@ public class DefaultTracer implements Tracer { } } SpanContextHolder.close(new SpanContextHolder.SpanFunction() { - @Override public void apply(Span span) { - if (span!=null) { - DefaultTracer.this.spanLogger.logStoppedSpan(savedSpan, span); - } + @Override + public void apply(Span span) { + DefaultTracer.this.spanLogger.logStoppedSpan(savedSpan, span); } }); } @@ -196,7 +194,8 @@ public class DefaultTracer implements Tracer { public Span continueSpan(Span span) { if (span != null) { this.spanLogger.logContinuedSpan(span); - } else { + } + else { return null; } Span newSpan = createContinuedSpan(span, SpanContextHolder.getCurrentSpan()); From b663ab397b60ed2bbae1d6eb10a2fa2190502ef7 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Thu, 24 Nov 2016 13:00:18 +0100 Subject: [PATCH 142/235] Bumping versions before release --- benchmarks/pom.xml | 2 +- docs/pom.xml | 2 +- pom.xml | 12 ++++++------ spring-cloud-sleuth-core/pom.xml | 2 +- spring-cloud-sleuth-dependencies/pom.xml | 4 ++-- spring-cloud-sleuth-samples/pom.xml | 2 +- .../spring-cloud-sleuth-sample-feign/pom.xml | 2 +- .../spring-cloud-sleuth-sample-messaging/pom.xml | 2 +- .../spring-cloud-sleuth-sample-ribbon/pom.xml | 2 +- .../spring-cloud-sleuth-sample-stream/pom.xml | 2 +- .../spring-cloud-sleuth-sample-test-core/pom.xml | 2 +- .../spring-cloud-sleuth-sample-websocket/pom.xml | 2 +- .../spring-cloud-sleuth-sample-zipkin-stream/pom.xml | 2 +- .../spring-cloud-sleuth-sample-zipkin/pom.xml | 2 +- .../spring-cloud-sleuth-sample/pom.xml | 2 +- spring-cloud-sleuth-stream/pom.xml | 2 +- spring-cloud-sleuth-zipkin-stream/pom.xml | 2 +- spring-cloud-sleuth-zipkin/pom.xml | 2 +- spring-cloud-starter-sleuth/pom.xml | 2 +- spring-cloud-starter-zipkin/pom.xml | 2 +- 20 files changed, 26 insertions(+), 26 deletions(-) diff --git a/benchmarks/pom.xml b/benchmarks/pom.xml index 09c4438dc..900ba6777 100644 --- a/benchmarks/pom.xml +++ b/benchmarks/pom.xml @@ -17,7 +17,7 @@ Benchmarks Benchmarks (JMH) org.springframework.cloud - 1.1.0.BUILD-SNAPSHOT + 1.1.0.RELEASE benchmarks diff --git a/docs/pom.xml b/docs/pom.xml index 44ae5a86b..dce044b42 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.0.BUILD-SNAPSHOT + 1.1.0.RELEASE spring-cloud-sleuth-docs pom diff --git a/pom.xml b/pom.xml index c87327474..f0164fe01 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 spring-cloud-sleuth - 1.1.0.BUILD-SNAPSHOT + 1.1.0.RELEASE pom Spring Cloud Sleuth Spring Cloud Sleuth @@ -13,7 +13,7 @@ org.springframework.cloud spring-cloud-build - 1.2.2.BUILD-SNAPSHOT + 1.2.1.RELEASE @@ -229,10 +229,10 @@ 1.8 2.19.1 2.17 - 1.2.2.BUILD-SNAPSHOT - 1.1.6.BUILD-SNAPSHOT - Brooklyn.BUILD-SNAPSHOT - 1.2.3.BUILD-SNAPSHOT + 1.2.1.RELEASE + 1.1.6.RELEASE + Brooklyn.SR1 + 1.2.3.RELEASE diff --git a/spring-cloud-sleuth-core/pom.xml b/spring-cloud-sleuth-core/pom.xml index 0d99a353b..8828ae696 100644 --- a/spring-cloud-sleuth-core/pom.xml +++ b/spring-cloud-sleuth-core/pom.xml @@ -11,7 +11,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.0.BUILD-SNAPSHOT + 1.1.0.RELEASE .. diff --git a/spring-cloud-sleuth-dependencies/pom.xml b/spring-cloud-sleuth-dependencies/pom.xml index 0442a162f..a89e216d8 100644 --- a/spring-cloud-sleuth-dependencies/pom.xml +++ b/spring-cloud-sleuth-dependencies/pom.xml @@ -5,11 +5,11 @@ spring-cloud-dependencies-parent org.springframework.cloud - 1.2.2.BUILD-SNAPSHOT + 1.2.1.RELEASE spring-cloud-sleuth-dependencies - 1.1.0.BUILD-SNAPSHOT + 1.1.0.RELEASE pom spring-cloud-sleuth-dependencies Spring Cloud Sleuth Dependencies diff --git a/spring-cloud-sleuth-samples/pom.xml b/spring-cloud-sleuth-samples/pom.xml index 83398de89..9bd381d1e 100644 --- a/spring-cloud-sleuth-samples/pom.xml +++ b/spring-cloud-sleuth-samples/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.0.BUILD-SNAPSHOT + 1.1.0.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml index 1158e08dd..70c89cad7 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml @@ -20,7 +20,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.0.BUILD-SNAPSHOT + 1.1.0.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml index 123a51d43..26dcd2152 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.0.BUILD-SNAPSHOT + 1.1.0.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml index 26614f659..8235d3f5b 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.0.BUILD-SNAPSHOT + 1.1.0.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml index 7edd1f78e..56a96c744 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.0.BUILD-SNAPSHOT + 1.1.0.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml index 113ef2e0b..7b9849e07 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.0.BUILD-SNAPSHOT + 1.1.0.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml index bb0b4d4ce..6cd74db16 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.0.BUILD-SNAPSHOT + 1.1.0.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml index a21476c86..f4e78de80 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.0.BUILD-SNAPSHOT + 1.1.0.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml index 19098d337..f2b50242b 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml @@ -27,7 +27,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.0.BUILD-SNAPSHOT + 1.1.0.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml index 38062baf7..e3e1a269d 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.0.BUILD-SNAPSHOT + 1.1.0.RELEASE .. diff --git a/spring-cloud-sleuth-stream/pom.xml b/spring-cloud-sleuth-stream/pom.xml index 403e28622..4c7b58ddf 100644 --- a/spring-cloud-sleuth-stream/pom.xml +++ b/spring-cloud-sleuth-stream/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.0.BUILD-SNAPSHOT + 1.1.0.RELEASE diff --git a/spring-cloud-sleuth-zipkin-stream/pom.xml b/spring-cloud-sleuth-zipkin-stream/pom.xml index eaccc27c1..04580094d 100644 --- a/spring-cloud-sleuth-zipkin-stream/pom.xml +++ b/spring-cloud-sleuth-zipkin-stream/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.0.BUILD-SNAPSHOT + 1.1.0.RELEASE diff --git a/spring-cloud-sleuth-zipkin/pom.xml b/spring-cloud-sleuth-zipkin/pom.xml index a5273fa3a..23c0a34c4 100644 --- a/spring-cloud-sleuth-zipkin/pom.xml +++ b/spring-cloud-sleuth-zipkin/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.0.BUILD-SNAPSHOT + 1.1.0.RELEASE .. diff --git a/spring-cloud-starter-sleuth/pom.xml b/spring-cloud-starter-sleuth/pom.xml index eceef2eb1..4bd0d1bd7 100644 --- a/spring-cloud-starter-sleuth/pom.xml +++ b/spring-cloud-starter-sleuth/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.0.BUILD-SNAPSHOT + 1.1.0.RELEASE .. spring-cloud-starter-sleuth diff --git a/spring-cloud-starter-zipkin/pom.xml b/spring-cloud-starter-zipkin/pom.xml index 092f00cf9..62a1fec0f 100644 --- a/spring-cloud-starter-zipkin/pom.xml +++ b/spring-cloud-starter-zipkin/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.0.BUILD-SNAPSHOT + 1.1.0.RELEASE .. spring-cloud-starter-zipkin From 822c5fbba2846225c13bb2e83a12627a9c66cf8e Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Thu, 24 Nov 2016 13:17:15 +0100 Subject: [PATCH 143/235] Going back to snapshots --- benchmarks/pom.xml | 2 +- docs/pom.xml | 2 +- pom.xml | 12 ++++++------ spring-cloud-sleuth-core/pom.xml | 2 +- spring-cloud-sleuth-dependencies/pom.xml | 4 ++-- spring-cloud-sleuth-samples/pom.xml | 2 +- .../spring-cloud-sleuth-sample-feign/pom.xml | 2 +- .../spring-cloud-sleuth-sample-messaging/pom.xml | 2 +- .../spring-cloud-sleuth-sample-ribbon/pom.xml | 2 +- .../spring-cloud-sleuth-sample-stream/pom.xml | 2 +- .../spring-cloud-sleuth-sample-test-core/pom.xml | 2 +- .../spring-cloud-sleuth-sample-websocket/pom.xml | 2 +- .../spring-cloud-sleuth-sample-zipkin-stream/pom.xml | 2 +- .../spring-cloud-sleuth-sample-zipkin/pom.xml | 2 +- .../spring-cloud-sleuth-sample/pom.xml | 2 +- spring-cloud-sleuth-stream/pom.xml | 2 +- spring-cloud-sleuth-zipkin-stream/pom.xml | 2 +- spring-cloud-sleuth-zipkin/pom.xml | 2 +- spring-cloud-starter-sleuth/pom.xml | 2 +- spring-cloud-starter-zipkin/pom.xml | 2 +- 20 files changed, 26 insertions(+), 26 deletions(-) diff --git a/benchmarks/pom.xml b/benchmarks/pom.xml index 900ba6777..ff46084bd 100644 --- a/benchmarks/pom.xml +++ b/benchmarks/pom.xml @@ -17,7 +17,7 @@ Benchmarks Benchmarks (JMH) org.springframework.cloud - 1.1.0.RELEASE + 1.1.1.BUILD-SNAPSHOT benchmarks diff --git a/docs/pom.xml b/docs/pom.xml index dce044b42..b09884dde 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.0.RELEASE + 1.1.1.BUILD-SNAPSHOT spring-cloud-sleuth-docs pom diff --git a/pom.xml b/pom.xml index f0164fe01..2c95adf89 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 spring-cloud-sleuth - 1.1.0.RELEASE + 1.1.1.BUILD-SNAPSHOT pom Spring Cloud Sleuth Spring Cloud Sleuth @@ -13,7 +13,7 @@ org.springframework.cloud spring-cloud-build - 1.2.1.RELEASE + 1.2.2.BUILD-SNAPSHOT @@ -229,10 +229,10 @@ 1.8 2.19.1 2.17 - 1.2.1.RELEASE - 1.1.6.RELEASE - Brooklyn.SR1 - 1.2.3.RELEASE + 1.2.2.BUILD-SNAPSHOT + 1.1.7.BUILD-SNAPSHOT + Brooklyn.BUILD-SNAPSHOT + 1.2.4.BUILD-SNAPSHOT diff --git a/spring-cloud-sleuth-core/pom.xml b/spring-cloud-sleuth-core/pom.xml index 8828ae696..ff7d6446f 100644 --- a/spring-cloud-sleuth-core/pom.xml +++ b/spring-cloud-sleuth-core/pom.xml @@ -11,7 +11,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.0.RELEASE + 1.1.1.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-dependencies/pom.xml b/spring-cloud-sleuth-dependencies/pom.xml index a89e216d8..37bcef11e 100644 --- a/spring-cloud-sleuth-dependencies/pom.xml +++ b/spring-cloud-sleuth-dependencies/pom.xml @@ -5,11 +5,11 @@ spring-cloud-dependencies-parent org.springframework.cloud - 1.2.1.RELEASE + 1.2.2.BUILD-SNAPSHOT spring-cloud-sleuth-dependencies - 1.1.0.RELEASE + 1.1.1.BUILD-SNAPSHOT pom spring-cloud-sleuth-dependencies Spring Cloud Sleuth Dependencies diff --git a/spring-cloud-sleuth-samples/pom.xml b/spring-cloud-sleuth-samples/pom.xml index 9bd381d1e..d237605c4 100644 --- a/spring-cloud-sleuth-samples/pom.xml +++ b/spring-cloud-sleuth-samples/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.0.RELEASE + 1.1.1.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml index 70c89cad7..bfd3d54b4 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml @@ -20,7 +20,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.0.RELEASE + 1.1.1.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml index 26dcd2152..3f50f86d8 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.0.RELEASE + 1.1.1.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml index 8235d3f5b..b35e9af86 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.0.RELEASE + 1.1.1.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml index 56a96c744..b28037998 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.0.RELEASE + 1.1.1.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml index 7b9849e07..a62d43484 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.0.RELEASE + 1.1.1.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml index 6cd74db16..819387b28 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.0.RELEASE + 1.1.1.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml index f4e78de80..0e3e7cad1 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.0.RELEASE + 1.1.1.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml index f2b50242b..e5cef3560 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml @@ -27,7 +27,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.0.RELEASE + 1.1.1.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml index e3e1a269d..2228d7b10 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.0.RELEASE + 1.1.1.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-stream/pom.xml b/spring-cloud-sleuth-stream/pom.xml index 4c7b58ddf..48aa8ed39 100644 --- a/spring-cloud-sleuth-stream/pom.xml +++ b/spring-cloud-sleuth-stream/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.0.RELEASE + 1.1.1.BUILD-SNAPSHOT diff --git a/spring-cloud-sleuth-zipkin-stream/pom.xml b/spring-cloud-sleuth-zipkin-stream/pom.xml index 04580094d..bc8cde97f 100644 --- a/spring-cloud-sleuth-zipkin-stream/pom.xml +++ b/spring-cloud-sleuth-zipkin-stream/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.0.RELEASE + 1.1.1.BUILD-SNAPSHOT diff --git a/spring-cloud-sleuth-zipkin/pom.xml b/spring-cloud-sleuth-zipkin/pom.xml index 23c0a34c4..6750fef8c 100644 --- a/spring-cloud-sleuth-zipkin/pom.xml +++ b/spring-cloud-sleuth-zipkin/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.0.RELEASE + 1.1.1.BUILD-SNAPSHOT .. diff --git a/spring-cloud-starter-sleuth/pom.xml b/spring-cloud-starter-sleuth/pom.xml index 4bd0d1bd7..d5954e6e6 100644 --- a/spring-cloud-starter-sleuth/pom.xml +++ b/spring-cloud-starter-sleuth/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.0.RELEASE + 1.1.1.BUILD-SNAPSHOT .. spring-cloud-starter-sleuth diff --git a/spring-cloud-starter-zipkin/pom.xml b/spring-cloud-starter-zipkin/pom.xml index 62a1fec0f..6ecd39bf7 100644 --- a/spring-cloud-starter-zipkin/pom.xml +++ b/spring-cloud-starter-zipkin/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.0.RELEASE + 1.1.1.BUILD-SNAPSHOT .. spring-cloud-starter-zipkin From 398d26186e54b7c11c76f58ae34ebbd796000023 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Courson Date: Sat, 3 Dec 2016 15:14:12 +0000 Subject: [PATCH 144/235] Custom HTTP status codes support (#460) Provides custom HTTP status codes support without this change applications using custom HTTP status codes are currently having issues with Spring Cloud Sleuth. TraceFilter.httpStatusSuccessful() is throwing an IllegalArgumentException on HttpStatus.valueOf(response.getStatus()). with this change that gets fixed --- .../sleuth/instrument/web/TraceFilter.java | 4 +-- .../instrument/web/TraceFilterTests.java | 26 +++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) 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 9380c1673..e9e04ac9f 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 @@ -250,8 +250,8 @@ public class TraceFilter extends GenericFilterBean { if (response.getStatus() == 0) { return false; } - HttpStatus httpStatus = HttpStatus.valueOf(response.getStatus()); - return httpStatus.is1xxInformational() || httpStatus.is2xxSuccessful() || httpStatus.is3xxRedirection(); + HttpStatus.Series httpStatusSeries = HttpStatus.Series.valueOf(response.getStatus()); + return httpStatusSeries == HttpStatus.Series.SUCCESSFUL || httpStatusSeries == HttpStatus.Series.REDIRECTION; } private Span getSpanFromAttribute(HttpServletRequest request) { diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterTests.java index db7a6a213..a1909fa15 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 @@ -327,6 +327,32 @@ public class TraceFilterTests { then(TestSpanContextHolder.getCurrentSpan()).isNull(); } + @Test + public void closesSpanWhenResponseStatusIs2xx() throws Exception { + this.request = builder().header(Span.SPAN_ID_NAME, PARENT_ID) + .header(Span.TRACE_ID_NAME, 20L).buildRequest(new MockServletContext()); + TraceFilter filter = new TraceFilter(this.tracer, this.traceKeys, this.spanReporter, + this.spanExtractor, this.httpTraceKeysInjector); + this.response.setStatus(200); + + filter.doFilter(this.request, this.response, this.filterChain); + + then(TestSpanContextHolder.getCurrentSpan()).isNull(); + } + + @Test + public void closesSpanWhenResponseStatusIs3xx() throws Exception { + this.request = builder().header(Span.SPAN_ID_NAME, PARENT_ID) + .header(Span.TRACE_ID_NAME, 20L).buildRequest(new MockServletContext()); + TraceFilter filter = new TraceFilter(this.tracer, this.traceKeys, this.spanReporter, + this.spanExtractor, this.httpTraceKeysInjector); + this.response.setStatus(302); + + filter.doFilter(this.request, this.response, this.filterChain); + + then(TestSpanContextHolder.getCurrentSpan()).isNull(); + } + @Test public void returns400IfSpanIsMalformedAndCreatesANewSpan() throws Exception { this.request = builder().header(Span.SPAN_ID_NAME, "asd") From 3c720116c31ed69b944011420f2fd70c70f143e3 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Courson Date: Sat, 3 Dec 2016 15:14:12 +0000 Subject: [PATCH 145/235] Custom HTTP status codes support (#460) Provides custom HTTP status codes support without this change applications using custom HTTP status codes are currently having issues with Spring Cloud Sleuth. TraceFilter.httpStatusSuccessful() is throwing an IllegalArgumentException on HttpStatus.valueOf(response.getStatus()). with this change that gets fixed --- .../sleuth/instrument/web/TraceFilter.java | 4 +-- .../instrument/web/TraceFilterTests.java | 26 +++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) 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 9380c1673..e9e04ac9f 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 @@ -250,8 +250,8 @@ public class TraceFilter extends GenericFilterBean { if (response.getStatus() == 0) { return false; } - HttpStatus httpStatus = HttpStatus.valueOf(response.getStatus()); - return httpStatus.is1xxInformational() || httpStatus.is2xxSuccessful() || httpStatus.is3xxRedirection(); + HttpStatus.Series httpStatusSeries = HttpStatus.Series.valueOf(response.getStatus()); + return httpStatusSeries == HttpStatus.Series.SUCCESSFUL || httpStatusSeries == HttpStatus.Series.REDIRECTION; } private Span getSpanFromAttribute(HttpServletRequest request) { diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterTests.java index db7a6a213..a1909fa15 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 @@ -327,6 +327,32 @@ public class TraceFilterTests { then(TestSpanContextHolder.getCurrentSpan()).isNull(); } + @Test + public void closesSpanWhenResponseStatusIs2xx() throws Exception { + this.request = builder().header(Span.SPAN_ID_NAME, PARENT_ID) + .header(Span.TRACE_ID_NAME, 20L).buildRequest(new MockServletContext()); + TraceFilter filter = new TraceFilter(this.tracer, this.traceKeys, this.spanReporter, + this.spanExtractor, this.httpTraceKeysInjector); + this.response.setStatus(200); + + filter.doFilter(this.request, this.response, this.filterChain); + + then(TestSpanContextHolder.getCurrentSpan()).isNull(); + } + + @Test + public void closesSpanWhenResponseStatusIs3xx() throws Exception { + this.request = builder().header(Span.SPAN_ID_NAME, PARENT_ID) + .header(Span.TRACE_ID_NAME, 20L).buildRequest(new MockServletContext()); + TraceFilter filter = new TraceFilter(this.tracer, this.traceKeys, this.spanReporter, + this.spanExtractor, this.httpTraceKeysInjector); + this.response.setStatus(302); + + filter.doFilter(this.request, this.response, this.filterChain); + + then(TestSpanContextHolder.getCurrentSpan()).isNull(); + } + @Test public void returns400IfSpanIsMalformedAndCreatesANewSpan() throws Exception { this.request = builder().header(Span.SPAN_ID_NAME, "asd") From 0f4427bbf3d2b46e045c99f72008b004d9301821 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Thu, 8 Dec 2016 15:05:23 +0100 Subject: [PATCH 146/235] Fixed disabling of Sleuth without this change it's impossible to disable Sleuth when you have registered a Random bean with this change the conditions are fixed fixes #462 --- .../TraceSpanMessagingAutoConfiguration.java | 3 +- ...erTest.java => DefaultSpanNamerTests.java} | 2 +- .../sleuth/{LogTest.java => LogTests.java} | 2 +- .../TraceAutoConfigurationTest.java | 82 ------------------- .../TraceAutoConfigurationTests.java | 75 +++++++++++++++++ ...oConfigurationWithDisabledSleuthTests.java | 59 +++++++++++++ .../MultipleHopsIntegrationTests.java | 9 +- 7 files changed, 144 insertions(+), 88 deletions(-) rename spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/{DefaultSpanNamerTest.java => DefaultSpanNamerTests.java} (97%) rename spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/{LogTest.java => LogTests.java} (98%) delete mode 100644 spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfigurationTest.java create mode 100644 spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfigurationTests.java create mode 100644 spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfigurationWithDisabledSleuthTests.java diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceSpanMessagingAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceSpanMessagingAutoConfiguration.java index 30a0470eb..d72b2be80 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceSpanMessagingAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceSpanMessagingAutoConfiguration.java @@ -23,6 +23,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 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.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.messaging.Message; @@ -37,7 +38,7 @@ import org.springframework.messaging.support.MessageBuilder; */ @Configuration @ConditionalOnClass(Message.class) -@ConditionalOnBean({ TraceKeys.class, Random.class }) +@ConditionalOnBean(Tracer.class) public class TraceSpanMessagingAutoConfiguration { @Bean diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/DefaultSpanNamerTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/DefaultSpanNamerTests.java similarity index 97% rename from spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/DefaultSpanNamerTest.java rename to spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/DefaultSpanNamerTests.java index 0c86fabac..87c967674 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/DefaultSpanNamerTest.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/DefaultSpanNamerTests.java @@ -23,7 +23,7 @@ import static org.assertj.core.api.BDDAssertions.then; /** * @author Marcin Grzejszczak */ -public class DefaultSpanNamerTest { +public class DefaultSpanNamerTests { DefaultSpanNamer defaultSpanNamer = new DefaultSpanNamer(); diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/LogTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/LogTests.java similarity index 98% rename from spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/LogTest.java rename to spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/LogTests.java index 3e66c0a2d..c830c9e20 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/LogTest.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/LogTests.java @@ -30,7 +30,7 @@ import static org.assertj.core.api.BDDAssertions.then; /** * @author Adrian Cole */ -public class LogTest { +public class LogTests { @Rule public ExpectedException thrown = ExpectedException.none(); diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfigurationTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfigurationTest.java deleted file mode 100644 index 389760318..000000000 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfigurationTest.java +++ /dev/null @@ -1,82 +0,0 @@ -/** - * Copyright 2015-2016 The OpenZipkin 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.autoconfig; - -import org.junit.After; -import org.junit.Test; -import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.log.SleuthLogAutoConfiguration; -import org.springframework.cloud.sleuth.sampler.NeverSampler; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.boot.test.EnvironmentTestUtils.addEnvironment; - -public class TraceAutoConfigurationTest { - - AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); - - @After - public void close() { - context.close(); - } - - @Test - public void defaultsTo64BitTraceId() { - context = new AnnotationConfigApplicationContext(); - context.register( - PropertyPlaceholderAutoConfiguration.class, - SleuthLogAutoConfiguration.class, - TraceAutoConfiguration.class - ); - context.refresh(); - Tracer tracer = context.getBean(Tracer.class); - - Span span = null; - try { - span = tracer.createSpan("foo", NeverSampler.INSTANCE); - assertThat(span.getTraceIdHigh()).isEqualTo(0L); - assertThat(span.getTraceId()).isNotEqualTo(0L); - } finally { - if (span != null){ - tracer.close(span); - } - } - } - - @Test - public void optInto128BitTraceId() { - addEnvironment(context, "spring.sleuth.traceId128:true"); - context.register( - PropertyPlaceholderAutoConfiguration.class, - SleuthLogAutoConfiguration.class, - TraceAutoConfiguration.class - ); - context.refresh(); - Tracer tracer = context.getBean(Tracer.class); - - Span span = null; - try { - span = tracer.createSpan("foo", NeverSampler.INSTANCE); - assertThat(span.getTraceIdHigh()).isNotEqualTo(0L); - assertThat(span.getTraceId()).isNotEqualTo(0L); - } finally { - if (span != null){ - tracer.close(span); - } - } - } -} diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfigurationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfigurationTests.java new file mode 100644 index 000000000..65eca35d3 --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfigurationTests.java @@ -0,0 +1,75 @@ +/** + * Copyright 2015-2016 The OpenZipkin 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.autoconfig; + +import org.junit.After; +import org.junit.Test; +import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; +import org.springframework.cloud.sleuth.Span; +import org.springframework.cloud.sleuth.Tracer; +import org.springframework.cloud.sleuth.log.SleuthLogAutoConfiguration; +import org.springframework.cloud.sleuth.sampler.NeverSampler; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.boot.test.EnvironmentTestUtils.addEnvironment; + +public class TraceAutoConfigurationTests { + + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + + @After public void close() { + context.close(); + } + + @Test public void defaultsTo64BitTraceId() { + context = new AnnotationConfigApplicationContext(); + context.register(PropertyPlaceholderAutoConfiguration.class, + SleuthLogAutoConfiguration.class, TraceAutoConfiguration.class); + context.refresh(); + Tracer tracer = context.getBean(Tracer.class); + + Span span = null; + try { + span = tracer.createSpan("foo", NeverSampler.INSTANCE); + assertThat(span.getTraceIdHigh()).isEqualTo(0L); + assertThat(span.getTraceId()).isNotEqualTo(0L); + } + finally { + if (span != null) { + tracer.close(span); + } + } + } + + @Test public void optInto128BitTraceId() { + addEnvironment(context, "spring.sleuth.traceId128:true"); + context.register(PropertyPlaceholderAutoConfiguration.class, + SleuthLogAutoConfiguration.class, TraceAutoConfiguration.class); + context.refresh(); + Tracer tracer = context.getBean(Tracer.class); + + Span span = null; + try { + span = tracer.createSpan("foo", NeverSampler.INSTANCE); + assertThat(span.getTraceIdHigh()).isNotEqualTo(0L); + assertThat(span.getTraceId()).isNotEqualTo(0L); + } + finally { + if (span != null) { + tracer.close(span); + } + } + } +} diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfigurationWithDisabledSleuthTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfigurationWithDisabledSleuthTests.java new file mode 100644 index 000000000..415c7392a --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfigurationWithDisabledSleuthTests.java @@ -0,0 +1,59 @@ +/* + * 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.autoconfig; + +import java.security.SecureRandom; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = TraceAutoConfigurationWithDisabledSleuthTests.Config.class) +@TestPropertySource(properties = "spring.sleuth.enabled=false") +public class TraceAutoConfigurationWithDisabledSleuthTests { + + @Test + public void shouldStartContext(){ + + } + + @EnableAutoConfiguration + static class Config { + @Bean + public FactoryBean secureRandom() { + return new FactoryBean() { + + @Override public SecureRandom getObject() throws Exception { + return new SecureRandom(); + } + + @Override public Class getObjectType() { + return SecureRandom.class; + } + + @Override public boolean isSingleton() { + return true; + } + }; + } + } +} diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/multiple/MultipleHopsIntegrationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/multiple/MultipleHopsIntegrationTests.java index 537a0a56f..8109024f5 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/multiple/MultipleHopsIntegrationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/multiple/MultipleHopsIntegrationTests.java @@ -4,18 +4,20 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration; import org.springframework.cloud.sleuth.Sampler; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.SpanReporter; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.util.ArrayListSpanAccumulator; import org.springframework.cloud.sleuth.TraceKeys; +import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.instrument.web.TraceFilter; import org.springframework.cloud.sleuth.instrument.web.common.AbstractMvcIntegrationTest; import org.springframework.cloud.sleuth.sampler.AlwaysSampler; +import org.springframework.cloud.sleuth.util.ArrayListSpanAccumulator; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.web.servlet.result.MockMvcResultMatchers; import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder; @@ -28,6 +30,7 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = MultipleHopsIntegrationTests.Config.class) +@TestPropertySource(properties = "spring.application.name=multiplehopsintegrationtests") public class MultipleHopsIntegrationTests extends AbstractMvcIntegrationTest { @Autowired Tracer tracer; @@ -55,7 +58,7 @@ public class MultipleHopsIntegrationTests extends AbstractMvcIntegrationTest { } @Configuration - @SpringBootApplication + @SpringBootApplication(exclude = JmxAutoConfiguration.class) public static class Config { @Bean ArrayListSpanAccumulator arrayListSpanAccumulator() { From 9662e79677badfdb5c7e431340b2b4949d331cf2 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Thu, 8 Dec 2016 15:05:23 +0100 Subject: [PATCH 147/235] Fixed disabling of Sleuth without this change it's impossible to disable Sleuth when you have registered a Random bean with this change the conditions are fixed fixes #462 --- .../TraceSpanMessagingAutoConfiguration.java | 3 +- ...erTest.java => DefaultSpanNamerTests.java} | 2 +- .../sleuth/{LogTest.java => LogTests.java} | 2 +- .../TraceAutoConfigurationTest.java | 82 ------------------- .../TraceAutoConfigurationTests.java | 75 +++++++++++++++++ ...oConfigurationWithDisabledSleuthTests.java | 59 +++++++++++++ .../MultipleHopsIntegrationTests.java | 9 +- 7 files changed, 144 insertions(+), 88 deletions(-) rename spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/{DefaultSpanNamerTest.java => DefaultSpanNamerTests.java} (97%) rename spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/{LogTest.java => LogTests.java} (98%) create mode 100644 spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfigurationTests.java create mode 100644 spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfigurationWithDisabledSleuthTests.java diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceSpanMessagingAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceSpanMessagingAutoConfiguration.java index 30a0470eb..d72b2be80 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceSpanMessagingAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceSpanMessagingAutoConfiguration.java @@ -23,6 +23,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 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.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.messaging.Message; @@ -37,7 +38,7 @@ import org.springframework.messaging.support.MessageBuilder; */ @Configuration @ConditionalOnClass(Message.class) -@ConditionalOnBean({ TraceKeys.class, Random.class }) +@ConditionalOnBean(Tracer.class) public class TraceSpanMessagingAutoConfiguration { @Bean diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/DefaultSpanNamerTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/DefaultSpanNamerTests.java similarity index 97% rename from spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/DefaultSpanNamerTest.java rename to spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/DefaultSpanNamerTests.java index 0c86fabac..87c967674 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/DefaultSpanNamerTest.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/DefaultSpanNamerTests.java @@ -23,7 +23,7 @@ import static org.assertj.core.api.BDDAssertions.then; /** * @author Marcin Grzejszczak */ -public class DefaultSpanNamerTest { +public class DefaultSpanNamerTests { DefaultSpanNamer defaultSpanNamer = new DefaultSpanNamer(); diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/LogTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/LogTests.java similarity index 98% rename from spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/LogTest.java rename to spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/LogTests.java index 3e66c0a2d..c830c9e20 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/LogTest.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/LogTests.java @@ -30,7 +30,7 @@ import static org.assertj.core.api.BDDAssertions.then; /** * @author Adrian Cole */ -public class LogTest { +public class LogTests { @Rule public ExpectedException thrown = ExpectedException.none(); diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfigurationTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfigurationTest.java index b0dfe97d2..e69de29bb 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfigurationTest.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfigurationTest.java @@ -1,82 +0,0 @@ -/** - * Copyright 2015-2016 The OpenZipkin 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.autoconfig; - -import org.junit.After; -import org.junit.Test; -import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.log.SleuthLogAutoConfiguration; -import org.springframework.cloud.sleuth.sampler.NeverSampler; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.boot.test.util.EnvironmentTestUtils.addEnvironment; - -public class TraceAutoConfigurationTest { - - AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); - - @After - public void close() { - context.close(); - } - - @Test - public void defaultsTo64BitTraceId() { - context = new AnnotationConfigApplicationContext(); - context.register( - PropertyPlaceholderAutoConfiguration.class, - SleuthLogAutoConfiguration.class, - TraceAutoConfiguration.class - ); - context.refresh(); - Tracer tracer = context.getBean(Tracer.class); - - Span span = null; - try { - span = tracer.createSpan("foo", NeverSampler.INSTANCE); - assertThat(span.getTraceIdHigh()).isEqualTo(0L); - assertThat(span.getTraceId()).isNotEqualTo(0L); - } finally { - if (span != null){ - tracer.close(span); - } - } - } - - @Test - public void optInto128BitTraceId() { - addEnvironment(context, "spring.sleuth.traceId128:true"); - context.register( - PropertyPlaceholderAutoConfiguration.class, - SleuthLogAutoConfiguration.class, - TraceAutoConfiguration.class - ); - context.refresh(); - Tracer tracer = context.getBean(Tracer.class); - - Span span = null; - try { - span = tracer.createSpan("foo", NeverSampler.INSTANCE); - assertThat(span.getTraceIdHigh()).isNotEqualTo(0L); - assertThat(span.getTraceId()).isNotEqualTo(0L); - } finally { - if (span != null){ - tracer.close(span); - } - } - } -} diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfigurationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfigurationTests.java new file mode 100644 index 000000000..65eca35d3 --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfigurationTests.java @@ -0,0 +1,75 @@ +/** + * Copyright 2015-2016 The OpenZipkin 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.autoconfig; + +import org.junit.After; +import org.junit.Test; +import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; +import org.springframework.cloud.sleuth.Span; +import org.springframework.cloud.sleuth.Tracer; +import org.springframework.cloud.sleuth.log.SleuthLogAutoConfiguration; +import org.springframework.cloud.sleuth.sampler.NeverSampler; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.boot.test.EnvironmentTestUtils.addEnvironment; + +public class TraceAutoConfigurationTests { + + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + + @After public void close() { + context.close(); + } + + @Test public void defaultsTo64BitTraceId() { + context = new AnnotationConfigApplicationContext(); + context.register(PropertyPlaceholderAutoConfiguration.class, + SleuthLogAutoConfiguration.class, TraceAutoConfiguration.class); + context.refresh(); + Tracer tracer = context.getBean(Tracer.class); + + Span span = null; + try { + span = tracer.createSpan("foo", NeverSampler.INSTANCE); + assertThat(span.getTraceIdHigh()).isEqualTo(0L); + assertThat(span.getTraceId()).isNotEqualTo(0L); + } + finally { + if (span != null) { + tracer.close(span); + } + } + } + + @Test public void optInto128BitTraceId() { + addEnvironment(context, "spring.sleuth.traceId128:true"); + context.register(PropertyPlaceholderAutoConfiguration.class, + SleuthLogAutoConfiguration.class, TraceAutoConfiguration.class); + context.refresh(); + Tracer tracer = context.getBean(Tracer.class); + + Span span = null; + try { + span = tracer.createSpan("foo", NeverSampler.INSTANCE); + assertThat(span.getTraceIdHigh()).isNotEqualTo(0L); + assertThat(span.getTraceId()).isNotEqualTo(0L); + } + finally { + if (span != null) { + tracer.close(span); + } + } + } +} diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfigurationWithDisabledSleuthTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfigurationWithDisabledSleuthTests.java new file mode 100644 index 000000000..415c7392a --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfigurationWithDisabledSleuthTests.java @@ -0,0 +1,59 @@ +/* + * 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.autoconfig; + +import java.security.SecureRandom; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = TraceAutoConfigurationWithDisabledSleuthTests.Config.class) +@TestPropertySource(properties = "spring.sleuth.enabled=false") +public class TraceAutoConfigurationWithDisabledSleuthTests { + + @Test + public void shouldStartContext(){ + + } + + @EnableAutoConfiguration + static class Config { + @Bean + public FactoryBean secureRandom() { + return new FactoryBean() { + + @Override public SecureRandom getObject() throws Exception { + return new SecureRandom(); + } + + @Override public Class getObjectType() { + return SecureRandom.class; + } + + @Override public boolean isSingleton() { + return true; + } + }; + } + } +} diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/multiple/MultipleHopsIntegrationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/multiple/MultipleHopsIntegrationTests.java index 537a0a56f..8109024f5 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/multiple/MultipleHopsIntegrationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/multiple/MultipleHopsIntegrationTests.java @@ -4,18 +4,20 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration; import org.springframework.cloud.sleuth.Sampler; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.SpanReporter; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.util.ArrayListSpanAccumulator; import org.springframework.cloud.sleuth.TraceKeys; +import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.instrument.web.TraceFilter; import org.springframework.cloud.sleuth.instrument.web.common.AbstractMvcIntegrationTest; import org.springframework.cloud.sleuth.sampler.AlwaysSampler; +import org.springframework.cloud.sleuth.util.ArrayListSpanAccumulator; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.web.servlet.result.MockMvcResultMatchers; import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder; @@ -28,6 +30,7 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = MultipleHopsIntegrationTests.Config.class) +@TestPropertySource(properties = "spring.application.name=multiplehopsintegrationtests") public class MultipleHopsIntegrationTests extends AbstractMvcIntegrationTest { @Autowired Tracer tracer; @@ -55,7 +58,7 @@ public class MultipleHopsIntegrationTests extends AbstractMvcIntegrationTest { } @Configuration - @SpringBootApplication + @SpringBootApplication(exclude = JmxAutoConfiguration.class) public static class Config { @Bean ArrayListSpanAccumulator arrayListSpanAccumulator() { From ee8c73df53478b031e7cd080b328a7b2131b391f Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Fri, 16 Dec 2016 13:02:58 +0100 Subject: [PATCH 148/235] Spans are continued in Handler Interceptors (#474) without this change an explicit new span is created on the server side. Its name is equal to the method name of the controller. It introduces some nice advantages in terms of readability of trace. with this change we're continuing a previous span on the server side. We're attaching the tags and logs to that span with information about controller class and controller name. Also events related to start and finish of the controller are there. fixes #471 #469 #427 --- .../sleuth/instrument/web/TraceFilter.java | 2 +- .../web/TraceHandlerInterceptor.java | 63 +++++++++-------- .../web/TraceRequestAttributes.java | 6 ++ .../cloud/sleuth/trace/DefaultTracer.java | 1 + .../sleuth/assertions/ListOfSpansAssert.java | 10 +-- .../web/TraceFilterIntegrationTests.java | 10 +-- .../FeignClientServerErrorTests.java | 3 - .../client/integration/WebClientTests.java | 3 +- .../sleuth/instrument/web/view/Issue469.java | 29 ++++++++ .../instrument/web/view/Issue469Tests.java | 68 +++++++++++++++++++ .../MessagingApplicationTests.java | 7 +- 11 files changed, 153 insertions(+), 49 deletions(-) create mode 100644 spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/view/Issue469.java create mode 100644 spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/view/Issue469Tests.java 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 e9e04ac9f..3dfb72be4 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 @@ -221,7 +221,7 @@ public class TraceFilter extends GenericFilterBean { log.debug( "Won't detach the span " + span + " since error has already been handled"); } - } else { + } else if (this.tracer.isTracing()) { if (log.isDebugEnabled()) { log.debug("Detaching the span " + span + " since the response was unsuccessful"); } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptor.java index 538195de3..ff5a144f8 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptor.java @@ -62,26 +62,18 @@ public class TraceHandlerInterceptor extends HandlerInterceptorAdapter { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { - if (isErrorControllerRelated(request)) { - if (log.isDebugEnabled()) { - log.debug("Skipping creation of a span for error controller processing"); - } - return true; - } - if (isSpanContinued(request)) { - if (log.isDebugEnabled()) { - log.debug("Skipping creation of a span since the span is continued"); - } - return true; - } String spanName = spanName(handler); - Span span = getTracer().createSpan(spanName); + boolean continueSpan = getRootSpanFromAttribute(request) != null; + Span span = continueSpan ? getRootSpanFromAttribute(request) : getTracer().createSpan(spanName); if (log.isDebugEnabled()) { log.debug("Created new span " + span + " with name [" + spanName + "]"); } addClassMethodTag(handler, span); addClassNameTag(handler, span); setSpanInAttribute(request, span); + if (!continueSpan) { + setNewSpanCreatedAttribute(request, span); + } return true; } @@ -123,7 +115,7 @@ public class TraceHandlerInterceptor extends HandlerInterceptorAdapter { @Override public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { - Span spanFromRequest = getSpanFromAttribute(request); + Span spanFromRequest = getNewSpanFromAttribute(request); Span rootSpanFromRequest = getRootSpanFromAttribute(request); if (log.isDebugEnabled()) { log.debug("Closing the span " + spanFromRequest + " and detaching its parent " + rootSpanFromRequest + " since the request is asynchronous"); @@ -141,28 +133,27 @@ public class TraceHandlerInterceptor extends HandlerInterceptorAdapter { } return; } - if (isSpanContinued(request)) { - if (log.isDebugEnabled()) { - log.debug("Skipping closing of a span since it's been continued"); - } - return; - } - Span span = getSpanFromAttribute(request); - if (log.isDebugEnabled()) { - log.debug("Closing span " + span); - } + Span span = getRootSpanFromAttribute(request); if (ex != null) { - getTracer().addTag(Span.SPAN_ERROR_TAG_NAME, ExceptionUtils.getExceptionMessage(ex)); + String errorMsg = ExceptionUtils.getExceptionMessage(ex); + if (log.isDebugEnabled()) { + log.debug("Adding an error tag [" + errorMsg + "] to span " + span + ""); + } + getTracer().addTag(Span.SPAN_ERROR_TAG_NAME, errorMsg); + } + if (getNewSpanFromAttribute(request) != null) { + if (log.isDebugEnabled()) { + log.debug("Closing span " + span); + } + Span newSpan = getNewSpanFromAttribute(request); + getTracer().continueSpan(newSpan); + getTracer().close(newSpan); + clearNewSpanCreatedAttribute(request); } - getTracer().close(span); } - private boolean isSpanContinued(HttpServletRequest request) { - return request.getAttribute(TraceRequestAttributes.SPAN_CONTINUED_REQUEST_ATTR) != null; - } - - private Span getSpanFromAttribute(HttpServletRequest request) { - return (Span) request.getAttribute(TraceRequestAttributes.HANDLED_SPAN_REQUEST_ATTR); + private Span getNewSpanFromAttribute(HttpServletRequest request) { + return (Span) request.getAttribute(TraceRequestAttributes.NEW_SPAN_REQUEST_ATTR); } private Span getRootSpanFromAttribute(HttpServletRequest request) { @@ -173,6 +164,14 @@ public class TraceHandlerInterceptor extends HandlerInterceptorAdapter { request.setAttribute(TraceRequestAttributes.HANDLED_SPAN_REQUEST_ATTR, span); } + private void setNewSpanCreatedAttribute(HttpServletRequest request, Span span) { + request.setAttribute(TraceRequestAttributes.NEW_SPAN_REQUEST_ATTR, span); + } + + private void clearNewSpanCreatedAttribute(HttpServletRequest request) { + request.removeAttribute(TraceRequestAttributes.NEW_SPAN_REQUEST_ATTR); + } + private Tracer getTracer() { if (this.tracer == null) { this.tracer = this.beanFactory.getBean(Tracer.class); diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceRequestAttributes.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceRequestAttributes.java index 683293807..1b3ccfbb0 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceRequestAttributes.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceRequestAttributes.java @@ -31,6 +31,12 @@ public final class TraceRequestAttributes { public static final String HANDLED_SPAN_REQUEST_ATTR = TraceRequestAttributes.class.getName() + ".TRACE_HANDLED"; + /** + * Set if Handler interceptor has executed some logic + */ + public static final String NEW_SPAN_REQUEST_ATTR = TraceRequestAttributes.class.getName() + + ".TRACE_HANDLED_NEW_SPAN"; + /** * Attribute set when the {@link org.springframework.cloud.sleuth.Span} got continued in the {@link TraceFilter}. * The Sleuth tracing components will most likely continue the current Span instead of creating a new one. 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 a1cb5edd0..f9dc325b8 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 @@ -18,6 +18,7 @@ package org.springframework.cloud.sleuth.trace; import java.util.Random; import java.util.concurrent.Callable; + import org.springframework.cloud.sleuth.Sampler; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.SpanNamer; diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/ListOfSpansAssert.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/ListOfSpansAssert.java index 06fb23942..b1e237d75 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/ListOfSpansAssert.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/ListOfSpansAssert.java @@ -16,6 +16,9 @@ package org.springframework.cloud.sleuth.assertions; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -26,9 +29,6 @@ import org.apache.commons.logging.LogFactory; import org.assertj.core.api.AbstractAssert; import org.springframework.cloud.sleuth.Span; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; - import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.toList; import static org.assertj.core.api.Assertions.assertThat; @@ -106,7 +106,7 @@ public class ListOfSpansAssert extends AbstractAssert \nto contain at least one span with tag key " - + "equal to <%s> and value equal to <%s>", spansToString(), tagKey, tagValue); + + "equal to <%s> and value equal to <%s>.\n\n", spansToString(), tagKey, tagValue); } return this; } @@ -152,7 +152,7 @@ public class ListOfSpansAssert extends AbstractAssert span.getSpanId() == TraceFilterIntegrationTests.span.getParents().get(0)) - .findFirst().get(); - then(parentSpan).hasLoggedAnEvent(Span.SERVER_RECV) + then(this.spanAccumulator.getSpans()).hasSize(1); + Span span = this.spanAccumulator.getSpans().get(0); + then(span).hasLoggedAnEvent(Span.SERVER_RECV) + .hasATagWithKey(new TraceKeys().getMvc().getControllerClass()) + .hasATagWithKey(new TraceKeys().getMvc().getControllerMethod()) .hasLoggedAnEvent(Span.SERVER_SEND); then(ExceptionUtils.getLastException()).isNull(); } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/servererrors/FeignClientServerErrorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/servererrors/FeignClientServerErrorTests.java index 5cd501607..d102beca5 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/servererrors/FeignClientServerErrorTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/servererrors/FeignClientServerErrorTests.java @@ -101,9 +101,6 @@ public class FeignClientServerErrorTests { then(new ListOfSpans(this.listener.getEvents())) .hasASpanWithTagEqualTo(Span.SPAN_ERROR_TAG_NAME, "Request processing failed; nested exception is java.lang.RuntimeException: Internal Error"); - then(new ListOfSpans(this.listener.getEvents())) - .hasASpanWithTagEqualTo(Span.SPAN_ERROR_TAG_NAME, - "Internal Error"); }); } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/integration/WebClientTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/integration/WebClientTests.java index 14c7c8ba5..336401d13 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/integration/WebClientTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/integration/WebClientTests.java @@ -114,7 +114,8 @@ public class WebClientTests { then(getHeader(response, Span.SPAN_ID_NAME)).isNull(); then(this.listener.getSpans()).isNotEmpty(); Optional noTraceSpan = new ArrayList<>(this.listener.getSpans()).stream().filter(span -> - "http:/notrace".equals(span.getName()) && !span.tags().isEmpty()).findFirst(); + "http:/notrace".equals(span.getName()) && !span.tags().isEmpty() + && span.tags().containsKey("http.path")).findFirst(); then(noTraceSpan.isPresent()).isTrue(); // TODO: matches cause there is an issue with Feign not providing the full URL at the interceptor level then(noTraceSpan.get()).matchesATag("http.url", ".*/notrace") diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/view/Issue469.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/view/Issue469.java new file mode 100644 index 000000000..e73f7fdad --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/view/Issue469.java @@ -0,0 +1,29 @@ +/* + * 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.view; + +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; + +@EnableAutoConfiguration +public class Issue469 extends WebMvcConfigurerAdapter { + + @Override public void addViewControllers(ViewControllerRegistry registry) { + registry.addViewController("/welcome").setViewName("welcome"); + } +} \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/view/Issue469Tests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/view/Issue469Tests.java new file mode 100644 index 000000000..1cfbd65d5 --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/view/Issue469Tests.java @@ -0,0 +1,68 @@ +/* + * Copyright 2013-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.sleuth.instrument.web.view; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.boot.test.WebIntegrationTest; +import org.springframework.cloud.sleuth.Tracer; +import org.springframework.cloud.sleuth.util.ExceptionUtils; +import org.springframework.core.env.Environment; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.web.client.RestTemplate; + +import static org.assertj.core.api.BDDAssertions.then; + +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(classes = Issue469.class) +@TestPropertySource(properties = {"spring.mvc.view.prefix=/WEB-INF/jsp/", + "spring.mvc.view.suffix=.jsp"}) +@WebIntegrationTest({ "server.port=0" }) +public class Issue469Tests { + + @Autowired Tracer tracer; + @Autowired Environment environment; + RestTemplate restTemplate = new RestTemplate(); + + @Before + public void setup() { + ExceptionUtils.setFail(true); + } + + @Test + public void should_not_result_in_tracing_exceptions_when_using_view_controllers() throws Exception { + try { + this.restTemplate + .getForObject("http://localhost:" + port() + "/welcome", String.class); + } catch (Exception e) { + // JSPs are not rendered + then(e).hasMessage("404 Not Found"); + } + + then(ExceptionUtils.getLastException()).isNull(); + then(this.tracer.getCurrentSpan()).isNull(); + } + + private int port() { + return this.environment.getProperty("local.server.port", Integer.class); + } + +} \ No newline at end of file diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/src/test/java/integration/MessagingApplicationTests.java b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/src/test/java/integration/MessagingApplicationTests.java index b8ccb3123..7060429d0 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/src/test/java/integration/MessagingApplicationTests.java +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/src/test/java/integration/MessagingApplicationTests.java @@ -113,10 +113,10 @@ public class MessagingApplicationTests extends AbstractIntegrationTest { Optional eventSentSpan = findSpanWithAnnotation(Constants.SERVER_SEND); Optional eventReceivedSpan = findSpanWithAnnotation(Constants.CLIENT_RECV); Optional lastHttpSpansParent = findLastHttpSpansParent(); - // "http:/parent/" -> "home" -> "message:messages" -> "http:/foo" (CS + CR) -> "http:/foo" (SS) -> "foo" + // "http:/parent/" -> "message:messages" -> "http:/foo" (CS + CR) -> "http:/foo" (SS) Collections.sort(this.integrationTestSpanCollector.hashedSpans); thenAllSpansArePresent(firstHttpSpan, eventSpans, lastHttpSpansParent, eventSentSpan, eventReceivedSpan); - then(this.integrationTestSpanCollector.hashedSpans).as("There were 6 spans").hasSize(6); + then(this.integrationTestSpanCollector.hashedSpans).as("There were 4 spans").hasSize(4); log.info("Checking the parent child structure"); List> parentChild = this.integrationTestSpanCollector.hashedSpans.stream() .filter(span -> span.parentId != null) @@ -147,7 +147,8 @@ public class MessagingApplicationTests extends AbstractIntegrationTest { private Optional findFirstHttpRequestSpan() { return this.integrationTestSpanCollector.hashedSpans.stream() // home is the name of the method - .filter(span -> "home".equals(span.name)).findFirst(); + .filter(span -> span.binaryAnnotations.stream() + .anyMatch(binaryAnnotation -> new String(binaryAnnotation.value).equals("home"))).findFirst(); } private void thenAllSpansArePresent(Optional firstHttpSpan, From 9c5c965162f4d4f704b9ad355962d4599c68f62e Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Fri, 16 Dec 2016 13:02:58 +0100 Subject: [PATCH 149/235] Spans are continued in Handler Interceptors (#474) without this change an explicit new span is created on the server side. Its name is equal to the method name of the controller. It introduces some nice advantages in terms of readability of trace. with this change we're continuing a previous span on the server side. We're attaching the tags and logs to that span with information about controller class and controller name. Also events related to start and finish of the controller are there. fixes #471 #469 #427 --- .../sleuth/instrument/web/TraceFilter.java | 2 +- .../web/TraceHandlerInterceptor.java | 63 +++++++++-------- .../web/TraceRequestAttributes.java | 6 ++ .../sleuth/assertions/ListOfSpansAssert.java | 10 +-- .../web/TraceFilterIntegrationTests.java | 10 +-- .../FeignClientServerErrorTests.java | 3 - .../client/integration/WebClientTests.java | 3 +- .../sleuth/instrument/web/view/Issue469.java | 29 ++++++++ .../instrument/web/view/Issue469Tests.java | 68 +++++++++++++++++++ .../MessagingApplicationTests.java | 7 +- 10 files changed, 152 insertions(+), 49 deletions(-) create mode 100644 spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/view/Issue469.java create mode 100644 spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/view/Issue469Tests.java 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 e9e04ac9f..3dfb72be4 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 @@ -221,7 +221,7 @@ public class TraceFilter extends GenericFilterBean { log.debug( "Won't detach the span " + span + " since error has already been handled"); } - } else { + } else if (this.tracer.isTracing()) { if (log.isDebugEnabled()) { log.debug("Detaching the span " + span + " since the response was unsuccessful"); } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptor.java index 538195de3..ff5a144f8 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptor.java @@ -62,26 +62,18 @@ public class TraceHandlerInterceptor extends HandlerInterceptorAdapter { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { - if (isErrorControllerRelated(request)) { - if (log.isDebugEnabled()) { - log.debug("Skipping creation of a span for error controller processing"); - } - return true; - } - if (isSpanContinued(request)) { - if (log.isDebugEnabled()) { - log.debug("Skipping creation of a span since the span is continued"); - } - return true; - } String spanName = spanName(handler); - Span span = getTracer().createSpan(spanName); + boolean continueSpan = getRootSpanFromAttribute(request) != null; + Span span = continueSpan ? getRootSpanFromAttribute(request) : getTracer().createSpan(spanName); if (log.isDebugEnabled()) { log.debug("Created new span " + span + " with name [" + spanName + "]"); } addClassMethodTag(handler, span); addClassNameTag(handler, span); setSpanInAttribute(request, span); + if (!continueSpan) { + setNewSpanCreatedAttribute(request, span); + } return true; } @@ -123,7 +115,7 @@ public class TraceHandlerInterceptor extends HandlerInterceptorAdapter { @Override public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { - Span spanFromRequest = getSpanFromAttribute(request); + Span spanFromRequest = getNewSpanFromAttribute(request); Span rootSpanFromRequest = getRootSpanFromAttribute(request); if (log.isDebugEnabled()) { log.debug("Closing the span " + spanFromRequest + " and detaching its parent " + rootSpanFromRequest + " since the request is asynchronous"); @@ -141,28 +133,27 @@ public class TraceHandlerInterceptor extends HandlerInterceptorAdapter { } return; } - if (isSpanContinued(request)) { - if (log.isDebugEnabled()) { - log.debug("Skipping closing of a span since it's been continued"); - } - return; - } - Span span = getSpanFromAttribute(request); - if (log.isDebugEnabled()) { - log.debug("Closing span " + span); - } + Span span = getRootSpanFromAttribute(request); if (ex != null) { - getTracer().addTag(Span.SPAN_ERROR_TAG_NAME, ExceptionUtils.getExceptionMessage(ex)); + String errorMsg = ExceptionUtils.getExceptionMessage(ex); + if (log.isDebugEnabled()) { + log.debug("Adding an error tag [" + errorMsg + "] to span " + span + ""); + } + getTracer().addTag(Span.SPAN_ERROR_TAG_NAME, errorMsg); + } + if (getNewSpanFromAttribute(request) != null) { + if (log.isDebugEnabled()) { + log.debug("Closing span " + span); + } + Span newSpan = getNewSpanFromAttribute(request); + getTracer().continueSpan(newSpan); + getTracer().close(newSpan); + clearNewSpanCreatedAttribute(request); } - getTracer().close(span); } - private boolean isSpanContinued(HttpServletRequest request) { - return request.getAttribute(TraceRequestAttributes.SPAN_CONTINUED_REQUEST_ATTR) != null; - } - - private Span getSpanFromAttribute(HttpServletRequest request) { - return (Span) request.getAttribute(TraceRequestAttributes.HANDLED_SPAN_REQUEST_ATTR); + private Span getNewSpanFromAttribute(HttpServletRequest request) { + return (Span) request.getAttribute(TraceRequestAttributes.NEW_SPAN_REQUEST_ATTR); } private Span getRootSpanFromAttribute(HttpServletRequest request) { @@ -173,6 +164,14 @@ public class TraceHandlerInterceptor extends HandlerInterceptorAdapter { request.setAttribute(TraceRequestAttributes.HANDLED_SPAN_REQUEST_ATTR, span); } + private void setNewSpanCreatedAttribute(HttpServletRequest request, Span span) { + request.setAttribute(TraceRequestAttributes.NEW_SPAN_REQUEST_ATTR, span); + } + + private void clearNewSpanCreatedAttribute(HttpServletRequest request) { + request.removeAttribute(TraceRequestAttributes.NEW_SPAN_REQUEST_ATTR); + } + private Tracer getTracer() { if (this.tracer == null) { this.tracer = this.beanFactory.getBean(Tracer.class); diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceRequestAttributes.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceRequestAttributes.java index 683293807..1b3ccfbb0 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceRequestAttributes.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceRequestAttributes.java @@ -31,6 +31,12 @@ public final class TraceRequestAttributes { public static final String HANDLED_SPAN_REQUEST_ATTR = TraceRequestAttributes.class.getName() + ".TRACE_HANDLED"; + /** + * Set if Handler interceptor has executed some logic + */ + public static final String NEW_SPAN_REQUEST_ATTR = TraceRequestAttributes.class.getName() + + ".TRACE_HANDLED_NEW_SPAN"; + /** * Attribute set when the {@link org.springframework.cloud.sleuth.Span} got continued in the {@link TraceFilter}. * The Sleuth tracing components will most likely continue the current Span instead of creating a new one. diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/ListOfSpansAssert.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/ListOfSpansAssert.java index 06fb23942..b1e237d75 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/ListOfSpansAssert.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/ListOfSpansAssert.java @@ -16,6 +16,9 @@ package org.springframework.cloud.sleuth.assertions; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -26,9 +29,6 @@ import org.apache.commons.logging.LogFactory; import org.assertj.core.api.AbstractAssert; import org.springframework.cloud.sleuth.Span; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; - import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.toList; import static org.assertj.core.api.Assertions.assertThat; @@ -106,7 +106,7 @@ public class ListOfSpansAssert extends AbstractAssert \nto contain at least one span with tag key " - + "equal to <%s> and value equal to <%s>", spansToString(), tagKey, tagValue); + + "equal to <%s> and value equal to <%s>.\n\n", spansToString(), tagKey, tagValue); } return this; } @@ -152,7 +152,7 @@ public class ListOfSpansAssert extends AbstractAssert span.getSpanId() == TraceFilterIntegrationTests.span.getParents().get(0)) - .findFirst().get(); - then(parentSpan).hasLoggedAnEvent(Span.SERVER_RECV) + then(this.spanAccumulator.getSpans()).hasSize(1); + Span span = this.spanAccumulator.getSpans().get(0); + then(span).hasLoggedAnEvent(Span.SERVER_RECV) + .hasATagWithKey(new TraceKeys().getMvc().getControllerClass()) + .hasATagWithKey(new TraceKeys().getMvc().getControllerMethod()) .hasLoggedAnEvent(Span.SERVER_SEND); then(ExceptionUtils.getLastException()).isNull(); } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/servererrors/FeignClientServerErrorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/servererrors/FeignClientServerErrorTests.java index 5cd501607..d102beca5 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/servererrors/FeignClientServerErrorTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/servererrors/FeignClientServerErrorTests.java @@ -101,9 +101,6 @@ public class FeignClientServerErrorTests { then(new ListOfSpans(this.listener.getEvents())) .hasASpanWithTagEqualTo(Span.SPAN_ERROR_TAG_NAME, "Request processing failed; nested exception is java.lang.RuntimeException: Internal Error"); - then(new ListOfSpans(this.listener.getEvents())) - .hasASpanWithTagEqualTo(Span.SPAN_ERROR_TAG_NAME, - "Internal Error"); }); } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/integration/WebClientTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/integration/WebClientTests.java index 14c7c8ba5..336401d13 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/integration/WebClientTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/integration/WebClientTests.java @@ -114,7 +114,8 @@ public class WebClientTests { then(getHeader(response, Span.SPAN_ID_NAME)).isNull(); then(this.listener.getSpans()).isNotEmpty(); Optional noTraceSpan = new ArrayList<>(this.listener.getSpans()).stream().filter(span -> - "http:/notrace".equals(span.getName()) && !span.tags().isEmpty()).findFirst(); + "http:/notrace".equals(span.getName()) && !span.tags().isEmpty() + && span.tags().containsKey("http.path")).findFirst(); then(noTraceSpan.isPresent()).isTrue(); // TODO: matches cause there is an issue with Feign not providing the full URL at the interceptor level then(noTraceSpan.get()).matchesATag("http.url", ".*/notrace") diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/view/Issue469.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/view/Issue469.java new file mode 100644 index 000000000..e73f7fdad --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/view/Issue469.java @@ -0,0 +1,29 @@ +/* + * 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.view; + +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; + +@EnableAutoConfiguration +public class Issue469 extends WebMvcConfigurerAdapter { + + @Override public void addViewControllers(ViewControllerRegistry registry) { + registry.addViewController("/welcome").setViewName("welcome"); + } +} \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/view/Issue469Tests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/view/Issue469Tests.java new file mode 100644 index 000000000..1cfbd65d5 --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/view/Issue469Tests.java @@ -0,0 +1,68 @@ +/* + * Copyright 2013-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.sleuth.instrument.web.view; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.boot.test.WebIntegrationTest; +import org.springframework.cloud.sleuth.Tracer; +import org.springframework.cloud.sleuth.util.ExceptionUtils; +import org.springframework.core.env.Environment; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.web.client.RestTemplate; + +import static org.assertj.core.api.BDDAssertions.then; + +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(classes = Issue469.class) +@TestPropertySource(properties = {"spring.mvc.view.prefix=/WEB-INF/jsp/", + "spring.mvc.view.suffix=.jsp"}) +@WebIntegrationTest({ "server.port=0" }) +public class Issue469Tests { + + @Autowired Tracer tracer; + @Autowired Environment environment; + RestTemplate restTemplate = new RestTemplate(); + + @Before + public void setup() { + ExceptionUtils.setFail(true); + } + + @Test + public void should_not_result_in_tracing_exceptions_when_using_view_controllers() throws Exception { + try { + this.restTemplate + .getForObject("http://localhost:" + port() + "/welcome", String.class); + } catch (Exception e) { + // JSPs are not rendered + then(e).hasMessage("404 Not Found"); + } + + then(ExceptionUtils.getLastException()).isNull(); + then(this.tracer.getCurrentSpan()).isNull(); + } + + private int port() { + return this.environment.getProperty("local.server.port", Integer.class); + } + +} \ No newline at end of file diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/src/test/java/integration/MessagingApplicationTests.java b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/src/test/java/integration/MessagingApplicationTests.java index b8ccb3123..7060429d0 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/src/test/java/integration/MessagingApplicationTests.java +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/src/test/java/integration/MessagingApplicationTests.java @@ -113,10 +113,10 @@ public class MessagingApplicationTests extends AbstractIntegrationTest { Optional eventSentSpan = findSpanWithAnnotation(Constants.SERVER_SEND); Optional eventReceivedSpan = findSpanWithAnnotation(Constants.CLIENT_RECV); Optional lastHttpSpansParent = findLastHttpSpansParent(); - // "http:/parent/" -> "home" -> "message:messages" -> "http:/foo" (CS + CR) -> "http:/foo" (SS) -> "foo" + // "http:/parent/" -> "message:messages" -> "http:/foo" (CS + CR) -> "http:/foo" (SS) Collections.sort(this.integrationTestSpanCollector.hashedSpans); thenAllSpansArePresent(firstHttpSpan, eventSpans, lastHttpSpansParent, eventSentSpan, eventReceivedSpan); - then(this.integrationTestSpanCollector.hashedSpans).as("There were 6 spans").hasSize(6); + then(this.integrationTestSpanCollector.hashedSpans).as("There were 4 spans").hasSize(4); log.info("Checking the parent child structure"); List> parentChild = this.integrationTestSpanCollector.hashedSpans.stream() .filter(span -> span.parentId != null) @@ -147,7 +147,8 @@ public class MessagingApplicationTests extends AbstractIntegrationTest { private Optional findFirstHttpRequestSpan() { return this.integrationTestSpanCollector.hashedSpans.stream() // home is the name of the method - .filter(span -> "home".equals(span.name)).findFirst(); + .filter(span -> span.binaryAnnotations.stream() + .anyMatch(binaryAnnotation -> new String(binaryAnnotation.value).equals("home"))).findFirst(); } private void thenAllSpansArePresent(Optional firstHttpSpan, From ee51d41e1e7044d66e07ddbe47957444cbc25dca Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Fri, 16 Dec 2016 13:32:49 +0100 Subject: [PATCH 150/235] Made tests less brittle --- .../cloud/sleuth/instrument/web/view/Issue469Tests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/view/Issue469Tests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/view/Issue469Tests.java index 1cfbd65d5..286dbe37f 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/view/Issue469Tests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/view/Issue469Tests.java @@ -54,7 +54,7 @@ public class Issue469Tests { .getForObject("http://localhost:" + port() + "/welcome", String.class); } catch (Exception e) { // JSPs are not rendered - then(e).hasMessage("404 Not Found"); + then(e).hasMessage("404"); } then(ExceptionUtils.getLastException()).isNull(); From ac365686cd50da9bf5dc81a48cbb29f47fbf0e9f Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Fri, 16 Dec 2016 13:32:49 +0100 Subject: [PATCH 151/235] Made tests less brittle --- .../cloud/sleuth/instrument/web/view/Issue469Tests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/view/Issue469Tests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/view/Issue469Tests.java index 1cfbd65d5..286dbe37f 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/view/Issue469Tests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/view/Issue469Tests.java @@ -54,7 +54,7 @@ public class Issue469Tests { .getForObject("http://localhost:" + port() + "/welcome", String.class); } catch (Exception e) { // JSPs are not rendered - then(e).hasMessage("404 Not Found"); + then(e).hasMessage("404"); } then(ExceptionUtils.getLastException()).isNull(); From 1fd3afe7aa66c0efd19953d3151ae42abdbff746 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Fri, 16 Dec 2016 13:36:43 +0100 Subject: [PATCH 152/235] Made tests less brittle - fixed the stupid change --- .../cloud/sleuth/instrument/web/view/Issue469Tests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/view/Issue469Tests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/view/Issue469Tests.java index 286dbe37f..66e9ea6d5 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/view/Issue469Tests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/view/Issue469Tests.java @@ -54,7 +54,7 @@ public class Issue469Tests { .getForObject("http://localhost:" + port() + "/welcome", String.class); } catch (Exception e) { // JSPs are not rendered - then(e).hasMessage("404"); + then(e).hasMessageContaining("404"); } then(ExceptionUtils.getLastException()).isNull(); From 8ff34c28a954288cf7739d23e5da48ba3ee4da1d Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Fri, 16 Dec 2016 13:36:43 +0100 Subject: [PATCH 153/235] Made tests less brittle - fixed the stupid change --- .../cloud/sleuth/instrument/web/view/Issue469Tests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/view/Issue469Tests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/view/Issue469Tests.java index 286dbe37f..66e9ea6d5 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/view/Issue469Tests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/view/Issue469Tests.java @@ -54,7 +54,7 @@ public class Issue469Tests { .getForObject("http://localhost:" + port() + "/welcome", String.class); } catch (Exception e) { // JSPs are not rendered - then(e).hasMessage("404"); + then(e).hasMessageContaining("404"); } then(ExceptionUtils.getLastException()).isNull(); From 5502bab61b0261171def1ba0062f1e5c34ef1b48 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Fri, 16 Dec 2016 13:59:03 +0100 Subject: [PATCH 154/235] Premature async rest template (#479) without this change the async rest template provides wrong value of the span duration with this change the span is closed via a callback fixes #475 --- .../web/client/TraceAsyncRestTemplate.java | 67 ++++++++--- ...stTemplateTraceAspectIntegrationTests.java | 17 ++- ...eWebAsyncClientAutoConfigurationTests.java | 104 +++++++++++++++++- 3 files changed, 167 insertions(+), 21 deletions(-) diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncRestTemplate.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncRestTemplate.java index 4db2d1567..8543f0849 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncRestTemplate.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncRestTemplate.java @@ -20,11 +20,13 @@ import java.net.URI; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.Tracer; +import org.springframework.cloud.sleuth.util.ExceptionUtils; import org.springframework.core.task.AsyncListenableTaskExecutor; import org.springframework.http.HttpMethod; import org.springframework.http.client.AsyncClientHttpRequestFactory; import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.util.concurrent.ListenableFuture; +import org.springframework.util.concurrent.ListenableFutureCallback; import org.springframework.web.client.AsyncRequestCallback; import org.springframework.web.client.AsyncRestTemplate; import org.springframework.web.client.ResponseExtractor; @@ -75,27 +77,62 @@ public class TraceAsyncRestTemplate extends AsyncRestTemplate { protected ListenableFuture doExecute(URI url, HttpMethod method, AsyncRequestCallback requestCallback, ResponseExtractor responseExtractor) throws RestClientException { - try { - return super.doExecute(url, method, requestCallback, responseExtractor); - } finally { + ListenableFuture future = super.doExecute(url, method, requestCallback, responseExtractor); + Span span = this.tracer.getCurrentSpan(); + future.addCallback(new TraceListenableFutureCallback<>(this.tracer, span)); + // potential race can happen here + if (span != null && span.equals(this.tracer.getCurrentSpan())) { + this.tracer.detach(span); + } + return future; + } + + private static class TraceListenableFutureCallback implements ListenableFutureCallback { + + private final Tracer tracer; + private final Span parent; + + private TraceListenableFutureCallback(Tracer tracer, Span parent) { + this.tracer = tracer; + this.parent = parent; + } + + @Override + public void onFailure(Throwable ex) { + continueSpan(); + this.tracer.addTag(Span.SPAN_ERROR_TAG_NAME, ExceptionUtils.getExceptionMessage(ex)); finish(); } - } - private void finish() { - if (!isTracing()) { - return; + @Override + public void onSuccess(T result) { + continueSpan(); + finish(); + } + + private void continueSpan() { + this.tracer.continueSpan(this.parent); + } + + private void finish() { + if (!isTracing()) { + return; + } + currentSpan().logEvent(Span.CLIENT_RECV); + this.tracer.close(currentSpan()); + } + + private Span currentSpan() { + return this.tracer.getCurrentSpan(); + } + + private boolean isTracing() { + return this.tracer.isTracing(); } - currentSpan().logEvent(Span.CLIENT_RECV); - this.tracer.close(this.currentSpan()); } - private Span currentSpan() { - return this.tracer.getCurrentSpan(); - } - private boolean isTracing() { - return this.tracer.isTracing(); - } + + } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/RestTemplateTraceAspectIntegrationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/RestTemplateTraceAspectIntegrationTests.java index 3a3de25bd..b4ac59913 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/RestTemplateTraceAspectIntegrationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/RestTemplateTraceAspectIntegrationTests.java @@ -3,6 +3,7 @@ package org.springframework.cloud.sleuth.instrument.web; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -49,11 +50,9 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @DirtiesContext public class RestTemplateTraceAspectIntegrationTests { - @Autowired - private WebApplicationContext context; - - @Autowired - private AspectTestingController controller; + @Autowired WebApplicationContext context; + @Autowired AspectTestingController controller; + @Autowired Tracer tracer; private MockMvc mockMvc; @@ -61,6 +60,14 @@ public class RestTemplateTraceAspectIntegrationTests { public void init() { this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build(); this.controller.reset(); + ExceptionUtils.setFail(true); + } + + @Before + @After + public void verify() { + then(this.tracer.getCurrentSpan()).isNull(); + then(ExceptionUtils.getLastException()).isNull(); } @Test diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfigurationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfigurationTests.java index afbc9bee0..92de940d7 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfigurationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfigurationTests.java @@ -18,7 +18,10 @@ package org.springframework.cloud.sleuth.instrument.web.client; import java.io.IOException; import java.net.URI; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import org.junit.Before; import org.junit.Test; import org.junit.experimental.runners.Enclosed; import org.junit.runner.RunWith; @@ -26,18 +29,28 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.boot.test.WebIntegrationTest; +import org.springframework.cloud.sleuth.Span; +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.ExceptionUtils; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; import org.springframework.http.client.AsyncClientHttpRequest; import org.springframework.http.client.AsyncClientHttpRequestFactory; import org.springframework.http.client.ClientHttpRequest; import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.http.client.SimpleClientHttpRequestFactory; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.util.concurrent.ListenableFuture; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.AsyncRestTemplate; -import static org.assertj.core.api.BDDAssertions.then; +import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; /** * @author Marcin Grzejszczak @@ -147,4 +160,93 @@ public class TraceWebAsyncClientAutoConfigurationTests { } } + @RunWith(SpringJUnit4ClassRunner.class) + @SpringApplicationConfiguration(classes = { DurationChecking.TestConfiguration.class }) + @WebIntegrationTest(randomPort = true) + public static class DurationChecking { + @Autowired AsyncRestTemplate asyncRestTemplate; + @Autowired Environment environment; + @Autowired ArrayListSpanAccumulator accumulator; + @Autowired Tracer tracer; + + @Before + public void setup() { + ExceptionUtils.setFail(true); + } + + @Test + public void should_close_span_upon_success_callback() + throws ExecutionException, InterruptedException { + ListenableFuture> future = this.asyncRestTemplate + .getForEntity("http://localhost:" + port() + "/foo", String.class); + String result = future.get().getBody(); + + then(result).isEqualTo("foo"); + then(this.accumulator.getSpans().stream().filter( + span -> span.logs().stream().filter(log -> Span.CLIENT_RECV.equals(log.getEvent())).findFirst().isPresent() + ).findFirst().get()).matches(span -> span.getAccumulatedMicros() >= TimeUnit.MILLISECONDS.toMicros(100)); + then(this.tracer.getCurrentSpan()).isNull(); + then(ExceptionUtils.getLastException()).isNull(); + } + + @Test + public void should_close_span_upon_failure_callback() + throws ExecutionException, InterruptedException { + ListenableFuture> future; + try { + future = this.asyncRestTemplate + .getForEntity("http://localhost:" + port() + "/blowsup", String.class); + future.get(); + } catch (Exception e) { + then(e.getMessage()).contains("Internal Server Error"); + } + + then(this.accumulator.getSpans().stream().filter( + span -> span.logs().stream().filter(log -> Span.CLIENT_RECV.equals(log.getEvent())).findFirst().isPresent() + ).findFirst().get()).matches(span -> span.getAccumulatedMicros() >= TimeUnit.MILLISECONDS.toMicros(100)) + .hasATag(Span.SPAN_ERROR_TAG_NAME, "500 Internal Server Error"); + then(this.tracer.getCurrentSpan()).isNull(); + then(ExceptionUtils.getLastException()).isNull(); + } + + int port() { + return this.environment.getProperty("local.server.port", Integer.class); + } + + @EnableAutoConfiguration + @Configuration + public static class TestConfiguration { + + @Bean ArrayListSpanAccumulator accumulator() { + return new ArrayListSpanAccumulator(); + } + + @Bean + MyController myController() { + return new MyController(); + } + + @Bean AlwaysSampler sampler() { + return new AlwaysSampler(); + } + } + + @RestController + public static class MyController { + + @RequestMapping("/foo") + String foo() throws Exception { + Thread.sleep(100); + return "foo"; + } + + @RequestMapping("/blowsup") + String blowsup() throws Exception { + Thread.sleep(100); + throw new RuntimeException("boom"); + } + } + + } + } \ No newline at end of file From 22a4c77219806112afd5a761c0c8a9fb0c0f45e9 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Fri, 16 Dec 2016 13:59:03 +0100 Subject: [PATCH 155/235] Premature async rest template (#479) without this change the async rest template provides wrong value of the span duration with this change the span is closed via a callback fixes #475 --- .../web/client/TraceAsyncRestTemplate.java | 67 ++++++++--- ...stTemplateTraceAspectIntegrationTests.java | 17 ++- ...eWebAsyncClientAutoConfigurationTests.java | 104 +++++++++++++++++- 3 files changed, 167 insertions(+), 21 deletions(-) diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncRestTemplate.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncRestTemplate.java index 4db2d1567..8543f0849 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncRestTemplate.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncRestTemplate.java @@ -20,11 +20,13 @@ import java.net.URI; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.Tracer; +import org.springframework.cloud.sleuth.util.ExceptionUtils; import org.springframework.core.task.AsyncListenableTaskExecutor; import org.springframework.http.HttpMethod; import org.springframework.http.client.AsyncClientHttpRequestFactory; import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.util.concurrent.ListenableFuture; +import org.springframework.util.concurrent.ListenableFutureCallback; import org.springframework.web.client.AsyncRequestCallback; import org.springframework.web.client.AsyncRestTemplate; import org.springframework.web.client.ResponseExtractor; @@ -75,27 +77,62 @@ public class TraceAsyncRestTemplate extends AsyncRestTemplate { protected ListenableFuture doExecute(URI url, HttpMethod method, AsyncRequestCallback requestCallback, ResponseExtractor responseExtractor) throws RestClientException { - try { - return super.doExecute(url, method, requestCallback, responseExtractor); - } finally { + ListenableFuture future = super.doExecute(url, method, requestCallback, responseExtractor); + Span span = this.tracer.getCurrentSpan(); + future.addCallback(new TraceListenableFutureCallback<>(this.tracer, span)); + // potential race can happen here + if (span != null && span.equals(this.tracer.getCurrentSpan())) { + this.tracer.detach(span); + } + return future; + } + + private static class TraceListenableFutureCallback implements ListenableFutureCallback { + + private final Tracer tracer; + private final Span parent; + + private TraceListenableFutureCallback(Tracer tracer, Span parent) { + this.tracer = tracer; + this.parent = parent; + } + + @Override + public void onFailure(Throwable ex) { + continueSpan(); + this.tracer.addTag(Span.SPAN_ERROR_TAG_NAME, ExceptionUtils.getExceptionMessage(ex)); finish(); } - } - private void finish() { - if (!isTracing()) { - return; + @Override + public void onSuccess(T result) { + continueSpan(); + finish(); + } + + private void continueSpan() { + this.tracer.continueSpan(this.parent); + } + + private void finish() { + if (!isTracing()) { + return; + } + currentSpan().logEvent(Span.CLIENT_RECV); + this.tracer.close(currentSpan()); + } + + private Span currentSpan() { + return this.tracer.getCurrentSpan(); + } + + private boolean isTracing() { + return this.tracer.isTracing(); } - currentSpan().logEvent(Span.CLIENT_RECV); - this.tracer.close(this.currentSpan()); } - private Span currentSpan() { - return this.tracer.getCurrentSpan(); - } - private boolean isTracing() { - return this.tracer.isTracing(); - } + + } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/RestTemplateTraceAspectIntegrationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/RestTemplateTraceAspectIntegrationTests.java index 3a3de25bd..b4ac59913 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/RestTemplateTraceAspectIntegrationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/RestTemplateTraceAspectIntegrationTests.java @@ -3,6 +3,7 @@ package org.springframework.cloud.sleuth.instrument.web; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -49,11 +50,9 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @DirtiesContext public class RestTemplateTraceAspectIntegrationTests { - @Autowired - private WebApplicationContext context; - - @Autowired - private AspectTestingController controller; + @Autowired WebApplicationContext context; + @Autowired AspectTestingController controller; + @Autowired Tracer tracer; private MockMvc mockMvc; @@ -61,6 +60,14 @@ public class RestTemplateTraceAspectIntegrationTests { public void init() { this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build(); this.controller.reset(); + ExceptionUtils.setFail(true); + } + + @Before + @After + public void verify() { + then(this.tracer.getCurrentSpan()).isNull(); + then(ExceptionUtils.getLastException()).isNull(); } @Test diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfigurationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfigurationTests.java index afbc9bee0..92de940d7 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfigurationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfigurationTests.java @@ -18,7 +18,10 @@ package org.springframework.cloud.sleuth.instrument.web.client; import java.io.IOException; import java.net.URI; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import org.junit.Before; import org.junit.Test; import org.junit.experimental.runners.Enclosed; import org.junit.runner.RunWith; @@ -26,18 +29,28 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.boot.test.WebIntegrationTest; +import org.springframework.cloud.sleuth.Span; +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.ExceptionUtils; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; import org.springframework.http.client.AsyncClientHttpRequest; import org.springframework.http.client.AsyncClientHttpRequestFactory; import org.springframework.http.client.ClientHttpRequest; import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.http.client.SimpleClientHttpRequestFactory; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.util.concurrent.ListenableFuture; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.AsyncRestTemplate; -import static org.assertj.core.api.BDDAssertions.then; +import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; /** * @author Marcin Grzejszczak @@ -147,4 +160,93 @@ public class TraceWebAsyncClientAutoConfigurationTests { } } + @RunWith(SpringJUnit4ClassRunner.class) + @SpringApplicationConfiguration(classes = { DurationChecking.TestConfiguration.class }) + @WebIntegrationTest(randomPort = true) + public static class DurationChecking { + @Autowired AsyncRestTemplate asyncRestTemplate; + @Autowired Environment environment; + @Autowired ArrayListSpanAccumulator accumulator; + @Autowired Tracer tracer; + + @Before + public void setup() { + ExceptionUtils.setFail(true); + } + + @Test + public void should_close_span_upon_success_callback() + throws ExecutionException, InterruptedException { + ListenableFuture> future = this.asyncRestTemplate + .getForEntity("http://localhost:" + port() + "/foo", String.class); + String result = future.get().getBody(); + + then(result).isEqualTo("foo"); + then(this.accumulator.getSpans().stream().filter( + span -> span.logs().stream().filter(log -> Span.CLIENT_RECV.equals(log.getEvent())).findFirst().isPresent() + ).findFirst().get()).matches(span -> span.getAccumulatedMicros() >= TimeUnit.MILLISECONDS.toMicros(100)); + then(this.tracer.getCurrentSpan()).isNull(); + then(ExceptionUtils.getLastException()).isNull(); + } + + @Test + public void should_close_span_upon_failure_callback() + throws ExecutionException, InterruptedException { + ListenableFuture> future; + try { + future = this.asyncRestTemplate + .getForEntity("http://localhost:" + port() + "/blowsup", String.class); + future.get(); + } catch (Exception e) { + then(e.getMessage()).contains("Internal Server Error"); + } + + then(this.accumulator.getSpans().stream().filter( + span -> span.logs().stream().filter(log -> Span.CLIENT_RECV.equals(log.getEvent())).findFirst().isPresent() + ).findFirst().get()).matches(span -> span.getAccumulatedMicros() >= TimeUnit.MILLISECONDS.toMicros(100)) + .hasATag(Span.SPAN_ERROR_TAG_NAME, "500 Internal Server Error"); + then(this.tracer.getCurrentSpan()).isNull(); + then(ExceptionUtils.getLastException()).isNull(); + } + + int port() { + return this.environment.getProperty("local.server.port", Integer.class); + } + + @EnableAutoConfiguration + @Configuration + public static class TestConfiguration { + + @Bean ArrayListSpanAccumulator accumulator() { + return new ArrayListSpanAccumulator(); + } + + @Bean + MyController myController() { + return new MyController(); + } + + @Bean AlwaysSampler sampler() { + return new AlwaysSampler(); + } + } + + @RestController + public static class MyController { + + @RequestMapping("/foo") + String foo() throws Exception { + Thread.sleep(100); + return "foo"; + } + + @RequestMapping("/blowsup") + String blowsup() throws Exception { + Thread.sleep(100); + throw new RuntimeException("boom"); + } + } + + } + } \ No newline at end of file From ce94d15a54cc4d4c87c61948c046a89dd6a6e225 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Fri, 16 Dec 2016 14:53:54 +0100 Subject: [PATCH 156/235] Made tests less brittle --- ...eWebAsyncClientAutoConfigurationTests.java | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfigurationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfigurationTests.java index 92de940d7..0453ce15f 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfigurationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfigurationTests.java @@ -21,6 +21,7 @@ import java.net.URI; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; +import org.assertj.core.api.BDDAssertions; import org.junit.Before; import org.junit.Test; import org.junit.experimental.runners.Enclosed; @@ -50,6 +51,8 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.AsyncRestTemplate; +import com.jayway.awaitility.Awaitility; + import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; /** @@ -197,16 +200,20 @@ public class TraceWebAsyncClientAutoConfigurationTests { future = this.asyncRestTemplate .getForEntity("http://localhost:" + port() + "/blowsup", String.class); future.get(); + BDDAssertions.fail("should throw an exception from the controller"); } catch (Exception e) { - then(e.getMessage()).contains("Internal Server Error"); + } - then(this.accumulator.getSpans().stream().filter( - span -> span.logs().stream().filter(log -> Span.CLIENT_RECV.equals(log.getEvent())).findFirst().isPresent() - ).findFirst().get()).matches(span -> span.getAccumulatedMicros() >= TimeUnit.MILLISECONDS.toMicros(100)) - .hasATag(Span.SPAN_ERROR_TAG_NAME, "500 Internal Server Error"); - then(this.tracer.getCurrentSpan()).isNull(); - then(ExceptionUtils.getLastException()).isNull(); + Awaitility.await().until(() -> { + then(this.accumulator.getSpans().stream() + .filter(span -> span.logs().stream().filter(log -> Span.CLIENT_RECV.equals(log.getEvent())) + .findFirst().isPresent()).findFirst().get()).matches( + span -> span.getAccumulatedMicros() >= TimeUnit.MILLISECONDS.toMicros(100)) + .hasATag(Span.SPAN_ERROR_TAG_NAME, "500 Internal Server Error"); + then(this.tracer.getCurrentSpan()).isNull(); + then(ExceptionUtils.getLastException()).isNull(); + }); } int port() { From 1298d3d5816892cc804e27ec556fc1682a3bd772 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Fri, 16 Dec 2016 14:53:54 +0100 Subject: [PATCH 157/235] Made tests less brittle --- ...eWebAsyncClientAutoConfigurationTests.java | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfigurationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfigurationTests.java index 92de940d7..0453ce15f 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfigurationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfigurationTests.java @@ -21,6 +21,7 @@ import java.net.URI; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; +import org.assertj.core.api.BDDAssertions; import org.junit.Before; import org.junit.Test; import org.junit.experimental.runners.Enclosed; @@ -50,6 +51,8 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.AsyncRestTemplate; +import com.jayway.awaitility.Awaitility; + import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; /** @@ -197,16 +200,20 @@ public class TraceWebAsyncClientAutoConfigurationTests { future = this.asyncRestTemplate .getForEntity("http://localhost:" + port() + "/blowsup", String.class); future.get(); + BDDAssertions.fail("should throw an exception from the controller"); } catch (Exception e) { - then(e.getMessage()).contains("Internal Server Error"); + } - then(this.accumulator.getSpans().stream().filter( - span -> span.logs().stream().filter(log -> Span.CLIENT_RECV.equals(log.getEvent())).findFirst().isPresent() - ).findFirst().get()).matches(span -> span.getAccumulatedMicros() >= TimeUnit.MILLISECONDS.toMicros(100)) - .hasATag(Span.SPAN_ERROR_TAG_NAME, "500 Internal Server Error"); - then(this.tracer.getCurrentSpan()).isNull(); - then(ExceptionUtils.getLastException()).isNull(); + Awaitility.await().until(() -> { + then(this.accumulator.getSpans().stream() + .filter(span -> span.logs().stream().filter(log -> Span.CLIENT_RECV.equals(log.getEvent())) + .findFirst().isPresent()).findFirst().get()).matches( + span -> span.getAccumulatedMicros() >= TimeUnit.MILLISECONDS.toMicros(100)) + .hasATag(Span.SPAN_ERROR_TAG_NAME, "500 Internal Server Error"); + then(this.tracer.getCurrentSpan()).isNull(); + then(ExceptionUtils.getLastException()).isNull(); + }); } int port() { From 439fefd3af017183a11d259b2a80697b0ebc2320 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Fri, 16 Dec 2016 15:00:03 +0100 Subject: [PATCH 158/235] Made tests even less brittle --- .../web/client/TraceWebAsyncClientAutoConfigurationTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfigurationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfigurationTests.java index 0453ce15f..7871f591a 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfigurationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfigurationTests.java @@ -210,7 +210,7 @@ public class TraceWebAsyncClientAutoConfigurationTests { .filter(span -> span.logs().stream().filter(log -> Span.CLIENT_RECV.equals(log.getEvent())) .findFirst().isPresent()).findFirst().get()).matches( span -> span.getAccumulatedMicros() >= TimeUnit.MILLISECONDS.toMicros(100)) - .hasATag(Span.SPAN_ERROR_TAG_NAME, "500 Internal Server Error"); + .hasATagWithKey(Span.SPAN_ERROR_TAG_NAME); then(this.tracer.getCurrentSpan()).isNull(); then(ExceptionUtils.getLastException()).isNull(); }); From 5fb1eebac25bcbb21e727c949ec2250e7485d921 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Fri, 16 Dec 2016 15:00:03 +0100 Subject: [PATCH 159/235] Made tests even less brittle --- .../web/client/TraceWebAsyncClientAutoConfigurationTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfigurationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfigurationTests.java index 0453ce15f..7871f591a 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfigurationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfigurationTests.java @@ -210,7 +210,7 @@ public class TraceWebAsyncClientAutoConfigurationTests { .filter(span -> span.logs().stream().filter(log -> Span.CLIENT_RECV.equals(log.getEvent())) .findFirst().isPresent()).findFirst().get()).matches( span -> span.getAccumulatedMicros() >= TimeUnit.MILLISECONDS.toMicros(100)) - .hasATag(Span.SPAN_ERROR_TAG_NAME, "500 Internal Server Error"); + .hasATagWithKey(Span.SPAN_ERROR_TAG_NAME); then(this.tracer.getCurrentSpan()).isNull(); then(ExceptionUtils.getLastException()).isNull(); }); From 76209a365375977f807879c6e9b6a3a8f499de92 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Mon, 19 Dec 2016 10:19:52 +0100 Subject: [PATCH 160/235] Fixing race conditions in tests --- .../client/TraceWebAsyncClientAutoConfigurationTests.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfigurationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfigurationTests.java index 7871f591a..ea7570cd3 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfigurationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfigurationTests.java @@ -18,6 +18,7 @@ package org.springframework.cloud.sleuth.instrument.web.client; import java.io.IOException; import java.net.URI; +import java.util.ArrayList; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; @@ -185,7 +186,7 @@ public class TraceWebAsyncClientAutoConfigurationTests { String result = future.get().getBody(); then(result).isEqualTo("foo"); - then(this.accumulator.getSpans().stream().filter( + then(new ArrayList<>(this.accumulator.getSpans()).stream().filter( span -> span.logs().stream().filter(log -> Span.CLIENT_RECV.equals(log.getEvent())).findFirst().isPresent() ).findFirst().get()).matches(span -> span.getAccumulatedMicros() >= TimeUnit.MILLISECONDS.toMicros(100)); then(this.tracer.getCurrentSpan()).isNull(); @@ -206,7 +207,7 @@ public class TraceWebAsyncClientAutoConfigurationTests { } Awaitility.await().until(() -> { - then(this.accumulator.getSpans().stream() + then(new ArrayList<>(this.accumulator.getSpans()).stream() .filter(span -> span.logs().stream().filter(log -> Span.CLIENT_RECV.equals(log.getEvent())) .findFirst().isPresent()).findFirst().get()).matches( span -> span.getAccumulatedMicros() >= TimeUnit.MILLISECONDS.toMicros(100)) From c3bc578323b0547ee55357334b63a3198883b37b Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Mon, 19 Dec 2016 10:19:52 +0100 Subject: [PATCH 161/235] Fixing race conditions in tests --- .../client/TraceWebAsyncClientAutoConfigurationTests.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfigurationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfigurationTests.java index 7871f591a..ea7570cd3 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfigurationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfigurationTests.java @@ -18,6 +18,7 @@ package org.springframework.cloud.sleuth.instrument.web.client; import java.io.IOException; import java.net.URI; +import java.util.ArrayList; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; @@ -185,7 +186,7 @@ public class TraceWebAsyncClientAutoConfigurationTests { String result = future.get().getBody(); then(result).isEqualTo("foo"); - then(this.accumulator.getSpans().stream().filter( + then(new ArrayList<>(this.accumulator.getSpans()).stream().filter( span -> span.logs().stream().filter(log -> Span.CLIENT_RECV.equals(log.getEvent())).findFirst().isPresent() ).findFirst().get()).matches(span -> span.getAccumulatedMicros() >= TimeUnit.MILLISECONDS.toMicros(100)); then(this.tracer.getCurrentSpan()).isNull(); @@ -206,7 +207,7 @@ public class TraceWebAsyncClientAutoConfigurationTests { } Awaitility.await().until(() -> { - then(this.accumulator.getSpans().stream() + then(new ArrayList<>(this.accumulator.getSpans()).stream() .filter(span -> span.logs().stream().filter(log -> Span.CLIENT_RECV.equals(log.getEvent())) .findFirst().isPresent()).findFirst().get()).matches( span -> span.getAccumulatedMicros() >= TimeUnit.MILLISECONDS.toMicros(100)) From b24713c9432ffc9b3cfccf03d318105afed777b3 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Mon, 19 Dec 2016 11:44:25 +0100 Subject: [PATCH 162/235] Added consistency between metric and metrics without this change we have 2 different properties. One is `spring.sleuth.metric` and the other is `spring.sleuth.metrics`. with this change we're introducing conditionality on `spring.sleuth.metric.enabled` fixes #477 --- .../sleuth/metric/SleuthMetricProperties.java | 10 ++ .../metric/TraceMetricsAutoConfiguration.java | 42 ++++++- .../TraceMetricsAutoConfigurationTests.java | 114 ++++++++++++++++++ 3 files changed, 164 insertions(+), 2 deletions(-) create mode 100644 spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/metric/TraceMetricsAutoConfigurationTests.java diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/metric/SleuthMetricProperties.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/metric/SleuthMetricProperties.java index 4cbc67902..e40105224 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/metric/SleuthMetricProperties.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/metric/SleuthMetricProperties.java @@ -11,6 +11,16 @@ import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties("spring.sleuth.metric") public class SleuthMetricProperties { + private boolean enabled; + + public boolean isEnabled() { + return this.enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + private Span span = new Span(); public Span getSpan() { diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/metric/TraceMetricsAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/metric/TraceMetricsAutoConfiguration.java index e2bf3bbc0..55971834f 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/metric/TraceMetricsAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/metric/TraceMetricsAutoConfiguration.java @@ -16,15 +16,23 @@ package org.springframework.cloud.sleuth.metric; +import java.lang.invoke.MethodHandles; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.boot.actuate.metrics.CounterService; +import org.springframework.boot.autoconfigure.condition.ConditionOutcome; 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.ConditionalOnMissingClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.condition.SpringBootCondition; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; +import org.springframework.core.type.AnnotatedTypeMetadata; /** * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration Auto-configuration} @@ -34,7 +42,7 @@ import org.springframework.context.annotation.Configuration; * @since 1.0.0 */ @Configuration -@ConditionalOnProperty(value="spring.sleuth.metrics.enabled", matchIfMissing=true) +@Conditional(TraceMetricsAutoConfiguration.PickMetricIfMetricsIsMissing.class) @EnableConfigurationProperties public class TraceMetricsAutoConfiguration { @@ -69,4 +77,34 @@ public class TraceMetricsAutoConfiguration { public SpanMetricReporter noOpSpanReporterCounterService() { return new NoOpSpanMetricReporter(); } + + static class PickMetricIfMetricsIsMissing extends SpringBootCondition { + + private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); + + static final String DEPRECATED_SPRING_SLEUTH_METRICS_ENABLED = "spring.sleuth.metrics.enabled"; + static final String SPRING_SLEUTH_METRIC_ENABLED = "spring.sleuth.metric.enabled"; + + @Override + public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { + Boolean oldValue = context.getEnvironment().getProperty(DEPRECATED_SPRING_SLEUTH_METRICS_ENABLED, Boolean.class); + Boolean newValue = context.getEnvironment().getProperty(SPRING_SLEUTH_METRIC_ENABLED, Boolean.class); + if (oldValue != null) { + log.warn("You're using an old version of the metrics property. Instead of using [" + + DEPRECATED_SPRING_SLEUTH_METRICS_ENABLED + "] please use [" + SPRING_SLEUTH_METRIC_ENABLED + "]"); + return matchCondition(oldValue, DEPRECATED_SPRING_SLEUTH_METRICS_ENABLED); + } + if (newValue != null) { + return matchCondition(newValue, SPRING_SLEUTH_METRIC_ENABLED); + } + return ConditionOutcome.match("No property was passed - assuming that metrics are enabled."); + } + + private ConditionOutcome matchCondition(Boolean value, String property) { + if (Boolean.TRUE.equals(value)) { + return ConditionOutcome.match(); + } + return ConditionOutcome.noMatch("Property [" + property + "] is set to false."); + } + } } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/metric/TraceMetricsAutoConfigurationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/metric/TraceMetricsAutoConfigurationTests.java new file mode 100644 index 000000000..990f94fb3 --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/metric/TraceMetricsAutoConfigurationTests.java @@ -0,0 +1,114 @@ +/* + * 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.metric; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.BDDMockito; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.boot.autoconfigure.condition.ConditionOutcome; +import org.springframework.cloud.sleuth.metric.TraceMetricsAutoConfiguration.PickMetricIfMetricsIsMissing; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; +import org.springframework.mock.env.MockEnvironment; + +import static org.assertj.core.api.BDDAssertions.then; +import static org.springframework.cloud.sleuth.metric.TraceMetricsAutoConfiguration.PickMetricIfMetricsIsMissing.DEPRECATED_SPRING_SLEUTH_METRICS_ENABLED; +import static org.springframework.cloud.sleuth.metric.TraceMetricsAutoConfiguration.PickMetricIfMetricsIsMissing.SPRING_SLEUTH_METRIC_ENABLED; + +/** + * @author Marcin Grzejszczak + */ +@RunWith(MockitoJUnitRunner.class) +public class TraceMetricsAutoConfigurationTests { + + @Mock ConditionContext conditionContext; + @Mock AnnotatedTypeMetadata annotatedTypeMetadata; + MockEnvironment mockEnvironment = new MockEnvironment(); + + PickMetricIfMetricsIsMissing condition = new PickMetricIfMetricsIsMissing(); + + @Before + public void setup() { + BDDMockito.given(this.conditionContext.getEnvironment()).willReturn(this.mockEnvironment); + } + + @Test + public void should_turn_on_the_feature_when_no_explicit_one_was_provided() { + ConditionOutcome outcome = this.condition.getMatchOutcome(this.conditionContext, this.annotatedTypeMetadata); + + then(outcome.isMatch()).isTrue(); + } + + @Test + public void should_turn_on_the_feature_when_deprecated_property_is_enabled() { + this.mockEnvironment.setProperty(DEPRECATED_SPRING_SLEUTH_METRICS_ENABLED, "true"); + + ConditionOutcome outcome = this.condition.getMatchOutcome(this.conditionContext, this.annotatedTypeMetadata); + + then(outcome.isMatch()).isTrue(); + } + + @Test + public void should_turn_off_the_feature_when_deprecated_property_is_disabled() { + this.mockEnvironment.setProperty(DEPRECATED_SPRING_SLEUTH_METRICS_ENABLED, "false"); + + ConditionOutcome outcome = this.condition.getMatchOutcome(this.conditionContext, this.annotatedTypeMetadata); + + then(outcome.isMatch()).isFalse(); + } + + @Test + public void should_turn_on_the_feature_when_property_is_enabled() { + this.mockEnvironment.setProperty(SPRING_SLEUTH_METRIC_ENABLED, "true"); + + ConditionOutcome outcome = this.condition.getMatchOutcome(this.conditionContext, this.annotatedTypeMetadata); + + then(outcome.isMatch()).isTrue(); + } + + @Test + public void should_turn_off_the_feature_when_property_is_disabled() { + this.mockEnvironment.setProperty(SPRING_SLEUTH_METRIC_ENABLED, "false"); + + ConditionOutcome outcome = this.condition.getMatchOutcome(this.conditionContext, this.annotatedTypeMetadata); + + then(outcome.isMatch()).isFalse(); + } + + @Test + public void should_turn_on_the_feature_when_new_property_is_disabled_and_old_is_enabled() { + this.mockEnvironment.setProperty(DEPRECATED_SPRING_SLEUTH_METRICS_ENABLED, "true"); + this.mockEnvironment.setProperty(SPRING_SLEUTH_METRIC_ENABLED, "false"); + + ConditionOutcome outcome = this.condition.getMatchOutcome(this.conditionContext, this.annotatedTypeMetadata); + + then(outcome.isMatch()).isTrue(); + } + + @Test + public void should_turn_off_the_feature_when_new_property_is_disabled_and_old_is_enabled() { + this.mockEnvironment.setProperty(DEPRECATED_SPRING_SLEUTH_METRICS_ENABLED, "false"); + this.mockEnvironment.setProperty(SPRING_SLEUTH_METRIC_ENABLED, "true"); + + ConditionOutcome outcome = this.condition.getMatchOutcome(this.conditionContext, this.annotatedTypeMetadata); + + then(outcome.isMatch()).isFalse(); + } +} \ No newline at end of file From 4d3b13800ecdbbc1354ea98f8baefd2f008a0074 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Mon, 19 Dec 2016 11:44:25 +0100 Subject: [PATCH 163/235] Added consistency between metric and metrics without this change we have 2 different properties. One is `spring.sleuth.metric` and the other is `spring.sleuth.metrics`. with this change we're introducing conditionality on `spring.sleuth.metric.enabled` fixes #477 --- .../sleuth/metric/SleuthMetricProperties.java | 10 ++ .../metric/TraceMetricsAutoConfiguration.java | 42 ++++++- .../TraceMetricsAutoConfigurationTests.java | 114 ++++++++++++++++++ 3 files changed, 164 insertions(+), 2 deletions(-) create mode 100644 spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/metric/TraceMetricsAutoConfigurationTests.java diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/metric/SleuthMetricProperties.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/metric/SleuthMetricProperties.java index 4cbc67902..e40105224 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/metric/SleuthMetricProperties.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/metric/SleuthMetricProperties.java @@ -11,6 +11,16 @@ import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties("spring.sleuth.metric") public class SleuthMetricProperties { + private boolean enabled; + + public boolean isEnabled() { + return this.enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + private Span span = new Span(); public Span getSpan() { diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/metric/TraceMetricsAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/metric/TraceMetricsAutoConfiguration.java index e2bf3bbc0..55971834f 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/metric/TraceMetricsAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/metric/TraceMetricsAutoConfiguration.java @@ -16,15 +16,23 @@ package org.springframework.cloud.sleuth.metric; +import java.lang.invoke.MethodHandles; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.boot.actuate.metrics.CounterService; +import org.springframework.boot.autoconfigure.condition.ConditionOutcome; 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.ConditionalOnMissingClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.condition.SpringBootCondition; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; +import org.springframework.core.type.AnnotatedTypeMetadata; /** * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration Auto-configuration} @@ -34,7 +42,7 @@ import org.springframework.context.annotation.Configuration; * @since 1.0.0 */ @Configuration -@ConditionalOnProperty(value="spring.sleuth.metrics.enabled", matchIfMissing=true) +@Conditional(TraceMetricsAutoConfiguration.PickMetricIfMetricsIsMissing.class) @EnableConfigurationProperties public class TraceMetricsAutoConfiguration { @@ -69,4 +77,34 @@ public class TraceMetricsAutoConfiguration { public SpanMetricReporter noOpSpanReporterCounterService() { return new NoOpSpanMetricReporter(); } + + static class PickMetricIfMetricsIsMissing extends SpringBootCondition { + + private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); + + static final String DEPRECATED_SPRING_SLEUTH_METRICS_ENABLED = "spring.sleuth.metrics.enabled"; + static final String SPRING_SLEUTH_METRIC_ENABLED = "spring.sleuth.metric.enabled"; + + @Override + public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { + Boolean oldValue = context.getEnvironment().getProperty(DEPRECATED_SPRING_SLEUTH_METRICS_ENABLED, Boolean.class); + Boolean newValue = context.getEnvironment().getProperty(SPRING_SLEUTH_METRIC_ENABLED, Boolean.class); + if (oldValue != null) { + log.warn("You're using an old version of the metrics property. Instead of using [" + + DEPRECATED_SPRING_SLEUTH_METRICS_ENABLED + "] please use [" + SPRING_SLEUTH_METRIC_ENABLED + "]"); + return matchCondition(oldValue, DEPRECATED_SPRING_SLEUTH_METRICS_ENABLED); + } + if (newValue != null) { + return matchCondition(newValue, SPRING_SLEUTH_METRIC_ENABLED); + } + return ConditionOutcome.match("No property was passed - assuming that metrics are enabled."); + } + + private ConditionOutcome matchCondition(Boolean value, String property) { + if (Boolean.TRUE.equals(value)) { + return ConditionOutcome.match(); + } + return ConditionOutcome.noMatch("Property [" + property + "] is set to false."); + } + } } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/metric/TraceMetricsAutoConfigurationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/metric/TraceMetricsAutoConfigurationTests.java new file mode 100644 index 000000000..990f94fb3 --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/metric/TraceMetricsAutoConfigurationTests.java @@ -0,0 +1,114 @@ +/* + * 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.metric; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.BDDMockito; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.boot.autoconfigure.condition.ConditionOutcome; +import org.springframework.cloud.sleuth.metric.TraceMetricsAutoConfiguration.PickMetricIfMetricsIsMissing; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; +import org.springframework.mock.env.MockEnvironment; + +import static org.assertj.core.api.BDDAssertions.then; +import static org.springframework.cloud.sleuth.metric.TraceMetricsAutoConfiguration.PickMetricIfMetricsIsMissing.DEPRECATED_SPRING_SLEUTH_METRICS_ENABLED; +import static org.springframework.cloud.sleuth.metric.TraceMetricsAutoConfiguration.PickMetricIfMetricsIsMissing.SPRING_SLEUTH_METRIC_ENABLED; + +/** + * @author Marcin Grzejszczak + */ +@RunWith(MockitoJUnitRunner.class) +public class TraceMetricsAutoConfigurationTests { + + @Mock ConditionContext conditionContext; + @Mock AnnotatedTypeMetadata annotatedTypeMetadata; + MockEnvironment mockEnvironment = new MockEnvironment(); + + PickMetricIfMetricsIsMissing condition = new PickMetricIfMetricsIsMissing(); + + @Before + public void setup() { + BDDMockito.given(this.conditionContext.getEnvironment()).willReturn(this.mockEnvironment); + } + + @Test + public void should_turn_on_the_feature_when_no_explicit_one_was_provided() { + ConditionOutcome outcome = this.condition.getMatchOutcome(this.conditionContext, this.annotatedTypeMetadata); + + then(outcome.isMatch()).isTrue(); + } + + @Test + public void should_turn_on_the_feature_when_deprecated_property_is_enabled() { + this.mockEnvironment.setProperty(DEPRECATED_SPRING_SLEUTH_METRICS_ENABLED, "true"); + + ConditionOutcome outcome = this.condition.getMatchOutcome(this.conditionContext, this.annotatedTypeMetadata); + + then(outcome.isMatch()).isTrue(); + } + + @Test + public void should_turn_off_the_feature_when_deprecated_property_is_disabled() { + this.mockEnvironment.setProperty(DEPRECATED_SPRING_SLEUTH_METRICS_ENABLED, "false"); + + ConditionOutcome outcome = this.condition.getMatchOutcome(this.conditionContext, this.annotatedTypeMetadata); + + then(outcome.isMatch()).isFalse(); + } + + @Test + public void should_turn_on_the_feature_when_property_is_enabled() { + this.mockEnvironment.setProperty(SPRING_SLEUTH_METRIC_ENABLED, "true"); + + ConditionOutcome outcome = this.condition.getMatchOutcome(this.conditionContext, this.annotatedTypeMetadata); + + then(outcome.isMatch()).isTrue(); + } + + @Test + public void should_turn_off_the_feature_when_property_is_disabled() { + this.mockEnvironment.setProperty(SPRING_SLEUTH_METRIC_ENABLED, "false"); + + ConditionOutcome outcome = this.condition.getMatchOutcome(this.conditionContext, this.annotatedTypeMetadata); + + then(outcome.isMatch()).isFalse(); + } + + @Test + public void should_turn_on_the_feature_when_new_property_is_disabled_and_old_is_enabled() { + this.mockEnvironment.setProperty(DEPRECATED_SPRING_SLEUTH_METRICS_ENABLED, "true"); + this.mockEnvironment.setProperty(SPRING_SLEUTH_METRIC_ENABLED, "false"); + + ConditionOutcome outcome = this.condition.getMatchOutcome(this.conditionContext, this.annotatedTypeMetadata); + + then(outcome.isMatch()).isTrue(); + } + + @Test + public void should_turn_off_the_feature_when_new_property_is_disabled_and_old_is_enabled() { + this.mockEnvironment.setProperty(DEPRECATED_SPRING_SLEUTH_METRICS_ENABLED, "false"); + this.mockEnvironment.setProperty(SPRING_SLEUTH_METRIC_ENABLED, "true"); + + ConditionOutcome outcome = this.condition.getMatchOutcome(this.conditionContext, this.annotatedTypeMetadata); + + then(outcome.isMatch()).isFalse(); + } +} \ No newline at end of file From ad700ea66421da47b037be5602188e1497f069b3 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Mon, 19 Dec 2016 13:26:04 +0100 Subject: [PATCH 164/235] Adding SA only if peer tag is present without this change Zipkin doesn't properly visualize the span on the server side. with this change we're setting SA only if peer tag is set. fixes #481 --- .../zipkin/stream/ConvertToZipkinSpanList.java | 11 ++++++----- .../stream/ConvertToZipkinSpanListTests.java | 18 +++++++++++++++++- .../sleuth/zipkin/ZipkinSpanListener.java | 9 +++++---- .../sleuth/zipkin/ZipkinSpanListenerTests.java | 16 ++++++++++++++-- 4 files changed, 42 insertions(+), 12 deletions(-) diff --git a/spring-cloud-sleuth-zipkin-stream/src/main/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanList.java b/spring-cloud-sleuth-zipkin-stream/src/main/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanList.java index 7bd83c15b..96b1809ad 100644 --- a/spring-cloud-sleuth-zipkin-stream/src/main/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanList.java +++ b/spring-cloud-sleuth-zipkin-stream/src/main/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanList.java @@ -130,11 +130,12 @@ final class ConvertToZipkinSpanList { private static void ensureServerAddr(Span span, Builder zipkinSpan, Endpoint ep) { - String serviceName = span.tags().containsKey(Span.SPAN_PEER_SERVICE_TAG_NAME) - ? span.tags().get(Span.SPAN_PEER_SERVICE_TAG_NAME) : ep.serviceName; - Endpoint endpoint = ep.toBuilder().serviceName(serviceName).build(); - zipkinSpan.addBinaryAnnotation( - BinaryAnnotation.address(Constants.SERVER_ADDR, endpoint)); + if (span.tags().containsKey(Span.SPAN_PEER_SERVICE_TAG_NAME)) { + Endpoint endpoint = ep.toBuilder().serviceName(span.tags().get( + Span.SPAN_PEER_SERVICE_TAG_NAME)).build(); + zipkinSpan.addBinaryAnnotation( + BinaryAnnotation.address(Constants.SERVER_ADDR, endpoint)); + } } private static boolean notClientOrServer(Span span) { diff --git a/spring-cloud-sleuth-zipkin-stream/src/test/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanListTests.java b/spring-cloud-sleuth-zipkin-stream/src/test/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanListTests.java index 26161e080..6c5700e57 100644 --- a/spring-cloud-sleuth-zipkin-stream/src/test/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanListTests.java +++ b/spring-cloud-sleuth-zipkin-stream/src/test/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanListTests.java @@ -74,9 +74,10 @@ public class ConvertToZipkinSpanListTests { } @Test - public void appendsServerAddressTagIfClientLogIsPresent() { + public void appendServerAddressTagIfClientLogIsPresentWhenPeerServiceIsPresent() { Span span = span("foo"); span.logEvent(Constants.CLIENT_SEND); + span.tag(Span.SPAN_PEER_SERVICE_TAG_NAME, "myservice"); Spans spans = new Spans(this.host, Collections.singletonList(span)); List result = ConvertToZipkinSpanList.convert(spans); @@ -89,6 +90,21 @@ public class ConvertToZipkinSpanListTests { .contains("myservice"); } + @Test + public void doesNotAppendServerAddressTagIfClientLogIsPresent() { + Span span = span("foo"); + span.logEvent(Constants.CLIENT_SEND); + Spans spans = new Spans(this.host, Collections.singletonList(span)); + + List result = ConvertToZipkinSpanList.convert(spans); + + assertThat(result) + .hasSize(1) + .flatExtracting(input1 -> input1.binaryAnnotations) + .filteredOn("key", Constants.SERVER_ADDR) + .isEmpty(); + } + @Test public void shouldReuseServerAddressTag() { Span span = span("foo"); diff --git a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListener.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListener.java index 46a34f5b5..e10ec4936 100644 --- a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListener.java +++ b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListener.java @@ -127,10 +127,11 @@ public class ZipkinSpanListener implements SpanReporter { } private void ensureServerAddr(Span span, zipkin.Span.Builder zipkinSpan) { - String serviceName = span.tags().containsKey(Span.SPAN_PEER_SERVICE_TAG_NAME) ? - span.tags().get(Span.SPAN_PEER_SERVICE_TAG_NAME) : this.endpointLocator.local().serviceName; - zipkinSpan.addBinaryAnnotation(BinaryAnnotation.address(Constants.SERVER_ADDR, - this.endpointLocator.local().toBuilder().serviceName(serviceName).build())); + if (span.tags().containsKey(Span.SPAN_PEER_SERVICE_TAG_NAME)) { + zipkinSpan.addBinaryAnnotation(BinaryAnnotation.address(Constants.SERVER_ADDR, + this.endpointLocator.local().toBuilder().serviceName( + span.tags().get(Span.SPAN_PEER_SERVICE_TAG_NAME)).build())); + } } private boolean notClientOrServer(Span span) { diff --git a/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListenerTests.java b/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListenerTests.java index 239754a49..c38cc37c5 100644 --- a/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListenerTests.java +++ b/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListenerTests.java @@ -17,7 +17,6 @@ package org.springframework.cloud.sleuth.zipkin; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import javax.annotation.PostConstruct; @@ -204,8 +203,9 @@ public class ZipkinSpanListenerTests { } @Test - public void appendsServerAddressTagIfClientLogIsPresent() { + public void appendServerAddressTagIfClientLogIsPresentWhenPeerServiceIsPresent() { this.parent.logEvent(Constants.CLIENT_SEND); + this.parent.tag(Span.SPAN_PEER_SERVICE_TAG_NAME, "fooservice"); this.parent.stop(); zipkin.Span result = this.spanReporter.convert(this.parent); @@ -215,6 +215,18 @@ public class ZipkinSpanListenerTests { .isNotEmpty(); } + @Test + public void doesNotAppendServerAddressTagIfClientLogIsPresent() { + this.parent.logEvent(Constants.CLIENT_SEND); + this.parent.stop(); + + zipkin.Span result = this.spanReporter.convert(this.parent); + + assertThat(result.binaryAnnotations) + .filteredOn("key", Constants.SERVER_ADDR) + .isEmpty(); + } + @Test public void converts128BitTraceId() { Span span = Span.builder().traceIdHigh(1L).traceId(2L).spanId(3L).name("foo").build(); From cd6a25ac1a32b44c7360a755e7a686b7247be989 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Mon, 19 Dec 2016 13:26:04 +0100 Subject: [PATCH 165/235] Adding SA only if peer tag is present without this change Zipkin doesn't properly visualize the span on the server side. with this change we're setting SA only if peer tag is set. fixes #481 --- .../zipkin/stream/ConvertToZipkinSpanList.java | 11 ++++++----- .../stream/ConvertToZipkinSpanListTests.java | 18 +++++++++++++++++- .../sleuth/zipkin/ZipkinSpanListener.java | 9 +++++---- .../sleuth/zipkin/ZipkinSpanListenerTests.java | 16 ++++++++++++++-- 4 files changed, 42 insertions(+), 12 deletions(-) diff --git a/spring-cloud-sleuth-zipkin-stream/src/main/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanList.java b/spring-cloud-sleuth-zipkin-stream/src/main/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanList.java index 7bd83c15b..96b1809ad 100644 --- a/spring-cloud-sleuth-zipkin-stream/src/main/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanList.java +++ b/spring-cloud-sleuth-zipkin-stream/src/main/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanList.java @@ -130,11 +130,12 @@ final class ConvertToZipkinSpanList { private static void ensureServerAddr(Span span, Builder zipkinSpan, Endpoint ep) { - String serviceName = span.tags().containsKey(Span.SPAN_PEER_SERVICE_TAG_NAME) - ? span.tags().get(Span.SPAN_PEER_SERVICE_TAG_NAME) : ep.serviceName; - Endpoint endpoint = ep.toBuilder().serviceName(serviceName).build(); - zipkinSpan.addBinaryAnnotation( - BinaryAnnotation.address(Constants.SERVER_ADDR, endpoint)); + if (span.tags().containsKey(Span.SPAN_PEER_SERVICE_TAG_NAME)) { + Endpoint endpoint = ep.toBuilder().serviceName(span.tags().get( + Span.SPAN_PEER_SERVICE_TAG_NAME)).build(); + zipkinSpan.addBinaryAnnotation( + BinaryAnnotation.address(Constants.SERVER_ADDR, endpoint)); + } } private static boolean notClientOrServer(Span span) { diff --git a/spring-cloud-sleuth-zipkin-stream/src/test/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanListTests.java b/spring-cloud-sleuth-zipkin-stream/src/test/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanListTests.java index 26161e080..6c5700e57 100644 --- a/spring-cloud-sleuth-zipkin-stream/src/test/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanListTests.java +++ b/spring-cloud-sleuth-zipkin-stream/src/test/java/org/springframework/cloud/sleuth/zipkin/stream/ConvertToZipkinSpanListTests.java @@ -74,9 +74,10 @@ public class ConvertToZipkinSpanListTests { } @Test - public void appendsServerAddressTagIfClientLogIsPresent() { + public void appendServerAddressTagIfClientLogIsPresentWhenPeerServiceIsPresent() { Span span = span("foo"); span.logEvent(Constants.CLIENT_SEND); + span.tag(Span.SPAN_PEER_SERVICE_TAG_NAME, "myservice"); Spans spans = new Spans(this.host, Collections.singletonList(span)); List result = ConvertToZipkinSpanList.convert(spans); @@ -89,6 +90,21 @@ public class ConvertToZipkinSpanListTests { .contains("myservice"); } + @Test + public void doesNotAppendServerAddressTagIfClientLogIsPresent() { + Span span = span("foo"); + span.logEvent(Constants.CLIENT_SEND); + Spans spans = new Spans(this.host, Collections.singletonList(span)); + + List result = ConvertToZipkinSpanList.convert(spans); + + assertThat(result) + .hasSize(1) + .flatExtracting(input1 -> input1.binaryAnnotations) + .filteredOn("key", Constants.SERVER_ADDR) + .isEmpty(); + } + @Test public void shouldReuseServerAddressTag() { Span span = span("foo"); diff --git a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListener.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListener.java index 46a34f5b5..e10ec4936 100644 --- a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListener.java +++ b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListener.java @@ -127,10 +127,11 @@ public class ZipkinSpanListener implements SpanReporter { } private void ensureServerAddr(Span span, zipkin.Span.Builder zipkinSpan) { - String serviceName = span.tags().containsKey(Span.SPAN_PEER_SERVICE_TAG_NAME) ? - span.tags().get(Span.SPAN_PEER_SERVICE_TAG_NAME) : this.endpointLocator.local().serviceName; - zipkinSpan.addBinaryAnnotation(BinaryAnnotation.address(Constants.SERVER_ADDR, - this.endpointLocator.local().toBuilder().serviceName(serviceName).build())); + if (span.tags().containsKey(Span.SPAN_PEER_SERVICE_TAG_NAME)) { + zipkinSpan.addBinaryAnnotation(BinaryAnnotation.address(Constants.SERVER_ADDR, + this.endpointLocator.local().toBuilder().serviceName( + span.tags().get(Span.SPAN_PEER_SERVICE_TAG_NAME)).build())); + } } private boolean notClientOrServer(Span span) { diff --git a/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListenerTests.java b/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListenerTests.java index 239754a49..c38cc37c5 100644 --- a/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListenerTests.java +++ b/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListenerTests.java @@ -17,7 +17,6 @@ package org.springframework.cloud.sleuth.zipkin; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import javax.annotation.PostConstruct; @@ -204,8 +203,9 @@ public class ZipkinSpanListenerTests { } @Test - public void appendsServerAddressTagIfClientLogIsPresent() { + public void appendServerAddressTagIfClientLogIsPresentWhenPeerServiceIsPresent() { this.parent.logEvent(Constants.CLIENT_SEND); + this.parent.tag(Span.SPAN_PEER_SERVICE_TAG_NAME, "fooservice"); this.parent.stop(); zipkin.Span result = this.spanReporter.convert(this.parent); @@ -215,6 +215,18 @@ public class ZipkinSpanListenerTests { .isNotEmpty(); } + @Test + public void doesNotAppendServerAddressTagIfClientLogIsPresent() { + this.parent.logEvent(Constants.CLIENT_SEND); + this.parent.stop(); + + zipkin.Span result = this.spanReporter.convert(this.parent); + + assertThat(result.binaryAnnotations) + .filteredOn("key", Constants.SERVER_ADDR) + .isEmpty(); + } + @Test public void converts128BitTraceId() { Span span = Span.builder().traceIdHigh(1L).traceId(2L).spanId(3L).name("foo").build(); From dcb3efd5b01293309acac1681a7106a0600c263e Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Mon, 19 Dec 2016 14:45:59 +0100 Subject: [PATCH 166/235] Updated docs with new span approach --- .../images/zipkin-error-trace-screenshot.png | Bin 0 -> 211220 bytes .../asciidoc/images/zipkin-error-traces.png | Bin 0 -> 118843 bytes .../images/zipkin-trace-screenshot.png | Bin 223525 -> 169484 bytes .../main/asciidoc/images/zipkin-traces.png | Bin 108535 -> 152669 bytes docs/src/main/asciidoc/images/zipkin-ui.png | Bin 142506 -> 111015 bytes docs/src/main/asciidoc/intro.adoc | 75 +++++++++++------- 6 files changed, 47 insertions(+), 28 deletions(-) create mode 100644 docs/src/main/asciidoc/images/zipkin-error-trace-screenshot.png create mode 100644 docs/src/main/asciidoc/images/zipkin-error-traces.png diff --git a/docs/src/main/asciidoc/images/zipkin-error-trace-screenshot.png b/docs/src/main/asciidoc/images/zipkin-error-trace-screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..02a7270eb34f99f383b005ea6913ea9f2d2d9441 GIT binary patch literal 211220 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͕Y$7MUW4?O++6bLy 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%0A_}B|`pUb7QeGd9E@=YiLA6`NCA*_%;Y7Ezc3;oT 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%0DulG6m1i{s!ruAXx}z+ylAD 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 SuhmZhKPpO^4>9*G0{;)S$IR~l literal 0 HcmV?d00001 diff --git a/docs/src/main/asciidoc/images/zipkin-trace-screenshot.png b/docs/src/main/asciidoc/images/zipkin-trace-screenshot.png index f0452f0f490caacfa2f21ed214a120e6338d04fa..b230a19a39318da5c43a299ee086650a32bde944 100644 GIT binary patch literal 169484 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 gHa5D?`^f0^D)(c(OizMN8C9Rw(yZRSLjsH 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%0AKSB|mV0)vF_p6XU3b75F9R_yzJ&&44-dqb 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 OAn&oHEqoPadEHyLa3XHq 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 B%n<+p diff --git a/docs/src/main/asciidoc/images/zipkin-ui.png b/docs/src/main/asciidoc/images/zipkin-ui.png index 34b0215b791c9611062eeb72d383954cd29c7868..6bfe38e67a815f7f3ecc8aedb0b67e3e366dc25d 100644 GIT binary patch literal 111015 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 literal 142506 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%0D%J3* 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�wPAy>EkjefwLFh45%apZAReu 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 Date: Mon, 19 Dec 2016 14:48:03 +0100 Subject: [PATCH 167/235] Updated readme with new span approach --- README.adoc | 75 +++++++++++++++++++++++++++++++++-------------------- 1 file changed, 47 insertions(+), 28 deletions(-) diff --git a/README.adoc b/README.adoc index 7fa260c97..21336698d 100644 --- a/README.adoc +++ b/README.adoc @@ -7,6 +7,8 @@ image::https://codecov.io/gh/spring-cloud/spring-cloud-sleuth/branch/1.0.x/graph image::https://badges.gitter.im/spring-cloud/spring-cloud-sleuth.svg[Gitter, link="https://gitter.im/spring-cloud/spring-cloud-sleuth?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge"] == Spring Cloud Sleuth +:branch: 1.0.x + Spring Cloud Sleuth implements a distributed tracing solution for http://cloud.spring.io[Spring Cloud]. === Terminology @@ -42,7 +44,7 @@ the start and stop of a request are: Visualization of what *Span* and *Trace* will look in a system together with the Zipkin annotations: -image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/master/docs/src/main/asciidoc/images/trace-id.png[Trace Info propagation] +image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/{branch}/docs/src/main/asciidoc/images/trace-id.png[Trace Info propagation] Each color of a note signifies a span (7 spans - from *A* to *G*). If you have such information in the note: @@ -56,7 +58,7 @@ That means that the current span has *Trace-Id* set to *X*, *Span-Id* set to *D* This is how the visualization of the parent / child relationship of spans would look like: -image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/master/docs/src/main/asciidoc/images/parents.png[Parent child relationship] +image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/{branch}/docs/src/main/asciidoc/images/parents.png[Parent child relationship] === Purpose @@ -64,49 +66,66 @@ In the following sections the example from the image above will be taken into co ==== Distributed tracing with Zipkin -Altogether there are *10 spans* . If you go to traces in Zipkin you will see this number: +Altogether there are *7 spans* . If you go to traces in Zipkin you will see this number in the second trace: -image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/master/docs/src/main/asciidoc/images/zipkin-traces.png[Traces] +image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/{branch}/docs/src/main/asciidoc/images/zipkin-traces.png[Traces] -However if you pick a particular trace then you will see *7 spans*: +However if you pick a particular trace then you will see *4 spans*: -image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/master/docs/src/main/asciidoc/images/zipkin-ui.png[Traces Info propagation] +image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/{branch}/docs/src/main/asciidoc/images/zipkin-ui.png[Traces Info propagation] NOTE: When picking a particular trace you will see merged spans. That means that if there were 2 spans sent to Zipkin with Server Received and Server Sent / Client Received and Client Sent annotations then they will presented as a single span. -In the image depicting the visualization of what *Span* and *Trace* is you can see 20 -colorful labels. How does it happen that in Zipkin 10 spans are received? +Why is there a difference between the 7 and 4 spans in this case? - - 2 span *A* labels signify span started and closed. Upon closing a single span is sent to Zipkin. - - 4 span *B* labels are in fact are single span with 4 annotations. However this span is composed of - two separate instances. One sent from service 1 and one from service 2. So in fact two span instances will be sent - to Zipkin and merged there. - - 2 span *C* labels signify span started and closed. Upon closing a single span is sent to Zipkin. - - 4 span *B* labels are in fact are single span with 4 annotations. However this span is composed of - two separate instances. One sent from service 2 and one from service 3. So in fact two span instances will be sent - to Zipkin and merged there. - - 2 span *E* labels signify span started and closed. Upon closing a single span is sent to Zipkin. - - 4 span *B* labels are in fact are single span with 4 annotations. However this span is composed of - two separate instances. One sent from service 2 and one from service 4. So in fact two span instances will be sent - to Zipkin and merged there. - - 2 span *G* labels signify span started and closed. Upon closing a single span is sent to Zipkin. + - 2 spans come from `http:/start` span. It has the Server Received (SR) and Server Sent (SS) annotations. + - 2 spans come from the RPC call from `service1` to `service2` to the `http:/foo` endpoint. It has the Client Sent (CS) + and Client Received (CR) annotations on `service1` side. It also has Server Received (SR) and Server Sent (SS) annotations + on the `service2` side. Physically there are 2 spans but they form 1 logical span related to an RPC call. + - 2 spans come from the RPC call from `service2` to `service3` to the `http:/bar` endpoint. It has the Client Sent (CS) + and Client Received (CR) annotations on `service2` side. It also has Server Received (SR) and Server Sent (SS) annotations + on the `service3` side. Physically there are 2 spans but they form 1 logical span related to an RPC call. + - 2 spans come from the RPC call from `service2` to `service4` to the `http:/baz` endpoint. It has the Client Sent (CS) + and Client Received (CR) annotations on `service2` side. It also has Server Received (SR) and Server Sent (SS) annotations + on the `service4` side. Physically there are 2 spans but they form 1 logical span related to an RPC call. -So 1 span from *A*, 2 spans from *B*, 1 span from *C*, 2 spans from *D*, 1 span from *E*, 2 spans from *F* and 1 from *G*. -Altogether *10* spans. +So if we count the physical spans we have *1* from `http:/start`, *2* from `service1` calling `service2`, *2* form `service2` +calling `service3` and *2* from `service2` calling `service4`. Altogether *7* spans. + +Logically we see the information of *Total Spans: 4* because we have *1* span related to the incoming request +to `service1` and *3* spans related to RPC calls. + +==== Visualizing errors + +Zipkin allows you to visualize errors in your trace. When an exception was thrown and wasn't caught then we're +setting proper tags on the span which Zipkin can properly colorize. You could see in the list of traces one + trace that was in red color. That's because there was an exception thrown. + +If you click that trace then you'll see a similar picture + +image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/{branch}/docs/src/main/asciidoc/images/zipkin-error-traces.png[Error Traces] + +Then if you click on one of the spans you'll see the following + +image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/{branch}/docs/src/main/asciidoc/images/zipkin-error-trace-screenshot.png[Error Traces Info propagation] + +As you can see you can easily see the reason for an error and the whole stacktrace related to it. + +==== Live examples .Click Pivotal Web Services icon to see it live! [caption="Click Pivotal Web Services icon to see it live!"] -image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/master/docs/src/main/asciidoc/images/pws.png["Zipkin deployed on Pivotal Web Services", link="http://docssleuth-zipkin-server.cfapps.io/", width=150, height=74] +image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/{branch}/docs/src/main/asciidoc/images/pws.png["Zipkin deployed on Pivotal Web Services", link="http://docssleuth-zipkin-server.cfapps.io/", width=150, height=74] The dependency graph in Zipkin would look like this: -image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/master/docs/src/main/asciidoc/images/dependencies.png[Dependencies] +image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/{branch}/docs/src/main/asciidoc/images/dependencies.png[Dependencies] .Click Pivotal Web Services icon to see it live! [caption="Click Pivotal Web Services icon to see it live!"] -image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/master/docs/src/main/asciidoc/images/pws.png["Zipkin deployed on Pivotal Web Services", link="http://docssleuth-zipkin-server.cfapps.io/dependency", width=150, height=74] +image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/{branch}/docs/src/main/asciidoc/images/pws.png["Zipkin deployed on Pivotal Web Services", link="http://docssleuth-zipkin-server.cfapps.io/dependency", width=150, height=74] ==== Log correlation @@ -126,7 +145,7 @@ If you're using a log aggregating tool like https://www.elastic.co/products/kiba http://www.splunk.com/[Splunk] etc. you can order the events that took place. An example of Kibana would look like this: -image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/master/docs/src/main/asciidoc/images/kibana.png[Log correlation with Kibana] +image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/{branch}/docs/src/main/asciidoc/images/kibana.png[Log correlation with Kibana] If you want to use https://www.elastic.co/guide/en/logstash/current/index.html[Logstash] here is the Grok pattern for Logstash: @@ -159,7 +178,7 @@ we're passing the dependencies in the `groupId:artifactId:version` notation. *Logback setup* -Below you can find an example of a Logback configuration (file named `https://github.com/spring-cloud-samples/sleuth-documentation-apps/blob/master/service1/src/main/resources/logback-spring.xml[logback-spring.xml]`) that: +Below you can find an example of a Logback configuration (file named https://github.com/spring-cloud-samples/sleuth-documentation-apps/blob/master/service1/src/main/resources/logback-spring.xml[logback-spring.xml]) that: - logs information from the application in a JSON format to a `build/${spring.application.name}.json` file - has commented out two additional appenders - console and standard log file From fb75423f37ca323a14008c3e7ef35b5a24d37c4f Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Mon, 19 Dec 2016 14:48:03 +0100 Subject: [PATCH 168/235] Updated readme with new span approach --- README.adoc | 75 +++++++++++++++++++++++++++++++++-------------------- 1 file changed, 47 insertions(+), 28 deletions(-) diff --git a/README.adoc b/README.adoc index 7fa260c97..21336698d 100644 --- a/README.adoc +++ b/README.adoc @@ -7,6 +7,8 @@ image::https://codecov.io/gh/spring-cloud/spring-cloud-sleuth/branch/1.0.x/graph image::https://badges.gitter.im/spring-cloud/spring-cloud-sleuth.svg[Gitter, link="https://gitter.im/spring-cloud/spring-cloud-sleuth?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge"] == Spring Cloud Sleuth +:branch: 1.0.x + Spring Cloud Sleuth implements a distributed tracing solution for http://cloud.spring.io[Spring Cloud]. === Terminology @@ -42,7 +44,7 @@ the start and stop of a request are: Visualization of what *Span* and *Trace* will look in a system together with the Zipkin annotations: -image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/master/docs/src/main/asciidoc/images/trace-id.png[Trace Info propagation] +image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/{branch}/docs/src/main/asciidoc/images/trace-id.png[Trace Info propagation] Each color of a note signifies a span (7 spans - from *A* to *G*). If you have such information in the note: @@ -56,7 +58,7 @@ That means that the current span has *Trace-Id* set to *X*, *Span-Id* set to *D* This is how the visualization of the parent / child relationship of spans would look like: -image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/master/docs/src/main/asciidoc/images/parents.png[Parent child relationship] +image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/{branch}/docs/src/main/asciidoc/images/parents.png[Parent child relationship] === Purpose @@ -64,49 +66,66 @@ In the following sections the example from the image above will be taken into co ==== Distributed tracing with Zipkin -Altogether there are *10 spans* . If you go to traces in Zipkin you will see this number: +Altogether there are *7 spans* . If you go to traces in Zipkin you will see this number in the second trace: -image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/master/docs/src/main/asciidoc/images/zipkin-traces.png[Traces] +image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/{branch}/docs/src/main/asciidoc/images/zipkin-traces.png[Traces] -However if you pick a particular trace then you will see *7 spans*: +However if you pick a particular trace then you will see *4 spans*: -image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/master/docs/src/main/asciidoc/images/zipkin-ui.png[Traces Info propagation] +image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/{branch}/docs/src/main/asciidoc/images/zipkin-ui.png[Traces Info propagation] NOTE: When picking a particular trace you will see merged spans. That means that if there were 2 spans sent to Zipkin with Server Received and Server Sent / Client Received and Client Sent annotations then they will presented as a single span. -In the image depicting the visualization of what *Span* and *Trace* is you can see 20 -colorful labels. How does it happen that in Zipkin 10 spans are received? +Why is there a difference between the 7 and 4 spans in this case? - - 2 span *A* labels signify span started and closed. Upon closing a single span is sent to Zipkin. - - 4 span *B* labels are in fact are single span with 4 annotations. However this span is composed of - two separate instances. One sent from service 1 and one from service 2. So in fact two span instances will be sent - to Zipkin and merged there. - - 2 span *C* labels signify span started and closed. Upon closing a single span is sent to Zipkin. - - 4 span *B* labels are in fact are single span with 4 annotations. However this span is composed of - two separate instances. One sent from service 2 and one from service 3. So in fact two span instances will be sent - to Zipkin and merged there. - - 2 span *E* labels signify span started and closed. Upon closing a single span is sent to Zipkin. - - 4 span *B* labels are in fact are single span with 4 annotations. However this span is composed of - two separate instances. One sent from service 2 and one from service 4. So in fact two span instances will be sent - to Zipkin and merged there. - - 2 span *G* labels signify span started and closed. Upon closing a single span is sent to Zipkin. + - 2 spans come from `http:/start` span. It has the Server Received (SR) and Server Sent (SS) annotations. + - 2 spans come from the RPC call from `service1` to `service2` to the `http:/foo` endpoint. It has the Client Sent (CS) + and Client Received (CR) annotations on `service1` side. It also has Server Received (SR) and Server Sent (SS) annotations + on the `service2` side. Physically there are 2 spans but they form 1 logical span related to an RPC call. + - 2 spans come from the RPC call from `service2` to `service3` to the `http:/bar` endpoint. It has the Client Sent (CS) + and Client Received (CR) annotations on `service2` side. It also has Server Received (SR) and Server Sent (SS) annotations + on the `service3` side. Physically there are 2 spans but they form 1 logical span related to an RPC call. + - 2 spans come from the RPC call from `service2` to `service4` to the `http:/baz` endpoint. It has the Client Sent (CS) + and Client Received (CR) annotations on `service2` side. It also has Server Received (SR) and Server Sent (SS) annotations + on the `service4` side. Physically there are 2 spans but they form 1 logical span related to an RPC call. -So 1 span from *A*, 2 spans from *B*, 1 span from *C*, 2 spans from *D*, 1 span from *E*, 2 spans from *F* and 1 from *G*. -Altogether *10* spans. +So if we count the physical spans we have *1* from `http:/start`, *2* from `service1` calling `service2`, *2* form `service2` +calling `service3` and *2* from `service2` calling `service4`. Altogether *7* spans. + +Logically we see the information of *Total Spans: 4* because we have *1* span related to the incoming request +to `service1` and *3* spans related to RPC calls. + +==== Visualizing errors + +Zipkin allows you to visualize errors in your trace. When an exception was thrown and wasn't caught then we're +setting proper tags on the span which Zipkin can properly colorize. You could see in the list of traces one + trace that was in red color. That's because there was an exception thrown. + +If you click that trace then you'll see a similar picture + +image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/{branch}/docs/src/main/asciidoc/images/zipkin-error-traces.png[Error Traces] + +Then if you click on one of the spans you'll see the following + +image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/{branch}/docs/src/main/asciidoc/images/zipkin-error-trace-screenshot.png[Error Traces Info propagation] + +As you can see you can easily see the reason for an error and the whole stacktrace related to it. + +==== Live examples .Click Pivotal Web Services icon to see it live! [caption="Click Pivotal Web Services icon to see it live!"] -image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/master/docs/src/main/asciidoc/images/pws.png["Zipkin deployed on Pivotal Web Services", link="http://docssleuth-zipkin-server.cfapps.io/", width=150, height=74] +image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/{branch}/docs/src/main/asciidoc/images/pws.png["Zipkin deployed on Pivotal Web Services", link="http://docssleuth-zipkin-server.cfapps.io/", width=150, height=74] The dependency graph in Zipkin would look like this: -image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/master/docs/src/main/asciidoc/images/dependencies.png[Dependencies] +image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/{branch}/docs/src/main/asciidoc/images/dependencies.png[Dependencies] .Click Pivotal Web Services icon to see it live! [caption="Click Pivotal Web Services icon to see it live!"] -image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/master/docs/src/main/asciidoc/images/pws.png["Zipkin deployed on Pivotal Web Services", link="http://docssleuth-zipkin-server.cfapps.io/dependency", width=150, height=74] +image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/{branch}/docs/src/main/asciidoc/images/pws.png["Zipkin deployed on Pivotal Web Services", link="http://docssleuth-zipkin-server.cfapps.io/dependency", width=150, height=74] ==== Log correlation @@ -126,7 +145,7 @@ If you're using a log aggregating tool like https://www.elastic.co/products/kiba http://www.splunk.com/[Splunk] etc. you can order the events that took place. An example of Kibana would look like this: -image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/master/docs/src/main/asciidoc/images/kibana.png[Log correlation with Kibana] +image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/{branch}/docs/src/main/asciidoc/images/kibana.png[Log correlation with Kibana] If you want to use https://www.elastic.co/guide/en/logstash/current/index.html[Logstash] here is the Grok pattern for Logstash: @@ -159,7 +178,7 @@ we're passing the dependencies in the `groupId:artifactId:version` notation. *Logback setup* -Below you can find an example of a Logback configuration (file named `https://github.com/spring-cloud-samples/sleuth-documentation-apps/blob/master/service1/src/main/resources/logback-spring.xml[logback-spring.xml]`) that: +Below you can find an example of a Logback configuration (file named https://github.com/spring-cloud-samples/sleuth-documentation-apps/blob/master/service1/src/main/resources/logback-spring.xml[logback-spring.xml]) that: - logs information from the application in a JSON format to a `build/${spring.application.name}.json` file - has commented out two additional appenders - console and standard log file From 9e596705be13a6c30ab88473f2206f94bb86f7b9 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Mon, 19 Dec 2016 14:45:59 +0100 Subject: [PATCH 169/235] Updated docs with new span approach --- README.adoc | 2 +- .../images/zipkin-error-trace-screenshot.png | Bin 0 -> 211220 bytes .../asciidoc/images/zipkin-error-traces.png | Bin 0 -> 118843 bytes .../images/zipkin-trace-screenshot.png | Bin 223525 -> 169484 bytes .../main/asciidoc/images/zipkin-traces.png | Bin 108535 -> 152669 bytes docs/src/main/asciidoc/images/zipkin-ui.png | Bin 142506 -> 111015 bytes docs/src/main/asciidoc/intro.adoc | 75 +++++++++++------- 7 files changed, 48 insertions(+), 29 deletions(-) create mode 100644 docs/src/main/asciidoc/images/zipkin-error-trace-screenshot.png create mode 100644 docs/src/main/asciidoc/images/zipkin-error-traces.png diff --git a/README.adoc b/README.adoc index 21336698d..5a6fb78bc 100644 --- a/README.adoc +++ b/README.adoc @@ -7,7 +7,7 @@ image::https://codecov.io/gh/spring-cloud/spring-cloud-sleuth/branch/1.0.x/graph image::https://badges.gitter.im/spring-cloud/spring-cloud-sleuth.svg[Gitter, link="https://gitter.im/spring-cloud/spring-cloud-sleuth?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge"] == Spring Cloud Sleuth -:branch: 1.0.x +:branch: 1.1.x Spring Cloud Sleuth implements a distributed tracing solution for http://cloud.spring.io[Spring Cloud]. diff --git a/docs/src/main/asciidoc/images/zipkin-error-trace-screenshot.png b/docs/src/main/asciidoc/images/zipkin-error-trace-screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..02a7270eb34f99f383b005ea6913ea9f2d2d9441 GIT binary patch literal 211220 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͕Y$7MUW4?O++6bLy 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%0A_}B|`pUb7QeGd9E@=YiLA6`NCA*_%;Y7Ezc3;oT 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%0DulG6m1i{s!ruAXx}z+ylAD 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 SuhmZhKPpO^4>9*G0{;)S$IR~l literal 0 HcmV?d00001 diff --git a/docs/src/main/asciidoc/images/zipkin-trace-screenshot.png b/docs/src/main/asciidoc/images/zipkin-trace-screenshot.png index f0452f0f490caacfa2f21ed214a120e6338d04fa..b230a19a39318da5c43a299ee086650a32bde944 100644 GIT binary patch literal 169484 zcmeFZcT`hvvooNR=)fK|$#~^ax0A(o3i!pwdL7caV|-p(BBSNUx!V zUPMYDlu#2|;0>Ph{?50~>+_udzjgk}O0stL-uFGTXRf*Cntaq$SGq>YM0xJqxofXp zKG!~X?&9*fa~CWwUnG5_=;NAr?%cg|ubw~C^)XwU4stZX1mF%dAi<8qPu@zv(xb4( z0_q|VdRFdpS9o6n$#m-_E$JR9$mJpma*?T=1m)e5qRUAhWMB9!zSNB_gmE91yxh}g z@uV9tj%(xk=1NEXI`Z6&o7Xw-U5Tb^x!1=@Ck(#TSDgAPWm6c&Ahghv?!MH+@Ak60 zFYWZ32ucbg4&O;vgdhr1W9%bg?pH!ov1Lo}}UJAD-5c=m6Ple>3c zPOe@4-=9qGH5J9>oe{l8AsKe11>ku&IqtO1}qXKgEBTW<<};2jz85CwyGtKhZpFOABJhRRjH{Mlt%+ z-+BY7tFaQw0htRs##%Ys2CChY+zVb#zYa_VXhA_*0(wRAWi6-rGG;mDPOIrY;C5q- z@c!+;hb+hFA1;AAcX)YtY};d44nt-4L*Ho}(wuJ6d{WFijF&yQMeM^Jt2bqFFUXN2 zX*^AlF3F31DX$QG)88e+W96-JdZ5M3~13cGCfqJn{ zj*zwtC`3ANM(`yet-^jv@8|WmmO96m119?4*c2n=3m)SxbM7t)N$dz1XBm6yR4<4> z`AHeLQy&Pcdc6t|s12||W1(GvDJtgIJTm|bI$;s!WxxQ%_LF%-^mnln$JPzhiZKI< z`?g!4>;(xF7J5wt!YOPJ87AVp=rZi-JGhlfjsgTbgSx(Xc+GK0wYMNw3c!>%QG*Av0}|ERCDJLuC(j z8i=yHic7gc5epI(#769n7qv+OH8nVm`VZ28j9TqEd%(i2nIijGym6l=zUynuI`3|p z55|s(-~u70%|m?rJ=4dI1f)kr?gb_Of{)Ep{T#=JTDaSTWu2~kT9s%Q-Y;xxw+(Qn z!|F?4qRxRlM5Ww_UhoNeDlHb);iFC=Qr;xXoZL8)|0CG_vvr0-Mz}&K#C0*#aBW%( zomJX!zC7UfTI=T+Jq&B<@I2k`E`h)2ugy8i^AZI--IIEJ5enP z2-%Z)*^z3eF#UTA4PNAkUXwd_(}MX52IBXyr-7~pAFmfZR;tp7nG@yB<`&$ag!$5P z)RyJ3i6MqJ%f+8=+3v63Ks%m3rRcaL#I-hLOkjRqaER`gNfjveCkxtFOo}ns@H7V` zx=i`!y;qQy7!=RMJsH9gnbJ1&&|a9>+k{7YpzqKRTi-SuD(w9SYbnoGA|gPo_}A1P zLZf!ijEsS1GCi<7%tL+7WP9h}9nymDYjDZ!VvAUjpOnL_1NAHJcU$R`=2o2~25m~t zo782qn2C(w2DUYVvODbx6TP%?dxi&DoDH5c))bblF~)0Kt$cC4vN8p~a{R6ldprux zBU1x<;pMSv!Sx59wuBtV-LGfFp++AS8lnB4P4voSNt7_?FWT!*_Jba;`kQXwC6-ri z#yo1oJt-^*oZgUHe!KGk3wPGEmU`?P5IvDss;ID3WAFAsQEdvFC^;VsP?nwNYrqx;nN)&0|#8U#F(@jnG0xclub*hQy>X8Wi#cvkmK!d}D1TqC6eP21A;@6XSC zx2GW(^!59>Du-YxKL(e)J>UgVTTnJ=!gKCQXz+T=B{8;*WGt&y1_~$k^5^uFb$j{g z?JW3Bv@@kj0iY#ztH~t=X2=7~$*`GwnPj|#XMOjewaCfG7?K^U^^oHFWE-}_9e4OU zcgBpyPTxKw@>kjp;~QKd6TEvqVf;)n&;M>=r5E=AFKZk|`D#r{p68pop~R2sRap93 z+c~m9y87*G60tq_589?jA1=m9tnm}xs`H|Z2oggZ)N059m__5^EbMiU@020SWLmyR zrlm^HTDz=$jq8Sdq&iR(AeIV=y1Y!;U~0U_+ylJAxrxy69K-6mU-VGfp=7>nz~xmp zo33Y>NTj1`*f+T(lOB&79~!Z5WFZT4uIy@HGg%w?j0IjbQgj!&#x-ncbLgUVjb;0> z?Ufl5rmJDPHh2410`jcYrZzMWT?~|^&0Y=PzVLS_D{~APlQJ1scR#cY}?UMi+peZhHYZ_#JGW|@(5Doo?-4^i$ zQ9Db2aXw*U(bq^s!*^T%TPtfDkMm-z9_NjASWD#-p}bn}AXvvBfBw1#WToR2hDs?< z-Tw5c-d%*Xt>-CUu2); z;CT{u;`qbGIr~K+Jmq|*M6@bR*SrCRt?lufV!s{`dVRHjEr03v68B@9&(j-WJZ>RWw%<`@~gtU!iY2Yfht&S-lO$F&zF|IC4Op? zA~Rh(`3Z4d2zhXNTdd?OXq45ru~KQ{lZAeE&N8wzkeQm>UpBXO3=SwX$qeLJ+xo79 z)>j|3Y_V`4*sSMGa_d%$hIM3!2YBO6r;plSyvouK=zfL%9+?6RTq*XUP9Lccu&yOX z^q@d=>C|0yAL;7UL5mc8s$kDXoriaa&s}2jTOzmmH zL65V4yl}|2vx6FAw|?Ua935QQ3*H_hJ#5xz!RuLxlSB$%=;}hi&kb|V;|+n>d}-&Wj2FDy3d zj<$BDdk+LbfczOxm-q9nmy$+Uqo-`C(?@Eq( zAEV#55BED?8{@ewd5pd5_v(^&e&cl%!aF&BCZ^(W5{_D#6<@DSTHFWUX}?MLjuG<_ zcr+`MS2l8s$!#K(Tg2=*5D)PUN}Q=05Z@!1qC++GzH!~K#I^65j0PWzL-KNm6t*)e z^m+EwrxIm-nIeMu%4Pz>4^gBr#9TR1GCet*GvgxMoxySLvIbBORlAc2;lsRG)poam z_lct4G~9}!ps+XQu}nvbLM^jspr>yl6`gQt6@UVd9!uL}S!8b70HvP4{a#cV@I~d& zsFPi<(U>fa@h0+k2RDXLDs& zWCxf&Z0G2usb{!7(Bl=)x2N&mfw^1KF-I34Ju0s=v#J zn4J^J`>n?54ZEI=%IN@?5jP8yzZTe{roTdIWcoD%x8vV&t$1xrfl&b)?rEZhUno4{ zR_=?8{S2G!p z%?UvY=%(ayt<%O~GV$=;_Yd^%+K6cq1H*A;N;)MD>T5g0E8ZvM4bKDJ0SJ#k!Q5@1 z>lKwe0V@uOvA`$Y(rG%`CzX4Vn1Jb2)*E(#Evon?RyWp!)QDluvLnyseQ?XDmIrI za_ZJiV{j4gyGuThUxMYB`Riv2u`!8n^k<5oeostce)PYY-N6bP*2YkC4V%Sl#iep* zr!m@UrGY0z41MY0GxbM#rMZp5-<-a5>*Z$vVP7N-7ma&Dt;9tkzwT#+_pEceU)=9Y zDC>o*70v&!&rTfwf`-PeyiWWyy)S62Y3vl4uk3+#u5z*lx4TPZo!aI-f=6QjQ=>V* zMz2wr_4p@d$V?p@y=1urqe;3^3%+%&l_+`tKtyyJ$^}{3o@I;q8;P-BeEma&2LU%b ziV8Z}fsGzT1no!o9dEUZq4R{vOcx3ucO*tcv|Nrpyu%ZF5`SVPUKc-l?6_$i-Rorl z5rdJL9(VVdgfy82EYfT&PbPxCSe@pzyuYgEHDbhN@*C~ctn&=rQjsHn9?=oyf6a*^ z{DJ-zR7$pD45;|D?0sNZZF_TFX}CJ8RzJOB3e=F2m0KmU+d_taNXCE)U^Kjqs8zFx ziStTF2N5f9%d-rw{*ZSi$@iXg)24HYR#eIKF!qaA;=8{y#E_Y4b#>tvPk&a4R?(6| zg?h>jc{*{_`)&p{#wN96VR6D+Zv0?I46xn(dddc%xmIh)HZZyu4}tXbFLxU4rExJ< zpkyn$P@dU_sACJ=c|)%IrbnshqptD2n)9Yc9haZ+^6Ih#Fw4YKs#!7xcdJD?u8=`1$TPHuJO5Y3+Um$KyYZ&!W`y|l zzsBp>EV+j_2Q-2XhhPl)dV2Y@r^MOWVJiMtav0s5g$aiKZcJu8;bR8ZXgNJ@7Tp`S zBXPI<5_@3uG*&OcP6Lq^fX04zuG?Vo#1bPbAd`3Q@)y+A%nm@;i79k8YDC1e?~}bb zdA#TL8dL{uW09I(q8U^-Y)(ag437d6J}cA-6#n{QLT8{UhuLIzv%s@vtV=>e(IDLe zup95m?xocF{`{Pkp5TFU%$IN4hgQIV@CV)-*rwmPKXEg2NsY4S znB)_UP4tvF%n~J2+~xKde`1PsU)0+JqOMAqa)i?f;%-#m)SnDoo$>w%9Uq0ARI@>R z-3q$^iFBab15+(fEW*)Y#UB2X>t43|Iv!F0k)P`~6*2Qw=>Ut_wA zGx^qQ62M$M8ejWd@{nwLN-k18%?R2G?NGE1-F~yCK&!KpZF&wu z`Y~SB?ny=-hq>bWD*B&`dxZgVHFbtfr~dk8M~{rvK?=^7GLg5)U3P^dNO9TFF>7qKjltU1&gFIZQWks>j= zx+0&IA0&qn7buEj08H8!1X@t@2F*841?cC9#aJDKM29dsX~pcY1D2)AEVJ#{>uUs# zos=&;r|Pt2qEBi69v9-}8oI=fs+_G~0#buFwB-u+i!4n;JQzpd^BE@F}vbzB78+M1Phs3J({Zqd?+gIrRqM~ zzUQoJC(^9hsuL)(0nD&@94NZY%wuw;sP$&Gn8R=Vh!sCdn&k&MtRf!P`f9%ZWC2xk zjJ{I|+G`<8CzGXz@Nq8s`RjF(!h4DpJAsMg+04*WYjnNXJim}C+tYfW&YY8O6N5jK zVAXdI#}LOfjV1S_t3E2yX4S!c_!*5}5_-J}-rN34u3^=1dk5)Y!HD{}fMeEjX}inw z((fsJ;G-|tzxdGEaQkEhu;96f5X<*@aQpmU4X*^q9(&8@%brFznD8*03P`p5AwOdV zTe+(4v6|eds3AM2Z+L%}s^s}_MPrk;*4IXM(+yGUK47-Zk-Vqp%)mn8j-B4tclkWa1fpO5H7o0$K}$>Z6JUi zXqf4P73-?jX^Rz0B|waB(_dn`CcR0^(JbUJxM!d_?|T2i(WBwjZkEexUiy=Gfv=*2 zF&ox4RTA=#798*ASZ<<=J{nIO%EPM!G9be1bZ*??Q3<=uUFFDgi4PEd1*b`K*;^3=&+^|+?71hz5$O?1@L|{0<&lZn#3Y~keLoL_$V{DP zCUL|0mx<%{Bh}SvS)@&{q6#&=MDDi|hS9C?F663a*rsuM=A9J!*Cy1y!IcF{%o#3j zzZ+!opLof=eRU@4EV5?kv_=srP3l&67ehTEGk|zv`2RR`J|O?HS!)d|CYkU5N5!k-9iL_zT1_f$5bx@zjlOqcfPx5GR} z3N1+HkPQjXCEQ(R2&W~Ls9}w=;GD?$YUR@XLaNLD%<8{AW+h4%iuyWjkX|wVz;}~{ z+C5t-&q9=%b=V-CIxm)oxkLbZz`hy_M>F0%vhGZ4&kRc@4|~bw+oBgl+y>Jyt!d+_ z0xlFIpK`{UU+^|G7IhFm{3HayHZvHbHEcHQl5FGB__HVDkg0ACr)YzA_g~k0K&T6J zQqJC}DIo9UU7nViv;l8tSxw(uVPC`U2{0xVTdv;{2CrFpIU5z$n$6e8qy< zsDMr?d+~=yq*QzG;`Y}V@ygpmG;!l?sQjiH1qjG-uP7=Yb{O_?lBIlDd66ob8PC~O zG|YYR^{wMi;>g6GOcK7&pXo#*_Az(PN;#m3ag)WnvKHnYL%8Y7vUz%Ue*s`ynrWoC zQp3@WE1uw-p<`x0;bZ&<`;n32#T|-Nt4twpwEecvyt#oGN1L$3LCjLn^0B;oL*_A= z=}FgwzjoUGkre8gVykPSG#?8gTL>ps8>jN27o0yc!6gP@Po4*lR0Jl{(>{GoWQ$5C zGo2YpOSx|?#giGp*3Ig1hY7w(P#uI8dB0QmWk-XJJ|kv_tCbu|JWHFQ73=)q+8Imx z&vkKO%{DjwT0+=fp}p0|4(9>9B!uz%Kg%Mn*{HnJEDz?BNn6f%MO+M88(ct9W~C8& zO5e2uCPNv$OCTfF-uiQFDY3hpPo=+%)i@*Pl-5Y;qh{SM(HU@CE2yf9E)DZlVNolS zzciynEGY8(+Jm$?KxL!OUXERCy?i_OVFTT5dRj?mfp&&Ht9r)YbLsx5%xc}cCGdBA z1;42F5~b@svH=zFgN(*>{{EhoOsh&=)vyOpr3eKgiH$Z|Uaw{$+@M+h4Tzr?_jy~a zrgX6f?Z7G`s%Pih*(Xxi`I&cYYA)ea#d!rF&?$-f(dBi|`6;`Z9QcbVfs_q`9)W=g+I>LeE%xNtByhX*;FP0MKc3Gd~?jR-z zkZ@+GM#WPSwC>NhQZm-e;Xn4b>`%%)~ya7=PUEt z*tw>>D!ssY)%E{s0bq;eHoMw&vRg0Q1;`?;cnZBN9Dp0x|DcJfy{CkDfah*z=tQE7 zS_h9O-b+x^0-O?4UxJCOec-~q`k#{khDolykaW20w)-|W2jMO zkI6EKF}G>hC{LG5tV@7s&5q+2#}jd#*gFx@!-IMbt97V5S7ev?s+MEj_;RL41>Y&nH$}E@=z5rmY%D*syo@GLdG;m{7Bs zJ$26D{v7*P5yocRjhEpbVej2LBbCEFPIombVaJ#%q z3S~e@6q?Q*1Cja>l`2bPDxJaIm!VMn2Oc1AD&&6o^vW30ciPlM{S;BJEux{vesY={ z$~yEa9U`526u!tRS-exw6_4l>xF%YhiQ10mH+YLMqo75Q$S1lKh0BEj$W-J4zz|G) zAhzgG8hDVgXQacBdC(fY3kv{ zRNfpqUavC}g)b(!t%LE9pS!c48eDvf0UTIi=aPWaPvSF=vm-B=HM?BW+ezLz6xq;# zjL^S)*_--(I!AZTlqa`VlICHB;ek3@DuJ)p-H5?gL8d*JkCltsVc|`+zR#8jz8>KS z$`hehEvlQzDEeKA%UfJ$*6DxzL;Z6Lt(2}yahuK+d^Y#n47y$H)K^S)u#iin-R>Kt zm4+xTCHq}FaQF2G6oN!8l4!n7{8=pk9?XhSI%Oe6R2*!9^%~FFp^w` z%Ne>W_2zvI_l>v87rv7i6nRWqM_Qo9 zSmIa9y=<^iSukD4{lB+>OFxp|p;=zg6kuQZ(=nF(rmteY$1IcUY7DA>&i?X&93!bR zTs?9A0&J=QE%sg(zp6%^t~n~ABAwZD5RV0Lm!@DQ1kjk!Myrbml#9*X;i&goC;#q~&yy61X6a zgW+xv5ZZPk`1v4Rod!IYF0z2S97ctA4?I1RR2rUqVZB82$*9BUHx&3$wJ8jqH{Fys?>jQz2_I&~4l;w$0l6eqmXBM%*FNi!cY2r8#+um)4 zJkFKXBkJ^Qjh{!LyKQBLxPvm3=!|sz?>3c0j!5L7^GuimCEtL*yHmU7e~oBn>aFFA zhlrCSTJE2bubz!k8S|7yDKqMUzXZdsW1`Z;PPAv{9kiqlpam=U$Y) zw{Tx1GKj!c-2$Nf(kZK(4*?`1MW-7reVKpJhoZseOHIvJGSl6W&;Hfel@?zwG04$s zpf8jwKmP?UPdyZOTop=+67NPHT>5L;3zh>@DBtWCuQA~EfKc<^y4&}7b8Q6%oUyq( zo`3T3Gj)OFsgo@Y0=@HwrvB!Ury9j#Z*{Fv4sfaD$Zu6xP?{6vmDYi1@7|4!O|Mll z$RUiF*ONEy^Gg`;4o{dj@^z!f2;JPj_x;IC@7t@R{SU|`3a{UL%JOxzfp6_r2SY!d zpm{`M;VO_He+xmIPThIbsdO)!d(U0-v(p^eba|p&wvpe7Mi>MfPSa6r6H%w)0 zB;lrPmt4{t#zgNfYSzw8<*I8Bj2aiteI!GFukfEU+V$xRgeF=iLz*AIH9BzaHYq{kRIbP_uMv=DKLFqD>CGWn>X z>kil4S+t3yS{~U+N2L1gSr^uNE=;t1!bjR2n;`uPs1Q#wkW%@U8dgW$;b;N+5>K#I zcBxI_hN@=h7V^OD`0E>~2;yb;;?}{79$Ns{=S@MQ+4m!qM@&XDs}?=XYa;XhR{fIX zdbchp)AzG4SwHvO!G-T)SLY3lSy=N>5`EnsPR=I5}ocGb3W z;mNNv4!zrD3CY$o%p?6jY-563@|W;Qo`-KUxT+ToZ?JC)W!=ZmsAZhW0@3>fp~;kj zm~L;E=u(7z?3)Yl89YgHRbWg{!no;N#*UT+aQ1=}2g7t00xi$leh>frs<4-XW&*(( zizcZ)tdFz7LTI1pJwFc@-{4;{Dv_RY$=46_mHM+1^#dr8RJOqNDJATj({L!6kPv=n@Y71hK( zYUJU^@l;=$FWPETx7=b6n+pzk6Lo@0&fkA?N;Rl)<*7QO0x4ipZ$w; zZyrK&5Ae$EPcP`{K1%gl@Dy0RTAbw4)iMMtd*@HZ(w#b)^W4;bTAqBH|3s$&Dep{R zs2p0?Hf+eYZ&o#ymYc$bY|O-RR@06Oy(yC42-3-yuA5p#%xE7nmL;=J(KxueYLz9l zOuw9(%y6!mD*26D*I!LV+3+wLnSH#|K5ARrRgG~bOIqFcFMbpqaYOn8e6BUwg1Z~) zu>VmSGV!AZ6E_);@Wn82aRZTD3JLa7eowqNP49ib5s7UgqHTOUSrVbYU2E`b5bYNY z_>@j}*zHeKG^a|?3kpU1D;!a@pVvC;}M~tg1ez|%9w2&2= zpZq{Jdbuf71azj#oG{d@>O_HaI*_G5KZ0rxbp6{BGpJt6PxghSu^zl+Dt4puTG9h~P+NIqg?my;vBoTh&CZ%)m|O**DyhUpPr>(*rwyWH`k z-fqH-qA+yZ`LxLN%Fc_w3bp>MAf%U;bTg`^Z70PyFV8LyfKOLTyb#@0^_4t&BG|nK z>)Jpl?RSH}LYw~xQym;~BvI{$S+>7!=RZr-lqVxc+_Vs+{d=1DN49@J+R*JQTcCe) z#WV6snNv;xO|F4&Jw!V8==g3TD?gz&Hb02@bOSvl~S-kRxPR+kRjQ{**fn>RE zjuzZs>#P6b)HriUSG^*@NcnFZhqQSi{^S=t4&Lue{|{TjPBIbq{YMIa#~A;t?6XF2 zFkja7Mx}qhCE4#uS7mQ`!}&k#@b;DK{foDL6aI%S`Kn3s{P)RM!~VICe|GC1o03i; zCqpv7zx*-l|F9)DDM|XG>+v~%Pl5le?0=o^|FPcxI^F-*-~M=`oJ@$?RT9G#(U4cC zqV4NX-&Wp52sAt?G!e(Vd5+o3sA<+tsdW6hx?)3;LUMJVq!<{)i!D`}BtBL7Eky^) z8fYq<>dO*%kXnCYI<%#Yu0VCBtui0b1{nu6-bD9)^rnrRHCJ#Ol_^Z+aGQeC^mb}4 zAnOoEc^@oPxmHq%1 z6~(zsd-JyW-mrDJyA6N12TZ~^Av0%-RLvhW1Q`8cL+S3TxpEH~oJpv(y?ucZ*8C!} zf@G#|Ai!(uBTq)Q$h~X0b`|An9V8?34mep&H!BF{CMJ$%PJ1D{M%@Ate+24ZK5sOC zS4+S3ui{YllMpq^1i@zF3+dhz3 z&<(ov{b~M)z0m?)wc|*PAU$o7({(xAItFYf%OB)fAp=Hh|?J#D@7sY37{v3c4LqC%~4`d<|V ze==O-kAe#xZ5diHYCs`qM22sCfq-Wn0+ITT@qxm7X_$K6ga3 z8<~&3qAznou|Bof#mXZ=!B_Tyo?gFwUlq-&Cw(@8^W{GKiP);fac~RnmQo3EmLgn& zJNyBuKihN^32k=a2ISW%OHR{;h2^RUgyF)fZX2un*VpH2lQ5@tn9fB@_?k}MBWLgK z2JRe@mK|Fk`{yEI)8VvQBw5ezqsT;GJ%o1`(SU4D^XEg@CETfnXZ?D@M6E;NR18Cv zS670_(fc`UP~?lB2-0kjQKFZhgRWuRcS(mnap%G^uf}=H#NvS5{vT*B*_-Y`xj$CT z(%hex7cK4|jEuG2jgGd<$R z9q=j~WF?Q(P=%a4KU5+9;n>Bi#D#_3SM&lf0brBC?Jpjit2Ul*0dr)*u0iUU#_@bD zV~5t=Yg>?cd3Y*e(()UcEo!-*Z?B3wnUc1*W-xCg#Eb%4>@Kzu4aL7jO@x?5Pg8Q? zt&_;luNdXc9;NT1PCqkqgI+S8Q3ECjJG)%=H}S)IpogZlm30a_B>69WhV4?9ZxvD| zb8%&-s0wGrvg4Z`D#4c9*%+v2nj}OJilSMTMZ=TSnRUa&4WfD3OL)X;cFg7e>A#8piW{joEET%BEoY^fhTL(viqa;+mU-~bm1(c)_Vy~ zVtSybrr;%Uq1+osC$VLvia?xJo=o zJ=5g;CJ}+#6e7`aGp`d6hw{iixKMgzmS=f|k!jBE!I8p!7Fg+ePC>AIO$PZjg*^UY z$gD>o3Y@K|?ni~FDI*aGLBfn*7f(Jkx~n3qKR@xZG1+V137YY&1iDn+Ri!Z1@AhH0 z%x`l#@(jrFOA7QqGPBn{@pO1>Wl~$RgckU84{?)K)n}Hhts!N&8!fY~ zrJ?Mc-4)~rcrRq@mAi(ti!6A489n_M zVpox$uRg zD{d&2hIsSEPLk6kUF@A(Y!>q^ahxI3Ob)6h#%*< z(k>R7T1indqRH_`0L_!24|R67eD=(^zbJqu6;Y{=WXKrZ_b^yoTr@H>O9Y*|LtYhl zgSn$^mp}XRE5H>CLbu1bDg>QZ2j3Y{R~q2PCy4vI&3E7Qb<-dAnc5GmL{@{A97cpT ztRkv33*%OrhSRmlrm0^UauH6F;6a9`C}3lJYW0vr74Alg)FW?@A*iUv?-QcHTAG-= z+qXCn^{a%?Yj`j!F9@RQYB8F^0On7ZM}GEZTRS@B%y)fF-Ki`RPPF($ zewRhOvUbLS%_&$W=q0AVbVT!sJTyMvHu)eG(jxdS^bY}9am|v3HQ!OskP%Z8VIWDK z>H|g7vW`KA9tO?6_X~gu;`QI3;LVZ#m5=n;8R~?<&Fyun#W#mQ;PLZG3sYG2J*=Zn z23O51ZFA@Us|9GK9pczsXUGIRq1*TQqd`$!r?aey&<)~BMpU*nT=8tjBynfg$%5^7 zj6N$NNBO#3?18sas3CJEh}I%iUdDA^5zlOeTc==gJ@&$}cYZO|wo|mabThew{k;ts zwWEj168$6%t|Og4b!4YARzjjkv(6656NdKuVUqDEA2ODE)}~M;biwEPyCJUZ+(?Vm zHAz^>@JaNSTJ50*hDq$MiT(N=&G{3q1m4zf8dCjp7`62`EE6VurnUns-7B9L2^Ld)o!`YuvehWV|>o)Y=G&ZDa9lByP>8i>5`u{k3=dq zsS?;lbA)zNke3$4D6Sg1G$0RE11S{vyVEGGez!X9Fa=1$w#)bX7a?0Lx@b!)Z4X7> z1`m~INpL`pOi&wm&0JsbmwUP6+8v!5)Qn$0lUMTjpZlFKP_XY=YeMa~dg=1A^`XDL z5QSPVJZv~lYs(hS=V9j~@hx@zY?duWzFCo^B-c+sKH&1xkpU`6Y;Nyit3zWc9N8gT zNkGlm{NG)Fx(^aAr#1)Qk%v8tt0Q;d!d+IW#{OAV-Wxpd8PvA2GzO}&7H4y<%>;{N z{c?KcXg#XvR5Mgjr^yn*#3PjL4kq;0XzSTS*%+B*0np5q-R~o!NjLU%#W?B0Qr?x4 zBAN9MYw)&eW>;xVloNV*Jvho(VH>%8eRG0unpF{FFOGQ2o1GZqaXXo{YwqBXvmqr# z=wDGGKiZA5J;+Sk8uX({EmL0ir8uqPrPa~(hR~67E&8qT#x|kZRJL-se*6CbZr>Em`!STqu^}M{XRZ?wlzVr`aK&sWXWk=p;-j!S)Y@co6cBcZl z=F6ZaZX;SE#~65LDMRzl72{YlOU zk>Z>CR%p@^+U?%wfyBy^m7>g}8ja4KR%r-H&)%iFBjR}UU7^=vwrk>QZ%Lb>SJO{}h>(Q(J6165Qt{E5#8mvTac>$hX7c=khF41$v5v z{VaxXaM3o<_s8ibFAZ)^x`3@gQp&$>TBad2l@DXZL;s(m`X8Q0^SxXrIby8%J~|EZ=C}KDU@>s9Q91A$@DWnBq-3<$ zX+;%n;1J)Z|BT`L^})_|43D|-Z#G|}TN9H9uX?dT#YT#5W>!{4{!j&s8l6(tkQw~) zY)j67@v{Rn;VL(e+xHs8jCXc={Yqxy?SXc)HSk|M8yo$@cY3){1Lge^iPBQa0LHcr z6FuVA4!D_DFOH-lPWW(0NHbzG!WVG=;(8FjEs{LxMZcNSa=#B9yTxg{F#(m3er=zV z{8VPfe(RbDfMJwIz+G(|gVB~Mb#UBX{WVkd)s{UslUUGICC~p#yL@n%wLb$X2(<*o zuM4Q1pMZ*blXTK;_D|~GvS%lb#wXZ}*bkD%XBGxByf>nOU1Zq-kW+pm z4@Xz*AGHlTI7}w*zfe5tlDDy%5QA;TE=UQwflN#b{T7D5qPCm%XUtr@itOv8RUcbC zAXWF+wz8<;jkU$cETAqhLgka{9yCW9sbWy>XZL3ml%2p09U5E9hI~oUBx#>nucr_m z@ca^tk_6~EP*PCscTC)8r6^Q_t-re42gQU1Tfq@6O_EG>$NDw(tyb<17y)5#V!b*c zA6eczfF6d{ND#TYZ9R?l_1KMTjm}H_!8jQ%g=@Tu076Pd74`IJ@wiGfSnjE@oQ=z> zT?wK)h)y7yG`Dr`_*!SSVzX}lN z$kTW=Nvis}D1TJ7cx5X0>%#(U=KcWw^SwRx0DI1(lQ&zyr?~s{RqFwIKOD6q-ZE;$PaTERWWXqU3D9)rBDdQuF66EPK#B#Yaf~OK>bIBwsGDz1`_KB zNlf;3W+CT8Jc7T&0ToI0e&|YI%Ib2q{JQ5#g$>nm)L9$MR?zobtyRI$R6JRa(Z0o0 zmadZyf)~61AaILx1N8qZ8$+GoAUx|;>SF#8kKL}E4bIGA4N2e_JSVY>@Nlx?mYWfbX15M1LrF_8={5DpmQU+p;+)I z`{V8J?8(>wcl{GnsFk7N*Fow^smM(d-@0dMsY2GfN&P z*8Xoo!xeIpRE|VSiLQ$k_<%Ylw`Jo3s)f$WRGF$=y1m3dO+~r<(CYoWt#cd0rHbk! zu*|0tY*{E1G-h;c-OLJp^byiH!Qnn6zZ2-7C=A;?(l)H$v>E@*3e`@K4C=S6_tvXY z8UlSsBL#Ds6daDeoL*8|MU~O*p(_;&El%~!t1V>0*cBfh;j>O$6Nd;mEe>pcPmKVnxXt)GcY+ZORi=DN5l>=Pj8S-{1| zCQ;}$yQ15g>~)4T?g8qhf7Kd!suiMsON$gma;y&O`IME0pETc~P^0erRX9c|VRb=Z zWyiXjDVhT?qAZpI`5ASF4l3JQ=CyGNVvDnN`mWWa0dWVL;e?4q)i3wbtZHV9Q zIJgSJZ(Q}IRZw*zg~b;CG{gy-ehbrLQsU=gWDhoHn#c z|7)QN_`puHdPZy@Mc%}a=G+O%ye6u^n%%ytSCc0oRzt45wWG0OwU$+f;mq}JKMaDn zvvnlHsUh{e0yho#rI2v7c+L3>m}TKlk$>&ESIeT}K7PjWU6eb8_C z;@ze*m`3Z@E6$WV-}S8k9&&}r#fLms=B64%FcnFAfx-?bd>s?g**Kya$=)o6@K{(J zE_n~VH9n_jNo3{aug`nsuF}7+GbLWoR8ZwNI#zFlC80)#q`3(|VTOvgh)Yo5!<1>t zdLnc9KTz?%w*-GAUUB5f{y*%!XH=72w>C;g5V6q_upt6cq!(!-U8I9_l_r5u14suI zQMyzqq1S}o2_+x`(t9WYLhmH>76M=NdH308e|tZV=ifQwJMSOIU}TQlRpy%ITGyN_ zw3l0xjbE>^c0}g-keoNUQgC>i<{YUXnkFmHR9#@8eUzHhMGDY9s z^Nra3)FoZ~1(>{z!&mG=P`Vj0j%M*{5qW7Y+$U0%T+Btkp-(5c);%-vymfS3&g z(sek;(#wQ(4~8IM4~jX`U8zWbuhrJ~t8pzow4g5*`gbY)s#@j~f$K%|Pbq&ui=N9U zS}JldQ99%EoKxch0_Qm+Ssr3TBgy4=mC@e&OL!EdZhJqyri--1Cx0QurAMCOMtxt;$#pquZ$gOB!#g|f5N<(o`_|f5^iW`MLjKB7o zGV6~u=NG+k04`&R@U_h`kTQc6cGLK~Oz>@pS8=b{jEtjBFsxsQ!${|W|1D@0Wi9qM zAsyONM!WPA#IFTTIQuf4Jm`esHbe(!Py4ym_^O9v!R}IFw&>GR*lBs9(8f~nFa&Ak z8(lcIgue@McHbu2=jlHS6HSGuMqTWIlP@3nr zDq5itG8p|m!WHn{@SiEXxRpK17dHCu+GWsq+GY-YWT$cSOcd!Oa_YZtT=;631ivr9 z$wKKqV&=yRDDx3KcV~&Vz{fj?ixnD+)Kf0x{KddIdz3jogr~tg*_i-G*6UhBYQ2R{ zs1)v9tvq%*fK}n?F=()hOgtaINj=lOTxCnGvfAV>J+12(4aT&jem+tU)}$yJyY!3b z;B28<8x7J6kXUH?7se0;)`#WRH_R``NxIV=PaK$NQOH=-u|YS+aVK1Qiv_D{eYX$! zGuxD(m&IAdIYKqt7J_7mjY2qg1(8PMbG*k>?7FZ1fg6Cp^HFMeMWpwg=|3CT;pxKq znGTK~!MQUJ3kCKU(BBXKK8guwZpYIsL>zuzViP3vwi}4x)&a~IzYJRF2z%fC_X|o8 zsD2I4Q6bU&boTZ#1HR_y@c87%j2{aN|YqW%Y} z(62GR%EGS+GwtHu-(Eue^#cvQai;+ZUGU#uLj86LrCRtk38&ok{QDs5w*d^nD*!i9 zsQe!$F`iFAIIT!}^5hRYrf})n0BvAf#vdkOuNx5HkoDf@pVaFYoPQTjSqf>M*ZjjI zM7Z!LM@vrnPh6z`v(;|)!Ul7m1%+VFP;hH;Zo`9UYE-I;#1D5`31R13&oH+a*qz^& zT14->kTp1dOgYxBD;S{A99#MDEe#1X6RD2h^U6C5+ZKx*0CvIH9{_Eo`Kq5&oH>n8 z;c>Nv!37D!e8Y>O35^e+9$S0&Y%s2e;x?EHjoX17{&hVz7hapb(%1?aO#ce*zZ#q; zm#(Gn1t@6!VGqwJ@nYOR5C5P!@2=gJT_Ft3{DasrEndtZYU>Yby?t9YppTeN;ZI`R zc(Ib($Uhj4S-AiLF!`g9KOD_j1H72IT=gHUTJr}2LW!#{Y5rh0noICv@b~tA(ER{% zLLv}r>BB!5wE%Oxm>=oH9}XtrRU(qrC$F^rmOJsUOC2~~?2NMfPc%GMl1l@g&6$64 z2Y#fN22N-#|3qN?x5E8zNx}cO6)sTbcr`21597h{CmkjrLgN*s&NcH-Ojq&ix7(~< zFicSV!CU=5|D0^XO-1*=X-WAXrYJxXe|FF*5C6p1e1V_D(#Nmhe+ZC%1sJn0@Mc1i zL-ik`zF%TdH}U_g_-jj!{}{&KL@l*9@fNY~i}oMH7M}$WnErd|{>vi%d+Gk0()|Bb z>qPbC|F1U!{>ixf`|$oZ>n8heyZ7JL9_Xpe*&rh) zU#rT>3iS51fyI7QdZz*@y4Wc!-US#vf6B{cKFkdjIx&*xfcfPg@hYE0^D>>l>UOE) z!umf-Db9%jf9FkOM5!p@lM}8K&CCMP7y2z##a9=aW}6vV#C@LA@AZhrgC4_`{e8zk zrbD9Vn#zv1yiAGbYH~qZL8o1t0;I*x{)`FL**GZ-Zk#^O98?c+;Eu+5*5;q>47+s{ zb}}a0%tUt5T`~a@eTrZ?&v;M2Fb1sd%tPtrbS6~3ub-R(mn6UT;=9_Ui!ecRAHL5h0`J~9+%ddnqGZza9i$o z;uy%S)!biieZH^-XyB;9L@(}fYGIS>g@uD#ng>c=tm4Mg<3fkr*mbO!_gv><>H19a zb>f+uKsJ%<4lp5%Kc+wN>wfA{Q{?BpE4VLPWm8yh8PRIlSDTj^*jTmJr0k-3E!QD9 zfH}VlN+3&}*8-QEb+ZqYqPA1qIha$*OmgjKS+nw3M_s(|}#t1xA+qa0w&t1K0g-|iL5S<1QWHsYekZH6Aq zWL9zv;N8J*t6654Vm%={(=VY$8Smgc7zw_TEvXGIrDW@sX>`zq?e-UV9f@sEH z6kaLg2x0`?+f9D`OR|~v-mIvs&C^2P91Yj=`=YinN)7Vg)wg>UR?w?n1TK$1I6x** z_8#Bl=rys)rsCihG1#UUGYM~C^8jN}9WXT_TTFL>+czmKOJe_qiTYu%lg@=T{OCBI zHzMW?tYDgO+3PT{IiJ3k5y%BH*; z*o%j;^UHctzRDYzuBGGQZszpilbe&(2cOA0B8SDgg|CuD0?ZA*UouJ7%if+1iVAVS zCeigiiguRHcO{PQIdJ`SpOyylS$H*{>=*0=HER}^6*Whi7f18{pi}EIXd(k;Zg@|g z;clu}vxg$9B$fV}blF0+MyNw2IRp87xx|v*ZTOvn*$M@&pXyXDB&p)^9cD9ysi?4F zG34`BxyE|ev`vpk=@3#^-B?XtP=>O(ag(wFljgQ4@z=wr`;(Vt%olB^W3SiBx_@c@ zM#u23#T9Ue2xXhzyp|6gI`=@>xfJSjdZ`6dT{&A$d7lxv zUw_#;oy$D!+>x}a8(Ue7wU_n$0(P*zwvj|wg%VanN=IPcuHV}N!2Kaq@}0SS4CGqkL|%F-!nUt?2SoQ zAvkrHxnEkxe$$C^Ag*Y#gohLHOv3dYcv)%(f;$Ug9<8wP3o+cBS@+gpxiLqLaFw9< zPB2L>`EGYPR;EcaDDeGadh|&XqgZZ`f7Orbrl}CHlo3cLFzimZB>$+lt1=~=Tb;1t zIV2tIhK*fnn6TW_Y&e}Q!q@j|CmzBp+UkmEBm)knZX$JC{X@B)T{wJv?}h16`s#67 zm4w)MHz-82DTB{q_v)3A`K6jM956fP@OSlG%rC3unyUgb?nTA}dXv{g9uA|M zc(_#b(!R;mY~PmLApe}9d7SXO*UL4DrjTs%uc50{re}rUT1EUVpl}=T~a`9rr)=AeHxb! z%dm9bcF#7t12r{m-W;%_WQ+Y(Xr490WvSOK>~J(Vo(tZov>jX1qnysUrfYma!wQ@w zf;{!;Yo?!8;nfv?IADs-4s{$>6oGSCag=5$<>(G=Bbyc^<}(m zp9NgsK6~-_ML{tr!&hJ5IqUSWN8_`el>w@LedH2&sF(%ip3+MXcVss@?YRkoh85N@ z`BWHIuuUh-Rg;bQhKi5ACS(cFsnORkl7rY^ZI0t-ecggLLEKx`A7tb0ocW zJ#-3vqru@vHUi=|187vN#D&|^?_^!*#Z8hC#@YI#=+s599=!KqJ%i`P3KEAsZ-n8A zQ>+i`@SO~$&De44SBi`V-Wm_{x${_sCRd%eNZ$HTp8zpknvUbSwtd@4+EX)N7_pbO zp}o)}JCycbsyC^T%glomZHwrG3s+jVBz>GH`>L*nHiXi%re)1ZvEv+wjr?Eb?VekO zGkHAd{~WJc>AphU%UE2pnI|;h{~S41GAJGF7~s@7&sAhzp{*y{Uecz6V=j6j4}T|* zOvP54sk_|@w(u11+fqz|^|^FTn3U9c?$+Bk9kL~5u}PO{ZpFK`1YgyAV!nlB5_W3{ zw)q9;&Z;-t_&)LB7aSfG)fYbs(iH&=+ZUkcth?*P3s*KQ*g6loZ?Sl-B%iJ}*Hl^R z_*^r6f8=j`+m?)hH{s97^eyS+*~7qJFvF}`^Pc3=cZ$*8 z!U_*6)KGp?!XrWnwgcZ>0`ShU;%m+wT(EHHCU8HCOwV&e=KPB-}^0sBYb=qe`V2oV0xd>g@`_G+Gp? z7tJMm1oh=IeTC6wqFxox4)6APk!Va6NQxY-0>FR<3FW47Crl3mWe*H0fniWk@8`qWuTGniv$ZvHWt#zHz6g2yo_CzBrO-HROM5^viIkzwwSs+HyD5fE9Iho8o;>yLZY- znt8>o6fN9WMWIdRYd;&l{A(`i%)q3j2kaqCbALt`lm0w|E;B%q~ zOspPuH1%%V(wBq;C^{&sdb-`lD)|Cw?wckpOFsfLw&AWzPJd<<7bG#}Hj>eLe~2@a z_;S@Y=?Wtn5lYby;#RqyUSB5LA)*KP0(2VC1!8Q+_UEWcgVV=*4%WTXEQMzEA@h zMDS9-RWnHFy76ET>}gfE3@jh3OYCP)UoPgIevRfvqPC=r_t6VGZaZXt)oK;2KhJzE zrE0{Jscp9k?}EK1O;!fpmk0Q#Ma-*$h^75=v~$lFE!R z7k5OHNm@49S$`4lXBnDIw7lV*r@%?laOt=tGABLB5xnI`aJ;={NDMkZdJQ#yK$0DK zPd@KvFj4H2a$`xG)SYYh)mEV}C)LX~yw3r~@P4T^IWI;l80UHx&j%6bR(u^r{Z;#H&NlWDydTYBY}X?W4R27Z&vWoBqf<2iQa zqCVdOTO*#$TtS|HhB#Uy0(Gv@=M*e^pR}wEGTMKNXlzhbyFv8X?&`j6-^WlU{*smN z-bT{iUw~xk;@CHX;iPj1!@HL$V?{w-o@CSEqym0r+_u;H&x%X|4Lp-jhhk^Z?xuFr zO^hFKYFf*#K#e9nxlR|!R;)c2bhO{-D6;;!l`2hki4th!6HM7BnedognFlsVHDG$` z9_L|DE!zwZumD#9Y9{Gp-_B0E_Zl_yR^Q7zkkKJs?ZW%{5bv&7wkHtbtM~Kj3%yRG z5GX6eLg#_uV!ECYe9w-HyK8LF-zZC1WsP^`?S-UW)`p&ho4uNK$fRRmqiN6>)oPy1 zX=?nF7_s_dRV)@^x9fkUw%A`rqEc}$wJdKUvN!5-+M%XqeGf*^I7u1Pq2<48Kld)6 zZ{=V)hk76pGp5eHvMS-5ZIB94d*NXV#;u|sXW?%XYOnoN@V(__lg!2DpdmJ?TZAgp zw-PnuSXf9~_m1QlY|0Lt-61gLJ^X4*Alu2KlQXMy+7*M+n~C?XKto+5`a)QwjA)Ft zm%lRty`54gi^iClW0$wliKjo^t59s+-}uu-MR@eHiqZ;Iu_(hdKd>eJQPSPN(ylOt zKyGeXdSmhqy7bU6b_jAZC#Q_MavLm>wN%qrkyBb;vJ}rNY`Lh<4ENH@xr~@VduNGX z=9}uI!P-Q8D?udjs%if;W*Xfa^63yZ5RiIyk1d^h@Q`f_u|Ljq>BU-4tfuv97-Cwa zUmE&)+2}yZCKu=Z1PoZ`HSFuFz^gPBUj_6la^{C3?3ScIoD*H!vii(#|u&`N7l+>oYv{J8hE~$jCoBfB@q(D z*;VcBh;xPHK0ZR`c6ofGFgf_??&~@SqBXf$H#;C%zkik?*d-e2IhR?Cg_aaJAQrc0 zqkafOi3ORf_6kn8LfkMPBW$N#OH{{})nE^O52=fk%XrI@y`MAwg*~*FH^Ndaf7-{L>UwC8hrI@-4 zQr$z>c#N~V$<_*G{(f8$4_}%S!i*76OwOu=5~qZDBjP9#k?soPT6muknwFxo4Nn`8 zaWKrfM{zGfkbLz9*@x`Q11hTMMb6>#CZ0LB4-rrNpg3Q)4Sy@w*LVUAKAR)_i37zniljI4d80qYdCY=bNQ9e6`p$L=t+VGjom#h0~=jviq z&z%^=qIhNL-@U4bvA(!ed{8*NsfQr>zT+4tSTbUp3cS*P1BjUbs%$Y=sd%t*&Rj1h zTezaq-bQ#OMJDg)qERV1Z9G28?cQ8HmplurG;Q#Kh=t&)i*w@(3}Ue*bBMWL7qWwm zmrGGWy<|<347nE|TQ1AHZV?>dHJi$_UY<=9!7^^kGw+0pPP-L*)DUT>L$Z2{Oz;?} zqb{8+HZ1l+vdX0i(&aGPE2?hLxxzxGD@pzIi#=Oyj_PG}gQ@7Wke+A)4JRMd=EC4# zxBjyLI)-{eSfB+Y*-Aqv=Z97>u^bC-tOm5ds?~Vz&!+dV_51ocF$m4ZzTw4{u1@U}LT<)-tyE8Fc>cC)=#{ojpaqZhM6CDL*9rb{C?%87< zp?II{rJ4zwA0xo6!677NxjA{)SiRFCG3ZMRNgl|{HB~&jDlY!xXoPL#FZ}CzGl}R$~RXA*b6@xG(45^rW3a$>g3lMSD1aI$b2@T)p*dS#e#$Tt=IN_#nCNF z$gW&?#h|RTUE*oidX5t8Qr$#u6H3s-c|-8I#%+#+vSEU38aok=ev?pymJ7p=MK?B1 zB9ID@N6=hX-|m|cQkR}3-qpD}&8SXSjjn$fq*jXl(d`EDZh1XD@X_=eVSQX^0%TpE zPITwcQitgmhig{#215lQY(TOX;xpcakn5ax-|}+;9G{b*?8i2hw(KM=Va^ExvNgYP z$=%c@=WLq+c5-a_<@v;xp(d%lxRtzC5i4@pT**$yFcA^k4CA>5?Sox zPojEvR=;@wiU%&hJXYCA;DVq_FOTAVe9Nr+w;gZ^?urxm^y28Fn`!OIx>wEP)N1#4 zO2h!TU3|46`7Vo(i%3T562!zxqm?;T?kKp^?!DKHQV4;mwsE&jfJ3di6-kxj zFqg1DO8VP2x{%51SN$C2Ga#Yk*mE&cOMq8| zSGpAMeDiMk=4y8rt<@F=UeCK7+i@yAO$D1x?o9$3qj*#eO>$4HM4^lA$-2ioygVq& zEt;<`Z0RvC8Y(s2Bp-ok+gvO6{Ux)2B`*~zjS;!7R0E!sotbU?TzDk~53$+|goEk< zy13ioiQMwHe?tnraRL>mq8D|%!hJ$YQen{qrt^=e_yku!jb*>6A_Ggcl@3d`gB~Ut z=9Dw%t$#{>urbB0Hj+|mX7)(RilJBGHotF(8k^v1=GcbNhX?{weHn6*r_E`y#p@QL zoDEqvfPFY*s19)uHQbZ#%D{iUxLteeo2}%4;Ef%xxdEUX!JhK8PZ2M*J%Px)M0ZoUZnG8udJH^~rnb zCf29guxl!=sWU}Y9)^mc)L<1=g(XQ@(To?S!3h#!4|}IuXR8E9IdPU}62m$0FtDpT zKCE87L4PUr!X%Q(#w+b|l%&Gp?=EoH&;(8|38YOY7X#C_h6>5GsT`bNUYoJ=O@j@O z{1q!8IGG1d6Rzg3D|TbEFZ8AX8#IiG1g0(>DvO>W56-DBJffb*N#W-5+oRMfE=D%`=ZC#JRIpnnC9YhH9}ixD(fECOg42B%Ci@_8YP3U67$t0s zEetG3+!#GdvEo@??aVIDaw)sIQe6)v0FQ}`4g|b!3H+&NdXF}YPdhM?_WPNi7~6;j zN<6bSlB1Y{NpmlQcUtRy(0e)4TOro-wbhym3t95L-&>lpA~qEJQ6S~;3T3y%wYp`@ z?_DrRZO*?k5cg!DP~G4A1L)4FHXAHI@*GRicseh^;;ig_<7KIW>7biaw6rzHBfb7P z39JzX8|z@vQDNEi;B!LQuM3H_E7My;&{Qg``f@*X>z zb*rlb;zb(xoeZYt(|aF%C?%%W8}GJh!gDJvG3>_gk>DLYT!-vMzJorvkXtpIdH+u2 zfiH|;8XuzoF`uK!C#$e?%0?UG@L%`_P_1qGu53*O9)>UP(dZuea!dN9W^YYjywqUz z!SfqR*Ca{0pG>~<|J-2~fOe^bHq@7s60MQEpLE}_uR?~9RPOJ@adml&qmx2U=i=QY z0LkIk2IAS=3hd8TFX0{CQ+H(r_XnK9*}4ZkcZwp592%C`!Lp*a@1 z#x)S|qM=T$JFU!PE+-irwFZ;&$dw@Z$-71+;kuUTffTlL{_Yjf<~q`_Y&RG5kzD#o zhG{>~n6#QxpZ(8j_(XBRpq5dh$+09pmkkK;AneD`1XGU`L{{R%0p;`_$6sdLO#0g)hX5+iVN`F^@)eUA%3I z91Jt$k#wBez04AB$4H~&XY*c)iTFt8z{l^R>0_V8hV+?v36dIwNY4_Hd$ma7V#>x0 zAI}-WZW-S1InT)Hw;xJDKG=A({jupNd`ZRkIRekJI-HBbefNA-0T+oIP7giNz#u}p zZY(7poCc*IO6>(C)faMd-*a1AjJ!GQqso36u@MvurMYy3`L;1ERm7gWi3z9WJF*=N zpNWA*PU-we{fos~D@0@<8^D`>Py&LiYs~ZQ4oD((PfC3^0pDDoe#woH$-Bw9Yo=jZ zWxRFyjV(V|f_OnR$8yJE%dhmuW2u*?#s<05jt*KX@qrxu;DW5%vNnF>@YC5xMHQs8 zL)_}>iSzqIXZ&K6_&s$JzVyy7j|w~pLoRnBq`cI!@Yavb)N2%vYB}h)mK9yDvb5Q~ zdk{E(gEmzrM~4&9T3Te|Joj+`Jj1t4A zE^#VkCvBv`ZaSSjjnP)^;)*Uim&(7uFbe}b^1Ia>5+SY^es|koeiSHu#>n9;g%g?Q zH`BXwSvdWDn!^zF4UQYRbMX|TEYZp6k{4rH?cdN;fR4Y{i7 zhuIwn{_u5azA@t8pn{F~hk{?iwic|+d`m}I1Jq39TP{Hw-Q9=mxm+qAa1Cj#Pn#`hjK<}6L|uGRB*5W+6!3DnneF5G?P#EH~1a3gJQThVWx zJ4qNCA#YXkc#&vF&HL>f&DC4s8!AS=s$IRp($`rK;Q{f!DQHN7vK`wDp7zD`c>2^J zW-JV`l0HR=8z?yIvC@iMye&r8r#DNvtfwO}?T)Msx~=~}qS4s2=(_M!9T=$;(|E^e zlp;*l_X+COHEAc+Zkq{mXRB)U{2E_$D-d@>l1~N*8Iat9fDAFonEJz1o+Kt?CJE4U zw>UL>&PT(B!syLGA3NAUPigF5rCzfk*BMv{VOJybPg55wbzgJ^&KJNM3sv1 zORU3c#-h;aop;(3?-hB9>W$e6{dEpmh(Xnf{$BvgMps6SEL5bfrEz{kJC@-XycQAx z#!YV6=kgUP@*L?vMFk)84rV~h}#k#Y9DzXE%_v2lfE^n2G*wVjh!jeSn zC%LAQJzl5+{fJvR>wyPGr?PP&H!7Z)URwXz2Q;b>d`w!t)TmXb%gt)G$iJGJck6fY zQ6N<5l`hGuVlW*P?njJmV%rm~q$)zg!c2-qI*9xk32 z`pMEnDAofQNEsTUJIlu_8vQK63Pqfe-Uo(1yxH}$Uq`O`XsJ9!iu^ZD;Ka>;;{exu zlHM1T$+u*;d$1;(K{V0u_KJMFf7{HKr|JQ~!@J0=JN#q#m@C7nseI?b9v$xSBWE)%G7@Ai!x+19t5Kw^QM)4U?MhmdK5tJSD&k)(&0|_Ed=O)<~9H)F2I%)c^ zU(ZI@G&VYE(fze1YV1yJ>tE#E8$BGTu3?{ z{@VxsYkw)Hd-zh0@EcZt5c~O+fDm>|t{hMN`M-_jzkcf14^#N!7T2t2|3c*cR@UV{ z-XkkTZIb<-{NMj0il4;PL#;oE1>T~#mfn5+e*GWT{ zXi8qt{FCZ&Kb;LVKg%rHz*iH5Muz_ta{jkv{+EUqi{NV$43I1$7d#cu)|8lIRNV7#wL5P_P`CjP0@JRYwJ!=ET-3(>IQf3CJ- zK;jQ*`yp;&^PflB{tZ8&$L_6SrvGR^0?T-0103v8GJXGCZ}sot{ZA(I->dgOD%bye zx@X`X;P6(HBQkABK_!qm&vr4DHXGq>8Ft+3CZe0Yu2ery zE^)eho&)tzqv=uEAsmPK1%T+H^b!jy#_O$7bsj1wq)G3l@6(1Kfj()A9BD*9X3*aL z;1|@xd||GAp2q*x;V{T)F!R1 zE=b)U|0MP02Y@D`!2bMISB0+bpn{WRNjqu2uPBrADnj)$ldnVzQ&N0~t}&EEgQ&v# z-s#W3e76bZv6sg?-wSF+flnr>FDqXn!dcTW!V5l{Es=eSGiuhzyZl-)Q-}=d$Sw8H zLziyzGm(<{`)W&-xD`~?7;yMA0?m^-Br;wKo*!xJ&1hxp!oS7N8#fQ^+%sJlrJ_CC zq0&23z;|96=p2KA)^Ey_rX}WvAh%FNS%WPb;#X3oic;@^sT~M=YwJwnE|k=nL`#(F z_m{#4RNNY!RTcdLt)~n#?na`Z3Fl$rcZm!>%ZlJ=%;26Knv$Z!rYukJJ#s5DoZu$4 ze6PRXS3gZj?yGz5U40>Sytg2MmB~5lkAK@m?^1g4QKWW4V<%CD*JZk735w(ET#h$S zZ?*~16>?62`oX_$-lI6LRd6+6ixpA;P6Lyoi2;rtULV!22903(8kuSz(!PL>gz-g` zUj`W_keDO$>onXyVRY;~l5@k$yBX%{f1g*9TDJ>#XnoxixKjJb4iy!-yH0_O8te@u$bJ9W23p#YYZOXX&&v6GZ z%t?&(suDA`JH+RWr69aR%rjdZnufkaeja^pYF>n}VHN^razFEg`On2*#%9#j0lr(6 ztu4e5Ux>m1udm6STuzXSP1U-&5N*ilp;vJ3yYpA9BQ5N-gEl|g_BQ4|Hl`#yAJ$5w zI%s!juwe2b2Gx+)GeQR8KNfXv>0r%(sG=Wg9=CS&F-A9y0#ps8c(;&P9j)=C#6S~O~LqmfRcpjsGOQ7rGv zf*jV<#ydpCoMsN(Bz94ts%dG8x*~0lN_fjlmG%pZN?MBUmccr$0}HZAl#1mHP-pZV zMLSV#pd<_A&yUIT=U&U)eGr>327NIl=O%NJpLAINPU(5R=Eh-%iGNyHGp$Xd+CpO^ zSBc#fNAokMU-cOA4D84k0lW@hh4>Xw0^gl7b7|0$>`$Cu zJvYuvYHHHzb1Z$sgB;KHhNryI>{UMcnj%v?Ssx#SqzS|tkJi0Qy1JqTTjR*n!^Rl} zyALl(o0qtK$$@*ihn_YqZ80CanDcZf%>r)Lz9$CVar3D%0uH`Hlpi`wd&`GxkDbH- zYN2L*8R8rFw2GukN)Xx4TjDDkxr#g=HO$4vd5?(npDZz+ssuv}o)LY9HOVzzXvPQ( zTt8C@UT*Ko^;9LUNCs^i(=rl0kOMvfbGtqYaxg2J*&3bO^^4p&j4%>z*rd5tk>)f! zgSUrwbfWrNae^Lf62+(MR-8eA;9ke5(^z>nCZ_p7M`%kURA^fcK67sCQtv(C zQSXLHGj8$>8wNZq3Ex~d(f9QP zW;;HXmjN#4-B)5SFH|G*pUDVW&5j$b_tL{DNVL0vl*X?RiPP8KB?f)4(E#)hAFN;M ze-(PB#xlz=GE<_TKPtdov~0`+NjxWAVuX+Gu%SoV6L>P}B}MZw1-q4^^NW36qruGN zZ2+mM4BIb)0bz1rAB)KKv}&Q3LwaW`k49*}cBqUe5$mq&~s@ta-bed;r)FirCzhnDn;Ts-rKw{I`N=){}=9rA5k+nCS$gNH0)W_eD=CK*G z{obSdnlbE}-xLDSW7Aw<)P40Rn#Knggc6Y}hn*^;{y8)5EYM`O!AcoiSlGp}>@*5k znyByiBz@F2weH!b`z~MQ%3hbAr8=Pl)%r2B_l?O33KScH0KDbhN@Y2k-+5+s z`YuVr%#n$fkP9xCH*3t3gk&f=%9}!Ehnp8^HcPxj$E15*6evVCzCCGY{*cv;Gcxs+ zd4zxZ%Ad##%X#Lr7lC$nAnRy}fs{C*)%dX`=Y9$?R@*AFJYaCrbw@I9m`eLA&Bpqq zk$o4Rro=P?)9nssVGp;nEoT0T(;mJtdg+)vRO)$IQw3~PK)Ke4{M@aI-8de82!BM3 zYrS-nYh-`igqGe{@w9(<5_$Gbf(I9`PVTMuI0d|ZWB=<%FFjM-OUwBA27R~UTXTV! z)YOl+=tNFOb^X#Jl5x&$P`6TXfs8qq^X?|zvi&$SR65!}UnRL}%f;+ohvF=NzV;~* z=+n&1L?-b?KV#5~v=Q52&iw+STZpRpp5PW+UAZS6<%W0D<6FT=U%9qsG~r3-yV60e zmuo}|YQzO9ejXnnC%3;7UZMJUOQ}AWInmk0qj5cW_?@1e24?*uqud9H@s}C#;}6p| zhI_A-MsO7kr1}GgvhD8sckqwYWt%Wt5Dp@lN2cvdj>E6Q^jsZ>oubOl>!%?stkcOj zoM7Syr6eJbch!bL^5h?U?*n0m;i=pt_wplbjP@%u$Uvt>G1Q#b8pu}P1M0nt*~b~g zvkHko(tW)%1o;E0W63wHBbe5Go6I7&BEzSO^=!-=O{$EBW9epp0uJD&&ft**fv0n3bx5lWM0_^G_TTa@1cL5V8;Tx-eJ%^Ua z0Or6LNjtrZH$LtrxMgC9dIH&_{b~SoA(7Oc#fFm@L?V#StTk?SY&L6do;PT=(`)X4$_&YfDE7C;cdmnKGtuK6;m`ZbwR&BO1ebd`FS_(A~ z?#Xm*dvS+^x2l=;MmqDzOg;C=`ZuJ+(xyIS2)WQ zGiiTZ-IwInSXI~$)Fg)+T{n$+rkXPG*e6jQ2RQD=eo}&qH*SXC;dE3krxq`FfACP9 ze**B>$|`trSphLxnv4A`)B5H3!}>9x&55%2)1leu6#pkL6W)X5fQ36zaRG1-rD7CZ zCE=Qv@#yH4!N8lelf%eG>FJ{Wqa@&Tly1c#StPXjZanpbfY+1vd;*I^z^wWMa;o~nE4R-CeQx``0nX;#&Kf?F!koq%II%e7J3OP)P z(Y|;X?e<;uYKr-dsU5nbCJ09($VSCIIWm1R1(|h{W4j zfM&6u`lRC|!(^+;AcZ)grpXIynPB!(!|ADl(fCcK$9paQ8=7rn_qrEfmbm`FDi;DN zkj+oA!~6`C>r)q!4F0GKtqe+zu*>iY0o>jQ;*vjRDm3WZe{cbwHOYL!e(#wwf}_VK zBIUmd+xJcc%{ApyUHLpNiJY?8eA0ViQ*9_XF&)p7{tYtc8;|@ zHZv*&^@{yYFR9tInl^6OnU{rzIVGZR`YRCxe!c!v7iB52&USWw1i$djeY$q;;77AO zZ+;OeIdaN}<0{SOqd4@~u$&ZL-OL)%A@%6ls}R1eO-5UYRr`iMSAk2U0`=gOnF+~! zzEcJ0@xlsSLWwN%D@$d!m6NF3TNb#AUeZl_tfxmE676W<7N!DW{5-j zP~YChO?2A#0&$Dbk=)CN-@=Pp1_JPf%%ls`!q=vx5|*D96|HZ=-_}*&)_r&ouRwTn z5fPAFUalTVdyBy=xKCV`UR{0P$dXKPcYfP2HMwdhXWuUR<%io*S{Kn0B{#+KKf#>FL-TQiXiss>^klC0s7|wR&+;X z+~%G)3(qR1G5%U@WiLZbYF?vB;k^k|*%nLnazJLDgLF{|3b|Nny+Q*VKQfK;@6$Yr z;25=3Uty8#rJw~&pMT4}lGp#`?Dh7Cn$go|A9#8r4!UxCjEi!Ep?ZrDdE7dV!g=YE z7{y=7YWt8~TZO6Rh{Q!T1QT*awbXJ-IeA{7T0ePD^po>pwnVCV5w)7?%-mjWlM#;w z-*6b;OUcZ_imTT73r-(SJ%kF@G@l&2^uaP!0c{6j-|FAkIF)-bw$2NNHJTN@n-55W zXy@TX;a6d4IM|`jE5FE@><$s(e6cd~-b;}B#k20!Q$N#QDQUwvn4o3GlPqBvT1tosA)x=&Xr|tA&QGaLfE^D?w?0r zKh6NO+=P59hd&0L*Bk=mEudCkXg0p!Um7lYz>_T1>#P}iRQ-v@kz0Kb@8xv{$)}BQ zZ)0X6UQ8Z4dh`ulyer~xxv^$FEP3F&ByOWnVWxEvc?9WRFO3`lk#-apZVXNhjR_nNy#=d*P zK$e_>_gr#Sv3+{ST!mw^dkl!R{onbfm;x^V`FXadf>8h^)0uR)cT$!DER{#-`Vk`j zYFQ_UD@6=_V>!GUP9SOK*xit;B-HOFCJX?POa$ZvE-#UI)&-K?Ij_=c!M;OAKw?>7h9l_m*Hx2wjmf!pr9oz^emRegwoxF1~Ny z@yEEHFyg(Cz!R(!{~c22E{k`!u7FQWI0KtUZ@+Sc|FDj#YuX*(6y8ui5F&&fduZ;~ zd_bbST0^HHClxO>w4a8|gmJK9S${rJW8coePoe=^ zCLZRr!~h`qk&jHHmQi7GKtLkBjHn%lUAu*{Dyx%7KAMAjWIrjnC~IpwSQ4L;MFZda zjnTu{@U+*isUXriXJa5x%YrGswAn;iW%Q12=SEFdOnKTftoa9I8 zYP=!&V~6Fe(^l9g$6YLR;L`_RE>ZGEq=2NS<3+M}X_5bm80YQL&1ZH24wk3Ng}h%d zkFdLU3(~IUIhJouG9e08X#po_A>i?;BJx%I20agg&tvOz>hpEE6-2=vTe;5u;lrE|r*&$xGv}`21fo;z zacFOnwcul5#@qq}f?Cly>cP&MEA6u|M@w9-TTEtU1}-4i_qgBaFnd^e_|2%Jz4lfD zSSQB%G~*q%zG`8j_btDCV?Tn*C>si&+H<$y3@0F>Aew+nZr+o^HTeVc+^UUpgw+Qx;--_HHqF?T z=jN$hP?!2WYLmF>!-Cbp)3C=3ptdKCNdj^bxCLsd|Ha-n2U+$d>vq{jmu=g2RhMns z?6SM;>auOyw#_cvw%+;8eRtx%J2N-t{eL6&AN!mWadz%pncvF%R<2x4tS|jFZa)LN z7=45JoF;Q*yKbH=Js6vQhAMw709AI)O1~?uR0m3lLg4QwFRK?V*62(_1&g5oIHAdHT!4z$w`x& znV}Gs@j92tA=IXJJ{-bgiCe(^`e*h@k1Jalw}AzOE6Bqi9tOaAIbho%z)!q z#y6%W<$cMjB9_}XT4SggGYhsN*{hU3i_VH6&8F4>EP-aogBj04LGWrJ70t+etKL)T z#q99zL$!;y6+&P}=GxZ|2w4RydP}D+Hh!t<%`afgTpyQ9b9pNk&-m7gC>xt@qzmF& z*n2f0&)%duJ?QSU!z9Z&vtf&C_Tt6X#?d4P;-zJ@uFZ;dtjE>h$80G9X4`DQo^F>#A|x{M2O?_ zGmVqO#nTPl3q+FoYA3SdoI!bc>@UZvJ}3)YJ4HJu(L+qYu^J5@*436)c-XRfiI$b& zo8&(p)ZIu-4_B=uV^q_O)UiuW_~APzX`JS-AGm}XpFh!EXtH*`l7Ih1*sNl{S4}U& zCLYDOu&NwX!YP2BNzKIMhmm|45(WFr(?pA=bgUphJN$Fbtfg4x9Y2FnUxa>STMd`at{EkrrXnhzv*TsG6qGwI$ z%zveqUe9}-Oc!O_w!U`;2zqj?JUeBS6rD{uT*#ienw0!5r^sCRRS3`Qg9jdD%d^+= z7hTsNYH)nJRLAr3g+sP3pfa(r;h#!*(z56dEoA!hR%uV)mL$5JUUCWT!Jt;aB{?T6 z#&$L9A!jtWD}et-U_x!N3Jbb;c~`CF0hK)yRPKMv^bXe9f0w(mleb}sQd1OoXb@tv1@xm-WF61J~-bU~Wt135?O*S_2eNL#lT=65#d$Mw*~9=O^CBEUwxo%WTvy)1iMETOeN;#JoNC;gu?O zdvK2C#C;~ zLY@cqZdQ8wbnkn`I81BTdOWRo+3^@wQ`s{d`eB>J@fgs9Mxg&QXK_+R+-;zlx6^t_ z?TA&t40|@q6pqIVTebEI!PAfKfWqdCpn716@5fP9wuV9OlgDCHp=c(5)V`9$2Yt>O zd7848i4OKKpC_HmX(Pa{8-;9UkzQ*bI&HSdDY^S9Km_(qn$N8eRnG*rEIFryn_Os+ zbH-nvdV?X23Af+4dB^QG6=tUJSUH4oNmz;k%YAJpI#{v+M2WO^*l(os zzXpu=Pdq9I3*WKo_~zc>A6gZfvc-n!1Ja4CyR{_=z*a0%qgpr$SE+-ITmaMSztZy; zix)|^_&aaI7+0OXe&hGk6nbep2#GxL_F*kqj4&Hiwqt-JK}9WcH}KgvBww_DVZ zf*srH7EN6|t%+a21~ojLE#BxWhNBj?zS*?G1O#Pw0v5$s5kc1MYnaozc^t`XtG3=a z+j-cnE}xGD0eM&v#8^1%H9r4*mf3o+nKgWNpi!3dlbJc27=EQy9W4#d!H#Mpt#oaX zL0^?z_fpXsd45#Z@m3P23tl@vsT7f)02?sf7H+Him!{kdUzwZK+P3N|%-w_TxQQR# zMVipmLo4lblA&04qx8-7h-`b9(~&>xpiF@mfD>xFno^WWWs(%#Q!2onI^;CO2CWhU z(t^kyPk2eq-~m6jwum)lx461hJTbf#yuI};PO2Ud4`e?-Rhj%==(Z+JX)?xi#!2eD zeOUKXx7i73{#XPw_WrJj=Ugp}TRYjN+ zQc@X+K+r0$VxPIj!np0L`Zv0LNzSaz`Fkg%V-Nw%08qPM_SOA)x4XC`_wo`m|+^gDI@ZG$u)q z?o-7O(x{FXcJ@>u$60YhPkM46J<_65mg%!kaFx#kmW_K+=E4RibW&d%xHm_JQiyQu zQ=d97H{Rs|6Q50=CQSf~wG+oCeXp(5kZwJWXsrI^VsFpz9y*lde6oc=S{E)X?efhqG(u5{tfU6!lr-Di&;)?A%6ckT3d`OIaRW-ArA&udbOX{M9^UnWxq7 zwBTjzu=hfrJv=olaID!&Y7_ zT#QoVV=7!W@k2U$8(Fl7Ep1>zr!DOsZkfX-7kJBnY9~zL_^MGQ-3qx0BR|`jw#%lF zbR?*u0}a6L|G?>8o9@3YsTsFO9QU{=@w;KIp&0Q%Qnp^ze7Q~x!=w%*=6 zO_tBj{e^AyMJ+g1%*R52<<4j@aT4Z1r{R3Gx~c^OaM3Q^WmNnMoOZFZo@g2Xl?FYK zG}nri)y$r&X|rWjLyzM4*`R4@qsdav75R4MCmNiMB0nPHje>z-ER z&B%xUM^DQrgSI7|4!4PQKGo(hjW$QKe5&j9ncxfcqUbmG8G!UQ4*0eFxO#)zsQGwn z5u+j(?lWHTI{oJf#DhojuN}G3fabb`1W~bVuFa(ngHyjw_JLHYt>@~xp^mu$OlO6Z zx%mLLIgriqxpHeiEZgwg0btIUl5)!xl$m8UFnp`2E+wzK<(COs9dotM5Nr)e2^ z`6eYVcv7Z}j;r+cB@Pwc;>sIZzMi>zhwul;GL5TKB^TRLIfs#~i!eDtW7(&M=W7_{ z6Z=VP2-Pvh;}c>%EAS?0otrBxe$VjRvPXQ=lmB|7zLIuf^^;~Fmh*|?A9V=J0q&oB z#5DEp#ip;JZ1k&kn0x1v7ppt>wlx8=v%%h5Dy^wQ!GJ7#Td`+TcXGwH`NBos^QubT zs$q|guZ&-XVWL&W&BL|Ra=v0K=~U=+yBKJ<4Ko^bNklSNz zB{V;tBldd_MlXf~3!)ao5Z{{Exx&QnZ?4ug^M>oRln~6n^|{b8PPTcP$l0AZ<(znv z;M?&mZ7=THmiXQ-GT^|kJwI!aA2+Qkr`J3yw_ZDm$S#(VGfp=G7N&gyzzF-3C&OU0 z4?PYcxt5J3%u;BxoYZgqHn~cM^{PC(N^()w@z|5zGV8+4tUHoGB?zQ?qO*kd56&mB zz2<(!ISo$;kc)$2iD|EU-3%G>Cb;}Kn|aw=O!Khpof?U=B_s|Pe$kUvu-kR>xkIVY zYM_gEp-5U^Ig>cAdH8&Vg=ahO$IPd2LHpCZw4*$4SwIRjWem3}U}j14`*eu+SD+dE zZtlq~&QM0Pvk9uZZ}gRS;#K2|RJps9{q>P}f#zXYHR;U>(8W`nTKqStNHid;PD0^d+UKg*&M!+59LtI&l*pD$DmNI%wt9n8cGUvK z*DaBGv@4J7KNyCIBq*@jJ}{y))&pUQbu$EiGs)YSm^6o;s-O+)goFf>&O3YxD_4cc8`F`YBm|87Tbe? zEMb2YdLh{HR{i`do#S>PPhugAEl9E>-=(QK>?A@4eg(_q2S@yx#`;v0oq_64_&s^s zzPI)A=l$Q~&(~_a$M1O3G6Q$g*s5H2CbOyMCBLI@?C@OQ*Oj& zK3>VyQL8cogwEe~h$x!-=!|z^Me^^JA?iZ#CwuNK*bXKfm}bI|OsdS_^PDDym#QP( zhJ+<%xu~cG^sN7cu!!=?3^+pK7omYYzY@m~*|hPJ3%^(+M+H1zUvpW*x*ua>KLzw7 zCl1_QQ+!8#h8p7qh+1^n3doYvp~nPVYes z-QJ&6S+5+{72Z#xDOOaW%K4<03Io?{IN6pFYA><(>jxogYb7P!Gm$s!5eobc-B4P8 zPd_)@&q6@qojcN7_&kjQb>v5pf!+X zvd!TA3uY_asg7pNWj_)fv$z=Ti#Xg~H74glB*7!)Z!BMimF)bD^0ozTjy&04xJ)yY zdfJ1DUvh$qbIt|cypsf2v<}tmrO%aA=-y^9n-#jq*F|-AC|0&5%k&;U@pUyHOT715 zGR55B09u9v4!gp&Z~2nS#O`n4KXuByu&V1bctDue-^e~01=k#o)2HG#vx%wqw^jBp z`=#nD>A^~xkp5iraaD8kV7}JkVwc%_${!}oR(G>>gN&Rq2HGI2O^?SN(r2%O`rF^r zw-(}u9;_J}qNU-mMGi_PC`}aKPC}Zbq+p2HDB%>xLz1O5P7G?Y;Z~H@oH+rNTCGWM zxPIHDy(SYQa^LMlorUw*VbzArW!hC zwDuoz5s(eH6Nv&3hw;apyVc=5Z;pW`t9YX7Ijqo0egP7*+iJ0V(*B>C5qDfx4T>&<7 zcD;*R5DaF-@LhF~y1C$)Djw1YKd);@vl=t1NexlA+>+mGUuJKakO_dodUWh^l=!#< zz%By?DMUSI-U4b?MdaNbu1gC!kfkv1cIBpWug}Uz1!=h2AcuWh;hc6$n+@j$TpIz& zoHYVS`2az@s)2iB>i9} zn$S#Le4mtb)i`tmr_BLE$_B#Zx(h$5iP1oHf9)MLrHYA&1(P4Vsalzy_xH|;+bAt# zv9o{HpvZltqL4ME9|n;K0Am5mKEj*5RsTU^OjCQxLA#oelnQ))V%4$;Z5T_khf@i$ zSyzxJ@rW|#VEEDPet7@@Ca%yJy8(|5l<_l3eI~@2ghf+EexAU^HhAROCjq8Y(Npk^ z0dC(;NKc`Ms7V|+J~vtjEv|rR7|(@uu6_&*X74XI#2_?rAzWCeMRz+^&!EBpC#H^9 zK+&c&GH_FuLx`C!(xAw1r^#YTHf2oFyceHZh#-SvI-)umHOHSQ+;K`TL5 zxD?D$5==hJb9TFlT-m%PFI`rLVdOemI+A*fSq2{UW6aLibn}s-vuca_BxscZ) z2hG;GHmvd|2p82Mg)QCl2@R|v|JbJjk?5yL34Wdzq!aR=;!n`+g66wts-*c&#Y zUtf`2t01af(B+etML6uI`B1+)TpQR|`J~v>Bmp34koa#ESDg6>O>0?k4 z^tw3RK9HRO2;9+o8ES%KHvHPFazp5DL1PYjXZvsTeZ7I9xFbt-Cjr<+ z07MKd`vgz^R$U}#IC~ihCvIP`!0{lpuln0%?4+yc{ZYDruXt_qbVx~^T(om*fLkS^ z=147fQ^K>Mo_8@ifN0ig-+}j+cP7M*d8F?z1o6){L;4ZxjL4!nj zBUe?z#}9KDJeQLKtkBZ@BFReZYJYgY1XCrsCh+Z#RXiGR&V}I-EK|9u2CJNL1{uBT zVHNVSw-7tS(d-j(K?*&u(C^9Cu%gpIh^NUOzO^U-h%Gksg?;049lBOq9v~%M7kU`a zZ?xIv?+=|N&LdK~n2+F?moFKWbwtSoNX^_4Qc~-S9lf2C*-8Jo9OoMQ4ayvHFBKjlF_QutmOaolOkX!Powwyza2msVDE<@(VZ zVmS;uk@-*r)*f;)>n`gIP@R&+^I7=i`W(3}3HwG@fXkLHZf3rUr+tldK6i&VnrD_S z((G?hVn&nxQzPt01G&iGZ2?Zzv|$CD2xlq7#mj z$QV|@EHf5SKfd8Ucf}F8K2s_&|@V*2b4*15J8PKOFP| zvdjOh@VI5WsQV@ne|y%hsOKvk;pTAsjVQh3b=iiWGep!Ma%5YMN*-IT5~2U3sWhX; zO{ZeGdONdQ7xCt@u{fb=l|3psI!)E(p-d|2ixWJ>Sa;UAkR2;Pa?E>n-!Kbq$1N71Zcu;p38EthW31A>FKM%j&Sy@51Q1Y(P0=;-b?I5Q!I7fY=*3Iy`M;k8c` zTitLFDK?v7?^j&#zHyK51(Pi9N(cgx5woO?^RllXr~t4jD5}@>E0=W`2PAm4_c3>Q z?LBu45%FYDdcU6jHhz2UQ3-)jxkYy~-f$DJU~Y<^KfGRs@Kk*AFL+b^4VZGqr(8}6 ztWV(13%lLz>3}k}hwnPB7h|c9irQtElv+SB?hk=Xb;zhOQVmsS~F z!@V$E_fq1VBc7R-xtMSE22=-4L#{o*tKUyVP`uwsw-miNyadXgdr^<+PQV+|_Cx%{ zbgt6#%fyE;MHgR4(94w&UFE>Y@uRJ-=;_~b?)AM8#_{!zshJAigu^;+DTwXCCD#9l z;^w)NB2Gs1)7|{ev{V8TRk=)4?9r4QJsoY0-D64h zMSWbMNx;e9f~OY{_2VusF6|e6)&1_|1w>F6$QqXYtP3wk*v-h7B51b9UBz&XNk25t ztIT(=O=^z2B{t{a%qh{~NQdRPP_ri3^AjFq)|PN2JwZt@?B*(gp^s9DA;=E=%>J8? zE0NT&ww_-mQO|JBYdw(yYEgM4GWQK?YfA~20gCz*)0G?CY z!h**m2Fk2@(HmE14=s%k&!>VR?U`Bv5rAQ8t&b6|F>oPmcdP*t*H?TMy&5Wi5w5sK7R7|Dkf^4D*}x2VKV#oA z`Xa%A(%?2nviGO=+Ps%wi)J3=E3cJI8_4h&i+CoW$qVFdM=6cwlh+0{wM9i?8NE%I{ zx(Vj%ida%0L6x|q!LzJ43-UU3cc=i6i}Gl7-FfOK{O!jI1cs0EiEZuvgM}z{tI_xj z$(nLT1d-e@N2y%tYhF(NnY|ZP-n2VX<;}i;CZkMS8qT5{JX86LRIC=;dLxTi^Lj14 z`*L7@`W!XEST+BfVSPynBHuug(3y*NI>I zF{X9HMCft^*oda_F2}w5qL}G=Vy$!K0Rac|K2Nb_xSJ?2w7tR~OAO3$uF;77Eh~Mr zA80Kd@Kck+8wij$C4CqhFpdI+b>{NN47R9|0iisfo^3KZs*z9K$~=I*ne_YoxefQY%}ZF@ReSriMp-WKW>TF2ee zmO=Ku0ghM7nO4wBi#sklZw^Xn9+DR^C6_orwR$TkZlZizJZ3ff{wFgw5#`5(Ip}Moq2*tzSd;id-!#>-50W44TC(a2#vPww|-@`UpRpPB^QL^*5$$)GK8& zi5s%z*M&~y^SK=2Z!T7eIa+AgNJ~~(!>>?qeD~mR-c!4F2x^X!KEB0x6aSo!x}x1% z1Vq+d)BHVgAa=W%dZNTj4a!Vi_3*x0O~42zoIPPynIuQyjs;y61x0>5j{5k~kh6|) zO=i5o-mM^)|tS&%vb-00I8vs49P}^iWTSsrFlSw zpYBi`CM(FWl0(RSe-u7nuZ6qv!daMZF$LyH+zlpu}%7yUuhxx=qC8=BKoXC_5#h2d4uWk!VUnc-k>!g0n z&5M=IR?}bni-9B|a#WMZ6jc4!UiTkU`K_67f-hNM5{4|$q6U9DLi>DZuP?q|ah|3G zFON|@-4@I)vesiVUbnL*&xxo!?<>M7bPHNPN;Vj~C3_e3^oG87gLFO4F_GVI?$%;AyA{OE&K!z$7et$E=SFxWwV9wHe$ zP>(VO<~@3par0)6<#7TR3%1iAnoySRRzl(OXQ-hD*w&sz&*dj~v<`!3L%z3+!uB1U zfrvs50Tg97KL{P~YF`dW^ctR=Zg@cYC*hwSSQ2V>h5+M&Hf@PE;kOSr_^4?ze<*Q* z#KErX-3SBz>+Wx&3bFSPdi@#I#l5VXOsS42d|PnOiGI#e30rbOR)@wfGiZPA;S7$y zKC-VjAP<%0vUd7>Up|xzrqZHG+Ra)Nz}UaMA=xie{V|*EK$kifs%5%~S+#*Zdt~?% z6bLLls1(hHX0M>0i0Xv_@}tD{xQ(TaU8G~&mU879!+JY_Fl+x7PtGZ6E8IbvNXd%C zW*0v{N$Nv6SN=q>Pp9C*0MKu`em49{5KoAYnVg~G4+{m&c76O_AT4I#Vz%I4jh$vP zLK?)c8-M(kLvl9frJgW6_!H9E8CkqQ#!7&te+ZSm>il7TtV=}Id7Ti%(I8&%%m|G@ zdJ_S5r3BEDmvdo9HKM@m4u=QDwHIc5ElC%`#gYw#t#RL=0Acw*xq3E)1S;Y>&Ce*U zsr9kmMj4*|#!=Z=qL#yWa&hNBfRGCxt%@VJMEu1@eV1ViDU;i_T<5BQMD;ib9(4T$ z3Yakd>6Hii%aKw&YHHJEhVrM7K1!HhN?OCQ%DtsxxLiS19X*MCGfIyCrL8XrhYu)p zYjdH|7DDs3LasjT>_3t7KZNhmk=u@NqR_ro%xOA{ACk3xaY{QTyXC$a-EmQWLJveD zyTz%sBS9n+0Kwe?17u{5=#};)z?B{p8&a5t9M|)im zlEoVkIL>1q82f*DF`z+G=z3j8$Zg9x5+2=%GkUKnV=OFS z2i^{nb3E04Zy65C==MULFWyAtBtlM61U1h|SDYX1{gIdFN$(o4+`_F9Zzli+T zYxRhb3xI5hWD`Ed{BukBe+1W;`b*7HGUO1#|60p`7o1@hYDR#4)-3dc_-5*E3pGUr2?!a)) zvd1^N|M2&Jf6m`;{71$9dY}xO{yF`>;ZNV7yb?ii#Amz{GtJij9p5HlSOtJL`>JW= z2kGA*`mb+N7XAXIh56yB-~Y{%|Jg@rw!c8B3Dd||@?T5@|JA+8SOK6!_HbW&_wNY) zI|=|wziozA^!^b!P!)i+AVSo<|cnnUL-#a;5KSYkcC-!N!bTXau!cYnDRpVI;9}B9N=`HL; z7eB5A^WG_8?8rNi{5zj^_bX@cLKr?+ypky~FEbqFy!8%ldc=F+@tZQPFB4u$dZPNe z5B)1&C*E<)#nZCv70R)e-$~;cMq8nfjF_b*mFZS?a6zqpnU9k$eH8*&ck90AqS{O>s4_;8^6kK5Bh6ECaJ zmnehJmjTu-yFA6x1G$@{e3V^2@An^qE8IM!#1Eg+#wLYwn5#@2J`a&RAgeE664~zL zC-t<*DZrq}vq}S=hqx*^kwe2&l%tz3oQI`*G*(haAS%iBdR%i?F<)N#qWzw&2+MXr zqffkk6`V%bUU1)4euB^2!G(l8DarM6`GIHBk-?MLK zfupKqfyRQ##Xx8n`K?*nUL9|VdvmPcI6b2gxLmr%y-h-~^=SFP>f*wl97eh z&e6(7e-FRv=DT-n&*QDs;my&|2%Z&_M@3j$x;uu>)6KJ<6pOJg3h3&z7E}hJ^SY(& z!`Y9~udRW5aK*vJ80Q^hjvYzn>!2 zYI=71)WWrYD^V_n&`ibXk1%bit>ECU$iZeyI(9Smz>Qf_W1dT8=iOe5ODF=FzoZxe zAFrsP*`|NKZ9-Qry$1G7T>wTfS`TK2FIF>#LY%hs)SV>=%8-Tkym z*=Lc%nMVffF_Dq)V0xN-E!DE}l{vI+3qqk>MM#;0Q>BIAQ?NBe2KjMiGq^lgHw%Lp zpdN^u4SF!*u?TA+lYztGNG<0xC{aj1+`dTn gHa5D?`^f0^D)(c(OizMN8C9Rw(yZRSLjsH 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%0AKSB|mV0)vF_p6XU3b75F9R_yzJ&&44-dqb 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 OAn&oHEqoPadEHyLa3XHq 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 B%n<+p diff --git a/docs/src/main/asciidoc/images/zipkin-ui.png b/docs/src/main/asciidoc/images/zipkin-ui.png index 34b0215b791c9611062eeb72d383954cd29c7868..6bfe38e67a815f7f3ecc8aedb0b67e3e366dc25d 100644 GIT binary patch literal 111015 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 literal 142506 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%0D%J3* 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�wPAy>EkjefwLFh45%apZAReu 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 Date: Mon, 19 Dec 2016 14:59:15 +0100 Subject: [PATCH 170/235] Added video with Sleuth & Zipkin --- README.adoc | 8 ++++++++ docs/src/main/asciidoc/intro.adoc | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/README.adoc b/README.adoc index 5a6fb78bc..18fd4ef5c 100644 --- a/README.adoc +++ b/README.adoc @@ -501,6 +501,14 @@ public class ZipkinStreamServerApplication { } ---- +== Additional resources + +*Marcin Grzejszczak talking about Spring Cloud Sleuth and Zipkin* + +video::eQV71Mw1u1c[youtube] + +https://www.youtube.com/watch?v=eQV71Mw1u1c[click here to see the video] + == Features * Adds trace and span ids to the Slf4J MDC, so you can extract all the logs from a given trace or span in a log aggregator. Example logs: diff --git a/docs/src/main/asciidoc/intro.adoc b/docs/src/main/asciidoc/intro.adoc index 367cb0bd4..df79f5900 100644 --- a/docs/src/main/asciidoc/intro.adoc +++ b/docs/src/main/asciidoc/intro.adoc @@ -405,3 +405,11 @@ and then just annotate your main class with `@EnableZipkinStreamServer` annotati ---- include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/master/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/src/main/java/example/ZipkinStreamServerApplication.java[] ---- + +== Additional resources + +*Marcin Grzejszczak talking about Spring Cloud Sleuth and Zipkin* + +video::eQV71Mw1u1c[youtube] + +https://www.youtube.com/watch?v=eQV71Mw1u1c[click here to see the video] From e96076b624018925d43a8fb9778865d1de49aad6 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Mon, 19 Dec 2016 15:02:23 +0100 Subject: [PATCH 171/235] Added video with Sleuth & Zipkin --- README.adoc | 8 ++++++++ docs/src/main/asciidoc/intro.adoc | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/README.adoc b/README.adoc index 21336698d..5e90286a3 100644 --- a/README.adoc +++ b/README.adoc @@ -501,6 +501,14 @@ public class ZipkinStreamServerApplication { } ---- +== Additional resources + +*Marcin Grzejszczak talking about Spring Cloud Sleuth and Zipkin* + +video::eQV71Mw1u1c[youtube] + +https://www.youtube.com/watch?v=eQV71Mw1u1c[click here to see the video] + == Features * Adds trace and span ids to the Slf4J MDC, so you can extract all the logs from a given trace or span in a log aggregator. Example logs: diff --git a/docs/src/main/asciidoc/intro.adoc b/docs/src/main/asciidoc/intro.adoc index af39de6c6..149fad061 100644 --- a/docs/src/main/asciidoc/intro.adoc +++ b/docs/src/main/asciidoc/intro.adoc @@ -405,3 +405,11 @@ and then just annotate your main class with `@EnableZipkinStreamServer` annotati ---- include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/master/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/src/main/java/example/ZipkinStreamServerApplication.java[] ---- + +== Additional resources + +*Marcin Grzejszczak talking about Spring Cloud Sleuth and Zipkin* + +video::eQV71Mw1u1c[youtube] + +https://www.youtube.com/watch?v=eQV71Mw1u1c[click here to see the video] From 362f270f5473c0715588119d1ac1a4d4621a9492 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Mon, 19 Dec 2016 16:52:13 +0100 Subject: [PATCH 172/235] Detaching async traces only if tracing is still in progress --- .../instrument/async/TraceContinuingCallable.java | 4 +++- .../hystrix/SleuthHystrixConcurrencyStrategy.java | 14 +++++++------- .../rxjava/SleuthRxJavaSchedulersHook.java | 2 +- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceContinuingCallable.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceContinuingCallable.java index 07fd05211..c3b48ef36 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceContinuingCallable.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceContinuingCallable.java @@ -43,6 +43,8 @@ public class TraceContinuingCallable extends TraceCallable implements Call @Override protected void close(Span span) { - getTracer().detach(span); + if (getTracer().isTracing()) { + getTracer().detach(span); + } } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/hystrix/SleuthHystrixConcurrencyStrategy.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/hystrix/SleuthHystrixConcurrencyStrategy.java index 544f43e0b..86b5486b7 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/hystrix/SleuthHystrixConcurrencyStrategy.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/hystrix/SleuthHystrixConcurrencyStrategy.java @@ -19,6 +19,12 @@ package org.springframework.cloud.sleuth.instrument.hystrix; import java.lang.invoke.MethodHandles; import java.util.concurrent.Callable; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.cloud.sleuth.Span; +import org.springframework.cloud.sleuth.TraceKeys; +import org.springframework.cloud.sleuth.Tracer; + import com.netflix.hystrix.strategy.HystrixPlugins; import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy; import com.netflix.hystrix.strategy.eventnotifier.HystrixEventNotifier; @@ -26,12 +32,6 @@ import com.netflix.hystrix.strategy.executionhook.HystrixCommandExecutionHook; import com.netflix.hystrix.strategy.metrics.HystrixMetricsPublisher; import com.netflix.hystrix.strategy.properties.HystrixPropertiesStrategy; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.TraceKeys; -import org.springframework.cloud.sleuth.Tracer; - /** * A {@link HystrixConcurrencyStrategy} that wraps a {@link Callable} in a * {@link Callable} that either starts a new span or continues one if the tracing was @@ -160,7 +160,7 @@ public class SleuthHystrixConcurrencyStrategy extends HystrixConcurrencyStrategy } this.tracer.close(span); } - else { + else if(this.tracer.isTracing()) { if (log.isDebugEnabled()) { log.debug("Detaching span since it was continued " + span); } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/rxjava/SleuthRxJavaSchedulersHook.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/rxjava/SleuthRxJavaSchedulersHook.java index 04cf31788..47811e5f3 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/rxjava/SleuthRxJavaSchedulersHook.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/rxjava/SleuthRxJavaSchedulersHook.java @@ -129,7 +129,7 @@ class SleuthRxJavaSchedulersHook extends RxJavaSchedulersHook { } finally { if (created) { this.tracer.close(span); - } else { + } else if (this.tracer.isTracing()) { this.tracer.detach(span); } } From 3b09f2789cd88653353c7d302fc2c4374756a8c3 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Mon, 19 Dec 2016 16:52:13 +0100 Subject: [PATCH 173/235] Detaching async traces only if tracing is still in progress --- .../instrument/async/TraceContinuingCallable.java | 4 +++- .../hystrix/SleuthHystrixConcurrencyStrategy.java | 14 +++++++------- .../rxjava/SleuthRxJavaSchedulersHook.java | 2 +- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceContinuingCallable.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceContinuingCallable.java index 07fd05211..c3b48ef36 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceContinuingCallable.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceContinuingCallable.java @@ -43,6 +43,8 @@ public class TraceContinuingCallable extends TraceCallable implements Call @Override protected void close(Span span) { - getTracer().detach(span); + if (getTracer().isTracing()) { + getTracer().detach(span); + } } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/hystrix/SleuthHystrixConcurrencyStrategy.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/hystrix/SleuthHystrixConcurrencyStrategy.java index 544f43e0b..86b5486b7 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/hystrix/SleuthHystrixConcurrencyStrategy.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/hystrix/SleuthHystrixConcurrencyStrategy.java @@ -19,6 +19,12 @@ package org.springframework.cloud.sleuth.instrument.hystrix; import java.lang.invoke.MethodHandles; import java.util.concurrent.Callable; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.cloud.sleuth.Span; +import org.springframework.cloud.sleuth.TraceKeys; +import org.springframework.cloud.sleuth.Tracer; + import com.netflix.hystrix.strategy.HystrixPlugins; import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy; import com.netflix.hystrix.strategy.eventnotifier.HystrixEventNotifier; @@ -26,12 +32,6 @@ import com.netflix.hystrix.strategy.executionhook.HystrixCommandExecutionHook; import com.netflix.hystrix.strategy.metrics.HystrixMetricsPublisher; import com.netflix.hystrix.strategy.properties.HystrixPropertiesStrategy; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.TraceKeys; -import org.springframework.cloud.sleuth.Tracer; - /** * A {@link HystrixConcurrencyStrategy} that wraps a {@link Callable} in a * {@link Callable} that either starts a new span or continues one if the tracing was @@ -160,7 +160,7 @@ public class SleuthHystrixConcurrencyStrategy extends HystrixConcurrencyStrategy } this.tracer.close(span); } - else { + else if(this.tracer.isTracing()) { if (log.isDebugEnabled()) { log.debug("Detaching span since it was continued " + span); } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/rxjava/SleuthRxJavaSchedulersHook.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/rxjava/SleuthRxJavaSchedulersHook.java index 04cf31788..47811e5f3 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/rxjava/SleuthRxJavaSchedulersHook.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/rxjava/SleuthRxJavaSchedulersHook.java @@ -129,7 +129,7 @@ class SleuthRxJavaSchedulersHook extends RxJavaSchedulersHook { } finally { if (created) { this.tracer.close(span); - } else { + } else if (this.tracer.isTracing()) { this.tracer.detach(span); } } From fa74bf8f275a1d1d642c939cad1fca5256463983 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Tue, 20 Dec 2016 15:56:29 +0100 Subject: [PATCH 174/235] Added a check for tracing in Trace Filter --- .../cloud/sleuth/instrument/web/TraceFilter.java | 5 +++-- .../cloud/sleuth/instrument/web/TraceHandlerInterceptor.java | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) 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 3dfb72be4..8a937b724 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 @@ -211,12 +211,13 @@ public class TraceFilter extends GenericFilterBean { } recordParentSpan(span); // in case of a response with exception status will close the span when exception dispatch is handled - if (httpStatusSuccessful(response)) { + // checking if tracing is in progress due to async / different order of view controller processing + if (httpStatusSuccessful(response) && this.tracer.isTracing()) { if (log.isDebugEnabled()) { log.debug("Closing the span " + span + " since the response was successful"); } this.tracer.close(span); - } else if (errorAlreadyHandled(request)) { + } else if (errorAlreadyHandled(request) && this.tracer.isTracing()) { if (log.isDebugEnabled()) { log.debug( "Won't detach the span " + span + " since error has already been handled"); diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptor.java index ff5a144f8..69d7fb5c9 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptor.java @@ -66,7 +66,7 @@ public class TraceHandlerInterceptor extends HandlerInterceptorAdapter { boolean continueSpan = getRootSpanFromAttribute(request) != null; Span span = continueSpan ? getRootSpanFromAttribute(request) : getTracer().createSpan(spanName); if (log.isDebugEnabled()) { - log.debug("Created new span " + span + " with name [" + spanName + "]"); + log.debug("Handling span " + span); } addClassMethodTag(handler, span); addClassNameTag(handler, span); From bbc246bb539d8aa9a203a434d23bb8063a1998ef Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Tue, 20 Dec 2016 15:56:29 +0100 Subject: [PATCH 175/235] Added a check for tracing in Trace Filter --- .../cloud/sleuth/instrument/web/TraceFilter.java | 5 +++-- .../cloud/sleuth/instrument/web/TraceHandlerInterceptor.java | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) 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 3dfb72be4..8a937b724 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 @@ -211,12 +211,13 @@ public class TraceFilter extends GenericFilterBean { } recordParentSpan(span); // in case of a response with exception status will close the span when exception dispatch is handled - if (httpStatusSuccessful(response)) { + // checking if tracing is in progress due to async / different order of view controller processing + if (httpStatusSuccessful(response) && this.tracer.isTracing()) { if (log.isDebugEnabled()) { log.debug("Closing the span " + span + " since the response was successful"); } this.tracer.close(span); - } else if (errorAlreadyHandled(request)) { + } else if (errorAlreadyHandled(request) && this.tracer.isTracing()) { if (log.isDebugEnabled()) { log.debug( "Won't detach the span " + span + " since error has already been handled"); diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptor.java index ff5a144f8..69d7fb5c9 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptor.java @@ -66,7 +66,7 @@ public class TraceHandlerInterceptor extends HandlerInterceptorAdapter { boolean continueSpan = getRootSpanFromAttribute(request) != null; Span span = continueSpan ? getRootSpanFromAttribute(request) : getTracer().createSpan(spanName); if (log.isDebugEnabled()) { - log.debug("Created new span " + span + " with name [" + spanName + "]"); + log.debug("Handling span " + span); } addClassMethodTag(handler, span); addClassNameTag(handler, span); From b0533f7a60bcf535a51699601c4e3baa7cbeb457 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Tue, 20 Dec 2016 18:13:57 +0100 Subject: [PATCH 176/235] Adding parent id to MDC without this change we couldn't reference parent id in the logs with this change we add the parent id to MDC context fixes #480 --- .../cloud/sleuth/log/Slf4jSpanLogger.java | 10 +++++++ .../cloud/sleuth/log/Slf4JSpanLoggerTest.java | 26 ++++++++++++++++++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/Slf4jSpanLogger.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/Slf4jSpanLogger.java index 08468ffbf..ecc1e41bd 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/Slf4jSpanLogger.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/Slf4jSpanLogger.java @@ -51,6 +51,7 @@ public class Slf4jSpanLogger implements SpanLogger { log("Starting span: {}", span); if (parent != null) { log("With parent: {}", parent); + MDC.put(Span.PARENT_ID_NAME, Span.idToHex(parent.getSpanId())); } } @@ -59,9 +60,16 @@ public class Slf4jSpanLogger implements SpanLogger { MDC.put(Span.SPAN_ID_NAME, Span.idToHex(span.getSpanId())); MDC.put(Span.TRACE_ID_NAME, span.traceIdString()); MDC.put(Span.SPAN_EXPORT_NAME, String.valueOf(span.isExportable())); + setParentIdIfPresent(span); log("Continued span: {}", span); } + private void setParentIdIfPresent(Span span) { + if (!span.getParents().isEmpty()) { + MDC.put(Span.PARENT_ID_NAME, Span.idToHex(span.getParents().get(0))); + } + } + @Override public void logStoppedSpan(Span parent, Span span) { if (span != null) { @@ -71,11 +79,13 @@ public class Slf4jSpanLogger implements SpanLogger { log("With parent: {}", parent); MDC.put(Span.SPAN_ID_NAME, Span.idToHex(parent.getSpanId())); MDC.put(Span.SPAN_EXPORT_NAME, String.valueOf(parent.isExportable())); + setParentIdIfPresent(parent); } else { MDC.remove(Span.SPAN_ID_NAME); MDC.remove(Span.SPAN_EXPORT_NAME); MDC.remove(Span.TRACE_ID_NAME); + MDC.remove(Span.PARENT_ID_NAME); } } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/log/Slf4JSpanLoggerTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/log/Slf4JSpanLoggerTest.java index 649ea1a9d..76c84d70d 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/log/Slf4JSpanLoggerTest.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/log/Slf4JSpanLoggerTest.java @@ -37,7 +37,7 @@ import static org.mockito.Mockito.times; public class Slf4JSpanLoggerTest { Span spanWithNameToBeExcluded = Span.builder().name("Hystrix").build(); - Span spanWithNameNotToBeExcluded = Span.builder().name("Aspect").build(); + Span spanWithNameNotToBeExcluded = Span.builder().name("Aspect").parent(3L).build(); String nameExcludingPattern = "^.*Hystrix.*$"; Logger log = Mockito.mock(Logger.class); Slf4jSpanLogger slf4JSpanLogger = new Slf4jSpanLogger(this.nameExcludingPattern, this.log); @@ -152,4 +152,28 @@ public class Slf4JSpanLoggerTest { then(this.log).should().trace(anyString(), anyList()); } + + @Test + public void should_set_mdc_entry_for_parent_when_starting() throws Exception { + this.slf4JSpanLogger.logStartedSpan(this.spanWithNameNotToBeExcluded, + this.spanWithNameNotToBeExcluded); + + assertThat(MDC.get(Span.PARENT_ID_NAME)).isEqualTo( + Span.idToHex(this.spanWithNameNotToBeExcluded.getSpanId())); + } + + @Test + public void should_set_mdc_entry_for_parent_when_continuing() throws Exception { + this.slf4JSpanLogger.logContinuedSpan(this.spanWithNameNotToBeExcluded); + + assertThat(MDC.get(Span.PARENT_ID_NAME)).isEqualTo(Span.idToHex(3L)); + } + + @Test + public void should_set_mdc_entry_for_parent_when_stopping() throws Exception { + this.slf4JSpanLogger.logStoppedSpan(this.spanWithNameNotToBeExcluded, + this.spanWithNameNotToBeExcluded); + + assertThat(MDC.get(Span.PARENT_ID_NAME)).isEqualTo(Span.idToHex(3L)); + } } \ No newline at end of file From a88731fd9bf3a3bb0893df706dc6481b2e36f938 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Tue, 20 Dec 2016 18:13:57 +0100 Subject: [PATCH 177/235] Adding parent id to MDC without this change we couldn't reference parent id in the logs with this change we add the parent id to MDC context fixes #480 --- .../cloud/sleuth/log/Slf4jSpanLogger.java | 10 +++++++ .../cloud/sleuth/log/Slf4JSpanLoggerTest.java | 26 ++++++++++++++++++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/Slf4jSpanLogger.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/Slf4jSpanLogger.java index 08468ffbf..ecc1e41bd 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/Slf4jSpanLogger.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/Slf4jSpanLogger.java @@ -51,6 +51,7 @@ public class Slf4jSpanLogger implements SpanLogger { log("Starting span: {}", span); if (parent != null) { log("With parent: {}", parent); + MDC.put(Span.PARENT_ID_NAME, Span.idToHex(parent.getSpanId())); } } @@ -59,9 +60,16 @@ public class Slf4jSpanLogger implements SpanLogger { MDC.put(Span.SPAN_ID_NAME, Span.idToHex(span.getSpanId())); MDC.put(Span.TRACE_ID_NAME, span.traceIdString()); MDC.put(Span.SPAN_EXPORT_NAME, String.valueOf(span.isExportable())); + setParentIdIfPresent(span); log("Continued span: {}", span); } + private void setParentIdIfPresent(Span span) { + if (!span.getParents().isEmpty()) { + MDC.put(Span.PARENT_ID_NAME, Span.idToHex(span.getParents().get(0))); + } + } + @Override public void logStoppedSpan(Span parent, Span span) { if (span != null) { @@ -71,11 +79,13 @@ public class Slf4jSpanLogger implements SpanLogger { log("With parent: {}", parent); MDC.put(Span.SPAN_ID_NAME, Span.idToHex(parent.getSpanId())); MDC.put(Span.SPAN_EXPORT_NAME, String.valueOf(parent.isExportable())); + setParentIdIfPresent(parent); } else { MDC.remove(Span.SPAN_ID_NAME); MDC.remove(Span.SPAN_EXPORT_NAME); MDC.remove(Span.TRACE_ID_NAME); + MDC.remove(Span.PARENT_ID_NAME); } } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/log/Slf4JSpanLoggerTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/log/Slf4JSpanLoggerTest.java index 649ea1a9d..76c84d70d 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/log/Slf4JSpanLoggerTest.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/log/Slf4JSpanLoggerTest.java @@ -37,7 +37,7 @@ import static org.mockito.Mockito.times; public class Slf4JSpanLoggerTest { Span spanWithNameToBeExcluded = Span.builder().name("Hystrix").build(); - Span spanWithNameNotToBeExcluded = Span.builder().name("Aspect").build(); + Span spanWithNameNotToBeExcluded = Span.builder().name("Aspect").parent(3L).build(); String nameExcludingPattern = "^.*Hystrix.*$"; Logger log = Mockito.mock(Logger.class); Slf4jSpanLogger slf4JSpanLogger = new Slf4jSpanLogger(this.nameExcludingPattern, this.log); @@ -152,4 +152,28 @@ public class Slf4JSpanLoggerTest { then(this.log).should().trace(anyString(), anyList()); } + + @Test + public void should_set_mdc_entry_for_parent_when_starting() throws Exception { + this.slf4JSpanLogger.logStartedSpan(this.spanWithNameNotToBeExcluded, + this.spanWithNameNotToBeExcluded); + + assertThat(MDC.get(Span.PARENT_ID_NAME)).isEqualTo( + Span.idToHex(this.spanWithNameNotToBeExcluded.getSpanId())); + } + + @Test + public void should_set_mdc_entry_for_parent_when_continuing() throws Exception { + this.slf4JSpanLogger.logContinuedSpan(this.spanWithNameNotToBeExcluded); + + assertThat(MDC.get(Span.PARENT_ID_NAME)).isEqualTo(Span.idToHex(3L)); + } + + @Test + public void should_set_mdc_entry_for_parent_when_stopping() throws Exception { + this.slf4JSpanLogger.logStoppedSpan(this.spanWithNameNotToBeExcluded, + this.spanWithNameNotToBeExcluded); + + assertThat(MDC.get(Span.PARENT_ID_NAME)).isEqualTo(Span.idToHex(3L)); + } } \ No newline at end of file From 6c99c73bd7281a46ea39c2de75af25bd887b7e80 Mon Sep 17 00:00:00 2001 From: Toshiaki Maki Date: Tue, 27 Dec 2016 16:50:34 +0900 Subject: [PATCH 178/235] Add tip in case of using spring-cloud-sleuth-stream and spring-cloud-netflix-hystrix-stream (#482) * Add tip in case of spring-cloud-sleuth-stream and spring-cloud-netflix-hystrix-stream --- docs/src/main/asciidoc/spring-cloud-sleuth.adoc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc index 984ed90bf..3812c750f 100644 --- a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc +++ b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc @@ -534,6 +534,8 @@ If you want to skip Span creation for some `@Scheduled` annotated classes you ca `spring.sleuth.scheduled.skipPattern` with a regular expression that will match the fully qualified name of the `@Scheduled` annotated class. +TIP: If you are using `spring-cloud-sleuth-stream` and `spring-cloud-netflix-hystrix-stream` together, Span will be created for each Hystrix metrics and sent to Zipkin. This may be annoying. You can prevent this by setting `spring.sleuth.scheduled.skipPattern=org.springframework.cloud.netflix.hystrix.stream.HystrixStreamTask` + ==== Executor, ExecutorService and ScheduledExecutorService We're providing `LazyTraceExecutor`, `TraceableExecutorService` and `TraceableScheduledExecutorService`. Those implementations From f7fea826f3253ef51563b2e386e994b87200ce33 Mon Sep 17 00:00:00 2001 From: Toshiaki Maki Date: Tue, 27 Dec 2016 16:50:34 +0900 Subject: [PATCH 179/235] Add tip in case of using spring-cloud-sleuth-stream and spring-cloud-netflix-hystrix-stream (#482) * Add tip in case of spring-cloud-sleuth-stream and spring-cloud-netflix-hystrix-stream --- docs/src/main/asciidoc/spring-cloud-sleuth.adoc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc index 984ed90bf..3812c750f 100644 --- a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc +++ b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc @@ -534,6 +534,8 @@ If you want to skip Span creation for some `@Scheduled` annotated classes you ca `spring.sleuth.scheduled.skipPattern` with a regular expression that will match the fully qualified name of the `@Scheduled` annotated class. +TIP: If you are using `spring-cloud-sleuth-stream` and `spring-cloud-netflix-hystrix-stream` together, Span will be created for each Hystrix metrics and sent to Zipkin. This may be annoying. You can prevent this by setting `spring.sleuth.scheduled.skipPattern=org.springframework.cloud.netflix.hystrix.stream.HystrixStreamTask` + ==== Executor, ExecutorService and ScheduledExecutorService We're providing `LazyTraceExecutor`, `TraceableExecutorService` and `TraceableScheduledExecutorService`. Those implementations From b5b5a4761b1d42add1363a26d7e307728d05813e Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Thu, 29 Dec 2016 13:37:55 +0100 Subject: [PATCH 180/235] Explained TraceAsyncRestTemplate bean creation fixes #484 --- docs/src/main/asciidoc/spring-cloud-sleuth.adoc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc index 3812c750f..fb2be678a 100644 --- a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc +++ b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc @@ -486,6 +486,11 @@ If you create a `RestTemplate` instance with a `new` keyword then the instrument ==== Asynchronous Rest Template +IMPORTANT: A traced version of an `AsyncRestTemplate` bean is registered for you out of the box. If you +have your own bean you have to wrap it in a `TraceAsyncRestTemplate` representation. The best solution +is to only customize the `ClientHttpRequestFactory` and / or `AsyncClientHttpRequestFactory`. +*If you have your own `AsyncRestTemplate` and you don't wrap it your calls WILL NOT GET TRACED*. + Custom instrumentation is set to create and close Spans upon sending and receiving requests. You can customize the `ClientHttpRequestFactory` and the `AsyncClientHttpRequestFactory` by registering your beans. Remember to use tracing compatible implementations (e.g. don't forget to wrap `ThreadPoolTaskScheduler` in a `TraceAsyncListenableTaskExecutor`). Example of custom request factories: From c968aae9e7daab1318ed499b4fdc6a08f836d0f9 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Thu, 29 Dec 2016 13:37:55 +0100 Subject: [PATCH 181/235] Explained TraceAsyncRestTemplate bean creation fixes #484 --- docs/src/main/asciidoc/spring-cloud-sleuth.adoc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc index 3812c750f..fb2be678a 100644 --- a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc +++ b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc @@ -486,6 +486,11 @@ If you create a `RestTemplate` instance with a `new` keyword then the instrument ==== Asynchronous Rest Template +IMPORTANT: A traced version of an `AsyncRestTemplate` bean is registered for you out of the box. If you +have your own bean you have to wrap it in a `TraceAsyncRestTemplate` representation. The best solution +is to only customize the `ClientHttpRequestFactory` and / or `AsyncClientHttpRequestFactory`. +*If you have your own `AsyncRestTemplate` and you don't wrap it your calls WILL NOT GET TRACED*. + Custom instrumentation is set to create and close Spans upon sending and receiving requests. You can customize the `ClientHttpRequestFactory` and the `AsyncClientHttpRequestFactory` by registering your beans. Remember to use tracing compatible implementations (e.g. don't forget to wrap `ThreadPoolTaskScheduler` in a `TraceAsyncListenableTaskExecutor`). Example of custom request factories: From 31531f4c84215b324740580a95c5197e2f17086d Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Fri, 30 Dec 2016 11:35:55 +0100 Subject: [PATCH 182/235] Added checks to close / detach only if tracing without this change during asynchronous communication some components are trying to detach or close spans that were aready detached. This leads to exceptions utils warnings and spans were not closed. with this change we're adding additional checks to ensure that we're tracing fixes #447 --- .../org/springframework/cloud/sleuth/TraceRunnable.java | 9 ++++++++- .../instrument/messaging/TraceChannelInterceptor.java | 7 +++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/TraceRunnable.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/TraceRunnable.java index cbf838713..688269354 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/TraceRunnable.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/TraceRunnable.java @@ -74,6 +74,10 @@ public class TraceRunnable implements Runnable { } protected void close(Span span) { + // race conditions - check #447 + if (!this.tracer.isTracing()) { + this.tracer.continueSpan(span); + } this.tracer.close(span); } @@ -82,7 +86,10 @@ public class TraceRunnable implements Runnable { } protected Span detachSpan(Span span) { - return this.tracer.detach(span); + if (this.tracer.isTracing()) { + return this.tracer.detach(span); + } + return span; } public Tracer getTracer() { diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceChannelInterceptor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceChannelInterceptor.java index 171ab01ad..4739851f2 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 @@ -109,7 +109,7 @@ public class TraceChannelInterceptor extends AbstractTraceChannelInterceptor { public Message beforeHandle(Message message, MessageChannel channel, MessageHandler handler) { Span spanFromHeader = getSpanFromHeader(message); - if (spanFromHeader!= null) { + if (spanFromHeader != null) { spanFromHeader.logEvent(Span.SERVER_RECV); } getTracer().continueSpan(spanFromHeader); @@ -124,7 +124,10 @@ public class TraceChannelInterceptor extends AbstractTraceChannelInterceptor { spanFromHeader.logEvent(Span.SERVER_SEND); addErrorTag(ex); } - getTracer().detach(spanFromHeader); + // related to #447 + if (getTracer().isTracing()) { + getTracer().detach(spanFromHeader); + } } private void addErrorTag(Exception ex) { From ab77e8ee7430a3bac6c004cf54cc6c0a072091f4 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Fri, 30 Dec 2016 11:35:55 +0100 Subject: [PATCH 183/235] Added checks to close / detach only if tracing without this change during asynchronous communication some components are trying to detach or close spans that were aready detached. This leads to exceptions utils warnings and spans were not closed. with this change we're adding additional checks to ensure that we're tracing fixes #447 --- .../org/springframework/cloud/sleuth/TraceRunnable.java | 9 ++++++++- .../instrument/messaging/TraceChannelInterceptor.java | 7 +++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/TraceRunnable.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/TraceRunnable.java index cbf838713..688269354 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/TraceRunnable.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/TraceRunnable.java @@ -74,6 +74,10 @@ public class TraceRunnable implements Runnable { } protected void close(Span span) { + // race conditions - check #447 + if (!this.tracer.isTracing()) { + this.tracer.continueSpan(span); + } this.tracer.close(span); } @@ -82,7 +86,10 @@ public class TraceRunnable implements Runnable { } protected Span detachSpan(Span span) { - return this.tracer.detach(span); + if (this.tracer.isTracing()) { + return this.tracer.detach(span); + } + return span; } public Tracer getTracer() { diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceChannelInterceptor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceChannelInterceptor.java index 171ab01ad..4739851f2 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 @@ -109,7 +109,7 @@ public class TraceChannelInterceptor extends AbstractTraceChannelInterceptor { public Message beforeHandle(Message message, MessageChannel channel, MessageHandler handler) { Span spanFromHeader = getSpanFromHeader(message); - if (spanFromHeader!= null) { + if (spanFromHeader != null) { spanFromHeader.logEvent(Span.SERVER_RECV); } getTracer().continueSpan(spanFromHeader); @@ -124,7 +124,10 @@ public class TraceChannelInterceptor extends AbstractTraceChannelInterceptor { spanFromHeader.logEvent(Span.SERVER_SEND); addErrorTag(ex); } - getTracer().detach(spanFromHeader); + // related to #447 + if (getTracer().isTracing()) { + getTracer().detach(spanFromHeader); + } } private void addErrorTag(Exception ex) { From 6aa7328cd9a3e2eb6ad39122ee7ceaf66fea7962 Mon Sep 17 00:00:00 2001 From: Arthur Gavlyukovskiy Date: Fri, 30 Dec 2016 13:35:46 +0200 Subject: [PATCH 184/235] Created @ConfigurationProperties classes and updated additional metadata (#486) * Created @ConfigurationProperties classes and updated additional metadata --- .../sleuth/autoconfig/SleuthProperties.java | 10 ++ .../rxjava/RxJavaAutoConfiguration.java | 28 +--- .../SleuthRxJavaSchedulersProperties.java | 51 +++++++ .../SleuthSchedulingProperties.java | 39 ++++++ .../TraceSchedulingAutoConfiguration.java | 18 +-- .../instrument/web/SleuthWebProperties.java | 126 ++++++++++++++++++ .../sleuth/instrument/web/TraceFilter.java | 9 +- .../web/TraceWebAutoConfiguration.java | 35 ++--- .../log/SleuthLogAutoConfiguration.java | 12 +- .../sleuth/log/SleuthSlf4jProperties.java | 39 ++++++ .../sleuth/metric/SleuthMetricProperties.java | 9 +- ...itional-spring-configuration-metadata.json | 42 ++++++ .../web/SkipPatternProviderConfigTest.java | 23 ++-- .../TraceFilterMockChainIntegrationTests.java | 4 +- .../instrument/web/TraceFilterTests.java | 2 +- 15 files changed, 364 insertions(+), 83 deletions(-) create mode 100644 spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/rxjava/SleuthRxJavaSchedulersProperties.java create mode 100644 spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/scheduling/SleuthSchedulingProperties.java create mode 100644 spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/SleuthWebProperties.java create mode 100644 spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/SleuthSlf4jProperties.java 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 57036095a..45d790b2b 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 @@ -25,9 +25,19 @@ import org.springframework.boot.context.properties.ConfigurationProperties; */ @ConfigurationProperties("spring.sleuth") public class SleuthProperties { + + private boolean enabled = true; /** When true, generate 128-bit trace IDs instead of 64-bit ones. */ private boolean traceId128 = false; + public boolean isEnabled() { + return this.enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + public boolean isTraceId128() { return this.traceId128; } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/rxjava/RxJavaAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/rxjava/RxJavaAutoConfiguration.java index 2056acf3c..286dc4753 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/rxjava/RxJavaAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/rxjava/RxJavaAutoConfiguration.java @@ -1,20 +1,17 @@ package org.springframework.cloud.sleuth.instrument.rxjava; -import java.util.ArrayList; import java.util.Arrays; -import java.util.List; -import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.sleuth.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.util.StringUtils; import rx.plugins.RxJavaSchedulersHook; @@ -30,28 +27,13 @@ import rx.plugins.RxJavaSchedulersHook; @ConditionalOnBean(Tracer.class) @ConditionalOnClass(RxJavaSchedulersHook.class) @ConditionalOnProperty(value = "spring.sleuth.rxjava.schedulers.hook.enabled", matchIfMissing = true) +@EnableConfigurationProperties(SleuthRxJavaSchedulersProperties.class) public class RxJavaAutoConfiguration { - /** - * Contains a list of thread names for which spans will not be sampled. Extracted to a constant - * for readability reasons. - */ - private static final List DEFAULT_IGNORED_THREADS = Arrays.asList("HystrixMetricPoller", "^RxComputation.*$"); - @Bean SleuthRxJavaSchedulersHook sleuthRxJavaSchedulersHook(Tracer tracer, TraceKeys traceKeys, - // Comma separated list of thread name matchers - @Value("${spring.sleuth.rxjava.schedulers.ignoredthreads:}") String threadsToSample) { - return new SleuthRxJavaSchedulersHook(tracer, traceKeys, threads(threadsToSample)); - } - - private List threads(String threadsToSample) { - List threads = new ArrayList<>(); - if (StringUtils.isEmpty(threadsToSample)) { - threads.addAll(DEFAULT_IGNORED_THREADS); - } else { - threads.addAll(Arrays.asList(threadsToSample.split(","))); - } - return threads; + SleuthRxJavaSchedulersProperties sleuthRxJavaSchedulersProperties) { + return new SleuthRxJavaSchedulersHook(tracer, traceKeys, + Arrays.asList(sleuthRxJavaSchedulersProperties.getIgnoredthreads())); } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/rxjava/SleuthRxJavaSchedulersProperties.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/rxjava/SleuthRxJavaSchedulersProperties.java new file mode 100644 index 000000000..568dbf099 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/rxjava/SleuthRxJavaSchedulersProperties.java @@ -0,0 +1,51 @@ +package org.springframework.cloud.sleuth.instrument.rxjava; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Configuration properties for RxJava tracing + * + * @author Arthur Gavlyukovskiy + * @since 1.0.12 + */ +@ConfigurationProperties("spring.sleuth.rxjava.schedulers") +public class SleuthRxJavaSchedulersProperties { + + /** + * Thread names for which spans will not be sampled. + */ + private String[] ignoredthreads = { "HystrixMetricPoller", "^RxComputation.*$" }; + private Hook hook; + + public String[] getIgnoredthreads() { + return this.ignoredthreads; + } + + public void setIgnoredthreads(String[] ignoredthreads) { + this.ignoredthreads = ignoredthreads; + } + + public Hook getHook() { + return this.hook; + } + + public void setHook(Hook hook) { + this.hook = hook; + } + + private static class Hook { + + /** + * Enable support for RxJava via RxJavaSchedulersHook. + */ + private boolean enabled = true; + + public boolean isEnabled() { + return this.enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + } +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/scheduling/SleuthSchedulingProperties.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/scheduling/SleuthSchedulingProperties.java new file mode 100644 index 000000000..cd44f1b36 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/scheduling/SleuthSchedulingProperties.java @@ -0,0 +1,39 @@ +package org.springframework.cloud.sleuth.instrument.scheduling; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Configuration properties for {@link org.springframework.scheduling.annotation.Scheduled} tracing + * + * @author Arthur Gavlyukovskiy + * @since 1.0.12 + */ +@ConfigurationProperties("spring.sleuth.scheduled") +public class SleuthSchedulingProperties { + + /** + * Enable tracing for {@link org.springframework.scheduling.annotation.Scheduled}. + */ + private boolean enabled = true; + + /** + * Pattern for the fully qualified name of a class that should be skipped. + */ + private String skipPattern = ""; + + public boolean isEnabled() { + return this.enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public String getSkipPattern() { + return this.skipPattern; + } + + public void setSkipPattern(String skipPattern) { + this.skipPattern = skipPattern; + } +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/scheduling/TraceSchedulingAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/scheduling/TraceSchedulingAutoConfiguration.java index 40da08e1a..a840eaae9 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/scheduling/TraceSchedulingAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/scheduling/TraceSchedulingAutoConfiguration.java @@ -16,13 +16,11 @@ package org.springframework.cloud.sleuth.instrument.scheduling; -import java.util.regex.Pattern; - -import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.sleuth.TraceKeys; import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration; @@ -30,6 +28,8 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; +import java.util.regex.Pattern; + /** * Registers beans related to task scheduling. * @@ -44,17 +44,13 @@ import org.springframework.context.annotation.EnableAspectJAutoProxy; @ConditionalOnProperty(value = "spring.sleuth.scheduled.enabled", matchIfMissing = true) @ConditionalOnBean(Tracer.class) @AutoConfigureAfter(TraceAutoConfiguration.class) +@EnableConfigurationProperties(SleuthSchedulingProperties.class) public class TraceSchedulingAutoConfiguration { - /** - * Pattern for the fully qualified name of a class that should be skipped - */ - private @Value("${spring.sleuth.scheduled.skipPattern:}") String skipPattern; - @ConditionalOnClass(name = "org.aspectj.lang.ProceedingJoinPoint") @Bean - public TraceSchedulingAspect traceSchedulingAspect(Tracer tracer, TraceKeys traceKeys) { - return new TraceSchedulingAspect(tracer, traceKeys, Pattern.compile(this.skipPattern)); + public TraceSchedulingAspect traceSchedulingAspect(Tracer tracer, TraceKeys traceKeys, + SleuthSchedulingProperties sleuthSchedulingProperties) { + return new TraceSchedulingAspect(tracer, traceKeys, Pattern.compile(sleuthSchedulingProperties.getSkipPattern())); } - } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/SleuthWebProperties.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/SleuthWebProperties.java new file mode 100644 index 000000000..7d6065d9c --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/SleuthWebProperties.java @@ -0,0 +1,126 @@ +package org.springframework.cloud.sleuth.instrument.web; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.NestedConfigurationProperty; + +/** + * Configuration properties for web tracing + * + * @author Arthur Gavlyukovskiy + * @since 1.0.12 + */ +@ConfigurationProperties("spring.sleuth.web") +public class SleuthWebProperties { + + public static final String DEFAULT_SKIP_PATTERN = + "/api-docs.*|/autoconfig|/configprops|/dump|/health|/info|/metrics.*|/mappings|/trace|/swagger.*|.*\\.png|.*\\.css|.*\\.js|.*\\.html|/favicon.ico|/hystrix.stream"; + + /** + * When true enables instrumentation for web applications + */ + private boolean enabled = true; + + /** + * Pattern for URLs that should be skipped in tracing + */ + private String skipPattern = DEFAULT_SKIP_PATTERN; + + private Client client; + + public boolean isEnabled() { + return this.enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public String getSkipPattern() { + return this.skipPattern; + } + + public void setSkipPattern(String skipPattern) { + this.skipPattern = skipPattern; + } + + public Client getClient() { + return this.client; + } + + public void setClient(Client client) { + this.client = client; + } + + public static class Client { + + /** + * Enable interceptor injecting into {@link org.springframework.web.client.RestTemplate} + */ + private boolean enabled = true; + + public boolean isEnabled() { + return this.enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + } + + public static class Async { + + @NestedConfigurationProperty + private AsyncClient client; + + public AsyncClient getClient() { + return this.client; + } + + public void setClient(AsyncClient client) { + this.client = client; + } + } + + public static class AsyncClient { + + /** + * Enable span information propagation for {@link org.springframework.http.client.AsyncClientHttpRequestFactory}. + */ + private boolean enabled; + + @NestedConfigurationProperty + private Template template; + + public boolean isEnabled() { + return this.enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public Template getTemplate() { + return this.template; + } + + public void setTemplate(Template template) { + this.template = template; + } + } + + public static class Template { + + /** + * Enable span information propagation for {@link org.springframework.web.client.AsyncRestTemplate}. + */ + private boolean enabled; + + public boolean isEnabled() { + return this.enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + } +} 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 8a937b724..86156f84a 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 @@ -81,8 +81,11 @@ public class TraceFilter extends GenericFilterBean { protected static final String TRACE_ERROR_HANDLED_REQUEST_ATTR = TraceFilter.class.getName() + ".ERROR_HANDLED"; - public static final String DEFAULT_SKIP_PATTERN = - "/api-docs.*|/autoconfig|/configprops|/dump|/health|/info|/metrics.*|/mappings|/trace|/swagger.*|.*\\.png|.*\\.css|.*\\.js|.*\\.html|/favicon.ico|/hystrix.stream"; + /** + * @deprecated please use {@link SleuthWebProperties#DEFAULT_SKIP_PATTERN} + */ + @Deprecated + public static final String DEFAULT_SKIP_PATTERN = SleuthWebProperties.DEFAULT_SKIP_PATTERN; private final Tracer tracer; private final TraceKeys traceKeys; @@ -96,7 +99,7 @@ public class TraceFilter extends GenericFilterBean { public TraceFilter(Tracer tracer, TraceKeys traceKeys, SpanReporter spanReporter, SpanExtractor spanExtractor, HttpTraceKeysInjector httpTraceKeysInjector) { - this(tracer, traceKeys, Pattern.compile(DEFAULT_SKIP_PATTERN), spanReporter, + this(tracer, traceKeys, Pattern.compile(SleuthWebProperties.DEFAULT_SKIP_PATTERN), spanReporter, spanExtractor, httpTraceKeysInjector); } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java index 0c5e0a590..f017aa6ee 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java @@ -19,7 +19,6 @@ import javax.servlet.http.HttpServletRequest; import java.util.regex.Pattern; import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.actuate.autoconfigure.ManagementServerProperties; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; @@ -63,15 +62,9 @@ import static javax.servlet.DispatcherType.REQUEST; @ConditionalOnWebApplication @ConditionalOnBean(Tracer.class) @AutoConfigureAfter(TraceAutoConfiguration.class) -@EnableConfigurationProperties(TraceKeys.class) +@EnableConfigurationProperties({TraceKeys.class, SleuthWebProperties.class}) public class TraceWebAutoConfiguration { - /** - * Pattern for URLs that should be skipped in tracing - */ - @Value("${spring.sleuth.web.skipPattern:}") - private String skipPattern; - /** * Nested config that configures Web MVC if it's present * (without adding a runtime dependency to it) @@ -128,23 +121,19 @@ public class TraceWebAutoConfiguration { @Configuration @ConditionalOnClass(ManagementServerProperties.class) @ConditionalOnMissingBean(SkipPatternProvider.class) + @EnableConfigurationProperties(SleuthWebProperties.class) protected static class SkipPatternProviderConfig { - /** - * Pattern for URLs that should be skipped in tracing - */ - @Value("${spring.sleuth.web.skipPattern:}") - private String skipPattern; - @Bean @ConditionalOnBean(ManagementServerProperties.class) public SkipPatternProvider skipPatternForManagementServerProperties( - final ManagementServerProperties managementServerProperties) { + final ManagementServerProperties managementServerProperties, + final SleuthWebProperties sleuthWebProperties) { return new SkipPatternProvider() { @Override public Pattern skipPattern() { return getPatternForManagementServerProperties( - managementServerProperties, SkipPatternProviderConfig.this.skipPattern); + managementServerProperties, sleuthWebProperties); } }; } @@ -154,7 +143,9 @@ public class TraceWebAutoConfiguration { * skip pattern. If neither is available then sets the default one */ static Pattern getPatternForManagementServerProperties( - ManagementServerProperties managementServerProperties, String skipPattern) { + ManagementServerProperties managementServerProperties, + SleuthWebProperties sleuthWebProperties) { + String skipPattern = sleuthWebProperties.getSkipPattern(); if (StringUtils.hasText(skipPattern) && StringUtils.hasText(managementServerProperties.getContextPath())) { return Pattern.compile(skipPattern + "|" + @@ -167,16 +158,16 @@ public class TraceWebAutoConfiguration { @Bean @ConditionalOnMissingBean(ManagementServerProperties.class) - public SkipPatternProvider defaultSkipPatternBeanIfManagementServerPropsArePresent() { - return defaultSkipPatternProvider(this.skipPattern); + public SkipPatternProvider defaultSkipPatternBeanIfManagementServerPropsArePresent(SleuthWebProperties sleuthWebProperties) { + return defaultSkipPatternProvider(sleuthWebProperties.getSkipPattern()); } } @Bean @ConditionalOnMissingClass("org.springframework.boot.actuate.autoconfigure.ManagementServerProperties") @ConditionalOnMissingBean(SkipPatternProvider.class) - public SkipPatternProvider defaultSkipPatternBean() { - return defaultSkipPatternProvider(this.skipPattern); + public SkipPatternProvider defaultSkipPatternBean(SleuthWebProperties sleuthWebProperties) { + return defaultSkipPatternProvider(sleuthWebProperties.getSkipPattern()); } private static SkipPatternProvider defaultSkipPatternProvider(final String skipPattern) { @@ -191,7 +182,7 @@ public class TraceWebAutoConfiguration { private static Pattern defaultSkipPattern(String skipPattern) { return StringUtils.hasText(skipPattern) ? Pattern.compile(skipPattern) - : Pattern.compile(TraceFilter.DEFAULT_SKIP_PATTERN); + : Pattern.compile(SleuthWebProperties.DEFAULT_SKIP_PATTERN); } interface SkipPatternProvider { diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/SleuthLogAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/SleuthLogAutoConfiguration.java index 16cfed62c..a1560b2c2 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/SleuthLogAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/SleuthLogAutoConfiguration.java @@ -17,11 +17,11 @@ package org.springframework.cloud.sleuth.log; import org.slf4j.MDC; -import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -40,19 +40,15 @@ public class SleuthLogAutoConfiguration { @Configuration @ConditionalOnClass(MDC.class) + @EnableConfigurationProperties(SleuthSlf4jProperties.class) protected static class Slf4jConfiguration { - /** - * Name pattern for which span should not be printed in the logs - */ - @Value("${spring.sleuth.log.slf4j.nameSkipPattern:}") - private String nameSkipPattern; @Bean @ConditionalOnProperty(value = "spring.sleuth.log.slf4j.enabled", matchIfMissing = true) @ConditionalOnMissingBean - public SpanLogger slf4jSpanLogger() { + public SpanLogger slf4jSpanLogger(SleuthSlf4jProperties sleuthSlf4jProperties) { // Sets up MDC entries X-B3-TraceId and X-B3-SpanId - return new Slf4jSpanLogger(this.nameSkipPattern); + return new Slf4jSpanLogger(sleuthSlf4jProperties.getNameSkipPattern()); } @Bean diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/SleuthSlf4jProperties.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/SleuthSlf4jProperties.java new file mode 100644 index 000000000..1e11d8431 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/SleuthSlf4jProperties.java @@ -0,0 +1,39 @@ +package org.springframework.cloud.sleuth.log; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Configuration properties for slf4j + * + * @author Arthur Gavlyukovskiy + * @since 1.0.12 + */ +@ConfigurationProperties("spring.sleuth.log.slf4j") +public class SleuthSlf4jProperties { + + /** + * Enable a {@link Slf4jSpanLogger} that prints tracing information in the logs. + */ + private boolean enabled = true; + + /** + * Name pattern for which span should not be printed in the logs. + */ + private String nameSkipPattern = ""; + + public boolean isEnabled() { + return this.enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public String getNameSkipPattern() { + return this.nameSkipPattern; + } + + public void setNameSkipPattern(String nameSkipPattern) { + this.nameSkipPattern = nameSkipPattern; + } +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/metric/SleuthMetricProperties.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/metric/SleuthMetricProperties.java index e40105224..0d88d0a45 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/metric/SleuthMetricProperties.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/metric/SleuthMetricProperties.java @@ -11,7 +11,12 @@ import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties("spring.sleuth.metric") public class SleuthMetricProperties { - private boolean enabled; + /** + * Enable calculation of accepted and dropped spans through {@link org.springframework.boot.actuate.metrics.CounterService} + */ + private boolean enabled = true; + + private Span span = new Span(); public boolean isEnabled() { return this.enabled; @@ -21,8 +26,6 @@ public class SleuthMetricProperties { this.enabled = enabled; } - private Span span = new Span(); - public Span getSpan() { return this.span; } diff --git a/spring-cloud-sleuth-core/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-cloud-sleuth-core/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 3dfc54046..f09f6cda2 100644 --- a/spring-cloud-sleuth-core/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-cloud-sleuth-core/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -10,5 +10,47 @@ "type": "java.lang.Boolean", "description": "Enable Spring Integration sleuth instrumentation.", "defaultValue": true + }, + { + "name": "spring.sleuth.integration.websockets.enabled", + "type": "java.lang.Boolean", + "description": "Enable tracing for WebSockets.", + "defaultValue": true + }, + { + "name": "spring.sleuth.async.enabled", + "type": "java.lang.Boolean", + "description": "Enable instrumenting async related components so that the tracing information is passed between threads.", + "defaultValue": true + }, + { + "name": "spring.sleuth.async.configurer.enabled", + "type": "java.lang.Boolean", + "description": "Enable default AsyncConfigurer.", + "defaultValue": true + }, + { + "name": "spring.sleuth.hystrix.strategy.enabled", + "type": "java.lang.Boolean", + "description": "Enable custom HystrixConcurrencyStrategy that wraps all Callable instances into their Sleuth representative - the TraceCallable.", + "defaultValue": true + }, + { + "name": "spring.sleuth.feign.enabled", + "type": "java.lang.Boolean", + "description": "Enable span information propagation when using Feign.", + "defaultValue": true + }, + { + "name": "spring.sleuth.feign.processor.enabled", + "type": "java.lang.Boolean", + "description": "Enable post processor that wraps Feign Context in its tracing representations.", + "defaultValue": true + }, + { + "name": "spring.sleuth.zuul.enabled", + "type": "java.lang.Boolean", + "description": "Enable span information propagation when using Zuul.", + "defaultValue": true } ]} \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SkipPatternProviderConfigTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SkipPatternProviderConfigTest.java index 104030042..5dac81bea 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SkipPatternProviderConfigTest.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SkipPatternProviderConfigTest.java @@ -31,40 +31,43 @@ public class SkipPatternProviderConfigTest { @Test public void should_combine_skip_pattern_and_management_context_when_they_are_both_not_empty() throws Exception { - String skipPattern = "foo.*|bar.*"; - + SleuthWebProperties sleuthWebProperties = new SleuthWebProperties(); + sleuthWebProperties.setSkipPattern("foo.*|bar.*"); Pattern pattern = SkipPatternProviderConfig.getPatternForManagementServerProperties( - managementServerPropertiesWithContextPath(), skipPattern); + managementServerPropertiesWithContextPath(), sleuthWebProperties); then(pattern.pattern()).isEqualTo("foo.*|bar.*|/management/context.*"); } @Test public void should_pick_skip_pattern_when_its_not_empty_and_management_context_is_empty() throws Exception { - String skipPattern = "foo.*|bar.*"; + SleuthWebProperties sleuthWebProperties = new SleuthWebProperties(); + sleuthWebProperties.setSkipPattern("foo.*|bar.*"); - Pattern pattern = SkipPatternProviderConfig.getPatternForManagementServerProperties(new ManagementServerProperties(), skipPattern); + Pattern pattern = SkipPatternProviderConfig.getPatternForManagementServerProperties(new ManagementServerProperties(), sleuthWebProperties); then(pattern.pattern()).isEqualTo("foo.*|bar.*"); } @Test public void should_pick_management_context_when_skip_patterns_is_empty_and_context_path_is_not() throws Exception { - String skipPattern = ""; + SleuthWebProperties sleuthWebProperties = new SleuthWebProperties(); + sleuthWebProperties.setSkipPattern(""); Pattern pattern = SkipPatternProviderConfig.getPatternForManagementServerProperties( - managementServerPropertiesWithContextPath(), skipPattern); + managementServerPropertiesWithContextPath(), sleuthWebProperties); then(pattern.pattern()).isEqualTo("/management/context.*"); } @Test public void should_pick_default_pattern_when_both_management_context_and_skip_patterns_are_empty() throws Exception { - String skipPattern = ""; + SleuthWebProperties sleuthWebProperties = new SleuthWebProperties(); + sleuthWebProperties.setSkipPattern(""); - Pattern pattern = SkipPatternProviderConfig.getPatternForManagementServerProperties(new ManagementServerProperties(), skipPattern); + Pattern pattern = SkipPatternProviderConfig.getPatternForManagementServerProperties(new ManagementServerProperties(), sleuthWebProperties); - then(pattern.pattern()).isEqualTo(TraceFilter.DEFAULT_SKIP_PATTERN); + then(pattern.pattern()).isEqualTo(SleuthWebProperties.DEFAULT_SKIP_PATTERN); } private ManagementServerProperties managementServerPropertiesWithContextPath() { diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterMockChainIntegrationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterMockChainIntegrationTests.java index 5f29beea6..d5ecfe9c9 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterMockChainIntegrationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterMockChainIntegrationTests.java @@ -73,7 +73,7 @@ public class TraceFilterMockChainIntegrationTests { @Test public void startsNewTrace() throws Exception { TraceFilter filter = new TraceFilter(this.tracer, this.traceKeys, new NoOpSpanReporter(), - new HttpServletRequestExtractor(Pattern.compile(TraceFilter.DEFAULT_SKIP_PATTERN)), + new HttpServletRequestExtractor(Pattern.compile(SleuthWebProperties.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 HttpServletRequestExtractor(Pattern.compile(SleuthWebProperties.DEFAULT_SKIP_PATTERN)), keysInjector); filter.doFilter(this.request, this.response, this.filterChain); assertNull(TestSpanContextHolder.getCurrentSpan()); diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterTests.java index a1909fa15..774313908 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 @@ -67,7 +67,7 @@ public class TraceFilterTests { @Mock SpanLogger spanLogger; ArrayListSpanAccumulator spanReporter = new ArrayListSpanAccumulator(); SpanExtractor spanExtractor = new HttpServletRequestExtractor(Pattern - .compile(TraceFilter.DEFAULT_SKIP_PATTERN)); + .compile(SleuthWebProperties.DEFAULT_SKIP_PATTERN)); private Tracer tracer; private TraceKeys traceKeys = new TraceKeys(); From 2a06734106e7486a434fecb2567e6dc50042e5ff Mon Sep 17 00:00:00 2001 From: Arthur Gavlyukovskiy Date: Fri, 30 Dec 2016 13:35:46 +0200 Subject: [PATCH 185/235] Created @ConfigurationProperties classes and updated additional metadata (#486) * Created @ConfigurationProperties classes and updated additional metadata --- .../sleuth/autoconfig/SleuthProperties.java | 10 ++ .../rxjava/RxJavaAutoConfiguration.java | 28 +--- .../SleuthRxJavaSchedulersProperties.java | 51 +++++++ .../SleuthSchedulingProperties.java | 39 ++++++ .../TraceSchedulingAutoConfiguration.java | 18 +-- .../instrument/web/SleuthWebProperties.java | 126 ++++++++++++++++++ .../sleuth/instrument/web/TraceFilter.java | 9 +- .../web/TraceWebAutoConfiguration.java | 35 ++--- .../log/SleuthLogAutoConfiguration.java | 12 +- .../sleuth/log/SleuthSlf4jProperties.java | 39 ++++++ .../sleuth/metric/SleuthMetricProperties.java | 9 +- ...itional-spring-configuration-metadata.json | 42 ++++++ .../web/SkipPatternProviderConfigTest.java | 23 ++-- .../TraceFilterMockChainIntegrationTests.java | 4 +- .../instrument/web/TraceFilterTests.java | 2 +- 15 files changed, 364 insertions(+), 83 deletions(-) create mode 100644 spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/rxjava/SleuthRxJavaSchedulersProperties.java create mode 100644 spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/scheduling/SleuthSchedulingProperties.java create mode 100644 spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/SleuthWebProperties.java create mode 100644 spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/SleuthSlf4jProperties.java 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 57036095a..45d790b2b 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 @@ -25,9 +25,19 @@ import org.springframework.boot.context.properties.ConfigurationProperties; */ @ConfigurationProperties("spring.sleuth") public class SleuthProperties { + + private boolean enabled = true; /** When true, generate 128-bit trace IDs instead of 64-bit ones. */ private boolean traceId128 = false; + public boolean isEnabled() { + return this.enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + public boolean isTraceId128() { return this.traceId128; } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/rxjava/RxJavaAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/rxjava/RxJavaAutoConfiguration.java index 2056acf3c..286dc4753 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/rxjava/RxJavaAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/rxjava/RxJavaAutoConfiguration.java @@ -1,20 +1,17 @@ package org.springframework.cloud.sleuth.instrument.rxjava; -import java.util.ArrayList; import java.util.Arrays; -import java.util.List; -import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.sleuth.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.util.StringUtils; import rx.plugins.RxJavaSchedulersHook; @@ -30,28 +27,13 @@ import rx.plugins.RxJavaSchedulersHook; @ConditionalOnBean(Tracer.class) @ConditionalOnClass(RxJavaSchedulersHook.class) @ConditionalOnProperty(value = "spring.sleuth.rxjava.schedulers.hook.enabled", matchIfMissing = true) +@EnableConfigurationProperties(SleuthRxJavaSchedulersProperties.class) public class RxJavaAutoConfiguration { - /** - * Contains a list of thread names for which spans will not be sampled. Extracted to a constant - * for readability reasons. - */ - private static final List DEFAULT_IGNORED_THREADS = Arrays.asList("HystrixMetricPoller", "^RxComputation.*$"); - @Bean SleuthRxJavaSchedulersHook sleuthRxJavaSchedulersHook(Tracer tracer, TraceKeys traceKeys, - // Comma separated list of thread name matchers - @Value("${spring.sleuth.rxjava.schedulers.ignoredthreads:}") String threadsToSample) { - return new SleuthRxJavaSchedulersHook(tracer, traceKeys, threads(threadsToSample)); - } - - private List threads(String threadsToSample) { - List threads = new ArrayList<>(); - if (StringUtils.isEmpty(threadsToSample)) { - threads.addAll(DEFAULT_IGNORED_THREADS); - } else { - threads.addAll(Arrays.asList(threadsToSample.split(","))); - } - return threads; + SleuthRxJavaSchedulersProperties sleuthRxJavaSchedulersProperties) { + return new SleuthRxJavaSchedulersHook(tracer, traceKeys, + Arrays.asList(sleuthRxJavaSchedulersProperties.getIgnoredthreads())); } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/rxjava/SleuthRxJavaSchedulersProperties.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/rxjava/SleuthRxJavaSchedulersProperties.java new file mode 100644 index 000000000..568dbf099 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/rxjava/SleuthRxJavaSchedulersProperties.java @@ -0,0 +1,51 @@ +package org.springframework.cloud.sleuth.instrument.rxjava; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Configuration properties for RxJava tracing + * + * @author Arthur Gavlyukovskiy + * @since 1.0.12 + */ +@ConfigurationProperties("spring.sleuth.rxjava.schedulers") +public class SleuthRxJavaSchedulersProperties { + + /** + * Thread names for which spans will not be sampled. + */ + private String[] ignoredthreads = { "HystrixMetricPoller", "^RxComputation.*$" }; + private Hook hook; + + public String[] getIgnoredthreads() { + return this.ignoredthreads; + } + + public void setIgnoredthreads(String[] ignoredthreads) { + this.ignoredthreads = ignoredthreads; + } + + public Hook getHook() { + return this.hook; + } + + public void setHook(Hook hook) { + this.hook = hook; + } + + private static class Hook { + + /** + * Enable support for RxJava via RxJavaSchedulersHook. + */ + private boolean enabled = true; + + public boolean isEnabled() { + return this.enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + } +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/scheduling/SleuthSchedulingProperties.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/scheduling/SleuthSchedulingProperties.java new file mode 100644 index 000000000..cd44f1b36 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/scheduling/SleuthSchedulingProperties.java @@ -0,0 +1,39 @@ +package org.springframework.cloud.sleuth.instrument.scheduling; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Configuration properties for {@link org.springframework.scheduling.annotation.Scheduled} tracing + * + * @author Arthur Gavlyukovskiy + * @since 1.0.12 + */ +@ConfigurationProperties("spring.sleuth.scheduled") +public class SleuthSchedulingProperties { + + /** + * Enable tracing for {@link org.springframework.scheduling.annotation.Scheduled}. + */ + private boolean enabled = true; + + /** + * Pattern for the fully qualified name of a class that should be skipped. + */ + private String skipPattern = ""; + + public boolean isEnabled() { + return this.enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public String getSkipPattern() { + return this.skipPattern; + } + + public void setSkipPattern(String skipPattern) { + this.skipPattern = skipPattern; + } +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/scheduling/TraceSchedulingAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/scheduling/TraceSchedulingAutoConfiguration.java index 40da08e1a..a840eaae9 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/scheduling/TraceSchedulingAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/scheduling/TraceSchedulingAutoConfiguration.java @@ -16,13 +16,11 @@ package org.springframework.cloud.sleuth.instrument.scheduling; -import java.util.regex.Pattern; - -import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.sleuth.TraceKeys; import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration; @@ -30,6 +28,8 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; +import java.util.regex.Pattern; + /** * Registers beans related to task scheduling. * @@ -44,17 +44,13 @@ import org.springframework.context.annotation.EnableAspectJAutoProxy; @ConditionalOnProperty(value = "spring.sleuth.scheduled.enabled", matchIfMissing = true) @ConditionalOnBean(Tracer.class) @AutoConfigureAfter(TraceAutoConfiguration.class) +@EnableConfigurationProperties(SleuthSchedulingProperties.class) public class TraceSchedulingAutoConfiguration { - /** - * Pattern for the fully qualified name of a class that should be skipped - */ - private @Value("${spring.sleuth.scheduled.skipPattern:}") String skipPattern; - @ConditionalOnClass(name = "org.aspectj.lang.ProceedingJoinPoint") @Bean - public TraceSchedulingAspect traceSchedulingAspect(Tracer tracer, TraceKeys traceKeys) { - return new TraceSchedulingAspect(tracer, traceKeys, Pattern.compile(this.skipPattern)); + public TraceSchedulingAspect traceSchedulingAspect(Tracer tracer, TraceKeys traceKeys, + SleuthSchedulingProperties sleuthSchedulingProperties) { + return new TraceSchedulingAspect(tracer, traceKeys, Pattern.compile(sleuthSchedulingProperties.getSkipPattern())); } - } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/SleuthWebProperties.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/SleuthWebProperties.java new file mode 100644 index 000000000..7d6065d9c --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/SleuthWebProperties.java @@ -0,0 +1,126 @@ +package org.springframework.cloud.sleuth.instrument.web; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.NestedConfigurationProperty; + +/** + * Configuration properties for web tracing + * + * @author Arthur Gavlyukovskiy + * @since 1.0.12 + */ +@ConfigurationProperties("spring.sleuth.web") +public class SleuthWebProperties { + + public static final String DEFAULT_SKIP_PATTERN = + "/api-docs.*|/autoconfig|/configprops|/dump|/health|/info|/metrics.*|/mappings|/trace|/swagger.*|.*\\.png|.*\\.css|.*\\.js|.*\\.html|/favicon.ico|/hystrix.stream"; + + /** + * When true enables instrumentation for web applications + */ + private boolean enabled = true; + + /** + * Pattern for URLs that should be skipped in tracing + */ + private String skipPattern = DEFAULT_SKIP_PATTERN; + + private Client client; + + public boolean isEnabled() { + return this.enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public String getSkipPattern() { + return this.skipPattern; + } + + public void setSkipPattern(String skipPattern) { + this.skipPattern = skipPattern; + } + + public Client getClient() { + return this.client; + } + + public void setClient(Client client) { + this.client = client; + } + + public static class Client { + + /** + * Enable interceptor injecting into {@link org.springframework.web.client.RestTemplate} + */ + private boolean enabled = true; + + public boolean isEnabled() { + return this.enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + } + + public static class Async { + + @NestedConfigurationProperty + private AsyncClient client; + + public AsyncClient getClient() { + return this.client; + } + + public void setClient(AsyncClient client) { + this.client = client; + } + } + + public static class AsyncClient { + + /** + * Enable span information propagation for {@link org.springframework.http.client.AsyncClientHttpRequestFactory}. + */ + private boolean enabled; + + @NestedConfigurationProperty + private Template template; + + public boolean isEnabled() { + return this.enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public Template getTemplate() { + return this.template; + } + + public void setTemplate(Template template) { + this.template = template; + } + } + + public static class Template { + + /** + * Enable span information propagation for {@link org.springframework.web.client.AsyncRestTemplate}. + */ + private boolean enabled; + + public boolean isEnabled() { + return this.enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + } +} 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 8a937b724..86156f84a 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 @@ -81,8 +81,11 @@ public class TraceFilter extends GenericFilterBean { protected static final String TRACE_ERROR_HANDLED_REQUEST_ATTR = TraceFilter.class.getName() + ".ERROR_HANDLED"; - public static final String DEFAULT_SKIP_PATTERN = - "/api-docs.*|/autoconfig|/configprops|/dump|/health|/info|/metrics.*|/mappings|/trace|/swagger.*|.*\\.png|.*\\.css|.*\\.js|.*\\.html|/favicon.ico|/hystrix.stream"; + /** + * @deprecated please use {@link SleuthWebProperties#DEFAULT_SKIP_PATTERN} + */ + @Deprecated + public static final String DEFAULT_SKIP_PATTERN = SleuthWebProperties.DEFAULT_SKIP_PATTERN; private final Tracer tracer; private final TraceKeys traceKeys; @@ -96,7 +99,7 @@ public class TraceFilter extends GenericFilterBean { public TraceFilter(Tracer tracer, TraceKeys traceKeys, SpanReporter spanReporter, SpanExtractor spanExtractor, HttpTraceKeysInjector httpTraceKeysInjector) { - this(tracer, traceKeys, Pattern.compile(DEFAULT_SKIP_PATTERN), spanReporter, + this(tracer, traceKeys, Pattern.compile(SleuthWebProperties.DEFAULT_SKIP_PATTERN), spanReporter, spanExtractor, httpTraceKeysInjector); } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java index 0c5e0a590..f017aa6ee 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java @@ -19,7 +19,6 @@ import javax.servlet.http.HttpServletRequest; import java.util.regex.Pattern; import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.actuate.autoconfigure.ManagementServerProperties; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; @@ -63,15 +62,9 @@ import static javax.servlet.DispatcherType.REQUEST; @ConditionalOnWebApplication @ConditionalOnBean(Tracer.class) @AutoConfigureAfter(TraceAutoConfiguration.class) -@EnableConfigurationProperties(TraceKeys.class) +@EnableConfigurationProperties({TraceKeys.class, SleuthWebProperties.class}) public class TraceWebAutoConfiguration { - /** - * Pattern for URLs that should be skipped in tracing - */ - @Value("${spring.sleuth.web.skipPattern:}") - private String skipPattern; - /** * Nested config that configures Web MVC if it's present * (without adding a runtime dependency to it) @@ -128,23 +121,19 @@ public class TraceWebAutoConfiguration { @Configuration @ConditionalOnClass(ManagementServerProperties.class) @ConditionalOnMissingBean(SkipPatternProvider.class) + @EnableConfigurationProperties(SleuthWebProperties.class) protected static class SkipPatternProviderConfig { - /** - * Pattern for URLs that should be skipped in tracing - */ - @Value("${spring.sleuth.web.skipPattern:}") - private String skipPattern; - @Bean @ConditionalOnBean(ManagementServerProperties.class) public SkipPatternProvider skipPatternForManagementServerProperties( - final ManagementServerProperties managementServerProperties) { + final ManagementServerProperties managementServerProperties, + final SleuthWebProperties sleuthWebProperties) { return new SkipPatternProvider() { @Override public Pattern skipPattern() { return getPatternForManagementServerProperties( - managementServerProperties, SkipPatternProviderConfig.this.skipPattern); + managementServerProperties, sleuthWebProperties); } }; } @@ -154,7 +143,9 @@ public class TraceWebAutoConfiguration { * skip pattern. If neither is available then sets the default one */ static Pattern getPatternForManagementServerProperties( - ManagementServerProperties managementServerProperties, String skipPattern) { + ManagementServerProperties managementServerProperties, + SleuthWebProperties sleuthWebProperties) { + String skipPattern = sleuthWebProperties.getSkipPattern(); if (StringUtils.hasText(skipPattern) && StringUtils.hasText(managementServerProperties.getContextPath())) { return Pattern.compile(skipPattern + "|" + @@ -167,16 +158,16 @@ public class TraceWebAutoConfiguration { @Bean @ConditionalOnMissingBean(ManagementServerProperties.class) - public SkipPatternProvider defaultSkipPatternBeanIfManagementServerPropsArePresent() { - return defaultSkipPatternProvider(this.skipPattern); + public SkipPatternProvider defaultSkipPatternBeanIfManagementServerPropsArePresent(SleuthWebProperties sleuthWebProperties) { + return defaultSkipPatternProvider(sleuthWebProperties.getSkipPattern()); } } @Bean @ConditionalOnMissingClass("org.springframework.boot.actuate.autoconfigure.ManagementServerProperties") @ConditionalOnMissingBean(SkipPatternProvider.class) - public SkipPatternProvider defaultSkipPatternBean() { - return defaultSkipPatternProvider(this.skipPattern); + public SkipPatternProvider defaultSkipPatternBean(SleuthWebProperties sleuthWebProperties) { + return defaultSkipPatternProvider(sleuthWebProperties.getSkipPattern()); } private static SkipPatternProvider defaultSkipPatternProvider(final String skipPattern) { @@ -191,7 +182,7 @@ public class TraceWebAutoConfiguration { private static Pattern defaultSkipPattern(String skipPattern) { return StringUtils.hasText(skipPattern) ? Pattern.compile(skipPattern) - : Pattern.compile(TraceFilter.DEFAULT_SKIP_PATTERN); + : Pattern.compile(SleuthWebProperties.DEFAULT_SKIP_PATTERN); } interface SkipPatternProvider { diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/SleuthLogAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/SleuthLogAutoConfiguration.java index 16cfed62c..a1560b2c2 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/SleuthLogAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/SleuthLogAutoConfiguration.java @@ -17,11 +17,11 @@ package org.springframework.cloud.sleuth.log; import org.slf4j.MDC; -import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -40,19 +40,15 @@ public class SleuthLogAutoConfiguration { @Configuration @ConditionalOnClass(MDC.class) + @EnableConfigurationProperties(SleuthSlf4jProperties.class) protected static class Slf4jConfiguration { - /** - * Name pattern for which span should not be printed in the logs - */ - @Value("${spring.sleuth.log.slf4j.nameSkipPattern:}") - private String nameSkipPattern; @Bean @ConditionalOnProperty(value = "spring.sleuth.log.slf4j.enabled", matchIfMissing = true) @ConditionalOnMissingBean - public SpanLogger slf4jSpanLogger() { + public SpanLogger slf4jSpanLogger(SleuthSlf4jProperties sleuthSlf4jProperties) { // Sets up MDC entries X-B3-TraceId and X-B3-SpanId - return new Slf4jSpanLogger(this.nameSkipPattern); + return new Slf4jSpanLogger(sleuthSlf4jProperties.getNameSkipPattern()); } @Bean diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/SleuthSlf4jProperties.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/SleuthSlf4jProperties.java new file mode 100644 index 000000000..1e11d8431 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/SleuthSlf4jProperties.java @@ -0,0 +1,39 @@ +package org.springframework.cloud.sleuth.log; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Configuration properties for slf4j + * + * @author Arthur Gavlyukovskiy + * @since 1.0.12 + */ +@ConfigurationProperties("spring.sleuth.log.slf4j") +public class SleuthSlf4jProperties { + + /** + * Enable a {@link Slf4jSpanLogger} that prints tracing information in the logs. + */ + private boolean enabled = true; + + /** + * Name pattern for which span should not be printed in the logs. + */ + private String nameSkipPattern = ""; + + public boolean isEnabled() { + return this.enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public String getNameSkipPattern() { + return this.nameSkipPattern; + } + + public void setNameSkipPattern(String nameSkipPattern) { + this.nameSkipPattern = nameSkipPattern; + } +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/metric/SleuthMetricProperties.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/metric/SleuthMetricProperties.java index e40105224..0d88d0a45 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/metric/SleuthMetricProperties.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/metric/SleuthMetricProperties.java @@ -11,7 +11,12 @@ import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties("spring.sleuth.metric") public class SleuthMetricProperties { - private boolean enabled; + /** + * Enable calculation of accepted and dropped spans through {@link org.springframework.boot.actuate.metrics.CounterService} + */ + private boolean enabled = true; + + private Span span = new Span(); public boolean isEnabled() { return this.enabled; @@ -21,8 +26,6 @@ public class SleuthMetricProperties { this.enabled = enabled; } - private Span span = new Span(); - public Span getSpan() { return this.span; } diff --git a/spring-cloud-sleuth-core/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-cloud-sleuth-core/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 3dfc54046..f09f6cda2 100644 --- a/spring-cloud-sleuth-core/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-cloud-sleuth-core/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -10,5 +10,47 @@ "type": "java.lang.Boolean", "description": "Enable Spring Integration sleuth instrumentation.", "defaultValue": true + }, + { + "name": "spring.sleuth.integration.websockets.enabled", + "type": "java.lang.Boolean", + "description": "Enable tracing for WebSockets.", + "defaultValue": true + }, + { + "name": "spring.sleuth.async.enabled", + "type": "java.lang.Boolean", + "description": "Enable instrumenting async related components so that the tracing information is passed between threads.", + "defaultValue": true + }, + { + "name": "spring.sleuth.async.configurer.enabled", + "type": "java.lang.Boolean", + "description": "Enable default AsyncConfigurer.", + "defaultValue": true + }, + { + "name": "spring.sleuth.hystrix.strategy.enabled", + "type": "java.lang.Boolean", + "description": "Enable custom HystrixConcurrencyStrategy that wraps all Callable instances into their Sleuth representative - the TraceCallable.", + "defaultValue": true + }, + { + "name": "spring.sleuth.feign.enabled", + "type": "java.lang.Boolean", + "description": "Enable span information propagation when using Feign.", + "defaultValue": true + }, + { + "name": "spring.sleuth.feign.processor.enabled", + "type": "java.lang.Boolean", + "description": "Enable post processor that wraps Feign Context in its tracing representations.", + "defaultValue": true + }, + { + "name": "spring.sleuth.zuul.enabled", + "type": "java.lang.Boolean", + "description": "Enable span information propagation when using Zuul.", + "defaultValue": true } ]} \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SkipPatternProviderConfigTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SkipPatternProviderConfigTest.java index 104030042..5dac81bea 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SkipPatternProviderConfigTest.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SkipPatternProviderConfigTest.java @@ -31,40 +31,43 @@ public class SkipPatternProviderConfigTest { @Test public void should_combine_skip_pattern_and_management_context_when_they_are_both_not_empty() throws Exception { - String skipPattern = "foo.*|bar.*"; - + SleuthWebProperties sleuthWebProperties = new SleuthWebProperties(); + sleuthWebProperties.setSkipPattern("foo.*|bar.*"); Pattern pattern = SkipPatternProviderConfig.getPatternForManagementServerProperties( - managementServerPropertiesWithContextPath(), skipPattern); + managementServerPropertiesWithContextPath(), sleuthWebProperties); then(pattern.pattern()).isEqualTo("foo.*|bar.*|/management/context.*"); } @Test public void should_pick_skip_pattern_when_its_not_empty_and_management_context_is_empty() throws Exception { - String skipPattern = "foo.*|bar.*"; + SleuthWebProperties sleuthWebProperties = new SleuthWebProperties(); + sleuthWebProperties.setSkipPattern("foo.*|bar.*"); - Pattern pattern = SkipPatternProviderConfig.getPatternForManagementServerProperties(new ManagementServerProperties(), skipPattern); + Pattern pattern = SkipPatternProviderConfig.getPatternForManagementServerProperties(new ManagementServerProperties(), sleuthWebProperties); then(pattern.pattern()).isEqualTo("foo.*|bar.*"); } @Test public void should_pick_management_context_when_skip_patterns_is_empty_and_context_path_is_not() throws Exception { - String skipPattern = ""; + SleuthWebProperties sleuthWebProperties = new SleuthWebProperties(); + sleuthWebProperties.setSkipPattern(""); Pattern pattern = SkipPatternProviderConfig.getPatternForManagementServerProperties( - managementServerPropertiesWithContextPath(), skipPattern); + managementServerPropertiesWithContextPath(), sleuthWebProperties); then(pattern.pattern()).isEqualTo("/management/context.*"); } @Test public void should_pick_default_pattern_when_both_management_context_and_skip_patterns_are_empty() throws Exception { - String skipPattern = ""; + SleuthWebProperties sleuthWebProperties = new SleuthWebProperties(); + sleuthWebProperties.setSkipPattern(""); - Pattern pattern = SkipPatternProviderConfig.getPatternForManagementServerProperties(new ManagementServerProperties(), skipPattern); + Pattern pattern = SkipPatternProviderConfig.getPatternForManagementServerProperties(new ManagementServerProperties(), sleuthWebProperties); - then(pattern.pattern()).isEqualTo(TraceFilter.DEFAULT_SKIP_PATTERN); + then(pattern.pattern()).isEqualTo(SleuthWebProperties.DEFAULT_SKIP_PATTERN); } private ManagementServerProperties managementServerPropertiesWithContextPath() { diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterMockChainIntegrationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterMockChainIntegrationTests.java index 5f29beea6..d5ecfe9c9 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterMockChainIntegrationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterMockChainIntegrationTests.java @@ -73,7 +73,7 @@ public class TraceFilterMockChainIntegrationTests { @Test public void startsNewTrace() throws Exception { TraceFilter filter = new TraceFilter(this.tracer, this.traceKeys, new NoOpSpanReporter(), - new HttpServletRequestExtractor(Pattern.compile(TraceFilter.DEFAULT_SKIP_PATTERN)), + new HttpServletRequestExtractor(Pattern.compile(SleuthWebProperties.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 HttpServletRequestExtractor(Pattern.compile(SleuthWebProperties.DEFAULT_SKIP_PATTERN)), keysInjector); filter.doFilter(this.request, this.response, this.filterChain); assertNull(TestSpanContextHolder.getCurrentSpan()); diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterTests.java index a1909fa15..774313908 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 @@ -67,7 +67,7 @@ public class TraceFilterTests { @Mock SpanLogger spanLogger; ArrayListSpanAccumulator spanReporter = new ArrayListSpanAccumulator(); SpanExtractor spanExtractor = new HttpServletRequestExtractor(Pattern - .compile(TraceFilter.DEFAULT_SKIP_PATTERN)); + .compile(SleuthWebProperties.DEFAULT_SKIP_PATTERN)); private Tracer tracer; private TraceKeys traceKeys = new TraceKeys(); From e982c4df80bf28259a6aee38186e00b82b13f230 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Mon, 2 Jan 2017 10:18:20 +0100 Subject: [PATCH 186/235] Added property for overriding the service name in Zipkin without this only either service discovery service id / spring.application.name can be chosen as a service name for zipkin with this change you can pass spring.zipkin.service.name property to change override that both for HTTP and Stream collectors fixes #324 --- README.adoc | 7 ++-- .../main/asciidoc/spring-cloud-sleuth.adoc | 13 ++++++ .../stream/DiscoveryClientHostLocator.java | 16 +++++++- .../stream/ServerPropertiesHostLocator.java | 15 ++++++- .../stream/SleuthStreamAutoConfiguration.java | 14 +++++-- .../cloud/sleuth/stream/ZipkinProperties.java | 40 +++++++++++++++++++ .../DiscoveryClientHostLocatorTest.java | 18 ++++++++- .../ServerPropertiesHostLocatorTests.java | 21 ++++++++-- .../DiscoveryClientEndpointLocator.java | 27 ++++++++++++- .../ServerPropertiesEndpointLocator.java | 26 +++++++++++- .../zipkin/ZipkinAutoConfiguration.java | 14 +++++-- .../cloud/sleuth/zipkin/ZipkinProperties.java | 25 ++++++++++++ .../DiscoveryClientEndpointLocatorTest.java | 23 ++++++++++- .../zipkin/HttpZipkinSpanReporterTest.java | 3 +- .../ServerPropertiesEndpointLocatorTests.java | 26 ++++++++---- 15 files changed, 255 insertions(+), 33 deletions(-) create mode 100644 spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/ZipkinProperties.java diff --git a/README.adoc b/README.adoc index 5e90286a3..7d426a73f 100644 --- a/README.adoc +++ b/README.adoc @@ -195,13 +195,13 @@ Below you can find an example of a Logback configuration (file named https://git ​ + value="%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr([${springAppName:-},%X{X-B3-TraceId:-},%X{X-B3-SpanId:-},%X{X-B3-ParentSpanId:-},%X{X-Span-Export:-}]){yellow} %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}"/> - INFO + DEBUG ${CONSOLE_LOG_PATTERN} @@ -241,6 +241,7 @@ Below you can find an example of a Logback configuration (file named https://git "service": "${springAppName:-}", "trace": "%X{X-B3-TraceId:-}", "span": "%X{X-B3-SpanId:-}", + "parent": "%X{X-B3-ParentSpanId:-}", "exportable": "%X{X-Span-Export:-}", "pid": "${PID:-}", "thread": "%thread", @@ -254,7 +255,7 @@ Below you can find an example of a Logback configuration (file named https://git ​ - + diff --git a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc index fb2be678a..446e24ca1 100644 --- a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc +++ b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc @@ -292,6 +292,19 @@ include::../../../..//spring-cloud-sleuth-zipkin/src/test/java/org/springframewo IMPORTANT: Remember not to add both `peer.service` tag and the `SA` tag! You have to add only `peer.service`. +=== Custom service name + +By default Sleuth assumes that when you send a span to Zipkin, you want the span's service name + to be equal to `spring.application.name` value. That's not always the case though. There + are situations in which you want to explicitly provide a different service name for all spans coming + from your application. To achieve that it's enough to just pass the following property + to your application to override that value (example for `foo` service name): + +[source,yaml] +---- +spring.zipkin.service.name: foo +---- + == Span Data as Messages You can accumulate and send span data over diff --git a/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/DiscoveryClientHostLocator.java b/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/DiscoveryClientHostLocator.java index 9b583b074..86b618f6e 100644 --- a/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/DiscoveryClientHostLocator.java +++ b/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/DiscoveryClientHostLocator.java @@ -22,27 +22,39 @@ import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.cloud.sleuth.Span; import org.springframework.util.Assert; +import org.springframework.util.StringUtils; /** * An {@link HostLocator} that tries to find local service information from a * {@link DiscoveryClient}. * + * You can override the value of service id by {@link ZipkinProperties#setName(String)} + * * @author Dave Syer * @since 1.0.0 */ public class DiscoveryClientHostLocator implements HostLocator { - private DiscoveryClient client; + private final DiscoveryClient client; + private final ZipkinProperties zipkinProperties; + @Deprecated public DiscoveryClientHostLocator(DiscoveryClient client) { + this(client, new ZipkinProperties()); + } + + public DiscoveryClientHostLocator(DiscoveryClient client, ZipkinProperties zipkinProperties) { this.client = client; Assert.notNull(this.client, "client"); + this.zipkinProperties = zipkinProperties; } @Override public Host locate(Span span) { ServiceInstance instance = this.client.getLocalServiceInstance(); - return new Host(instance.getServiceId(), getIpAddress(instance), + String serviceId = StringUtils.hasText(this.zipkinProperties.getName()) ? + this.zipkinProperties.getName() : instance.getServiceId(); + return new Host(serviceId, getIpAddress(instance), instance.getPort()); } diff --git a/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/ServerPropertiesHostLocator.java b/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/ServerPropertiesHostLocator.java index a471b63a9..37e33a582 100644 --- a/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/ServerPropertiesHostLocator.java +++ b/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/ServerPropertiesHostLocator.java @@ -21,6 +21,7 @@ import org.springframework.boot.context.embedded.EmbeddedServletContainerInitial import org.springframework.cloud.sleuth.Span; import org.springframework.context.event.EventListener; import org.springframework.util.Assert; +import org.springframework.util.StringUtils; /** * A {@link HostLocator} that retrieves: @@ -31,6 +32,8 @@ import org.springframework.util.Assert; *

  • port - from lazily assigned port or {@link ServerProperties}
  • * * + * You can override the value of service id by {@link ZipkinProperties#setName(String)} + * * @author Dave Syer * @since 1.0.0 */ @@ -38,13 +41,21 @@ public class ServerPropertiesHostLocator implements HostLocator { private final ServerProperties serverProperties; // Nullable private final String appName; + private final ZipkinProperties zipkinProperties; private Integer port; // Lazy assigned + @Deprecated public ServerPropertiesHostLocator(ServerProperties serverProperties, String appName) { + this(serverProperties, appName, new ZipkinProperties()); + } + + public ServerPropertiesHostLocator(ServerProperties serverProperties, + String appName, ZipkinProperties zipkinProperties) { this.serverProperties = serverProperties; this.appName = appName; Assert.notNull(this.appName, "appName"); + this.zipkinProperties = zipkinProperties; } @Override @@ -87,7 +98,9 @@ public class ServerPropertiesHostLocator implements HostLocator { private String getServiceName(Span span) { String serviceName; - if (span.getProcessId() != null) { + if (StringUtils.hasText(this.zipkinProperties.getName())) { + serviceName = this.zipkinProperties.getName(); + } else if (span.getProcessId() != null) { serviceName = span.getProcessId(); } else { diff --git a/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/SleuthStreamAutoConfiguration.java b/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/SleuthStreamAutoConfiguration.java index e69f04564..5ad57ba26 100644 --- a/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/SleuthStreamAutoConfiguration.java +++ b/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/SleuthStreamAutoConfiguration.java @@ -54,7 +54,7 @@ import org.springframework.scheduling.support.PeriodicTrigger; * @since 1.0.0 */ @Configuration -@EnableConfigurationProperties({ SleuthStreamProperties.class, SamplerProperties.class }) +@EnableConfigurationProperties({ SleuthStreamProperties.class, SamplerProperties.class, ZipkinProperties.class }) @AutoConfigureAfter(TraceMetricsAutoConfiguration.class) @AutoConfigureBefore(ChannelBindingAutoConfiguration.class) @EnableBinding(SleuthSource.class) @@ -96,12 +96,15 @@ public class SleuthStreamAutoConfiguration { @Autowired(required = false) private ServerProperties serverProperties; + @Autowired + private ZipkinProperties zipkinProperties; + @Value("${spring.application.name:unknown}") private String appName; @Bean public HostLocator zipkinEndpointLocator() { - return new ServerPropertiesHostLocator(this.serverProperties, this.appName); + return new ServerPropertiesHostLocator(this.serverProperties, this.appName, this.zipkinProperties); } } @@ -113,6 +116,9 @@ public class SleuthStreamAutoConfiguration { @Autowired(required = false) private ServerProperties serverProperties; + @Autowired + private ZipkinProperties zipkinProperties; + @Value("${spring.application.name:unknown}") private String appName; @@ -122,9 +128,9 @@ public class SleuthStreamAutoConfiguration { @Bean public HostLocator zipkinEndpointLocator() { if (this.client != null) { - return new DiscoveryClientHostLocator(this.client); + return new DiscoveryClientHostLocator(this.client, this.zipkinProperties); } - return new ServerPropertiesHostLocator(this.serverProperties, this.appName); + return new ServerPropertiesHostLocator(this.serverProperties, this.appName, this.zipkinProperties); } } diff --git a/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/ZipkinProperties.java b/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/ZipkinProperties.java new file mode 100644 index 000000000..700ee1023 --- /dev/null +++ b/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/ZipkinProperties.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.stream; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Zipkin settings for Zipkin Stream client + * + * @author Marcin Grzejszczak + * @since 1.0.12 + */ +@ConfigurationProperties("spring.zipkin.service") +public class ZipkinProperties { + + /** The name of the service, from which the Span was sent via Stream, that should appear in Zipkin */ + private String name; + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/DiscoveryClientHostLocatorTest.java b/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/DiscoveryClientHostLocatorTest.java index bed66f891..46f4bf72d 100644 --- a/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/DiscoveryClientHostLocatorTest.java +++ b/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/DiscoveryClientHostLocatorTest.java @@ -34,11 +34,11 @@ import static org.mockito.BDDMockito.given; public class DiscoveryClientHostLocatorTest { DiscoveryClient discoveryClient = Mockito.mock(DiscoveryClient.class); DiscoveryClientHostLocator discoveryClientHostLocator = - new DiscoveryClientHostLocator(this.discoveryClient); + new DiscoveryClientHostLocator(this.discoveryClient, new ZipkinProperties()); @Test(expected = IllegalArgumentException.class) public void should_throw_exception_when_no_discovery_client_is_present() throws Exception { - new DiscoveryClientHostLocator(null); + new DiscoveryClientHostLocator(null, new ZipkinProperties()); } @Test @@ -63,6 +63,20 @@ public class DiscoveryClientHostLocatorTest { then(host.getIpv4()).isEqualTo(InetUtils.getIpAddressAsInt("localhost")); } + @Test + public void should_override_the_service_name_from_properties() throws Exception { + given(this.discoveryClient.getLocalServiceInstance()).willReturn(serviceInstanceWithValidHost()); + ZipkinProperties zipkinProperties = new ZipkinProperties(); + zipkinProperties.setName("foo"); + this.discoveryClientHostLocator = new DiscoveryClientHostLocator(this.discoveryClient, zipkinProperties); + + Host host = this.discoveryClientHostLocator.locate(null); + + then(host.getServiceName()).isEqualTo("foo"); + then(host.getPort()).isEqualTo((short)8_000); + then(host.getIpv4()).isEqualTo(InetUtils.getIpAddressAsInt("localhost")); + } + private ServiceInstance serviceInstanceWithInvalidHost() { return new ServiceInstance() { @Override public String getServiceId() { diff --git a/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/ServerPropertiesHostLocatorTests.java b/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/ServerPropertiesHostLocatorTests.java index 1027d30e1..a63c02268 100644 --- a/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/ServerPropertiesHostLocatorTests.java +++ b/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/ServerPropertiesHostLocatorTests.java @@ -33,7 +33,7 @@ public class ServerPropertiesHostLocatorTests { @Test public void portDefaultsTo8080() { ServerPropertiesHostLocator locator = new ServerPropertiesHostLocator( - new ServerProperties(), "unknown"); + new ServerProperties(), "unknown", new ZipkinProperties()); assertThat(locator.locate(this.span).getPort()).isEqualTo((short) 8080); } @@ -44,7 +44,7 @@ public class ServerPropertiesHostLocatorTests { properties.setPort(1234); ServerPropertiesHostLocator locator = new ServerPropertiesHostLocator(properties, - "unknown"); + "unknown", new ZipkinProperties()); assertThat(locator.locate(this.span).getPort()).isEqualTo((short) 1234); } @@ -52,7 +52,7 @@ public class ServerPropertiesHostLocatorTests { @Test public void portDefaultsToLocalhost() { ServerPropertiesHostLocator locator = new ServerPropertiesHostLocator( - new ServerProperties(), "unknown"); + new ServerProperties(), "unknown", new ZipkinProperties()); assertThat(locator.locate(this.span).getAddress()).isEqualTo("127.0.0.1"); } @@ -63,8 +63,21 @@ public class ServerPropertiesHostLocatorTests { properties.setAddress(InetAddress.getByAddress(new byte[] { 1, 2, 3, 4 })); ServerPropertiesHostLocator locator = new ServerPropertiesHostLocator(properties, - "unknown"); + "unknown", new ZipkinProperties()); assertThat(locator.locate(this.span).getAddress()).isEqualTo("1.2.3.4"); } + + @Test + public void nameTakenFromProperties() throws UnknownHostException { + ServerProperties properties = new ServerProperties(); + properties.setAddress(InetAddress.getByAddress(new byte[] { 1, 2, 3, 4 })); + ZipkinProperties zipkinProperties = new ZipkinProperties(); + zipkinProperties.setName("foo"); + + ServerPropertiesHostLocator locator = new ServerPropertiesHostLocator(properties, + "unknown", zipkinProperties); + + assertThat(locator.locate(this.span).getServiceName()).isEqualTo("foo"); + } } diff --git a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/DiscoveryClientEndpointLocator.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/DiscoveryClientEndpointLocator.java index 41de557a8..0cd49850a 100644 --- a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/DiscoveryClientEndpointLocator.java +++ b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/DiscoveryClientEndpointLocator.java @@ -16,24 +16,42 @@ package org.springframework.cloud.sleuth.zipkin; +import java.lang.invoke.MethodHandles; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.cloud.commons.util.InetUtils; +import org.springframework.util.StringUtils; + import zipkin.Endpoint; /** * An {@link EndpointLocator} that tries to find local service information from a * {@link DiscoveryClient}. * + * You can override the name using {@link ZipkinProperties.Service#setName(String)} + * * @author Dave Syer * @since 1.0.0 */ public class DiscoveryClientEndpointLocator implements EndpointLocator { - private DiscoveryClient client; + private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); + private final DiscoveryClient client; + private final ZipkinProperties zipkinProperties; + + @Deprecated public DiscoveryClientEndpointLocator(DiscoveryClient client) { + this(client, new ZipkinProperties()); + } + + public DiscoveryClientEndpointLocator(DiscoveryClient client, + ZipkinProperties zipkinProperties) { this.client = client; + this.zipkinProperties = zipkinProperties; } @Override @@ -42,8 +60,13 @@ public class DiscoveryClientEndpointLocator implements EndpointLocator { if (instance == null) { throw new NoServiceInstanceAvailableException(); } + String serviceName = StringUtils.hasText(this.zipkinProperties.getService().getName()) ? + this.zipkinProperties.getService().getName() : instance.getServiceId(); + if (log.isDebugEnabled()) { + log.debug("Span will contain serviceName [" + serviceName + "]"); + } return Endpoint.builder() - .serviceName(instance.getServiceId()) + .serviceName(serviceName) .ipv4(getIpAddress(instance)) .port(instance.getPort()).build(); } diff --git a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ServerPropertiesEndpointLocator.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ServerPropertiesEndpointLocator.java index 472dc70de..b148b5ff9 100644 --- a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ServerPropertiesEndpointLocator.java +++ b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ServerPropertiesEndpointLocator.java @@ -16,10 +16,15 @@ package org.springframework.cloud.sleuth.zipkin; +import java.lang.invoke.MethodHandles; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.context.embedded.EmbeddedServletContainerInitializedEvent; import org.springframework.cloud.commons.util.InetUtils; import org.springframework.context.event.EventListener; +import org.springframework.util.StringUtils; import zipkin.Endpoint; @@ -31,25 +36,42 @@ import zipkin.Endpoint; *
  • port - from lazily assigned port or {@link ServerProperties}
  • * * + * You can override the name using {@link ZipkinProperties.Service#setName(String)} + * * @author Dave Syer * @since 1.0.0 */ public class ServerPropertiesEndpointLocator implements EndpointLocator { + private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); + private final ServerProperties serverProperties; private final String appName; + private final ZipkinProperties zipkinProperties; private Integer port; + @Deprecated public ServerPropertiesEndpointLocator(ServerProperties serverProperties, - String appName) { + String appName) { + this(serverProperties, appName, new ZipkinProperties()); + } + + public ServerPropertiesEndpointLocator(ServerProperties serverProperties, + String appName, ZipkinProperties zipkinProperties) { this.serverProperties = serverProperties; this.appName = appName; + this.zipkinProperties = zipkinProperties; } @Override public Endpoint local() { + String serviceName = StringUtils.hasText(this.zipkinProperties.getService().getName()) ? + this.zipkinProperties.getService().getName() : this.appName; + if (log.isDebugEnabled()) { + log.debug("Span will contain serviceName [" + serviceName + "]"); + } return Endpoint.builder() - .serviceName(this.appName) + .serviceName(serviceName) .ipv4(getAddress()) .port(getPort()) .build(); diff --git a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinAutoConfiguration.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinAutoConfiguration.java index f6c95285a..0c4594544 100644 --- a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinAutoConfiguration.java +++ b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinAutoConfiguration.java @@ -75,12 +75,16 @@ public class ZipkinAutoConfiguration { @Autowired(required=false) private ServerProperties serverProperties; + @Autowired + private ZipkinProperties zipkinProperties; + @Value("${spring.application.name:unknown}") private String appName; @Bean public EndpointLocator zipkinEndpointLocator() { - return new ServerPropertiesEndpointLocator(this.serverProperties, this.appName); + return new ServerPropertiesEndpointLocator(this.serverProperties, this.appName, + this.zipkinProperties); } } @@ -92,6 +96,9 @@ public class ZipkinAutoConfiguration { @Autowired(required=false) private ServerProperties serverProperties; + @Autowired + private ZipkinProperties zipkinProperties; + @Value("${spring.application.name:unknown}") private String appName; @@ -101,12 +108,13 @@ public class ZipkinAutoConfiguration { @Bean public EndpointLocator zipkinEndpointLocator() { return new FallbackHavingEndpointLocator(discoveryClientEndpointLocator(), - new ServerPropertiesEndpointLocator(this.serverProperties, this.appName)); + new ServerPropertiesEndpointLocator(this.serverProperties, this.appName, + this.zipkinProperties)); } private DiscoveryClientEndpointLocator discoveryClientEndpointLocator() { if (this.client!=null) { - return new DiscoveryClientEndpointLocator(this.client); + return new DiscoveryClientEndpointLocator(this.client, this.zipkinProperties); } return null; } diff --git a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinProperties.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinProperties.java index 6071714ef..1a27fd047 100644 --- a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinProperties.java +++ b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinProperties.java @@ -32,6 +32,8 @@ public class ZipkinProperties { private int flushInterval = 1; private Compression compression = new Compression(); + private Service service = new Service(); + public String getBaseUrl() { return this.baseUrl; } @@ -48,6 +50,10 @@ public class ZipkinProperties { return this.compression; } + public Service getService() { + return this.service; + } + public void setBaseUrl(String baseUrl) { this.baseUrl = baseUrl; } @@ -64,6 +70,10 @@ public class ZipkinProperties { this.compression = compression; } + public void setService(Service service) { + this.service = service; + } + /** When enabled, spans are gzipped before sent to the zipkin server */ public static class Compression { @@ -77,4 +87,19 @@ public class ZipkinProperties { this.enabled = enabled; } } + + /** When set will override the default {@code spring.application.name} value of the service id */ + public static class Service { + + /** The name of the service, from which the Span was sent via HTTP, that should appear in Zipkin */ + private String name; + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + } } diff --git a/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/DiscoveryClientEndpointLocatorTest.java b/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/DiscoveryClientEndpointLocatorTest.java index 47e7b4368..f23903429 100644 --- a/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/DiscoveryClientEndpointLocatorTest.java +++ b/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/DiscoveryClientEndpointLocatorTest.java @@ -19,9 +19,9 @@ package org.springframework.cloud.sleuth.zipkin; import java.net.URI; import java.util.Map; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; import org.springframework.cloud.client.ServiceInstance; @@ -41,7 +41,12 @@ import static org.mockito.BDDMockito.given; public class DiscoveryClientEndpointLocatorTest { @Mock DiscoveryClient discoveryClient; - @InjectMocks DiscoveryClientEndpointLocator discoveryClientEndpointLocator; + DiscoveryClientEndpointLocator discoveryClientEndpointLocator; + + @Before + public void setup() { + this.discoveryClientEndpointLocator = new DiscoveryClientEndpointLocator(this.discoveryClient, new ZipkinProperties()); + } @Test(expected = NoServiceInstanceAvailableException.class) public void should_throw_exception_when_no_instances_are_available() throws Exception { @@ -70,6 +75,20 @@ public class DiscoveryClientEndpointLocatorTest { then(local.ipv4).isEqualTo(InetUtils.getIpAddressAsInt("localhost")); } + @Test + public void should_create_endpoint_with_overridden_name() throws Exception { + ZipkinProperties zipkinProperties = new ZipkinProperties(); + zipkinProperties.getService().setName("foo"); + DiscoveryClientEndpointLocator locator = new DiscoveryClientEndpointLocator(this.discoveryClient, zipkinProperties); + given(this.discoveryClient.getLocalServiceInstance()).willReturn(serviceInstanceWithValidHost()); + + Endpoint local = locator.local(); + + then(local.serviceName).isEqualTo("foo"); + then(local.port).isEqualTo((short)8_000); + then(local.ipv4).isEqualTo(InetUtils.getIpAddressAsInt("localhost")); + } + private ServiceInstance serviceInstanceWithInvalidHost() { return new ServiceInstance() { @Override public String getServiceId() { diff --git a/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/HttpZipkinSpanReporterTest.java b/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/HttpZipkinSpanReporterTest.java index c30ce440e..e5b0a9b1e 100644 --- a/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/HttpZipkinSpanReporterTest.java +++ b/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/HttpZipkinSpanReporterTest.java @@ -129,7 +129,8 @@ public class HttpZipkinSpanReporterTest { AtomicReference receivedSpan = new AtomicReference<>(); Tracer tracer = new DefaultTracer(new AlwaysSampler(), new Random(), new DefaultSpanNamer(), new NoOpSpanLogger(), new ZipkinSpanListener(receivedSpan::set, - new ServerPropertiesEndpointLocator(new ServerProperties(), "foo"))); + new ServerPropertiesEndpointLocator(new ServerProperties(), "foo", + new ZipkinProperties()))); // tag::service_name[] org.springframework.cloud.sleuth.Span newSpan = tracer.createSpan("redis"); try { diff --git a/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/ServerPropertiesEndpointLocatorTests.java b/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/ServerPropertiesEndpointLocatorTests.java index fb2a0e3a1..2614388f5 100644 --- a/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/ServerPropertiesEndpointLocatorTests.java +++ b/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/ServerPropertiesEndpointLocatorTests.java @@ -16,12 +16,12 @@ package org.springframework.cloud.sleuth.zipkin; -import org.junit.Test; -import org.springframework.boot.autoconfigure.web.ServerProperties; - import java.net.InetAddress; import java.net.UnknownHostException; +import org.junit.Test; +import org.springframework.boot.autoconfigure.web.ServerProperties; + import static org.assertj.core.api.Assertions.assertThat; public class ServerPropertiesEndpointLocatorTests { @@ -29,7 +29,7 @@ public class ServerPropertiesEndpointLocatorTests { @Test public void portDefaultsTo8080() { ServerPropertiesEndpointLocator locator = new ServerPropertiesEndpointLocator( - new ServerProperties(), "unknown"); + new ServerProperties(), "unknown", new ZipkinProperties()); assertThat(locator.local().port).isEqualTo((short) 8080); } @@ -40,7 +40,7 @@ public class ServerPropertiesEndpointLocatorTests { properties.setPort(1234); ServerPropertiesEndpointLocator locator = new ServerPropertiesEndpointLocator( - properties, "unknown"); + properties, "unknown", new ZipkinProperties()); assertThat(locator.local().port).isEqualTo((short) 1234); } @@ -48,7 +48,7 @@ public class ServerPropertiesEndpointLocatorTests { @Test public void portDefaultsToLocalhost() { ServerPropertiesEndpointLocator locator = new ServerPropertiesEndpointLocator( - new ServerProperties(), "unknown"); + new ServerProperties(), "unknown", new ZipkinProperties()); assertThat(locator.local().ipv4).isEqualTo(127 << 24 | 1); } @@ -59,8 +59,20 @@ public class ServerPropertiesEndpointLocatorTests { properties.setAddress(InetAddress.getByAddress(new byte[] { 1, 2, 3, 4 })); ServerPropertiesEndpointLocator locator = new ServerPropertiesEndpointLocator( - properties, "unknown"); + properties, "unknown", new ZipkinProperties()); assertThat(locator.local().ipv4).isEqualTo(1 << 24 | 2 << 16 | 3 << 8 | 4); } + + @Test + public void appNameFromProperties() throws UnknownHostException { + ServerProperties properties = new ServerProperties(); + ZipkinProperties zipkinProperties = new ZipkinProperties(); + zipkinProperties.getService().setName("foo"); + + ServerPropertiesEndpointLocator locator = new ServerPropertiesEndpointLocator( + properties, "unknown", zipkinProperties); + + assertThat(locator.local().serviceName).isEqualTo("foo"); + } } From e117531d7526d825b2392d29da8bc9318f79dcab Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Mon, 2 Jan 2017 10:18:20 +0100 Subject: [PATCH 187/235] Added property for overriding the service name in Zipkin without this only either service discovery service id / spring.application.name can be chosen as a service name for zipkin with this change you can pass spring.zipkin.service.name property to change override that both for HTTP and Stream collectors fixes #324 --- README.adoc | 7 ++-- .../main/asciidoc/spring-cloud-sleuth.adoc | 13 ++++++ .../stream/DiscoveryClientHostLocator.java | 16 +++++++- .../stream/ServerPropertiesHostLocator.java | 15 ++++++- .../stream/SleuthStreamAutoConfiguration.java | 14 +++++-- .../cloud/sleuth/stream/ZipkinProperties.java | 40 +++++++++++++++++++ .../DiscoveryClientHostLocatorTest.java | 18 ++++++++- .../ServerPropertiesHostLocatorTests.java | 21 ++++++++-- .../DiscoveryClientEndpointLocator.java | 27 ++++++++++++- .../ServerPropertiesEndpointLocator.java | 26 +++++++++++- .../zipkin/ZipkinAutoConfiguration.java | 14 +++++-- .../cloud/sleuth/zipkin/ZipkinProperties.java | 25 ++++++++++++ .../DiscoveryClientEndpointLocatorTest.java | 23 ++++++++++- .../zipkin/HttpZipkinSpanReporterTest.java | 3 +- .../ServerPropertiesEndpointLocatorTests.java | 26 ++++++++---- 15 files changed, 255 insertions(+), 33 deletions(-) create mode 100644 spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/ZipkinProperties.java diff --git a/README.adoc b/README.adoc index 18fd4ef5c..6ee18ce72 100644 --- a/README.adoc +++ b/README.adoc @@ -195,13 +195,13 @@ Below you can find an example of a Logback configuration (file named https://git ​ + value="%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr([${springAppName:-},%X{X-B3-TraceId:-},%X{X-B3-SpanId:-},%X{X-B3-ParentSpanId:-},%X{X-Span-Export:-}]){yellow} %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}"/> - INFO + DEBUG ${CONSOLE_LOG_PATTERN} @@ -241,6 +241,7 @@ Below you can find an example of a Logback configuration (file named https://git "service": "${springAppName:-}", "trace": "%X{X-B3-TraceId:-}", "span": "%X{X-B3-SpanId:-}", + "parent": "%X{X-B3-ParentSpanId:-}", "exportable": "%X{X-Span-Export:-}", "pid": "${PID:-}", "thread": "%thread", @@ -254,7 +255,7 @@ Below you can find an example of a Logback configuration (file named https://git ​ - + diff --git a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc index fb2be678a..446e24ca1 100644 --- a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc +++ b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc @@ -292,6 +292,19 @@ include::../../../..//spring-cloud-sleuth-zipkin/src/test/java/org/springframewo IMPORTANT: Remember not to add both `peer.service` tag and the `SA` tag! You have to add only `peer.service`. +=== Custom service name + +By default Sleuth assumes that when you send a span to Zipkin, you want the span's service name + to be equal to `spring.application.name` value. That's not always the case though. There + are situations in which you want to explicitly provide a different service name for all spans coming + from your application. To achieve that it's enough to just pass the following property + to your application to override that value (example for `foo` service name): + +[source,yaml] +---- +spring.zipkin.service.name: foo +---- + == Span Data as Messages You can accumulate and send span data over diff --git a/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/DiscoveryClientHostLocator.java b/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/DiscoveryClientHostLocator.java index 9b583b074..86b618f6e 100644 --- a/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/DiscoveryClientHostLocator.java +++ b/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/DiscoveryClientHostLocator.java @@ -22,27 +22,39 @@ import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.cloud.sleuth.Span; import org.springframework.util.Assert; +import org.springframework.util.StringUtils; /** * An {@link HostLocator} that tries to find local service information from a * {@link DiscoveryClient}. * + * You can override the value of service id by {@link ZipkinProperties#setName(String)} + * * @author Dave Syer * @since 1.0.0 */ public class DiscoveryClientHostLocator implements HostLocator { - private DiscoveryClient client; + private final DiscoveryClient client; + private final ZipkinProperties zipkinProperties; + @Deprecated public DiscoveryClientHostLocator(DiscoveryClient client) { + this(client, new ZipkinProperties()); + } + + public DiscoveryClientHostLocator(DiscoveryClient client, ZipkinProperties zipkinProperties) { this.client = client; Assert.notNull(this.client, "client"); + this.zipkinProperties = zipkinProperties; } @Override public Host locate(Span span) { ServiceInstance instance = this.client.getLocalServiceInstance(); - return new Host(instance.getServiceId(), getIpAddress(instance), + String serviceId = StringUtils.hasText(this.zipkinProperties.getName()) ? + this.zipkinProperties.getName() : instance.getServiceId(); + return new Host(serviceId, getIpAddress(instance), instance.getPort()); } diff --git a/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/ServerPropertiesHostLocator.java b/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/ServerPropertiesHostLocator.java index a471b63a9..37e33a582 100644 --- a/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/ServerPropertiesHostLocator.java +++ b/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/ServerPropertiesHostLocator.java @@ -21,6 +21,7 @@ import org.springframework.boot.context.embedded.EmbeddedServletContainerInitial import org.springframework.cloud.sleuth.Span; import org.springframework.context.event.EventListener; import org.springframework.util.Assert; +import org.springframework.util.StringUtils; /** * A {@link HostLocator} that retrieves: @@ -31,6 +32,8 @@ import org.springframework.util.Assert; *
  • port - from lazily assigned port or {@link ServerProperties}
  • * * + * You can override the value of service id by {@link ZipkinProperties#setName(String)} + * * @author Dave Syer * @since 1.0.0 */ @@ -38,13 +41,21 @@ public class ServerPropertiesHostLocator implements HostLocator { private final ServerProperties serverProperties; // Nullable private final String appName; + private final ZipkinProperties zipkinProperties; private Integer port; // Lazy assigned + @Deprecated public ServerPropertiesHostLocator(ServerProperties serverProperties, String appName) { + this(serverProperties, appName, new ZipkinProperties()); + } + + public ServerPropertiesHostLocator(ServerProperties serverProperties, + String appName, ZipkinProperties zipkinProperties) { this.serverProperties = serverProperties; this.appName = appName; Assert.notNull(this.appName, "appName"); + this.zipkinProperties = zipkinProperties; } @Override @@ -87,7 +98,9 @@ public class ServerPropertiesHostLocator implements HostLocator { private String getServiceName(Span span) { String serviceName; - if (span.getProcessId() != null) { + if (StringUtils.hasText(this.zipkinProperties.getName())) { + serviceName = this.zipkinProperties.getName(); + } else if (span.getProcessId() != null) { serviceName = span.getProcessId(); } else { diff --git a/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/SleuthStreamAutoConfiguration.java b/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/SleuthStreamAutoConfiguration.java index e69f04564..5ad57ba26 100644 --- a/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/SleuthStreamAutoConfiguration.java +++ b/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/SleuthStreamAutoConfiguration.java @@ -54,7 +54,7 @@ import org.springframework.scheduling.support.PeriodicTrigger; * @since 1.0.0 */ @Configuration -@EnableConfigurationProperties({ SleuthStreamProperties.class, SamplerProperties.class }) +@EnableConfigurationProperties({ SleuthStreamProperties.class, SamplerProperties.class, ZipkinProperties.class }) @AutoConfigureAfter(TraceMetricsAutoConfiguration.class) @AutoConfigureBefore(ChannelBindingAutoConfiguration.class) @EnableBinding(SleuthSource.class) @@ -96,12 +96,15 @@ public class SleuthStreamAutoConfiguration { @Autowired(required = false) private ServerProperties serverProperties; + @Autowired + private ZipkinProperties zipkinProperties; + @Value("${spring.application.name:unknown}") private String appName; @Bean public HostLocator zipkinEndpointLocator() { - return new ServerPropertiesHostLocator(this.serverProperties, this.appName); + return new ServerPropertiesHostLocator(this.serverProperties, this.appName, this.zipkinProperties); } } @@ -113,6 +116,9 @@ public class SleuthStreamAutoConfiguration { @Autowired(required = false) private ServerProperties serverProperties; + @Autowired + private ZipkinProperties zipkinProperties; + @Value("${spring.application.name:unknown}") private String appName; @@ -122,9 +128,9 @@ public class SleuthStreamAutoConfiguration { @Bean public HostLocator zipkinEndpointLocator() { if (this.client != null) { - return new DiscoveryClientHostLocator(this.client); + return new DiscoveryClientHostLocator(this.client, this.zipkinProperties); } - return new ServerPropertiesHostLocator(this.serverProperties, this.appName); + return new ServerPropertiesHostLocator(this.serverProperties, this.appName, this.zipkinProperties); } } diff --git a/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/ZipkinProperties.java b/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/ZipkinProperties.java new file mode 100644 index 000000000..700ee1023 --- /dev/null +++ b/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/ZipkinProperties.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.stream; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Zipkin settings for Zipkin Stream client + * + * @author Marcin Grzejszczak + * @since 1.0.12 + */ +@ConfigurationProperties("spring.zipkin.service") +public class ZipkinProperties { + + /** The name of the service, from which the Span was sent via Stream, that should appear in Zipkin */ + private String name; + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/DiscoveryClientHostLocatorTest.java b/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/DiscoveryClientHostLocatorTest.java index bed66f891..46f4bf72d 100644 --- a/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/DiscoveryClientHostLocatorTest.java +++ b/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/DiscoveryClientHostLocatorTest.java @@ -34,11 +34,11 @@ import static org.mockito.BDDMockito.given; public class DiscoveryClientHostLocatorTest { DiscoveryClient discoveryClient = Mockito.mock(DiscoveryClient.class); DiscoveryClientHostLocator discoveryClientHostLocator = - new DiscoveryClientHostLocator(this.discoveryClient); + new DiscoveryClientHostLocator(this.discoveryClient, new ZipkinProperties()); @Test(expected = IllegalArgumentException.class) public void should_throw_exception_when_no_discovery_client_is_present() throws Exception { - new DiscoveryClientHostLocator(null); + new DiscoveryClientHostLocator(null, new ZipkinProperties()); } @Test @@ -63,6 +63,20 @@ public class DiscoveryClientHostLocatorTest { then(host.getIpv4()).isEqualTo(InetUtils.getIpAddressAsInt("localhost")); } + @Test + public void should_override_the_service_name_from_properties() throws Exception { + given(this.discoveryClient.getLocalServiceInstance()).willReturn(serviceInstanceWithValidHost()); + ZipkinProperties zipkinProperties = new ZipkinProperties(); + zipkinProperties.setName("foo"); + this.discoveryClientHostLocator = new DiscoveryClientHostLocator(this.discoveryClient, zipkinProperties); + + Host host = this.discoveryClientHostLocator.locate(null); + + then(host.getServiceName()).isEqualTo("foo"); + then(host.getPort()).isEqualTo((short)8_000); + then(host.getIpv4()).isEqualTo(InetUtils.getIpAddressAsInt("localhost")); + } + private ServiceInstance serviceInstanceWithInvalidHost() { return new ServiceInstance() { @Override public String getServiceId() { diff --git a/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/ServerPropertiesHostLocatorTests.java b/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/ServerPropertiesHostLocatorTests.java index 1027d30e1..a63c02268 100644 --- a/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/ServerPropertiesHostLocatorTests.java +++ b/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/ServerPropertiesHostLocatorTests.java @@ -33,7 +33,7 @@ public class ServerPropertiesHostLocatorTests { @Test public void portDefaultsTo8080() { ServerPropertiesHostLocator locator = new ServerPropertiesHostLocator( - new ServerProperties(), "unknown"); + new ServerProperties(), "unknown", new ZipkinProperties()); assertThat(locator.locate(this.span).getPort()).isEqualTo((short) 8080); } @@ -44,7 +44,7 @@ public class ServerPropertiesHostLocatorTests { properties.setPort(1234); ServerPropertiesHostLocator locator = new ServerPropertiesHostLocator(properties, - "unknown"); + "unknown", new ZipkinProperties()); assertThat(locator.locate(this.span).getPort()).isEqualTo((short) 1234); } @@ -52,7 +52,7 @@ public class ServerPropertiesHostLocatorTests { @Test public void portDefaultsToLocalhost() { ServerPropertiesHostLocator locator = new ServerPropertiesHostLocator( - new ServerProperties(), "unknown"); + new ServerProperties(), "unknown", new ZipkinProperties()); assertThat(locator.locate(this.span).getAddress()).isEqualTo("127.0.0.1"); } @@ -63,8 +63,21 @@ public class ServerPropertiesHostLocatorTests { properties.setAddress(InetAddress.getByAddress(new byte[] { 1, 2, 3, 4 })); ServerPropertiesHostLocator locator = new ServerPropertiesHostLocator(properties, - "unknown"); + "unknown", new ZipkinProperties()); assertThat(locator.locate(this.span).getAddress()).isEqualTo("1.2.3.4"); } + + @Test + public void nameTakenFromProperties() throws UnknownHostException { + ServerProperties properties = new ServerProperties(); + properties.setAddress(InetAddress.getByAddress(new byte[] { 1, 2, 3, 4 })); + ZipkinProperties zipkinProperties = new ZipkinProperties(); + zipkinProperties.setName("foo"); + + ServerPropertiesHostLocator locator = new ServerPropertiesHostLocator(properties, + "unknown", zipkinProperties); + + assertThat(locator.locate(this.span).getServiceName()).isEqualTo("foo"); + } } diff --git a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/DiscoveryClientEndpointLocator.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/DiscoveryClientEndpointLocator.java index 41de557a8..0cd49850a 100644 --- a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/DiscoveryClientEndpointLocator.java +++ b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/DiscoveryClientEndpointLocator.java @@ -16,24 +16,42 @@ package org.springframework.cloud.sleuth.zipkin; +import java.lang.invoke.MethodHandles; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.cloud.commons.util.InetUtils; +import org.springframework.util.StringUtils; + import zipkin.Endpoint; /** * An {@link EndpointLocator} that tries to find local service information from a * {@link DiscoveryClient}. * + * You can override the name using {@link ZipkinProperties.Service#setName(String)} + * * @author Dave Syer * @since 1.0.0 */ public class DiscoveryClientEndpointLocator implements EndpointLocator { - private DiscoveryClient client; + private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); + private final DiscoveryClient client; + private final ZipkinProperties zipkinProperties; + + @Deprecated public DiscoveryClientEndpointLocator(DiscoveryClient client) { + this(client, new ZipkinProperties()); + } + + public DiscoveryClientEndpointLocator(DiscoveryClient client, + ZipkinProperties zipkinProperties) { this.client = client; + this.zipkinProperties = zipkinProperties; } @Override @@ -42,8 +60,13 @@ public class DiscoveryClientEndpointLocator implements EndpointLocator { if (instance == null) { throw new NoServiceInstanceAvailableException(); } + String serviceName = StringUtils.hasText(this.zipkinProperties.getService().getName()) ? + this.zipkinProperties.getService().getName() : instance.getServiceId(); + if (log.isDebugEnabled()) { + log.debug("Span will contain serviceName [" + serviceName + "]"); + } return Endpoint.builder() - .serviceName(instance.getServiceId()) + .serviceName(serviceName) .ipv4(getIpAddress(instance)) .port(instance.getPort()).build(); } diff --git a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ServerPropertiesEndpointLocator.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ServerPropertiesEndpointLocator.java index 472dc70de..b148b5ff9 100644 --- a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ServerPropertiesEndpointLocator.java +++ b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ServerPropertiesEndpointLocator.java @@ -16,10 +16,15 @@ package org.springframework.cloud.sleuth.zipkin; +import java.lang.invoke.MethodHandles; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.context.embedded.EmbeddedServletContainerInitializedEvent; import org.springframework.cloud.commons.util.InetUtils; import org.springframework.context.event.EventListener; +import org.springframework.util.StringUtils; import zipkin.Endpoint; @@ -31,25 +36,42 @@ import zipkin.Endpoint; *
  • port - from lazily assigned port or {@link ServerProperties}
  • * * + * You can override the name using {@link ZipkinProperties.Service#setName(String)} + * * @author Dave Syer * @since 1.0.0 */ public class ServerPropertiesEndpointLocator implements EndpointLocator { + private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); + private final ServerProperties serverProperties; private final String appName; + private final ZipkinProperties zipkinProperties; private Integer port; + @Deprecated public ServerPropertiesEndpointLocator(ServerProperties serverProperties, - String appName) { + String appName) { + this(serverProperties, appName, new ZipkinProperties()); + } + + public ServerPropertiesEndpointLocator(ServerProperties serverProperties, + String appName, ZipkinProperties zipkinProperties) { this.serverProperties = serverProperties; this.appName = appName; + this.zipkinProperties = zipkinProperties; } @Override public Endpoint local() { + String serviceName = StringUtils.hasText(this.zipkinProperties.getService().getName()) ? + this.zipkinProperties.getService().getName() : this.appName; + if (log.isDebugEnabled()) { + log.debug("Span will contain serviceName [" + serviceName + "]"); + } return Endpoint.builder() - .serviceName(this.appName) + .serviceName(serviceName) .ipv4(getAddress()) .port(getPort()) .build(); diff --git a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinAutoConfiguration.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinAutoConfiguration.java index f6c95285a..0c4594544 100644 --- a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinAutoConfiguration.java +++ b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinAutoConfiguration.java @@ -75,12 +75,16 @@ public class ZipkinAutoConfiguration { @Autowired(required=false) private ServerProperties serverProperties; + @Autowired + private ZipkinProperties zipkinProperties; + @Value("${spring.application.name:unknown}") private String appName; @Bean public EndpointLocator zipkinEndpointLocator() { - return new ServerPropertiesEndpointLocator(this.serverProperties, this.appName); + return new ServerPropertiesEndpointLocator(this.serverProperties, this.appName, + this.zipkinProperties); } } @@ -92,6 +96,9 @@ public class ZipkinAutoConfiguration { @Autowired(required=false) private ServerProperties serverProperties; + @Autowired + private ZipkinProperties zipkinProperties; + @Value("${spring.application.name:unknown}") private String appName; @@ -101,12 +108,13 @@ public class ZipkinAutoConfiguration { @Bean public EndpointLocator zipkinEndpointLocator() { return new FallbackHavingEndpointLocator(discoveryClientEndpointLocator(), - new ServerPropertiesEndpointLocator(this.serverProperties, this.appName)); + new ServerPropertiesEndpointLocator(this.serverProperties, this.appName, + this.zipkinProperties)); } private DiscoveryClientEndpointLocator discoveryClientEndpointLocator() { if (this.client!=null) { - return new DiscoveryClientEndpointLocator(this.client); + return new DiscoveryClientEndpointLocator(this.client, this.zipkinProperties); } return null; } diff --git a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinProperties.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinProperties.java index 6071714ef..1a27fd047 100644 --- a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinProperties.java +++ b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinProperties.java @@ -32,6 +32,8 @@ public class ZipkinProperties { private int flushInterval = 1; private Compression compression = new Compression(); + private Service service = new Service(); + public String getBaseUrl() { return this.baseUrl; } @@ -48,6 +50,10 @@ public class ZipkinProperties { return this.compression; } + public Service getService() { + return this.service; + } + public void setBaseUrl(String baseUrl) { this.baseUrl = baseUrl; } @@ -64,6 +70,10 @@ public class ZipkinProperties { this.compression = compression; } + public void setService(Service service) { + this.service = service; + } + /** When enabled, spans are gzipped before sent to the zipkin server */ public static class Compression { @@ -77,4 +87,19 @@ public class ZipkinProperties { this.enabled = enabled; } } + + /** When set will override the default {@code spring.application.name} value of the service id */ + public static class Service { + + /** The name of the service, from which the Span was sent via HTTP, that should appear in Zipkin */ + private String name; + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + } } diff --git a/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/DiscoveryClientEndpointLocatorTest.java b/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/DiscoveryClientEndpointLocatorTest.java index 47e7b4368..f23903429 100644 --- a/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/DiscoveryClientEndpointLocatorTest.java +++ b/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/DiscoveryClientEndpointLocatorTest.java @@ -19,9 +19,9 @@ package org.springframework.cloud.sleuth.zipkin; import java.net.URI; import java.util.Map; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; import org.springframework.cloud.client.ServiceInstance; @@ -41,7 +41,12 @@ import static org.mockito.BDDMockito.given; public class DiscoveryClientEndpointLocatorTest { @Mock DiscoveryClient discoveryClient; - @InjectMocks DiscoveryClientEndpointLocator discoveryClientEndpointLocator; + DiscoveryClientEndpointLocator discoveryClientEndpointLocator; + + @Before + public void setup() { + this.discoveryClientEndpointLocator = new DiscoveryClientEndpointLocator(this.discoveryClient, new ZipkinProperties()); + } @Test(expected = NoServiceInstanceAvailableException.class) public void should_throw_exception_when_no_instances_are_available() throws Exception { @@ -70,6 +75,20 @@ public class DiscoveryClientEndpointLocatorTest { then(local.ipv4).isEqualTo(InetUtils.getIpAddressAsInt("localhost")); } + @Test + public void should_create_endpoint_with_overridden_name() throws Exception { + ZipkinProperties zipkinProperties = new ZipkinProperties(); + zipkinProperties.getService().setName("foo"); + DiscoveryClientEndpointLocator locator = new DiscoveryClientEndpointLocator(this.discoveryClient, zipkinProperties); + given(this.discoveryClient.getLocalServiceInstance()).willReturn(serviceInstanceWithValidHost()); + + Endpoint local = locator.local(); + + then(local.serviceName).isEqualTo("foo"); + then(local.port).isEqualTo((short)8_000); + then(local.ipv4).isEqualTo(InetUtils.getIpAddressAsInt("localhost")); + } + private ServiceInstance serviceInstanceWithInvalidHost() { return new ServiceInstance() { @Override public String getServiceId() { diff --git a/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/HttpZipkinSpanReporterTest.java b/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/HttpZipkinSpanReporterTest.java index c30ce440e..e5b0a9b1e 100644 --- a/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/HttpZipkinSpanReporterTest.java +++ b/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/HttpZipkinSpanReporterTest.java @@ -129,7 +129,8 @@ public class HttpZipkinSpanReporterTest { AtomicReference receivedSpan = new AtomicReference<>(); Tracer tracer = new DefaultTracer(new AlwaysSampler(), new Random(), new DefaultSpanNamer(), new NoOpSpanLogger(), new ZipkinSpanListener(receivedSpan::set, - new ServerPropertiesEndpointLocator(new ServerProperties(), "foo"))); + new ServerPropertiesEndpointLocator(new ServerProperties(), "foo", + new ZipkinProperties()))); // tag::service_name[] org.springframework.cloud.sleuth.Span newSpan = tracer.createSpan("redis"); try { diff --git a/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/ServerPropertiesEndpointLocatorTests.java b/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/ServerPropertiesEndpointLocatorTests.java index fb2a0e3a1..2614388f5 100644 --- a/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/ServerPropertiesEndpointLocatorTests.java +++ b/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/ServerPropertiesEndpointLocatorTests.java @@ -16,12 +16,12 @@ package org.springframework.cloud.sleuth.zipkin; -import org.junit.Test; -import org.springframework.boot.autoconfigure.web.ServerProperties; - import java.net.InetAddress; import java.net.UnknownHostException; +import org.junit.Test; +import org.springframework.boot.autoconfigure.web.ServerProperties; + import static org.assertj.core.api.Assertions.assertThat; public class ServerPropertiesEndpointLocatorTests { @@ -29,7 +29,7 @@ public class ServerPropertiesEndpointLocatorTests { @Test public void portDefaultsTo8080() { ServerPropertiesEndpointLocator locator = new ServerPropertiesEndpointLocator( - new ServerProperties(), "unknown"); + new ServerProperties(), "unknown", new ZipkinProperties()); assertThat(locator.local().port).isEqualTo((short) 8080); } @@ -40,7 +40,7 @@ public class ServerPropertiesEndpointLocatorTests { properties.setPort(1234); ServerPropertiesEndpointLocator locator = new ServerPropertiesEndpointLocator( - properties, "unknown"); + properties, "unknown", new ZipkinProperties()); assertThat(locator.local().port).isEqualTo((short) 1234); } @@ -48,7 +48,7 @@ public class ServerPropertiesEndpointLocatorTests { @Test public void portDefaultsToLocalhost() { ServerPropertiesEndpointLocator locator = new ServerPropertiesEndpointLocator( - new ServerProperties(), "unknown"); + new ServerProperties(), "unknown", new ZipkinProperties()); assertThat(locator.local().ipv4).isEqualTo(127 << 24 | 1); } @@ -59,8 +59,20 @@ public class ServerPropertiesEndpointLocatorTests { properties.setAddress(InetAddress.getByAddress(new byte[] { 1, 2, 3, 4 })); ServerPropertiesEndpointLocator locator = new ServerPropertiesEndpointLocator( - properties, "unknown"); + properties, "unknown", new ZipkinProperties()); assertThat(locator.local().ipv4).isEqualTo(1 << 24 | 2 << 16 | 3 << 8 | 4); } + + @Test + public void appNameFromProperties() throws UnknownHostException { + ServerProperties properties = new ServerProperties(); + ZipkinProperties zipkinProperties = new ZipkinProperties(); + zipkinProperties.getService().setName("foo"); + + ServerPropertiesEndpointLocator locator = new ServerPropertiesEndpointLocator( + properties, "unknown", zipkinProperties); + + assertThat(locator.local().serviceName).isEqualTo("foo"); + } } From f085aae3b6b34cf7601a14676dbd4b53bea78097 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Mon, 2 Jan 2017 14:39:26 +0100 Subject: [PATCH 188/235] Added test for non web apps, fixed the autoconfig in that case without this change the non web apps can't start with this change the missing bean gets registered also in terms of non boot apps if there is no port or address set we're providing some default values. In terms of the service name one can always use the override via the spring.zipkin.service.name property fixes #32 --- .../web/TraceHttpAutoConfiguration.java | 46 +++++++++++++ .../web/TraceWebAutoConfiguration.java | 15 ++-- .../main/resources/META-INF/spring.factories | 1 + .../web/TraceNoWebEnvironmentTests.java | 68 +++++++++++++++++++ 4 files changed, 119 insertions(+), 11 deletions(-) create mode 100644 spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHttpAutoConfiguration.java create mode 100644 spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceNoWebEnvironmentTests.java diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHttpAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHttpAutoConfiguration.java new file mode 100644 index 000000000..b638cf4a2 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHttpAutoConfiguration.java @@ -0,0 +1,46 @@ +/* + * 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 org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +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; + +/** + * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration Auto-configuration} + * related to HTTP based communication. + * + * @author Marcin Grzejszczak + * @since 1.0.12 + */ +@Configuration +@ConditionalOnBean(Tracer.class) +@AutoConfigureAfter(TraceAutoConfiguration.class) +@EnableConfigurationProperties({ TraceKeys.class }) +public class TraceHttpAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + public HttpTraceKeysInjector httpTraceKeysInjector(Tracer tracer, TraceKeys traceKeys) { + return new HttpTraceKeysInjector(tracer, traceKeys); + } +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java index f017aa6ee..8645462f8 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java @@ -15,8 +15,8 @@ */ package org.springframework.cloud.sleuth.instrument.web; -import javax.servlet.http.HttpServletRequest; import java.util.regex.Pattern; +import javax.servlet.http.HttpServletRequest; import org.springframework.beans.factory.BeanFactory; import org.springframework.boot.actuate.autoconfigure.ManagementServerProperties; @@ -34,7 +34,6 @@ import org.springframework.cloud.sleuth.SpanNamer; import org.springframework.cloud.sleuth.SpanReporter; 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.context.annotation.Import; @@ -49,7 +48,7 @@ import static javax.servlet.DispatcherType.REQUEST; /** * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration Auto-configuration} - * enables tracing to HTTP requests. + * enables tracing to HTTP requests for Web based applications. * * @author Tomasz Nurkewicz, 4financeIT * @author Michal Chmielarz, 4financeIT @@ -61,8 +60,8 @@ import static javax.servlet.DispatcherType.REQUEST; @ConditionalOnProperty(value = "spring.sleuth.web.enabled", matchIfMissing = true) @ConditionalOnWebApplication @ConditionalOnBean(Tracer.class) -@AutoConfigureAfter(TraceAutoConfiguration.class) -@EnableConfigurationProperties({TraceKeys.class, SleuthWebProperties.class}) +@AutoConfigureAfter(TraceHttpAutoConfiguration.class) +@EnableConfigurationProperties({ SleuthWebProperties.class }) public class TraceWebAutoConfiguration { /** @@ -86,12 +85,6 @@ public class TraceWebAutoConfiguration { return new TraceSpringDataBeanPostProcessor(beanFactory); } - @Bean - @ConditionalOnMissingBean - public HttpTraceKeysInjector httpTraceKeysInjector(Tracer tracer, TraceKeys traceKeys) { - return new HttpTraceKeysInjector(tracer, traceKeys); - } - @Bean public FilterRegistrationBean traceWebFilter(Tracer tracer, TraceKeys traceKeys, SkipPatternProvider skipPatternProvider, SpanReporter spanReporter, diff --git a/spring-cloud-sleuth-core/src/main/resources/META-INF/spring.factories b/spring-cloud-sleuth-core/src/main/resources/META-INF/spring.factories index 54f0d68a8..0759e9413 100644 --- a/spring-cloud-sleuth-core/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-sleuth-core/src/main/resources/META-INF/spring.factories @@ -10,6 +10,7 @@ org.springframework.cloud.sleuth.instrument.async.AsyncCustomAutoConfiguration,\ org.springframework.cloud.sleuth.instrument.async.AsyncDefaultAutoConfiguration,\ org.springframework.cloud.sleuth.instrument.hystrix.SleuthHystrixAutoConfiguration,\ org.springframework.cloud.sleuth.instrument.scheduling.TraceSchedulingAutoConfiguration,\ +org.springframework.cloud.sleuth.instrument.web.TraceHttpAutoConfiguration,\ org.springframework.cloud.sleuth.instrument.web.TraceWebAutoConfiguration,\ org.springframework.cloud.sleuth.instrument.web.client.TraceWebClientAutoConfiguration,\ org.springframework.cloud.sleuth.instrument.web.client.TraceWebAsyncClientAutoConfiguration,\ diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceNoWebEnvironmentTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceNoWebEnvironmentTests.java new file mode 100644 index 000000000..afecfdede --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceNoWebEnvironmentTests.java @@ -0,0 +1,68 @@ +/* + * Copyright 2013-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.sleuth.instrument.web; + +import org.junit.Test; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker; +import org.springframework.cloud.netflix.feign.EnableFeignClients; +import org.springframework.cloud.netflix.feign.FeignClient; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +import static org.assertj.core.api.BDDAssertions.then; + +/** + * @author Marcin Grzejszczak + */ +public class TraceNoWebEnvironmentTests { + + // issue #32 + @Test + public void should_work_when_using_web_client_without_the_web_environment() { + SpringApplication springApplication = new SpringApplication(Config.class); + springApplication.setWebEnvironment(false); + + try (ConfigurableApplicationContext context = springApplication.run()) { + Config.SomeFeignClient client = context.getBean(Config.SomeFeignClient.class); + client.createSomeTestRequest(); + } + catch (Exception e) { + then(e.getCause().getClass()).isNotEqualTo(NoSuchBeanDefinitionException.class); + } + } + + @Configuration + @EnableAutoConfiguration + @EnableFeignClients + @EnableCircuitBreaker + public static class Config { + + + @FeignClient(name = "google", url = "https://www.google.com/") + public interface SomeFeignClient { + + @RequestMapping(value = "/", method = RequestMethod.GET) + String createSomeTestRequest(); + + } + } +} From 9d606021150995f26b0628ddea11a07602376e64 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Mon, 2 Jan 2017 14:39:26 +0100 Subject: [PATCH 189/235] Added test for non web apps, fixed the autoconfig in that case without this change the non web apps can't start with this change the missing bean gets registered also in terms of non boot apps if there is no port or address set we're providing some default values. In terms of the service name one can always use the override via the spring.zipkin.service.name property fixes #32 --- .../web/TraceHttpAutoConfiguration.java | 46 +++++++++++++ .../web/TraceWebAutoConfiguration.java | 15 ++-- .../main/resources/META-INF/spring.factories | 1 + .../web/TraceNoWebEnvironmentTests.java | 68 +++++++++++++++++++ 4 files changed, 119 insertions(+), 11 deletions(-) create mode 100644 spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHttpAutoConfiguration.java create mode 100644 spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceNoWebEnvironmentTests.java diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHttpAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHttpAutoConfiguration.java new file mode 100644 index 000000000..b638cf4a2 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHttpAutoConfiguration.java @@ -0,0 +1,46 @@ +/* + * 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 org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +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; + +/** + * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration Auto-configuration} + * related to HTTP based communication. + * + * @author Marcin Grzejszczak + * @since 1.0.12 + */ +@Configuration +@ConditionalOnBean(Tracer.class) +@AutoConfigureAfter(TraceAutoConfiguration.class) +@EnableConfigurationProperties({ TraceKeys.class }) +public class TraceHttpAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + public HttpTraceKeysInjector httpTraceKeysInjector(Tracer tracer, TraceKeys traceKeys) { + return new HttpTraceKeysInjector(tracer, traceKeys); + } +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java index f017aa6ee..8645462f8 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java @@ -15,8 +15,8 @@ */ package org.springframework.cloud.sleuth.instrument.web; -import javax.servlet.http.HttpServletRequest; import java.util.regex.Pattern; +import javax.servlet.http.HttpServletRequest; import org.springframework.beans.factory.BeanFactory; import org.springframework.boot.actuate.autoconfigure.ManagementServerProperties; @@ -34,7 +34,6 @@ import org.springframework.cloud.sleuth.SpanNamer; import org.springframework.cloud.sleuth.SpanReporter; 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.context.annotation.Import; @@ -49,7 +48,7 @@ import static javax.servlet.DispatcherType.REQUEST; /** * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration Auto-configuration} - * enables tracing to HTTP requests. + * enables tracing to HTTP requests for Web based applications. * * @author Tomasz Nurkewicz, 4financeIT * @author Michal Chmielarz, 4financeIT @@ -61,8 +60,8 @@ import static javax.servlet.DispatcherType.REQUEST; @ConditionalOnProperty(value = "spring.sleuth.web.enabled", matchIfMissing = true) @ConditionalOnWebApplication @ConditionalOnBean(Tracer.class) -@AutoConfigureAfter(TraceAutoConfiguration.class) -@EnableConfigurationProperties({TraceKeys.class, SleuthWebProperties.class}) +@AutoConfigureAfter(TraceHttpAutoConfiguration.class) +@EnableConfigurationProperties({ SleuthWebProperties.class }) public class TraceWebAutoConfiguration { /** @@ -86,12 +85,6 @@ public class TraceWebAutoConfiguration { return new TraceSpringDataBeanPostProcessor(beanFactory); } - @Bean - @ConditionalOnMissingBean - public HttpTraceKeysInjector httpTraceKeysInjector(Tracer tracer, TraceKeys traceKeys) { - return new HttpTraceKeysInjector(tracer, traceKeys); - } - @Bean public FilterRegistrationBean traceWebFilter(Tracer tracer, TraceKeys traceKeys, SkipPatternProvider skipPatternProvider, SpanReporter spanReporter, diff --git a/spring-cloud-sleuth-core/src/main/resources/META-INF/spring.factories b/spring-cloud-sleuth-core/src/main/resources/META-INF/spring.factories index 54f0d68a8..0759e9413 100644 --- a/spring-cloud-sleuth-core/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-sleuth-core/src/main/resources/META-INF/spring.factories @@ -10,6 +10,7 @@ org.springframework.cloud.sleuth.instrument.async.AsyncCustomAutoConfiguration,\ org.springframework.cloud.sleuth.instrument.async.AsyncDefaultAutoConfiguration,\ org.springframework.cloud.sleuth.instrument.hystrix.SleuthHystrixAutoConfiguration,\ org.springframework.cloud.sleuth.instrument.scheduling.TraceSchedulingAutoConfiguration,\ +org.springframework.cloud.sleuth.instrument.web.TraceHttpAutoConfiguration,\ org.springframework.cloud.sleuth.instrument.web.TraceWebAutoConfiguration,\ org.springframework.cloud.sleuth.instrument.web.client.TraceWebClientAutoConfiguration,\ org.springframework.cloud.sleuth.instrument.web.client.TraceWebAsyncClientAutoConfiguration,\ diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceNoWebEnvironmentTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceNoWebEnvironmentTests.java new file mode 100644 index 000000000..afecfdede --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceNoWebEnvironmentTests.java @@ -0,0 +1,68 @@ +/* + * Copyright 2013-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.sleuth.instrument.web; + +import org.junit.Test; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker; +import org.springframework.cloud.netflix.feign.EnableFeignClients; +import org.springframework.cloud.netflix.feign.FeignClient; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +import static org.assertj.core.api.BDDAssertions.then; + +/** + * @author Marcin Grzejszczak + */ +public class TraceNoWebEnvironmentTests { + + // issue #32 + @Test + public void should_work_when_using_web_client_without_the_web_environment() { + SpringApplication springApplication = new SpringApplication(Config.class); + springApplication.setWebEnvironment(false); + + try (ConfigurableApplicationContext context = springApplication.run()) { + Config.SomeFeignClient client = context.getBean(Config.SomeFeignClient.class); + client.createSomeTestRequest(); + } + catch (Exception e) { + then(e.getCause().getClass()).isNotEqualTo(NoSuchBeanDefinitionException.class); + } + } + + @Configuration + @EnableAutoConfiguration + @EnableFeignClients + @EnableCircuitBreaker + public static class Config { + + + @FeignClient(name = "google", url = "https://www.google.com/") + public interface SomeFeignClient { + + @RequestMapping(value = "/", method = RequestMethod.GET) + String createSomeTestRequest(); + + } + } +} From 9d8b2a279fa2d15704c701cab946d648c5bc1735 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Tue, 3 Jan 2017 14:20:56 +0100 Subject: [PATCH 190/235] Adding the spring.instance_id tag without this tag it's impossible to discern from which server was the given span originated with this change we're adding a tag in which we're passing the instance id value. The value can be taken either from Cloud Foundry or from a concatanation of some local properties like instance_id / application name / application port etc. fixes #369 --- spring-cloud-sleuth-core/pom.xml | 4 + .../springframework/cloud/sleuth/Span.java | 5 ++ .../sleuth/trace/DefaultTracerTests.java | 1 + .../stream/SleuthStreamAutoConfiguration.java | 5 +- .../sleuth/stream/StreamSpanReporter.java | 34 +++++++- .../stream/StreamSpanReporterTests.java | 61 +++++++++++--- .../zipkin/ZipkinAutoConfiguration.java | 7 +- .../sleuth/zipkin/ZipkinSpanListener.java | 81 +++++++++++++------ .../zipkin/ZipkinSpanListenerTests.java | 66 +++++++++++---- 9 files changed, 207 insertions(+), 57 deletions(-) diff --git a/spring-cloud-sleuth-core/pom.xml b/spring-cloud-sleuth-core/pom.xml index cff5f2c94..f7e1dad27 100644 --- a/spring-cloud-sleuth-core/pom.xml +++ b/spring-cloud-sleuth-core/pom.xml @@ -36,6 +36,10 @@ spring-boot-starter-actuator true + + org.springframework.cloud + spring-cloud-commons + org.springframework.cloud spring-cloud-starter-feign diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java index 31574f18e..4f793a3f1 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java @@ -134,6 +134,11 @@ public class Span { */ public static final String SPAN_PEER_SERVICE_TAG_NAME = "peer.service"; + /** + * ID of the instance from which the span was originated. + */ + public static final String INSTANCEID = "spring.instance_id"; + private final long begin; private long end = 0; private final String name; 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 74058665e..a17ef1959 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 @@ -183,6 +183,7 @@ public class DefaultTracerTests { then(span).hasATag("key", "value").hasLoggedAnEvent("event"); then(continuedSpan).hasATag("key", "value").hasLoggedAnEvent("event"); then(span).isEqualTo(continuedSpan); + tracer.close(span); } private Span assertSpan(List spans, Long parentId, String name) { diff --git a/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/SleuthStreamAutoConfiguration.java b/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/SleuthStreamAutoConfiguration.java index 5ad57ba26..cd71a167a 100644 --- a/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/SleuthStreamAutoConfiguration.java +++ b/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/SleuthStreamAutoConfiguration.java @@ -37,6 +37,7 @@ import org.springframework.cloud.stream.config.ChannelBindingAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; +import org.springframework.core.env.Environment; import org.springframework.integration.config.GlobalChannelInterceptor; import org.springframework.integration.scheduling.PollerMetadata; import org.springframework.messaging.support.ChannelInterceptor; @@ -76,8 +77,8 @@ public class SleuthStreamAutoConfiguration { @Bean @ConditionalOnMissingBean public StreamSpanReporter sleuthStreamSpanReporter(HostLocator endpointLocator, - SpanMetricReporter spanMetricReporter) { - return new StreamSpanReporter(endpointLocator, spanMetricReporter); + SpanMetricReporter spanMetricReporter, Environment environment) { + return new StreamSpanReporter(endpointLocator, spanMetricReporter, environment); } @Bean(name = StreamSpanReporter.POLLER) diff --git a/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/StreamSpanReporter.java b/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/StreamSpanReporter.java index 3ddcbeade..82199b541 100644 --- a/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/StreamSpanReporter.java +++ b/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/StreamSpanReporter.java @@ -17,17 +17,20 @@ package org.springframework.cloud.sleuth.stream; import java.lang.invoke.MethodHandles; +import java.util.Arrays; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; -import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.cloud.commons.util.IdUtils; +import org.springframework.cloud.sleuth.Log; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.SpanReporter; import org.springframework.cloud.sleuth.metric.SpanMetricReporter; +import org.springframework.core.env.Environment; import org.springframework.integration.annotation.InboundChannelAdapter; import org.springframework.integration.annotation.MessageEndpoint; import org.springframework.integration.annotation.Poller; @@ -41,7 +44,11 @@ import org.springframework.integration.annotation.Poller; @MessageEndpoint public class StreamSpanReporter implements SpanReporter { - private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); + private static final org.apache.commons.logging.Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); + + private static final List RPC_EVENTS = Arrays.asList( + Span.CLIENT_RECV, Span.CLIENT_SEND, Span.SERVER_RECV, Span.SERVER_SEND + ); /** * Bean name for the @@ -53,10 +60,19 @@ public class StreamSpanReporter implements SpanReporter { private BlockingQueue queue = new LinkedBlockingQueue<>(1000); private final HostLocator endpointLocator; private final SpanMetricReporter spanMetricReporter; + private final Environment environment; - public StreamSpanReporter(HostLocator endpointLocator, SpanMetricReporter spanMetricReporter) { + @Deprecated + public StreamSpanReporter(HostLocator endpointLocator, + SpanMetricReporter spanMetricReporter) { + this(endpointLocator, spanMetricReporter, null); + } + + public StreamSpanReporter(HostLocator endpointLocator, + SpanMetricReporter spanMetricReporter, Environment environment) { this.endpointLocator = endpointLocator; this.spanMetricReporter = spanMetricReporter; + this.environment = environment; } public void setQueue(BlockingQueue queue) { @@ -84,6 +100,9 @@ public class StreamSpanReporter implements SpanReporter { public void report(Span span) { if (span.isExportable()) { try { + if (this.environment != null) { + processLogs(span); + } this.queue.add(span); } catch (Exception e) { this.spanMetricReporter.incrementDroppedSpans(1); @@ -97,4 +116,13 @@ public class StreamSpanReporter implements SpanReporter { } } } + + private void processLogs(Span span) { + for (Log spanLog : span.logs()) { + if (RPC_EVENTS.contains(spanLog.getEvent())) { + span.tag(Span.INSTANCEID, IdUtils.getDefaultInstanceId(this.environment)); + } + } + } + } diff --git a/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/StreamSpanReporterTests.java b/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/StreamSpanReporterTests.java index 49c5402bf..30deb5f7c 100644 --- a/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/StreamSpanReporterTests.java +++ b/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/StreamSpanReporterTests.java @@ -16,27 +16,34 @@ package org.springframework.cloud.sleuth.stream; +import java.util.Map; import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import org.junit.Before; import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.Mockito; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.metric.SpanMetricReporter; +import org.springframework.mock.env.MockEnvironment; +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.then; /** * @author Marcin Grzejszczak */ -@RunWith(MockitoJUnitRunner.class) public class StreamSpanReporterTests { - @Mock HostLocator endpointLocator; - @Mock SpanMetricReporter spanMetricReporter; - @InjectMocks StreamSpanReporter reporter; + HostLocator endpointLocator = Mockito.mock(HostLocator.class); + SpanMetricReporter spanMetricReporter = Mockito.mock(SpanMetricReporter.class); + MockEnvironment mockEnvironment = new MockEnvironment(); + StreamSpanReporter reporter; + + @Before + public void setup() { + this.reporter = new StreamSpanReporter(this.endpointLocator, this.spanMetricReporter, this.mockEnvironment); + } @Test public void should_not_throw_an_exception_when_queue_size_is_exceeded() throws Exception { @@ -46,7 +53,43 @@ public class StreamSpanReporterTests { this.reporter.report(Span.builder().name("bar").exportable(true).build()); - then(spanMetricReporter).should().incrementDroppedSpans(1); + then(this.spanMetricReporter).should().incrementDroppedSpans(1); + } + + @Test + @SuppressWarnings("unchecked") + public void should_append_client_serviceid_when_span_has_rpc_event() throws Exception { + LinkedBlockingQueue queue = new LinkedBlockingQueue<>(1000); + this.reporter.setQueue(queue); + this.mockEnvironment.setProperty("vcap.application.instance_id", "foo"); + Span span = Span.builder().name("bar").exportable(true).build(); + span.logEvent(Span.CLIENT_RECV); + + this.reporter.report(span); + + assertThat(queue).isNotEmpty(); + assertThat(queue.poll()) + .extracting(Span::tags) + .extracting(o -> ((Map) o).get(Span.INSTANCEID)) + .containsExactly("foo"); + } + + @Test + @SuppressWarnings("unchecked") + public void should_not_append_server_serviceid_when_span_has_rpc_event_and_there_is_no_environment() throws Exception { + this.reporter = new StreamSpanReporter(this.endpointLocator, this.spanMetricReporter, null); + LinkedBlockingQueue queue = new LinkedBlockingQueue<>(1000); + this.reporter.setQueue(queue); + Span span = Span.builder().name("bar").exportable(true).build(); + span.logEvent(Span.CLIENT_SEND); + + this.reporter.report(span); + + assertThat(queue).isNotEmpty(); + assertThat(queue.poll()) + .extracting(Span::tags) + .filteredOn(o -> ((Map) o).containsKey(Span.INSTANCEID)) + .isNullOrEmpty(); } } \ No newline at end of file diff --git a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinAutoConfiguration.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinAutoConfiguration.java index 0c4594544..829f7aa33 100644 --- a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinAutoConfiguration.java +++ b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinAutoConfiguration.java @@ -34,7 +34,7 @@ import org.springframework.cloud.sleuth.sampler.PercentageBasedSampler; import org.springframework.cloud.sleuth.sampler.SamplerProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; - +import org.springframework.core.env.Environment; /** * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration Auto-configuration} @@ -64,8 +64,9 @@ public class ZipkinAutoConfiguration { } @Bean - public SpanReporter zipkinSpanListener(ZipkinSpanReporter reporter, EndpointLocator endpointLocator) { - return new ZipkinSpanListener(reporter, endpointLocator); + public SpanReporter zipkinSpanListener(ZipkinSpanReporter reporter, EndpointLocator endpointLocator, + Environment environment) { + return new ZipkinSpanListener(reporter, endpointLocator, environment); } @Configuration diff --git a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListener.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListener.java index e10ec4936..f00aa22d3 100644 --- a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListener.java +++ b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListener.java @@ -21,9 +21,11 @@ import java.util.Arrays; import java.util.List; import java.util.Map; +import org.springframework.cloud.commons.util.IdUtils; import org.springframework.cloud.sleuth.Log; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.SpanReporter; +import org.springframework.core.env.Environment; import org.springframework.util.StringUtils; import zipkin.Annotation; @@ -41,13 +43,17 @@ public class ZipkinSpanListener implements SpanReporter { private static final List ZIPKIN_START_EVENTS = Arrays.asList( Constants.CLIENT_RECV, Constants.SERVER_RECV ); + private static final List RPC_EVENTS = Arrays.asList( + Constants.CLIENT_RECV, Constants.CLIENT_SEND, Constants.SERVER_RECV, Constants.SERVER_SEND + ); private static final org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory .getLog(ZipkinSpanListener.class); private static final Charset UTF_8 = Charset.forName("UTF-8"); private static final byte[] UNKNOWN_BYTES = "unknown".getBytes(UTF_8); - private ZipkinSpanReporter reporter; + private final ZipkinSpanReporter reporter; + private final Environment environment; /** * Endpoint is the visible IP address of this service, the port it is listening on and * the service name from discovery. @@ -55,9 +61,16 @@ public class ZipkinSpanListener implements SpanReporter { // Visible for testing EndpointLocator endpointLocator; + @Deprecated public ZipkinSpanListener(ZipkinSpanReporter reporter, EndpointLocator endpointLocator) { + this(reporter, endpointLocator, null); + } + + public ZipkinSpanListener(ZipkinSpanReporter reporter, EndpointLocator endpointLocator, + Environment environment) { this.reporter = reporter; this.endpointLocator = endpointLocator; + this.environment = environment; } /** @@ -76,15 +89,10 @@ public class ZipkinSpanListener implements SpanReporter { zipkin.Span convert(Span span) { zipkin.Span.Builder zipkinSpan = zipkin.Span.builder(); - // A zipkin span without any annotations cannot be queried, add special "lc" to avoid that. - if (notClientOrServer(span)) { - ensureLocalComponent(span, zipkinSpan); - } - addZipkinAnnotations(zipkinSpan, span, this.endpointLocator.local()); - addZipkinBinaryAnnotations(zipkinSpan, span, this.endpointLocator.local()); - if (hasClientSend(span)) { - ensureServerAddr(span, zipkinSpan); - } + Endpoint endpoint = this.endpointLocator.local(); + processLogs(span, zipkinSpan, endpoint); + addZipkinAnnotations(zipkinSpan, span, endpoint); + addZipkinBinaryAnnotations(zipkinSpan, span, endpoint); // In the RPC span model, the client owns the timestamp and duration of the span. If we // were propagated an id, we can assume that we shouldn't report timestamp or duration, // rather let the client do that. Worst case we were propagated an unreported ID and @@ -134,22 +142,40 @@ public class ZipkinSpanListener implements SpanReporter { } } - private boolean notClientOrServer(Span span) { + // Instead of going through the list of logs multiple times we're doing it only once + private void processLogs(Span span, zipkin.Span.Builder zipkinSpan, Endpoint endpoint) { + boolean notClientOrServer = true; + boolean hasClientSend = false; + boolean instanceIdToTag = false; for (Log log : span.logs()) { + if (RPC_EVENTS.contains(log.getEvent())) { + instanceIdToTag = true; + } if (ZIPKIN_START_EVENTS.contains(log.getEvent())) { - return false; + notClientOrServer = false; + } + if (Constants.CLIENT_SEND.equals(log.getEvent())) { + hasClientSend = !span.tags().containsKey(Constants.SERVER_ADDR); } } - return true; + if (notClientOrServer) { + // A zipkin span without any annotations cannot be queried, add special "lc" to avoid that. + ensureLocalComponent(span, zipkinSpan); + } + if (hasClientSend) { + ensureServerAddr(span, zipkinSpan); + } + if (instanceIdToTag && this.environment != null) { + setInstanceIdIfPresent(zipkinSpan, endpoint, Span.INSTANCEID); + } } - private boolean hasClientSend(Span span) { - for (Log log : span.logs()) { - if (Constants.CLIENT_SEND.equals(log.getEvent())) { - return !span.tags().containsKey(Constants.SERVER_ADDR); - } + private void setInstanceIdIfPresent(zipkin.Span.Builder zipkinSpan, + Endpoint endpoint, String key) { + String property = IdUtils.getDefaultInstanceId(this.environment); + if (StringUtils.hasText(property)) { + addZipkinBinaryAnnotation(key, property, endpoint, zipkinSpan); } - return false; } /** @@ -172,15 +198,20 @@ public class ZipkinSpanListener implements SpanReporter { private void addZipkinBinaryAnnotations(zipkin.Span.Builder zipkinSpan, Span span, Endpoint ep) { for (Map.Entry e : span.tags().entrySet()) { - BinaryAnnotation binaryAnn = BinaryAnnotation.builder() - .type(BinaryAnnotation.Type.STRING) - .key(e.getKey()) - .value(e.getValue().getBytes(UTF_8)) - .endpoint(ep).build(); - zipkinSpan.addBinaryAnnotation(binaryAnn); + addZipkinBinaryAnnotation(e.getKey(), e.getValue(), ep, zipkinSpan); } } + private void addZipkinBinaryAnnotation(String key, String value, Endpoint ep, + zipkin.Span.Builder zipkinSpan) { + BinaryAnnotation binaryAnn = BinaryAnnotation.builder() + .type(BinaryAnnotation.Type.STRING) + .key(key) + .value(value.getBytes(UTF_8)) + .endpoint(ep).build(); + zipkinSpan.addBinaryAnnotation(binaryAnn); + } + /** * There could be instrumentation delay between span creation and the * semantic start of the span (client send). When there's a difference, diff --git a/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListenerTests.java b/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListenerTests.java index c38cc37c5..7cd41d5cf 100644 --- a/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListenerTests.java +++ b/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListenerTests.java @@ -34,6 +34,8 @@ import org.springframework.cloud.sleuth.zipkin.ZipkinSpanListenerTests.TestConfi import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.mock.env.MockEnvironment; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import zipkin.Constants; @@ -52,7 +54,10 @@ public class ZipkinSpanListenerTests { @Autowired Tracer tracer; @Autowired ApplicationContext application; @Autowired TestConfiguration test; - @Autowired ZipkinSpanListener spanReporter; + @Autowired ZipkinSpanListener spanListener; + @Autowired ZipkinSpanReporter spanReporter; + @Autowired MockEnvironment mockEnvironment; + @Autowired EndpointLocator endpointLocator; @PostConstruct public void init() { @@ -69,7 +74,7 @@ public class ZipkinSpanListenerTests { span.logEvent("hystrix/retry"); // System.currentTimeMillis span.stop(); - zipkin.Span result = this.spanReporter.convert(span); + zipkin.Span result = this.spanListener.convert(span); assertThat(result.timestamp) .isEqualTo(span.getBegin() * 1000); @@ -90,7 +95,7 @@ public class ZipkinSpanListenerTests { Thread.sleep(20); span.stop(); - zipkin.Span result = this.spanReporter.convert(span); + zipkin.Span result = this.spanListener.convert(span); assertThat(result.timestamp) .isEqualTo(span.getBegin() * 1000); @@ -107,7 +112,7 @@ public class ZipkinSpanListenerTests { @Test public void doesntSetDurationWhenStillRunning() { Span span = Span.builder().traceId(1L).name("http:api").build(); - zipkin.Span result = this.spanReporter.convert(span); + zipkin.Span result = this.spanListener.convert(span); assertThat(result.timestamp) .isGreaterThan(0); // sanity check it did start @@ -124,7 +129,7 @@ public class ZipkinSpanListenerTests { @Test public void doesntSetTimestampOrDurationWhenRemote() { this.parent.stop(); - zipkin.Span result = this.spanReporter.convert(this.parent); + zipkin.Span result = this.spanListener.convert(this.parent); assertThat(result.timestamp) .isNull(); @@ -138,10 +143,10 @@ public class ZipkinSpanListenerTests { this.parent.logEvent("hystrix/retry"); this.parent.tag("spring-boot/version", "1.3.1.RELEASE"); - zipkin.Span result = this.spanReporter.convert(this.parent); + zipkin.Span result = this.spanListener.convert(this.parent); assertThat(result.annotations.get(0).endpoint) - .isEqualTo(this.spanReporter.endpointLocator.local()); + .isEqualTo(this.spanListener.endpointLocator.local()); assertThat(result.binaryAnnotations.get(0).endpoint) .isEqualTo(result.annotations.get(0).endpoint); } @@ -149,7 +154,7 @@ public class ZipkinSpanListenerTests { /** zipkin's Endpoint.serviceName should never be null. */ @Test public void localEndpointIncludesServiceName() { - assertThat(this.spanReporter.endpointLocator.local().serviceName) + assertThat(this.spanListener.endpointLocator.local().serviceName) .isNotEmpty(); } @@ -172,7 +177,7 @@ public class ZipkinSpanListenerTests { Span context = this.tracer.createSpan("http:child", this.parent); context.logEvent(Span.CLIENT_SEND); logServerReceived(this.parent); - logServerSent(this.spanReporter, this.parent); + logServerSent(this.spanListener, this.parent); this.tracer.close(context); assertEquals(2, this.test.zipkinSpans.size()); } @@ -195,7 +200,7 @@ public class ZipkinSpanListenerTests { this.parent.logEvent("hystrix/retry"); this.parent.stop(); - zipkin.Span result = this.spanReporter.convert(this.parent); + zipkin.Span result = this.spanListener.convert(this.parent); assertThat(result.binaryAnnotations) .extracting(input -> input.key) @@ -208,7 +213,7 @@ public class ZipkinSpanListenerTests { this.parent.tag(Span.SPAN_PEER_SERVICE_TAG_NAME, "fooservice"); this.parent.stop(); - zipkin.Span result = this.spanReporter.convert(this.parent); + zipkin.Span result = this.spanListener.convert(this.parent); assertThat(result.binaryAnnotations) .filteredOn("key", Constants.SERVER_ADDR) @@ -220,7 +225,7 @@ public class ZipkinSpanListenerTests { this.parent.logEvent(Constants.CLIENT_SEND); this.parent.stop(); - zipkin.Span result = this.spanReporter.convert(this.parent); + zipkin.Span result = this.spanListener.convert(this.parent); assertThat(result.binaryAnnotations) .filteredOn("key", Constants.SERVER_ADDR) @@ -231,7 +236,7 @@ public class ZipkinSpanListenerTests { public void converts128BitTraceId() { Span span = Span.builder().traceIdHigh(1L).traceId(2L).spanId(3L).name("foo").build(); - zipkin.Span result = this.spanReporter.convert(span); + zipkin.Span result = this.spanListener.convert(span); assertThat(result.traceIdHigh).isEqualTo(span.getTraceIdHigh()); assertThat(result.traceId).isEqualTo(span.getTraceId()); @@ -243,7 +248,7 @@ public class ZipkinSpanListenerTests { this.parent.tag(Span.SPAN_PEER_SERVICE_TAG_NAME, "fooservice"); this.parent.stop(); - zipkin.Span result = this.spanReporter.convert(this.parent); + zipkin.Span result = this.spanListener.convert(this.parent); assertThat(result.binaryAnnotations) .filteredOn("key", Constants.SERVER_ADDR) @@ -255,11 +260,38 @@ public class ZipkinSpanListenerTests { public void shouldNotReportToZipkinWhenSpanIsNotExportable() { Span span = Span.builder().exportable(false).build(); - this.spanReporter.report(span); + this.spanListener.report(span); assertThat(this.test.zipkinSpans).isEmpty(); } + @Test + public void shouldAddClientServiceIdTagWhenSpanContainsRpcEvent() { + this.parent.logEvent(Span.CLIENT_SEND); + this.mockEnvironment.setProperty("vcap.application.instance_id", "foo"); + + zipkin.Span result = this.spanListener.convert(this.parent); + + assertThat(result.binaryAnnotations) + .filteredOn("key", Span.INSTANCEID) + .extracting(input -> input.value) + .containsOnly("foo".getBytes()); + } + + @Test + public void shouldNotAddAnyServiceIdTagWhenSpanContainsRpcEventAndThereIsNoEnvironment() { + this.parent.logEvent(Span.CLIENT_RECV); + ZipkinSpanListener spanListener = new ZipkinSpanListener(this.spanReporter, + this.endpointLocator, null); + + zipkin.Span result = spanListener.convert(this.parent); + + assertThat(result.binaryAnnotations) + .filteredOn("key", Span.INSTANCEID) + .extracting(input -> input.value) + .isEmpty(); + } + @Configuration @EnableAutoConfiguration protected static class TestConfiguration { @@ -276,6 +308,10 @@ public class ZipkinSpanListenerTests { return this.zipkinSpans::add; } + @Bean @Primary MockEnvironment mockEnvironment() { + return new MockEnvironment(); + } + } } From 2097acb486ee1e1851a76140b17367c00b78025a Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Tue, 3 Jan 2017 14:20:56 +0100 Subject: [PATCH 191/235] Adding the spring.instance_id tag without this tag it's impossible to discern from which server was the given span originated with this change we're adding a tag in which we're passing the instance id value. The value can be taken either from Cloud Foundry or from a concatanation of some local properties like instance_id / application name / application port etc. fixes #369 --- spring-cloud-sleuth-core/pom.xml | 4 + .../springframework/cloud/sleuth/Span.java | 5 ++ .../sleuth/trace/DefaultTracerTests.java | 1 + .../stream/SleuthStreamAutoConfiguration.java | 5 +- .../sleuth/stream/StreamSpanReporter.java | 34 +++++++- .../stream/StreamSpanReporterTests.java | 61 +++++++++++--- .../zipkin/ZipkinAutoConfiguration.java | 7 +- .../sleuth/zipkin/ZipkinSpanListener.java | 81 +++++++++++++------ .../zipkin/ZipkinSpanListenerTests.java | 66 +++++++++++---- 9 files changed, 207 insertions(+), 57 deletions(-) diff --git a/spring-cloud-sleuth-core/pom.xml b/spring-cloud-sleuth-core/pom.xml index ff7d6446f..2f5de0e91 100644 --- a/spring-cloud-sleuth-core/pom.xml +++ b/spring-cloud-sleuth-core/pom.xml @@ -36,6 +36,10 @@ spring-boot-starter-actuator true + + org.springframework.cloud + spring-cloud-commons + org.springframework.cloud spring-cloud-starter-feign diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java index 31574f18e..4f793a3f1 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java @@ -134,6 +134,11 @@ public class Span { */ public static final String SPAN_PEER_SERVICE_TAG_NAME = "peer.service"; + /** + * ID of the instance from which the span was originated. + */ + public static final String INSTANCEID = "spring.instance_id"; + private final long begin; private long end = 0; private final String name; 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 74058665e..a17ef1959 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 @@ -183,6 +183,7 @@ public class DefaultTracerTests { then(span).hasATag("key", "value").hasLoggedAnEvent("event"); then(continuedSpan).hasATag("key", "value").hasLoggedAnEvent("event"); then(span).isEqualTo(continuedSpan); + tracer.close(span); } private Span assertSpan(List spans, Long parentId, String name) { diff --git a/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/SleuthStreamAutoConfiguration.java b/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/SleuthStreamAutoConfiguration.java index 5ad57ba26..cd71a167a 100644 --- a/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/SleuthStreamAutoConfiguration.java +++ b/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/SleuthStreamAutoConfiguration.java @@ -37,6 +37,7 @@ import org.springframework.cloud.stream.config.ChannelBindingAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; +import org.springframework.core.env.Environment; import org.springframework.integration.config.GlobalChannelInterceptor; import org.springframework.integration.scheduling.PollerMetadata; import org.springframework.messaging.support.ChannelInterceptor; @@ -76,8 +77,8 @@ public class SleuthStreamAutoConfiguration { @Bean @ConditionalOnMissingBean public StreamSpanReporter sleuthStreamSpanReporter(HostLocator endpointLocator, - SpanMetricReporter spanMetricReporter) { - return new StreamSpanReporter(endpointLocator, spanMetricReporter); + SpanMetricReporter spanMetricReporter, Environment environment) { + return new StreamSpanReporter(endpointLocator, spanMetricReporter, environment); } @Bean(name = StreamSpanReporter.POLLER) diff --git a/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/StreamSpanReporter.java b/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/StreamSpanReporter.java index 3ddcbeade..82199b541 100644 --- a/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/StreamSpanReporter.java +++ b/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/StreamSpanReporter.java @@ -17,17 +17,20 @@ package org.springframework.cloud.sleuth.stream; import java.lang.invoke.MethodHandles; +import java.util.Arrays; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; -import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.cloud.commons.util.IdUtils; +import org.springframework.cloud.sleuth.Log; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.SpanReporter; import org.springframework.cloud.sleuth.metric.SpanMetricReporter; +import org.springframework.core.env.Environment; import org.springframework.integration.annotation.InboundChannelAdapter; import org.springframework.integration.annotation.MessageEndpoint; import org.springframework.integration.annotation.Poller; @@ -41,7 +44,11 @@ import org.springframework.integration.annotation.Poller; @MessageEndpoint public class StreamSpanReporter implements SpanReporter { - private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); + private static final org.apache.commons.logging.Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); + + private static final List RPC_EVENTS = Arrays.asList( + Span.CLIENT_RECV, Span.CLIENT_SEND, Span.SERVER_RECV, Span.SERVER_SEND + ); /** * Bean name for the @@ -53,10 +60,19 @@ public class StreamSpanReporter implements SpanReporter { private BlockingQueue queue = new LinkedBlockingQueue<>(1000); private final HostLocator endpointLocator; private final SpanMetricReporter spanMetricReporter; + private final Environment environment; - public StreamSpanReporter(HostLocator endpointLocator, SpanMetricReporter spanMetricReporter) { + @Deprecated + public StreamSpanReporter(HostLocator endpointLocator, + SpanMetricReporter spanMetricReporter) { + this(endpointLocator, spanMetricReporter, null); + } + + public StreamSpanReporter(HostLocator endpointLocator, + SpanMetricReporter spanMetricReporter, Environment environment) { this.endpointLocator = endpointLocator; this.spanMetricReporter = spanMetricReporter; + this.environment = environment; } public void setQueue(BlockingQueue queue) { @@ -84,6 +100,9 @@ public class StreamSpanReporter implements SpanReporter { public void report(Span span) { if (span.isExportable()) { try { + if (this.environment != null) { + processLogs(span); + } this.queue.add(span); } catch (Exception e) { this.spanMetricReporter.incrementDroppedSpans(1); @@ -97,4 +116,13 @@ public class StreamSpanReporter implements SpanReporter { } } } + + private void processLogs(Span span) { + for (Log spanLog : span.logs()) { + if (RPC_EVENTS.contains(spanLog.getEvent())) { + span.tag(Span.INSTANCEID, IdUtils.getDefaultInstanceId(this.environment)); + } + } + } + } diff --git a/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/StreamSpanReporterTests.java b/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/StreamSpanReporterTests.java index 49c5402bf..30deb5f7c 100644 --- a/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/StreamSpanReporterTests.java +++ b/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/StreamSpanReporterTests.java @@ -16,27 +16,34 @@ package org.springframework.cloud.sleuth.stream; +import java.util.Map; import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import org.junit.Before; import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.Mockito; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.metric.SpanMetricReporter; +import org.springframework.mock.env.MockEnvironment; +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.then; /** * @author Marcin Grzejszczak */ -@RunWith(MockitoJUnitRunner.class) public class StreamSpanReporterTests { - @Mock HostLocator endpointLocator; - @Mock SpanMetricReporter spanMetricReporter; - @InjectMocks StreamSpanReporter reporter; + HostLocator endpointLocator = Mockito.mock(HostLocator.class); + SpanMetricReporter spanMetricReporter = Mockito.mock(SpanMetricReporter.class); + MockEnvironment mockEnvironment = new MockEnvironment(); + StreamSpanReporter reporter; + + @Before + public void setup() { + this.reporter = new StreamSpanReporter(this.endpointLocator, this.spanMetricReporter, this.mockEnvironment); + } @Test public void should_not_throw_an_exception_when_queue_size_is_exceeded() throws Exception { @@ -46,7 +53,43 @@ public class StreamSpanReporterTests { this.reporter.report(Span.builder().name("bar").exportable(true).build()); - then(spanMetricReporter).should().incrementDroppedSpans(1); + then(this.spanMetricReporter).should().incrementDroppedSpans(1); + } + + @Test + @SuppressWarnings("unchecked") + public void should_append_client_serviceid_when_span_has_rpc_event() throws Exception { + LinkedBlockingQueue queue = new LinkedBlockingQueue<>(1000); + this.reporter.setQueue(queue); + this.mockEnvironment.setProperty("vcap.application.instance_id", "foo"); + Span span = Span.builder().name("bar").exportable(true).build(); + span.logEvent(Span.CLIENT_RECV); + + this.reporter.report(span); + + assertThat(queue).isNotEmpty(); + assertThat(queue.poll()) + .extracting(Span::tags) + .extracting(o -> ((Map) o).get(Span.INSTANCEID)) + .containsExactly("foo"); + } + + @Test + @SuppressWarnings("unchecked") + public void should_not_append_server_serviceid_when_span_has_rpc_event_and_there_is_no_environment() throws Exception { + this.reporter = new StreamSpanReporter(this.endpointLocator, this.spanMetricReporter, null); + LinkedBlockingQueue queue = new LinkedBlockingQueue<>(1000); + this.reporter.setQueue(queue); + Span span = Span.builder().name("bar").exportable(true).build(); + span.logEvent(Span.CLIENT_SEND); + + this.reporter.report(span); + + assertThat(queue).isNotEmpty(); + assertThat(queue.poll()) + .extracting(Span::tags) + .filteredOn(o -> ((Map) o).containsKey(Span.INSTANCEID)) + .isNullOrEmpty(); } } \ No newline at end of file diff --git a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinAutoConfiguration.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinAutoConfiguration.java index 0c4594544..829f7aa33 100644 --- a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinAutoConfiguration.java +++ b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinAutoConfiguration.java @@ -34,7 +34,7 @@ import org.springframework.cloud.sleuth.sampler.PercentageBasedSampler; import org.springframework.cloud.sleuth.sampler.SamplerProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; - +import org.springframework.core.env.Environment; /** * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration Auto-configuration} @@ -64,8 +64,9 @@ public class ZipkinAutoConfiguration { } @Bean - public SpanReporter zipkinSpanListener(ZipkinSpanReporter reporter, EndpointLocator endpointLocator) { - return new ZipkinSpanListener(reporter, endpointLocator); + public SpanReporter zipkinSpanListener(ZipkinSpanReporter reporter, EndpointLocator endpointLocator, + Environment environment) { + return new ZipkinSpanListener(reporter, endpointLocator, environment); } @Configuration diff --git a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListener.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListener.java index e10ec4936..f00aa22d3 100644 --- a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListener.java +++ b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListener.java @@ -21,9 +21,11 @@ import java.util.Arrays; import java.util.List; import java.util.Map; +import org.springframework.cloud.commons.util.IdUtils; import org.springframework.cloud.sleuth.Log; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.SpanReporter; +import org.springframework.core.env.Environment; import org.springframework.util.StringUtils; import zipkin.Annotation; @@ -41,13 +43,17 @@ public class ZipkinSpanListener implements SpanReporter { private static final List ZIPKIN_START_EVENTS = Arrays.asList( Constants.CLIENT_RECV, Constants.SERVER_RECV ); + private static final List RPC_EVENTS = Arrays.asList( + Constants.CLIENT_RECV, Constants.CLIENT_SEND, Constants.SERVER_RECV, Constants.SERVER_SEND + ); private static final org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory .getLog(ZipkinSpanListener.class); private static final Charset UTF_8 = Charset.forName("UTF-8"); private static final byte[] UNKNOWN_BYTES = "unknown".getBytes(UTF_8); - private ZipkinSpanReporter reporter; + private final ZipkinSpanReporter reporter; + private final Environment environment; /** * Endpoint is the visible IP address of this service, the port it is listening on and * the service name from discovery. @@ -55,9 +61,16 @@ public class ZipkinSpanListener implements SpanReporter { // Visible for testing EndpointLocator endpointLocator; + @Deprecated public ZipkinSpanListener(ZipkinSpanReporter reporter, EndpointLocator endpointLocator) { + this(reporter, endpointLocator, null); + } + + public ZipkinSpanListener(ZipkinSpanReporter reporter, EndpointLocator endpointLocator, + Environment environment) { this.reporter = reporter; this.endpointLocator = endpointLocator; + this.environment = environment; } /** @@ -76,15 +89,10 @@ public class ZipkinSpanListener implements SpanReporter { zipkin.Span convert(Span span) { zipkin.Span.Builder zipkinSpan = zipkin.Span.builder(); - // A zipkin span without any annotations cannot be queried, add special "lc" to avoid that. - if (notClientOrServer(span)) { - ensureLocalComponent(span, zipkinSpan); - } - addZipkinAnnotations(zipkinSpan, span, this.endpointLocator.local()); - addZipkinBinaryAnnotations(zipkinSpan, span, this.endpointLocator.local()); - if (hasClientSend(span)) { - ensureServerAddr(span, zipkinSpan); - } + Endpoint endpoint = this.endpointLocator.local(); + processLogs(span, zipkinSpan, endpoint); + addZipkinAnnotations(zipkinSpan, span, endpoint); + addZipkinBinaryAnnotations(zipkinSpan, span, endpoint); // In the RPC span model, the client owns the timestamp and duration of the span. If we // were propagated an id, we can assume that we shouldn't report timestamp or duration, // rather let the client do that. Worst case we were propagated an unreported ID and @@ -134,22 +142,40 @@ public class ZipkinSpanListener implements SpanReporter { } } - private boolean notClientOrServer(Span span) { + // Instead of going through the list of logs multiple times we're doing it only once + private void processLogs(Span span, zipkin.Span.Builder zipkinSpan, Endpoint endpoint) { + boolean notClientOrServer = true; + boolean hasClientSend = false; + boolean instanceIdToTag = false; for (Log log : span.logs()) { + if (RPC_EVENTS.contains(log.getEvent())) { + instanceIdToTag = true; + } if (ZIPKIN_START_EVENTS.contains(log.getEvent())) { - return false; + notClientOrServer = false; + } + if (Constants.CLIENT_SEND.equals(log.getEvent())) { + hasClientSend = !span.tags().containsKey(Constants.SERVER_ADDR); } } - return true; + if (notClientOrServer) { + // A zipkin span without any annotations cannot be queried, add special "lc" to avoid that. + ensureLocalComponent(span, zipkinSpan); + } + if (hasClientSend) { + ensureServerAddr(span, zipkinSpan); + } + if (instanceIdToTag && this.environment != null) { + setInstanceIdIfPresent(zipkinSpan, endpoint, Span.INSTANCEID); + } } - private boolean hasClientSend(Span span) { - for (Log log : span.logs()) { - if (Constants.CLIENT_SEND.equals(log.getEvent())) { - return !span.tags().containsKey(Constants.SERVER_ADDR); - } + private void setInstanceIdIfPresent(zipkin.Span.Builder zipkinSpan, + Endpoint endpoint, String key) { + String property = IdUtils.getDefaultInstanceId(this.environment); + if (StringUtils.hasText(property)) { + addZipkinBinaryAnnotation(key, property, endpoint, zipkinSpan); } - return false; } /** @@ -172,15 +198,20 @@ public class ZipkinSpanListener implements SpanReporter { private void addZipkinBinaryAnnotations(zipkin.Span.Builder zipkinSpan, Span span, Endpoint ep) { for (Map.Entry e : span.tags().entrySet()) { - BinaryAnnotation binaryAnn = BinaryAnnotation.builder() - .type(BinaryAnnotation.Type.STRING) - .key(e.getKey()) - .value(e.getValue().getBytes(UTF_8)) - .endpoint(ep).build(); - zipkinSpan.addBinaryAnnotation(binaryAnn); + addZipkinBinaryAnnotation(e.getKey(), e.getValue(), ep, zipkinSpan); } } + private void addZipkinBinaryAnnotation(String key, String value, Endpoint ep, + zipkin.Span.Builder zipkinSpan) { + BinaryAnnotation binaryAnn = BinaryAnnotation.builder() + .type(BinaryAnnotation.Type.STRING) + .key(key) + .value(value.getBytes(UTF_8)) + .endpoint(ep).build(); + zipkinSpan.addBinaryAnnotation(binaryAnn); + } + /** * There could be instrumentation delay between span creation and the * semantic start of the span (client send). When there's a difference, diff --git a/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListenerTests.java b/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListenerTests.java index c38cc37c5..7cd41d5cf 100644 --- a/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListenerTests.java +++ b/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListenerTests.java @@ -34,6 +34,8 @@ import org.springframework.cloud.sleuth.zipkin.ZipkinSpanListenerTests.TestConfi import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.mock.env.MockEnvironment; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import zipkin.Constants; @@ -52,7 +54,10 @@ public class ZipkinSpanListenerTests { @Autowired Tracer tracer; @Autowired ApplicationContext application; @Autowired TestConfiguration test; - @Autowired ZipkinSpanListener spanReporter; + @Autowired ZipkinSpanListener spanListener; + @Autowired ZipkinSpanReporter spanReporter; + @Autowired MockEnvironment mockEnvironment; + @Autowired EndpointLocator endpointLocator; @PostConstruct public void init() { @@ -69,7 +74,7 @@ public class ZipkinSpanListenerTests { span.logEvent("hystrix/retry"); // System.currentTimeMillis span.stop(); - zipkin.Span result = this.spanReporter.convert(span); + zipkin.Span result = this.spanListener.convert(span); assertThat(result.timestamp) .isEqualTo(span.getBegin() * 1000); @@ -90,7 +95,7 @@ public class ZipkinSpanListenerTests { Thread.sleep(20); span.stop(); - zipkin.Span result = this.spanReporter.convert(span); + zipkin.Span result = this.spanListener.convert(span); assertThat(result.timestamp) .isEqualTo(span.getBegin() * 1000); @@ -107,7 +112,7 @@ public class ZipkinSpanListenerTests { @Test public void doesntSetDurationWhenStillRunning() { Span span = Span.builder().traceId(1L).name("http:api").build(); - zipkin.Span result = this.spanReporter.convert(span); + zipkin.Span result = this.spanListener.convert(span); assertThat(result.timestamp) .isGreaterThan(0); // sanity check it did start @@ -124,7 +129,7 @@ public class ZipkinSpanListenerTests { @Test public void doesntSetTimestampOrDurationWhenRemote() { this.parent.stop(); - zipkin.Span result = this.spanReporter.convert(this.parent); + zipkin.Span result = this.spanListener.convert(this.parent); assertThat(result.timestamp) .isNull(); @@ -138,10 +143,10 @@ public class ZipkinSpanListenerTests { this.parent.logEvent("hystrix/retry"); this.parent.tag("spring-boot/version", "1.3.1.RELEASE"); - zipkin.Span result = this.spanReporter.convert(this.parent); + zipkin.Span result = this.spanListener.convert(this.parent); assertThat(result.annotations.get(0).endpoint) - .isEqualTo(this.spanReporter.endpointLocator.local()); + .isEqualTo(this.spanListener.endpointLocator.local()); assertThat(result.binaryAnnotations.get(0).endpoint) .isEqualTo(result.annotations.get(0).endpoint); } @@ -149,7 +154,7 @@ public class ZipkinSpanListenerTests { /** zipkin's Endpoint.serviceName should never be null. */ @Test public void localEndpointIncludesServiceName() { - assertThat(this.spanReporter.endpointLocator.local().serviceName) + assertThat(this.spanListener.endpointLocator.local().serviceName) .isNotEmpty(); } @@ -172,7 +177,7 @@ public class ZipkinSpanListenerTests { Span context = this.tracer.createSpan("http:child", this.parent); context.logEvent(Span.CLIENT_SEND); logServerReceived(this.parent); - logServerSent(this.spanReporter, this.parent); + logServerSent(this.spanListener, this.parent); this.tracer.close(context); assertEquals(2, this.test.zipkinSpans.size()); } @@ -195,7 +200,7 @@ public class ZipkinSpanListenerTests { this.parent.logEvent("hystrix/retry"); this.parent.stop(); - zipkin.Span result = this.spanReporter.convert(this.parent); + zipkin.Span result = this.spanListener.convert(this.parent); assertThat(result.binaryAnnotations) .extracting(input -> input.key) @@ -208,7 +213,7 @@ public class ZipkinSpanListenerTests { this.parent.tag(Span.SPAN_PEER_SERVICE_TAG_NAME, "fooservice"); this.parent.stop(); - zipkin.Span result = this.spanReporter.convert(this.parent); + zipkin.Span result = this.spanListener.convert(this.parent); assertThat(result.binaryAnnotations) .filteredOn("key", Constants.SERVER_ADDR) @@ -220,7 +225,7 @@ public class ZipkinSpanListenerTests { this.parent.logEvent(Constants.CLIENT_SEND); this.parent.stop(); - zipkin.Span result = this.spanReporter.convert(this.parent); + zipkin.Span result = this.spanListener.convert(this.parent); assertThat(result.binaryAnnotations) .filteredOn("key", Constants.SERVER_ADDR) @@ -231,7 +236,7 @@ public class ZipkinSpanListenerTests { public void converts128BitTraceId() { Span span = Span.builder().traceIdHigh(1L).traceId(2L).spanId(3L).name("foo").build(); - zipkin.Span result = this.spanReporter.convert(span); + zipkin.Span result = this.spanListener.convert(span); assertThat(result.traceIdHigh).isEqualTo(span.getTraceIdHigh()); assertThat(result.traceId).isEqualTo(span.getTraceId()); @@ -243,7 +248,7 @@ public class ZipkinSpanListenerTests { this.parent.tag(Span.SPAN_PEER_SERVICE_TAG_NAME, "fooservice"); this.parent.stop(); - zipkin.Span result = this.spanReporter.convert(this.parent); + zipkin.Span result = this.spanListener.convert(this.parent); assertThat(result.binaryAnnotations) .filteredOn("key", Constants.SERVER_ADDR) @@ -255,11 +260,38 @@ public class ZipkinSpanListenerTests { public void shouldNotReportToZipkinWhenSpanIsNotExportable() { Span span = Span.builder().exportable(false).build(); - this.spanReporter.report(span); + this.spanListener.report(span); assertThat(this.test.zipkinSpans).isEmpty(); } + @Test + public void shouldAddClientServiceIdTagWhenSpanContainsRpcEvent() { + this.parent.logEvent(Span.CLIENT_SEND); + this.mockEnvironment.setProperty("vcap.application.instance_id", "foo"); + + zipkin.Span result = this.spanListener.convert(this.parent); + + assertThat(result.binaryAnnotations) + .filteredOn("key", Span.INSTANCEID) + .extracting(input -> input.value) + .containsOnly("foo".getBytes()); + } + + @Test + public void shouldNotAddAnyServiceIdTagWhenSpanContainsRpcEventAndThereIsNoEnvironment() { + this.parent.logEvent(Span.CLIENT_RECV); + ZipkinSpanListener spanListener = new ZipkinSpanListener(this.spanReporter, + this.endpointLocator, null); + + zipkin.Span result = spanListener.convert(this.parent); + + assertThat(result.binaryAnnotations) + .filteredOn("key", Span.INSTANCEID) + .extracting(input -> input.value) + .isEmpty(); + } + @Configuration @EnableAutoConfiguration protected static class TestConfiguration { @@ -276,6 +308,10 @@ public class ZipkinSpanListenerTests { return this.zipkinSpans::add; } + @Bean @Primary MockEnvironment mockEnvironment() { + return new MockEnvironment(); + } + } } From e4546afd495e961ee5628ad48e6814bec3d4d90e Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Mon, 9 Jan 2017 16:22:49 +0100 Subject: [PATCH 192/235] Bumping versions before release --- benchmarks/pom.xml | 2 +- docs/pom.xml | 2 +- pom.xml | 12 ++++++------ spring-cloud-sleuth-core/pom.xml | 2 +- spring-cloud-sleuth-dependencies/pom.xml | 4 ++-- spring-cloud-sleuth-samples/pom.xml | 2 +- .../spring-cloud-sleuth-sample-feign/pom.xml | 2 +- .../spring-cloud-sleuth-sample-messaging/pom.xml | 2 +- .../spring-cloud-sleuth-sample-ribbon/pom.xml | 2 +- .../spring-cloud-sleuth-sample-stream/pom.xml | 2 +- .../spring-cloud-sleuth-sample-test-core/pom.xml | 2 +- .../spring-cloud-sleuth-sample-websocket/pom.xml | 2 +- .../spring-cloud-sleuth-sample-zipkin-stream/pom.xml | 2 +- .../spring-cloud-sleuth-sample-zipkin/pom.xml | 2 +- .../spring-cloud-sleuth-sample/pom.xml | 2 +- spring-cloud-sleuth-stream/pom.xml | 2 +- spring-cloud-sleuth-zipkin-stream/pom.xml | 2 +- spring-cloud-sleuth-zipkin/pom.xml | 2 +- spring-cloud-starter-sleuth/pom.xml | 2 +- spring-cloud-starter-zipkin/pom.xml | 2 +- 20 files changed, 26 insertions(+), 26 deletions(-) diff --git a/benchmarks/pom.xml b/benchmarks/pom.xml index 5f21d7276..81331e52c 100644 --- a/benchmarks/pom.xml +++ b/benchmarks/pom.xml @@ -17,7 +17,7 @@ Benchmarks Benchmarks (JMH) org.springframework.cloud - 1.0.12.BUILD-SNAPSHOT + 1.0.12.RELEASE benchmarks diff --git a/docs/pom.xml b/docs/pom.xml index 65d6b68f9..2b3fb2bc8 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.12.BUILD-SNAPSHOT + 1.0.12.RELEASE spring-cloud-sleuth-docs pom diff --git a/pom.xml b/pom.xml index f34815422..33ea1ba22 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 spring-cloud-sleuth - 1.0.12.BUILD-SNAPSHOT + 1.0.12.RELEASE pom Spring Cloud Sleuth Spring Cloud Sleuth @@ -13,7 +13,7 @@ org.springframework.cloud spring-cloud-build - 1.1.3.BUILD-SNAPSHOT + 1.1.2.RELEASE @@ -229,10 +229,10 @@ 1.8 2.19.1 2.17 - 1.1.3.BUILD-SNAPSHOT - 1.1.3.BUILD-SNAPSHOT - Brooklyn.BUILD-SNAPSHOT - 1.1.8.BUILD-SNAPSHOT + 1.1.2.RELEASE + 1.1.3.RELEASE + Brooklyn.RELEASE + 1.1.7.RELEASE diff --git a/spring-cloud-sleuth-core/pom.xml b/spring-cloud-sleuth-core/pom.xml index f7e1dad27..36eab35f9 100644 --- a/spring-cloud-sleuth-core/pom.xml +++ b/spring-cloud-sleuth-core/pom.xml @@ -11,7 +11,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.12.BUILD-SNAPSHOT + 1.0.12.RELEASE .. diff --git a/spring-cloud-sleuth-dependencies/pom.xml b/spring-cloud-sleuth-dependencies/pom.xml index db43c722f..db9868565 100644 --- a/spring-cloud-sleuth-dependencies/pom.xml +++ b/spring-cloud-sleuth-dependencies/pom.xml @@ -5,11 +5,11 @@ spring-cloud-dependencies-parent org.springframework.cloud - 1.1.3.BUILD-SNAPSHOT + 1.1.2.RELEASE spring-cloud-sleuth-dependencies - 1.0.12.BUILD-SNAPSHOT + 1.0.12.RELEASE pom spring-cloud-sleuth-dependencies Spring Cloud Sleuth Dependencies diff --git a/spring-cloud-sleuth-samples/pom.xml b/spring-cloud-sleuth-samples/pom.xml index 4564e1882..680893183 100644 --- a/spring-cloud-sleuth-samples/pom.xml +++ b/spring-cloud-sleuth-samples/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.12.BUILD-SNAPSHOT + 1.0.12.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml index b9ed14f34..8929d86a2 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml @@ -20,7 +20,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.12.BUILD-SNAPSHOT + 1.0.12.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml index c5e60be4e..b447aff99 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.12.BUILD-SNAPSHOT + 1.0.12.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml index a3558d75a..802f21ebe 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.12.BUILD-SNAPSHOT + 1.0.12.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml index 57d65e294..6246c28f9 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.12.BUILD-SNAPSHOT + 1.0.12.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml index 445b4bcc3..194902b88 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.12.BUILD-SNAPSHOT + 1.0.12.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml index 9d5247e7d..0f88fba42 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.12.BUILD-SNAPSHOT + 1.0.12.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml index 3c3388749..ecec6b28e 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.12.BUILD-SNAPSHOT + 1.0.12.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml index 5bf157a98..9c2349812 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml @@ -27,7 +27,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.12.BUILD-SNAPSHOT + 1.0.12.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml index 8043c7b08..ce0fee52d 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.12.BUILD-SNAPSHOT + 1.0.12.RELEASE .. diff --git a/spring-cloud-sleuth-stream/pom.xml b/spring-cloud-sleuth-stream/pom.xml index c9fb5c948..21f91a12e 100644 --- a/spring-cloud-sleuth-stream/pom.xml +++ b/spring-cloud-sleuth-stream/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.12.BUILD-SNAPSHOT + 1.0.12.RELEASE diff --git a/spring-cloud-sleuth-zipkin-stream/pom.xml b/spring-cloud-sleuth-zipkin-stream/pom.xml index e2957662e..c9dcfa240 100644 --- a/spring-cloud-sleuth-zipkin-stream/pom.xml +++ b/spring-cloud-sleuth-zipkin-stream/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.12.BUILD-SNAPSHOT + 1.0.12.RELEASE diff --git a/spring-cloud-sleuth-zipkin/pom.xml b/spring-cloud-sleuth-zipkin/pom.xml index bd4dc4be5..96a092b5b 100644 --- a/spring-cloud-sleuth-zipkin/pom.xml +++ b/spring-cloud-sleuth-zipkin/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.12.BUILD-SNAPSHOT + 1.0.12.RELEASE .. diff --git a/spring-cloud-starter-sleuth/pom.xml b/spring-cloud-starter-sleuth/pom.xml index 092259366..374ca535b 100644 --- a/spring-cloud-starter-sleuth/pom.xml +++ b/spring-cloud-starter-sleuth/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.12.BUILD-SNAPSHOT + 1.0.12.RELEASE .. spring-cloud-starter-sleuth diff --git a/spring-cloud-starter-zipkin/pom.xml b/spring-cloud-starter-zipkin/pom.xml index f68cd4295..4c6335d49 100644 --- a/spring-cloud-starter-zipkin/pom.xml +++ b/spring-cloud-starter-zipkin/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.12.BUILD-SNAPSHOT + 1.0.12.RELEASE .. spring-cloud-starter-zipkin From 863ba87f5f0448d87ba962aa151e2878017b06c6 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Mon, 9 Jan 2017 17:01:38 +0100 Subject: [PATCH 193/235] Going back to snapshots --- benchmarks/pom.xml | 2 +- docs/pom.xml | 2 +- pom.xml | 12 ++++++------ spring-cloud-sleuth-core/pom.xml | 2 +- spring-cloud-sleuth-dependencies/pom.xml | 4 ++-- spring-cloud-sleuth-samples/pom.xml | 2 +- .../spring-cloud-sleuth-sample-feign/pom.xml | 2 +- .../spring-cloud-sleuth-sample-messaging/pom.xml | 2 +- .../spring-cloud-sleuth-sample-ribbon/pom.xml | 2 +- .../spring-cloud-sleuth-sample-stream/pom.xml | 2 +- .../spring-cloud-sleuth-sample-test-core/pom.xml | 2 +- .../spring-cloud-sleuth-sample-websocket/pom.xml | 2 +- .../spring-cloud-sleuth-sample-zipkin-stream/pom.xml | 2 +- .../spring-cloud-sleuth-sample-zipkin/pom.xml | 2 +- .../spring-cloud-sleuth-sample/pom.xml | 2 +- spring-cloud-sleuth-stream/pom.xml | 2 +- spring-cloud-sleuth-zipkin-stream/pom.xml | 2 +- spring-cloud-sleuth-zipkin/pom.xml | 2 +- spring-cloud-starter-sleuth/pom.xml | 2 +- spring-cloud-starter-zipkin/pom.xml | 2 +- 20 files changed, 26 insertions(+), 26 deletions(-) diff --git a/benchmarks/pom.xml b/benchmarks/pom.xml index 81331e52c..06528abfe 100644 --- a/benchmarks/pom.xml +++ b/benchmarks/pom.xml @@ -17,7 +17,7 @@ Benchmarks Benchmarks (JMH) org.springframework.cloud - 1.0.12.RELEASE + 1.0.13.BUILD-SNAPSHOT benchmarks diff --git a/docs/pom.xml b/docs/pom.xml index 2b3fb2bc8..233f80ca8 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.12.RELEASE + 1.0.13.BUILD-SNAPSHOT spring-cloud-sleuth-docs pom diff --git a/pom.xml b/pom.xml index 33ea1ba22..d422ee6fe 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 spring-cloud-sleuth - 1.0.12.RELEASE + 1.0.13.BUILD-SNAPSHOT pom Spring Cloud Sleuth Spring Cloud Sleuth @@ -13,7 +13,7 @@ org.springframework.cloud spring-cloud-build - 1.1.2.RELEASE + 1.1.3.BUILD-SNAPSHOT @@ -229,10 +229,10 @@ 1.8 2.19.1 2.17 - 1.1.2.RELEASE - 1.1.3.RELEASE - Brooklyn.RELEASE - 1.1.7.RELEASE + 1.1.3.BUILD-SNAPSHOT + 1.1.3.BUILD-SNAPSHOT + Brooklyn.BUILD-SNAPSHOT + 1.1.8.BUILD-SNAPSHOT diff --git a/spring-cloud-sleuth-core/pom.xml b/spring-cloud-sleuth-core/pom.xml index 36eab35f9..4dd04241f 100644 --- a/spring-cloud-sleuth-core/pom.xml +++ b/spring-cloud-sleuth-core/pom.xml @@ -11,7 +11,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.12.RELEASE + 1.0.13.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-dependencies/pom.xml b/spring-cloud-sleuth-dependencies/pom.xml index db9868565..08935748f 100644 --- a/spring-cloud-sleuth-dependencies/pom.xml +++ b/spring-cloud-sleuth-dependencies/pom.xml @@ -5,11 +5,11 @@ spring-cloud-dependencies-parent org.springframework.cloud - 1.1.2.RELEASE + 1.1.3.BUILD-SNAPSHOT spring-cloud-sleuth-dependencies - 1.0.12.RELEASE + 1.0.13.BUILD-SNAPSHOT pom spring-cloud-sleuth-dependencies Spring Cloud Sleuth Dependencies diff --git a/spring-cloud-sleuth-samples/pom.xml b/spring-cloud-sleuth-samples/pom.xml index 680893183..c777f4457 100644 --- a/spring-cloud-sleuth-samples/pom.xml +++ b/spring-cloud-sleuth-samples/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.12.RELEASE + 1.0.13.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml index 8929d86a2..5a184ddf1 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml @@ -20,7 +20,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.12.RELEASE + 1.0.13.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml index b447aff99..4e961c348 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.12.RELEASE + 1.0.13.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml index 802f21ebe..8a65fb4ef 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.12.RELEASE + 1.0.13.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml index 6246c28f9..5f423f384 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.12.RELEASE + 1.0.13.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml index 194902b88..4190cf39c 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.12.RELEASE + 1.0.13.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml index 0f88fba42..14381cf9a 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.12.RELEASE + 1.0.13.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml index ecec6b28e..5df95d73e 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.12.RELEASE + 1.0.13.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml index 9c2349812..130ca1bcd 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml @@ -27,7 +27,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.12.RELEASE + 1.0.13.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml index ce0fee52d..3ffb3dd06 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.0.12.RELEASE + 1.0.13.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-stream/pom.xml b/spring-cloud-sleuth-stream/pom.xml index 21f91a12e..33759d78e 100644 --- a/spring-cloud-sleuth-stream/pom.xml +++ b/spring-cloud-sleuth-stream/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.12.RELEASE + 1.0.13.BUILD-SNAPSHOT diff --git a/spring-cloud-sleuth-zipkin-stream/pom.xml b/spring-cloud-sleuth-zipkin-stream/pom.xml index c9dcfa240..ae788df8e 100644 --- a/spring-cloud-sleuth-zipkin-stream/pom.xml +++ b/spring-cloud-sleuth-zipkin-stream/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.12.RELEASE + 1.0.13.BUILD-SNAPSHOT diff --git a/spring-cloud-sleuth-zipkin/pom.xml b/spring-cloud-sleuth-zipkin/pom.xml index 96a092b5b..1c2eff394 100644 --- a/spring-cloud-sleuth-zipkin/pom.xml +++ b/spring-cloud-sleuth-zipkin/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.12.RELEASE + 1.0.13.BUILD-SNAPSHOT .. diff --git a/spring-cloud-starter-sleuth/pom.xml b/spring-cloud-starter-sleuth/pom.xml index 374ca535b..00060d44f 100644 --- a/spring-cloud-starter-sleuth/pom.xml +++ b/spring-cloud-starter-sleuth/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.12.RELEASE + 1.0.13.BUILD-SNAPSHOT .. spring-cloud-starter-sleuth diff --git a/spring-cloud-starter-zipkin/pom.xml b/spring-cloud-starter-zipkin/pom.xml index 4c6335d49..045f8582c 100644 --- a/spring-cloud-starter-zipkin/pom.xml +++ b/spring-cloud-starter-zipkin/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.0.12.RELEASE + 1.0.13.BUILD-SNAPSHOT .. spring-cloud-starter-zipkin From a559c11bcd11e1eb25fc69a756315440949be070 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Wed, 11 Jan 2017 12:31:18 +0100 Subject: [PATCH 194/235] Due to possible performance problems when DiscoveryClient gives nonexistent DNS name as local address Local endpoint is created from server properties by default. Local Ip determined from spring.commons InetUtils. To enable local endpoint resolution via service discovery, DiscoveryClient must be present and 'spring.zipkin.service.locator.discovery' should be set to true. FallbackHavingEndpointLocator was caching and recreating EndpointLocator in the same time - removed the caching part. Fixes gh-403 --- .../main/asciidoc/spring-cloud-sleuth.adoc | 15 +++ spring-cloud-sleuth-stream/pom.xml | 1 - .../stream/DiscoveryClientHostLocator.java | 4 +- .../stream/ServerPropertiesHostLocator.java | 34 +++++-- .../stream/SleuthStreamAutoConfiguration.java | 19 +++- .../cloud/sleuth/stream/ZipkinProperties.java | 63 +++++++++++-- ...lientEndpointLocatorConfigurationTest.java | 94 +++++++++++++++++++ .../DiscoveryClientHostLocatorTest.java | 2 +- .../ServerPropertiesHostLocatorTests.java | 37 +++++--- .../SleuthStreamAutoConfigurationTest.java | 3 +- .../stream/StreamSpanListenerTests.java | 3 +- spring-cloud-sleuth-zipkin/pom.xml | 1 - .../zipkin/FallbackHavingEndpointLocator.java | 7 +- .../ServerPropertiesEndpointLocator.java | 22 +++-- .../zipkin/ZipkinAutoConfiguration.java | 17 +++- .../cloud/sleuth/zipkin/ZipkinProperties.java | 37 ++++++++ .../sleuth/zipkin/ZipkinSpanListener.java | 12 +-- ...lientEndpointLocatorConfigurationTest.java | 89 ++++++++++++++++++ .../zipkin/HttpZipkinSpanReporterTest.java | 4 +- .../ServerPropertiesEndpointLocatorTests.java | 34 +++++-- 20 files changed, 426 insertions(+), 72 deletions(-) create mode 100644 spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/DiscoveryClientEndpointLocatorConfigurationTest.java create mode 100644 spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/DiscoveryClientEndpointLocatorConfigurationTest.java diff --git a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc index 446e24ca1..6f07f6a07 100644 --- a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc +++ b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc @@ -305,6 +305,21 @@ By default Sleuth assumes that when you send a span to Zipkin, you want the span spring.zipkin.service.name: foo ---- +=== Host locator + +In order to define the host that is corresponding to a particular span we need to resolve the host name +and port. The default approach is to take it from server properties. If those for some reason are not set +then we're trying to retrieve the host name from the network interfaces. + +If you have the discovery client enabled and prefer to retrieve the host address from the registered +instance in a service registry then you have to set the property (it's applicable for both HTTP and +Stream based span reporting). + +[source,yaml] +---- +spring.zipkin.locator.discovery.enabled: true +---- + == Span Data as Messages You can accumulate and send span data over diff --git a/spring-cloud-sleuth-stream/pom.xml b/spring-cloud-sleuth-stream/pom.xml index 33759d78e..2fac45dd4 100644 --- a/spring-cloud-sleuth-stream/pom.xml +++ b/spring-cloud-sleuth-stream/pom.xml @@ -27,7 +27,6 @@ org.springframework.cloud spring-cloud-commons - true org.springframework.boot diff --git a/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/DiscoveryClientHostLocator.java b/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/DiscoveryClientHostLocator.java index 86b618f6e..172c80eb0 100644 --- a/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/DiscoveryClientHostLocator.java +++ b/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/DiscoveryClientHostLocator.java @@ -52,8 +52,8 @@ public class DiscoveryClientHostLocator implements HostLocator { @Override public Host locate(Span span) { ServiceInstance instance = this.client.getLocalServiceInstance(); - String serviceId = StringUtils.hasText(this.zipkinProperties.getName()) ? - this.zipkinProperties.getName() : instance.getServiceId(); + String serviceId = StringUtils.hasText(this.zipkinProperties.getService().getName()) ? + this.zipkinProperties.getService().getName() : instance.getServiceId(); return new Host(serviceId, getIpAddress(instance), instance.getPort()); } diff --git a/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/ServerPropertiesHostLocator.java b/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/ServerPropertiesHostLocator.java index 37e33a582..1a9c90f4d 100644 --- a/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/ServerPropertiesHostLocator.java +++ b/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/ServerPropertiesHostLocator.java @@ -16,8 +16,14 @@ package org.springframework.cloud.sleuth.stream; +import java.lang.invoke.MethodHandles; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.context.embedded.EmbeddedServletContainerInitializedEvent; +import org.springframework.cloud.commons.util.InetUtils; +import org.springframework.cloud.commons.util.InetUtilsProperties; import org.springframework.cloud.sleuth.Span; import org.springframework.context.event.EventListener; import org.springframework.util.Assert; @@ -32,30 +38,37 @@ import org.springframework.util.StringUtils; *
  • port - from lazily assigned port or {@link ServerProperties}
  • * * - * You can override the value of service id by {@link ZipkinProperties#setName(String)} + * You can override the value of service id by {@link ZipkinProperties#getService()} * * @author Dave Syer * @since 1.0.0 */ public class ServerPropertiesHostLocator implements HostLocator { + private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); + private final ServerProperties serverProperties; // Nullable private final String appName; + private final InetUtils inetUtils; private final ZipkinProperties zipkinProperties; private Integer port; // Lazy assigned @Deprecated - public ServerPropertiesHostLocator(ServerProperties serverProperties, - String appName) { - this(serverProperties, appName, new ZipkinProperties()); + public ServerPropertiesHostLocator(ServerProperties serverProperties, String appName) { + this(serverProperties, appName, new ZipkinProperties(),null); } - public ServerPropertiesHostLocator(ServerProperties serverProperties, - String appName, ZipkinProperties zipkinProperties) { + public ServerPropertiesHostLocator(ServerProperties serverProperties, String appName, + ZipkinProperties zipkinProperties, InetUtils inetUtils) { this.serverProperties = serverProperties; this.appName = appName; Assert.notNull(this.appName, "appName"); this.zipkinProperties = zipkinProperties; + if (inetUtils == null) { + this.inetUtils = new InetUtils(new InetUtilsProperties()); + } else { + this.inetUtils = inetUtils; + } } @Override @@ -91,21 +104,24 @@ public class ServerPropertiesHostLocator implements HostLocator { address = this.serverProperties.getAddress().getHostAddress(); } else { - address = "127.0.0.1"; + address = this.inetUtils.findFirstNonLoopbackAddress().getHostAddress(); } return address; } private String getServiceName(Span span) { String serviceName; - if (StringUtils.hasText(this.zipkinProperties.getName())) { - serviceName = this.zipkinProperties.getName(); + if (StringUtils.hasText(this.zipkinProperties.getService().getName())) { + serviceName = this.zipkinProperties.getService().getName(); } else if (span.getProcessId() != null) { serviceName = span.getProcessId(); } else { serviceName = this.appName; } + if (log.isDebugEnabled()) { + log.debug("Span will contain serviceName [" + serviceName + "]"); + } return serviceName; } diff --git a/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/SleuthStreamAutoConfiguration.java b/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/SleuthStreamAutoConfiguration.java index cd71a167a..eb027ff00 100644 --- a/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/SleuthStreamAutoConfiguration.java +++ b/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/SleuthStreamAutoConfiguration.java @@ -22,11 +22,11 @@ import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.client.discovery.DiscoveryClient; +import org.springframework.cloud.commons.util.InetUtils; import org.springframework.cloud.sleuth.Sampler; import org.springframework.cloud.sleuth.metric.SpanMetricReporter; import org.springframework.cloud.sleuth.metric.TraceMetricsAutoConfiguration; @@ -91,7 +91,8 @@ public class SleuthStreamAutoConfiguration { } @Configuration - @ConditionalOnMissingClass("org.springframework.cloud.client.discovery.DiscoveryClient") + @ConditionalOnMissingBean(HostLocator.class) + @ConditionalOnProperty(value = "spring.zipkin.locator.discovery.enabled", havingValue = "false", matchIfMissing = true) protected static class DefaultEndpointLocatorConfiguration { @Autowired(required = false) @@ -100,18 +101,24 @@ public class SleuthStreamAutoConfiguration { @Autowired private ZipkinProperties zipkinProperties; + @Autowired + private InetUtils inetUtils; + @Value("${spring.application.name:unknown}") private String appName; @Bean public HostLocator zipkinEndpointLocator() { - return new ServerPropertiesHostLocator(this.serverProperties, this.appName, this.zipkinProperties); + return new ServerPropertiesHostLocator(this.serverProperties, this.appName, this.zipkinProperties, + this.inetUtils); } } @Configuration @ConditionalOnClass(DiscoveryClient.class) + @ConditionalOnMissingBean(HostLocator.class) + @ConditionalOnProperty(value = "spring.zipkin.locator.discovery.enabled", havingValue = "true") protected static class DiscoveryClientEndpointLocatorConfiguration { @Autowired(required = false) @@ -120,6 +127,9 @@ public class SleuthStreamAutoConfiguration { @Autowired private ZipkinProperties zipkinProperties; + @Autowired(required = false) + private InetUtils inetUtils; + @Value("${spring.application.name:unknown}") private String appName; @@ -131,7 +141,8 @@ public class SleuthStreamAutoConfiguration { if (this.client != null) { return new DiscoveryClientHostLocator(this.client, this.zipkinProperties); } - return new ServerPropertiesHostLocator(this.serverProperties, this.appName, this.zipkinProperties); + return new ServerPropertiesHostLocator(this.serverProperties, this.appName, this.zipkinProperties, + this.inetUtils); } } diff --git a/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/ZipkinProperties.java b/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/ZipkinProperties.java index 700ee1023..908de229c 100644 --- a/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/ZipkinProperties.java +++ b/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/ZipkinProperties.java @@ -24,17 +24,66 @@ import org.springframework.boot.context.properties.ConfigurationProperties; * @author Marcin Grzejszczak * @since 1.0.12 */ -@ConfigurationProperties("spring.zipkin.service") +@ConfigurationProperties("spring.zipkin") public class ZipkinProperties { - /** The name of the service, from which the Span was sent via Stream, that should appear in Zipkin */ - private String name; + private Service service = new Service(); - public String getName() { - return this.name; + private Locator locator = new Locator(); + + public Service getService() { + return this.service; } - public void setName(String name) { - this.name = name; + public void setService(Service service) { + this.service = service; + } + + public Locator getLocator() { + return this.locator; + } + + public void setLocator(Locator locator) { + this.locator = locator; + } + + public static class Service { + /** The name of the service, from which the Span was sent via Stream, that should appear in Zipkin */ + private String name; + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + } + + public static class Locator { + + private Discovery discovery; + + public Discovery getDiscovery() { + return this.discovery; + } + + public void setDiscovery(Discovery discovery) { + this.discovery = discovery; + } + + public static class Discovery { + + /** Enabling of locating the host name via service discovery */ + private boolean enabled; + + public boolean isEnabled() { + return this.enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + } } } diff --git a/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/DiscoveryClientEndpointLocatorConfigurationTest.java b/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/DiscoveryClientEndpointLocatorConfigurationTest.java new file mode 100644 index 000000000..c01dd1e16 --- /dev/null +++ b/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/DiscoveryClientEndpointLocatorConfigurationTest.java @@ -0,0 +1,94 @@ +package org.springframework.cloud.sleuth.stream; + +import org.junit.Test; +import org.mockito.Mockito; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.cloud.client.discovery.DiscoveryClient; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Matcin Wielgus + */ +public class DiscoveryClientEndpointLocatorConfigurationTest { + @Test + public void endpointLocatorShouldDefaultToServerPropertiesEndpointLocator() { + try (ConfigurableApplicationContext ctxt = new SpringApplication( + EmptyConfiguration.class).run("--spring.main.web_environment=false")) { + assertThat(ctxt.getBean(HostLocator.class)) + .isInstanceOf(ServerPropertiesHostLocator.class); + } + } + + @Test + public void endpointLocatorShouldDefaultToServerPropertiesEndpointLocatorEvenWhenDiscoveryClientPresent() { + try (ConfigurableApplicationContext ctxt = new SpringApplication( + ConfigurationWithDiscoveryClient.class) + .run("--spring.main.web_environment=false")) { + assertThat(ctxt.getBean(HostLocator.class)) + .isInstanceOf(ServerPropertiesHostLocator.class); + } + } + + @Test + public void endpointLocatorShouldRespectExistingEndpointLocator() { + try (ConfigurableApplicationContext ctxt = new SpringApplication( + ConfigurationWithCustomLocator.class) + .run("--spring.main.web_environment=false")) { + assertThat(ctxt.getBean(HostLocator.class)) + .isSameAs(ConfigurationWithCustomLocator.locator); + } + } + + @Test + public void endpointLocatorShouldBeFallbackHavingEndpointLocatorWhenAskedTo() { + try (ConfigurableApplicationContext ctxt = new SpringApplication( + ConfigurationWithDiscoveryClient.class).run( + "--spring.zipkin.locator.discovery.enabled=true", + "--spring.main.web_environment=false")) { + assertThat(ctxt.getBean(HostLocator.class)) + .isInstanceOf(DiscoveryClientHostLocator.class); + } + } + + @Test + public void endpointLocatorShouldRespectExistingEndpointLocatorEvenWhenAskedToBeDiscovery() { + try (ConfigurableApplicationContext ctxt = new SpringApplication( + ConfigurationWithDiscoveryClient.class, + ConfigurationWithCustomLocator.class).run( + "--spring.zipkin.locator.discovery.enabled=true", + "--spring.main.web_environment=false")) { + assertThat(ctxt.getBean(HostLocator.class)) + .isSameAs(ConfigurationWithCustomLocator.locator); + } + } + + @Configuration + @EnableAutoConfiguration + public static class EmptyConfiguration { + } + + @Configuration + @EnableAutoConfiguration + public static class ConfigurationWithDiscoveryClient { + @Bean + public DiscoveryClient getDiscoveryClient() { + return Mockito.mock(DiscoveryClient.class); + } + } + + @Configuration + @EnableAutoConfiguration + public static class ConfigurationWithCustomLocator { + static HostLocator locator = Mockito.mock(HostLocator.class); + + @Bean + public HostLocator getEndpointLocator() { + return locator; + } + } +} \ No newline at end of file diff --git a/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/DiscoveryClientHostLocatorTest.java b/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/DiscoveryClientHostLocatorTest.java index 46f4bf72d..b596eed16 100644 --- a/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/DiscoveryClientHostLocatorTest.java +++ b/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/DiscoveryClientHostLocatorTest.java @@ -67,7 +67,7 @@ public class DiscoveryClientHostLocatorTest { public void should_override_the_service_name_from_properties() throws Exception { given(this.discoveryClient.getLocalServiceInstance()).willReturn(serviceInstanceWithValidHost()); ZipkinProperties zipkinProperties = new ZipkinProperties(); - zipkinProperties.setName("foo"); + zipkinProperties.getService().setName("foo"); this.discoveryClientHostLocator = new DiscoveryClientHostLocator(this.discoveryClient, zipkinProperties); Host host = this.discoveryClientHostLocator.locate(null); diff --git a/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/ServerPropertiesHostLocatorTests.java b/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/ServerPropertiesHostLocatorTests.java index a63c02268..22ca4ea5c 100644 --- a/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/ServerPropertiesHostLocatorTests.java +++ b/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/ServerPropertiesHostLocatorTests.java @@ -21,49 +21,57 @@ import java.net.UnknownHostException; import java.util.Collections; import org.junit.Test; +import org.mockito.Mockito; import org.springframework.boot.autoconfigure.web.ServerProperties; +import org.springframework.cloud.commons.util.InetUtils; +import org.springframework.cloud.commons.util.InetUtilsProperties; import org.springframework.cloud.sleuth.Span; import static org.assertj.core.api.Assertions.assertThat; public class ServerPropertiesHostLocatorTests { + + public static final byte[] ADR1234 = { 1, 2, 3, 4 }; + Span span = new Span(1, 3, "http:name", 1L, Collections.emptyList(), 2L, true, true, "process"); @Test - public void portDefaultsTo8080() { + public void portDefaultsTo8080() throws UnknownHostException { ServerPropertiesHostLocator locator = new ServerPropertiesHostLocator( - new ServerProperties(), "unknown", new ZipkinProperties()); + new ServerProperties(), "unknown", new ZipkinProperties(), + localAddress(ADR1234)); assertThat(locator.locate(this.span).getPort()).isEqualTo((short) 8080); } @Test - public void portFromServerProperties() { + public void portFromServerProperties() throws UnknownHostException { ServerProperties properties = new ServerProperties(); properties.setPort(1234); ServerPropertiesHostLocator locator = new ServerPropertiesHostLocator(properties, - "unknown", new ZipkinProperties()); + "unknown", new ZipkinProperties(),localAddress(ADR1234)); assertThat(locator.locate(this.span).getPort()).isEqualTo((short) 1234); } @Test - public void portDefaultsToLocalhost() { + public void portDefaultsToLocalhost() throws UnknownHostException { ServerPropertiesHostLocator locator = new ServerPropertiesHostLocator( - new ServerProperties(), "unknown", new ZipkinProperties()); + new ServerProperties(), "unknown", new ZipkinProperties(), + localAddress(ADR1234)); - assertThat(locator.locate(this.span).getAddress()).isEqualTo("127.0.0.1"); + assertThat(locator.locate(this.span).getAddress()).isEqualTo("1.2.3.4"); } @Test public void hostFromServerPropertiesIp() throws UnknownHostException { ServerProperties properties = new ServerProperties(); - properties.setAddress(InetAddress.getByAddress(new byte[] { 1, 2, 3, 4 })); + properties.setAddress(InetAddress.getByAddress(ADR1234)); ServerPropertiesHostLocator locator = new ServerPropertiesHostLocator(properties, - "unknown", new ZipkinProperties()); + "unknown", new ZipkinProperties(),localAddress(new byte[] { 1, 1, 1, 1 })); assertThat(locator.locate(this.span).getAddress()).isEqualTo("1.2.3.4"); } @@ -73,11 +81,18 @@ public class ServerPropertiesHostLocatorTests { ServerProperties properties = new ServerProperties(); properties.setAddress(InetAddress.getByAddress(new byte[] { 1, 2, 3, 4 })); ZipkinProperties zipkinProperties = new ZipkinProperties(); - zipkinProperties.setName("foo"); + zipkinProperties.getService().setName("foo"); ServerPropertiesHostLocator locator = new ServerPropertiesHostLocator(properties, - "unknown", zipkinProperties); + "unknown", zipkinProperties,localAddress(ADR1234)); assertThat(locator.locate(this.span).getServiceName()).isEqualTo("foo"); } + + private InetUtils localAddress(byte[] address) throws UnknownHostException { + InetUtils mocked = Mockito.spy(new InetUtils(new InetUtilsProperties())); + Mockito.when(mocked.findFirstNonLoopbackAddress()) + .thenReturn(InetAddress.getByAddress(address)); + return mocked; + } } diff --git a/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/SleuthStreamAutoConfigurationTest.java b/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/SleuthStreamAutoConfigurationTest.java index 3e0d02bf1..51cfab3bf 100644 --- a/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/SleuthStreamAutoConfigurationTest.java +++ b/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/SleuthStreamAutoConfigurationTest.java @@ -10,6 +10,7 @@ import org.junit.BeforeClass; import org.junit.Test; import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.test.EnvironmentTestUtils; +import org.springframework.cloud.commons.util.UtilAutoConfiguration; import org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration; import org.springframework.cloud.sleuth.log.NoOpSpanLogger; import org.springframework.cloud.sleuth.log.SpanLogger; @@ -120,7 +121,7 @@ public class SleuthStreamAutoConfigurationTest { @Import({ SleuthStreamAutoConfiguration.class, TraceMetricsAutoConfiguration.class, TestSupportBinderAutoConfiguration.class, ChannelBindingAutoConfiguration.class, TraceAutoConfiguration.class, - PropertyPlaceholderAutoConfiguration.class }) + PropertyPlaceholderAutoConfiguration.class, UtilAutoConfiguration.class }) public static class BaseConfiguration { @Bean SpanLogger spanLogger() { diff --git a/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/StreamSpanListenerTests.java b/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/StreamSpanListenerTests.java index c99df54f0..f26bb6399 100644 --- a/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/StreamSpanListenerTests.java +++ b/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/StreamSpanListenerTests.java @@ -33,6 +33,7 @@ import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.actuate.metrics.CounterService; import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; +import org.springframework.cloud.commons.util.UtilAutoConfiguration; import org.springframework.cloud.sleuth.Sampler; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.SpanReporter; @@ -158,7 +159,7 @@ public class StreamSpanListenerTests { @Import({ ZipkinTestConfiguration.class, SleuthStreamAutoConfiguration.class, TraceMetricsAutoConfiguration.class, TestSupportBinderAutoConfiguration.class, ChannelBindingAutoConfiguration.class, TraceAutoConfiguration.class, - PropertyPlaceholderAutoConfiguration.class }) + PropertyPlaceholderAutoConfiguration.class, UtilAutoConfiguration.class }) protected static class TestConfiguration { } diff --git a/spring-cloud-sleuth-zipkin/pom.xml b/spring-cloud-sleuth-zipkin/pom.xml index 1c2eff394..d2c1f579e 100644 --- a/spring-cloud-sleuth-zipkin/pom.xml +++ b/spring-cloud-sleuth-zipkin/pom.xml @@ -40,7 +40,6 @@ org.springframework.cloud spring-cloud-commons - true org.springframework.boot diff --git a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/FallbackHavingEndpointLocator.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/FallbackHavingEndpointLocator.java index 5ac4ca5d2..e9c58805d 100644 --- a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/FallbackHavingEndpointLocator.java +++ b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/FallbackHavingEndpointLocator.java @@ -1,7 +1,5 @@ package org.springframework.cloud.sleuth.zipkin; -import java.util.concurrent.atomic.AtomicReference; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import zipkin.Endpoint; @@ -14,8 +12,6 @@ import zipkin.Endpoint; */ public class FallbackHavingEndpointLocator implements EndpointLocator { - private final AtomicReference cachedEndpoint = new AtomicReference<>(); - private static final Log log = LogFactory.getLog(FallbackHavingEndpointLocator.class); private final DiscoveryClientEndpointLocator discoveryClientEndpointLocator; @@ -29,8 +25,7 @@ public class FallbackHavingEndpointLocator implements EndpointLocator { @Override public Endpoint local() { - this.cachedEndpoint.compareAndSet(null, endpoint()); - return this.cachedEndpoint.get(); + return endpoint(); } private Endpoint endpoint() { diff --git a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ServerPropertiesEndpointLocator.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ServerPropertiesEndpointLocator.java index b148b5ff9..8749914a9 100644 --- a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ServerPropertiesEndpointLocator.java +++ b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ServerPropertiesEndpointLocator.java @@ -17,12 +17,14 @@ package org.springframework.cloud.sleuth.zipkin; import java.lang.invoke.MethodHandles; +import java.nio.ByteBuffer; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.context.embedded.EmbeddedServletContainerInitializedEvent; import org.springframework.cloud.commons.util.InetUtils; +import org.springframework.cloud.commons.util.InetUtilsProperties; import org.springframework.context.event.EventListener; import org.springframework.util.StringUtils; @@ -47,20 +49,25 @@ public class ServerPropertiesEndpointLocator implements EndpointLocator { private final ServerProperties serverProperties; private final String appName; + private final InetUtils inetUtils; private final ZipkinProperties zipkinProperties; private Integer port; @Deprecated - public ServerPropertiesEndpointLocator(ServerProperties serverProperties, - String appName) { - this(serverProperties, appName, new ZipkinProperties()); + public ServerPropertiesEndpointLocator(ServerProperties serverProperties,String appName) { + this(serverProperties,appName,new ZipkinProperties(), null); } public ServerPropertiesEndpointLocator(ServerProperties serverProperties, - String appName, ZipkinProperties zipkinProperties) { + String appName, ZipkinProperties zipkinProperties, InetUtils inetUtils) { this.serverProperties = serverProperties; this.appName = appName; this.zipkinProperties = zipkinProperties; + if (inetUtils == null) { + this.inetUtils = new InetUtils(new InetUtilsProperties()); + } else { + this.inetUtils = inetUtils; + } } @Override @@ -97,11 +104,12 @@ public class ServerPropertiesEndpointLocator implements EndpointLocator { } private int getAddress() { - if (this.serverProperties!=null && this.serverProperties.getAddress() != null) { - return InetUtils.getIpAddressAsInt(this.serverProperties.getAddress().getHostAddress()); + if (this.serverProperties != null && this.serverProperties.getAddress() != null) { + return ByteBuffer.wrap(this.serverProperties.getAddress().getAddress()) + .getInt(); } else { - return 127 << 24 | 1; + return ByteBuffer.wrap(this.inetUtils.findFirstNonLoopbackAddress().getAddress()).getInt(); } } } diff --git a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinAutoConfiguration.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinAutoConfiguration.java index 829f7aa33..01082bdb1 100644 --- a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinAutoConfiguration.java +++ b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinAutoConfiguration.java @@ -21,11 +21,11 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.client.discovery.DiscoveryClient; +import org.springframework.cloud.commons.util.InetUtils; import org.springframework.cloud.sleuth.Sampler; import org.springframework.cloud.sleuth.SpanReporter; import org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration; @@ -70,7 +70,8 @@ public class ZipkinAutoConfiguration { } @Configuration - @ConditionalOnMissingClass("org.springframework.cloud.client.discovery.DiscoveryClient") + @ConditionalOnMissingBean(EndpointLocator.class) + @ConditionalOnProperty(value = "spring.zipkin.locator.discovery.enabled", havingValue = "false", matchIfMissing = true) protected static class DefaultEndpointLocatorConfiguration { @Autowired(required=false) @@ -79,19 +80,24 @@ public class ZipkinAutoConfiguration { @Autowired private ZipkinProperties zipkinProperties; + @Autowired(required=false) + private InetUtils inetUtils; + @Value("${spring.application.name:unknown}") private String appName; @Bean public EndpointLocator zipkinEndpointLocator() { return new ServerPropertiesEndpointLocator(this.serverProperties, this.appName, - this.zipkinProperties); + this.zipkinProperties, this.inetUtils); } } @Configuration @ConditionalOnClass(DiscoveryClient.class) + @ConditionalOnMissingBean(EndpointLocator.class) + @ConditionalOnProperty(value = "spring.zipkin.locator.discovery.enabled", havingValue = "true") protected static class DiscoveryClientEndpointLocatorConfiguration { @Autowired(required=false) @@ -100,6 +106,9 @@ public class ZipkinAutoConfiguration { @Autowired private ZipkinProperties zipkinProperties; + @Autowired(required=false) + private InetUtils inetUtils; + @Value("${spring.application.name:unknown}") private String appName; @@ -110,7 +119,7 @@ public class ZipkinAutoConfiguration { public EndpointLocator zipkinEndpointLocator() { return new FallbackHavingEndpointLocator(discoveryClientEndpointLocator(), new ServerPropertiesEndpointLocator(this.serverProperties, this.appName, - this.zipkinProperties)); + this.zipkinProperties, this.inetUtils)); } private DiscoveryClientEndpointLocator discoveryClientEndpointLocator() { diff --git a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinProperties.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinProperties.java index 1a27fd047..1144d0d0c 100644 --- a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinProperties.java +++ b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinProperties.java @@ -34,6 +34,12 @@ public class ZipkinProperties { private Service service = new Service(); + private Locator locator = new Locator(); + + public Locator getLocator() { + return this.locator; + } + public String getBaseUrl() { return this.baseUrl; } @@ -74,6 +80,10 @@ public class ZipkinProperties { this.service = service; } + public void setLocator(Locator locator) { + this.locator = locator; + } + /** When enabled, spans are gzipped before sent to the zipkin server */ public static class Compression { @@ -102,4 +112,31 @@ public class ZipkinProperties { this.name = name; } } + + public static class Locator { + + private Discovery discovery; + + public Discovery getDiscovery() { + return this.discovery; + } + + public void setDiscovery(Discovery discovery) { + this.discovery = discovery; + } + + public static class Discovery { + + /** Enabling of locating the host name via service discovery */ + private boolean enabled; + + public boolean isEnabled() { + return this.enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + } + } } diff --git a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListener.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListener.java index f00aa22d3..4b33fdf91 100644 --- a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListener.java +++ b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListener.java @@ -119,7 +119,7 @@ public class ZipkinSpanListener implements SpanReporter { return zipkinSpan.build(); } - private void ensureLocalComponent(Span span, zipkin.Span.Builder zipkinSpan) { + private void ensureLocalComponent(Span span, zipkin.Span.Builder zipkinSpan, Endpoint localEndpoint) { if (span.tags().containsKey(Constants.LOCAL_COMPONENT)) { return; } @@ -130,14 +130,14 @@ public class ZipkinSpanListener implements SpanReporter { .type(BinaryAnnotation.Type.STRING) .key("lc") // LOCAL_COMPONENT .value(processId) - .endpoint(this.endpointLocator.local()).build(); + .endpoint(localEndpoint).build(); zipkinSpan.addBinaryAnnotation(component); } - private void ensureServerAddr(Span span, zipkin.Span.Builder zipkinSpan) { + private void ensureServerAddr(Span span, zipkin.Span.Builder zipkinSpan, Endpoint localEndpoint) { if (span.tags().containsKey(Span.SPAN_PEER_SERVICE_TAG_NAME)) { zipkinSpan.addBinaryAnnotation(BinaryAnnotation.address(Constants.SERVER_ADDR, - this.endpointLocator.local().toBuilder().serviceName( + localEndpoint.toBuilder().serviceName( span.tags().get(Span.SPAN_PEER_SERVICE_TAG_NAME)).build())); } } @@ -160,10 +160,10 @@ public class ZipkinSpanListener implements SpanReporter { } if (notClientOrServer) { // A zipkin span without any annotations cannot be queried, add special "lc" to avoid that. - ensureLocalComponent(span, zipkinSpan); + ensureLocalComponent(span, zipkinSpan, endpoint); } if (hasClientSend) { - ensureServerAddr(span, zipkinSpan); + ensureServerAddr(span, zipkinSpan, endpoint); } if (instanceIdToTag && this.environment != null) { setInstanceIdIfPresent(zipkinSpan, endpoint, Span.INSTANCEID); diff --git a/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/DiscoveryClientEndpointLocatorConfigurationTest.java b/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/DiscoveryClientEndpointLocatorConfigurationTest.java new file mode 100644 index 000000000..510d18899 --- /dev/null +++ b/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/DiscoveryClientEndpointLocatorConfigurationTest.java @@ -0,0 +1,89 @@ +package org.springframework.cloud.sleuth.zipkin; + +import org.junit.Test; +import org.mockito.Mockito; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.cloud.client.discovery.DiscoveryClient; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Matcin Wielgus + */ +public class DiscoveryClientEndpointLocatorConfigurationTest { + + @Test + public void endpointLocatorShouldDefaultToServerPropertiesEndpointLocator() { + ConfigurableApplicationContext ctxt = new SpringApplication( + EmptyConfiguration.class).run(); + assertThat(ctxt.getBean(EndpointLocator.class)) + .isInstanceOf(ServerPropertiesEndpointLocator.class); + ctxt.close(); + } + + @Test + public void endpointLocatorShouldDefaultToServerPropertiesEndpointLocatorEvenWhenDiscoveryClientPresent() { + ConfigurableApplicationContext ctxt = new SpringApplication( + ConfigurationWithDiscoveryClient.class).run(); + assertThat(ctxt.getBean(EndpointLocator.class)) + .isInstanceOf(ServerPropertiesEndpointLocator.class); + ctxt.close(); + } + + @Test + public void endpointLocatorShouldRespectExistingEndpointLocator() { + ConfigurableApplicationContext ctxt = new SpringApplication( + ConfigurationWithCustomLocator.class).run(); + assertThat(ctxt.getBean(EndpointLocator.class)) + .isSameAs(ConfigurationWithCustomLocator.locator); + ctxt.close(); + } + + @Test + public void endpointLocatorShouldBeFallbackHavingEndpointLocatorWhenAskedTo() { + ConfigurableApplicationContext ctxt = new SpringApplication( + ConfigurationWithDiscoveryClient.class).run("--spring.zipkin.locator.discovery.enabled=true"); + assertThat(ctxt.getBean(EndpointLocator.class)) + .isInstanceOf(FallbackHavingEndpointLocator.class); + ctxt.close(); + } + + @Test + public void endpointLocatorShouldRespectExistingEndpointLocatorEvenWhenAskedToBeDiscovery() { + ConfigurableApplicationContext ctxt = new SpringApplication( + ConfigurationWithDiscoveryClient.class,ConfigurationWithCustomLocator.class).run("--spring.zipkin.locator.discovery.enabled=true"); + assertThat(ctxt.getBean(EndpointLocator.class)) + .isSameAs(ConfigurationWithCustomLocator.locator); + ctxt.close(); + } + + @Configuration + @EnableAutoConfiguration + public static class EmptyConfiguration { + } + + @Configuration + @EnableAutoConfiguration + public static class ConfigurationWithDiscoveryClient { + @Bean + public DiscoveryClient getDiscoveryClient() { + return Mockito.mock(DiscoveryClient.class); + } + } + + @Configuration + @EnableAutoConfiguration + public static class ConfigurationWithCustomLocator { + static EndpointLocator locator = Mockito.mock(EndpointLocator.class); + + @Bean + public EndpointLocator getEndpointLocator() { + return locator; + } + } + +} \ No newline at end of file diff --git a/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/HttpZipkinSpanReporterTest.java b/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/HttpZipkinSpanReporterTest.java index e5b0a9b1e..a7b9ddb82 100644 --- a/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/HttpZipkinSpanReporterTest.java +++ b/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/HttpZipkinSpanReporterTest.java @@ -6,6 +6,8 @@ import java.util.concurrent.atomic.AtomicReference; import org.junit.Rule; import org.junit.Test; import org.springframework.boot.autoconfigure.web.ServerProperties; +import org.springframework.cloud.commons.util.InetUtils; +import org.springframework.cloud.commons.util.InetUtilsProperties; import org.springframework.cloud.sleuth.DefaultSpanNamer; import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.log.NoOpSpanLogger; @@ -130,7 +132,7 @@ public class HttpZipkinSpanReporterTest { Tracer tracer = new DefaultTracer(new AlwaysSampler(), new Random(), new DefaultSpanNamer(), new NoOpSpanLogger(), new ZipkinSpanListener(receivedSpan::set, new ServerPropertiesEndpointLocator(new ServerProperties(), "foo", - new ZipkinProperties()))); + new ZipkinProperties(), new InetUtils(new InetUtilsProperties())))); // tag::service_name[] org.springframework.cloud.sleuth.Span newSpan = tracer.createSpan("redis"); try { diff --git a/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/ServerPropertiesEndpointLocatorTests.java b/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/ServerPropertiesEndpointLocatorTests.java index 2614388f5..92304542c 100644 --- a/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/ServerPropertiesEndpointLocatorTests.java +++ b/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/ServerPropertiesEndpointLocatorTests.java @@ -20,46 +20,53 @@ import java.net.InetAddress; import java.net.UnknownHostException; import org.junit.Test; +import org.mockito.Mockito; import org.springframework.boot.autoconfigure.web.ServerProperties; +import org.springframework.cloud.commons.util.InetUtils; +import org.springframework.cloud.commons.util.InetUtilsProperties; import static org.assertj.core.api.Assertions.assertThat; public class ServerPropertiesEndpointLocatorTests { + public static final byte[] ADDRESS1234 = { 1, 2, 3, 4 }; + @Test - public void portDefaultsTo8080() { + public void portDefaultsTo8080() throws UnknownHostException { ServerPropertiesEndpointLocator locator = new ServerPropertiesEndpointLocator( - new ServerProperties(), "unknown", new ZipkinProperties()); + new ServerProperties(), "unknown", new ZipkinProperties(), + localAddress(ADDRESS1234)); assertThat(locator.local().port).isEqualTo((short) 8080); } @Test - public void portFromServerProperties() { + public void portFromServerProperties() throws UnknownHostException { ServerProperties properties = new ServerProperties(); properties.setPort(1234); ServerPropertiesEndpointLocator locator = new ServerPropertiesEndpointLocator( - properties, "unknown", new ZipkinProperties()); + properties, "unknown", new ZipkinProperties(),localAddress(ADDRESS1234)); assertThat(locator.local().port).isEqualTo((short) 1234); } @Test - public void portDefaultsToLocalhost() { + public void portDefaultsToLocalhost() throws UnknownHostException { ServerPropertiesEndpointLocator locator = new ServerPropertiesEndpointLocator( - new ServerProperties(), "unknown", new ZipkinProperties()); + new ServerProperties(), "unknown", new ZipkinProperties(), localAddress(ADDRESS1234)); - assertThat(locator.local().ipv4).isEqualTo(127 << 24 | 1); + assertThat(locator.local().ipv4).isEqualTo(1 << 24 | 2 << 16 | 3 << 8 | 4); } @Test public void hostFromServerPropertiesIp() throws UnknownHostException { ServerProperties properties = new ServerProperties(); - properties.setAddress(InetAddress.getByAddress(new byte[] { 1, 2, 3, 4 })); + properties.setAddress(InetAddress.getByAddress(ADDRESS1234)); ServerPropertiesEndpointLocator locator = new ServerPropertiesEndpointLocator( - properties, "unknown", new ZipkinProperties()); + properties, "unknown", new ZipkinProperties(), + localAddress(new byte[] { 4, 4, 4, 4 })); assertThat(locator.local().ipv4).isEqualTo(1 << 24 | 2 << 16 | 3 << 8 | 4); } @@ -71,8 +78,15 @@ public class ServerPropertiesEndpointLocatorTests { zipkinProperties.getService().setName("foo"); ServerPropertiesEndpointLocator locator = new ServerPropertiesEndpointLocator( - properties, "unknown", zipkinProperties); + properties, "unknown", zipkinProperties,localAddress(ADDRESS1234)); assertThat(locator.local().serviceName).isEqualTo("foo"); } + + private InetUtils localAddress(byte[] address) throws UnknownHostException { + InetUtils mocked = Mockito.spy(new InetUtils(new InetUtilsProperties())); + Mockito.when(mocked.findFirstNonLoopbackAddress()) + .thenReturn(InetAddress.getByAddress(address)); + return mocked; + } } From 7dd5a2680293a24b66b8a41ac3d82678f783f164 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Thu, 12 Jan 2017 12:28:53 +0100 Subject: [PATCH 195/235] Bumped versions before release --- benchmarks/pom.xml | 2 +- docs/pom.xml | 2 +- pom.xml | 12 ++++++------ spring-cloud-sleuth-core/pom.xml | 2 +- spring-cloud-sleuth-dependencies/pom.xml | 4 ++-- spring-cloud-sleuth-samples/pom.xml | 2 +- .../spring-cloud-sleuth-sample-feign/pom.xml | 2 +- .../spring-cloud-sleuth-sample-messaging/pom.xml | 2 +- .../spring-cloud-sleuth-sample-ribbon/pom.xml | 2 +- .../spring-cloud-sleuth-sample-stream/pom.xml | 2 +- .../spring-cloud-sleuth-sample-test-core/pom.xml | 2 +- .../spring-cloud-sleuth-sample-websocket/pom.xml | 2 +- .../spring-cloud-sleuth-sample-zipkin-stream/pom.xml | 2 +- .../spring-cloud-sleuth-sample-zipkin/pom.xml | 2 +- .../spring-cloud-sleuth-sample/pom.xml | 2 +- spring-cloud-sleuth-stream/pom.xml | 2 +- spring-cloud-sleuth-zipkin-stream/pom.xml | 2 +- spring-cloud-sleuth-zipkin/pom.xml | 2 +- spring-cloud-starter-sleuth/pom.xml | 2 +- spring-cloud-starter-zipkin/pom.xml | 2 +- 20 files changed, 26 insertions(+), 26 deletions(-) diff --git a/benchmarks/pom.xml b/benchmarks/pom.xml index ff46084bd..56d7de53a 100644 --- a/benchmarks/pom.xml +++ b/benchmarks/pom.xml @@ -17,7 +17,7 @@ Benchmarks Benchmarks (JMH) org.springframework.cloud - 1.1.1.BUILD-SNAPSHOT + 1.1.1.RELEASE benchmarks diff --git a/docs/pom.xml b/docs/pom.xml index b09884dde..7f96cfdd6 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.1.BUILD-SNAPSHOT + 1.1.1.RELEASE spring-cloud-sleuth-docs pom diff --git a/pom.xml b/pom.xml index 2c95adf89..63017867e 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 spring-cloud-sleuth - 1.1.1.BUILD-SNAPSHOT + 1.1.1.RELEASE pom Spring Cloud Sleuth Spring Cloud Sleuth @@ -13,7 +13,7 @@ org.springframework.cloud spring-cloud-build - 1.2.2.BUILD-SNAPSHOT + 1.2.1.RELEASE @@ -229,10 +229,10 @@ 1.8 2.19.1 2.17 - 1.2.2.BUILD-SNAPSHOT - 1.1.7.BUILD-SNAPSHOT - Brooklyn.BUILD-SNAPSHOT - 1.2.4.BUILD-SNAPSHOT + 1.2.1.RELEASE + 1.1.7.RELEASE + Brooklyn.SR1 + 1.2.4.RELEASE diff --git a/spring-cloud-sleuth-core/pom.xml b/spring-cloud-sleuth-core/pom.xml index 2f5de0e91..2b9329f5f 100644 --- a/spring-cloud-sleuth-core/pom.xml +++ b/spring-cloud-sleuth-core/pom.xml @@ -11,7 +11,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.1.BUILD-SNAPSHOT + 1.1.1.RELEASE .. diff --git a/spring-cloud-sleuth-dependencies/pom.xml b/spring-cloud-sleuth-dependencies/pom.xml index 37bcef11e..7ad669fd6 100644 --- a/spring-cloud-sleuth-dependencies/pom.xml +++ b/spring-cloud-sleuth-dependencies/pom.xml @@ -5,11 +5,11 @@ spring-cloud-dependencies-parent org.springframework.cloud - 1.2.2.BUILD-SNAPSHOT + 1.2.1.RELEASE spring-cloud-sleuth-dependencies - 1.1.1.BUILD-SNAPSHOT + 1.1.1.RELEASE pom spring-cloud-sleuth-dependencies Spring Cloud Sleuth Dependencies diff --git a/spring-cloud-sleuth-samples/pom.xml b/spring-cloud-sleuth-samples/pom.xml index d237605c4..7241d64c5 100644 --- a/spring-cloud-sleuth-samples/pom.xml +++ b/spring-cloud-sleuth-samples/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.1.BUILD-SNAPSHOT + 1.1.1.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml index bfd3d54b4..33e8157ac 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml @@ -20,7 +20,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.1.BUILD-SNAPSHOT + 1.1.1.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml index 3f50f86d8..26bb29c45 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.1.BUILD-SNAPSHOT + 1.1.1.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml index b35e9af86..4fa3f33ef 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.1.BUILD-SNAPSHOT + 1.1.1.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml index b28037998..4f76b4591 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.1.BUILD-SNAPSHOT + 1.1.1.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml index a62d43484..855abbbbf 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.1.BUILD-SNAPSHOT + 1.1.1.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml index 819387b28..4acedef9f 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.1.BUILD-SNAPSHOT + 1.1.1.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml index 0e3e7cad1..17463db65 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.1.BUILD-SNAPSHOT + 1.1.1.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml index e5cef3560..753252511 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml @@ -27,7 +27,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.1.BUILD-SNAPSHOT + 1.1.1.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml index 2228d7b10..ac8c30a2f 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.1.BUILD-SNAPSHOT + 1.1.1.RELEASE .. diff --git a/spring-cloud-sleuth-stream/pom.xml b/spring-cloud-sleuth-stream/pom.xml index 1082b1a7a..d1bbb99d1 100644 --- a/spring-cloud-sleuth-stream/pom.xml +++ b/spring-cloud-sleuth-stream/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.1.BUILD-SNAPSHOT + 1.1.1.RELEASE diff --git a/spring-cloud-sleuth-zipkin-stream/pom.xml b/spring-cloud-sleuth-zipkin-stream/pom.xml index bc8cde97f..bc9a1e870 100644 --- a/spring-cloud-sleuth-zipkin-stream/pom.xml +++ b/spring-cloud-sleuth-zipkin-stream/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.1.BUILD-SNAPSHOT + 1.1.1.RELEASE diff --git a/spring-cloud-sleuth-zipkin/pom.xml b/spring-cloud-sleuth-zipkin/pom.xml index 9e6ebd7c5..a499c3ff3 100644 --- a/spring-cloud-sleuth-zipkin/pom.xml +++ b/spring-cloud-sleuth-zipkin/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.1.BUILD-SNAPSHOT + 1.1.1.RELEASE .. diff --git a/spring-cloud-starter-sleuth/pom.xml b/spring-cloud-starter-sleuth/pom.xml index d5954e6e6..e22bfd2f3 100644 --- a/spring-cloud-starter-sleuth/pom.xml +++ b/spring-cloud-starter-sleuth/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.1.BUILD-SNAPSHOT + 1.1.1.RELEASE .. spring-cloud-starter-sleuth diff --git a/spring-cloud-starter-zipkin/pom.xml b/spring-cloud-starter-zipkin/pom.xml index 6ecd39bf7..03fff3553 100644 --- a/spring-cloud-starter-zipkin/pom.xml +++ b/spring-cloud-starter-zipkin/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.1.BUILD-SNAPSHOT + 1.1.1.RELEASE .. spring-cloud-starter-zipkin From 49e92443fb0361e51573322059b6ac7c1de4e353 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Thu, 12 Jan 2017 13:02:08 +0100 Subject: [PATCH 196/235] Going back to snapshots --- benchmarks/pom.xml | 2 +- docs/pom.xml | 2 +- pom.xml | 12 ++++++------ spring-cloud-sleuth-core/pom.xml | 2 +- spring-cloud-sleuth-dependencies/pom.xml | 4 ++-- spring-cloud-sleuth-samples/pom.xml | 2 +- .../spring-cloud-sleuth-sample-feign/pom.xml | 2 +- .../spring-cloud-sleuth-sample-messaging/pom.xml | 2 +- .../spring-cloud-sleuth-sample-ribbon/pom.xml | 2 +- .../spring-cloud-sleuth-sample-stream/pom.xml | 2 +- .../spring-cloud-sleuth-sample-test-core/pom.xml | 2 +- .../spring-cloud-sleuth-sample-websocket/pom.xml | 2 +- .../spring-cloud-sleuth-sample-zipkin-stream/pom.xml | 2 +- .../spring-cloud-sleuth-sample-zipkin/pom.xml | 2 +- .../spring-cloud-sleuth-sample/pom.xml | 2 +- spring-cloud-sleuth-stream/pom.xml | 2 +- spring-cloud-sleuth-zipkin-stream/pom.xml | 2 +- spring-cloud-sleuth-zipkin/pom.xml | 2 +- spring-cloud-starter-sleuth/pom.xml | 2 +- spring-cloud-starter-zipkin/pom.xml | 2 +- 20 files changed, 26 insertions(+), 26 deletions(-) diff --git a/benchmarks/pom.xml b/benchmarks/pom.xml index 56d7de53a..cd9695e76 100644 --- a/benchmarks/pom.xml +++ b/benchmarks/pom.xml @@ -17,7 +17,7 @@ Benchmarks Benchmarks (JMH) org.springframework.cloud - 1.1.1.RELEASE + 1.1.2.BUILD-SNAPSHOT benchmarks diff --git a/docs/pom.xml b/docs/pom.xml index 7f96cfdd6..d6fb933d7 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.1.RELEASE + 1.1.2.BUILD-SNAPSHOT spring-cloud-sleuth-docs pom diff --git a/pom.xml b/pom.xml index 63017867e..80455f1c5 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 spring-cloud-sleuth - 1.1.1.RELEASE + 1.1.2.BUILD-SNAPSHOT pom Spring Cloud Sleuth Spring Cloud Sleuth @@ -13,7 +13,7 @@ org.springframework.cloud spring-cloud-build - 1.2.1.RELEASE + 1.2.2.BUILD-SNAPSHOT @@ -229,10 +229,10 @@ 1.8 2.19.1 2.17 - 1.2.1.RELEASE - 1.1.7.RELEASE - Brooklyn.SR1 - 1.2.4.RELEASE + 1.2.2.BUILD-SNAPSHOT + 1.1.8.BUILD-SNAPSHOT + Brooklyn.BUILD-SNAPSHOT + 1.2.5.BUILD-SNAPSHOT diff --git a/spring-cloud-sleuth-core/pom.xml b/spring-cloud-sleuth-core/pom.xml index 2b9329f5f..5e7a6ca7f 100644 --- a/spring-cloud-sleuth-core/pom.xml +++ b/spring-cloud-sleuth-core/pom.xml @@ -11,7 +11,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.1.RELEASE + 1.1.2.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-dependencies/pom.xml b/spring-cloud-sleuth-dependencies/pom.xml index 7ad669fd6..bafefab5a 100644 --- a/spring-cloud-sleuth-dependencies/pom.xml +++ b/spring-cloud-sleuth-dependencies/pom.xml @@ -5,11 +5,11 @@ spring-cloud-dependencies-parent org.springframework.cloud - 1.2.1.RELEASE + 1.2.2.BUILD-SNAPSHOT spring-cloud-sleuth-dependencies - 1.1.1.RELEASE + 1.1.2.BUILD-SNAPSHOT pom spring-cloud-sleuth-dependencies Spring Cloud Sleuth Dependencies diff --git a/spring-cloud-sleuth-samples/pom.xml b/spring-cloud-sleuth-samples/pom.xml index 7241d64c5..0a68a9f82 100644 --- a/spring-cloud-sleuth-samples/pom.xml +++ b/spring-cloud-sleuth-samples/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.1.RELEASE + 1.1.2.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml index 33e8157ac..4bb5a3a13 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml @@ -20,7 +20,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.1.RELEASE + 1.1.2.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml index 26bb29c45..f18694096 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.1.RELEASE + 1.1.2.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml index 4fa3f33ef..06b531e81 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.1.RELEASE + 1.1.2.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml index 4f76b4591..e4800a105 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.1.RELEASE + 1.1.2.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml index 855abbbbf..068551237 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.1.RELEASE + 1.1.2.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml index 4acedef9f..464cf8d49 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.1.RELEASE + 1.1.2.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml index 17463db65..b4f0ad234 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.1.RELEASE + 1.1.2.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml index 753252511..ebe115ae2 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml @@ -27,7 +27,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.1.RELEASE + 1.1.2.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml index ac8c30a2f..cea334fb2 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.1.RELEASE + 1.1.2.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-stream/pom.xml b/spring-cloud-sleuth-stream/pom.xml index d1bbb99d1..48205c5c0 100644 --- a/spring-cloud-sleuth-stream/pom.xml +++ b/spring-cloud-sleuth-stream/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.1.RELEASE + 1.1.2.BUILD-SNAPSHOT diff --git a/spring-cloud-sleuth-zipkin-stream/pom.xml b/spring-cloud-sleuth-zipkin-stream/pom.xml index bc9a1e870..0fcd5d59c 100644 --- a/spring-cloud-sleuth-zipkin-stream/pom.xml +++ b/spring-cloud-sleuth-zipkin-stream/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.1.RELEASE + 1.1.2.BUILD-SNAPSHOT diff --git a/spring-cloud-sleuth-zipkin/pom.xml b/spring-cloud-sleuth-zipkin/pom.xml index a499c3ff3..ea63e7d72 100644 --- a/spring-cloud-sleuth-zipkin/pom.xml +++ b/spring-cloud-sleuth-zipkin/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.1.RELEASE + 1.1.2.BUILD-SNAPSHOT .. diff --git a/spring-cloud-starter-sleuth/pom.xml b/spring-cloud-starter-sleuth/pom.xml index e22bfd2f3..46914864e 100644 --- a/spring-cloud-starter-sleuth/pom.xml +++ b/spring-cloud-starter-sleuth/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.1.RELEASE + 1.1.2.BUILD-SNAPSHOT .. spring-cloud-starter-sleuth diff --git a/spring-cloud-starter-zipkin/pom.xml b/spring-cloud-starter-zipkin/pom.xml index 03fff3553..bae3c944e 100644 --- a/spring-cloud-starter-zipkin/pom.xml +++ b/spring-cloud-starter-zipkin/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.1.RELEASE + 1.1.2.BUILD-SNAPSHOT .. spring-cloud-starter-zipkin From a396a096035c9ca22dda4291f86c03727200bd91 Mon Sep 17 00:00:00 2001 From: Adrian Cole Date: Thu, 19 Jan 2017 20:17:24 +0800 Subject: [PATCH 197/235] Updates to latest zipkin 1.19 (#495) Most changes are not very interesting to sleuth, though the server is quite a lot better with UI fixes galore. For sleuth mainly, small api unaffecting glitch fixes around the Span.Builder. --- spring-cloud-sleuth-dependencies/pom.xml | 4 ++-- spring-cloud-sleuth-samples/pom.xml | 4 ++-- .../spring-cloud-sleuth-sample-zipkin/pom.xml | 7 +++++++ spring-cloud-sleuth-zipkin/pom.xml | 7 +++++++ 4 files changed, 18 insertions(+), 4 deletions(-) diff --git a/spring-cloud-sleuth-dependencies/pom.xml b/spring-cloud-sleuth-dependencies/pom.xml index bafefab5a..94cb8316f 100644 --- a/spring-cloud-sleuth-dependencies/pom.xml +++ b/spring-cloud-sleuth-dependencies/pom.xml @@ -14,8 +14,8 @@ spring-cloud-sleuth-dependencies Spring Cloud Sleuth Dependencies - 1.16.2 - 0.6.9 + 1.19.2 + 0.6.12 diff --git a/spring-cloud-sleuth-samples/pom.xml b/spring-cloud-sleuth-samples/pom.xml index 0a68a9f82..ba912bd17 100644 --- a/spring-cloud-sleuth-samples/pom.xml +++ b/spring-cloud-sleuth-samples/pom.xml @@ -59,12 +59,12 @@ io.zipkin.java zipkin - 1.16.2 + 1.19.2 io.zipkin.java zipkin-server - 1.16.2 + 1.19.2 diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml index ebe115ae2..c08fe8fb6 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml @@ -107,6 +107,13 @@ zipkin-junit test + + + com.squareup.okhttp3 + okhttp + 3.5.0 + test +
    diff --git a/spring-cloud-sleuth-zipkin/pom.xml b/spring-cloud-sleuth-zipkin/pom.xml index ea63e7d72..eb1e2141b 100644 --- a/spring-cloud-sleuth-zipkin/pom.xml +++ b/spring-cloud-sleuth-zipkin/pom.xml @@ -88,6 +88,13 @@ zipkin-junit test
    + + + com.squareup.okhttp3 + okhttp + 3.5.0 + test + org.aspectj aspectjrt From e027d4cfb50433cfdd892e6193a84f2ecaadf96f Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Mon, 23 Jan 2017 12:37:39 +0100 Subject: [PATCH 198/235] Making the branch 1.5.0 boot compatible fixes #499 --- .../web/TraceWebAutoConfiguration.java | 2 +- .../TraceAutoConfigurationTests.java | 2 +- ...oConfigurationWithDisabledSleuthTests.java | 4 +- .../async/issues/issue410/Issue410Tests.java | 14 ++-- .../HystrixAnnotationsIntegrationTests.java | 18 ++--- .../instrument/hystrix/TraceCommandTests.java | 1 - .../MessagingSpanExtractorTests.java | 1 - .../messaging/MessagingSpanInjectorTests.java | 1 - .../TraceChannelInterceptorTests.java | 13 ++- ...extPropagationChannelInterceptorTests.java | 10 +-- .../TraceWebSocketAutoConfigurationTest.java | 9 ++- .../instrument/rxjava/SleuthRxJavaTests.java | 8 +- .../scheduling/TracingOnScheduledTests.java | 8 +- ...stTemplateTraceAspectIntegrationTests.java | 13 ++- .../web/SpringDataInstrumentationTests.java | 24 +++--- .../web/TraceAsyncIntegrationTests.java | 14 ++-- ...raceCustomFilterResponseInjectorTests.java | 11 ++- ...ceFilterAlwaysSamplerIntegrationTests.java | 8 +- .../web/TraceFilterCustomExtractorTests.java | 12 ++- .../web/TraceFilterIntegrationTests.java | 9 +-- ...terWebIntegrationMultipleFiltersTests.java | 13 ++- .../web/TraceFilterWebIntegrationTests.java | 11 ++- .../instrument/web/TraceWebDisabledTests.java | 11 ++- ...eWebAsyncClientAutoConfigurationTests.java | 29 ++++--- .../WebClientDiscoveryExceptionTests.java | 15 ++-- .../exception/WebClientExceptionTests.java | 19 +++-- .../feign/issues/issue350/Issue350Tests.java | 19 ++--- .../feign/issues/issue362/Issue362Tests.java | 15 ++-- .../feign/issues/issue393/Issue393Tests.java | 13 ++- .../FeignClientServerErrorTests.java | 15 ++-- .../client/integration/WebClientTests.java | 9 +-- .../MultipleHopsIntegrationTests.java | 4 +- .../instrument/web/view/Issue469Tests.java | 15 ++-- .../zuul/TraceZuulIntegrationTests.java | 80 ++++++++++--------- .../sample/SampleFeignApplicationTests.java | 14 ++-- .../MessagingApplicationTests.java | 14 ++-- .../SampleMessagingApplicationTests.java | 14 ++-- .../sample/SampleRibbonApplicationTests.java | 14 ++-- .../sample/SampleSleuthApplicationTests.java | 11 ++- .../SampleWebsocketApplicationTests.java | 14 ++-- .../example/ZipkinServerApplicationTests.java | 13 +-- .../java/integration/ZipkinStreamTests.java | 16 ++-- .../test/java/integration/ZipkinTests.java | 16 ++-- .../sample/SampleSleuthApplicationTests.java | 14 ++-- .../sample/SampleSleuthApplicationTests.java | 10 +-- .../SleuthStreamAutoConfigurationTest.java | 2 +- .../StreamEnvironmentPostProcessorTests.java | 2 +- .../stream/StreamSpanListenerTests.java | 4 +- .../TraceIgnoringChannelInterceptorTests.java | 4 +- .../stream/ZipkinServerApplicationTests.java | 16 ++-- .../zipkin/ZipkinSpanListenerTests.java | 8 +- 51 files changed, 298 insertions(+), 338 deletions(-) diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java index 8645462f8..7b761239c 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java @@ -27,8 +27,8 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; 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.boot.web.servlet.FilterRegistrationBean; import org.springframework.cloud.sleuth.SpanExtractor; import org.springframework.cloud.sleuth.SpanNamer; import org.springframework.cloud.sleuth.SpanReporter; diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfigurationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfigurationTests.java index 65eca35d3..843c71bac 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfigurationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfigurationTests.java @@ -23,7 +23,7 @@ import org.springframework.cloud.sleuth.sampler.NeverSampler; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.boot.test.EnvironmentTestUtils.addEnvironment; +import static org.springframework.boot.test.util.EnvironmentTestUtils.addEnvironment; public class TraceAutoConfigurationTests { diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfigurationWithDisabledSleuthTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfigurationWithDisabledSleuthTests.java index 415c7392a..bd9789c48 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfigurationWithDisabledSleuthTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfigurationWithDisabledSleuthTests.java @@ -24,9 +24,9 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit4.SpringRunner; -@RunWith(SpringJUnit4ClassRunner.class) +@RunWith(SpringRunner.class) @ContextConfiguration(classes = TraceAutoConfigurationWithDisabledSleuthTests.Config.class) @TestPropertySource(properties = "spring.sleuth.enabled=false") public class TraceAutoConfigurationWithDisabledSleuthTests { diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue410/Issue410Tests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue410/Issue410Tests.java index c7572f627..388ed3c3a 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue410/Issue410Tests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue410/Issue410Tests.java @@ -30,8 +30,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration; -import org.springframework.boot.test.SpringApplicationConfiguration; -import org.springframework.boot.test.WebIntegrationTest; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.sleuth.Sampler; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.Tracer; @@ -44,8 +43,7 @@ import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.stereotype.Component; -import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit4.SpringRunner; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; @@ -57,10 +55,10 @@ import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; /** * @author Marcin Grzejszczak */ -@RunWith(SpringJUnit4ClassRunner.class) -@SpringApplicationConfiguration(classes = Application.class) -@WebIntegrationTest -@TestPropertySource(properties = {"ribbon.eureka.enabled=false", "feign.hystrix.enabled=false", "server.port=0"}) +@RunWith(SpringRunner.class) +@SpringBootTest(classes = Application.class, + properties = {"ribbon.eureka.enabled=false", "feign.hystrix.enabled=false", "server.port=0"}, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class Issue410Tests { @Autowired Environment environment; diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/hystrix/HystrixAnnotationsIntegrationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/hystrix/HystrixAnnotationsIntegrationTests.java index 11dd80608..208d25767 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/hystrix/HystrixAnnotationsIntegrationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/hystrix/HystrixAnnotationsIntegrationTests.java @@ -16,16 +16,15 @@ package org.springframework.cloud.sleuth.instrument.hystrix; -import com.jayway.awaitility.Awaitility; -import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; -import com.netflix.hystrix.strategy.HystrixPlugins; +import java.util.concurrent.atomic.AtomicReference; + import org.junit.After; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.netflix.hystrix.EnableHystrix; import org.springframework.cloud.sleuth.Sampler; import org.springframework.cloud.sleuth.Span; @@ -36,15 +35,16 @@ import org.springframework.cloud.sleuth.trace.TestSpanContextHolder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit4.SpringRunner; -import java.util.concurrent.atomic.AtomicReference; +import com.jayway.awaitility.Awaitility; +import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; +import com.netflix.hystrix.strategy.HystrixPlugins; -import static org.assertj.core.api.BDDAssertions.then; import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; -@RunWith(SpringJUnit4ClassRunner.class) -@SpringApplicationConfiguration(classes = { +@RunWith(SpringRunner.class) +@SpringBootTest(classes = { HystrixAnnotationsIntegrationTests.TestConfig.class }) @DirtiesContext public class HystrixAnnotationsIntegrationTests { diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/hystrix/TraceCommandTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/hystrix/TraceCommandTests.java index 1ab54bf9f..b9e03c4fd 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/hystrix/TraceCommandTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/hystrix/TraceCommandTests.java @@ -39,7 +39,6 @@ import com.netflix.hystrix.strategy.HystrixPlugins; import static com.netflix.hystrix.HystrixCommand.Setter.withGroupKey; import static com.netflix.hystrix.HystrixCommandGroupKey.Factory.asKey; -import static org.assertj.core.api.BDDAssertions.then; import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; public class TraceCommandTests { diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingSpanExtractorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingSpanExtractorTests.java index 1070d43f4..5e9bb1cb9 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingSpanExtractorTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingSpanExtractorTests.java @@ -28,7 +28,6 @@ import org.springframework.messaging.support.MessageBuilder; import org.springframework.util.StringUtils; import static org.assertj.core.api.Assertions.fail; -import static org.assertj.core.api.BDDAssertions.then; import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; public class MessagingSpanExtractorTests { diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingSpanInjectorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingSpanInjectorTests.java index 1be79222a..5d25c6ac3 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingSpanInjectorTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingSpanInjectorTests.java @@ -16,7 +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; 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 d86eab7fd..e80bc69f2 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 @@ -26,8 +26,7 @@ import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.IntegrationTest; -import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.sleuth.Sampler; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.Tracer; @@ -47,18 +46,18 @@ import org.springframework.messaging.MessageHandler; import org.springframework.messaging.MessagingException; import org.springframework.messaging.support.MessageHeaderAccessor; import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit4.SpringRunner; -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 */ -@RunWith(SpringJUnit4ClassRunner.class) -@SpringApplicationConfiguration(classes = App.class) -@IntegrationTest("spring.sleuth.integration.patterns=traced*") +@RunWith(SpringRunner.class) +@SpringBootTest(classes = App.class, + properties = "spring.sleuth.integration.patterns=traced*", + webEnvironment = SpringBootTest.WebEnvironment.NONE) @DirtiesContext public class TraceChannelInterceptorTests implements MessageHandler { diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/TraceContextPropagationChannelInterceptorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/TraceContextPropagationChannelInterceptorTests.java index 5461d7283..94647b6bb 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/TraceContextPropagationChannelInterceptorTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/TraceContextPropagationChannelInterceptorTests.java @@ -22,8 +22,7 @@ import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.IntegrationTest; -import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.sleuth.Sampler; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.Tracer; @@ -37,7 +36,7 @@ import org.springframework.integration.support.MessageBuilder; import org.springframework.messaging.Message; import org.springframework.messaging.PollableChannel; import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit4.SpringRunner; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; @@ -46,9 +45,8 @@ import static org.junit.Assert.assertNotNull; /** * @author Spencer Gibb */ -@RunWith(SpringJUnit4ClassRunner.class) -@SpringApplicationConfiguration(classes=App.class) -@IntegrationTest +@RunWith(SpringRunner.class) +@SpringBootTest(classes=App.class, webEnvironment = SpringBootTest.WebEnvironment.NONE) @DirtiesContext public class TraceContextPropagationChannelInterceptorTests { diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/websocket/TraceWebSocketAutoConfigurationTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/websocket/TraceWebSocketAutoConfigurationTest.java index 9e9839e01..729ee7ceb 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/websocket/TraceWebSocketAutoConfigurationTest.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/websocket/TraceWebSocketAutoConfigurationTest.java @@ -20,14 +20,14 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.sleuth.Sampler; import org.springframework.cloud.sleuth.instrument.messaging.TraceChannelInterceptor; import org.springframework.cloud.sleuth.sampler.AlwaysSampler; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.messaging.simp.config.MessageBrokerRegistry; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit4.SpringRunner; import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer; import org.springframework.web.socket.config.annotation.DelegatingWebSocketMessageBrokerConfiguration; import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; @@ -38,8 +38,9 @@ import static org.assertj.core.api.BDDAssertions.then; /** * @author Marcin Grzejszczak */ -@RunWith(SpringJUnit4ClassRunner.class) -@SpringApplicationConfiguration(classes = TraceWebSocketAutoConfigurationTest.Config.class) +@RunWith(SpringRunner.class) +@SpringBootTest(classes = TraceWebSocketAutoConfigurationTest.Config.class, + webEnvironment = SpringBootTest.WebEnvironment.NONE) public class TraceWebSocketAutoConfigurationTest { @Autowired diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/rxjava/SleuthRxJavaTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/rxjava/SleuthRxJavaTests.java index d9618e0eb..ebcf3b844 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/rxjava/SleuthRxJavaTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/rxjava/SleuthRxJavaTests.java @@ -11,7 +11,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.sleuth.Sampler; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.SpanReporter; @@ -21,7 +21,7 @@ import org.springframework.cloud.sleuth.trace.TestSpanContextHolder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit4.SpringRunner; import rx.Observable; import rx.functions.Action0; @@ -31,8 +31,8 @@ import rx.schedulers.Schedulers; import static com.jayway.awaitility.Awaitility.await; import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; -@RunWith(SpringJUnit4ClassRunner.class) -@SpringApplicationConfiguration(classes = {SleuthRxJavaTests.TestConfig.class}) +@RunWith(SpringRunner.class) +@SpringBootTest(classes = {SleuthRxJavaTests.TestConfig.class}) @DirtiesContext public class SleuthRxJavaTests { diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/scheduling/TracingOnScheduledTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/scheduling/TracingOnScheduledTests.java index 9ae6ac763..0d4498548 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/scheduling/TracingOnScheduledTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/scheduling/TracingOnScheduledTests.java @@ -22,7 +22,7 @@ import org.hamcrest.Matchers; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.instrument.DefaultTestAutoConfiguration; import org.springframework.cloud.sleuth.sampler.AlwaysSampler; @@ -30,13 +30,13 @@ import org.springframework.cloud.sleuth.trace.TestSpanContextHolder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit4.SpringRunner; import static com.jayway.awaitility.Awaitility.await; import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; -@RunWith(SpringJUnit4ClassRunner.class) -@SpringApplicationConfiguration(classes = {ScheduledTestConfiguration.class}) +@RunWith(SpringRunner.class) +@SpringBootTest(classes = {ScheduledTestConfiguration.class}) public class TracingOnScheduledTests { @Autowired TestBeanWithScheduledMethod beanWithScheduledMethod; diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/RestTemplateTraceAspectIntegrationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/RestTemplateTraceAspectIntegrationTests.java index b4ac59913..a7e56c454 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/RestTemplateTraceAspectIntegrationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/RestTemplateTraceAspectIntegrationTests.java @@ -8,8 +8,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.SpringApplicationConfiguration; -import org.springframework.boot.test.WebIntegrationTest; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.sleuth.Sampler; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.Tracer; @@ -21,7 +20,7 @@ import org.springframework.context.annotation.Import; import org.springframework.core.env.Environment; import org.springframework.http.MediaType; import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; @@ -43,10 +42,10 @@ import static org.springframework.test.web.servlet.result.MockMvcResultHandlers. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -@SpringApplicationConfiguration(classes = { - RestTemplateTraceAspectIntegrationTests.Config.class }) -@RunWith(SpringJUnit4ClassRunner.class) -@WebIntegrationTest(randomPort = true) +@SpringBootTest(classes = { + RestTemplateTraceAspectIntegrationTests.Config.class }, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@RunWith(SpringRunner.class) @DirtiesContext public class RestTemplateTraceAspectIntegrationTests { diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SpringDataInstrumentationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SpringDataInstrumentationTests.java index 742b382f2..5b26825dd 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SpringDataInstrumentationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SpringDataInstrumentationTests.java @@ -16,25 +16,21 @@ package org.springframework.cloud.sleuth.instrument.web; +import java.util.Collection; +import java.util.stream.Collectors; +import java.util.stream.Stream; import javax.annotation.PostConstruct; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; -import java.util.Collection; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import com.jayway.awaitility.Awaitility; - -import org.assertj.core.api.BDDAssertions; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.orm.jpa.EntityScan; -import org.springframework.boot.test.SpringApplicationConfiguration; -import org.springframework.boot.test.WebIntegrationTest; +import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.sleuth.Sampler; import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.assertions.ListOfSpans; @@ -52,17 +48,19 @@ import org.springframework.hateoas.Resources; import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit4.SpringRunner; import org.springframework.web.client.RestTemplate; +import com.jayway.awaitility.Awaitility; + import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; /** * @author Marcin Grzejszczak */ -@RunWith(SpringJUnit4ClassRunner.class) -@WebIntegrationTest({ "server.port=0" }) -@SpringApplicationConfiguration(classes = { ReservationServiceApplication.class }) +@RunWith(SpringRunner.class) +@SpringBootTest(classes = { ReservationServiceApplication.class }, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @DirtiesContext public class SpringDataInstrumentationTests { diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceAsyncIntegrationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceAsyncIntegrationTests.java index ba2003122..ce2eb910c 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceAsyncIntegrationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceAsyncIntegrationTests.java @@ -3,12 +3,11 @@ package org.springframework.cloud.sleuth.instrument.web; import java.util.concurrent.atomic.AtomicReference; -import com.jayway.awaitility.Awaitility; import org.junit.After; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.sleuth.Sampler; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.Tracer; @@ -19,13 +18,16 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.EnableAsync; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit4.SpringRunner; + +import com.jayway.awaitility.Awaitility; import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; -@RunWith(SpringJUnit4ClassRunner.class) -@SpringApplicationConfiguration(classes = { - TraceAsyncIntegrationTests.TraceAsyncITestConfiguration.class }) +@RunWith(SpringRunner.class) +@SpringBootTest(classes = { + TraceAsyncIntegrationTests.TraceAsyncITestConfiguration.class }, + webEnvironment = SpringBootTest.WebEnvironment.NONE) public class TraceAsyncIntegrationTests { @Autowired ClassPerformingAsyncLogic classPerformingAsyncLogic; diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceCustomFilterResponseInjectorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceCustomFilterResponseInjectorTests.java index a2f65aee1..254eb5578 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceCustomFilterResponseInjectorTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceCustomFilterResponseInjectorTests.java @@ -31,8 +31,7 @@ import org.junit.runner.RunWith; 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.SpringApplicationConfiguration; -import org.springframework.boot.test.WebIntegrationTest; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.SpanInjector; import org.springframework.cloud.sleuth.Tracer; @@ -43,7 +42,7 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.RequestEntity; import org.springframework.http.ResponseEntity; import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit4.SpringRunner; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -52,9 +51,9 @@ import org.springframework.web.filter.GenericFilterBean; import static org.assertj.core.api.BDDAssertions.then; -@RunWith(SpringJUnit4ClassRunner.class) -@SpringApplicationConfiguration(TraceCustomFilterResponseInjectorTests.Config.class) -@WebIntegrationTest(randomPort = true) +@RunWith(SpringRunner.class) +@SpringBootTest(classes = TraceCustomFilterResponseInjectorTests.Config.class, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @DirtiesContext public class TraceCustomFilterResponseInjectorTests { @Autowired RestTemplate restTemplate; 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 3e799e838..443fa292d 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 @@ -9,7 +9,7 @@ import org.apache.commons.logging.LogFactory; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.sleuth.NoOpSpanReporter; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.Tracer; @@ -19,7 +19,7 @@ import org.springframework.cloud.sleuth.sampler.AlwaysSampler; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.http.MediaType; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; @@ -27,8 +27,8 @@ import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -@RunWith(SpringJUnit4ClassRunner.class) -@SpringApplicationConfiguration(TraceFilterAlwaysSamplerIntegrationTests.Config.class) +@RunWith(SpringRunner.class) +@SpringBootTest(classes = TraceFilterAlwaysSamplerIntegrationTests.Config.class) public class TraceFilterAlwaysSamplerIntegrationTests extends AbstractMvcIntegrationTest { private static Log logger = LogFactory diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterCustomExtractorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterCustomExtractorTests.java index 708a5f4a0..b2956e533 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterCustomExtractorTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterCustomExtractorTests.java @@ -28,8 +28,7 @@ import org.junit.runner.RunWith; 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.SpringApplicationConfiguration; -import org.springframework.boot.test.WebIntegrationTest; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.sleuth.Sampler; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.SpanExtractor; @@ -44,19 +43,18 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.RequestEntity; import org.springframework.http.ResponseEntity; import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit4.SpringRunner; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import static com.jayway.awaitility.Awaitility.await; -import static org.assertj.core.api.BDDAssertions.then; import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; -@RunWith(SpringJUnit4ClassRunner.class) -@SpringApplicationConfiguration(TraceFilterCustomExtractorTests.Config.class) -@WebIntegrationTest(randomPort = true) +@RunWith(SpringRunner.class) +@SpringBootTest(classes = TraceFilterCustomExtractorTests.Config.class, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @DirtiesContext public class TraceFilterCustomExtractorTests { @Autowired Random random; diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterIntegrationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterIntegrationTests.java index 2942663f0..da98a0dbb 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterIntegrationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterIntegrationTests.java @@ -13,7 +13,7 @@ import org.junit.runner.RunWith; import org.slf4j.MDC; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.actuate.autoconfigure.ManagementServerProperties; -import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.sleuth.Sampler; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.SpanReporter; @@ -28,7 +28,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder; @@ -36,13 +36,12 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.request.async.DeferredResult; -import static org.assertj.core.api.BDDAssertions.then; import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -@RunWith(SpringJUnit4ClassRunner.class) -@SpringApplicationConfiguration(TraceFilterIntegrationTests.Config.class) +@RunWith(SpringRunner.class) +@SpringBootTest(classes = TraceFilterIntegrationTests.Config.class) public class TraceFilterIntegrationTests extends AbstractMvcIntegrationTest { private static Log logger = LogFactory.getLog(TraceFilterIntegrationTests.class); diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterWebIntegrationMultipleFiltersTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterWebIntegrationMultipleFiltersTests.java index e51800a45..e08ac7126 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterWebIntegrationMultipleFiltersTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterWebIntegrationMultipleFiltersTests.java @@ -29,9 +29,8 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.context.embedded.FilterRegistrationBean; -import org.springframework.boot.test.SpringApplicationConfiguration; -import org.springframework.boot.test.WebIntegrationTest; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.cloud.sleuth.Sampler; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.Tracer; @@ -42,7 +41,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.Environment; import org.springframework.http.client.ClientHttpResponse; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit4.SpringRunner; import org.springframework.web.client.DefaultResponseErrorHandler; import org.springframework.web.client.RestTemplate; import org.springframework.web.filter.GenericFilterBean; @@ -52,9 +51,9 @@ import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; /** * @author Marcin Grzejszczak */ -@RunWith(SpringJUnit4ClassRunner.class) -@WebIntegrationTest({ "server.port=0" }) -@SpringApplicationConfiguration(classes = { TraceFilterWebIntegrationMultipleFiltersTests.Config.class }) +@RunWith(SpringRunner.class) +@SpringBootTest(classes = { TraceFilterWebIntegrationMultipleFiltersTests.Config.class }, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class TraceFilterWebIntegrationMultipleFiltersTests { @Autowired Tracer tracer; diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterWebIntegrationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterWebIntegrationTests.java index 662f1567a..4db80f0e1 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterWebIntegrationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterWebIntegrationTests.java @@ -24,8 +24,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.SpringApplicationConfiguration; -import org.springframework.boot.test.WebIntegrationTest; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.sleuth.Sampler; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.Tracer; @@ -38,7 +37,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.Environment; import org.springframework.http.client.ClientHttpResponse; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit4.SpringRunner; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.DefaultResponseErrorHandler; @@ -49,9 +48,9 @@ import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; /** * @author Marcin Grzejszczak */ -@RunWith(SpringJUnit4ClassRunner.class) -@WebIntegrationTest({ "server.port=0" }) -@SpringApplicationConfiguration(classes = { TraceFilterWebIntegrationTests.Config.class }) +@RunWith(SpringRunner.class) +@SpringBootTest(classes = { TraceFilterWebIntegrationTests.Config.class }, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class TraceFilterWebIntegrationTests { @Autowired Tracer tracer; diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceWebDisabledTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceWebDisabledTests.java index 73919b566..51346249f 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceWebDisabledTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceWebDisabledTests.java @@ -3,17 +3,16 @@ package org.springframework.cloud.sleuth.instrument.web; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.IntegrationTest; -import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Configuration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit4.SpringRunner; /** * @author Marcin Grzejszczak */ -@RunWith(SpringJUnit4ClassRunner.class) -@IntegrationTest({ "spring.sleuth.web.enabled=true", "spring.sleuth.web.client.enabled=false"}) -@SpringApplicationConfiguration(classes = { TraceWebDisabledTests.Config.class }) +@RunWith(SpringRunner.class) +@SpringBootTest(classes = { TraceWebDisabledTests.Config.class }, + properties = { "spring.sleuth.web.enabled=true", "spring.sleuth.web.client.enabled=false"}) public class TraceWebDisabledTests { @Test diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfigurationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfigurationTests.java index ea7570cd3..fa350aa8d 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfigurationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfigurationTests.java @@ -29,8 +29,7 @@ import org.junit.experimental.runners.Enclosed; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.SpringApplicationConfiguration; -import org.springframework.boot.test.WebIntegrationTest; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.sampler.AlwaysSampler; @@ -46,7 +45,7 @@ import org.springframework.http.client.AsyncClientHttpRequestFactory; import org.springframework.http.client.ClientHttpRequest; import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.http.client.SimpleClientHttpRequestFactory; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit4.SpringRunner; import org.springframework.util.concurrent.ListenableFuture; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -62,9 +61,9 @@ import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; @RunWith(Enclosed.class) public class TraceWebAsyncClientAutoConfigurationTests { - @RunWith(SpringJUnit4ClassRunner.class) - @SpringApplicationConfiguration(classes = { CustomSyncAndAsyncClientFactory.TestConfiguration.class }) - @WebIntegrationTest(randomPort = true) + @RunWith(SpringRunner.class) + @SpringBootTest(classes = { CustomSyncAndAsyncClientFactory.TestConfiguration.class }, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public static class CustomSyncAndAsyncClientFactory { @Autowired AsyncRestTemplate asyncRestTemplate; @@ -96,9 +95,9 @@ public class TraceWebAsyncClientAutoConfigurationTests { } - @RunWith(SpringJUnit4ClassRunner.class) - @SpringApplicationConfiguration(classes = { CustomSyncClientFactory.TestConfiguration.class }) - @WebIntegrationTest(randomPort = true) + @RunWith(SpringRunner.class) + @SpringBootTest(classes = { CustomSyncClientFactory.TestConfiguration.class }, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public static class CustomSyncClientFactory { @Autowired AsyncRestTemplate asyncRestTemplate; @@ -123,9 +122,9 @@ public class TraceWebAsyncClientAutoConfigurationTests { } - @RunWith(SpringJUnit4ClassRunner.class) - @SpringApplicationConfiguration(classes = { CustomASyncClientFactory.TestConfiguration.class }) - @WebIntegrationTest(randomPort = true) + @RunWith(SpringRunner.class) + @SpringBootTest(classes = { CustomASyncClientFactory.TestConfiguration.class }, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public static class CustomASyncClientFactory { @Autowired AsyncRestTemplate asyncRestTemplate; @@ -164,9 +163,9 @@ public class TraceWebAsyncClientAutoConfigurationTests { } } - @RunWith(SpringJUnit4ClassRunner.class) - @SpringApplicationConfiguration(classes = { DurationChecking.TestConfiguration.class }) - @WebIntegrationTest(randomPort = true) + @RunWith(SpringRunner.class) + @SpringBootTest(classes = { DurationChecking.TestConfiguration.class }, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public static class DurationChecking { @Autowired AsyncRestTemplate asyncRestTemplate; @Autowired Environment environment; diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/discoveryexception/WebClientDiscoveryExceptionTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/discoveryexception/WebClientDiscoveryExceptionTests.java index eeeea91c6..cdeea84bd 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/discoveryexception/WebClientDiscoveryExceptionTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/discoveryexception/WebClientDiscoveryExceptionTests.java @@ -26,8 +26,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.SpringApplicationConfiguration; -import org.springframework.boot.test.WebIntegrationTest; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration; @@ -44,7 +43,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.ResponseEntity; import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit4.SpringRunner; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.client.RestTemplate; @@ -52,11 +51,11 @@ import org.springframework.web.client.RestTemplate; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.BDDAssertions.then; -@RunWith(SpringJUnit4ClassRunner.class) -@SpringApplicationConfiguration(classes = { - WebClientDiscoveryExceptionTests.TestConfiguration.class }) -@WebIntegrationTest(value = { - "spring.application.name=exceptionservice" }, randomPort = true) +@RunWith(SpringRunner.class) +@SpringBootTest(classes = { + WebClientDiscoveryExceptionTests.TestConfiguration.class }, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + properties = { "spring.application.name=exceptionservice" }) @DirtiesContext public class WebClientDiscoveryExceptionTests { diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/exception/WebClientExceptionTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/exception/WebClientExceptionTests.java index 73d167bec..dac57ca0e 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/exception/WebClientExceptionTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/exception/WebClientExceptionTests.java @@ -21,10 +21,6 @@ import java.lang.invoke.MethodHandles; import java.util.Collections; import java.util.Map; -import com.netflix.loadbalancer.BaseLoadBalancer; -import com.netflix.loadbalancer.ILoadBalancer; -import com.netflix.loadbalancer.Server; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.junit.After; @@ -38,8 +34,7 @@ import org.junit.contrib.java.lang.system.SystemOutRule; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.SpringApplicationConfiguration; -import org.springframework.boot.test.WebIntegrationTest; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.cloud.netflix.feign.EnableFeignClients; import org.springframework.cloud.netflix.feign.FeignClient; @@ -60,6 +55,10 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.client.RestTemplate; +import com.netflix.loadbalancer.BaseLoadBalancer; +import com.netflix.loadbalancer.ILoadBalancer; +import com.netflix.loadbalancer.Server; + import junitparams.JUnitParamsRunner; import junitparams.Parameters; @@ -67,10 +66,10 @@ import static junitparams.JUnitParamsRunner.$; import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; @RunWith(JUnitParamsRunner.class) -@SpringApplicationConfiguration(classes = { - WebClientExceptionTests.TestConfiguration.class }) -@WebIntegrationTest(value = {"ribbon.ConnectTimeout=30000", - "spring.application.name=exceptionservice" }, randomPort = true) +@SpringBootTest(classes = { + WebClientExceptionTests.TestConfiguration.class }, + properties = {"ribbon.ConnectTimeout=30000", "spring.application.name=exceptionservice" }, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class WebClientExceptionTests { private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue350/Issue350Tests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue350/Issue350Tests.java index 8c0c277ea..4d63cbcec 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue350/Issue350Tests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue350/Issue350Tests.java @@ -23,9 +23,8 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.SpringApplicationConfiguration; -import org.springframework.boot.test.TestRestTemplate; -import org.springframework.boot.test.WebIntegrationTest; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.cloud.netflix.feign.EnableFeignClients; import org.springframework.cloud.netflix.feign.FeignClient; import org.springframework.cloud.sleuth.Tracer; @@ -34,12 +33,10 @@ import org.springframework.cloud.sleuth.util.ExceptionUtils; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpStatus; -import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit4.SpringRunner; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.client.RestTemplate; import feign.Logger; @@ -48,13 +45,13 @@ import static org.assertj.core.api.BDDAssertions.then; /** * @author Marcin Grzejszczak */ -@RunWith(SpringJUnit4ClassRunner.class) -@SpringApplicationConfiguration(classes = Application.class) -@WebIntegrationTest -@TestPropertySource(properties = {"ribbon.eureka.enabled=false", "feign.hystrix.enabled=false", "server.port=9988"}) +@RunWith(SpringRunner.class) +@SpringBootTest(classes = Application.class, + webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, + properties = {"ribbon.eureka.enabled=false", "feign.hystrix.enabled=false", "server.port=9988"}) public class Issue350Tests { - RestTemplate template = new TestRestTemplate(); + TestRestTemplate template = new TestRestTemplate(); @Autowired Tracer tracer; @Before diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue362/Issue362Tests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue362/Issue362Tests.java index dcfd729fa..b1780f45e 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue362/Issue362Tests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue362/Issue362Tests.java @@ -26,20 +26,17 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.SpringApplicationConfiguration; -import org.springframework.boot.test.WebIntegrationTest; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.netflix.feign.EnableFeignClients; import org.springframework.cloud.netflix.feign.FeignClient; import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.assertions.SleuthAssertions; import org.springframework.cloud.sleuth.sampler.AlwaysSampler; import org.springframework.cloud.sleuth.util.ExceptionUtils; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit4.SpringRunner; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; @@ -57,10 +54,10 @@ import static org.assertj.core.api.BDDAssertions.then; /** * @author Marcin Grzejszczak */ -@RunWith(SpringJUnit4ClassRunner.class) -@SpringApplicationConfiguration(Application.class) -@WebIntegrationTest -@TestPropertySource(properties = {"ribbon.eureka.enabled=false", "feign.hystrix.enabled=false", "server.port=9998"}) +@RunWith(SpringRunner.class) +@SpringBootTest(classes = Application.class, webEnvironment = + SpringBootTest.WebEnvironment.DEFINED_PORT, + properties = {"ribbon.eureka.enabled=false", "feign.hystrix.enabled=false", "server.port=9998"}) public class Issue362Tests { RestTemplate template = new RestTemplate(); diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue393/Issue393Tests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue393/Issue393Tests.java index 10278508c..7e1387951 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue393/Issue393Tests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue393/Issue393Tests.java @@ -22,8 +22,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.SpringApplicationConfiguration; -import org.springframework.boot.test.WebIntegrationTest; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.netflix.feign.EnableFeignClients; import org.springframework.cloud.netflix.feign.FeignClient; @@ -34,8 +33,7 @@ import org.springframework.cloud.sleuth.util.ExceptionUtils; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.ResponseEntity; -import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit4.SpringRunner; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @@ -47,10 +45,9 @@ import static org.assertj.core.api.BDDAssertions.then; /** * @author Marcin Grzejszczak */ -@RunWith(SpringJUnit4ClassRunner.class) -@SpringApplicationConfiguration(Application.class) -@WebIntegrationTest -@TestPropertySource(properties = {"spring.application.name=demo-feign-uri", +@RunWith(SpringRunner.class) +@SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, + properties = {"spring.application.name=demo-feign-uri", "server.port=9978", "eureka.client.enabled=true", "ribbon.eureka.enabled=true"}) public class Issue393Tests { diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/servererrors/FeignClientServerErrorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/servererrors/FeignClientServerErrorTests.java index d102beca5..1f680e34c 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/servererrors/FeignClientServerErrorTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/servererrors/FeignClientServerErrorTests.java @@ -27,9 +27,8 @@ import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.OutputCapture; -import org.springframework.boot.test.SpringApplicationConfiguration; -import org.springframework.boot.test.WebIntegrationTest; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.rule.OutputCapture; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.cloud.netflix.feign.EnableFeignClients; import org.springframework.cloud.netflix.feign.FeignClient; @@ -47,7 +46,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit4.SpringRunner; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @@ -70,10 +69,10 @@ import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; * * @author ryarabori */ -@RunWith(SpringJUnit4ClassRunner.class) -@SpringApplicationConfiguration( - classes = {FeignClientServerErrorTests.TestConfiguration.class}) -@WebIntegrationTest(value = {"spring.application.name=fooservice"}, randomPort = true) +@RunWith(SpringRunner.class) +@SpringBootTest( + classes = {FeignClientServerErrorTests.TestConfiguration.class}, + properties = {"spring.application.name=fooservice"}, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class FeignClientServerErrorTests { @Autowired TestFeignInterface feignInterface; diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/integration/WebClientTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/integration/WebClientTests.java index 336401d13..c587a1196 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/integration/WebClientTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/integration/WebClientTests.java @@ -39,8 +39,7 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.web.BasicErrorController; import org.springframework.boot.autoconfigure.web.ErrorAttributes; import org.springframework.boot.autoconfigure.web.ServerProperties; -import org.springframework.boot.test.SpringApplicationConfiguration; -import org.springframework.boot.test.WebIntegrationTest; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.cloud.netflix.feign.EnableFeignClients; import org.springframework.cloud.netflix.feign.FeignClient; @@ -76,12 +75,12 @@ import junitparams.Parameters; import static junitparams.JUnitParamsRunner.$; import static org.assertj.core.api.Assertions.fail; -import static org.assertj.core.api.BDDAssertions.then; import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; @RunWith(JUnitParamsRunner.class) -@SpringApplicationConfiguration(classes = { WebClientTests.TestConfiguration.class }) -@WebIntegrationTest(value = { "spring.application.name=fooservice", "feign.hystrix.enabled=false" }, randomPort = true) +@SpringBootTest(classes = { WebClientTests.TestConfiguration.class }, + properties = { "spring.application.name=fooservice", "feign.hystrix.enabled=false" }, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @DirtiesContext public class WebClientTests { diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/multiple/MultipleHopsIntegrationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/multiple/MultipleHopsIntegrationTests.java index 8109024f5..126ff1a99 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/multiple/MultipleHopsIntegrationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/multiple/MultipleHopsIntegrationTests.java @@ -18,7 +18,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.result.MockMvcResultMatchers; import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder; @@ -28,7 +28,7 @@ import static java.util.stream.Collectors.toList; import static org.assertj.core.api.BDDAssertions.then; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -@RunWith(SpringJUnit4ClassRunner.class) +@RunWith(SpringRunner.class) @ContextConfiguration(classes = MultipleHopsIntegrationTests.Config.class) @TestPropertySource(properties = "spring.application.name=multiplehopsintegrationtests") public class MultipleHopsIntegrationTests extends AbstractMvcIntegrationTest { diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/view/Issue469Tests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/view/Issue469Tests.java index 66e9ea6d5..7637d96cb 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/view/Issue469Tests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/view/Issue469Tests.java @@ -20,22 +20,19 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.SpringApplicationConfiguration; -import org.springframework.boot.test.WebIntegrationTest; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.util.ExceptionUtils; import org.springframework.core.env.Environment; -import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit4.SpringRunner; import org.springframework.web.client.RestTemplate; import static org.assertj.core.api.BDDAssertions.then; -@RunWith(SpringJUnit4ClassRunner.class) -@SpringApplicationConfiguration(classes = Issue469.class) -@TestPropertySource(properties = {"spring.mvc.view.prefix=/WEB-INF/jsp/", - "spring.mvc.view.suffix=.jsp"}) -@WebIntegrationTest({ "server.port=0" }) +@RunWith(SpringRunner.class) +@SpringBootTest(classes = Issue469.class, + properties = {"spring.mvc.view.prefix=/WEB-INF/jsp/", "spring.mvc.view.suffix=.jsp"}, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class Issue469Tests { @Autowired Tracer tracer; diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulIntegrationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulIntegrationTests.java index df168fe7d..ddf251627 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulIntegrationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulIntegrationTests.java @@ -4,10 +4,6 @@ import java.io.IOException; import java.lang.invoke.MethodHandles; import java.util.HashMap; -import com.netflix.loadbalancer.Server; -import com.netflix.loadbalancer.ServerList; -import com.netflix.zuul.context.RequestContext; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.junit.After; @@ -17,8 +13,7 @@ import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.IntegrationTest; -import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.cloud.netflix.ribbon.RibbonClient; import org.springframework.cloud.netflix.ribbon.StaticServerList; @@ -44,29 +39,35 @@ import org.springframework.http.ResponseEntity; import org.springframework.http.client.ClientHttpResponse; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.context.junit4.SpringRunner; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.DefaultResponseErrorHandler; import org.springframework.web.client.RestTemplate; +import com.netflix.loadbalancer.Server; +import com.netflix.loadbalancer.ServerList; +import com.netflix.zuul.context.RequestContext; + import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; -@RunWith(SpringJUnit4ClassRunner.class) -@SpringApplicationConfiguration(classes = SampleZuulProxyApplication.class) -@WebAppConfiguration -@IntegrationTest({ "server.port: 0", "zuul.routes.simple: /simple/**" }) +@RunWith(SpringRunner.class) +@SpringBootTest(classes = SampleZuulProxyApplication.class, properties = { + "zuul.routes.simple: /simple/**" }, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @DirtiesContext public class TraceZuulIntegrationTests { - private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); + private static final Log log = LogFactory + .getLog(MethodHandles.lookup().lookupClass()); @Value("${local.server.port}") private int port; - @Autowired Tracer tracer; - @Autowired ArrayListSpanAccumulator spanAccumulator; - @Autowired RestTemplate restTemplate; + @Autowired + Tracer tracer; + @Autowired + ArrayListSpanAccumulator spanAccumulator; + @Autowired + RestTemplate restTemplate; @Before @After @@ -91,10 +92,10 @@ public class TraceZuulIntegrationTests { then(this.tracer.getCurrentSpan()).isNull(); then(new ListOfSpans(this.spanAccumulator.getSpans())) .everyParentIdHasItsCorrespondingSpan() - .clientSideSpanWithNameHasTags("http:/simple/foo", TestTag.tag() - .tag("http.method", "GET") - .tag("http.status_code", "200") - .tag("http.path", "/simple/foo")); + .clientSideSpanWithNameHasTags("http:/simple/foo", + TestTag.tag().tag("http.method", "GET") + .tag("http.status_code", "200") + .tag("http.path", "/simple/foo")); then(ExceptionUtils.getLastException()).isNull(); } @@ -103,8 +104,8 @@ public class TraceZuulIntegrationTests { Span span = this.tracer.createSpan("new_span"); log.info("Started span " + span); ResponseEntity result = this.restTemplate.exchange( - "http://localhost:" + this.port + "/simple/nonExistentUrl", HttpMethod.GET, - new HttpEntity<>((Void) null), String.class); + "http://localhost:" + this.port + "/simple/nonExistentUrl", + HttpMethod.GET, new HttpEntity<>((Void) null), String.class); this.tracer.close(span); @@ -112,10 +113,10 @@ public class TraceZuulIntegrationTests { then(this.tracer.getCurrentSpan()).isNull(); then(new ListOfSpans(this.spanAccumulator.getSpans())) .everyParentIdHasItsCorrespondingSpan() - .clientSideSpanWithNameHasTags("http:/simple/nonExistentUrl", TestTag.tag() - .tag("http.method", "GET") - .tag("http.status_code", "404") - .tag("http.path", "/simple/nonExistentUrl")); + .clientSideSpanWithNameHasTags("http:/simple/nonExistentUrl", + TestTag.tag().tag("http.method", "GET") + .tag("http.status_code", "404") + .tag("http.path", "/simple/nonExistentUrl")); then(ExceptionUtils.getLastException()).isNull(); } @@ -140,7 +141,6 @@ public class TraceZuulIntegrationTests { @RibbonClient(name = "simple", configuration = SimpleRibbonClientConfiguration.class) class SampleZuulProxyApplication { - @RequestMapping("/foo") public String home() { return "Hello world"; @@ -151,35 +151,41 @@ class SampleZuulProxyApplication { throw new RuntimeException(); } - @Bean RouteLocator routeLocator(DiscoveryClient discoveryClient, ZuulProperties zuulProperties) { + @Bean + RouteLocator routeLocator(DiscoveryClient discoveryClient, + ZuulProperties zuulProperties) { return new MyRouteLocator("/", discoveryClient, zuulProperties); } - @Bean SpanReporter testSpanReporter() { + @Bean + SpanReporter testSpanReporter() { return new ArrayListSpanAccumulator(); } - @Bean RestTemplate restTemplate() { + @Bean + RestTemplate restTemplate() { HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(); factory.setReadTimeout(5000); RestTemplate restTemplate = new RestTemplate(factory); restTemplate.setErrorHandler(new DefaultResponseErrorHandler() { - @Override public void handleError(ClientHttpResponse response) - throws IOException { + @Override + public void handleError(ClientHttpResponse response) throws IOException { } }); return restTemplate; } - @Bean Sampler alwaysSampler() { + @Bean + Sampler alwaysSampler() { return new AlwaysSampler(); } } class MyRouteLocator extends DiscoveryClientRouteLocator { - public MyRouteLocator(String servletPath, DiscoveryClient discovery, ZuulProperties properties) { + public MyRouteLocator(String servletPath, DiscoveryClient discovery, + ZuulProperties properties) { super(servletPath, discovery, properties); } } @@ -188,9 +194,11 @@ class MyRouteLocator extends DiscoveryClientRouteLocator { @Configuration class SimpleRibbonClientConfiguration { - @Value("${local.server.port}") private int port; + @Value("${local.server.port}") + private int port; - @Bean public ServerList ribbonServerList() { + @Bean + public ServerList ribbonServerList() { return new StaticServerList<>(new Server("localhost", this.port)); } } diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/src/test/java/sample/SampleFeignApplicationTests.java b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/src/test/java/sample/SampleFeignApplicationTests.java index 523356062..13a030608 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/src/test/java/sample/SampleFeignApplicationTests.java +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/src/test/java/sample/SampleFeignApplicationTests.java @@ -2,15 +2,13 @@ package sample; import org.junit.Test; import org.junit.runner.RunWith; -import org.springframework.boot.test.SpringApplicationConfiguration; -import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; -@RunWith(SpringJUnit4ClassRunner.class) -@SpringApplicationConfiguration(classes = SampleFeignApplication.class) -@WebAppConfiguration -@TestPropertySource(properties="sample.zipkin.enabled=false") +@RunWith(SpringRunner.class) +@SpringBootTest(classes = SampleFeignApplication.class, + properties="sample.zipkin.enabled=false", + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class SampleFeignApplicationTests { @Test diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/src/test/java/integration/MessagingApplicationTests.java b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/src/test/java/integration/MessagingApplicationTests.java index 7060429d0..82b99b2b3 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/src/test/java/integration/MessagingApplicationTests.java +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/src/test/java/integration/MessagingApplicationTests.java @@ -26,14 +26,12 @@ import org.junit.After; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.SpringApplicationConfiguration; -import org.springframework.boot.test.WebIntegrationTest; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.sleuth.zipkin.ZipkinSpanReporter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit4.SpringRunner; import integration.MessagingApplicationTests.IntegrationSpanCollectorConfig; import sample.SampleMessagingApplication; @@ -43,10 +41,10 @@ import zipkin.Span; import static org.assertj.core.api.BDDAssertions.then; -@RunWith(SpringJUnit4ClassRunner.class) -@SpringApplicationConfiguration(classes = { IntegrationSpanCollectorConfig.class, SampleMessagingApplication.class }) -@WebIntegrationTest -@TestPropertySource(properties="sample.zipkin.enabled=true") +@RunWith(SpringRunner.class) +@SpringBootTest(classes = { IntegrationSpanCollectorConfig.class, SampleMessagingApplication.class }, + properties="sample.zipkin.enabled=true", + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @DirtiesContext public class MessagingApplicationTests extends AbstractIntegrationTest { diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/src/test/java/sample/SampleMessagingApplicationTests.java b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/src/test/java/sample/SampleMessagingApplicationTests.java index 013cec9d2..76711dd6c 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/src/test/java/sample/SampleMessagingApplicationTests.java +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/src/test/java/sample/SampleMessagingApplicationTests.java @@ -2,15 +2,13 @@ package sample; import org.junit.Test; import org.junit.runner.RunWith; -import org.springframework.boot.test.SpringApplicationConfiguration; -import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; -@RunWith(SpringJUnit4ClassRunner.class) -@SpringApplicationConfiguration(classes = SampleMessagingApplication.class) -@WebAppConfiguration -@TestPropertySource(properties="sample.zipkin.enabled=false") +@RunWith(SpringRunner.class) +@SpringBootTest(classes = SampleMessagingApplication.class, + properties="sample.zipkin.enabled=false", + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class SampleMessagingApplicationTests { @Test diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/src/test/java/sample/SampleRibbonApplicationTests.java b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/src/test/java/sample/SampleRibbonApplicationTests.java index d18c8fcc0..5890aaf4a 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/src/test/java/sample/SampleRibbonApplicationTests.java +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/src/test/java/sample/SampleRibbonApplicationTests.java @@ -2,15 +2,13 @@ package sample; import org.junit.Test; import org.junit.runner.RunWith; -import org.springframework.boot.test.SpringApplicationConfiguration; -import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; -@RunWith(SpringJUnit4ClassRunner.class) -@SpringApplicationConfiguration(classes = SampleRibbonApplication.class) -@WebAppConfiguration -@TestPropertySource(properties="sample.zipkin.enabled=false") +@RunWith(SpringRunner.class) +@SpringBootTest(classes = SampleRibbonApplication.class, + properties="sample.zipkin.enabled=false", + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class SampleRibbonApplicationTests { @Test diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/src/test/java/sample/SampleSleuthApplicationTests.java b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/src/test/java/sample/SampleSleuthApplicationTests.java index 0b9c935b1..5161ea6cb 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/src/test/java/sample/SampleSleuthApplicationTests.java +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/src/test/java/sample/SampleSleuthApplicationTests.java @@ -2,13 +2,12 @@ package sample; import org.junit.Test; import org.junit.runner.RunWith; -import org.springframework.boot.test.SpringApplicationConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; -@RunWith(SpringJUnit4ClassRunner.class) -@SpringApplicationConfiguration(classes = SampleSleuthApplication.class) -@WebAppConfiguration +@RunWith(SpringRunner.class) +@SpringBootTest(classes = SampleSleuthApplication.class, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class SampleSleuthApplicationTests { @Test diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/src/test/java/sample/SampleWebsocketApplicationTests.java b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/src/test/java/sample/SampleWebsocketApplicationTests.java index 53d68bfe2..7a8ee94d3 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/src/test/java/sample/SampleWebsocketApplicationTests.java +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/src/test/java/sample/SampleWebsocketApplicationTests.java @@ -2,15 +2,13 @@ package sample; import org.junit.Test; import org.junit.runner.RunWith; -import org.springframework.boot.test.SpringApplicationConfiguration; -import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; -@RunWith(SpringJUnit4ClassRunner.class) -@SpringApplicationConfiguration(classes = SampleWebsocketApplication.class) -@WebAppConfiguration -@TestPropertySource(properties="sample.zipkin.enabled=false") +@RunWith(SpringRunner.class) +@SpringBootTest(classes = SampleWebsocketApplication.class, + properties="sample.zipkin.enabled=false", + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class SampleWebsocketApplicationTests { @Test diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/src/test/java/example/ZipkinServerApplicationTests.java b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/src/test/java/example/ZipkinServerApplicationTests.java index c87052e13..d063d9fac 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/src/test/java/example/ZipkinServerApplicationTests.java +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/src/test/java/example/ZipkinServerApplicationTests.java @@ -3,17 +3,18 @@ package example; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.IntegrationTest; -import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit4.SpringRunner; + import zipkin.storage.StorageComponent; import static org.junit.Assert.assertEquals; -@RunWith(SpringJUnit4ClassRunner.class) -@SpringApplicationConfiguration(classes = ZipkinStreamServerApplication.class) -@IntegrationTest({ "server.port=0", "spring.datasource.initialize=true" }) +@RunWith(SpringRunner.class) +@SpringBootTest(classes = ZipkinStreamServerApplication.class, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + properties = { "spring.datasource.initialize=true" }) @ActiveProfiles("test") public class ZipkinServerApplicationTests { diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/src/test/java/integration/ZipkinStreamTests.java b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/src/test/java/integration/ZipkinStreamTests.java index ad4c4d4f5..5c363fa98 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/src/test/java/integration/ZipkinStreamTests.java +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/src/test/java/integration/ZipkinStreamTests.java @@ -18,15 +18,13 @@ package integration; import java.util.Collections; import java.util.Random; -import example.ZipkinStreamServerApplication; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.test.SpringApplicationConfiguration; -import org.springframework.boot.test.WebIntegrationTest; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.stream.Host; import org.springframework.cloud.sleuth.stream.SleuthSink; @@ -36,13 +34,15 @@ import org.springframework.integration.support.MessageBuilder; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit4.SpringRunner; + +import example.ZipkinStreamServerApplication; import tools.AbstractIntegrationTest; -@RunWith(SpringJUnit4ClassRunner.class) -@SpringApplicationConfiguration(classes = { TestSupportBinderAutoConfiguration.class, - ZipkinStreamServerApplication.class }) -@WebIntegrationTest({ "server.port=0", "management.health.rabbit.enabled=false" }) +@RunWith(SpringRunner.class) +@SpringBootTest(classes = { TestSupportBinderAutoConfiguration.class, ZipkinStreamServerApplication.class }, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + properties = { "management.health.rabbit.enabled=false" }) @ActiveProfiles("test") public class ZipkinStreamTests extends AbstractIntegrationTest { diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/src/test/java/integration/ZipkinTests.java b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/src/test/java/integration/ZipkinTests.java index e82565de5..fc2a2fb05 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/src/test/java/integration/ZipkinTests.java +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/src/test/java/integration/ZipkinTests.java @@ -25,14 +25,12 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.test.SpringApplicationConfiguration; -import org.springframework.boot.test.WebIntegrationTest; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.sleuth.zipkin.ZipkinProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; -import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit4.SpringRunner; import integration.ZipkinTests.WaitUntilZipkinIsUpConfig; import sample.SampleZipkinApplication; @@ -40,11 +38,11 @@ import tools.AbstractIntegrationTest; import zipkin.junit.ZipkinRule; import zipkin.server.EnableZipkinServer; -@RunWith(SpringJUnit4ClassRunner.class) -@SpringApplicationConfiguration(classes = { WaitUntilZipkinIsUpConfig.class, - SampleZipkinApplication.class }) -@WebIntegrationTest -@TestPropertySource(properties = {"sample.zipkin.enabled=true"}) +@RunWith(SpringRunner.class) +@SpringBootTest(classes = { WaitUntilZipkinIsUpConfig.class, + SampleZipkinApplication.class }, + properties = {"sample.zipkin.enabled=true"}, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class ZipkinTests extends AbstractIntegrationTest { @ClassRule public static final ZipkinRule zipkin = new ZipkinRule(); diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/src/test/java/sample/SampleSleuthApplicationTests.java b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/src/test/java/sample/SampleSleuthApplicationTests.java index e7efa95d8..41d7a755e 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/src/test/java/sample/SampleSleuthApplicationTests.java +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/src/test/java/sample/SampleSleuthApplicationTests.java @@ -2,15 +2,13 @@ package sample; import org.junit.Test; import org.junit.runner.RunWith; -import org.springframework.boot.test.SpringApplicationConfiguration; -import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; -@RunWith(SpringJUnit4ClassRunner.class) -@SpringApplicationConfiguration(classes = SampleZipkinApplication.class) -@WebAppConfiguration -@TestPropertySource(properties="sample.zipkin.enabled=false") +@RunWith(SpringRunner.class) +@SpringBootTest(classes = SampleZipkinApplication.class, + properties="sample.zipkin.enabled=false", + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class SampleSleuthApplicationTests { @Test diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/src/test/java/sample/SampleSleuthApplicationTests.java b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/src/test/java/sample/SampleSleuthApplicationTests.java index 0b9c935b1..4230ef3be 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/src/test/java/sample/SampleSleuthApplicationTests.java +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/src/test/java/sample/SampleSleuthApplicationTests.java @@ -2,13 +2,11 @@ package sample; import org.junit.Test; import org.junit.runner.RunWith; -import org.springframework.boot.test.SpringApplicationConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; -@RunWith(SpringJUnit4ClassRunner.class) -@SpringApplicationConfiguration(classes = SampleSleuthApplication.class) -@WebAppConfiguration +@RunWith(SpringRunner.class) +@SpringBootTest(classes = SampleSleuthApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class SampleSleuthApplicationTests { @Test diff --git a/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/SleuthStreamAutoConfigurationTest.java b/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/SleuthStreamAutoConfigurationTest.java index 51cfab3bf..317ee998e 100644 --- a/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/SleuthStreamAutoConfigurationTest.java +++ b/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/SleuthStreamAutoConfigurationTest.java @@ -9,7 +9,7 @@ import org.junit.After; import org.junit.BeforeClass; import org.junit.Test; import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; -import org.springframework.boot.test.EnvironmentTestUtils; +import org.springframework.boot.test.util.EnvironmentTestUtils; import org.springframework.cloud.commons.util.UtilAutoConfiguration; import org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration; import org.springframework.cloud.sleuth.log.NoOpSpanLogger; diff --git a/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/StreamEnvironmentPostProcessorTests.java b/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/StreamEnvironmentPostProcessorTests.java index c90c78591..7ef40cc16 100644 --- a/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/StreamEnvironmentPostProcessorTests.java +++ b/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/StreamEnvironmentPostProcessorTests.java @@ -22,7 +22,7 @@ import java.util.stream.Collectors; import org.junit.Test; import org.springframework.boot.SpringApplication; -import org.springframework.boot.test.EnvironmentTestUtils; +import org.springframework.boot.test.util.EnvironmentTestUtils; import org.springframework.cloud.sleuth.Span; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.StandardEnvironment; diff --git a/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/StreamSpanListenerTests.java b/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/StreamSpanListenerTests.java index f26bb6399..5ea9148b4 100644 --- a/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/StreamSpanListenerTests.java +++ b/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/StreamSpanListenerTests.java @@ -54,14 +54,14 @@ import org.springframework.integration.annotation.MessageEndpoint; import org.springframework.integration.annotation.ServiceActivator; import org.springframework.messaging.Message; import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit4.SpringRunner; /** * @author Dave Syer * */ @ContextConfiguration(classes = TestConfiguration.class) -@RunWith(SpringJUnit4ClassRunner.class) +@RunWith(SpringRunner.class) public class StreamSpanListenerTests { @Autowired diff --git a/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/TraceIgnoringChannelInterceptorTests.java b/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/TraceIgnoringChannelInterceptorTests.java index 01485ff4d..e5de17ca7 100644 --- a/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/TraceIgnoringChannelInterceptorTests.java +++ b/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/TraceIgnoringChannelInterceptorTests.java @@ -37,14 +37,14 @@ import org.springframework.integration.core.MessagingTemplate; import org.springframework.integration.support.MessageBuilder; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit4.SpringRunner; import static org.assertj.core.api.BDDAssertions.then; /** * @author Dave Syer */ -@RunWith(SpringJUnit4ClassRunner.class) +@RunWith(SpringRunner.class) @ContextConfiguration(classes = TraceIgnoringChannelInterceptorTests.App.class) @DirtiesContext public class TraceIgnoringChannelInterceptorTests { diff --git a/spring-cloud-sleuth-zipkin-stream/src/test/java/org/springframework/cloud/sleuth/zipkin/stream/ZipkinServerApplicationTests.java b/spring-cloud-sleuth-zipkin-stream/src/test/java/org/springframework/cloud/sleuth/zipkin/stream/ZipkinServerApplicationTests.java index 90b2f6a99..7517df953 100644 --- a/spring-cloud-sleuth-zipkin-stream/src/test/java/org/springframework/cloud/sleuth/zipkin/stream/ZipkinServerApplicationTests.java +++ b/spring-cloud-sleuth-zipkin-stream/src/test/java/org/springframework/cloud/sleuth/zipkin/stream/ZipkinServerApplicationTests.java @@ -1,23 +1,23 @@ package org.springframework.cloud.sleuth.zipkin.stream; -import static org.junit.Assert.assertEquals; - import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; -import org.springframework.boot.test.IntegrationTest; -import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.sleuth.zipkin.stream.ZipkinServerApplicationTests.ZipkinStreamServerApplication; import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit4.SpringRunner; import zipkin.storage.StorageComponent; -@RunWith(SpringJUnit4ClassRunner.class) -@SpringApplicationConfiguration(classes = ZipkinStreamServerApplication.class) -@IntegrationTest({ "server.port=0", "spring.datasource.initialize=true" }) +import static org.junit.Assert.assertEquals; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = ZipkinStreamServerApplication.class, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + properties = { "spring.datasource.initialize=true" }) @ActiveProfiles("test") public class ZipkinServerApplicationTests { diff --git a/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListenerTests.java b/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListenerTests.java index 7cd41d5cf..a51e97e3c 100644 --- a/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListenerTests.java +++ b/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListenerTests.java @@ -24,7 +24,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.sleuth.Sampler; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.SpanReporter; @@ -36,7 +36,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.mock.env.MockEnvironment; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit4.SpringRunner; import zipkin.Constants; @@ -47,8 +47,8 @@ import static org.junit.Assert.assertEquals; * @author Dave Syer * */ -@SpringApplicationConfiguration(classes = TestConfiguration.class) -@RunWith(SpringJUnit4ClassRunner.class) +@SpringBootTest(classes = TestConfiguration.class) +@RunWith(SpringRunner.class) public class ZipkinSpanListenerTests { @Autowired Tracer tracer; From a84e624bc40ce422ec4c1d6d0accf0c25cf17c30 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Mon, 23 Jan 2017 13:07:11 +0100 Subject: [PATCH 199/235] Fixed broken tests --- .../src/test/java/integration/MessagingApplicationTests.java | 2 +- .../src/test/java/integration/ZipkinTests.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/src/test/java/integration/MessagingApplicationTests.java b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/src/test/java/integration/MessagingApplicationTests.java index 82b99b2b3..9a7d5f530 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/src/test/java/integration/MessagingApplicationTests.java +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/src/test/java/integration/MessagingApplicationTests.java @@ -44,7 +44,7 @@ import static org.assertj.core.api.BDDAssertions.then; @RunWith(SpringRunner.class) @SpringBootTest(classes = { IntegrationSpanCollectorConfig.class, SampleMessagingApplication.class }, properties="sample.zipkin.enabled=true", - webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) + webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) @DirtiesContext public class MessagingApplicationTests extends AbstractIntegrationTest { diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/src/test/java/integration/ZipkinTests.java b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/src/test/java/integration/ZipkinTests.java index fc2a2fb05..9becc626c 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/src/test/java/integration/ZipkinTests.java +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/src/test/java/integration/ZipkinTests.java @@ -42,7 +42,7 @@ import zipkin.server.EnableZipkinServer; @SpringBootTest(classes = { WaitUntilZipkinIsUpConfig.class, SampleZipkinApplication.class }, properties = {"sample.zipkin.enabled=true"}, - webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) + webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) public class ZipkinTests extends AbstractIntegrationTest { @ClassRule public static final ZipkinRule zipkin = new ZipkinRule(); From 7151a9cdb3844e2de0024e661f14902b07ea1d04 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Thu, 26 Jan 2017 18:47:43 +0100 Subject: [PATCH 200/235] Giving an option to reuse a custom feign client without this change you couldn't register your own client (for example to set your ssl config) for Sleuth to reuse it with this change we're reusing a registered feign client bean or creating a default one if necessary fixes #502 --- .../client/feign/FeignBeanPostProcessor.java | 48 ------- .../web/client/feign/TraceFeignAspect.java | 53 ++++++++ .../web/client/feign/TraceFeignClient.java | 13 +- .../TraceFeignClientAutoConfiguration.java | 12 +- .../feign/TraceLoadBalancerFeignClient.java | 26 ---- .../feign/issues/issue502/Issue502Tests.java | 121 ++++++++++++++++++ 6 files changed, 191 insertions(+), 82 deletions(-) delete mode 100644 spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignBeanPostProcessor.java create mode 100644 spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignAspect.java create mode 100644 spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue502/Issue502Tests.java diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignBeanPostProcessor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignBeanPostProcessor.java deleted file mode 100644 index 36b09f289..000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignBeanPostProcessor.java +++ /dev/null @@ -1,48 +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 org.springframework.beans.BeansException; -import org.springframework.beans.factory.config.BeanPostProcessor; - -/** - * Post processor that wraps Feign related classes in their tracing representations. - * - * @author Marcin Grzejszczak - * - * @since 1.0.0 - */ -final class FeignBeanPostProcessor implements BeanPostProcessor { - - private final TraceFeignObjectWrapper traceFeignObjectWrapper; - - FeignBeanPostProcessor(TraceFeignObjectWrapper traceFeignObjectWrapper) { - this.traceFeignObjectWrapper = traceFeignObjectWrapper; - } - - @Override - public Object postProcessBeforeInitialization(Object bean, String beanName) - throws BeansException { - return this.traceFeignObjectWrapper.wrap(bean); - } - - @Override - public Object postProcessAfterInitialization(Object bean, String beanName) - throws BeansException { - return bean; - } -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignAspect.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignAspect.java new file mode 100644 index 000000000..2a8dd6395 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignAspect.java @@ -0,0 +1,53 @@ +/* + * Copyright 2013-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.sleuth.instrument.web.client.feign; + +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.springframework.beans.factory.BeanFactory; + +import feign.Client; +import feign.Request; + +/** + * Aspect for Feign clients so that you can autowire your custom components + * + * @author Marcin Grzejszczak + * @since 1.1.2 + */ +@Aspect +class TraceFeignAspect { + + private final BeanFactory beanFactory; + + TraceFeignAspect(BeanFactory beanFactory) { + this.beanFactory = beanFactory; + } + + @Around("execution (* feign.Client.*(..))") + public Object feignClientWasCalled(final ProceedingJoinPoint pjp) throws Throwable { + Object[] args = pjp.getArgs(); + Request request = (Request) args[0]; + Request.Options options = (Request.Options) args[1]; + Object bean = pjp.getTarget(); + if (!(bean instanceof TraceFeignClient)) { + return new TraceFeignClient(this.beanFactory, (Client) bean).execute(request, options); + } + return pjp.proceed(); + } +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClient.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClient.java index 33acce88b..64fc0d182 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClient.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClient.java @@ -24,14 +24,15 @@ import java.util.concurrent.atomic.AtomicReference; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; 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 @@ -53,7 +54,15 @@ class TraceFeignClient implements Client { TraceFeignClient(BeanFactory beanFactory) { this.beanFactory = beanFactory; - this.delegate = new Client.Default(null, null); + this.delegate = client(beanFactory); + } + + private Client client(BeanFactory beanFactory) { + try { + return beanFactory.getBean(Client.class); + } catch (NoSuchBeanDefinitionException e) { + return new Client.Default(null, null); + } } TraceFeignClient(BeanFactory beanFactory, Client delegate) { diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientAutoConfiguration.java index 664f57bc8..b42fbf584 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientAutoConfiguration.java @@ -60,7 +60,7 @@ public class TraceFeignClientAutoConfiguration { @Bean @ConditionalOnMissingBean @Scope("prototype") - @ConditionalOnProperty(name = "feign.hystrix.enabled", havingValue = "false", matchIfMissing = false) + @ConditionalOnProperty(name = "feign.hystrix.enabled", havingValue = "false") Feign.Builder feignBuilder(BeanFactory beanFactory) { return SleuthFeignBuilder.builder(beanFactory); } @@ -69,11 +69,6 @@ public class TraceFeignClientAutoConfiguration { @ConditionalOnProperty(name = "spring.sleuth.feign.processor.enabled", matchIfMissing = true) protected static class FeignBeanPostProcessorConfiguration { - @Bean - FeignBeanPostProcessor feignBeanPostProcessor(TraceFeignObjectWrapper traceFeignObjectWrapper) { - return new FeignBeanPostProcessor(traceFeignObjectWrapper); - } - @Bean FeignContextBeanPostProcessor feignContextBeanPostProcessor(BeanFactory beanFactory) { return new FeignContextBeanPostProcessor(beanFactory); @@ -84,4 +79,9 @@ public class TraceFeignClientAutoConfiguration { TraceFeignObjectWrapper traceFeignObjectWrapper(BeanFactory beanFactory) { return new TraceFeignObjectWrapper(beanFactory); } + + @Bean + TraceFeignAspect traceFeignAspect(BeanFactory beanFactory) { + return new TraceFeignAspect(beanFactory); + } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceLoadBalancerFeignClient.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceLoadBalancerFeignClient.java index fd4655e72..4efc57638 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceLoadBalancerFeignClient.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceLoadBalancerFeignClient.java @@ -1,19 +1,11 @@ package org.springframework.cloud.sleuth.instrument.web.client.feign; -import java.io.IOException; -import java.lang.invoke.MethodHandles; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.BeanFactory; import org.springframework.cloud.netflix.feign.ribbon.CachingSpringLoadBalancerFactory; import org.springframework.cloud.netflix.feign.ribbon.LoadBalancerFeignClient; import org.springframework.cloud.netflix.ribbon.SpringClientFactory; -import org.springframework.cloud.sleuth.Tracer; import feign.Client; -import feign.Request; -import feign.Response; /** * We need to wrap the {@link LoadBalancerFeignClient} into a trace representation @@ -24,31 +16,13 @@ import feign.Response; */ class TraceLoadBalancerFeignClient extends LoadBalancerFeignClient { - private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); - - private final BeanFactory beanFactory; - private Tracer tracer; - TraceLoadBalancerFeignClient(Client delegate, CachingSpringLoadBalancerFactory lbClientFactory, SpringClientFactory clientFactory, BeanFactory beanFactory) { super(wrap(delegate, beanFactory), lbClientFactory, clientFactory); - this.beanFactory = beanFactory; - } - - @Override public Response execute(Request request, Request.Options options) - throws IOException { - return super.execute(request, options); } private static Client wrap(Client delegate, BeanFactory beanFactory) { return (Client) new TraceFeignObjectWrapper(beanFactory).wrap(delegate); } - - private Tracer tracer() { - if (this.tracer == null) { - this.tracer = this.beanFactory.getBean(Tracer.class); - } - return this.tracer; - } } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue502/Issue502Tests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue502/Issue502Tests.java new file mode 100644 index 000000000..d8d6bb3e6 --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue502/Issue502Tests.java @@ -0,0 +1,121 @@ +/* + * 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.issues.issue502; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.HashMap; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.netflix.feign.EnableFeignClients; +import org.springframework.cloud.netflix.feign.FeignClient; +import org.springframework.cloud.sleuth.sampler.AlwaysSampler; +import org.springframework.cloud.sleuth.trace.TestSpanContextHolder; +import org.springframework.cloud.sleuth.util.ExceptionUtils; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +import feign.Client; +import feign.Request; +import feign.Response; + +import static org.assertj.core.api.BDDAssertions.then; + +/** + * @author Marcin Grzejszczak + */ +@RunWith(SpringRunner.class) +@SpringBootTest(classes = Application.class, + webEnvironment = SpringBootTest.WebEnvironment.NONE, + properties = {"feign.hystrix.enabled=false"}) +public class Issue502Tests { + + @Autowired MyClient myClient; + @Autowired MyNameRemote myNameRemote; + + @Before + public void open() { + TestSpanContextHolder.removeCurrentSpan(); + ExceptionUtils.setFail(true); + } + + @After + public void close() { + TestSpanContextHolder.removeCurrentSpan(); + } + + @Test + public void should_reuse_custom_feign_client() { + String response = this.myNameRemote.get(); + + then(this.myClient.wasCalled()).isTrue(); + then(response).isEqualTo("foo"); + then(ExceptionUtils.getLastException()).isNull(); + } +} + +@Configuration +@EnableAutoConfiguration +@EnableFeignClients +class Application { + + @Bean + public Client client() { + return new MyClient(); + } + + @Bean + public AlwaysSampler defaultSampler() { + return new AlwaysSampler(); + } + +} + +@FeignClient(name="foo", + url="http://non.existing.url") +interface MyNameRemote { + + @RequestMapping(value = "/", method = RequestMethod.GET) + String get(); +} + +class MyClient implements Client { + + boolean wasCalled; + + @Override public Response execute(Request request, Request.Options options) + throws IOException { + this.wasCalled = true; + return Response.builder() + .body("foo", Charset.forName("UTF-8")) + .headers(new HashMap<>()) + .status(200).build(); + } + + boolean wasCalled() { + return this.wasCalled; + } +} From 92bcae68f358cb1f0e175be3f7a6ab51526ee3bc Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Tue, 31 Jan 2017 15:57:22 +0100 Subject: [PATCH 201/235] Fixing the moment when SS is set (#505) without this change there's a problem with the time when the SS is set on a span. Currently it's done in TraceFilter's finally block. The problem is that this code is executed after the response has been sent back to the client. Thus CR sometimes was set faster than SS (it doesn't make any sense from the logical point of view). with this change we're introducing wrappers over the HttpServletResponse where we annotate the span with SS just after the response gets sent to the recipient. fixes #492 #431 --- .../sleuth/instrument/web/TraceFilter.java | 253 +++++++++++++++++- .../web/client/TraceAsyncRestTemplate.java | 11 + .../client/TraceRestTemplateInterceptor.java | 2 +- .../web/client/feign/TraceFeignClient.java | 1 + .../sleuth/assertions/ListOfSpansAssert.java | 140 +++++++++- .../exception/WebClientExceptionTests.java | 8 + .../web/client/feign/FeignRetriesTests.java | 1 - .../client/feign/TraceFeignClientTests.java | 1 - .../client/integration/WebClientTests.java | 20 +- 9 files changed, 421 insertions(+), 16 deletions(-) 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 86156f84a..488dd39af 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 @@ -16,17 +16,22 @@ package org.springframework.cloud.sleuth.instrument.web; import java.io.IOException; +import java.io.PrintWriter; import java.lang.invoke.MethodHandles; import java.util.ArrayList; import java.util.Collections; import java.util.Enumeration; +import java.util.Locale; import java.util.regex.Pattern; import javax.servlet.FilterChain; import javax.servlet.ServletException; +import javax.servlet.ServletOutputStream; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; +import javax.servlet.WriteListener; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpServletResponseWrapper; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -134,14 +139,15 @@ public class TraceFilter extends GenericFilterBean { } // in case of a response with exception status a exception controller will close the span if (!httpStatusSuccessful(response) && isSpanContinued(request)) { - processErrorRequest(filterChain, request, response, spanFromRequest); + Span parentSpan = spanFromRequest != null ? spanFromRequest.getSavedSpan() : null; + processErrorRequest(filterChain, request, new TraceHttpServletResponse(response, parentSpan), spanFromRequest); return; } String name = HTTP_COMPONENT + ":" + uri; Throwable exception = null; try { spanFromRequest = createSpan(request, skip, spanFromRequest, name); - filterChain.doFilter(request, response); + filterChain.doFilter(request, new TraceHttpServletResponse(response, spanFromRequest)); } catch (Throwable e) { exception = e; this.tracer.addTag(Span.SPAN_ERROR_TAG_NAME, ExceptionUtils.getExceptionMessage(e)); @@ -163,7 +169,7 @@ public class TraceFilter extends GenericFilterBean { HttpServletResponse response, Span spanFromRequest) throws IOException, ServletException { if (log.isDebugEnabled()) { - log.debug("The span [" + spanFromRequest + "] was already detached once and we're processing an error"); + log.debug("The span " + spanFromRequest + " was already detached once and we're processing an error"); } try { filterChain.doFilter(request, response); @@ -243,10 +249,7 @@ public class TraceFilter extends GenericFilterBean { log.debug("Trying to send the parent span " + parent + " to Zipkin"); } parent.stop(); - parent.logEvent(Span.SERVER_SEND); this.spanReporter.report(parent); - } else { - parent.logEvent(Span.SERVER_SEND); } } @@ -370,3 +373,241 @@ public class TraceFilter extends GenericFilterBean { } } } + +/** + * We want to set SS as fast as possible after the response was sent back. The response + * can be sent back by calling either of these methods. + */ +class TraceHttpServletResponse extends HttpServletResponseWrapper { + + private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); + + private final Span span; + + TraceHttpServletResponse(HttpServletResponse response, Span span) { + super(response); + this.span = span; + } + + @Override public void flushBuffer() throws IOException { + if (log.isTraceEnabled()) { + log.trace("Will annotate SS once the response is flushed"); + } + try { + super.flushBuffer(); + } finally { + SsLogSetter.annotateWithServerSendIfLogIsNotAlreadyPresent(this.span); + } + } + + @Override public ServletOutputStream getOutputStream() throws IOException { + return new TraceServletOutputStream(super.getOutputStream(), this.span); + } + + @Override public PrintWriter getWriter() throws IOException { + return new TracePrintWriter(super.getWriter(), this.span); + } +} + +class TraceServletOutputStream extends ServletOutputStream { + + private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); + + private final ServletOutputStream delegate; + private final Span span; + + TraceServletOutputStream(ServletOutputStream delegate, Span span) { + this.delegate = delegate; + this.span = span; + } + + @Override public boolean isReady() { + return this.delegate.isReady(); + } + + @Override public void setWriteListener(WriteListener listener) { + this.delegate.setWriteListener(listener); + } + + @Override public void write(int b) throws IOException { + if (log.isTraceEnabled()) { + log.trace("Will annotate SS once the response is flushed"); + } + try { + this.delegate.write(b); + } finally { + SsLogSetter.annotateWithServerSendIfLogIsNotAlreadyPresent(this.span); + } + } +} + +class TracePrintWriter extends PrintWriter { + + private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); + + private final PrintWriter delegate; + private final Span span; + + TracePrintWriter(PrintWriter delegate, Span span) { + super(delegate); + this.delegate = delegate; + this.span = span; + } + + @Override public void flush() { + if (log.isTraceEnabled()) { + log.trace("Will annotate SS once the response is flushed"); + } + try { + this.delegate.flush(); + } finally { + SsLogSetter.annotateWithServerSendIfLogIsNotAlreadyPresent(this.span); + } + } + + @Override public void close() { + this.delegate.close(); + } + + @Override public boolean checkError() { + return this.delegate.checkError(); + } + + @Override public void write(int c) { + this.delegate.write(c); + } + + @Override public void write(char[] buf, int off, int len) { + this.delegate.write(buf, off, len); + } + + @Override public void write(char[] buf) { + this.delegate.write(buf); + } + + @Override public void write(String s, int off, int len) { + this.delegate.write(s, off, len); + } + + @Override public void write(String s) { + this.delegate.write(s); + } + + @Override public void print(boolean b) { + this.delegate.print(b); + } + + @Override public void print(char c) { + this.delegate.print(c); + } + + @Override public void print(int i) { + this.delegate.print(i); + } + + @Override public void print(long l) { + this.delegate.print(l); + } + + @Override public void print(float f) { + this.delegate.print(f); + } + + @Override public void print(double d) { + this.delegate.print(d); + } + + @Override public void print(char[] s) { + this.delegate.print(s); + } + + @Override public void print(String s) { + this.delegate.print(s); + } + + @Override public void print(Object obj) { + this.delegate.print(obj); + } + + @Override public void println() { + this.delegate.println(); + } + + @Override public void println(boolean x) { + this.delegate.println(x); + } + + @Override public void println(char x) { + this.delegate.println(x); + } + + @Override public void println(int x) { + this.delegate.println(x); + } + + @Override public void println(long x) { + this.delegate.println(x); + } + + @Override public void println(float x) { + this.delegate.println(x); + } + + @Override public void println(double x) { + this.delegate.println(x); + } + + @Override public void println(char[] x) { + this.delegate.println(x); + } + + @Override public void println(String x) { + this.delegate.println(x); + } + + @Override public void println(Object x) { + this.delegate.println(x); + } + + @Override public PrintWriter printf(String format, Object... args) { + return this.delegate.printf(format, args); + } + + @Override public PrintWriter printf(Locale l, String format, Object... args) { + return this.delegate.printf(l, format, args); + } + + @Override public PrintWriter format(String format, Object... args) { + return this.delegate.format(format, args); + } + + @Override public PrintWriter format(Locale l, String format, Object... args) { + return this.delegate.format(l, format, args); + } + + @Override public PrintWriter append(CharSequence csq) { + return this.delegate.append(csq); + } + + @Override public PrintWriter append(CharSequence csq, int start, int end) { + return this.delegate.append(csq, start, end); + } + + @Override public PrintWriter append(char c) { + return this.delegate.append(c); + } +} + +class SsLogSetter { + static void annotateWithServerSendIfLogIsNotAlreadyPresent(Span span) { + if (span == null) { + return; + } + for (org.springframework.cloud.sleuth.Log log1 : span.logs()) { + if (Span.SERVER_SEND.equals(log1.getEvent())) { + return; + } + } + span.logEvent(Span.SERVER_SEND); + } +} \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncRestTemplate.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncRestTemplate.java index 8543f0849..097b87f67 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncRestTemplate.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncRestTemplate.java @@ -16,8 +16,11 @@ package org.springframework.cloud.sleuth.instrument.web.client; +import java.lang.invoke.MethodHandles; import java.net.URI; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.util.ExceptionUtils; @@ -89,6 +92,8 @@ public class TraceAsyncRestTemplate extends AsyncRestTemplate { private static class TraceListenableFutureCallback implements ListenableFutureCallback { + private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); + private final Tracer tracer; private final Span parent; @@ -99,6 +104,9 @@ public class TraceAsyncRestTemplate extends AsyncRestTemplate { @Override public void onFailure(Throwable ex) { + if (log.isDebugEnabled()) { + log.debug("The callback failed - will close the span"); + } continueSpan(); this.tracer.addTag(Span.SPAN_ERROR_TAG_NAME, ExceptionUtils.getExceptionMessage(ex)); finish(); @@ -106,6 +114,9 @@ public class TraceAsyncRestTemplate extends AsyncRestTemplate { @Override public void onSuccess(T result) { + if (log.isDebugEnabled()) { + log.debug("The callback succeeded - will close the span"); + } continueSpan(); finish(); } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptor.java index ce71beb84..5c7cc9c3e 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptor.java @@ -62,7 +62,7 @@ public class TraceRestTemplateInterceptor extends AbstractTraceHttpRequestInterc log.debug("Exception occurred while trying to execute the request. Will close the span [" + currentSpan() + "]", e); } this.tracer.addTag(Span.SPAN_ERROR_TAG_NAME, ExceptionUtils.getExceptionMessage(e)); - this.tracer.close(currentSpan()); + finish(); throw e; } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClient.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClient.java index 64fc0d182..3de4e78a9 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClient.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClient.java @@ -90,6 +90,7 @@ class TraceFeignClient implements Client { logCr(); return response; } catch (RuntimeException | IOException e) { + logCr(); logError(e); throw e; } finally { diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/ListOfSpansAssert.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/ListOfSpansAssert.java index b1e237d75..a0b557403 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/ListOfSpansAssert.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/ListOfSpansAssert.java @@ -16,9 +16,7 @@ package org.springframework.cloud.sleuth.assertions; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; - +import java.lang.invoke.MethodHandles; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -29,6 +27,9 @@ import org.apache.commons.logging.LogFactory; import org.assertj.core.api.AbstractAssert; import org.springframework.cloud.sleuth.Span; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.toList; import static org.assertj.core.api.Assertions.assertThat; @@ -142,6 +143,28 @@ public class ListOfSpansAssert extends AbstractAssert(this.actual.spans))); @@ -155,4 +178,115 @@ public class ListOfSpansAssert extends AbstractAssert span.logs().forEach(log -> { + switch (log.getEvent()) { + case Span.CLIENT_SEND: + rpcLogKeeper.cs = log; + rpcLogKeeper.csSpanId = span.getSpanId(); + rpcLogKeeper.csTraceId = span.getTraceId(); + break; + case Span.SERVER_RECV: + rpcLogKeeper.sr = log; + rpcLogKeeper.srSpanId = span.getSpanId(); + rpcLogKeeper.srTraceId = span.getTraceId(); + break; + case Span.SERVER_SEND: + rpcLogKeeper.ss = log; + rpcLogKeeper.ssSpanId = span.getSpanId(); + rpcLogKeeper.ssTraceId = span.getTraceId(); + break; + case Span.CLIENT_RECV: + rpcLogKeeper.cr = log; + rpcLogKeeper.crSpanId = span.getSpanId(); + rpcLogKeeper.crTraceId = span.getTraceId(); + break; + default: + break; + } + })); + return rpcLogKeeper; + } +} + +class RpcLogKeeper { + + private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); + + org.springframework.cloud.sleuth.Log cs; + long csSpanId; + long csTraceId; + org.springframework.cloud.sleuth.Log sr; + long srSpanId; + long srTraceId; + org.springframework.cloud.sleuth.Log ss; + long ssSpanId; + long ssTraceId; + org.springframework.cloud.sleuth.Log cr; + long crSpanId; + long crTraceId; + + void assertThatFullRpcCycleTookPlace() { + log.info("Checking if Client Send took place"); + assertThat(this.cs).describedAs("Client Send log").isNotNull(); + log.info("Checking if Server Received took place"); + assertThat(this.sr).describedAs("Server Received log").isNotNull(); + log.info("Checking if Server Send took place"); + assertThat(this.ss).describedAs("Server Send log").isNotNull(); + log.info("Checking if Client Received took place"); + assertThat(this.cr).describedAs("Client Received log").isNotNull(); + } + + void assertThatClientSideEventsTookPlace() { + log.info("Checking if Client Send took place"); + assertThat(this.cs).describedAs("Client Send log").isNotNull(); + log.info("Checking if Client Received took place"); + assertThat(this.cr).describedAs("Client Received log").isNotNull(); + } + + void assertThatAllBelongToSameTraceAndSpan() { + log.info("Checking if RPC spans are coming from the same span"); + assertThat(this.csSpanId).describedAs("All logs should come from the same span") + .isEqualTo(this.srSpanId).isEqualTo(this.ssSpanId).isEqualTo(this.crSpanId); + log.info("Checking if RPC spans have the same trace id"); + assertThat(this.csTraceId).describedAs("All logs should come from the same trace") + .isEqualTo(this.srTraceId).isEqualTo(this.ssTraceId).isEqualTo(this.crTraceId); + } + + void assertThatAllButBelongToSameTraceAndSpan() { + log.info("Checking if CR/CS spans are coming from the same span"); + assertThat(this.csSpanId).describedAs("All logs should come from the same span").isEqualTo(this.crSpanId); + log.info("Checking if CR/CS spans have the same trace id"); + assertThat(this.csTraceId).describedAs("All logs should come from the same trace").isEqualTo(this.crTraceId); + } + + void assertThatRpcLogsTookPlaceInOrder() { + long csTimestamp = this.cs.getTimestamp(); + long srTimestamp = this.sr.getTimestamp(); + long ssTimestamp = this.ss.getTimestamp(); + long crTimestamp = this.cr.getTimestamp(); + log.info("Checking if CR is before SR"); + assertThat(csTimestamp).as("CS timestamp should be before SR timestamp").isLessThanOrEqualTo(srTimestamp); + log.info("Checking if SR is before SS"); + assertThat(srTimestamp).as("SR timestamp should be before SS timestamp").isLessThanOrEqualTo(ssTimestamp); + log.info("Checking if SS is before CR"); + assertThat(ssTimestamp).as("SS timestamp should be before CR timestamp").isLessThanOrEqualTo(crTimestamp); + } + + void assertThatCliendLogsTookPlaceInOrder() { + long csTimestamp = this.cs.getTimestamp(); + long crTimestamp = this.cr.getTimestamp(); + log.info("Checking if CS is before CR"); + assertThat(csTimestamp).as("CS timestamp should be before CR timestamp").isLessThanOrEqualTo(crTimestamp); + } + + @Override public String toString() { + return "RpcLogKeeper{" + "cs=" + cs + ", csSpanId=" + csSpanId + ", csTraceId=" + + csTraceId + ", sr=" + sr + ", srSpanId=" + srSpanId + ", srTraceId=" + + srTraceId + ", ss=" + ss + ", ssSpanId=" + ssSpanId + ", ssTraceId=" + + ssTraceId + ", cr=" + cr + ", crSpanId=" + crSpanId + ", crTraceId=" + + crTraceId + '}'; + } } \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/exception/WebClientExceptionTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/exception/WebClientExceptionTests.java index dac57ca0e..b6d781a8e 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/exception/WebClientExceptionTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/exception/WebClientExceptionTests.java @@ -42,8 +42,10 @@ import org.springframework.cloud.netflix.ribbon.RibbonClient; import org.springframework.cloud.sleuth.Sampler; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.Tracer; +import org.springframework.cloud.sleuth.assertions.ListOfSpans; 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; @@ -86,6 +88,7 @@ public class WebClientExceptionTests { @Autowired TestFeignInterfaceWithException testFeignInterfaceWithException; @Autowired @LoadBalanced RestTemplate template; @Autowired Tracer tracer; + @Autowired ArrayListSpanAccumulator accumulator; @Before public void open() { @@ -120,6 +123,7 @@ public class WebClientExceptionTests { then(ExceptionUtils.getLastException()).isNull(); then(this.systemErrRule.getLog()).doesNotContain("Tried to detach trace span but it is not the current span"); then(this.systemOutRule.getLog()).doesNotContain("Tried to detach trace span but it is not the current span"); + then(new ListOfSpans(this.accumulator.getSpans())).hasRpcWithoutSeverSideDueToException(); } Object[] parametersForShouldCloseSpanUponException() { @@ -155,6 +159,10 @@ public class WebClientExceptionTests { Sampler alwaysSampler() { return new AlwaysSampler(); } + + @Bean ArrayListSpanAccumulator accumulator() { + return new ArrayListSpanAccumulator(); + } } @Configuration diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignRetriesTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignRetriesTests.java index daaf6fba4..ff83f8490 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignRetriesTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignRetriesTests.java @@ -132,7 +132,6 @@ public class FeignRetriesTests { then(this.tracer.getCurrentSpan()).isNull(); then(ExceptionUtils.getLastException()).isNull(); then(this.spanAccumulator.getSpans().get(0)) - .hasNotLoggedAnEvent(Span.CLIENT_RECV) .hasATag("error", "java.io.IOException"); then(this.spanAccumulator.getSpans().get(1)) .hasLoggedAnEvent(Span.CLIENT_RECV); diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientTests.java index ce8f7ea21..fd1476e5c 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientTests.java @@ -98,7 +98,6 @@ public class TraceFeignClientTests { then(this.tracer.getCurrentSpan()).isEqualTo(span); then(this.spanAccumulator.getSpans().get(0)) - .hasNotLoggedAnEvent(Span.CLIENT_RECV) .hasATag(Span.SPAN_ERROR_TAG_NAME, "exception has occurred"); } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/integration/WebClientTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/integration/WebClientTests.java index c587a1196..096f88756 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/integration/WebClientTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/integration/WebClientTests.java @@ -49,6 +49,7 @@ import org.springframework.cloud.sleuth.Sampler; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.SpanReporter; import org.springframework.cloud.sleuth.Tracer; +import org.springframework.cloud.sleuth.assertions.ListOfSpans; import org.springframework.cloud.sleuth.sampler.AlwaysSampler; import org.springframework.cloud.sleuth.trace.TestSpanContextHolder; import org.springframework.cloud.sleuth.util.ArrayListSpanAccumulator; @@ -111,8 +112,9 @@ public class WebClientTests { then(getHeader(response, Span.TRACE_ID_NAME)).isNull(); then(getHeader(response, Span.SPAN_ID_NAME)).isNull(); - then(this.listener.getSpans()).isNotEmpty(); - Optional noTraceSpan = new ArrayList<>(this.listener.getSpans()).stream().filter(span -> + List spans = new ArrayList<>(this.listener.getSpans()); + then(spans).isNotEmpty(); + Optional noTraceSpan = new ArrayList<>(spans).stream().filter(span -> "http:/notrace".equals(span.getName()) && !span.tags().isEmpty() && span.tags().containsKey("http.path")).findFirst(); then(noTraceSpan.isPresent()).isTrue(); @@ -120,13 +122,23 @@ public class WebClientTests { then(noTraceSpan.get()).matchesATag("http.url", ".*/notrace") .hasATag("http.path", "/notrace") .hasATag("http.method", "GET"); + then(new ListOfSpans(spans)).hasRpcTagsInProperOrder(); } Object[] parametersForShouldCreateANewSpanWithClientSideTagsWhenNoPreviousTracingWasPresent() { return $( (ResponseEntityProvider) (tests) -> tests.testFeignInterface.getNoTrace(), - (ResponseEntityProvider) (tests) -> tests.template - .getForEntity("http://fooservice/notrace", String.class)); + (ResponseEntityProvider) (tests) -> tests.testFeignInterface.getNoTrace(), + (ResponseEntityProvider) (tests) -> tests.testFeignInterface.getNoTrace(), + (ResponseEntityProvider) (tests) -> tests.testFeignInterface.getNoTrace(), + (ResponseEntityProvider) (tests) -> tests.testFeignInterface.getNoTrace(), + (ResponseEntityProvider) (tests) -> tests.testFeignInterface.getNoTrace(), + (ResponseEntityProvider) (tests) -> tests.template.getForEntity("http://fooservice/notrace", String.class), + (ResponseEntityProvider) (tests) -> tests.template.getForEntity("http://fooservice/notrace", String.class), + (ResponseEntityProvider) (tests) -> tests.template.getForEntity("http://fooservice/notrace", String.class), + (ResponseEntityProvider) (tests) -> tests.template.getForEntity("http://fooservice/notrace", String.class), + (ResponseEntityProvider) (tests) -> tests.template.getForEntity("http://fooservice/notrace", String.class), + (ResponseEntityProvider) (tests) -> tests.template.getForEntity("http://fooservice/notrace", String.class)); } @Test From 5c117534aa60e7cb19859233984546c060b8e18a Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Tue, 31 Jan 2017 16:20:31 +0100 Subject: [PATCH 202/235] Revert "Fixing the moment when SS is set (#505)" This reverts commit 92bcae68f358cb1f0e175be3f7a6ab51526ee3bc. --- .../sleuth/instrument/web/TraceFilter.java | 253 +----------------- .../web/client/TraceAsyncRestTemplate.java | 11 - .../client/TraceRestTemplateInterceptor.java | 2 +- .../web/client/feign/TraceFeignClient.java | 1 - .../sleuth/assertions/ListOfSpansAssert.java | 140 +--------- .../exception/WebClientExceptionTests.java | 8 - .../web/client/feign/FeignRetriesTests.java | 1 + .../client/feign/TraceFeignClientTests.java | 1 + .../client/integration/WebClientTests.java | 20 +- 9 files changed, 16 insertions(+), 421 deletions(-) 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 488dd39af..86156f84a 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 @@ -16,22 +16,17 @@ package org.springframework.cloud.sleuth.instrument.web; import java.io.IOException; -import java.io.PrintWriter; import java.lang.invoke.MethodHandles; import java.util.ArrayList; import java.util.Collections; import java.util.Enumeration; -import java.util.Locale; import java.util.regex.Pattern; import javax.servlet.FilterChain; import javax.servlet.ServletException; -import javax.servlet.ServletOutputStream; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; -import javax.servlet.WriteListener; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpServletResponseWrapper; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -139,15 +134,14 @@ public class TraceFilter extends GenericFilterBean { } // in case of a response with exception status a exception controller will close the span if (!httpStatusSuccessful(response) && isSpanContinued(request)) { - Span parentSpan = spanFromRequest != null ? spanFromRequest.getSavedSpan() : null; - processErrorRequest(filterChain, request, new TraceHttpServletResponse(response, parentSpan), spanFromRequest); + processErrorRequest(filterChain, request, response, spanFromRequest); return; } String name = HTTP_COMPONENT + ":" + uri; Throwable exception = null; try { spanFromRequest = createSpan(request, skip, spanFromRequest, name); - filterChain.doFilter(request, new TraceHttpServletResponse(response, spanFromRequest)); + filterChain.doFilter(request, response); } catch (Throwable e) { exception = e; this.tracer.addTag(Span.SPAN_ERROR_TAG_NAME, ExceptionUtils.getExceptionMessage(e)); @@ -169,7 +163,7 @@ public class TraceFilter extends GenericFilterBean { HttpServletResponse response, Span spanFromRequest) throws IOException, ServletException { if (log.isDebugEnabled()) { - log.debug("The span " + spanFromRequest + " was already detached once and we're processing an error"); + log.debug("The span [" + spanFromRequest + "] was already detached once and we're processing an error"); } try { filterChain.doFilter(request, response); @@ -249,7 +243,10 @@ public class TraceFilter extends GenericFilterBean { log.debug("Trying to send the parent span " + parent + " to Zipkin"); } parent.stop(); + parent.logEvent(Span.SERVER_SEND); this.spanReporter.report(parent); + } else { + parent.logEvent(Span.SERVER_SEND); } } @@ -373,241 +370,3 @@ public class TraceFilter extends GenericFilterBean { } } } - -/** - * We want to set SS as fast as possible after the response was sent back. The response - * can be sent back by calling either of these methods. - */ -class TraceHttpServletResponse extends HttpServletResponseWrapper { - - private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); - - private final Span span; - - TraceHttpServletResponse(HttpServletResponse response, Span span) { - super(response); - this.span = span; - } - - @Override public void flushBuffer() throws IOException { - if (log.isTraceEnabled()) { - log.trace("Will annotate SS once the response is flushed"); - } - try { - super.flushBuffer(); - } finally { - SsLogSetter.annotateWithServerSendIfLogIsNotAlreadyPresent(this.span); - } - } - - @Override public ServletOutputStream getOutputStream() throws IOException { - return new TraceServletOutputStream(super.getOutputStream(), this.span); - } - - @Override public PrintWriter getWriter() throws IOException { - return new TracePrintWriter(super.getWriter(), this.span); - } -} - -class TraceServletOutputStream extends ServletOutputStream { - - private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); - - private final ServletOutputStream delegate; - private final Span span; - - TraceServletOutputStream(ServletOutputStream delegate, Span span) { - this.delegate = delegate; - this.span = span; - } - - @Override public boolean isReady() { - return this.delegate.isReady(); - } - - @Override public void setWriteListener(WriteListener listener) { - this.delegate.setWriteListener(listener); - } - - @Override public void write(int b) throws IOException { - if (log.isTraceEnabled()) { - log.trace("Will annotate SS once the response is flushed"); - } - try { - this.delegate.write(b); - } finally { - SsLogSetter.annotateWithServerSendIfLogIsNotAlreadyPresent(this.span); - } - } -} - -class TracePrintWriter extends PrintWriter { - - private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); - - private final PrintWriter delegate; - private final Span span; - - TracePrintWriter(PrintWriter delegate, Span span) { - super(delegate); - this.delegate = delegate; - this.span = span; - } - - @Override public void flush() { - if (log.isTraceEnabled()) { - log.trace("Will annotate SS once the response is flushed"); - } - try { - this.delegate.flush(); - } finally { - SsLogSetter.annotateWithServerSendIfLogIsNotAlreadyPresent(this.span); - } - } - - @Override public void close() { - this.delegate.close(); - } - - @Override public boolean checkError() { - return this.delegate.checkError(); - } - - @Override public void write(int c) { - this.delegate.write(c); - } - - @Override public void write(char[] buf, int off, int len) { - this.delegate.write(buf, off, len); - } - - @Override public void write(char[] buf) { - this.delegate.write(buf); - } - - @Override public void write(String s, int off, int len) { - this.delegate.write(s, off, len); - } - - @Override public void write(String s) { - this.delegate.write(s); - } - - @Override public void print(boolean b) { - this.delegate.print(b); - } - - @Override public void print(char c) { - this.delegate.print(c); - } - - @Override public void print(int i) { - this.delegate.print(i); - } - - @Override public void print(long l) { - this.delegate.print(l); - } - - @Override public void print(float f) { - this.delegate.print(f); - } - - @Override public void print(double d) { - this.delegate.print(d); - } - - @Override public void print(char[] s) { - this.delegate.print(s); - } - - @Override public void print(String s) { - this.delegate.print(s); - } - - @Override public void print(Object obj) { - this.delegate.print(obj); - } - - @Override public void println() { - this.delegate.println(); - } - - @Override public void println(boolean x) { - this.delegate.println(x); - } - - @Override public void println(char x) { - this.delegate.println(x); - } - - @Override public void println(int x) { - this.delegate.println(x); - } - - @Override public void println(long x) { - this.delegate.println(x); - } - - @Override public void println(float x) { - this.delegate.println(x); - } - - @Override public void println(double x) { - this.delegate.println(x); - } - - @Override public void println(char[] x) { - this.delegate.println(x); - } - - @Override public void println(String x) { - this.delegate.println(x); - } - - @Override public void println(Object x) { - this.delegate.println(x); - } - - @Override public PrintWriter printf(String format, Object... args) { - return this.delegate.printf(format, args); - } - - @Override public PrintWriter printf(Locale l, String format, Object... args) { - return this.delegate.printf(l, format, args); - } - - @Override public PrintWriter format(String format, Object... args) { - return this.delegate.format(format, args); - } - - @Override public PrintWriter format(Locale l, String format, Object... args) { - return this.delegate.format(l, format, args); - } - - @Override public PrintWriter append(CharSequence csq) { - return this.delegate.append(csq); - } - - @Override public PrintWriter append(CharSequence csq, int start, int end) { - return this.delegate.append(csq, start, end); - } - - @Override public PrintWriter append(char c) { - return this.delegate.append(c); - } -} - -class SsLogSetter { - static void annotateWithServerSendIfLogIsNotAlreadyPresent(Span span) { - if (span == null) { - return; - } - for (org.springframework.cloud.sleuth.Log log1 : span.logs()) { - if (Span.SERVER_SEND.equals(log1.getEvent())) { - return; - } - } - span.logEvent(Span.SERVER_SEND); - } -} \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncRestTemplate.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncRestTemplate.java index 097b87f67..8543f0849 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncRestTemplate.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncRestTemplate.java @@ -16,11 +16,8 @@ package org.springframework.cloud.sleuth.instrument.web.client; -import java.lang.invoke.MethodHandles; import java.net.URI; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.util.ExceptionUtils; @@ -92,8 +89,6 @@ public class TraceAsyncRestTemplate extends AsyncRestTemplate { private static class TraceListenableFutureCallback implements ListenableFutureCallback { - private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); - private final Tracer tracer; private final Span parent; @@ -104,9 +99,6 @@ public class TraceAsyncRestTemplate extends AsyncRestTemplate { @Override public void onFailure(Throwable ex) { - if (log.isDebugEnabled()) { - log.debug("The callback failed - will close the span"); - } continueSpan(); this.tracer.addTag(Span.SPAN_ERROR_TAG_NAME, ExceptionUtils.getExceptionMessage(ex)); finish(); @@ -114,9 +106,6 @@ public class TraceAsyncRestTemplate extends AsyncRestTemplate { @Override public void onSuccess(T result) { - if (log.isDebugEnabled()) { - log.debug("The callback succeeded - will close the span"); - } continueSpan(); finish(); } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptor.java index 5c7cc9c3e..ce71beb84 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptor.java @@ -62,7 +62,7 @@ public class TraceRestTemplateInterceptor extends AbstractTraceHttpRequestInterc log.debug("Exception occurred while trying to execute the request. Will close the span [" + currentSpan() + "]", e); } this.tracer.addTag(Span.SPAN_ERROR_TAG_NAME, ExceptionUtils.getExceptionMessage(e)); - finish(); + this.tracer.close(currentSpan()); throw e; } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClient.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClient.java index 3de4e78a9..64fc0d182 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClient.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClient.java @@ -90,7 +90,6 @@ class TraceFeignClient implements Client { logCr(); return response; } catch (RuntimeException | IOException e) { - logCr(); logError(e); throw e; } finally { diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/ListOfSpansAssert.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/ListOfSpansAssert.java index a0b557403..b1e237d75 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/ListOfSpansAssert.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/ListOfSpansAssert.java @@ -16,7 +16,9 @@ package org.springframework.cloud.sleuth.assertions; -import java.lang.invoke.MethodHandles; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -27,9 +29,6 @@ import org.apache.commons.logging.LogFactory; import org.assertj.core.api.AbstractAssert; import org.springframework.cloud.sleuth.Span; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; - import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.toList; import static org.assertj.core.api.Assertions.assertThat; @@ -143,28 +142,6 @@ public class ListOfSpansAssert extends AbstractAssert(this.actual.spans))); @@ -178,115 +155,4 @@ public class ListOfSpansAssert extends AbstractAssert span.logs().forEach(log -> { - switch (log.getEvent()) { - case Span.CLIENT_SEND: - rpcLogKeeper.cs = log; - rpcLogKeeper.csSpanId = span.getSpanId(); - rpcLogKeeper.csTraceId = span.getTraceId(); - break; - case Span.SERVER_RECV: - rpcLogKeeper.sr = log; - rpcLogKeeper.srSpanId = span.getSpanId(); - rpcLogKeeper.srTraceId = span.getTraceId(); - break; - case Span.SERVER_SEND: - rpcLogKeeper.ss = log; - rpcLogKeeper.ssSpanId = span.getSpanId(); - rpcLogKeeper.ssTraceId = span.getTraceId(); - break; - case Span.CLIENT_RECV: - rpcLogKeeper.cr = log; - rpcLogKeeper.crSpanId = span.getSpanId(); - rpcLogKeeper.crTraceId = span.getTraceId(); - break; - default: - break; - } - })); - return rpcLogKeeper; - } -} - -class RpcLogKeeper { - - private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); - - org.springframework.cloud.sleuth.Log cs; - long csSpanId; - long csTraceId; - org.springframework.cloud.sleuth.Log sr; - long srSpanId; - long srTraceId; - org.springframework.cloud.sleuth.Log ss; - long ssSpanId; - long ssTraceId; - org.springframework.cloud.sleuth.Log cr; - long crSpanId; - long crTraceId; - - void assertThatFullRpcCycleTookPlace() { - log.info("Checking if Client Send took place"); - assertThat(this.cs).describedAs("Client Send log").isNotNull(); - log.info("Checking if Server Received took place"); - assertThat(this.sr).describedAs("Server Received log").isNotNull(); - log.info("Checking if Server Send took place"); - assertThat(this.ss).describedAs("Server Send log").isNotNull(); - log.info("Checking if Client Received took place"); - assertThat(this.cr).describedAs("Client Received log").isNotNull(); - } - - void assertThatClientSideEventsTookPlace() { - log.info("Checking if Client Send took place"); - assertThat(this.cs).describedAs("Client Send log").isNotNull(); - log.info("Checking if Client Received took place"); - assertThat(this.cr).describedAs("Client Received log").isNotNull(); - } - - void assertThatAllBelongToSameTraceAndSpan() { - log.info("Checking if RPC spans are coming from the same span"); - assertThat(this.csSpanId).describedAs("All logs should come from the same span") - .isEqualTo(this.srSpanId).isEqualTo(this.ssSpanId).isEqualTo(this.crSpanId); - log.info("Checking if RPC spans have the same trace id"); - assertThat(this.csTraceId).describedAs("All logs should come from the same trace") - .isEqualTo(this.srTraceId).isEqualTo(this.ssTraceId).isEqualTo(this.crTraceId); - } - - void assertThatAllButBelongToSameTraceAndSpan() { - log.info("Checking if CR/CS spans are coming from the same span"); - assertThat(this.csSpanId).describedAs("All logs should come from the same span").isEqualTo(this.crSpanId); - log.info("Checking if CR/CS spans have the same trace id"); - assertThat(this.csTraceId).describedAs("All logs should come from the same trace").isEqualTo(this.crTraceId); - } - - void assertThatRpcLogsTookPlaceInOrder() { - long csTimestamp = this.cs.getTimestamp(); - long srTimestamp = this.sr.getTimestamp(); - long ssTimestamp = this.ss.getTimestamp(); - long crTimestamp = this.cr.getTimestamp(); - log.info("Checking if CR is before SR"); - assertThat(csTimestamp).as("CS timestamp should be before SR timestamp").isLessThanOrEqualTo(srTimestamp); - log.info("Checking if SR is before SS"); - assertThat(srTimestamp).as("SR timestamp should be before SS timestamp").isLessThanOrEqualTo(ssTimestamp); - log.info("Checking if SS is before CR"); - assertThat(ssTimestamp).as("SS timestamp should be before CR timestamp").isLessThanOrEqualTo(crTimestamp); - } - - void assertThatCliendLogsTookPlaceInOrder() { - long csTimestamp = this.cs.getTimestamp(); - long crTimestamp = this.cr.getTimestamp(); - log.info("Checking if CS is before CR"); - assertThat(csTimestamp).as("CS timestamp should be before CR timestamp").isLessThanOrEqualTo(crTimestamp); - } - - @Override public String toString() { - return "RpcLogKeeper{" + "cs=" + cs + ", csSpanId=" + csSpanId + ", csTraceId=" - + csTraceId + ", sr=" + sr + ", srSpanId=" + srSpanId + ", srTraceId=" - + srTraceId + ", ss=" + ss + ", ssSpanId=" + ssSpanId + ", ssTraceId=" - + ssTraceId + ", cr=" + cr + ", crSpanId=" + crSpanId + ", crTraceId=" - + crTraceId + '}'; - } } \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/exception/WebClientExceptionTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/exception/WebClientExceptionTests.java index b6d781a8e..dac57ca0e 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/exception/WebClientExceptionTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/exception/WebClientExceptionTests.java @@ -42,10 +42,8 @@ import org.springframework.cloud.netflix.ribbon.RibbonClient; import org.springframework.cloud.sleuth.Sampler; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.assertions.ListOfSpans; 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; @@ -88,7 +86,6 @@ public class WebClientExceptionTests { @Autowired TestFeignInterfaceWithException testFeignInterfaceWithException; @Autowired @LoadBalanced RestTemplate template; @Autowired Tracer tracer; - @Autowired ArrayListSpanAccumulator accumulator; @Before public void open() { @@ -123,7 +120,6 @@ public class WebClientExceptionTests { then(ExceptionUtils.getLastException()).isNull(); then(this.systemErrRule.getLog()).doesNotContain("Tried to detach trace span but it is not the current span"); then(this.systemOutRule.getLog()).doesNotContain("Tried to detach trace span but it is not the current span"); - then(new ListOfSpans(this.accumulator.getSpans())).hasRpcWithoutSeverSideDueToException(); } Object[] parametersForShouldCloseSpanUponException() { @@ -159,10 +155,6 @@ public class WebClientExceptionTests { Sampler alwaysSampler() { return new AlwaysSampler(); } - - @Bean ArrayListSpanAccumulator accumulator() { - return new ArrayListSpanAccumulator(); - } } @Configuration diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignRetriesTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignRetriesTests.java index ff83f8490..daaf6fba4 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignRetriesTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignRetriesTests.java @@ -132,6 +132,7 @@ public class FeignRetriesTests { then(this.tracer.getCurrentSpan()).isNull(); then(ExceptionUtils.getLastException()).isNull(); then(this.spanAccumulator.getSpans().get(0)) + .hasNotLoggedAnEvent(Span.CLIENT_RECV) .hasATag("error", "java.io.IOException"); then(this.spanAccumulator.getSpans().get(1)) .hasLoggedAnEvent(Span.CLIENT_RECV); diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientTests.java index fd1476e5c..ce8f7ea21 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientTests.java @@ -98,6 +98,7 @@ public class TraceFeignClientTests { then(this.tracer.getCurrentSpan()).isEqualTo(span); then(this.spanAccumulator.getSpans().get(0)) + .hasNotLoggedAnEvent(Span.CLIENT_RECV) .hasATag(Span.SPAN_ERROR_TAG_NAME, "exception has occurred"); } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/integration/WebClientTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/integration/WebClientTests.java index 096f88756..c587a1196 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/integration/WebClientTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/integration/WebClientTests.java @@ -49,7 +49,6 @@ import org.springframework.cloud.sleuth.Sampler; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.SpanReporter; import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.assertions.ListOfSpans; import org.springframework.cloud.sleuth.sampler.AlwaysSampler; import org.springframework.cloud.sleuth.trace.TestSpanContextHolder; import org.springframework.cloud.sleuth.util.ArrayListSpanAccumulator; @@ -112,9 +111,8 @@ public class WebClientTests { then(getHeader(response, Span.TRACE_ID_NAME)).isNull(); then(getHeader(response, Span.SPAN_ID_NAME)).isNull(); - List spans = new ArrayList<>(this.listener.getSpans()); - then(spans).isNotEmpty(); - Optional noTraceSpan = new ArrayList<>(spans).stream().filter(span -> + then(this.listener.getSpans()).isNotEmpty(); + Optional noTraceSpan = new ArrayList<>(this.listener.getSpans()).stream().filter(span -> "http:/notrace".equals(span.getName()) && !span.tags().isEmpty() && span.tags().containsKey("http.path")).findFirst(); then(noTraceSpan.isPresent()).isTrue(); @@ -122,23 +120,13 @@ public class WebClientTests { then(noTraceSpan.get()).matchesATag("http.url", ".*/notrace") .hasATag("http.path", "/notrace") .hasATag("http.method", "GET"); - then(new ListOfSpans(spans)).hasRpcTagsInProperOrder(); } Object[] parametersForShouldCreateANewSpanWithClientSideTagsWhenNoPreviousTracingWasPresent() { return $( (ResponseEntityProvider) (tests) -> tests.testFeignInterface.getNoTrace(), - (ResponseEntityProvider) (tests) -> tests.testFeignInterface.getNoTrace(), - (ResponseEntityProvider) (tests) -> tests.testFeignInterface.getNoTrace(), - (ResponseEntityProvider) (tests) -> tests.testFeignInterface.getNoTrace(), - (ResponseEntityProvider) (tests) -> tests.testFeignInterface.getNoTrace(), - (ResponseEntityProvider) (tests) -> tests.testFeignInterface.getNoTrace(), - (ResponseEntityProvider) (tests) -> tests.template.getForEntity("http://fooservice/notrace", String.class), - (ResponseEntityProvider) (tests) -> tests.template.getForEntity("http://fooservice/notrace", String.class), - (ResponseEntityProvider) (tests) -> tests.template.getForEntity("http://fooservice/notrace", String.class), - (ResponseEntityProvider) (tests) -> tests.template.getForEntity("http://fooservice/notrace", String.class), - (ResponseEntityProvider) (tests) -> tests.template.getForEntity("http://fooservice/notrace", String.class), - (ResponseEntityProvider) (tests) -> tests.template.getForEntity("http://fooservice/notrace", String.class)); + (ResponseEntityProvider) (tests) -> tests.template + .getForEntity("http://fooservice/notrace", String.class)); } @Test From 4060c0b805999816da1677ce41d39797c855d442 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Wed, 1 Feb 2017 10:31:50 +0100 Subject: [PATCH 203/235] Fixing the moment when SS is set without this change there's a problem with the time when the SS is set on a span. Currently it's done in TraceFilter's finally block. The problem is that this code is executed after the response has been sent back to the client. Thus CR sometimes was set faster than SS (it doesn't make any sense from the logical point of view). with this change we're introducing wrappers over the HttpServletResponse where we annotate the span with SS just after the response gets sent to the recipient. fixes #492 #431 --- .../sleuth/instrument/web/SsLogSetter.java | 51 +++++ .../sleuth/instrument/web/TraceFilter.java | 24 ++- .../web/TraceHttpServletResponse.java | 60 ++++++ .../instrument/web/TracePrintWriter.java | 186 ++++++++++++++++++ .../web/TraceServletOutputStream.java | 138 +++++++++++++ .../web/client/TraceAsyncRestTemplate.java | 11 ++ .../client/TraceRestTemplateInterceptor.java | 2 +- .../web/client/feign/TraceFeignClient.java | 1 + .../sleuth/assertions/ListOfSpansAssert.java | 166 +++++++++++++++- .../web/SpringDataInstrumentationTests.java | 1 + .../web/TraceFilterIntegrationTests.java | 4 + .../web/TraceFilterWebIntegrationTests.java | 3 +- ...stTemplateInterceptorIntegrationTests.java | 4 +- .../exception/WebClientExceptionTests.java | 8 + .../web/client/feign/FeignRetriesTests.java | 1 - .../client/feign/TraceFeignClientTests.java | 1 - .../client/integration/WebClientTests.java | 45 +++-- 17 files changed, 679 insertions(+), 27 deletions(-) create mode 100644 spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/SsLogSetter.java create mode 100644 spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHttpServletResponse.java create mode 100644 spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TracePrintWriter.java create mode 100644 spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceServletOutputStream.java diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/SsLogSetter.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/SsLogSetter.java new file mode 100644 index 000000000..0eefc1ab6 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/SsLogSetter.java @@ -0,0 +1,51 @@ +/* + * 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 org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.cloud.sleuth.Span; + +/** + * Utility class to set SS log if it wasn't already set + * + * @author Marcin Grzejszczak + */ +class SsLogSetter { + + private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); + + static void annotateWithServerSendIfLogIsNotAlreadyPresent(Span span) { + if (span == null) { + return; + } + for (org.springframework.cloud.sleuth.Log log1 : span.logs()) { + if (Span.SERVER_SEND.equals(log1.getEvent())) { + if (log.isTraceEnabled()) { + log.trace("Span was already annotated with SS, will not do it again"); + } + return; + } + } + if (log.isTraceEnabled()) { + log.trace("Will set SS on the span"); + } + span.logEvent(Span.SERVER_SEND); + } +} 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 86156f84a..c38d849a2 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 @@ -134,14 +134,15 @@ public class TraceFilter extends GenericFilterBean { } // in case of a response with exception status a exception controller will close the span if (!httpStatusSuccessful(response) && isSpanContinued(request)) { - processErrorRequest(filterChain, request, response, spanFromRequest); + Span parentSpan = parentSpan(spanFromRequest); + processErrorRequest(filterChain, request, new TraceHttpServletResponse(response, parentSpan), spanFromRequest); return; } String name = HTTP_COMPONENT + ":" + uri; Throwable exception = null; try { spanFromRequest = createSpan(request, skip, spanFromRequest, name); - filterChain.doFilter(request, response); + filterChain.doFilter(request, new TraceHttpServletResponse(response, spanFromRequest)); } catch (Throwable e) { exception = e; this.tracer.addTag(Span.SPAN_ERROR_TAG_NAME, ExceptionUtils.getExceptionMessage(e)); @@ -159,11 +160,21 @@ public class TraceFilter extends GenericFilterBean { } } + private Span parentSpan(Span span) { + if (span == null) { + return null; + } + if (span.hasSavedSpan()) { + return span.getSavedSpan(); + } + return span; + } + private void processErrorRequest(FilterChain filterChain, HttpServletRequest request, HttpServletResponse response, Span spanFromRequest) throws IOException, ServletException { if (log.isDebugEnabled()) { - log.debug("The span [" + spanFromRequest + "] was already detached once and we're processing an error"); + log.debug("The span " + spanFromRequest + " was already detached once and we're processing an error"); } try { filterChain.doFilter(request, response); @@ -243,10 +254,12 @@ public class TraceFilter extends GenericFilterBean { log.debug("Trying to send the parent span " + parent + " to Zipkin"); } parent.stop(); - parent.logEvent(Span.SERVER_SEND); + // should be already done by HttpServletResponse wrappers + SsLogSetter.annotateWithServerSendIfLogIsNotAlreadyPresent(parent); this.spanReporter.report(parent); } else { - parent.logEvent(Span.SERVER_SEND); + // should be already done by HttpServletResponse wrappers + SsLogSetter.annotateWithServerSendIfLogIsNotAlreadyPresent(parent); } } @@ -370,3 +383,4 @@ public class TraceFilter extends GenericFilterBean { } } } + diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHttpServletResponse.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHttpServletResponse.java new file mode 100644 index 000000000..a02d49797 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHttpServletResponse.java @@ -0,0 +1,60 @@ +/* + * Copyright 2013-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.sleuth.instrument.web; + +import java.io.IOException; +import java.io.PrintWriter; +import java.lang.invoke.MethodHandles; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpServletResponseWrapper; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.cloud.sleuth.Span; + +/** + * We want to set SS as fast as possible after the response was sent back. The response + * can be sent back by calling either an {@link ServletOutputStream} or {@link PrintWriter}. + */ +class TraceHttpServletResponse extends HttpServletResponseWrapper { + + private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); + + private final Span span; + + TraceHttpServletResponse(HttpServletResponse response, Span span) { + super(response); + this.span = span; + } + + @Override public void flushBuffer() throws IOException { + if (log.isTraceEnabled()) { + log.trace("Will annotate SS once the response is flushed"); + } + SsLogSetter.annotateWithServerSendIfLogIsNotAlreadyPresent(this.span); + super.flushBuffer(); + } + + @Override public ServletOutputStream getOutputStream() throws IOException { + return new TraceServletOutputStream(super.getOutputStream(), this.span); + } + + @Override public PrintWriter getWriter() throws IOException { + return new TracePrintWriter(super.getWriter(), this.span); + } +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TracePrintWriter.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TracePrintWriter.java new file mode 100644 index 000000000..96ea7855e --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TracePrintWriter.java @@ -0,0 +1,186 @@ +/* + * 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.io.PrintWriter; +import java.lang.invoke.MethodHandles; +import java.util.Locale; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.cloud.sleuth.Span; + +/** + * @author Marcin Grzejszczak + */ +class TracePrintWriter extends PrintWriter { + + private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); + + private final PrintWriter delegate; + private final Span span; + + TracePrintWriter(PrintWriter delegate, Span span) { + super(delegate); + this.delegate = delegate; + this.span = span; + } + + @Override public void flush() { + if (log.isTraceEnabled()) { + log.trace("Will annotate SS once the response is flushed"); + } + SsLogSetter.annotateWithServerSendIfLogIsNotAlreadyPresent(this.span); + this.delegate.flush(); + } + + @Override public void close() { + if (log.isTraceEnabled()) { + log.trace("Will annotate SS once the stream is closed"); + } + SsLogSetter.annotateWithServerSendIfLogIsNotAlreadyPresent(this.span); + this.delegate.close(); + } + + @Override public boolean checkError() { + return this.delegate.checkError(); + } + + @Override public void write(int c) { + this.delegate.write(c); + } + + @Override public void write(char[] buf, int off, int len) { + this.delegate.write(buf, off, len); + } + + @Override public void write(char[] buf) { + this.delegate.write(buf); + } + + @Override public void write(String s, int off, int len) { + this.delegate.write(s, off, len); + } + + @Override public void write(String s) { + this.delegate.write(s); + } + + @Override public void print(boolean b) { + this.delegate.print(b); + } + + @Override public void print(char c) { + this.delegate.print(c); + } + + @Override public void print(int i) { + this.delegate.print(i); + } + + @Override public void print(long l) { + this.delegate.print(l); + } + + @Override public void print(float f) { + this.delegate.print(f); + } + + @Override public void print(double d) { + this.delegate.print(d); + } + + @Override public void print(char[] s) { + this.delegate.print(s); + } + + @Override public void print(String s) { + this.delegate.print(s); + } + + @Override public void print(Object obj) { + this.delegate.print(obj); + } + + @Override public void println() { + this.delegate.println(); + } + + @Override public void println(boolean x) { + this.delegate.println(x); + } + + @Override public void println(char x) { + this.delegate.println(x); + } + + @Override public void println(int x) { + this.delegate.println(x); + } + + @Override public void println(long x) { + this.delegate.println(x); + } + + @Override public void println(float x) { + this.delegate.println(x); + } + + @Override public void println(double x) { + this.delegate.println(x); + } + + @Override public void println(char[] x) { + this.delegate.println(x); + } + + @Override public void println(String x) { + this.delegate.println(x); + } + + @Override public void println(Object x) { + this.delegate.println(x); + } + + @Override public PrintWriter printf(String format, Object... args) { + return this.delegate.printf(format, args); + } + + @Override public PrintWriter printf(Locale l, String format, Object... args) { + return this.delegate.printf(l, format, args); + } + + @Override public PrintWriter format(String format, Object... args) { + return this.delegate.format(format, args); + } + + @Override public PrintWriter format(Locale l, String format, Object... args) { + return this.delegate.format(l, format, args); + } + + @Override public PrintWriter append(CharSequence csq) { + return this.delegate.append(csq); + } + + @Override public PrintWriter append(CharSequence csq, int start, int end) { + return this.delegate.append(csq, start, end); + } + + @Override public PrintWriter append(char c) { + return this.delegate.append(c); + } +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceServletOutputStream.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceServletOutputStream.java new file mode 100644 index 000000000..f5e4779ab --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceServletOutputStream.java @@ -0,0 +1,138 @@ +/* + * 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.io.IOException; +import java.lang.invoke.MethodHandles; +import javax.servlet.ServletOutputStream; +import javax.servlet.WriteListener; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.cloud.sleuth.Span; + +/** + * @author Marcin Grzejszczak + */ +class TraceServletOutputStream extends ServletOutputStream { + + private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); + + private final ServletOutputStream delegate; + private final Span span; + + TraceServletOutputStream(ServletOutputStream delegate, Span span) { + this.delegate = delegate; + this.span = span; + } + + @Override public boolean isReady() { + return this.delegate.isReady(); + } + + @Override public void setWriteListener(WriteListener listener) { + this.delegate.setWriteListener(listener); + } + + @Override public void write(int b) throws IOException { + this.delegate.write(b); + } + + @Override public void print(String s) throws IOException { + this.delegate.print(s); + } + + @Override public void print(boolean b) throws IOException { + this.delegate.print(b); + } + + @Override public void print(char c) throws IOException { + this.delegate.print(c); + } + + @Override public void print(int i) throws IOException { + this.delegate.print(i); + } + + @Override public void print(long l) throws IOException { + this.delegate.print(l); + } + + @Override public void print(float f) throws IOException { + this.delegate.print(f); + } + + @Override public void print(double d) throws IOException { + this.delegate.print(d); + } + + @Override public void println() throws IOException { + this.delegate.println(); + } + + @Override public void println(String s) throws IOException { + this.delegate.println(s); + } + + @Override public void println(boolean b) throws IOException { + this.delegate.println(b); + } + + @Override public void println(char c) throws IOException { + this.delegate.println(c); + } + + @Override public void println(int i) throws IOException { + this.delegate.println(i); + } + + @Override public void println(long l) throws IOException { + this.delegate.println(l); + } + + @Override public void println(float f) throws IOException { + this.delegate.println(f); + } + + @Override public void println(double d) throws IOException { + this.delegate.println(d); + } + + @Override public void write(byte[] b) throws IOException { + this.delegate.write(b); + } + + @Override public void write(byte[] b, int off, int len) throws IOException { + this.delegate.write(b, off, len); + } + + @Override public void flush() throws IOException { + if (log.isTraceEnabled()) { + log.trace("Will annotate SS once the stream is flushed"); + } + SsLogSetter.annotateWithServerSendIfLogIsNotAlreadyPresent(this.span); + this.delegate.flush(); + } + + @Override public void close() throws IOException { + if (log.isTraceEnabled()) { + log.trace("Will annotate SS once the stream is closed"); + } + SsLogSetter.annotateWithServerSendIfLogIsNotAlreadyPresent(this.span); + this.delegate.close(); + } +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncRestTemplate.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncRestTemplate.java index 8543f0849..097b87f67 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncRestTemplate.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncRestTemplate.java @@ -16,8 +16,11 @@ package org.springframework.cloud.sleuth.instrument.web.client; +import java.lang.invoke.MethodHandles; import java.net.URI; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.util.ExceptionUtils; @@ -89,6 +92,8 @@ public class TraceAsyncRestTemplate extends AsyncRestTemplate { private static class TraceListenableFutureCallback implements ListenableFutureCallback { + private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); + private final Tracer tracer; private final Span parent; @@ -99,6 +104,9 @@ public class TraceAsyncRestTemplate extends AsyncRestTemplate { @Override public void onFailure(Throwable ex) { + if (log.isDebugEnabled()) { + log.debug("The callback failed - will close the span"); + } continueSpan(); this.tracer.addTag(Span.SPAN_ERROR_TAG_NAME, ExceptionUtils.getExceptionMessage(ex)); finish(); @@ -106,6 +114,9 @@ public class TraceAsyncRestTemplate extends AsyncRestTemplate { @Override public void onSuccess(T result) { + if (log.isDebugEnabled()) { + log.debug("The callback succeeded - will close the span"); + } continueSpan(); finish(); } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptor.java index ce71beb84..5c7cc9c3e 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptor.java @@ -62,7 +62,7 @@ public class TraceRestTemplateInterceptor extends AbstractTraceHttpRequestInterc log.debug("Exception occurred while trying to execute the request. Will close the span [" + currentSpan() + "]", e); } this.tracer.addTag(Span.SPAN_ERROR_TAG_NAME, ExceptionUtils.getExceptionMessage(e)); - this.tracer.close(currentSpan()); + finish(); throw e; } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClient.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClient.java index 64fc0d182..3de4e78a9 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClient.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClient.java @@ -90,6 +90,7 @@ class TraceFeignClient implements Client { logCr(); return response; } catch (RuntimeException | IOException e) { + logCr(); logError(e); throw e; } finally { diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/ListOfSpansAssert.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/ListOfSpansAssert.java index b1e237d75..5326892f7 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/ListOfSpansAssert.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/ListOfSpansAssert.java @@ -16,9 +16,7 @@ package org.springframework.cloud.sleuth.assertions; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; - +import java.lang.invoke.MethodHandles; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -29,6 +27,9 @@ import org.apache.commons.logging.LogFactory; import org.assertj.core.api.AbstractAssert; import org.springframework.cloud.sleuth.Span; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.toList; import static org.assertj.core.api.Assertions.assertThat; @@ -142,6 +143,39 @@ public class ListOfSpansAssert extends AbstractAssert(this.actual.spans))); @@ -155,4 +189,130 @@ public class ListOfSpansAssert extends AbstractAssert span.logs().forEach(log -> { + switch (log.getEvent()) { + case Span.CLIENT_SEND: + rpcLogKeeper.cs = log; + rpcLogKeeper.csSpanId = span.getSpanId(); + rpcLogKeeper.csTraceId = span.getTraceId(); + break; + case Span.SERVER_RECV: + rpcLogKeeper.sr = log; + rpcLogKeeper.srSpanId = span.getSpanId(); + rpcLogKeeper.srTraceId = span.getTraceId(); + break; + case Span.SERVER_SEND: + rpcLogKeeper.ss = log; + rpcLogKeeper.ssSpanId = span.getSpanId(); + rpcLogKeeper.ssTraceId = span.getTraceId(); + break; + case Span.CLIENT_RECV: + rpcLogKeeper.cr = log; + rpcLogKeeper.crSpanId = span.getSpanId(); + rpcLogKeeper.crTraceId = span.getTraceId(); + break; + default: + break; + } + })); + return rpcLogKeeper; + } +} + +class RpcLogKeeper { + + private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); + + org.springframework.cloud.sleuth.Log cs; + long csSpanId; + long csTraceId; + org.springframework.cloud.sleuth.Log sr; + long srSpanId; + long srTraceId; + org.springframework.cloud.sleuth.Log ss; + long ssSpanId; + long ssTraceId; + org.springframework.cloud.sleuth.Log cr; + long crSpanId; + long crTraceId; + + void assertThatFullRpcCycleTookPlace() { + assertThatServerSideEventsTookPlace(); + assertThatClientSideEventsTookPlace(); + } + void assertThatServerSideEventsTookPlace() { + log.info("Checking if Server Received took place"); + assertThat(this.sr).describedAs("Server Received log").isNotNull(); + log.info("Checking if Server Send took place"); + assertThat(this.ss).describedAs("Server Send log").isNotNull(); + log.info("Checking if Client Received took place"); + } + + void assertThatClientSideEventsTookPlace() { + log.info("Checking if Client Send took place"); + assertThat(this.cs).describedAs("Client Send log").isNotNull(); + log.info("Checking if Client Received took place"); + assertThat(this.cr).describedAs("Client Received log").isNotNull(); + } + + void assertThatAllBelongToSameTraceAndSpan() { + log.info("Checking if RPC spans are coming from the same span"); + assertThat(this.csSpanId).describedAs("All logs should come from the same span") + .isEqualTo(this.srSpanId).isEqualTo(this.ssSpanId).isEqualTo(this.crSpanId); + log.info("Checking if RPC spans have the same trace id"); + assertThat(this.csTraceId).describedAs("All logs should come from the same trace") + .isEqualTo(this.srTraceId).isEqualTo(this.ssTraceId).isEqualTo(this.crTraceId); + } + + void assertThatClientSideEventsBelongToSameTraceAndSpan() { + log.info("Checking if CR/CS logs are coming from the same span"); + assertThat(this.csSpanId).describedAs("All logs should come from the same span").isEqualTo(this.crSpanId); + log.info("Checking if CR/CS logs have the same trace id"); + assertThat(this.csTraceId).describedAs("All logs should come from the same trace").isEqualTo(this.crTraceId); + } + + void assertThatServerSideEventsBelongToSameTraceAndSpan() { + log.info("Checking if SS/SR logs are coming from the same span"); + assertThat(this.ssSpanId).describedAs("All logs should come from the same span").isEqualTo(this.srSpanId); + log.info("Checking if SS/SR logs have the same trace id"); + assertThat(this.ssTraceId).describedAs("All logs should come from the same trace").isEqualTo(this.srTraceId); + } + + void assertThatRpcLogsTookPlaceInOrder() { + long csTimestamp = this.cs.getTimestamp(); + long srTimestamp = this.sr.getTimestamp(); + long ssTimestamp = this.ss.getTimestamp(); + long crTimestamp = this.cr.getTimestamp(); + log.info("Checking if CR is before SR"); + assertThat(csTimestamp).as("CS timestamp should be before SR timestamp").isLessThanOrEqualTo(srTimestamp); + log.info("Checking if SR is before SS"); + assertThat(srTimestamp).as("SR timestamp should be before SS timestamp").isLessThanOrEqualTo(ssTimestamp); + log.info("Checking if SS is before CR"); + assertThat(ssTimestamp).as("SS timestamp should be before CR timestamp").isLessThanOrEqualTo(crTimestamp); + } + + void assertThatClientLogsTookPlaceInOrder() { + long csTimestamp = this.cs.getTimestamp(); + long crTimestamp = this.cr.getTimestamp(); + log.info("Checking if CS is before CR"); + assertThat(csTimestamp).as("CS timestamp should be before CR timestamp").isLessThanOrEqualTo(crTimestamp); + } + + void assertThatServerLogsTookPlaceInOrder() { + long srTimestamp = this.sr.getTimestamp(); + long ssTimestamp = this.ss.getTimestamp(); + log.info("Checking if CS is before CR"); + assertThat(srTimestamp).as("SR timestamp should be before SS timestamp").isLessThanOrEqualTo(ssTimestamp); + } + + @Override public String toString() { + return "RpcLogKeeper{" + "cs=" + cs + ", csSpanId=" + csSpanId + ", csTraceId=" + + csTraceId + ", sr=" + sr + ", srSpanId=" + srSpanId + ", srTraceId=" + + srTraceId + ", ss=" + ss + ", ssSpanId=" + ssSpanId + ", ssTraceId=" + + ssTraceId + ", cr=" + cr + ", crSpanId=" + crSpanId + ", crTraceId=" + + crTraceId + '}'; + } } \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SpringDataInstrumentationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SpringDataInstrumentationTests.java index 5b26825dd..126b952d2 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SpringDataInstrumentationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SpringDataInstrumentationTests.java @@ -86,6 +86,7 @@ public class SpringDataInstrumentationTests { }); then(this.tracer.getCurrentSpan()).isNull(); then(ExceptionUtils.getLastException()).isNull(); + then(new ListOfSpans(this.arrayListSpanAccumulator.getSpans())).hasRpcTagsInProperOrder(); } Collection names() { diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterIntegrationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterIntegrationTests.java index da98a0dbb..c8f470790 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterIntegrationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterIntegrationTests.java @@ -19,6 +19,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.assertions.ListOfSpans; import org.springframework.cloud.sleuth.instrument.DefaultTestAutoConfiguration; import org.springframework.cloud.sleuth.instrument.web.common.AbstractMvcIntegrationTest; import org.springframework.cloud.sleuth.sampler.AlwaysSampler; @@ -68,6 +69,7 @@ public class TraceFilterIntegrationTests extends AbstractMvcIntegrationTest { .hasATagWithKey(new TraceKeys().getMvc().getControllerMethod()) .hasLoggedAnEvent(Span.SERVER_SEND); then(ExceptionUtils.getLastException()).isNull(); + then(new ListOfSpans(this.spanAccumulator.getSpans())).hasServerSideSpansInProperOrder(); } @Test @@ -128,6 +130,7 @@ public class TraceFilterIntegrationTests extends AbstractMvcIntegrationTest { then(taggedSpan.get()).hasATag("mvc.controller.method", "deferredMethod"); then(taggedSpan.get()).hasATag("mvc.controller.class", "TestController"); then(ExceptionUtils.getLastException()).isNull(); + then(new ListOfSpans(this.spanAccumulator.getSpans())).hasServerSideSpansInProperOrder(); } @Test @@ -151,6 +154,7 @@ public class TraceFilterIntegrationTests extends AbstractMvcIntegrationTest { span.getSpanId() == span.getTraceId()).findAny().isPresent()).as("a root span exists").isTrue(); then(this.tracer.getCurrentSpan()).isNull(); then(ExceptionUtils.getLastException()).isNull(); + then(new ListOfSpans(this.spanAccumulator.getSpans())).hasServerSideSpansInProperOrder(); } @Override diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterWebIntegrationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterWebIntegrationTests.java index 4db80f0e1..597a2a6cb 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterWebIntegrationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterWebIntegrationTests.java @@ -76,7 +76,8 @@ public class TraceFilterWebIntegrationTests { then(ExceptionUtils.getLastException()).isNull(); then(new ListOfSpans(this.accumulator.getSpans())) .hasASpanWithTagEqualTo(Span.SPAN_ERROR_TAG_NAME, - "Request processing failed; nested exception is java.lang.RuntimeException: Throwing exception"); + "Request processing failed; nested exception is java.lang.RuntimeException: Throwing exception") + .hasRpcTagsInProperOrder(); } private int port() { diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptorIntegrationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptorIntegrationTests.java index 73a363ecc..5726739a7 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptorIntegrationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptorIntegrationTests.java @@ -95,8 +95,8 @@ public class TraceRestTemplateInterceptorIntegrationTests { SleuthAssertions.then(this.tracer.getCurrentSpan()).isEqualTo(span); this.tracer.close(span); SleuthAssertions.then(new ListOfSpans(this.spanAccumulator.getSpans())) - .hasASpanWithTagEqualTo(Span.SPAN_ERROR_TAG_NAME, - "Read timed out"); + .hasASpanWithTagEqualTo(Span.SPAN_ERROR_TAG_NAME, "Read timed out") + .hasRpcWithoutSeverSideDueToException(); then(ExceptionUtils.getLastException()).isNull(); } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/exception/WebClientExceptionTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/exception/WebClientExceptionTests.java index dac57ca0e..b6d781a8e 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/exception/WebClientExceptionTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/exception/WebClientExceptionTests.java @@ -42,8 +42,10 @@ import org.springframework.cloud.netflix.ribbon.RibbonClient; import org.springframework.cloud.sleuth.Sampler; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.Tracer; +import org.springframework.cloud.sleuth.assertions.ListOfSpans; 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; @@ -86,6 +88,7 @@ public class WebClientExceptionTests { @Autowired TestFeignInterfaceWithException testFeignInterfaceWithException; @Autowired @LoadBalanced RestTemplate template; @Autowired Tracer tracer; + @Autowired ArrayListSpanAccumulator accumulator; @Before public void open() { @@ -120,6 +123,7 @@ public class WebClientExceptionTests { then(ExceptionUtils.getLastException()).isNull(); then(this.systemErrRule.getLog()).doesNotContain("Tried to detach trace span but it is not the current span"); then(this.systemOutRule.getLog()).doesNotContain("Tried to detach trace span but it is not the current span"); + then(new ListOfSpans(this.accumulator.getSpans())).hasRpcWithoutSeverSideDueToException(); } Object[] parametersForShouldCloseSpanUponException() { @@ -155,6 +159,10 @@ public class WebClientExceptionTests { Sampler alwaysSampler() { return new AlwaysSampler(); } + + @Bean ArrayListSpanAccumulator accumulator() { + return new ArrayListSpanAccumulator(); + } } @Configuration diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignRetriesTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignRetriesTests.java index daaf6fba4..ff83f8490 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignRetriesTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignRetriesTests.java @@ -132,7 +132,6 @@ public class FeignRetriesTests { then(this.tracer.getCurrentSpan()).isNull(); then(ExceptionUtils.getLastException()).isNull(); then(this.spanAccumulator.getSpans().get(0)) - .hasNotLoggedAnEvent(Span.CLIENT_RECV) .hasATag("error", "java.io.IOException"); then(this.spanAccumulator.getSpans().get(1)) .hasLoggedAnEvent(Span.CLIENT_RECV); diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientTests.java index ce8f7ea21..fd1476e5c 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientTests.java @@ -98,7 +98,6 @@ public class TraceFeignClientTests { then(this.tracer.getCurrentSpan()).isEqualTo(span); then(this.spanAccumulator.getSpans().get(0)) - .hasNotLoggedAnEvent(Span.CLIENT_RECV) .hasATag(Span.SPAN_ERROR_TAG_NAME, "exception has occurred"); } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/integration/WebClientTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/integration/WebClientTests.java index c587a1196..cb68a11f0 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/integration/WebClientTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/integration/WebClientTests.java @@ -24,6 +24,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Random; +import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import javax.servlet.http.HttpServletRequest; @@ -49,6 +50,7 @@ import org.springframework.cloud.sleuth.Sampler; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.SpanReporter; import org.springframework.cloud.sleuth.Tracer; +import org.springframework.cloud.sleuth.assertions.ListOfSpans; import org.springframework.cloud.sleuth.sampler.AlwaysSampler; import org.springframework.cloud.sleuth.trace.TestSpanContextHolder; import org.springframework.cloud.sleuth.util.ArrayListSpanAccumulator; @@ -66,6 +68,7 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.RestTemplate; +import com.jayway.awaitility.Awaitility; import com.netflix.loadbalancer.BaseLoadBalancer; import com.netflix.loadbalancer.ILoadBalancer; import com.netflix.loadbalancer.Server; @@ -109,24 +112,40 @@ public class WebClientTests { ResponseEntityProvider provider) { ResponseEntity response = provider.get(this); - then(getHeader(response, Span.TRACE_ID_NAME)).isNull(); - then(getHeader(response, Span.SPAN_ID_NAME)).isNull(); - then(this.listener.getSpans()).isNotEmpty(); - Optional noTraceSpan = new ArrayList<>(this.listener.getSpans()).stream().filter(span -> - "http:/notrace".equals(span.getName()) && !span.tags().isEmpty() - && span.tags().containsKey("http.path")).findFirst(); - then(noTraceSpan.isPresent()).isTrue(); - // TODO: matches cause there is an issue with Feign not providing the full URL at the interceptor level - then(noTraceSpan.get()).matchesATag("http.url", ".*/notrace") - .hasATag("http.path", "/notrace") - .hasATag("http.method", "GET"); + Awaitility.await().atMost(2, TimeUnit.SECONDS).until(() -> { + then(getHeader(response, Span.TRACE_ID_NAME)).isNull(); + then(getHeader(response, Span.SPAN_ID_NAME)).isNull(); + List spans = new ArrayList<>(this.listener.getSpans()); + then(spans).isNotEmpty(); + Optional noTraceSpan = new ArrayList<>(spans).stream() + .filter(span -> "http:/notrace".equals(span.getName()) && !span.tags() + .isEmpty() && span.tags().containsKey("http.path")).findFirst(); + then(noTraceSpan.isPresent()).isTrue(); + // TODO: matches cause there is an issue with Feign not providing the full URL at the interceptor level + then(noTraceSpan.get()).matchesATag("http.url", ".*/notrace") + .hasATag("http.path", "/notrace").hasATag("http.method", "GET"); + then(new ListOfSpans(spans)).hasRpcTagsInProperOrder(); + }); } Object[] parametersForShouldCreateANewSpanWithClientSideTagsWhenNoPreviousTracingWasPresent() { return $( (ResponseEntityProvider) (tests) -> tests.testFeignInterface.getNoTrace(), - (ResponseEntityProvider) (tests) -> tests.template - .getForEntity("http://fooservice/notrace", String.class)); + (ResponseEntityProvider) (tests) -> tests.testFeignInterface.getNoTrace(), + (ResponseEntityProvider) (tests) -> tests.testFeignInterface.getNoTrace(), + (ResponseEntityProvider) (tests) -> tests.testFeignInterface.getNoTrace(), + (ResponseEntityProvider) (tests) -> tests.testFeignInterface.getNoTrace(), + (ResponseEntityProvider) (tests) -> tests.testFeignInterface.getNoTrace(), + (ResponseEntityProvider) (tests) -> tests.testFeignInterface.getNoTrace(), + (ResponseEntityProvider) (tests) -> tests.testFeignInterface.getNoTrace(), + (ResponseEntityProvider) (tests) -> tests.template.getForEntity("http://fooservice/notrace", String.class), + (ResponseEntityProvider) (tests) -> tests.template.getForEntity("http://fooservice/notrace", String.class), + (ResponseEntityProvider) (tests) -> tests.template.getForEntity("http://fooservice/notrace", String.class), + (ResponseEntityProvider) (tests) -> tests.template.getForEntity("http://fooservice/notrace", String.class), + (ResponseEntityProvider) (tests) -> tests.template.getForEntity("http://fooservice/notrace", String.class), + (ResponseEntityProvider) (tests) -> tests.template.getForEntity("http://fooservice/notrace", String.class), + (ResponseEntityProvider) (tests) -> tests.template.getForEntity("http://fooservice/notrace", String.class), + (ResponseEntityProvider) (tests) -> tests.template.getForEntity("http://fooservice/notrace", String.class)); } @Test From abefcaa35461c3c36366cf94ebb3ab0cd7fedb2e Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Wed, 1 Feb 2017 11:19:51 +0100 Subject: [PATCH 204/235] Removed duplicated dependency fixes #504 --- spring-cloud-sleuth-core/pom.xml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/spring-cloud-sleuth-core/pom.xml b/spring-cloud-sleuth-core/pom.xml index 5e7a6ca7f..236a80cd8 100644 --- a/spring-cloud-sleuth-core/pom.xml +++ b/spring-cloud-sleuth-core/pom.xml @@ -94,11 +94,6 @@ spring-boot-starter-test test - - org.aspectj - aspectjweaver - test - com.netflix.archaius archaius-core From 3179637eb836a79fc46b2abb0abbd641e7e0f238 Mon Sep 17 00:00:00 2001 From: Arthur Gavlyukovskiy Date: Thu, 2 Feb 2017 10:10:46 +0200 Subject: [PATCH 205/235] Display overriden service name in logs (addition to #487) (#507) Use `spring.zipkin.service.name` if present. --- .../cloud/sleuth/autoconfig/TraceEnvironmentPostProcessor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/TraceEnvironmentPostProcessor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/TraceEnvironmentPostProcessor.java index 8a4c1ae37..33a66b12c 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/TraceEnvironmentPostProcessor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/TraceEnvironmentPostProcessor.java @@ -51,7 +51,7 @@ public class TraceEnvironmentPostProcessor implements EnvironmentPostProcessor { // This doesn't work with all logging systems but it's a useful default so you see // traces in logs without having to configure it. map.put("logging.pattern.level", - "%clr(%5p) %clr([${spring.application.name:-},%X{X-B3-TraceId:-},%X{X-B3-SpanId:-},%X{X-Span-Export:-}]){yellow}"); + "%clr(%5p) %clr([${spring.zipkin.service.name:${spring.application.name:-}},%X{X-B3-TraceId:-},%X{X-B3-SpanId:-},%X{X-Span-Export:-}]){yellow}"); map.put("spring.aop.proxyTargetClass", "true"); addOrReplace(environment.getPropertySources(), map); } From ac0c209f949e4935df79578b47636779ae1f20b0 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Fri, 3 Feb 2017 13:30:35 +0100 Subject: [PATCH 206/235] Bumping versions before release --- README.adoc | 6 ++++-- benchmarks/pom.xml | 2 +- docs/pom.xml | 2 +- pom.xml | 12 ++++++------ spring-cloud-sleuth-core/pom.xml | 2 +- spring-cloud-sleuth-dependencies/pom.xml | 4 ++-- spring-cloud-sleuth-samples/pom.xml | 2 +- .../spring-cloud-sleuth-sample-feign/pom.xml | 2 +- .../spring-cloud-sleuth-sample-messaging/pom.xml | 2 +- .../spring-cloud-sleuth-sample-ribbon/pom.xml | 2 +- .../spring-cloud-sleuth-sample-stream/pom.xml | 2 +- .../spring-cloud-sleuth-sample-test-core/pom.xml | 2 +- .../spring-cloud-sleuth-sample-websocket/pom.xml | 2 +- .../spring-cloud-sleuth-sample-zipkin-stream/pom.xml | 2 +- .../spring-cloud-sleuth-sample-zipkin/pom.xml | 2 +- .../spring-cloud-sleuth-sample/pom.xml | 2 +- spring-cloud-sleuth-stream/pom.xml | 2 +- spring-cloud-sleuth-zipkin-stream/pom.xml | 2 +- spring-cloud-sleuth-zipkin/pom.xml | 2 +- spring-cloud-starter-sleuth/pom.xml | 2 +- spring-cloud-starter-zipkin/pom.xml | 2 +- 21 files changed, 30 insertions(+), 28 deletions(-) diff --git a/README.adoc b/README.adoc index 6ee18ce72..879003227 100644 --- a/README.adoc +++ b/README.adoc @@ -194,8 +194,9 @@ Below you can find an example of a Logback configuration (file named https://git ​ + + value="%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}"/> @@ -256,7 +257,8 @@ Below you can find an example of a Logback configuration (file named https://git ​ - + + diff --git a/benchmarks/pom.xml b/benchmarks/pom.xml index cd9695e76..2ccd50c41 100644 --- a/benchmarks/pom.xml +++ b/benchmarks/pom.xml @@ -17,7 +17,7 @@ Benchmarks Benchmarks (JMH) org.springframework.cloud - 1.1.2.BUILD-SNAPSHOT + 1.1.2.RELEASE benchmarks diff --git a/docs/pom.xml b/docs/pom.xml index d6fb933d7..80ce91ab7 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.2.BUILD-SNAPSHOT + 1.1.2.RELEASE spring-cloud-sleuth-docs pom diff --git a/pom.xml b/pom.xml index 80455f1c5..410ef1f27 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 spring-cloud-sleuth - 1.1.2.BUILD-SNAPSHOT + 1.1.2.RELEASE pom Spring Cloud Sleuth Spring Cloud Sleuth @@ -13,7 +13,7 @@ org.springframework.cloud spring-cloud-build - 1.2.2.BUILD-SNAPSHOT + 1.2.2.RELEASE @@ -229,10 +229,10 @@ 1.8 2.19.1 2.17 - 1.2.2.BUILD-SNAPSHOT - 1.1.8.BUILD-SNAPSHOT - Brooklyn.BUILD-SNAPSHOT - 1.2.5.BUILD-SNAPSHOT + 1.2.2.RELEASE + 1.1.7.RELEASE + Brooklyn.SR2 + 1.2.5.RELEASE diff --git a/spring-cloud-sleuth-core/pom.xml b/spring-cloud-sleuth-core/pom.xml index 236a80cd8..4cee929a1 100644 --- a/spring-cloud-sleuth-core/pom.xml +++ b/spring-cloud-sleuth-core/pom.xml @@ -11,7 +11,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.2.BUILD-SNAPSHOT + 1.1.2.RELEASE .. diff --git a/spring-cloud-sleuth-dependencies/pom.xml b/spring-cloud-sleuth-dependencies/pom.xml index 94cb8316f..40825a40c 100644 --- a/spring-cloud-sleuth-dependencies/pom.xml +++ b/spring-cloud-sleuth-dependencies/pom.xml @@ -5,11 +5,11 @@ spring-cloud-dependencies-parent org.springframework.cloud - 1.2.2.BUILD-SNAPSHOT + 1.2.2.RELEASE spring-cloud-sleuth-dependencies - 1.1.2.BUILD-SNAPSHOT + 1.1.2.RELEASE pom spring-cloud-sleuth-dependencies Spring Cloud Sleuth Dependencies diff --git a/spring-cloud-sleuth-samples/pom.xml b/spring-cloud-sleuth-samples/pom.xml index ba912bd17..691f0ffd1 100644 --- a/spring-cloud-sleuth-samples/pom.xml +++ b/spring-cloud-sleuth-samples/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.2.BUILD-SNAPSHOT + 1.1.2.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml index 4bb5a3a13..c35176bf7 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml @@ -20,7 +20,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.2.BUILD-SNAPSHOT + 1.1.2.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml index f18694096..7651dd147 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.2.BUILD-SNAPSHOT + 1.1.2.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml index 06b531e81..9d4a2df6e 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.2.BUILD-SNAPSHOT + 1.1.2.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml index e4800a105..c7477943a 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.2.BUILD-SNAPSHOT + 1.1.2.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml index 068551237..8e2785c00 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.2.BUILD-SNAPSHOT + 1.1.2.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml index 464cf8d49..2d3cc74dd 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.2.BUILD-SNAPSHOT + 1.1.2.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml index b4f0ad234..ca8c0f5a7 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.2.BUILD-SNAPSHOT + 1.1.2.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml index c08fe8fb6..bc59fe621 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml @@ -27,7 +27,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.2.BUILD-SNAPSHOT + 1.1.2.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml index cea334fb2..e9549bd1a 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.2.BUILD-SNAPSHOT + 1.1.2.RELEASE .. diff --git a/spring-cloud-sleuth-stream/pom.xml b/spring-cloud-sleuth-stream/pom.xml index 48205c5c0..d93cc1f2c 100644 --- a/spring-cloud-sleuth-stream/pom.xml +++ b/spring-cloud-sleuth-stream/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.2.BUILD-SNAPSHOT + 1.1.2.RELEASE diff --git a/spring-cloud-sleuth-zipkin-stream/pom.xml b/spring-cloud-sleuth-zipkin-stream/pom.xml index 0fcd5d59c..30357d814 100644 --- a/spring-cloud-sleuth-zipkin-stream/pom.xml +++ b/spring-cloud-sleuth-zipkin-stream/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.2.BUILD-SNAPSHOT + 1.1.2.RELEASE diff --git a/spring-cloud-sleuth-zipkin/pom.xml b/spring-cloud-sleuth-zipkin/pom.xml index eb1e2141b..826362e9c 100644 --- a/spring-cloud-sleuth-zipkin/pom.xml +++ b/spring-cloud-sleuth-zipkin/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.2.BUILD-SNAPSHOT + 1.1.2.RELEASE .. diff --git a/spring-cloud-starter-sleuth/pom.xml b/spring-cloud-starter-sleuth/pom.xml index 46914864e..d13addc13 100644 --- a/spring-cloud-starter-sleuth/pom.xml +++ b/spring-cloud-starter-sleuth/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.2.BUILD-SNAPSHOT + 1.1.2.RELEASE .. spring-cloud-starter-sleuth diff --git a/spring-cloud-starter-zipkin/pom.xml b/spring-cloud-starter-zipkin/pom.xml index bae3c944e..905c45902 100644 --- a/spring-cloud-starter-zipkin/pom.xml +++ b/spring-cloud-starter-zipkin/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.2.BUILD-SNAPSHOT + 1.1.2.RELEASE .. spring-cloud-starter-zipkin From 8f11bbe9f04399727652120fa29557d7bc16d5c4 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Fri, 3 Feb 2017 13:35:59 +0100 Subject: [PATCH 207/235] Going back to snapshots --- README.adoc | 6 ++---- benchmarks/pom.xml | 2 +- docs/pom.xml | 2 +- pom.xml | 12 ++++++------ spring-cloud-sleuth-core/pom.xml | 2 +- spring-cloud-sleuth-dependencies/pom.xml | 4 ++-- spring-cloud-sleuth-samples/pom.xml | 2 +- .../spring-cloud-sleuth-sample-feign/pom.xml | 2 +- .../spring-cloud-sleuth-sample-messaging/pom.xml | 2 +- .../spring-cloud-sleuth-sample-ribbon/pom.xml | 2 +- .../spring-cloud-sleuth-sample-stream/pom.xml | 2 +- .../spring-cloud-sleuth-sample-test-core/pom.xml | 2 +- .../spring-cloud-sleuth-sample-websocket/pom.xml | 2 +- .../spring-cloud-sleuth-sample-zipkin-stream/pom.xml | 2 +- .../spring-cloud-sleuth-sample-zipkin/pom.xml | 2 +- .../spring-cloud-sleuth-sample/pom.xml | 2 +- spring-cloud-sleuth-stream/pom.xml | 2 +- spring-cloud-sleuth-zipkin-stream/pom.xml | 2 +- spring-cloud-sleuth-zipkin/pom.xml | 2 +- spring-cloud-starter-sleuth/pom.xml | 2 +- spring-cloud-starter-zipkin/pom.xml | 2 +- 21 files changed, 28 insertions(+), 30 deletions(-) diff --git a/README.adoc b/README.adoc index 879003227..6ee18ce72 100644 --- a/README.adoc +++ b/README.adoc @@ -194,9 +194,8 @@ Below you can find an example of a Logback configuration (file named https://git ​ - + value="%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr([${springAppName:-},%X{X-B3-TraceId:-},%X{X-B3-SpanId:-},%X{X-B3-ParentSpanId:-},%X{X-Span-Export:-}]){yellow} %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}"/> @@ -257,8 +256,7 @@ Below you can find an example of a Logback configuration (file named https://git ​ - - + diff --git a/benchmarks/pom.xml b/benchmarks/pom.xml index 2ccd50c41..6a74d51b5 100644 --- a/benchmarks/pom.xml +++ b/benchmarks/pom.xml @@ -17,7 +17,7 @@ Benchmarks Benchmarks (JMH) org.springframework.cloud - 1.1.2.RELEASE + 1.1.3.BUILD-SNAPSHOT benchmarks diff --git a/docs/pom.xml b/docs/pom.xml index 80ce91ab7..9d5381169 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.2.RELEASE + 1.1.3.BUILD-SNAPSHOT spring-cloud-sleuth-docs pom diff --git a/pom.xml b/pom.xml index 410ef1f27..bd7538a61 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 spring-cloud-sleuth - 1.1.2.RELEASE + 1.1.3.BUILD-SNAPSHOT pom Spring Cloud Sleuth Spring Cloud Sleuth @@ -13,7 +13,7 @@ org.springframework.cloud spring-cloud-build - 1.2.2.RELEASE + 1.2.3.BUILD-SNAPSHOT @@ -229,10 +229,10 @@ 1.8 2.19.1 2.17 - 1.2.2.RELEASE - 1.1.7.RELEASE - Brooklyn.SR2 - 1.2.5.RELEASE + 1.2.3.BUILD-SNAPSHOT + 1.1.8.BUILD-SNAPSHOT + Brooklyn.BUILD-SNAPSHOT + 1.2.6.BUILD-SNAPSHOT diff --git a/spring-cloud-sleuth-core/pom.xml b/spring-cloud-sleuth-core/pom.xml index 4cee929a1..490852f8d 100644 --- a/spring-cloud-sleuth-core/pom.xml +++ b/spring-cloud-sleuth-core/pom.xml @@ -11,7 +11,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.2.RELEASE + 1.1.3.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-dependencies/pom.xml b/spring-cloud-sleuth-dependencies/pom.xml index 40825a40c..230101c05 100644 --- a/spring-cloud-sleuth-dependencies/pom.xml +++ b/spring-cloud-sleuth-dependencies/pom.xml @@ -5,11 +5,11 @@ spring-cloud-dependencies-parent org.springframework.cloud - 1.2.2.RELEASE + 1.2.3.BUILD-SNAPSHOT spring-cloud-sleuth-dependencies - 1.1.2.RELEASE + 1.1.3.BUILD-SNAPSHOT pom spring-cloud-sleuth-dependencies Spring Cloud Sleuth Dependencies diff --git a/spring-cloud-sleuth-samples/pom.xml b/spring-cloud-sleuth-samples/pom.xml index 691f0ffd1..1cf7bcb82 100644 --- a/spring-cloud-sleuth-samples/pom.xml +++ b/spring-cloud-sleuth-samples/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.2.RELEASE + 1.1.3.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml index c35176bf7..721ca2542 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml @@ -20,7 +20,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.2.RELEASE + 1.1.3.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml index 7651dd147..f27c33439 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.2.RELEASE + 1.1.3.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml index 9d4a2df6e..1634f92c4 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.2.RELEASE + 1.1.3.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml index c7477943a..f10cee8f1 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.2.RELEASE + 1.1.3.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml index 8e2785c00..ebe795557 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.2.RELEASE + 1.1.3.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml index 2d3cc74dd..8759ae06d 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.2.RELEASE + 1.1.3.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml index ca8c0f5a7..4188686a7 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.2.RELEASE + 1.1.3.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml index bc59fe621..e02baaee5 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml @@ -27,7 +27,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.2.RELEASE + 1.1.3.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml index e9549bd1a..98a4c5da3 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.2.RELEASE + 1.1.3.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-stream/pom.xml b/spring-cloud-sleuth-stream/pom.xml index d93cc1f2c..b47594ecf 100644 --- a/spring-cloud-sleuth-stream/pom.xml +++ b/spring-cloud-sleuth-stream/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.2.RELEASE + 1.1.3.BUILD-SNAPSHOT diff --git a/spring-cloud-sleuth-zipkin-stream/pom.xml b/spring-cloud-sleuth-zipkin-stream/pom.xml index 30357d814..68bda6625 100644 --- a/spring-cloud-sleuth-zipkin-stream/pom.xml +++ b/spring-cloud-sleuth-zipkin-stream/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.2.RELEASE + 1.1.3.BUILD-SNAPSHOT diff --git a/spring-cloud-sleuth-zipkin/pom.xml b/spring-cloud-sleuth-zipkin/pom.xml index 826362e9c..3d0c0c511 100644 --- a/spring-cloud-sleuth-zipkin/pom.xml +++ b/spring-cloud-sleuth-zipkin/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.2.RELEASE + 1.1.3.BUILD-SNAPSHOT .. diff --git a/spring-cloud-starter-sleuth/pom.xml b/spring-cloud-starter-sleuth/pom.xml index d13addc13..a8eb67393 100644 --- a/spring-cloud-starter-sleuth/pom.xml +++ b/spring-cloud-starter-sleuth/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.2.RELEASE + 1.1.3.BUILD-SNAPSHOT .. spring-cloud-starter-sleuth diff --git a/spring-cloud-starter-zipkin/pom.xml b/spring-cloud-starter-zipkin/pom.xml index 905c45902..70ab3fc49 100644 --- a/spring-cloud-starter-zipkin/pom.xml +++ b/spring-cloud-starter-zipkin/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.2.RELEASE + 1.1.3.BUILD-SNAPSHOT .. spring-cloud-starter-zipkin From 5930e15a9ea88375e93a95e22424802d492eb441 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Tue, 7 Feb 2017 17:34:45 +0100 Subject: [PATCH 208/235] Removing colour from the logging pattern\n\nwithout this change due to the presence of colours in the logging pattern, the logs could be corrupt in some cases\nwith this change we're removing the colour\n\nfixes #510 --- .../cloud/sleuth/autoconfig/TraceEnvironmentPostProcessor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/TraceEnvironmentPostProcessor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/TraceEnvironmentPostProcessor.java index 33a66b12c..408e3c311 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/TraceEnvironmentPostProcessor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/TraceEnvironmentPostProcessor.java @@ -51,7 +51,7 @@ public class TraceEnvironmentPostProcessor implements EnvironmentPostProcessor { // This doesn't work with all logging systems but it's a useful default so you see // traces in logs without having to configure it. map.put("logging.pattern.level", - "%clr(%5p) %clr([${spring.zipkin.service.name:${spring.application.name:-}},%X{X-B3-TraceId:-},%X{X-B3-SpanId:-},%X{X-Span-Export:-}]){yellow}"); + "%5p [${spring.zipkin.service.name:${spring.application.name:-}},%X{X-B3-TraceId:-},%X{X-B3-SpanId:-},%X{X-Span-Export:-}]"); map.put("spring.aop.proxyTargetClass", "true"); addOrReplace(environment.getPropertySources(), map); } From 11ef72369ca60b5f432ae94ed0f7c30e3290f70b Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Wed, 8 Feb 2017 10:39:44 +0100 Subject: [PATCH 209/235] Updated the docs with logging pattern --- docs/src/main/asciidoc/README.adoc | 4 ++-- docs/src/main/asciidoc/features.adoc | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/src/main/asciidoc/README.adoc b/docs/src/main/asciidoc/README.adoc index 59f73ccdd..8bb8ecba9 100644 --- a/docs/src/main/asciidoc/README.adoc +++ b/docs/src/main/asciidoc/README.adoc @@ -1,7 +1,7 @@ :jdkversion: 1.8 -image::https://circleci.com/gh/spring-cloud/spring-cloud-sleuth/tree/1.0.x.svg?style=svg["CircleCI", link="https://circleci.com/gh/spring-cloud/spring-cloud-sleuth/tree/1.0.x"] -image::https://codecov.io/gh/spring-cloud/spring-cloud-sleuth/branch/1.0.x/graph/badge.svg["codecov", link="https://codecov.io/gh/spring-cloud/spring-cloud-sleuth"] +image::https://circleci.com/gh/spring-cloud/spring-cloud-sleuth/tree/1.1.x.svg?style=svg["CircleCI", link="https://circleci.com/gh/spring-cloud/spring-cloud-sleuth/tree/1.1.x"] +image::https://codecov.io/gh/spring-cloud/spring-cloud-sleuth/branch/1.1.x/graph/badge.svg["codecov", link="https://codecov.io/gh/spring-cloud/spring-cloud-sleuth"] image::https://badges.gitter.im/spring-cloud/spring-cloud-sleuth.svg[Gitter, link="https://gitter.im/spring-cloud/spring-cloud-sleuth?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge"] == Spring Cloud Sleuth diff --git a/docs/src/main/asciidoc/features.adoc b/docs/src/main/asciidoc/features.adoc index f702a5058..b25a3c3d6 100644 --- a/docs/src/main/asciidoc/features.adoc +++ b/docs/src/main/asciidoc/features.adoc @@ -49,6 +49,6 @@ IMPORTANT: If using Zipkin or Stream, configure the percentage of spans exported NOTE: the SLF4J MDC is always set and logback users will immediately see the trace and span ids in logs per the example above. Other logging systems have to configure their own formatter to get the same result. The default is - `logging.pattern.level` set to `%clr(%5p) %clr([${spring.application.name:},%X{X-B3-TraceId:-},%X{X-B3-SpanId:-},%X{X-Span-Export:-}]){yellow}` + `logging.pattern.level` set to `%5p [${spring.zipkin.service.name:${spring.application.name:-}},%X{X-B3-TraceId:-},%X{X-B3-SpanId:-},%X{X-Span-Export:-}]` (this is a Spring Boot feature for logback users). *This means that if you're not using SLF4J this pattern WILL NOT be automatically applied*. \ No newline at end of file From 7f6dda71793b5d0658b3ec3be39f9112151808bd Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Mon, 13 Feb 2017 10:45:59 +0100 Subject: [PATCH 210/235] Lack of ErrorController will not result in blowing up the application without this change when someone has a missing ErrorController bean then TraceHandlerInterceptor blows up with this change that problem gets fixed fixes #512 --- .../web/TraceHandlerInterceptor.java | 9 +++- .../web/TraceHandlerInterceptorTests.java | 53 +++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptorTests.java diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptor.java index 69d7fb5c9..f0e5438c4 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptor.java @@ -23,6 +23,7 @@ import javax.servlet.http.HttpServletResponse; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.boot.autoconfigure.web.ErrorController; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.TraceKeys; @@ -188,7 +189,13 @@ public class TraceHandlerInterceptor extends HandlerInterceptorAdapter { private ErrorController getErrorController() { if (this.errorController == null) { - this.errorController = this.beanFactory.getBean(ErrorController.class); + try { + this.errorController = this.beanFactory.getBean(ErrorController.class); + } catch (NoSuchBeanDefinitionException e) { + if (log.isTraceEnabled()) { + log.trace("ErrorController bean not found"); + } + } } return this.errorController; } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptorTests.java new file mode 100644 index 000000000..9130095d9 --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptorTests.java @@ -0,0 +1,53 @@ +/* + * Copyright 2013-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.sleuth.instrument.web; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.BDDMockito; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.boot.autoconfigure.web.ErrorController; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; + +/** + * @author Marcin Grzejszczak + */ +@RunWith(MockitoJUnitRunner.class) +public class TraceHandlerInterceptorTests { + + @Mock BeanFactory beanFactory; + @InjectMocks TraceHandlerInterceptor traceHandlerInterceptor; + + @Test + public void should_not_blow_up_when_there_is_no_error_controller() throws Exception { + BDDMockito.given(this.beanFactory.getBean(ErrorController.class)).willThrow(new NoSuchBeanDefinitionException("errorController")); + HttpServletRequest request = new MockHttpServletRequest(); + HttpServletResponse response = new MockHttpServletResponse(); + Object handler = new Object(); + + this.traceHandlerInterceptor.afterCompletion(request, response, handler, null); + } + +} \ No newline at end of file From 694b929db376b675a3c5f1904ca5cee7ae234a35 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Mon, 13 Feb 2017 11:24:44 +0100 Subject: [PATCH 211/235] Caches the result of getting error controller bean related to #512 --- .../web/TraceHandlerInterceptor.java | 12 +++++--- .../web/TraceHandlerInterceptorTests.java | 29 ++++++++++++------- 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptor.java index f0e5438c4..5f9054d9f 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptor.java @@ -17,6 +17,7 @@ package org.springframework.cloud.sleuth.instrument.web; import java.lang.invoke.MethodHandles; +import java.util.concurrent.atomic.AtomicReference; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -54,7 +55,7 @@ public class TraceHandlerInterceptor extends HandlerInterceptorAdapter { private Tracer tracer; private TraceKeys traceKeys; - private ErrorController errorController; + private AtomicReference errorController; public TraceHandlerInterceptor(BeanFactory beanFactory) { this.beanFactory = beanFactory; @@ -187,16 +188,19 @@ public class TraceHandlerInterceptor extends HandlerInterceptorAdapter { return this.traceKeys; } - private ErrorController getErrorController() { + ErrorController getErrorController() { if (this.errorController == null) { try { - this.errorController = this.beanFactory.getBean(ErrorController.class); + ErrorController errorController = this.beanFactory.getBean(ErrorController.class); + this.errorController = new AtomicReference<>(errorController); } catch (NoSuchBeanDefinitionException e) { if (log.isTraceEnabled()) { log.trace("ErrorController bean not found"); } + this.errorController = new AtomicReference<>(); } } - return this.errorController; + return this.errorController.get(); } + } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptorTests.java index 9130095d9..dc761d7d2 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptorTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptorTests.java @@ -16,9 +16,6 @@ package org.springframework.cloud.sleuth.instrument.web; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.BDDMockito; @@ -28,8 +25,10 @@ import org.mockito.runners.MockitoJUnitRunner; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.boot.autoconfigure.web.ErrorController; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; + +import static org.assertj.core.api.BDDAssertions.then; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.only; /** * @author Marcin Grzejszczak @@ -41,13 +40,21 @@ public class TraceHandlerInterceptorTests { @InjectMocks TraceHandlerInterceptor traceHandlerInterceptor; @Test - public void should_not_blow_up_when_there_is_no_error_controller() throws Exception { - BDDMockito.given(this.beanFactory.getBean(ErrorController.class)).willThrow(new NoSuchBeanDefinitionException("errorController")); - HttpServletRequest request = new MockHttpServletRequest(); - HttpServletResponse response = new MockHttpServletResponse(); - Object handler = new Object(); + public void should_cache_the_retrieved_bean_when_exception_took_place() throws Exception { + given(this.beanFactory.getBean(ErrorController.class)).willThrow(new NoSuchBeanDefinitionException("errorController")); - this.traceHandlerInterceptor.afterCompletion(request, response, handler, null); + then(this.traceHandlerInterceptor.getErrorController()).isNull(); + then(this.traceHandlerInterceptor.getErrorController()).isNull(); + BDDMockito.then(this.beanFactory).should(only()).getBean(ErrorController.class); + } + + @Test + public void should_cache_the_retrieved_bean_when_no_exception_took_place() throws Exception { + given(this.beanFactory.getBean(ErrorController.class)).willReturn(() -> null); + + then(this.traceHandlerInterceptor.getErrorController()).isNotNull(); + then(this.traceHandlerInterceptor.getErrorController()).isNotNull(); + BDDMockito.then(this.beanFactory).should(only()).getBean(ErrorController.class); } } \ No newline at end of file From 4d8d0dae1fc4b2c050c01a8d2a43a72974cc9b1e Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Wed, 15 Feb 2017 10:34:39 +0100 Subject: [PATCH 212/235] Updated libs fixes #517 --- pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index bd7538a61..16883a7c1 100644 --- a/pom.xml +++ b/pom.xml @@ -204,19 +204,19 @@ com.github.tomakehurst wiremock - 2.0.6-beta + 2.5.1 test pl.pragmatists JUnitParams - 1.0.4 + 1.0.6 test org.assertj assertj-core - 3.5.2 + 3.6.2 test From 49484986645a52007728da93c2c84ad39d994768 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Wed, 15 Feb 2017 13:53:47 +0100 Subject: [PATCH 213/235] Ensured that we guard against negative ports fixes #518 --- .../sleuth/stream/ServerPropertiesHostLocator.java | 2 +- .../stream/ServerPropertiesHostLocatorTests.java | 11 +++++++++++ .../zipkin/ServerPropertiesEndpointLocator.java | 2 +- .../zipkin/ServerPropertiesEndpointLocatorTests.java | 11 +++++++++++ 4 files changed, 24 insertions(+), 2 deletions(-) diff --git a/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/ServerPropertiesHostLocator.java b/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/ServerPropertiesHostLocator.java index 1a9c90f4d..2bc0c1c3b 100644 --- a/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/ServerPropertiesHostLocator.java +++ b/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/ServerPropertiesHostLocator.java @@ -89,7 +89,7 @@ public class ServerPropertiesHostLocator implements HostLocator { return this.port; } Integer port; - if (this.serverProperties != null && this.serverProperties.getPort() != null) { + if (this.serverProperties != null && this.serverProperties.getPort() != null && this.serverProperties.getPort() > 0) { port = this.serverProperties.getPort(); } else { diff --git a/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/ServerPropertiesHostLocatorTests.java b/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/ServerPropertiesHostLocatorTests.java index 22ca4ea5c..6c3cc0ae9 100644 --- a/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/ServerPropertiesHostLocatorTests.java +++ b/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/ServerPropertiesHostLocatorTests.java @@ -89,6 +89,17 @@ public class ServerPropertiesHostLocatorTests { assertThat(locator.locate(this.span).getServiceName()).isEqualTo("foo"); } + @Test + public void negativePortFromServerProperties() throws UnknownHostException { + ServerProperties properties = new ServerProperties(); + properties.setPort(-1); + + ServerPropertiesHostLocator locator = new ServerPropertiesHostLocator(properties, + "unknown", new ZipkinProperties(),localAddress(ADR1234)); + + assertThat(locator.locate(this.span).getPort()).isEqualTo((short) 8080); + } + private InetUtils localAddress(byte[] address) throws UnknownHostException { InetUtils mocked = Mockito.spy(new InetUtils(new InetUtilsProperties())); Mockito.when(mocked.findFirstNonLoopbackAddress()) diff --git a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ServerPropertiesEndpointLocator.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ServerPropertiesEndpointLocator.java index 8749914a9..2cb6c11b7 100644 --- a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ServerPropertiesEndpointLocator.java +++ b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ServerPropertiesEndpointLocator.java @@ -94,7 +94,7 @@ public class ServerPropertiesEndpointLocator implements EndpointLocator { return this.port; } Integer port; - if (this.serverProperties!=null && this.serverProperties.getPort() != null) { + if (this.serverProperties!=null && this.serverProperties.getPort() != null && this.serverProperties.getPort() > 0) { port = this.serverProperties.getPort(); } else { diff --git a/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/ServerPropertiesEndpointLocatorTests.java b/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/ServerPropertiesEndpointLocatorTests.java index 92304542c..46e826eb6 100644 --- a/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/ServerPropertiesEndpointLocatorTests.java +++ b/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/ServerPropertiesEndpointLocatorTests.java @@ -83,6 +83,17 @@ public class ServerPropertiesEndpointLocatorTests { assertThat(locator.local().serviceName).isEqualTo("foo"); } + @Test + public void negativePortFromServerProperties() throws UnknownHostException { + ServerProperties properties = new ServerProperties(); + properties.setPort(-1); + + ServerPropertiesEndpointLocator locator = new ServerPropertiesEndpointLocator( + properties, "unknown", new ZipkinProperties(),localAddress(ADDRESS1234)); + + assertThat(locator.local().port).isEqualTo((short) 8080); + } + private InetUtils localAddress(byte[] address) throws UnknownHostException { InetUtils mocked = Mockito.spy(new InetUtils(new InetUtilsProperties())); Mockito.when(mocked.findFirstNonLoopbackAddress()) From f8e6b00f35da038c26af32b143504816dc201c16 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Wed, 15 Feb 2017 21:26:02 +0100 Subject: [PATCH 214/235] Updated the version of the deprecated headers fixes #519 --- .../sleuth/instrument/messaging/MessagingSpanExtractor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 8b14098f6..054332f28 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 @@ -53,7 +53,7 @@ class MessagingSpanExtractor implements SpanExtractor> { } 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 " + log.warn("Deprecated trace headers detected. Please upgrade Sleuth to 1.2 " + "or start sending headers present in the TraceMessageHeaders class"); return extractSpanFromOldHeaders(carrier, Span.builder()); } From 6f0a77cb1f8e6da7a96fcf227b683676fbaa9f7e Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Tue, 21 Feb 2017 18:01:00 +0100 Subject: [PATCH 215/235] Fixed the missing surefire plugin --- pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/pom.xml b/pom.xml index 16883a7c1..c3bfde101 100644 --- a/pom.xml +++ b/pom.xml @@ -362,6 +362,7 @@ org.apache.maven.plugins maven-surefire-plugin + ${surefire.plugin.version} ${surefireArgLine} From c202f53a85f2f24e53b205c59bcfa6c4f17cf463 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Thu, 23 Feb 2017 15:56:29 +0100 Subject: [PATCH 216/235] Showing a workaround to make message serializable this issue is there in core of Spring Framework (https://jira.spring.io/browse/SPR-15262) but this tests shows how to apply a workaround to get ridd of that problem until we bump up version of SF. related to https://github.com/spring-cloud/spring-cloud-sleuth/issues/520 and https://github.com/spring-cloud/spring-cloud-sleuth/issues/523 SO - http://stackoverflow.com/questions/42277170/messagedeliveryexception-thrown-when-using-amqp-backed-channel-with-brixton-sr7 fixes #520 --- .../TraceChannelInterceptorTests.java | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) 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 e80bc69f2..0cf2c2e83 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 @@ -42,11 +42,16 @@ import org.springframework.integration.channel.DirectChannel; import org.springframework.integration.core.MessagingTemplate; import org.springframework.integration.support.MessageBuilder; import org.springframework.messaging.Message; +import org.springframework.messaging.MessageChannel; import org.springframework.messaging.MessageHandler; import org.springframework.messaging.MessagingException; +import org.springframework.messaging.support.ChannelInterceptor; +import org.springframework.messaging.support.ChannelInterceptorAdapter; +import org.springframework.messaging.support.GenericMessage; import org.springframework.messaging.support.MessageHeaderAccessor; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.util.SerializationUtils; import static org.junit.Assert.assertNotNull; import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; @@ -314,6 +319,28 @@ public class TraceChannelInterceptorTests implements MessageHandler { then(TestSpanContextHolder.getCurrentSpan()).isNull(); } + @Test + public void serializeMutableHeaders() throws Exception { + Map headers = new HashMap<>(); + headers.put("foo", "bar"); + Message message = new GenericMessage<>("test", headers); + ChannelInterceptor immutableMessageInterceptor = new ChannelInterceptorAdapter() { + @Override + public Message preSend(Message message, MessageChannel channel) { + MessageHeaderAccessor headers = MessageHeaderAccessor.getMutableAccessor(message); + return new GenericMessage(message.getPayload(), headers.toMessageHeaders()); + } + }; + this.tracedChannel.addInterceptor(immutableMessageInterceptor); + + this.tracedChannel.send(message); + + Message output = (Message) SerializationUtils.deserialize(SerializationUtils.serialize(this.message)); + then(output.getPayload()).isEqualTo("test"); + then(output.getHeaders().get("foo")).isEqualTo("bar"); + this.tracedChannel.removeInterceptor(immutableMessageInterceptor); + } + @Configuration @EnableAutoConfiguration static class App { From e773ef452420bb0176253bff3191141660053fbe Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Fri, 24 Feb 2017 11:53:09 +0100 Subject: [PATCH 217/235] Limiting the span name size to 50 chars without this exceptions can occur when the span name is big with this we're truncating the name to 50 chars fixes #530 --- .../main/asciidoc/spring-cloud-sleuth.adoc | 4 ++ .../AbstractTraceChannelInterceptor.java | 3 +- .../AbstractTraceHttpRequestInterceptor.java | 7 +++- .../web/client/feign/TraceFeignClient.java | 3 +- .../cloud/sleuth/trace/DefaultTracer.java | 18 +++++++-- .../cloud/sleuth/util/SpanNameUtil.java | 14 ++++++- .../TraceChannelInterceptorTests.java | 38 +++++++++++++++---- .../TraceRestTemplateInterceptorTests.java | 23 +++++++++++ .../client/feign/TraceFeignClientTests.java | 17 +++++++++ .../sleuth/trace/DefaultTracerTests.java | 30 ++++++++++++++- .../cloud/sleuth/util/SpanNameUtilTests.java | 21 ++++++++++ 11 files changed, 161 insertions(+), 17 deletions(-) diff --git a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc index 6f07f6a07..50707514e 100644 --- a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc +++ b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc @@ -103,6 +103,10 @@ was a span present in this thread then it would become the parent of that span. IMPORTANT: Always clean after you create a span! Don't forget to close a span if you want to send it to Zipkin. +IMPORTANT: If your span contains a name greater than 50 chars, then that name will +be truncated to 50 chars. Your names have to be explicit and concrete. Big names lead to +latency issues and sometimes even thrown exceptions. + === Continuing spans [[continuing-spans]] Sometimes you don't want to create a new span but you want to continue one. Example of such a diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/AbstractTraceChannelInterceptor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/AbstractTraceChannelInterceptor.java index df22ff6e8..a382413a1 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/AbstractTraceChannelInterceptor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/AbstractTraceChannelInterceptor.java @@ -9,6 +9,7 @@ import org.springframework.cloud.sleuth.SpanExtractor; import org.springframework.cloud.sleuth.SpanInjector; import org.springframework.cloud.sleuth.TraceKeys; import org.springframework.cloud.sleuth.Tracer; +import org.springframework.cloud.sleuth.util.SpanNameUtil; import org.springframework.integration.channel.AbstractMessageChannel; import org.springframework.integration.context.IntegrationObjectSupport; import org.springframework.messaging.Message; @@ -96,7 +97,7 @@ abstract class AbstractTraceChannelInterceptor extends ChannelInterceptorAdapter } String getMessageChannelName(MessageChannel channel) { - return MESSAGE_COMPONENT + ":" + getChannelName(channel); + return SpanNameUtil.shorten(MESSAGE_COMPONENT + ":" + getChannelName(channel)); } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/AbstractTraceHttpRequestInterceptor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/AbstractTraceHttpRequestInterceptor.java index 413cf88f7..f8df9b183 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/AbstractTraceHttpRequestInterceptor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/AbstractTraceHttpRequestInterceptor.java @@ -25,6 +25,7 @@ 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.SpanNameUtil; import org.springframework.http.HttpRequest; /** * Abstraction over classes that interact with Http requests. Allows you @@ -54,7 +55,7 @@ abstract class AbstractTraceHttpRequestInterceptor { */ protected void publishStartEvent(HttpRequest request) { URI uri = request.getURI(); - String spanName = uriScheme(uri) + ":" + uri.getPath(); + String spanName = getName(uri); Span newSpan = this.tracer.createSpan(spanName); this.spanInjector.inject(newSpan, request); addRequestTags(request); @@ -64,6 +65,10 @@ abstract class AbstractTraceHttpRequestInterceptor { } } + private String getName(URI uri) { + return SpanNameUtil.shorten(uriScheme(uri) + ":" + uri.getPath()); + } + private String uriScheme(URI uri) { return uri.getScheme() == null ? "http" : uri.getScheme(); } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClient.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClient.java index 3de4e78a9..41f721087 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClient.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClient.java @@ -29,6 +29,7 @@ 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 org.springframework.cloud.sleuth.util.SpanNameUtil; import feign.Client; import feign.Request; @@ -100,7 +101,7 @@ class TraceFeignClient implements Client { private String getSpanName(Request request) { URI uri = URI.create(request.url()); - return uriScheme(uri) + ":" + uri.getPath(); + return SpanNameUtil.shorten(uriScheme(uri) + ":" + uri.getPath()); } private String uriScheme(URI uri) { 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 f9dc325b8..b5728856a 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 @@ -28,6 +28,7 @@ import org.springframework.cloud.sleuth.TraceRunnable; import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.log.SpanLogger; import org.springframework.cloud.sleuth.util.ExceptionUtils; +import org.springframework.cloud.sleuth.util.SpanNameUtil; /** * Default implementation of {@link Tracer} @@ -37,6 +38,8 @@ import org.springframework.cloud.sleuth.util.ExceptionUtils; */ public class DefaultTracer implements Tracer { + private static final int MAX_CHARS_IN_SPAN_NAME = 50; + private final Sampler defaultSampler; private final Random random; @@ -79,13 +82,14 @@ public class DefaultTracer implements Tracer { @Override public Span createSpan(String name, Sampler sampler) { + String shortenedName = SpanNameUtil.shorten(name); Span span; if (isTracing()) { - span = createChild(getCurrentSpan(), name); + span = createChild(getCurrentSpan(), shortenedName); } else { long id = createId(); - span = Span.builder().name(name) + span = Span.builder().name(shortenedName) .traceIdHigh(this.traceId128 ? createId() : 0L) .traceId(id) .spanId(id).build(); @@ -98,6 +102,11 @@ public class DefaultTracer implements Tracer { return continueSpan(span); } + private String shortenNameIfNecessary(String name) { + int maxLength = name.length() > MAX_CHARS_IN_SPAN_NAME ? MAX_CHARS_IN_SPAN_NAME : name.length(); + return name.substring(0, maxLength); + } + @Override public Span detach(Span span) { if (span == null) { @@ -148,9 +157,10 @@ public class DefaultTracer implements Tracer { } protected Span createChild(Span parent, String name) { + String shortenedName = SpanNameUtil.shorten(name); long id = createId(); if (parent == null) { - Span span = Span.builder().name(name) + Span span = Span.builder().name(shortenedName) .traceIdHigh(this.traceId128 ? createId() : 0L) .traceId(id) .spanId(id).build(); @@ -162,7 +172,7 @@ public class DefaultTracer implements Tracer { if (!isTracing()) { SpanContextHolder.push(parent, true); } - Span span = Span.builder().name(name) + Span span = Span.builder().name(shortenedName) .traceIdHigh(parent.getTraceIdHigh()) .traceId(parent.getTraceId()).parent(parent.getSpanId()).spanId(id) .processId(parent.getProcessId()).savedSpan(parent) diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/util/SpanNameUtil.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/util/SpanNameUtil.java index 78ef5ec7d..1aef47d1c 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/util/SpanNameUtil.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/util/SpanNameUtil.java @@ -16,6 +16,8 @@ package org.springframework.cloud.sleuth.util; +import org.springframework.util.StringUtils; + /** * Utility class that provides the name in hyphen based notation * @@ -24,6 +26,16 @@ package org.springframework.cloud.sleuth.util; */ public final class SpanNameUtil { + static final int MAX_NAME_LENGTH = 50; + + public static String shorten(String name) { + if (StringUtils.isEmpty(name)) { + return name; + } + int maxLength = name.length() > MAX_NAME_LENGTH ? MAX_NAME_LENGTH : name.length(); + return name.substring(0, maxLength); + } + public static String toLowerHyphen(String name) { StringBuilder result = new StringBuilder(); for (int i = 0; i < name.length(); i++) { @@ -35,6 +47,6 @@ public final class SpanNameUtil { result.append(c); } } - return result.toString(); + return SpanNameUtil.shorten(result.toString()); } } 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 0cf2c2e83..2ec60cbea 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 @@ -135,8 +135,8 @@ 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()); + .setHeader(TraceMessageHeaders.TRACE_ID_NAME, Span.idToHex(10L)) + .setHeader(TraceMessageHeaders.SPAN_ID_NAME, Span.idToHex(20L)).build()); then(this.message).isNotNull(); String spanId = this.message.getHeaders().get(Span.SPAN_ID_NAME, String.class); @@ -199,10 +199,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(); } @@ -231,10 +231,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(); } @@ -248,10 +248,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(); } @@ -319,6 +319,28 @@ public class TraceChannelInterceptorTests implements MessageHandler { then(TestSpanContextHolder.getCurrentSpan()).isNull(); } + @Test + public void shouldShortenTheNameWhenItsTooLarge() { + this.tracedChannel.send(MessageBuilder.withPayload("hi") + .setHeader(TraceMessageHeaders.SPAN_NAME_NAME, bigName()) + .setHeader(TraceMessageHeaders.TRACE_ID_NAME, Span.idToHex(10L)) + .setHeader(TraceMessageHeaders.SPAN_ID_NAME, Span.idToHex(20L)).build()); + + then(this.message).isNotNull(); + + then(this.accumulator.getSpans()).isNotEmpty(); + this.accumulator.getSpans().forEach(span1 -> then(span1.getName().length()).isLessThanOrEqualTo(50)); + then(TestSpanContextHolder.getCurrentSpan()).isNull(); + } + + private String bigName() { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < 60; i++) { + sb.append("a"); + } + return sb.toString(); + } + @Test public void serializeMutableHeaders() throws Exception { Map headers = new HashMap<>(); diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptorTests.java index dc9e58b3b..179ec2fea 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptorTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptorTests.java @@ -35,6 +35,7 @@ import org.springframework.cloud.sleuth.sampler.AlwaysSampler; import org.springframework.cloud.sleuth.trace.DefaultTracer; import org.springframework.cloud.sleuth.trace.TestSpanContextHolder; import org.springframework.cloud.sleuth.util.ArrayListSpanAccumulator; +import org.springframework.cloud.sleuth.util.ExceptionUtils; import org.springframework.http.HttpHeaders; import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.test.web.client.MockMvcClientHttpRequestFactory; @@ -152,6 +153,28 @@ public class TraceRestTemplateInterceptorTests { then(this.testController.span).hasNameEqualTo("http:/"); } + @Test + public void willShortenTheNameOfTheSpan() { + this.tracer.continueSpan(Span.builder().traceId(1L).spanId(2L).exportable(false).build()); + + try { + this.template.getForEntity("/" + bigName(), Map.class).getBody(); + } catch (Exception e) { + + } + + then(this.spanAccumulator.getSpans().get(0).getName()).hasSize(50); + then(ExceptionUtils.getLastException()).isNull(); + } + + private String bigName() { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < 60; i++) { + sb.append("a"); + } + return sb.toString(); + } + @RestController public class TestController { diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientTests.java index fd1476e5c..ea781cd0c 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientTests.java @@ -101,4 +101,21 @@ public class TraceFeignClientTests { .hasATag(Span.SPAN_ERROR_TAG_NAME, "exception has occurred"); } + @Test + public void should_shorten_the_span_name() throws IOException { + this.traceFeignClient.execute( + Request.create("GET", "http://foo/" + bigName(), new HashMap<>(), "".getBytes(), + Charset.defaultCharset()), new Request.Options()); + + then(this.spanAccumulator.getSpans().get(0).getName()).hasSize(50); + } + + private String bigName() { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < 60; i++) { + sb.append("a"); + } + return sb.toString(); + } + } \ No newline at end of file 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 a17ef1959..8bcd56b0f 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 @@ -67,7 +67,6 @@ public class DefaultTracerTests { @Test public void tracingWorks() { - DefaultTracer tracer = new DefaultTracer(NeverSampler.INSTANCE, new Random(), new DefaultSpanNamer(), this.spanLogger, this.spanReporter); @@ -186,6 +185,35 @@ public class DefaultTracerTests { tracer.close(span); } + @Test + public void shouldCreateNewSpanWithShortenedName() { + DefaultTracer tracer = new DefaultTracer(new AlwaysSampler(), new Random(), + this.spanNamer, this.spanLogger, this.spanReporter); + Span span = tracer.createSpan(bigName()); + + then(span.getName().length()).isEqualTo(50); + tracer.close(span); + } + + @Test + public void shouldCreateChildOfSpanWithShortenedName() { + DefaultTracer tracer = new DefaultTracer(new AlwaysSampler(), new Random(), + this.spanNamer, this.spanLogger, this.spanReporter); + Span span = Span.builder().name(bigName()).traceId(1L).spanId(1L).build(); + + Span child = tracer.createChild(span, bigName()); + + then(child.getName().length()).isEqualTo(50); + } + + private String bigName() { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < 60; i++) { + sb.append("a"); + } + return sb.toString(); + } + private Span assertSpan(List spans, Long parentId, String name) { List found = findSpans(spans, parentId); assertThat(found).as("More than one span with parentId %s", parentId).hasSize(1); diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/util/SpanNameUtilTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/util/SpanNameUtilTests.java index 517576f2b..d531691ef 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/util/SpanNameUtilTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/util/SpanNameUtilTests.java @@ -32,4 +32,25 @@ public class SpanNameUtilTests { SleuthAssertions.then(SpanNameUtil.toLowerHyphen("MySuperClassName")) .isEqualTo("my-super-class-name"); } + + @Test + public void should_not_shorten_a_name_that_is_below_max_threshold() throws Exception { + SleuthAssertions.then(SpanNameUtil.shorten("someName")) + .isEqualTo("someName"); + } + + @Test + public void should_not_shorten_a_name_that_is_null() throws Exception { + SleuthAssertions.then(SpanNameUtil.shorten(null)).isNull(); + } + + @Test + public void should_shorten_a_name_that_is_above_max_threshold() throws Exception { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < 60; i++) { + sb.append("a"); + } + SleuthAssertions.then(SpanNameUtil.shorten(sb.toString()).length()) + .isEqualTo(SpanNameUtil.MAX_NAME_LENGTH); + } } \ No newline at end of file From 90713eba3443d7c7aa8c3b8a0cbd4318b3229d6a Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Fri, 24 Feb 2017 16:58:27 +0100 Subject: [PATCH 218/235] Added support for Feign + OkHttpClient + Sleuth without this change we get exceptions related to not being able to extend a final class with this change we're not wrapping any final types and we have a custom bean post processor for okhttp fixes #513 --- .../main/asciidoc/spring-cloud-sleuth.adoc | 2 +- spring-cloud-sleuth-core/pom.xml | 5 ++ .../sleuth/instrument/web/TraceWebAspect.java | 5 +- .../OkHttpFeignClientBeanPostProcessor.java | 62 +++++++++++++++++++ .../web/client/feign/TraceFeignAspect.java | 2 +- .../TraceFeignClientAutoConfiguration.java | 11 ++++ .../feign/issues/issue393/Issue393Tests.java | 8 +++ 7 files changed, 90 insertions(+), 5 deletions(-) create mode 100644 spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/OkHttpFeignClientBeanPostProcessor.java diff --git a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc index 50707514e..b2219fb10 100644 --- a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc +++ b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc @@ -594,7 +594,7 @@ subscribe events. To disable Spring Integration instrumentation, set `spring.sle 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. +in Spring Cloud Sleuth 1.2 we will remove the support for the deprecated headers. Since 1.0.4 you can provide the `spring.sleuth.integration.patterns` pattern to explicitly provide the names of channels that you want to include for tracing. By default all channels diff --git a/spring-cloud-sleuth-core/pom.xml b/spring-cloud-sleuth-core/pom.xml index 490852f8d..422c4855c 100644 --- a/spring-cloud-sleuth-core/pom.xml +++ b/spring-cloud-sleuth-core/pom.xml @@ -85,6 +85,11 @@ spring-data-rest-webmvc true + + io.github.openfeign + feign-okhttp + true + org.aspectj aspectjrt diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAspect.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAspect.java index 7e7ce9182..bcf842bd0 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAspect.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAspect.java @@ -33,12 +33,12 @@ import org.springframework.web.context.request.async.WebAsyncTask; * Aspect that adds tracing to *

    *

      - *
    • {@link org.springframework.web.bind.annotation.RestController} annotated classes + *
    • {@code RestController} annotated classes * with public {@link Callable} methods
    • *
    • {@link org.springframework.stereotype.Controller} annotated classes with public * {@link Callable} methods
    • *
    • {@link org.springframework.stereotype.Controller} or - * {@link org.springframework.web.bind.annotation.RestController} annotated classes with + * {@code RestController} annotated classes with * public {@link WebAsyncTask} methods
    • *
    *

    @@ -56,7 +56,6 @@ import org.springframework.web.context.request.async.WebAsyncTask; * @author Spencer Gibb * @since 1.0.0 * - * @see org.springframework.web.bind.annotation.RestController * @see org.springframework.stereotype.Controller * @see org.springframework.web.client.RestOperations * @see org.springframework.cloud.sleuth.TraceCallable diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/OkHttpFeignClientBeanPostProcessor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/OkHttpFeignClientBeanPostProcessor.java new file mode 100644 index 000000000..9379a26de --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/OkHttpFeignClientBeanPostProcessor.java @@ -0,0 +1,62 @@ +/* + * Copyright 2013-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.sleuth.instrument.web.client.feign; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.config.BeanPostProcessor; + +import feign.okhttp.OkHttpClient; + +/** + * Post processor that wraps takes care of the OkHttp Feign Client instrumentation + * + * @author Marcin Grzejszczak + * + * @since 1.1.3 + */ +final class OkHttpFeignClientBeanPostProcessor implements BeanPostProcessor { + + private final BeanFactory beanFactory; + private TraceFeignObjectWrapper traceFeignObjectWrapper; + + OkHttpFeignClientBeanPostProcessor(BeanFactory beanFactory) { + this.beanFactory = beanFactory; + } + + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) + throws BeansException { + if (bean instanceof OkHttpClient) { + return getTraceFeignObjectWrapper().wrap(bean); + } + return bean; + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) + throws BeansException { + return bean; + } + + private TraceFeignObjectWrapper getTraceFeignObjectWrapper() { + if (this.traceFeignObjectWrapper == null) { + this.traceFeignObjectWrapper = this.beanFactory.getBean(TraceFeignObjectWrapper.class); + } + return this.traceFeignObjectWrapper; + } +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignAspect.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignAspect.java index 2a8dd6395..8f0dcd8f5 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignAspect.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignAspect.java @@ -39,7 +39,7 @@ class TraceFeignAspect { this.beanFactory = beanFactory; } - @Around("execution (* feign.Client.*(..))") + @Around("execution (* feign.Client.*(..)) && !within(is(FinalType))") public Object feignClientWasCalled(final ProceedingJoinPoint pjp) throws Throwable { Object[] args = pjp.getArgs(); Request request = (Request) args[0]; diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientAutoConfiguration.java index b42fbf584..6c866afa3 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientAutoConfiguration.java @@ -33,6 +33,7 @@ import org.springframework.context.annotation.Scope; import feign.Client; import feign.Feign; +import feign.okhttp.OkHttpClient; /** * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration @@ -75,6 +76,16 @@ public class TraceFeignClientAutoConfiguration { } } + @Configuration + @ConditionalOnClass(OkHttpClient.class) + protected static class OkHttpClientFeignBeanPostProcessorConfiguration { + + @Bean + OkHttpFeignClientBeanPostProcessor okHttpFeignClientBeanPostProcessor(BeanFactory beanFactory) { + return new OkHttpFeignClientBeanPostProcessor(beanFactory); + } + } + @Bean TraceFeignObjectWrapper traceFeignObjectWrapper(BeanFactory beanFactory) { return new TraceFeignObjectWrapper(beanFactory); diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue393/Issue393Tests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue393/Issue393Tests.java index 7e1387951..2ecd45ed0 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue393/Issue393Tests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue393/Issue393Tests.java @@ -40,6 +40,8 @@ import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; +import feign.okhttp.OkHttpClient; + import static org.assertj.core.api.BDDAssertions.then; /** @@ -88,6 +90,12 @@ class Application { return new DemoController(myNameRemote); } + // issue #513 + @Bean + public OkHttpClient myOkHttpClient() { + return new OkHttpClient(); + } + @Bean public feign.Logger.Level feignLoggerLevel() { return feign.Logger.Level.BASIC; From 8c17451e60d4d4b6a1d322f66158cc5f71bd1223 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Fri, 24 Feb 2017 17:15:37 +0100 Subject: [PATCH 219/235] Added missing new trace headers without this change for kafka the new message headers are not passed with this change we're finally fixing this fixes #529 --- .../sleuth/stream/StreamEnvironmentPostProcessor.java | 5 ++++- .../stream/StreamEnvironmentPostProcessorTests.java | 8 ++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/StreamEnvironmentPostProcessor.java b/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/StreamEnvironmentPostProcessor.java index d735ac7ad..7b013f0bd 100644 --- a/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/StreamEnvironmentPostProcessor.java +++ b/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/StreamEnvironmentPostProcessor.java @@ -26,6 +26,7 @@ import java.util.Properties; import org.springframework.boot.SpringApplication; import org.springframework.boot.env.EnvironmentPostProcessor; import org.springframework.cloud.sleuth.Span; +import org.springframework.cloud.sleuth.instrument.messaging.TraceMessageHeaders; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.MapPropertySource; import org.springframework.core.env.MutablePropertySources; @@ -48,7 +49,9 @@ public class StreamEnvironmentPostProcessor implements EnvironmentPostProcessor private static final String PROPERTY_SOURCE_NAME = "defaultProperties"; static String[] headers = new String[] { Span.SPAN_ID_NAME, Span.TRACE_ID_NAME, Span.PARENT_ID_NAME, Span.PROCESS_ID_NAME, - Span.SAMPLED_NAME, Span.SPAN_NAME_NAME }; + Span.SAMPLED_NAME, Span.SPAN_NAME_NAME, TraceMessageHeaders.SPAN_ID_NAME, + TraceMessageHeaders.TRACE_ID_NAME, TraceMessageHeaders.PARENT_ID_NAME, TraceMessageHeaders.PROCESS_ID_NAME, + TraceMessageHeaders.SAMPLED_NAME, TraceMessageHeaders.SPAN_NAME_NAME }; @Override public void postProcessEnvironment(ConfigurableEnvironment environment, diff --git a/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/StreamEnvironmentPostProcessorTests.java b/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/StreamEnvironmentPostProcessorTests.java index 7ef40cc16..e0d262e74 100644 --- a/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/StreamEnvironmentPostProcessorTests.java +++ b/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/StreamEnvironmentPostProcessorTests.java @@ -24,6 +24,7 @@ import org.junit.Test; import org.springframework.boot.SpringApplication; import org.springframework.boot.test.util.EnvironmentTestUtils; import org.springframework.cloud.sleuth.Span; +import org.springframework.cloud.sleuth.instrument.messaging.TraceMessageHeaders; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.StandardEnvironment; @@ -45,6 +46,13 @@ public class StreamEnvironmentPostProcessorTests { .isEqualTo(Span.SPAN_ID_NAME); } + @Test + public void should_append_new_tracing_headers() { + postProcess(); + assertThat(this.environment.getProperty("spring.cloud.stream.test.binder.headers[6]")) + .isEqualTo(TraceMessageHeaders.SPAN_ID_NAME); + } + @Test public void should_append_tracing_headers_to_existing_ones() { EnvironmentTestUtils.addEnvironment(this.environment, "spring.cloud.stream.test.binder.headers[0]=X-Custom", From cdba72b44c0a0d8753117d400c0f1888a75f0392 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Mon, 27 Feb 2017 16:05:25 +0100 Subject: [PATCH 220/235] Added missing aop dependency fixes #534 --- spring-cloud-sleuth-stream/pom.xml | 8 ++++++-- .../cloud/sleuth/stream/StreamSpanListenerTests.java | 6 ++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/spring-cloud-sleuth-stream/pom.xml b/spring-cloud-sleuth-stream/pom.xml index b47594ecf..54bb03684 100644 --- a/spring-cloud-sleuth-stream/pom.xml +++ b/spring-cloud-sleuth-stream/pom.xml @@ -28,6 +28,10 @@ org.springframework.cloud spring-cloud-commons + + org.springframework.boot + spring-boot-starter-aop + org.springframework.boot spring-boot-actuator @@ -59,8 +63,8 @@ test - org.aspectj - aspectjweaver + com.jayway.awaitility + awaitility test diff --git a/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/StreamSpanListenerTests.java b/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/StreamSpanListenerTests.java index 5ea9148b4..692dbb755 100644 --- a/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/StreamSpanListenerTests.java +++ b/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/StreamSpanListenerTests.java @@ -56,6 +56,8 @@ import org.springframework.messaging.Message; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringRunner; +import com.jayway.awaitility.Awaitility; + /** * @author Dave Syer * @@ -88,7 +90,7 @@ public class StreamSpanListenerTests { this.tracer.close(context); - assertThat(this.test.spans).hasSize(1); + Awaitility.await().until(() -> assertThat(StreamSpanListenerTests.this.test.spans).hasSize(1)); } @Test @@ -102,7 +104,7 @@ public class StreamSpanListenerTests { this.tracer.close(context); - assertThat(this.test.spans).hasSize(2); + Awaitility.await().until(() -> assertThat(StreamSpanListenerTests.this.test.spans).hasSize(2)); } void logServerReceived(Span parent) { From d1b4f73512cca815fd0375c485f430c99fc1e377 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Mon, 27 Feb 2017 16:41:41 +0100 Subject: [PATCH 221/235] Trying to fix flickering --- .../cloud/sleuth/stream/StreamSpanListenerTests.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/StreamSpanListenerTests.java b/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/StreamSpanListenerTests.java index 692dbb755..a6a3d14ff 100644 --- a/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/StreamSpanListenerTests.java +++ b/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/StreamSpanListenerTests.java @@ -17,6 +17,7 @@ package org.springframework.cloud.sleuth.stream; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.in; import static org.junit.Assert.assertEquals; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.atLeastOnce; @@ -27,6 +28,7 @@ import java.util.concurrent.LinkedBlockingQueue; import javax.annotation.PostConstruct; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; @@ -79,7 +81,7 @@ public class StreamSpanListenerTests { @Autowired SpanReporter spanReporter; - @PostConstruct + @Before public void init() { this.test.spans.clear(); } @@ -175,7 +177,9 @@ public class StreamSpanListenerTests { StreamSpanReporter listener; @ServiceActivator(inputChannel = SleuthSource.OUTPUT) - public void handle(Message msg) { + public void handle(Spans input) { + this.spans.clear(); + this.spans.addAll(input.getSpans()); } @Bean @@ -184,7 +188,7 @@ public class StreamSpanListenerTests { } @Bean - public Sampler defaultSampler() { + Sampler defaultSampler() { return new AlwaysSampler(); } From 64f4825966c3028c1b7fa415830088ef23aef6d5 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Sat, 4 Mar 2017 09:26:11 +0100 Subject: [PATCH 222/235] Fixed deps --- pom.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pom.xml b/pom.xml index c3bfde101..98bc251bf 100644 --- a/pom.xml +++ b/pom.xml @@ -89,6 +89,11 @@ + + org.apache.maven.plugins + maven-deploy-plugin + 2.8.2 + From 39b4617d51c3a7279a00565a3c66dc825ba591f6 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Wed, 8 Mar 2017 12:06:44 +0100 Subject: [PATCH 223/235] Updated mvnw --- mvnw | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/mvnw b/mvnw index 0a7dac221..25f750cb4 100755 --- a/mvnw +++ b/mvnw @@ -238,6 +238,14 @@ else echo $MAVEN_ARGS | grep -q milestone && MAVEN_ARGS=$(echo $MAVEN_ARGS | sed -e 's/-Pmilestone//') fi +if echo $VERSION | egrep -q 'RELEASE'; then + echo Activating \"central\" profile for version=\"$VERSION\" + echo $MAVEN_ARGS | grep -q milestone || MAVEN_ARGS="$MAVEN_ARGS -Pcentral" +else + echo Deactivating \"central\" profile for version=\"$VERSION\" + echo $MAVEN_ARGS | grep -q central && MAVEN_ARGS=$(echo $MAVEN_ARGS | sed -e 's/-Pcentral//') +fi + exec "$JAVACMD" \ $MAVEN_OPTS \ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ From 1cab456015acca5ac4e3634311df993e1029fdbb Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Fri, 10 Mar 2017 11:10:23 +0100 Subject: [PATCH 224/235] Bumping versions before release --- README.adoc | 12 +- benchmarks/pom.xml | 2 +- docs/pom.xml | 2 +- mvnw.cmd | 286 +++++++------- pom.xml | 12 +- spring-cloud-sleuth-core/pom.xml | 2 +- .../async/TraceableExecutorService.java | 348 +++++++++--------- .../TraceableScheduledExecutorService.java | 138 +++---- spring-cloud-sleuth-dependencies/pom.xml | 4 +- spring-cloud-sleuth-samples/pom.xml | 2 +- .../spring-cloud-sleuth-sample-feign/pom.xml | 2 +- .../pom.xml | 2 +- .../spring-cloud-sleuth-sample-ribbon/pom.xml | 2 +- .../spring-cloud-sleuth-sample-stream/pom.xml | 2 +- .../pom.xml | 2 +- .../pom.xml | 2 +- .../pom.xml | 2 +- .../spring-cloud-sleuth-sample-zipkin/pom.xml | 2 +- .../spring-cloud-sleuth-sample/pom.xml | 2 +- spring-cloud-sleuth-stream/pom.xml | 2 +- spring-cloud-sleuth-zipkin-stream/pom.xml | 2 +- spring-cloud-sleuth-zipkin/pom.xml | 2 +- spring-cloud-starter-sleuth/pom.xml | 2 +- spring-cloud-starter-zipkin/pom.xml | 2 +- 24 files changed, 419 insertions(+), 417 deletions(-) diff --git a/README.adoc b/README.adoc index 6ee18ce72..89e817c6f 100644 --- a/README.adoc +++ b/README.adoc @@ -2,8 +2,8 @@ :jdkversion: 1.8 -image::https://circleci.com/gh/spring-cloud/spring-cloud-sleuth/tree/1.0.x.svg?style=svg["CircleCI", link="https://circleci.com/gh/spring-cloud/spring-cloud-sleuth/tree/1.0.x"] -image::https://codecov.io/gh/spring-cloud/spring-cloud-sleuth/branch/1.0.x/graph/badge.svg["codecov", link="https://codecov.io/gh/spring-cloud/spring-cloud-sleuth"] +image::https://circleci.com/gh/spring-cloud/spring-cloud-sleuth/tree/1.1.x.svg?style=svg["CircleCI", link="https://circleci.com/gh/spring-cloud/spring-cloud-sleuth/tree/1.1.x"] +image::https://codecov.io/gh/spring-cloud/spring-cloud-sleuth/branch/1.1.x/graph/badge.svg["codecov", link="https://codecov.io/gh/spring-cloud/spring-cloud-sleuth"] image::https://badges.gitter.im/spring-cloud/spring-cloud-sleuth.svg[Gitter, link="https://gitter.im/spring-cloud/spring-cloud-sleuth?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge"] == Spring Cloud Sleuth @@ -194,8 +194,9 @@ Below you can find an example of a Logback configuration (file named https://git ​ + + value="%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}"/> @@ -256,7 +257,8 @@ Below you can find an example of a Logback configuration (file named https://git ​ - + + @@ -561,7 +563,7 @@ IMPORTANT: If using Zipkin or Stream, configure the percentage of spans exported NOTE: the SLF4J MDC is always set and logback users will immediately see the trace and span ids in logs per the example above. Other logging systems have to configure their own formatter to get the same result. The default is - `logging.pattern.level` set to `%clr(%5p) %clr([${spring.application.name:},%X{X-B3-TraceId:-},%X{X-B3-SpanId:-},%X{X-Span-Export:-}]){yellow}` + `logging.pattern.level` set to `%5p [${spring.zipkin.service.name:${spring.application.name:-}},%X{X-B3-TraceId:-},%X{X-B3-SpanId:-},%X{X-Span-Export:-}]` (this is a Spring Boot feature for logback users). *This means that if you're not using SLF4J this pattern WILL NOT be automatically applied*. diff --git a/benchmarks/pom.xml b/benchmarks/pom.xml index 6a74d51b5..50c101f5b 100644 --- a/benchmarks/pom.xml +++ b/benchmarks/pom.xml @@ -17,7 +17,7 @@ Benchmarks Benchmarks (JMH) org.springframework.cloud - 1.1.3.BUILD-SNAPSHOT + 1.1.3.RELEASE benchmarks diff --git a/docs/pom.xml b/docs/pom.xml index 9d5381169..dad34347a 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.3.BUILD-SNAPSHOT + 1.1.3.RELEASE spring-cloud-sleuth-docs pom diff --git a/mvnw.cmd b/mvnw.cmd index fc8302432..b0dc0e7e9 100644 --- a/mvnw.cmd +++ b/mvnw.cmd @@ -1,145 +1,145 @@ -@REM ---------------------------------------------------------------------------- -@REM Licensed to the Apache Software Foundation (ASF) under one -@REM or more contributor license agreements. See the NOTICE file -@REM distributed with this work for additional information -@REM regarding copyright ownership. The ASF licenses this file -@REM to you under the Apache License, Version 2.0 (the -@REM "License"); you may not use this file except in compliance -@REM with the License. You may obtain a copy of the License at -@REM -@REM http://www.apache.org/licenses/LICENSE-2.0 -@REM -@REM Unless required by applicable law or agreed to in writing, -@REM software distributed under the License is distributed on an -@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -@REM KIND, either express or implied. See the License for the -@REM specific language governing permissions and limitations -@REM under the License. -@REM ---------------------------------------------------------------------------- - -@REM ---------------------------------------------------------------------------- -@REM Maven2 Start Up Batch script -@REM -@REM Required ENV vars: -@REM JAVA_HOME - location of a JDK home dir -@REM -@REM Optional ENV vars -@REM M2_HOME - location of maven2's installed home dir -@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands -@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending -@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven -@REM e.g. to debug Maven itself, use -@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files -@REM ---------------------------------------------------------------------------- - -@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' -@echo off -@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' -@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% - -@REM set %HOME% to equivalent of $HOME -if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") - -@REM Execute a user defined script before this one -if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre -@REM check for pre script, once with legacy .bat ending and once with .cmd ending -if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" -if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" -:skipRcPre - -@setlocal - -set ERROR_CODE=0 - -@REM To isolate internal variables from possible post scripts, we use another setlocal -@setlocal - -@REM ==== START VALIDATION ==== -if not "%JAVA_HOME%" == "" goto OkJHome - -echo. -echo Error: JAVA_HOME not found in your environment. >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -:OkJHome -if exist "%JAVA_HOME%\bin\java.exe" goto init - -echo. -echo Error: JAVA_HOME is set to an invalid directory. >&2 -echo JAVA_HOME = "%JAVA_HOME%" >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -@REM ==== END VALIDATION ==== - -:init - -set MAVEN_CMD_LINE_ARGS=%* - -@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". -@REM Fallback to current working directory if not found. - -set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% -IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir - -set EXEC_DIR=%CD% -set WDIR=%EXEC_DIR% -:findBaseDir -IF EXIST "%WDIR%"\.mvn goto baseDirFound -cd .. -IF "%WDIR%"=="%CD%" goto baseDirNotFound -set WDIR=%CD% -goto findBaseDir - -:baseDirFound -set MAVEN_PROJECTBASEDIR=%WDIR% -cd "%EXEC_DIR%" -goto endDetectBaseDir - -:baseDirNotFound -set MAVEN_PROJECTBASEDIR=%EXEC_DIR% -cd "%EXEC_DIR%" - -:endDetectBaseDir - -IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig - -@setlocal EnableExtensions EnableDelayedExpansion -for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a -@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% - -:endReadAdditionalConfig - -SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven2 Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +set MAVEN_CMD_LINE_ARGS=%* + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" set WRAPPER_JAR="".\.mvn\wrapper\maven-wrapper.jar"" -set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain - -%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CMD_LINE_ARGS% -if ERRORLEVEL 1 goto error -goto end - -:error -set ERROR_CODE=1 - -:end -@endlocal & set ERROR_CODE=%ERROR_CODE% - -if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost -@REM check for post script, once with legacy .bat ending and once with .cmd ending -if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" -if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" -:skipRcPost - -@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' -if "%MAVEN_BATCH_PAUSE%" == "on" pause - -if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% - -exit /B %ERROR_CODE% +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CMD_LINE_ARGS% +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/pom.xml b/pom.xml index 98bc251bf..8f42c72af 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 spring-cloud-sleuth - 1.1.3.BUILD-SNAPSHOT + 1.1.3.RELEASE pom Spring Cloud Sleuth Spring Cloud Sleuth @@ -13,7 +13,7 @@ org.springframework.cloud spring-cloud-build - 1.2.3.BUILD-SNAPSHOT + 1.2.2.RELEASE @@ -234,10 +234,10 @@ 1.8 2.19.1 2.17 - 1.2.3.BUILD-SNAPSHOT - 1.1.8.BUILD-SNAPSHOT - Brooklyn.BUILD-SNAPSHOT - 1.2.6.BUILD-SNAPSHOT + 1.2.2.RELEASE + 1.1.8.RELEASE + Brooklyn.SR3 + 1.2.6.RELEASE diff --git a/spring-cloud-sleuth-core/pom.xml b/spring-cloud-sleuth-core/pom.xml index 422c4855c..61ec6055c 100644 --- a/spring-cloud-sleuth-core/pom.xml +++ b/spring-cloud-sleuth-core/pom.xml @@ -11,7 +11,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.3.BUILD-SNAPSHOT + 1.1.3.RELEASE .. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceableExecutorService.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceableExecutorService.java index fbf547f28..0218b8074 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceableExecutorService.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceableExecutorService.java @@ -1,174 +1,174 @@ -/* - * Copyright 2015 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.async; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -import org.springframework.beans.factory.BeanFactory; -import org.springframework.cloud.sleuth.SpanNamer; -import org.springframework.cloud.sleuth.TraceKeys; -import org.springframework.cloud.sleuth.Tracer; - -/** - * A decorator class for {@link ExecutorService} to support tracing in Executors - * - * @author Gaurav Rai Mazra - * @since 1.0.0 - */ -public class TraceableExecutorService implements ExecutorService { - ExecutorService delegate; - Tracer tracer; - private final String spanName; - TraceKeys traceKeys; - SpanNamer spanNamer; - BeanFactory beanFactory; - - public TraceableExecutorService(final ExecutorService delegate, final Tracer tracer, - TraceKeys traceKeys, SpanNamer spanNamer) { - this(delegate, tracer, traceKeys, spanNamer, null); - } - - public TraceableExecutorService(BeanFactory beanFactory, final ExecutorService delegate) { - this.delegate = delegate; - this.beanFactory = beanFactory; - this.spanName = null; - } - - public TraceableExecutorService(final ExecutorService delegate, final Tracer tracer, - TraceKeys traceKeys, SpanNamer spanNamer, String spanName) { - this.delegate = delegate; - this.tracer = tracer; - this.spanName = spanName; - this.traceKeys = traceKeys; - this.spanNamer = spanNamer; - } - - @Override - public void execute(Runnable command) { - final Runnable r = new LocalComponentTraceRunnable(tracer(), traceKeys(), - spanNamer(), command, this.spanName); - this.delegate.execute(r); - } - - @Override - public void shutdown() { - this.delegate.shutdown(); - } - - @Override - public List shutdownNow() { - return this.delegate.shutdownNow(); - } - - @Override - public boolean isShutdown() { - return this.delegate.isShutdown(); - } - - @Override - public boolean isTerminated() { - return this.delegate.isTerminated(); - } - - @Override - public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { - return this.delegate.awaitTermination(timeout, unit); - } - - @Override - public Future submit(Callable task) { - Callable c = new LocalComponentTraceCallable<>(tracer(), traceKeys(), - spanNamer(), this.spanName, task); - return this.delegate.submit(c); - } - - @Override - public Future submit(Runnable task, T result) { - Runnable r = new LocalComponentTraceRunnable(tracer(), traceKeys(), - spanNamer(), task, this.spanName); - return this.delegate.submit(r, result); - } - - @Override - public Future submit(Runnable task) { - Runnable r = new LocalComponentTraceRunnable(tracer(), traceKeys(), - spanNamer(), task, this.spanName); - return this.delegate.submit(r); - } - - @Override - public List> invokeAll(Collection> tasks) throws InterruptedException { - return this.delegate.invokeAll(wrapCallableCollection(tasks)); - } - - @Override - public List> invokeAll(Collection> tasks, long timeout, TimeUnit unit) - throws InterruptedException { - return this.delegate.invokeAll(wrapCallableCollection(tasks), timeout, unit); - } - - @Override - public T invokeAny(Collection> tasks) throws InterruptedException, ExecutionException { - return this.delegate.invokeAny(wrapCallableCollection(tasks)); - } - - @Override - public T invokeAny(Collection> tasks, long timeout, TimeUnit unit) - throws InterruptedException, ExecutionException, TimeoutException { - return this.delegate.invokeAny(wrapCallableCollection(tasks), timeout, unit); - } - - private Collection> wrapCallableCollection(Collection> tasks) { - List> ts = new ArrayList<>(); - for (Callable task : tasks) { - if (!(task instanceof LocalComponentTraceCallable)) { - ts.add(new LocalComponentTraceCallable<>(tracer(), traceKeys(), - spanNamer(), this.spanName, task)); - } - } - return ts; - } - - Tracer tracer() { - if (this.tracer == null && this.beanFactory != null) { - this.tracer = this.beanFactory.getBean(Tracer.class); - } - return this.tracer; - } - - TraceKeys traceKeys() { - if (this.traceKeys == null && this.beanFactory != null) { - this.traceKeys = this.beanFactory.getBean(TraceKeys.class); - } - return this.traceKeys; - } - - SpanNamer spanNamer() { - if (this.spanNamer == null && this.beanFactory != null) { - this.spanNamer = this.beanFactory.getBean(SpanNamer.class); - } - return this.spanNamer; - } - -} +/* + * Copyright 2015 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.async; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.cloud.sleuth.SpanNamer; +import org.springframework.cloud.sleuth.TraceKeys; +import org.springframework.cloud.sleuth.Tracer; + +/** + * A decorator class for {@link ExecutorService} to support tracing in Executors + * + * @author Gaurav Rai Mazra + * @since 1.0.0 + */ +public class TraceableExecutorService implements ExecutorService { + ExecutorService delegate; + Tracer tracer; + private final String spanName; + TraceKeys traceKeys; + SpanNamer spanNamer; + BeanFactory beanFactory; + + public TraceableExecutorService(final ExecutorService delegate, final Tracer tracer, + TraceKeys traceKeys, SpanNamer spanNamer) { + this(delegate, tracer, traceKeys, spanNamer, null); + } + + public TraceableExecutorService(BeanFactory beanFactory, final ExecutorService delegate) { + this.delegate = delegate; + this.beanFactory = beanFactory; + this.spanName = null; + } + + public TraceableExecutorService(final ExecutorService delegate, final Tracer tracer, + TraceKeys traceKeys, SpanNamer spanNamer, String spanName) { + this.delegate = delegate; + this.tracer = tracer; + this.spanName = spanName; + this.traceKeys = traceKeys; + this.spanNamer = spanNamer; + } + + @Override + public void execute(Runnable command) { + final Runnable r = new LocalComponentTraceRunnable(tracer(), traceKeys(), + spanNamer(), command, this.spanName); + this.delegate.execute(r); + } + + @Override + public void shutdown() { + this.delegate.shutdown(); + } + + @Override + public List shutdownNow() { + return this.delegate.shutdownNow(); + } + + @Override + public boolean isShutdown() { + return this.delegate.isShutdown(); + } + + @Override + public boolean isTerminated() { + return this.delegate.isTerminated(); + } + + @Override + public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { + return this.delegate.awaitTermination(timeout, unit); + } + + @Override + public Future submit(Callable task) { + Callable c = new LocalComponentTraceCallable<>(tracer(), traceKeys(), + spanNamer(), this.spanName, task); + return this.delegate.submit(c); + } + + @Override + public Future submit(Runnable task, T result) { + Runnable r = new LocalComponentTraceRunnable(tracer(), traceKeys(), + spanNamer(), task, this.spanName); + return this.delegate.submit(r, result); + } + + @Override + public Future submit(Runnable task) { + Runnable r = new LocalComponentTraceRunnable(tracer(), traceKeys(), + spanNamer(), task, this.spanName); + return this.delegate.submit(r); + } + + @Override + public List> invokeAll(Collection> tasks) throws InterruptedException { + return this.delegate.invokeAll(wrapCallableCollection(tasks)); + } + + @Override + public List> invokeAll(Collection> tasks, long timeout, TimeUnit unit) + throws InterruptedException { + return this.delegate.invokeAll(wrapCallableCollection(tasks), timeout, unit); + } + + @Override + public T invokeAny(Collection> tasks) throws InterruptedException, ExecutionException { + return this.delegate.invokeAny(wrapCallableCollection(tasks)); + } + + @Override + public T invokeAny(Collection> tasks, long timeout, TimeUnit unit) + throws InterruptedException, ExecutionException, TimeoutException { + return this.delegate.invokeAny(wrapCallableCollection(tasks), timeout, unit); + } + + private Collection> wrapCallableCollection(Collection> tasks) { + List> ts = new ArrayList<>(); + for (Callable task : tasks) { + if (!(task instanceof LocalComponentTraceCallable)) { + ts.add(new LocalComponentTraceCallable<>(tracer(), traceKeys(), + spanNamer(), this.spanName, task)); + } + } + return ts; + } + + Tracer tracer() { + if (this.tracer == null && this.beanFactory != null) { + this.tracer = this.beanFactory.getBean(Tracer.class); + } + return this.tracer; + } + + TraceKeys traceKeys() { + if (this.traceKeys == null && this.beanFactory != null) { + this.traceKeys = this.beanFactory.getBean(TraceKeys.class); + } + return this.traceKeys; + } + + SpanNamer spanNamer() { + if (this.spanNamer == null && this.beanFactory != null) { + this.spanNamer = this.beanFactory.getBean(SpanNamer.class); + } + return this.spanNamer; + } + +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceableScheduledExecutorService.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceableScheduledExecutorService.java index 011558ee3..ac0dc1697 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceableScheduledExecutorService.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceableScheduledExecutorService.java @@ -1,69 +1,69 @@ -/* - * Copyright 2015 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.async; - -import java.util.concurrent.Callable; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; - -import org.springframework.cloud.sleuth.SpanNamer; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.TraceKeys; - -/** - * A decorator class for {@link ScheduledExecutorService} to support tracing in Executors - * - * @author Gaurav Rai Mazra - * @since 1.0.0 - */ -public class TraceableScheduledExecutorService extends TraceableExecutorService implements ScheduledExecutorService { - - public TraceableScheduledExecutorService(ScheduledExecutorService delegate, - Tracer tracer, TraceKeys traceKeys, SpanNamer spanNamer) { - super(delegate, tracer, traceKeys, spanNamer); - } - - private ScheduledExecutorService getScheduledExecutorService() { - return (ScheduledExecutorService) this.delegate; - } - - @Override - public ScheduledFuture schedule(Runnable command, long delay, TimeUnit unit) { - Runnable r = new LocalComponentTraceRunnable(this.tracer, this.traceKeys, this.spanNamer, command); - return getScheduledExecutorService().schedule(r, delay, unit); - } - - @Override - public ScheduledFuture schedule(Callable callable, long delay, TimeUnit unit) { - Callable c = new LocalComponentTraceCallable<>(this.tracer, this.traceKeys, this.spanNamer, callable); - return getScheduledExecutorService().schedule(c, delay, unit); - } - - @Override - public ScheduledFuture scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) { - Runnable r = new LocalComponentTraceRunnable(this.tracer, this.traceKeys, this.spanNamer, command); - return getScheduledExecutorService().scheduleAtFixedRate(r, initialDelay, period, unit); - } - - @Override - public ScheduledFuture scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) { - Runnable r = new LocalComponentTraceRunnable(this.tracer, this.traceKeys, this.spanNamer, command); - return getScheduledExecutorService().scheduleWithFixedDelay(r, initialDelay, delay, unit); - } - -} +/* + * Copyright 2015 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.async; + +import java.util.concurrent.Callable; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import org.springframework.cloud.sleuth.SpanNamer; +import org.springframework.cloud.sleuth.Tracer; +import org.springframework.cloud.sleuth.TraceKeys; + +/** + * A decorator class for {@link ScheduledExecutorService} to support tracing in Executors + * + * @author Gaurav Rai Mazra + * @since 1.0.0 + */ +public class TraceableScheduledExecutorService extends TraceableExecutorService implements ScheduledExecutorService { + + public TraceableScheduledExecutorService(ScheduledExecutorService delegate, + Tracer tracer, TraceKeys traceKeys, SpanNamer spanNamer) { + super(delegate, tracer, traceKeys, spanNamer); + } + + private ScheduledExecutorService getScheduledExecutorService() { + return (ScheduledExecutorService) this.delegate; + } + + @Override + public ScheduledFuture schedule(Runnable command, long delay, TimeUnit unit) { + Runnable r = new LocalComponentTraceRunnable(this.tracer, this.traceKeys, this.spanNamer, command); + return getScheduledExecutorService().schedule(r, delay, unit); + } + + @Override + public ScheduledFuture schedule(Callable callable, long delay, TimeUnit unit) { + Callable c = new LocalComponentTraceCallable<>(this.tracer, this.traceKeys, this.spanNamer, callable); + return getScheduledExecutorService().schedule(c, delay, unit); + } + + @Override + public ScheduledFuture scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) { + Runnable r = new LocalComponentTraceRunnable(this.tracer, this.traceKeys, this.spanNamer, command); + return getScheduledExecutorService().scheduleAtFixedRate(r, initialDelay, period, unit); + } + + @Override + public ScheduledFuture scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) { + Runnable r = new LocalComponentTraceRunnable(this.tracer, this.traceKeys, this.spanNamer, command); + return getScheduledExecutorService().scheduleWithFixedDelay(r, initialDelay, delay, unit); + } + +} diff --git a/spring-cloud-sleuth-dependencies/pom.xml b/spring-cloud-sleuth-dependencies/pom.xml index 230101c05..8b0bd6a5f 100644 --- a/spring-cloud-sleuth-dependencies/pom.xml +++ b/spring-cloud-sleuth-dependencies/pom.xml @@ -5,11 +5,11 @@ spring-cloud-dependencies-parent org.springframework.cloud - 1.2.3.BUILD-SNAPSHOT + 1.2.2.RELEASE spring-cloud-sleuth-dependencies - 1.1.3.BUILD-SNAPSHOT + 1.1.3.RELEASE pom spring-cloud-sleuth-dependencies Spring Cloud Sleuth Dependencies diff --git a/spring-cloud-sleuth-samples/pom.xml b/spring-cloud-sleuth-samples/pom.xml index 1cf7bcb82..a5f107740 100644 --- a/spring-cloud-sleuth-samples/pom.xml +++ b/spring-cloud-sleuth-samples/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.3.BUILD-SNAPSHOT + 1.1.3.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml index 721ca2542..047e73e9b 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml @@ -20,7 +20,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.3.BUILD-SNAPSHOT + 1.1.3.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml index f27c33439..898b6d435 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.3.BUILD-SNAPSHOT + 1.1.3.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml index 1634f92c4..5e598f925 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.3.BUILD-SNAPSHOT + 1.1.3.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml index f10cee8f1..2411f9b00 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.3.BUILD-SNAPSHOT + 1.1.3.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml index ebe795557..9e7a444f6 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.3.BUILD-SNAPSHOT + 1.1.3.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml index 8759ae06d..8695ff86b 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.3.BUILD-SNAPSHOT + 1.1.3.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml index 4188686a7..14c1e85d8 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.3.BUILD-SNAPSHOT + 1.1.3.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml index e02baaee5..1e89a8b70 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml @@ -27,7 +27,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.3.BUILD-SNAPSHOT + 1.1.3.RELEASE .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml index 98a4c5da3..e33213e94 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.3.BUILD-SNAPSHOT + 1.1.3.RELEASE .. diff --git a/spring-cloud-sleuth-stream/pom.xml b/spring-cloud-sleuth-stream/pom.xml index 54bb03684..83471f825 100644 --- a/spring-cloud-sleuth-stream/pom.xml +++ b/spring-cloud-sleuth-stream/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.3.BUILD-SNAPSHOT + 1.1.3.RELEASE diff --git a/spring-cloud-sleuth-zipkin-stream/pom.xml b/spring-cloud-sleuth-zipkin-stream/pom.xml index 68bda6625..39d7f5b19 100644 --- a/spring-cloud-sleuth-zipkin-stream/pom.xml +++ b/spring-cloud-sleuth-zipkin-stream/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.3.BUILD-SNAPSHOT + 1.1.3.RELEASE diff --git a/spring-cloud-sleuth-zipkin/pom.xml b/spring-cloud-sleuth-zipkin/pom.xml index 3d0c0c511..4883344c2 100644 --- a/spring-cloud-sleuth-zipkin/pom.xml +++ b/spring-cloud-sleuth-zipkin/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.3.BUILD-SNAPSHOT + 1.1.3.RELEASE .. diff --git a/spring-cloud-starter-sleuth/pom.xml b/spring-cloud-starter-sleuth/pom.xml index a8eb67393..65df988c9 100644 --- a/spring-cloud-starter-sleuth/pom.xml +++ b/spring-cloud-starter-sleuth/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.3.BUILD-SNAPSHOT + 1.1.3.RELEASE .. spring-cloud-starter-sleuth diff --git a/spring-cloud-starter-zipkin/pom.xml b/spring-cloud-starter-zipkin/pom.xml index 70ab3fc49..08e6139cb 100644 --- a/spring-cloud-starter-zipkin/pom.xml +++ b/spring-cloud-starter-zipkin/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.3.BUILD-SNAPSHOT + 1.1.3.RELEASE .. spring-cloud-starter-zipkin From dde7d3b313109a6a963882a8f3714e2f6190916b Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Fri, 10 Mar 2017 11:16:09 +0100 Subject: [PATCH 225/235] Going back to snapshots --- README.adoc | 12 +- benchmarks/pom.xml | 2 +- docs/pom.xml | 2 +- mvnw.cmd | 286 +++++++------- pom.xml | 12 +- spring-cloud-sleuth-core/pom.xml | 2 +- .../async/TraceableExecutorService.java | 348 +++++++++--------- .../TraceableScheduledExecutorService.java | 138 +++---- spring-cloud-sleuth-dependencies/pom.xml | 4 +- spring-cloud-sleuth-samples/pom.xml | 2 +- .../spring-cloud-sleuth-sample-feign/pom.xml | 2 +- .../pom.xml | 2 +- .../spring-cloud-sleuth-sample-ribbon/pom.xml | 2 +- .../spring-cloud-sleuth-sample-stream/pom.xml | 2 +- .../pom.xml | 2 +- .../pom.xml | 2 +- .../pom.xml | 2 +- .../spring-cloud-sleuth-sample-zipkin/pom.xml | 2 +- .../spring-cloud-sleuth-sample/pom.xml | 2 +- spring-cloud-sleuth-stream/pom.xml | 2 +- spring-cloud-sleuth-zipkin-stream/pom.xml | 2 +- spring-cloud-sleuth-zipkin/pom.xml | 2 +- spring-cloud-starter-sleuth/pom.xml | 2 +- spring-cloud-starter-zipkin/pom.xml | 2 +- 24 files changed, 417 insertions(+), 419 deletions(-) diff --git a/README.adoc b/README.adoc index 89e817c6f..6ee18ce72 100644 --- a/README.adoc +++ b/README.adoc @@ -2,8 +2,8 @@ :jdkversion: 1.8 -image::https://circleci.com/gh/spring-cloud/spring-cloud-sleuth/tree/1.1.x.svg?style=svg["CircleCI", link="https://circleci.com/gh/spring-cloud/spring-cloud-sleuth/tree/1.1.x"] -image::https://codecov.io/gh/spring-cloud/spring-cloud-sleuth/branch/1.1.x/graph/badge.svg["codecov", link="https://codecov.io/gh/spring-cloud/spring-cloud-sleuth"] +image::https://circleci.com/gh/spring-cloud/spring-cloud-sleuth/tree/1.0.x.svg?style=svg["CircleCI", link="https://circleci.com/gh/spring-cloud/spring-cloud-sleuth/tree/1.0.x"] +image::https://codecov.io/gh/spring-cloud/spring-cloud-sleuth/branch/1.0.x/graph/badge.svg["codecov", link="https://codecov.io/gh/spring-cloud/spring-cloud-sleuth"] image::https://badges.gitter.im/spring-cloud/spring-cloud-sleuth.svg[Gitter, link="https://gitter.im/spring-cloud/spring-cloud-sleuth?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge"] == Spring Cloud Sleuth @@ -194,9 +194,8 @@ Below you can find an example of a Logback configuration (file named https://git ​ - + value="%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr([${springAppName:-},%X{X-B3-TraceId:-},%X{X-B3-SpanId:-},%X{X-B3-ParentSpanId:-},%X{X-Span-Export:-}]){yellow} %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}"/> @@ -257,8 +256,7 @@ Below you can find an example of a Logback configuration (file named https://git ​ - - + @@ -563,7 +561,7 @@ IMPORTANT: If using Zipkin or Stream, configure the percentage of spans exported NOTE: the SLF4J MDC is always set and logback users will immediately see the trace and span ids in logs per the example above. Other logging systems have to configure their own formatter to get the same result. The default is - `logging.pattern.level` set to `%5p [${spring.zipkin.service.name:${spring.application.name:-}},%X{X-B3-TraceId:-},%X{X-B3-SpanId:-},%X{X-Span-Export:-}]` + `logging.pattern.level` set to `%clr(%5p) %clr([${spring.application.name:},%X{X-B3-TraceId:-},%X{X-B3-SpanId:-},%X{X-Span-Export:-}]){yellow}` (this is a Spring Boot feature for logback users). *This means that if you're not using SLF4J this pattern WILL NOT be automatically applied*. diff --git a/benchmarks/pom.xml b/benchmarks/pom.xml index 50c101f5b..6a74d51b5 100644 --- a/benchmarks/pom.xml +++ b/benchmarks/pom.xml @@ -17,7 +17,7 @@ Benchmarks Benchmarks (JMH) org.springframework.cloud - 1.1.3.RELEASE + 1.1.3.BUILD-SNAPSHOT benchmarks diff --git a/docs/pom.xml b/docs/pom.xml index dad34347a..9d5381169 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.3.RELEASE + 1.1.3.BUILD-SNAPSHOT spring-cloud-sleuth-docs pom diff --git a/mvnw.cmd b/mvnw.cmd index b0dc0e7e9..fc8302432 100644 --- a/mvnw.cmd +++ b/mvnw.cmd @@ -1,145 +1,145 @@ -@REM ---------------------------------------------------------------------------- -@REM Licensed to the Apache Software Foundation (ASF) under one -@REM or more contributor license agreements. See the NOTICE file -@REM distributed with this work for additional information -@REM regarding copyright ownership. The ASF licenses this file -@REM to you under the Apache License, Version 2.0 (the -@REM "License"); you may not use this file except in compliance -@REM with the License. You may obtain a copy of the License at -@REM -@REM http://www.apache.org/licenses/LICENSE-2.0 -@REM -@REM Unless required by applicable law or agreed to in writing, -@REM software distributed under the License is distributed on an -@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -@REM KIND, either express or implied. See the License for the -@REM specific language governing permissions and limitations -@REM under the License. -@REM ---------------------------------------------------------------------------- - -@REM ---------------------------------------------------------------------------- -@REM Maven2 Start Up Batch script -@REM -@REM Required ENV vars: -@REM JAVA_HOME - location of a JDK home dir -@REM -@REM Optional ENV vars -@REM M2_HOME - location of maven2's installed home dir -@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands -@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending -@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven -@REM e.g. to debug Maven itself, use -@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files -@REM ---------------------------------------------------------------------------- - -@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' -@echo off -@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' -@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% - -@REM set %HOME% to equivalent of $HOME -if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") - -@REM Execute a user defined script before this one -if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre -@REM check for pre script, once with legacy .bat ending and once with .cmd ending -if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" -if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" -:skipRcPre - -@setlocal - -set ERROR_CODE=0 - -@REM To isolate internal variables from possible post scripts, we use another setlocal -@setlocal - -@REM ==== START VALIDATION ==== -if not "%JAVA_HOME%" == "" goto OkJHome - -echo. -echo Error: JAVA_HOME not found in your environment. >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -:OkJHome -if exist "%JAVA_HOME%\bin\java.exe" goto init - -echo. -echo Error: JAVA_HOME is set to an invalid directory. >&2 -echo JAVA_HOME = "%JAVA_HOME%" >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -@REM ==== END VALIDATION ==== - -:init - -set MAVEN_CMD_LINE_ARGS=%* - -@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". -@REM Fallback to current working directory if not found. - -set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% -IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir - -set EXEC_DIR=%CD% -set WDIR=%EXEC_DIR% -:findBaseDir -IF EXIST "%WDIR%"\.mvn goto baseDirFound -cd .. -IF "%WDIR%"=="%CD%" goto baseDirNotFound -set WDIR=%CD% -goto findBaseDir - -:baseDirFound -set MAVEN_PROJECTBASEDIR=%WDIR% -cd "%EXEC_DIR%" -goto endDetectBaseDir - -:baseDirNotFound -set MAVEN_PROJECTBASEDIR=%EXEC_DIR% -cd "%EXEC_DIR%" - -:endDetectBaseDir - -IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig - -@setlocal EnableExtensions EnableDelayedExpansion -for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a -@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% - -:endReadAdditionalConfig - -SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven2 Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +set MAVEN_CMD_LINE_ARGS=%* + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" set WRAPPER_JAR="".\.mvn\wrapper\maven-wrapper.jar"" -set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain - -%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CMD_LINE_ARGS% -if ERRORLEVEL 1 goto error -goto end - -:error -set ERROR_CODE=1 - -:end -@endlocal & set ERROR_CODE=%ERROR_CODE% - -if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost -@REM check for post script, once with legacy .bat ending and once with .cmd ending -if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" -if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" -:skipRcPost - -@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' -if "%MAVEN_BATCH_PAUSE%" == "on" pause - -if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% - -exit /B %ERROR_CODE% +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CMD_LINE_ARGS% +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/pom.xml b/pom.xml index 8f42c72af..98bc251bf 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 spring-cloud-sleuth - 1.1.3.RELEASE + 1.1.3.BUILD-SNAPSHOT pom Spring Cloud Sleuth Spring Cloud Sleuth @@ -13,7 +13,7 @@ org.springframework.cloud spring-cloud-build - 1.2.2.RELEASE + 1.2.3.BUILD-SNAPSHOT @@ -234,10 +234,10 @@ 1.8 2.19.1 2.17 - 1.2.2.RELEASE - 1.1.8.RELEASE - Brooklyn.SR3 - 1.2.6.RELEASE + 1.2.3.BUILD-SNAPSHOT + 1.1.8.BUILD-SNAPSHOT + Brooklyn.BUILD-SNAPSHOT + 1.2.6.BUILD-SNAPSHOT diff --git a/spring-cloud-sleuth-core/pom.xml b/spring-cloud-sleuth-core/pom.xml index 61ec6055c..422c4855c 100644 --- a/spring-cloud-sleuth-core/pom.xml +++ b/spring-cloud-sleuth-core/pom.xml @@ -11,7 +11,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.3.RELEASE + 1.1.3.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceableExecutorService.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceableExecutorService.java index 0218b8074..fbf547f28 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceableExecutorService.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceableExecutorService.java @@ -1,174 +1,174 @@ -/* - * Copyright 2015 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.async; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -import org.springframework.beans.factory.BeanFactory; -import org.springframework.cloud.sleuth.SpanNamer; -import org.springframework.cloud.sleuth.TraceKeys; -import org.springframework.cloud.sleuth.Tracer; - -/** - * A decorator class for {@link ExecutorService} to support tracing in Executors - * - * @author Gaurav Rai Mazra - * @since 1.0.0 - */ -public class TraceableExecutorService implements ExecutorService { - ExecutorService delegate; - Tracer tracer; - private final String spanName; - TraceKeys traceKeys; - SpanNamer spanNamer; - BeanFactory beanFactory; - - public TraceableExecutorService(final ExecutorService delegate, final Tracer tracer, - TraceKeys traceKeys, SpanNamer spanNamer) { - this(delegate, tracer, traceKeys, spanNamer, null); - } - - public TraceableExecutorService(BeanFactory beanFactory, final ExecutorService delegate) { - this.delegate = delegate; - this.beanFactory = beanFactory; - this.spanName = null; - } - - public TraceableExecutorService(final ExecutorService delegate, final Tracer tracer, - TraceKeys traceKeys, SpanNamer spanNamer, String spanName) { - this.delegate = delegate; - this.tracer = tracer; - this.spanName = spanName; - this.traceKeys = traceKeys; - this.spanNamer = spanNamer; - } - - @Override - public void execute(Runnable command) { - final Runnable r = new LocalComponentTraceRunnable(tracer(), traceKeys(), - spanNamer(), command, this.spanName); - this.delegate.execute(r); - } - - @Override - public void shutdown() { - this.delegate.shutdown(); - } - - @Override - public List shutdownNow() { - return this.delegate.shutdownNow(); - } - - @Override - public boolean isShutdown() { - return this.delegate.isShutdown(); - } - - @Override - public boolean isTerminated() { - return this.delegate.isTerminated(); - } - - @Override - public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { - return this.delegate.awaitTermination(timeout, unit); - } - - @Override - public Future submit(Callable task) { - Callable c = new LocalComponentTraceCallable<>(tracer(), traceKeys(), - spanNamer(), this.spanName, task); - return this.delegate.submit(c); - } - - @Override - public Future submit(Runnable task, T result) { - Runnable r = new LocalComponentTraceRunnable(tracer(), traceKeys(), - spanNamer(), task, this.spanName); - return this.delegate.submit(r, result); - } - - @Override - public Future submit(Runnable task) { - Runnable r = new LocalComponentTraceRunnable(tracer(), traceKeys(), - spanNamer(), task, this.spanName); - return this.delegate.submit(r); - } - - @Override - public List> invokeAll(Collection> tasks) throws InterruptedException { - return this.delegate.invokeAll(wrapCallableCollection(tasks)); - } - - @Override - public List> invokeAll(Collection> tasks, long timeout, TimeUnit unit) - throws InterruptedException { - return this.delegate.invokeAll(wrapCallableCollection(tasks), timeout, unit); - } - - @Override - public T invokeAny(Collection> tasks) throws InterruptedException, ExecutionException { - return this.delegate.invokeAny(wrapCallableCollection(tasks)); - } - - @Override - public T invokeAny(Collection> tasks, long timeout, TimeUnit unit) - throws InterruptedException, ExecutionException, TimeoutException { - return this.delegate.invokeAny(wrapCallableCollection(tasks), timeout, unit); - } - - private Collection> wrapCallableCollection(Collection> tasks) { - List> ts = new ArrayList<>(); - for (Callable task : tasks) { - if (!(task instanceof LocalComponentTraceCallable)) { - ts.add(new LocalComponentTraceCallable<>(tracer(), traceKeys(), - spanNamer(), this.spanName, task)); - } - } - return ts; - } - - Tracer tracer() { - if (this.tracer == null && this.beanFactory != null) { - this.tracer = this.beanFactory.getBean(Tracer.class); - } - return this.tracer; - } - - TraceKeys traceKeys() { - if (this.traceKeys == null && this.beanFactory != null) { - this.traceKeys = this.beanFactory.getBean(TraceKeys.class); - } - return this.traceKeys; - } - - SpanNamer spanNamer() { - if (this.spanNamer == null && this.beanFactory != null) { - this.spanNamer = this.beanFactory.getBean(SpanNamer.class); - } - return this.spanNamer; - } - -} +/* + * Copyright 2015 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.async; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.cloud.sleuth.SpanNamer; +import org.springframework.cloud.sleuth.TraceKeys; +import org.springframework.cloud.sleuth.Tracer; + +/** + * A decorator class for {@link ExecutorService} to support tracing in Executors + * + * @author Gaurav Rai Mazra + * @since 1.0.0 + */ +public class TraceableExecutorService implements ExecutorService { + ExecutorService delegate; + Tracer tracer; + private final String spanName; + TraceKeys traceKeys; + SpanNamer spanNamer; + BeanFactory beanFactory; + + public TraceableExecutorService(final ExecutorService delegate, final Tracer tracer, + TraceKeys traceKeys, SpanNamer spanNamer) { + this(delegate, tracer, traceKeys, spanNamer, null); + } + + public TraceableExecutorService(BeanFactory beanFactory, final ExecutorService delegate) { + this.delegate = delegate; + this.beanFactory = beanFactory; + this.spanName = null; + } + + public TraceableExecutorService(final ExecutorService delegate, final Tracer tracer, + TraceKeys traceKeys, SpanNamer spanNamer, String spanName) { + this.delegate = delegate; + this.tracer = tracer; + this.spanName = spanName; + this.traceKeys = traceKeys; + this.spanNamer = spanNamer; + } + + @Override + public void execute(Runnable command) { + final Runnable r = new LocalComponentTraceRunnable(tracer(), traceKeys(), + spanNamer(), command, this.spanName); + this.delegate.execute(r); + } + + @Override + public void shutdown() { + this.delegate.shutdown(); + } + + @Override + public List shutdownNow() { + return this.delegate.shutdownNow(); + } + + @Override + public boolean isShutdown() { + return this.delegate.isShutdown(); + } + + @Override + public boolean isTerminated() { + return this.delegate.isTerminated(); + } + + @Override + public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { + return this.delegate.awaitTermination(timeout, unit); + } + + @Override + public Future submit(Callable task) { + Callable c = new LocalComponentTraceCallable<>(tracer(), traceKeys(), + spanNamer(), this.spanName, task); + return this.delegate.submit(c); + } + + @Override + public Future submit(Runnable task, T result) { + Runnable r = new LocalComponentTraceRunnable(tracer(), traceKeys(), + spanNamer(), task, this.spanName); + return this.delegate.submit(r, result); + } + + @Override + public Future submit(Runnable task) { + Runnable r = new LocalComponentTraceRunnable(tracer(), traceKeys(), + spanNamer(), task, this.spanName); + return this.delegate.submit(r); + } + + @Override + public List> invokeAll(Collection> tasks) throws InterruptedException { + return this.delegate.invokeAll(wrapCallableCollection(tasks)); + } + + @Override + public List> invokeAll(Collection> tasks, long timeout, TimeUnit unit) + throws InterruptedException { + return this.delegate.invokeAll(wrapCallableCollection(tasks), timeout, unit); + } + + @Override + public T invokeAny(Collection> tasks) throws InterruptedException, ExecutionException { + return this.delegate.invokeAny(wrapCallableCollection(tasks)); + } + + @Override + public T invokeAny(Collection> tasks, long timeout, TimeUnit unit) + throws InterruptedException, ExecutionException, TimeoutException { + return this.delegate.invokeAny(wrapCallableCollection(tasks), timeout, unit); + } + + private Collection> wrapCallableCollection(Collection> tasks) { + List> ts = new ArrayList<>(); + for (Callable task : tasks) { + if (!(task instanceof LocalComponentTraceCallable)) { + ts.add(new LocalComponentTraceCallable<>(tracer(), traceKeys(), + spanNamer(), this.spanName, task)); + } + } + return ts; + } + + Tracer tracer() { + if (this.tracer == null && this.beanFactory != null) { + this.tracer = this.beanFactory.getBean(Tracer.class); + } + return this.tracer; + } + + TraceKeys traceKeys() { + if (this.traceKeys == null && this.beanFactory != null) { + this.traceKeys = this.beanFactory.getBean(TraceKeys.class); + } + return this.traceKeys; + } + + SpanNamer spanNamer() { + if (this.spanNamer == null && this.beanFactory != null) { + this.spanNamer = this.beanFactory.getBean(SpanNamer.class); + } + return this.spanNamer; + } + +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceableScheduledExecutorService.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceableScheduledExecutorService.java index ac0dc1697..011558ee3 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceableScheduledExecutorService.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceableScheduledExecutorService.java @@ -1,69 +1,69 @@ -/* - * Copyright 2015 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.async; - -import java.util.concurrent.Callable; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; - -import org.springframework.cloud.sleuth.SpanNamer; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.TraceKeys; - -/** - * A decorator class for {@link ScheduledExecutorService} to support tracing in Executors - * - * @author Gaurav Rai Mazra - * @since 1.0.0 - */ -public class TraceableScheduledExecutorService extends TraceableExecutorService implements ScheduledExecutorService { - - public TraceableScheduledExecutorService(ScheduledExecutorService delegate, - Tracer tracer, TraceKeys traceKeys, SpanNamer spanNamer) { - super(delegate, tracer, traceKeys, spanNamer); - } - - private ScheduledExecutorService getScheduledExecutorService() { - return (ScheduledExecutorService) this.delegate; - } - - @Override - public ScheduledFuture schedule(Runnable command, long delay, TimeUnit unit) { - Runnable r = new LocalComponentTraceRunnable(this.tracer, this.traceKeys, this.spanNamer, command); - return getScheduledExecutorService().schedule(r, delay, unit); - } - - @Override - public ScheduledFuture schedule(Callable callable, long delay, TimeUnit unit) { - Callable c = new LocalComponentTraceCallable<>(this.tracer, this.traceKeys, this.spanNamer, callable); - return getScheduledExecutorService().schedule(c, delay, unit); - } - - @Override - public ScheduledFuture scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) { - Runnable r = new LocalComponentTraceRunnable(this.tracer, this.traceKeys, this.spanNamer, command); - return getScheduledExecutorService().scheduleAtFixedRate(r, initialDelay, period, unit); - } - - @Override - public ScheduledFuture scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) { - Runnable r = new LocalComponentTraceRunnable(this.tracer, this.traceKeys, this.spanNamer, command); - return getScheduledExecutorService().scheduleWithFixedDelay(r, initialDelay, delay, unit); - } - -} +/* + * Copyright 2015 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.async; + +import java.util.concurrent.Callable; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import org.springframework.cloud.sleuth.SpanNamer; +import org.springframework.cloud.sleuth.Tracer; +import org.springframework.cloud.sleuth.TraceKeys; + +/** + * A decorator class for {@link ScheduledExecutorService} to support tracing in Executors + * + * @author Gaurav Rai Mazra + * @since 1.0.0 + */ +public class TraceableScheduledExecutorService extends TraceableExecutorService implements ScheduledExecutorService { + + public TraceableScheduledExecutorService(ScheduledExecutorService delegate, + Tracer tracer, TraceKeys traceKeys, SpanNamer spanNamer) { + super(delegate, tracer, traceKeys, spanNamer); + } + + private ScheduledExecutorService getScheduledExecutorService() { + return (ScheduledExecutorService) this.delegate; + } + + @Override + public ScheduledFuture schedule(Runnable command, long delay, TimeUnit unit) { + Runnable r = new LocalComponentTraceRunnable(this.tracer, this.traceKeys, this.spanNamer, command); + return getScheduledExecutorService().schedule(r, delay, unit); + } + + @Override + public ScheduledFuture schedule(Callable callable, long delay, TimeUnit unit) { + Callable c = new LocalComponentTraceCallable<>(this.tracer, this.traceKeys, this.spanNamer, callable); + return getScheduledExecutorService().schedule(c, delay, unit); + } + + @Override + public ScheduledFuture scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) { + Runnable r = new LocalComponentTraceRunnable(this.tracer, this.traceKeys, this.spanNamer, command); + return getScheduledExecutorService().scheduleAtFixedRate(r, initialDelay, period, unit); + } + + @Override + public ScheduledFuture scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) { + Runnable r = new LocalComponentTraceRunnable(this.tracer, this.traceKeys, this.spanNamer, command); + return getScheduledExecutorService().scheduleWithFixedDelay(r, initialDelay, delay, unit); + } + +} diff --git a/spring-cloud-sleuth-dependencies/pom.xml b/spring-cloud-sleuth-dependencies/pom.xml index 8b0bd6a5f..230101c05 100644 --- a/spring-cloud-sleuth-dependencies/pom.xml +++ b/spring-cloud-sleuth-dependencies/pom.xml @@ -5,11 +5,11 @@ spring-cloud-dependencies-parent org.springframework.cloud - 1.2.2.RELEASE + 1.2.3.BUILD-SNAPSHOT spring-cloud-sleuth-dependencies - 1.1.3.RELEASE + 1.1.3.BUILD-SNAPSHOT pom spring-cloud-sleuth-dependencies Spring Cloud Sleuth Dependencies diff --git a/spring-cloud-sleuth-samples/pom.xml b/spring-cloud-sleuth-samples/pom.xml index a5f107740..1cf7bcb82 100644 --- a/spring-cloud-sleuth-samples/pom.xml +++ b/spring-cloud-sleuth-samples/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.3.RELEASE + 1.1.3.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml index 047e73e9b..721ca2542 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml @@ -20,7 +20,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.3.RELEASE + 1.1.3.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml index 898b6d435..f27c33439 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.3.RELEASE + 1.1.3.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml index 5e598f925..1634f92c4 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.3.RELEASE + 1.1.3.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml index 2411f9b00..f10cee8f1 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.3.RELEASE + 1.1.3.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml index 9e7a444f6..ebe795557 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.3.RELEASE + 1.1.3.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml index 8695ff86b..8759ae06d 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.3.RELEASE + 1.1.3.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml index 14c1e85d8..4188686a7 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.3.RELEASE + 1.1.3.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml index 1e89a8b70..e02baaee5 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml @@ -27,7 +27,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.3.RELEASE + 1.1.3.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml index e33213e94..98a4c5da3 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.3.RELEASE + 1.1.3.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-stream/pom.xml b/spring-cloud-sleuth-stream/pom.xml index 83471f825..54bb03684 100644 --- a/spring-cloud-sleuth-stream/pom.xml +++ b/spring-cloud-sleuth-stream/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.3.RELEASE + 1.1.3.BUILD-SNAPSHOT diff --git a/spring-cloud-sleuth-zipkin-stream/pom.xml b/spring-cloud-sleuth-zipkin-stream/pom.xml index 39d7f5b19..68bda6625 100644 --- a/spring-cloud-sleuth-zipkin-stream/pom.xml +++ b/spring-cloud-sleuth-zipkin-stream/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.3.RELEASE + 1.1.3.BUILD-SNAPSHOT diff --git a/spring-cloud-sleuth-zipkin/pom.xml b/spring-cloud-sleuth-zipkin/pom.xml index 4883344c2..3d0c0c511 100644 --- a/spring-cloud-sleuth-zipkin/pom.xml +++ b/spring-cloud-sleuth-zipkin/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.3.RELEASE + 1.1.3.BUILD-SNAPSHOT .. diff --git a/spring-cloud-starter-sleuth/pom.xml b/spring-cloud-starter-sleuth/pom.xml index 65df988c9..a8eb67393 100644 --- a/spring-cloud-starter-sleuth/pom.xml +++ b/spring-cloud-starter-sleuth/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.3.RELEASE + 1.1.3.BUILD-SNAPSHOT .. spring-cloud-starter-sleuth diff --git a/spring-cloud-starter-zipkin/pom.xml b/spring-cloud-starter-zipkin/pom.xml index 08e6139cb..70ab3fc49 100644 --- a/spring-cloud-starter-zipkin/pom.xml +++ b/spring-cloud-starter-zipkin/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.3.RELEASE + 1.1.3.BUILD-SNAPSHOT .. spring-cloud-starter-zipkin From 93afed967cbfaa00aac6a07386a7b11db04cb907 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Fri, 10 Mar 2017 11:16:15 +0100 Subject: [PATCH 226/235] Bumping versions after release --- docs/pom.xml | 2 +- mvnw.cmd | 286 +++++++------- pom.xml | 2 +- spring-cloud-sleuth-core/pom.xml | 2 +- .../async/TraceableExecutorService.java | 348 +++++++++--------- .../TraceableScheduledExecutorService.java | 138 +++---- spring-cloud-sleuth-samples/pom.xml | 2 +- .../spring-cloud-sleuth-sample-feign/pom.xml | 2 +- .../pom.xml | 2 +- .../spring-cloud-sleuth-sample-ribbon/pom.xml | 2 +- .../spring-cloud-sleuth-sample-stream/pom.xml | 2 +- .../pom.xml | 2 +- .../pom.xml | 2 +- .../pom.xml | 2 +- .../spring-cloud-sleuth-sample-zipkin/pom.xml | 2 +- .../spring-cloud-sleuth-sample/pom.xml | 2 +- spring-cloud-sleuth-stream/pom.xml | 2 +- spring-cloud-sleuth-zipkin-stream/pom.xml | 2 +- spring-cloud-sleuth-zipkin/pom.xml | 2 +- spring-cloud-starter-sleuth/pom.xml | 2 +- spring-cloud-starter-zipkin/pom.xml | 2 +- 21 files changed, 404 insertions(+), 404 deletions(-) diff --git a/docs/pom.xml b/docs/pom.xml index 9d5381169..3a375fdfa 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.3.BUILD-SNAPSHOT + 1.1.4.BUILD-SNAPSHOT spring-cloud-sleuth-docs pom diff --git a/mvnw.cmd b/mvnw.cmd index fc8302432..b0dc0e7e9 100644 --- a/mvnw.cmd +++ b/mvnw.cmd @@ -1,145 +1,145 @@ -@REM ---------------------------------------------------------------------------- -@REM Licensed to the Apache Software Foundation (ASF) under one -@REM or more contributor license agreements. See the NOTICE file -@REM distributed with this work for additional information -@REM regarding copyright ownership. The ASF licenses this file -@REM to you under the Apache License, Version 2.0 (the -@REM "License"); you may not use this file except in compliance -@REM with the License. You may obtain a copy of the License at -@REM -@REM http://www.apache.org/licenses/LICENSE-2.0 -@REM -@REM Unless required by applicable law or agreed to in writing, -@REM software distributed under the License is distributed on an -@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -@REM KIND, either express or implied. See the License for the -@REM specific language governing permissions and limitations -@REM under the License. -@REM ---------------------------------------------------------------------------- - -@REM ---------------------------------------------------------------------------- -@REM Maven2 Start Up Batch script -@REM -@REM Required ENV vars: -@REM JAVA_HOME - location of a JDK home dir -@REM -@REM Optional ENV vars -@REM M2_HOME - location of maven2's installed home dir -@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands -@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending -@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven -@REM e.g. to debug Maven itself, use -@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files -@REM ---------------------------------------------------------------------------- - -@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' -@echo off -@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' -@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% - -@REM set %HOME% to equivalent of $HOME -if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") - -@REM Execute a user defined script before this one -if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre -@REM check for pre script, once with legacy .bat ending and once with .cmd ending -if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" -if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" -:skipRcPre - -@setlocal - -set ERROR_CODE=0 - -@REM To isolate internal variables from possible post scripts, we use another setlocal -@setlocal - -@REM ==== START VALIDATION ==== -if not "%JAVA_HOME%" == "" goto OkJHome - -echo. -echo Error: JAVA_HOME not found in your environment. >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -:OkJHome -if exist "%JAVA_HOME%\bin\java.exe" goto init - -echo. -echo Error: JAVA_HOME is set to an invalid directory. >&2 -echo JAVA_HOME = "%JAVA_HOME%" >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -@REM ==== END VALIDATION ==== - -:init - -set MAVEN_CMD_LINE_ARGS=%* - -@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". -@REM Fallback to current working directory if not found. - -set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% -IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir - -set EXEC_DIR=%CD% -set WDIR=%EXEC_DIR% -:findBaseDir -IF EXIST "%WDIR%"\.mvn goto baseDirFound -cd .. -IF "%WDIR%"=="%CD%" goto baseDirNotFound -set WDIR=%CD% -goto findBaseDir - -:baseDirFound -set MAVEN_PROJECTBASEDIR=%WDIR% -cd "%EXEC_DIR%" -goto endDetectBaseDir - -:baseDirNotFound -set MAVEN_PROJECTBASEDIR=%EXEC_DIR% -cd "%EXEC_DIR%" - -:endDetectBaseDir - -IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig - -@setlocal EnableExtensions EnableDelayedExpansion -for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a -@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% - -:endReadAdditionalConfig - -SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven2 Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +set MAVEN_CMD_LINE_ARGS=%* + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" set WRAPPER_JAR="".\.mvn\wrapper\maven-wrapper.jar"" -set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain - -%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CMD_LINE_ARGS% -if ERRORLEVEL 1 goto error -goto end - -:error -set ERROR_CODE=1 - -:end -@endlocal & set ERROR_CODE=%ERROR_CODE% - -if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost -@REM check for post script, once with legacy .bat ending and once with .cmd ending -if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" -if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" -:skipRcPost - -@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' -if "%MAVEN_BATCH_PAUSE%" == "on" pause - -if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% - -exit /B %ERROR_CODE% +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CMD_LINE_ARGS% +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/pom.xml b/pom.xml index 98bc251bf..c03a25d16 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 spring-cloud-sleuth - 1.1.3.BUILD-SNAPSHOT + 1.1.4.BUILD-SNAPSHOT pom Spring Cloud Sleuth Spring Cloud Sleuth diff --git a/spring-cloud-sleuth-core/pom.xml b/spring-cloud-sleuth-core/pom.xml index 422c4855c..228a88e7d 100644 --- a/spring-cloud-sleuth-core/pom.xml +++ b/spring-cloud-sleuth-core/pom.xml @@ -11,7 +11,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.3.BUILD-SNAPSHOT + 1.1.4.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceableExecutorService.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceableExecutorService.java index fbf547f28..0218b8074 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceableExecutorService.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceableExecutorService.java @@ -1,174 +1,174 @@ -/* - * Copyright 2015 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.async; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -import org.springframework.beans.factory.BeanFactory; -import org.springframework.cloud.sleuth.SpanNamer; -import org.springframework.cloud.sleuth.TraceKeys; -import org.springframework.cloud.sleuth.Tracer; - -/** - * A decorator class for {@link ExecutorService} to support tracing in Executors - * - * @author Gaurav Rai Mazra - * @since 1.0.0 - */ -public class TraceableExecutorService implements ExecutorService { - ExecutorService delegate; - Tracer tracer; - private final String spanName; - TraceKeys traceKeys; - SpanNamer spanNamer; - BeanFactory beanFactory; - - public TraceableExecutorService(final ExecutorService delegate, final Tracer tracer, - TraceKeys traceKeys, SpanNamer spanNamer) { - this(delegate, tracer, traceKeys, spanNamer, null); - } - - public TraceableExecutorService(BeanFactory beanFactory, final ExecutorService delegate) { - this.delegate = delegate; - this.beanFactory = beanFactory; - this.spanName = null; - } - - public TraceableExecutorService(final ExecutorService delegate, final Tracer tracer, - TraceKeys traceKeys, SpanNamer spanNamer, String spanName) { - this.delegate = delegate; - this.tracer = tracer; - this.spanName = spanName; - this.traceKeys = traceKeys; - this.spanNamer = spanNamer; - } - - @Override - public void execute(Runnable command) { - final Runnable r = new LocalComponentTraceRunnable(tracer(), traceKeys(), - spanNamer(), command, this.spanName); - this.delegate.execute(r); - } - - @Override - public void shutdown() { - this.delegate.shutdown(); - } - - @Override - public List shutdownNow() { - return this.delegate.shutdownNow(); - } - - @Override - public boolean isShutdown() { - return this.delegate.isShutdown(); - } - - @Override - public boolean isTerminated() { - return this.delegate.isTerminated(); - } - - @Override - public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { - return this.delegate.awaitTermination(timeout, unit); - } - - @Override - public Future submit(Callable task) { - Callable c = new LocalComponentTraceCallable<>(tracer(), traceKeys(), - spanNamer(), this.spanName, task); - return this.delegate.submit(c); - } - - @Override - public Future submit(Runnable task, T result) { - Runnable r = new LocalComponentTraceRunnable(tracer(), traceKeys(), - spanNamer(), task, this.spanName); - return this.delegate.submit(r, result); - } - - @Override - public Future submit(Runnable task) { - Runnable r = new LocalComponentTraceRunnable(tracer(), traceKeys(), - spanNamer(), task, this.spanName); - return this.delegate.submit(r); - } - - @Override - public List> invokeAll(Collection> tasks) throws InterruptedException { - return this.delegate.invokeAll(wrapCallableCollection(tasks)); - } - - @Override - public List> invokeAll(Collection> tasks, long timeout, TimeUnit unit) - throws InterruptedException { - return this.delegate.invokeAll(wrapCallableCollection(tasks), timeout, unit); - } - - @Override - public T invokeAny(Collection> tasks) throws InterruptedException, ExecutionException { - return this.delegate.invokeAny(wrapCallableCollection(tasks)); - } - - @Override - public T invokeAny(Collection> tasks, long timeout, TimeUnit unit) - throws InterruptedException, ExecutionException, TimeoutException { - return this.delegate.invokeAny(wrapCallableCollection(tasks), timeout, unit); - } - - private Collection> wrapCallableCollection(Collection> tasks) { - List> ts = new ArrayList<>(); - for (Callable task : tasks) { - if (!(task instanceof LocalComponentTraceCallable)) { - ts.add(new LocalComponentTraceCallable<>(tracer(), traceKeys(), - spanNamer(), this.spanName, task)); - } - } - return ts; - } - - Tracer tracer() { - if (this.tracer == null && this.beanFactory != null) { - this.tracer = this.beanFactory.getBean(Tracer.class); - } - return this.tracer; - } - - TraceKeys traceKeys() { - if (this.traceKeys == null && this.beanFactory != null) { - this.traceKeys = this.beanFactory.getBean(TraceKeys.class); - } - return this.traceKeys; - } - - SpanNamer spanNamer() { - if (this.spanNamer == null && this.beanFactory != null) { - this.spanNamer = this.beanFactory.getBean(SpanNamer.class); - } - return this.spanNamer; - } - -} +/* + * Copyright 2015 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.async; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.cloud.sleuth.SpanNamer; +import org.springframework.cloud.sleuth.TraceKeys; +import org.springframework.cloud.sleuth.Tracer; + +/** + * A decorator class for {@link ExecutorService} to support tracing in Executors + * + * @author Gaurav Rai Mazra + * @since 1.0.0 + */ +public class TraceableExecutorService implements ExecutorService { + ExecutorService delegate; + Tracer tracer; + private final String spanName; + TraceKeys traceKeys; + SpanNamer spanNamer; + BeanFactory beanFactory; + + public TraceableExecutorService(final ExecutorService delegate, final Tracer tracer, + TraceKeys traceKeys, SpanNamer spanNamer) { + this(delegate, tracer, traceKeys, spanNamer, null); + } + + public TraceableExecutorService(BeanFactory beanFactory, final ExecutorService delegate) { + this.delegate = delegate; + this.beanFactory = beanFactory; + this.spanName = null; + } + + public TraceableExecutorService(final ExecutorService delegate, final Tracer tracer, + TraceKeys traceKeys, SpanNamer spanNamer, String spanName) { + this.delegate = delegate; + this.tracer = tracer; + this.spanName = spanName; + this.traceKeys = traceKeys; + this.spanNamer = spanNamer; + } + + @Override + public void execute(Runnable command) { + final Runnable r = new LocalComponentTraceRunnable(tracer(), traceKeys(), + spanNamer(), command, this.spanName); + this.delegate.execute(r); + } + + @Override + public void shutdown() { + this.delegate.shutdown(); + } + + @Override + public List shutdownNow() { + return this.delegate.shutdownNow(); + } + + @Override + public boolean isShutdown() { + return this.delegate.isShutdown(); + } + + @Override + public boolean isTerminated() { + return this.delegate.isTerminated(); + } + + @Override + public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { + return this.delegate.awaitTermination(timeout, unit); + } + + @Override + public Future submit(Callable task) { + Callable c = new LocalComponentTraceCallable<>(tracer(), traceKeys(), + spanNamer(), this.spanName, task); + return this.delegate.submit(c); + } + + @Override + public Future submit(Runnable task, T result) { + Runnable r = new LocalComponentTraceRunnable(tracer(), traceKeys(), + spanNamer(), task, this.spanName); + return this.delegate.submit(r, result); + } + + @Override + public Future submit(Runnable task) { + Runnable r = new LocalComponentTraceRunnable(tracer(), traceKeys(), + spanNamer(), task, this.spanName); + return this.delegate.submit(r); + } + + @Override + public List> invokeAll(Collection> tasks) throws InterruptedException { + return this.delegate.invokeAll(wrapCallableCollection(tasks)); + } + + @Override + public List> invokeAll(Collection> tasks, long timeout, TimeUnit unit) + throws InterruptedException { + return this.delegate.invokeAll(wrapCallableCollection(tasks), timeout, unit); + } + + @Override + public T invokeAny(Collection> tasks) throws InterruptedException, ExecutionException { + return this.delegate.invokeAny(wrapCallableCollection(tasks)); + } + + @Override + public T invokeAny(Collection> tasks, long timeout, TimeUnit unit) + throws InterruptedException, ExecutionException, TimeoutException { + return this.delegate.invokeAny(wrapCallableCollection(tasks), timeout, unit); + } + + private Collection> wrapCallableCollection(Collection> tasks) { + List> ts = new ArrayList<>(); + for (Callable task : tasks) { + if (!(task instanceof LocalComponentTraceCallable)) { + ts.add(new LocalComponentTraceCallable<>(tracer(), traceKeys(), + spanNamer(), this.spanName, task)); + } + } + return ts; + } + + Tracer tracer() { + if (this.tracer == null && this.beanFactory != null) { + this.tracer = this.beanFactory.getBean(Tracer.class); + } + return this.tracer; + } + + TraceKeys traceKeys() { + if (this.traceKeys == null && this.beanFactory != null) { + this.traceKeys = this.beanFactory.getBean(TraceKeys.class); + } + return this.traceKeys; + } + + SpanNamer spanNamer() { + if (this.spanNamer == null && this.beanFactory != null) { + this.spanNamer = this.beanFactory.getBean(SpanNamer.class); + } + return this.spanNamer; + } + +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceableScheduledExecutorService.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceableScheduledExecutorService.java index 011558ee3..ac0dc1697 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceableScheduledExecutorService.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceableScheduledExecutorService.java @@ -1,69 +1,69 @@ -/* - * Copyright 2015 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.async; - -import java.util.concurrent.Callable; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; - -import org.springframework.cloud.sleuth.SpanNamer; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.TraceKeys; - -/** - * A decorator class for {@link ScheduledExecutorService} to support tracing in Executors - * - * @author Gaurav Rai Mazra - * @since 1.0.0 - */ -public class TraceableScheduledExecutorService extends TraceableExecutorService implements ScheduledExecutorService { - - public TraceableScheduledExecutorService(ScheduledExecutorService delegate, - Tracer tracer, TraceKeys traceKeys, SpanNamer spanNamer) { - super(delegate, tracer, traceKeys, spanNamer); - } - - private ScheduledExecutorService getScheduledExecutorService() { - return (ScheduledExecutorService) this.delegate; - } - - @Override - public ScheduledFuture schedule(Runnable command, long delay, TimeUnit unit) { - Runnable r = new LocalComponentTraceRunnable(this.tracer, this.traceKeys, this.spanNamer, command); - return getScheduledExecutorService().schedule(r, delay, unit); - } - - @Override - public ScheduledFuture schedule(Callable callable, long delay, TimeUnit unit) { - Callable c = new LocalComponentTraceCallable<>(this.tracer, this.traceKeys, this.spanNamer, callable); - return getScheduledExecutorService().schedule(c, delay, unit); - } - - @Override - public ScheduledFuture scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) { - Runnable r = new LocalComponentTraceRunnable(this.tracer, this.traceKeys, this.spanNamer, command); - return getScheduledExecutorService().scheduleAtFixedRate(r, initialDelay, period, unit); - } - - @Override - public ScheduledFuture scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) { - Runnable r = new LocalComponentTraceRunnable(this.tracer, this.traceKeys, this.spanNamer, command); - return getScheduledExecutorService().scheduleWithFixedDelay(r, initialDelay, delay, unit); - } - -} +/* + * Copyright 2015 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.async; + +import java.util.concurrent.Callable; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import org.springframework.cloud.sleuth.SpanNamer; +import org.springframework.cloud.sleuth.Tracer; +import org.springframework.cloud.sleuth.TraceKeys; + +/** + * A decorator class for {@link ScheduledExecutorService} to support tracing in Executors + * + * @author Gaurav Rai Mazra + * @since 1.0.0 + */ +public class TraceableScheduledExecutorService extends TraceableExecutorService implements ScheduledExecutorService { + + public TraceableScheduledExecutorService(ScheduledExecutorService delegate, + Tracer tracer, TraceKeys traceKeys, SpanNamer spanNamer) { + super(delegate, tracer, traceKeys, spanNamer); + } + + private ScheduledExecutorService getScheduledExecutorService() { + return (ScheduledExecutorService) this.delegate; + } + + @Override + public ScheduledFuture schedule(Runnable command, long delay, TimeUnit unit) { + Runnable r = new LocalComponentTraceRunnable(this.tracer, this.traceKeys, this.spanNamer, command); + return getScheduledExecutorService().schedule(r, delay, unit); + } + + @Override + public ScheduledFuture schedule(Callable callable, long delay, TimeUnit unit) { + Callable c = new LocalComponentTraceCallable<>(this.tracer, this.traceKeys, this.spanNamer, callable); + return getScheduledExecutorService().schedule(c, delay, unit); + } + + @Override + public ScheduledFuture scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) { + Runnable r = new LocalComponentTraceRunnable(this.tracer, this.traceKeys, this.spanNamer, command); + return getScheduledExecutorService().scheduleAtFixedRate(r, initialDelay, period, unit); + } + + @Override + public ScheduledFuture scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) { + Runnable r = new LocalComponentTraceRunnable(this.tracer, this.traceKeys, this.spanNamer, command); + return getScheduledExecutorService().scheduleWithFixedDelay(r, initialDelay, delay, unit); + } + +} diff --git a/spring-cloud-sleuth-samples/pom.xml b/spring-cloud-sleuth-samples/pom.xml index 1cf7bcb82..fe0ff7a22 100644 --- a/spring-cloud-sleuth-samples/pom.xml +++ b/spring-cloud-sleuth-samples/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.3.BUILD-SNAPSHOT + 1.1.4.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml index 721ca2542..ef88ada47 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml @@ -20,7 +20,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.3.BUILD-SNAPSHOT + 1.1.4.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml index f27c33439..e63c8be64 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.3.BUILD-SNAPSHOT + 1.1.4.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml index 1634f92c4..18cbf499a 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-ribbon/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.3.BUILD-SNAPSHOT + 1.1.4.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml index f10cee8f1..673e3b916 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-stream/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.3.BUILD-SNAPSHOT + 1.1.4.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml index ebe795557..9be760491 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.3.BUILD-SNAPSHOT + 1.1.4.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml index 8759ae06d..9e03dc5e1 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-websocket/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.3.BUILD-SNAPSHOT + 1.1.4.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml index 4188686a7..c67871081 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.3.BUILD-SNAPSHOT + 1.1.4.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml index e02baaee5..4cfcbb287 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml @@ -27,7 +27,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.3.BUILD-SNAPSHOT + 1.1.4.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml index 98a4c5da3..97e27166b 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth-samples - 1.1.3.BUILD-SNAPSHOT + 1.1.4.BUILD-SNAPSHOT .. diff --git a/spring-cloud-sleuth-stream/pom.xml b/spring-cloud-sleuth-stream/pom.xml index 54bb03684..25552b75e 100644 --- a/spring-cloud-sleuth-stream/pom.xml +++ b/spring-cloud-sleuth-stream/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.3.BUILD-SNAPSHOT + 1.1.4.BUILD-SNAPSHOT diff --git a/spring-cloud-sleuth-zipkin-stream/pom.xml b/spring-cloud-sleuth-zipkin-stream/pom.xml index 68bda6625..ab8ed5db7 100644 --- a/spring-cloud-sleuth-zipkin-stream/pom.xml +++ b/spring-cloud-sleuth-zipkin-stream/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.3.BUILD-SNAPSHOT + 1.1.4.BUILD-SNAPSHOT diff --git a/spring-cloud-sleuth-zipkin/pom.xml b/spring-cloud-sleuth-zipkin/pom.xml index 3d0c0c511..82b98d6eb 100644 --- a/spring-cloud-sleuth-zipkin/pom.xml +++ b/spring-cloud-sleuth-zipkin/pom.xml @@ -28,7 +28,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.3.BUILD-SNAPSHOT + 1.1.4.BUILD-SNAPSHOT .. diff --git a/spring-cloud-starter-sleuth/pom.xml b/spring-cloud-starter-sleuth/pom.xml index a8eb67393..d4d56eee7 100644 --- a/spring-cloud-starter-sleuth/pom.xml +++ b/spring-cloud-starter-sleuth/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.3.BUILD-SNAPSHOT + 1.1.4.BUILD-SNAPSHOT .. spring-cloud-starter-sleuth diff --git a/spring-cloud-starter-zipkin/pom.xml b/spring-cloud-starter-zipkin/pom.xml index 70ab3fc49..f7b824459 100644 --- a/spring-cloud-starter-zipkin/pom.xml +++ b/spring-cloud-starter-zipkin/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-sleuth - 1.1.3.BUILD-SNAPSHOT + 1.1.4.BUILD-SNAPSHOT .. spring-cloud-starter-zipkin From 035caf1ec86990b7c6f9cf30bd18594ee04f3c47 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Fri, 10 Mar 2017 11:21:49 +0100 Subject: [PATCH 227/235] Updated wrong version for sc-sleuth-deps --- spring-cloud-sleuth-dependencies/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-cloud-sleuth-dependencies/pom.xml b/spring-cloud-sleuth-dependencies/pom.xml index 230101c05..336dd2d53 100644 --- a/spring-cloud-sleuth-dependencies/pom.xml +++ b/spring-cloud-sleuth-dependencies/pom.xml @@ -9,7 +9,7 @@ spring-cloud-sleuth-dependencies - 1.1.3.BUILD-SNAPSHOT + 1.1.4.BUILD-SNAPSHOT pom spring-cloud-sleuth-dependencies Spring Cloud Sleuth Dependencies From 53f8f9eaa37e87c2671a147185e946cfae66e322 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Mon, 13 Mar 2017 09:50:42 +0100 Subject: [PATCH 228/235] Bumped versions --- benchmarks/pom.xml | 2 +- pom.xml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/benchmarks/pom.xml b/benchmarks/pom.xml index 6a74d51b5..73aad6c72 100644 --- a/benchmarks/pom.xml +++ b/benchmarks/pom.xml @@ -17,7 +17,7 @@ Benchmarks Benchmarks (JMH) org.springframework.cloud - 1.1.3.BUILD-SNAPSHOT + 1.1.4.BUILD-SNAPSHOT benchmarks diff --git a/pom.xml b/pom.xml index c03a25d16..0902f7558 100644 --- a/pom.xml +++ b/pom.xml @@ -235,9 +235,9 @@ 2.19.1 2.17 1.2.3.BUILD-SNAPSHOT - 1.1.8.BUILD-SNAPSHOT + 1.1.9.BUILD-SNAPSHOT Brooklyn.BUILD-SNAPSHOT - 1.2.6.BUILD-SNAPSHOT + 1.2.7.BUILD-SNAPSHOT From e9480898b500cac8bcfd65bd6b0c58bca4f690cd Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Wed, 15 Mar 2017 12:57:44 +0100 Subject: [PATCH 229/235] Publish artifact for samples-test-core --- .../pom.xml | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml index 9be760491..9f3467ff8 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml @@ -39,17 +39,6 @@ - - - - - maven-deploy-plugin - - true - - - - @@ -64,6 +53,12 @@ + + maven-deploy-plugin + + false + + From 327ce80c384223846eca54ec3b60de3ef6fda829 Mon Sep 17 00:00:00 2001 From: Denis Stepanov Date: Wed, 22 Mar 2017 10:43:56 +0100 Subject: [PATCH 230/235] Use HystrixFeign only if it's present on classpath (#544) --- .../web/client/feign/TraceFeignClientAutoConfiguration.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientAutoConfiguration.java index 6c866afa3..258f5d991 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientAutoConfiguration.java @@ -52,7 +52,7 @@ public class TraceFeignClientAutoConfiguration { @Bean @Scope("prototype") - @ConditionalOnClass(name = "com.netflix.hystrix.HystrixCommand") + @ConditionalOnClass(name = {"com.netflix.hystrix.HystrixCommand", "feign.hystrix.HystrixFeign"}) @ConditionalOnProperty(name = "feign.hystrix.enabled", matchIfMissing = true) Feign.Builder feignHystrixBuilder(BeanFactory beanFactory) { return SleuthHystrixFeignBuilder.builder(beanFactory); From e316e8bc3d853213aadbb4dd4ca9b2f2c7294c09 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Mon, 10 Apr 2017 16:48:50 +0200 Subject: [PATCH 231/235] Stopped producing warnings if the current span is null fixes #552 --- .../cloud/sleuth/trace/DefaultTracer.java | 16 +++++++++++----- .../cloud/sleuth/trace/DefaultTracerTests.java | 15 +++++++++++++++ 2 files changed, 26 insertions(+), 5 deletions(-) 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 b5728856a..40c2f104b 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 @@ -16,9 +16,12 @@ package org.springframework.cloud.sleuth.trace; +import java.lang.invoke.MethodHandles; import java.util.Random; import java.util.concurrent.Callable; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.cloud.sleuth.Sampler; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.SpanNamer; @@ -38,6 +41,8 @@ import org.springframework.cloud.sleuth.util.SpanNameUtil; */ public class DefaultTracer implements Tracer { + private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); + private static final int MAX_CHARS_IN_SPAN_NAME = 50; private final Sampler defaultSampler; @@ -102,17 +107,18 @@ public class DefaultTracer implements Tracer { return continueSpan(span); } - private String shortenNameIfNecessary(String name) { - int maxLength = name.length() > MAX_CHARS_IN_SPAN_NAME ? MAX_CHARS_IN_SPAN_NAME : name.length(); - return name.substring(0, maxLength); - } - @Override public Span detach(Span span) { if (span == null) { return null; } Span cur = SpanContextHolder.getCurrentSpan(); + if (cur == null) { + if (log.isTraceEnabled()) { + log.trace("Span in the context is null so something has already detached the span. Won't do anything about it"); + } + return null; + } if (!span.equals(cur)) { ExceptionUtils.warn("Tried to detach trace span but " + "it is not the current span: " + span + ". You may have forgotten to close or detach " + cur); 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 8bcd56b0f..eb19e9115 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 @@ -22,9 +22,11 @@ import java.util.Random; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Mockito; +import org.springframework.boot.test.rule.OutputCapture; import org.springframework.cloud.sleuth.DefaultSpanNamer; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.SpanNamer; @@ -54,6 +56,7 @@ public class DefaultTracerTests { private SpanNamer spanNamer = new DefaultSpanNamer(); private SpanLogger spanLogger = Mockito.mock(SpanLogger.class); private SpanReporter spanReporter = Mockito.mock(SpanReporter.class); + @Rule public OutputCapture capture = new OutputCapture(); @Before public void setup() { @@ -206,6 +209,18 @@ public class DefaultTracerTests { then(child.getName().length()).isEqualTo(50); } + @Test + public void shouldNotProduceAWarningMessageWhenThereIsNoSpanInContextAndWeDetachASpan() { + DefaultTracer tracer = new DefaultTracer(new AlwaysSampler(), new Random(), + this.spanNamer, this.spanLogger, this.spanReporter); + Span span = Span.builder().name("foo").traceId(1L).spanId(1L).build(); + + Span child = tracer.detach(span); + + then(child).isNull(); + then(this.capture.toString()).doesNotContain("Tried to detach trace span"); + } + private String bigName() { StringBuilder sb = new StringBuilder(); for (int i = 0; i < 60; i++) { From 21d73f6ed632ab9ce1dd4cfb6e7f269605bc07cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Szyma=C5=84ski?= Date: Mon, 10 Apr 2017 17:09:23 +0200 Subject: [PATCH 232/235] Grok pattern fix (#559) --- README.adoc | 4 ++-- docs/src/main/asciidoc/intro.adoc | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.adoc b/README.adoc index 6ee18ce72..6dba687a5 100644 --- a/README.adoc +++ b/README.adoc @@ -153,7 +153,7 @@ If you want to use https://www.elastic.co/guide/en/logstash/current/index.html[L filter { # pattern matching logback pattern grok { - match => { "message" => "%{TIMESTAMP_ISO8601:timestamp}\s+%{LOGLEVEL:severity}\s+\[%{DATA:service},%{DATA:trace},%{DATA:span},%{DATA:exportable}\]\s+%{DATA:pid}---\s+\[%{DATA:thread}\]\s+%{DATA:class}\s+:\s+%{GREEDYDATA:rest}" } + match => { "message" => "%{TIMESTAMP_ISO8601:timestamp}\s+%{LOGLEVEL:severity}\s+\[%{DATA:service},%{DATA:trace},%{DATA:span},%{DATA:exportable}\]\s+%{DATA:pid}\s+---\s+\[%{DATA:thread}\]\s+%{DATA:class}\s+:\s+%{GREEDYDATA:rest}" } } } @@ -162,7 +162,7 @@ NOTE: If you want to use Grok together with the logs from Cloud Foundry you have filter { # pattern matching logback pattern grok { - match => { "message" => "(?m)OUT\s+%{TIMESTAMP_ISO8601:timestamp}\s+%{LOGLEVEL:severity}\s+\[%{DATA:service},%{DATA:trace},%{DATA:span},%{DATA:exportable}\]\s+%{DATA:pid}---\s+\[%{DATA:thread}\]\s+%{DATA:class}\s+:\s+%{GREEDYDATA:rest}" } + match => { "message" => "(?m)OUT\s+%{TIMESTAMP_ISO8601:timestamp}\s+%{LOGLEVEL:severity}\s+\[%{DATA:service},%{DATA:trace},%{DATA:span},%{DATA:exportable}\]\s+%{DATA:pid}\s+---\s+\[%{DATA:thread}\]\s+%{DATA:class}\s+:\s+%{GREEDYDATA:rest}" } } } diff --git a/docs/src/main/asciidoc/intro.adoc b/docs/src/main/asciidoc/intro.adoc index df79f5900..c16bc0396 100644 --- a/docs/src/main/asciidoc/intro.adoc +++ b/docs/src/main/asciidoc/intro.adoc @@ -144,7 +144,7 @@ If you want to use https://www.elastic.co/guide/en/logstash/current/index.html[L filter { # pattern matching logback pattern grok { - match => { "message" => "%{TIMESTAMP_ISO8601:timestamp}\s+%{LOGLEVEL:severity}\s+\[%{DATA:service},%{DATA:trace},%{DATA:span},%{DATA:exportable}\]\s+%{DATA:pid}---\s+\[%{DATA:thread}\]\s+%{DATA:class}\s+:\s+%{GREEDYDATA:rest}" } + match => { "message" => "%{TIMESTAMP_ISO8601:timestamp}\s+%{LOGLEVEL:severity}\s+\[%{DATA:service},%{DATA:trace},%{DATA:span},%{DATA:exportable}\]\s+%{DATA:pid}\s+---\s+\[%{DATA:thread}\]\s+%{DATA:class}\s+:\s+%{GREEDYDATA:rest}" } } } @@ -153,7 +153,7 @@ NOTE: If you want to use Grok together with the logs from Cloud Foundry you have filter { # pattern matching logback pattern grok { - match => { "message" => "(?m)OUT\s+%{TIMESTAMP_ISO8601:timestamp}\s+%{LOGLEVEL:severity}\s+\[%{DATA:service},%{DATA:trace},%{DATA:span},%{DATA:exportable}\]\s+%{DATA:pid}---\s+\[%{DATA:thread}\]\s+%{DATA:class}\s+:\s+%{GREEDYDATA:rest}" } + match => { "message" => "(?m)OUT\s+%{TIMESTAMP_ISO8601:timestamp}\s+%{LOGLEVEL:severity}\s+\[%{DATA:service},%{DATA:trace},%{DATA:span},%{DATA:exportable}\]\s+%{DATA:pid}\s+---\s+\[%{DATA:thread}\]\s+%{DATA:class}\s+:\s+%{GREEDYDATA:rest}" } } } From cce5b18decb9849be3cd7ae39f04daa9bf4d586f Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Tue, 11 Apr 2017 14:16:25 +0200 Subject: [PATCH 233/235] Wrapping callbacks in trace representation without this change when user adds a callback then it's not wrapped in a trace representation, thus when executed in a separate thread, it's then missing all the tracing information with this change we're wrapping the future in our representation that passes the tracing information around fixes #546 --- .../web/client/TraceAsyncRestTemplate.java | 145 +++++++++++++++++- .../async/issues/issue546/Issue546Tests.java | 136 ++++++++++++++++ 2 files changed, 276 insertions(+), 5 deletions(-) create mode 100644 spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue546/Issue546Tests.java diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncRestTemplate.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncRestTemplate.java index 097b87f67..dd5d6374b 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncRestTemplate.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncRestTemplate.java @@ -18,6 +18,9 @@ package org.springframework.cloud.sleuth.instrument.web.client; import java.lang.invoke.MethodHandles; import java.net.URI; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -28,8 +31,10 @@ import org.springframework.core.task.AsyncListenableTaskExecutor; import org.springframework.http.HttpMethod; import org.springframework.http.client.AsyncClientHttpRequestFactory; import org.springframework.http.client.ClientHttpRequestFactory; +import org.springframework.util.concurrent.FailureCallback; import org.springframework.util.concurrent.ListenableFuture; import org.springframework.util.concurrent.ListenableFutureCallback; +import org.springframework.util.concurrent.SuccessCallback; import org.springframework.web.client.AsyncRequestCallback; import org.springframework.web.client.AsyncRestTemplate; import org.springframework.web.client.ResponseExtractor; @@ -80,14 +85,144 @@ public class TraceAsyncRestTemplate extends AsyncRestTemplate { protected ListenableFuture doExecute(URI url, HttpMethod method, AsyncRequestCallback requestCallback, ResponseExtractor responseExtractor) throws RestClientException { - ListenableFuture future = super.doExecute(url, method, requestCallback, responseExtractor); - Span span = this.tracer.getCurrentSpan(); + final ListenableFuture future = super.doExecute(url, method, requestCallback, responseExtractor); + final Span span = this.tracer.getCurrentSpan(); future.addCallback(new TraceListenableFutureCallback<>(this.tracer, span)); // potential race can happen here if (span != null && span.equals(this.tracer.getCurrentSpan())) { this.tracer.detach(span); } - return future; + return new ListenableFuture() { + + @Override public boolean cancel(boolean mayInterruptIfRunning) { + return future.cancel(mayInterruptIfRunning); + } + + @Override public boolean isCancelled() { + return future.isCancelled(); + } + + @Override public boolean isDone() { + return future.isDone(); + } + + @Override public T get() throws InterruptedException, ExecutionException { + return future.get(); + } + + @Override public T get(long timeout, TimeUnit unit) + throws InterruptedException, ExecutionException, TimeoutException { + return future.get(timeout, unit); + } + + @Override + public void addCallback(ListenableFutureCallback callback) { + future.addCallback(new TraceListenableFutureCallbackWrapper<>(TraceAsyncRestTemplate.this.tracer, span, callback)); + } + + @Override public void addCallback(SuccessCallback successCallback, + FailureCallback failureCallback) { + future.addCallback( + new TraceSuccessCallback<>(TraceAsyncRestTemplate.this.tracer, span, successCallback), + new TraceFailureCallback(TraceAsyncRestTemplate.this.tracer, span, failureCallback)); + } + }; + } + + private static class TraceSuccessCallback implements SuccessCallback { + + private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); + + private final Tracer tracer; + private final Span parent; + private final SuccessCallback delegate; + + private TraceSuccessCallback(Tracer tracer, Span parent, + SuccessCallback delegate) { + this.tracer = tracer; + this.parent = parent; + this.delegate = delegate; + } + + @Override public void onSuccess(T result) { + continueSpan(); + if (log.isDebugEnabled()) { + log.debug("Calling on success of the delegate"); + } + this.delegate.onSuccess(result); + finish(); + } + + private void continueSpan() { + this.tracer.continueSpan(this.parent); + } + + private void finish() { + this.tracer.detach(currentSpan()); + } + + private Span currentSpan() { + return this.tracer.getCurrentSpan(); + } + } + + private static class TraceFailureCallback implements FailureCallback { + + private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); + + private final Tracer tracer; + private final Span parent; + private final FailureCallback delegate; + + private TraceFailureCallback(Tracer tracer, Span parent, + FailureCallback delegate) { + this.tracer = tracer; + this.parent = parent; + this.delegate = delegate; + } + + @Override public void onFailure(Throwable ex) { + continueSpan(); + if (log.isDebugEnabled()) { + log.debug("Calling on failure of the delegate"); + } + this.delegate.onFailure(ex); + finish(); + } + + private void continueSpan() { + this.tracer.continueSpan(this.parent); + } + + private void finish() { + this.tracer.detach(currentSpan()); + } + + private Span currentSpan() { + return this.tracer.getCurrentSpan(); + } + } + + private static class TraceListenableFutureCallbackWrapper implements ListenableFutureCallback { + + private final Tracer tracer; + private final Span parent; + private final ListenableFutureCallback delegate; + + private TraceListenableFutureCallbackWrapper(Tracer tracer, Span parent, + ListenableFutureCallback delegate) { + this.tracer = tracer; + this.parent = parent; + this.delegate = delegate; + } + + @Override public void onFailure(Throwable ex) { + new TraceFailureCallback(this.tracer, this.parent, this.delegate).onFailure(ex); + } + + @Override public void onSuccess(T result) { + new TraceSuccessCallback<>(this.tracer, this.parent, this.delegate).onSuccess(result); + } } private static class TraceListenableFutureCallback implements ListenableFutureCallback { @@ -104,20 +239,20 @@ public class TraceAsyncRestTemplate extends AsyncRestTemplate { @Override public void onFailure(Throwable ex) { + continueSpan(); if (log.isDebugEnabled()) { log.debug("The callback failed - will close the span"); } - continueSpan(); this.tracer.addTag(Span.SPAN_ERROR_TAG_NAME, ExceptionUtils.getExceptionMessage(ex)); finish(); } @Override public void onSuccess(T result) { + continueSpan(); if (log.isDebugEnabled()) { log.debug("The callback succeeded - will close the span"); } - continueSpan(); finish(); } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue546/Issue546Tests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue546/Issue546Tests.java new file mode 100644 index 000000000..60d29a2c7 --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue546/Issue546Tests.java @@ -0,0 +1,136 @@ +/* + * 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.async.issues.issue546; + +import java.lang.invoke.MethodHandles; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.sleuth.Tracer; +import org.springframework.core.env.Environment; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.util.concurrent.ListenableFuture; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.client.AsyncRestTemplate; +import org.springframework.web.client.RestTemplate; + +import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; + +/** + * @author Marcin Grzejszczak + */ +@RunWith(SpringRunner.class) +@SpringBootTest(classes = Issue546TestsApp.class, + properties = {"ribbon.eureka.enabled=false", "feign.hystrix.enabled=false", "server.port=0"}, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class Issue546Tests { + + @Autowired Environment environment; + + @Test + public void should_pass_tracing_info_when_using_callbacks() { + new RestTemplate() + .getForObject("http://localhost:" + port() + "/trace-async-rest-template", + String.class); + } + + private int port() { + return this.environment.getProperty("local.server.port", Integer.class); + } +} + +@SpringBootApplication +class Issue546TestsApp { + +} + +@RestController +class Controller { + private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); + + private final AsyncRestTemplate traceAsyncRestTemplate; + private final Tracer tracer; + + public Controller(AsyncRestTemplate traceAsyncRestTemplate, Tracer tracer) { + this.traceAsyncRestTemplate = traceAsyncRestTemplate; + this.tracer = tracer; + } + + @Value("${server.port}") private String port; + + @RequestMapping(value = "/bean") public HogeBean bean() { + log.info("(/bean) I got a request!"); + return new HogeBean("test", 18); + } + + @RequestMapping(value = "/trace-async-rest-template") + public void asyncTest(@RequestParam(required = false) boolean isSleep) + throws InterruptedException { + log.info("(/trace-async-rest-template) I got a request!"); + final long traceId = tracer.getCurrentSpan().getTraceId(); + ListenableFuture> res = traceAsyncRestTemplate + .getForEntity("http://localhost:" + port + "/bean", HogeBean.class); + if (isSleep) { + Thread.sleep(1000); + } + res.addCallback(success -> { + then(Controller.this.tracer.getCurrentSpan()).hasTraceIdEqualTo(traceId); + log.info("(/trace-async-rest-template) success"); + then(Controller.this.tracer.getCurrentSpan()).hasTraceIdEqualTo(traceId); + }, failure -> { + then(Controller.this.tracer.getCurrentSpan()).hasTraceIdEqualTo(traceId); + log.error("(/trace-async-rest-template) failure", failure); + then(Controller.this.tracer.getCurrentSpan()).hasTraceIdEqualTo(traceId); + }); + } + +} + +class HogeBean { + private String name; + private int age; + + public HogeBean(String name, int age) { + this.name = name; + this.age = age; + } + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + + public int getAge() { + return this.age; + } + + public void setAge(int age) { + this.age = age; + } +} \ No newline at end of file From ab0f785cd6cbe41dda4b4c39ab61f2a865309b2b Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Tue, 11 Apr 2017 14:17:22 +0200 Subject: [PATCH 234/235] Bumping mvnw to 3.5.0 fixes #561 --- .mvn/wrapper/maven-wrapper.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties index 6637cedb2..56bb0164e 100644 --- a/.mvn/wrapper/maven-wrapper.properties +++ b/.mvn/wrapper/maven-wrapper.properties @@ -1 +1 @@ -distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.3.9/apache-maven-3.3.9-bin.zip \ No newline at end of file +distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.0/apache-maven-3.5.0-bin.zip \ No newline at end of file From 46257ea3701c2d67b4ea174362ce521b40a3a651 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Wed, 12 Apr 2017 12:02:39 +0200 Subject: [PATCH 235/235] Moved an executor aspect to a bean post processor without this change when an executor implementation was a final class or had a final method - the application wouldn't start or would produce NPE at startup. with this change we have a custom bean post processor that wraps an aspect manualy either via a JDK Proxy when a class is final or a method is final or creates a CGLIB proxy if that's possible. fixes #550 --- .../app/SleuthBenchmarkingSpringApp.java | 2 +- .../jmh/benchmarks/AsyncBenchmarks.java | 2 +- .../jmh/benchmarks/HttpFilterBenchmarks.java | 2 +- .../jmh/benchmarks/RestTemplateBenchmark.java | 2 +- .../jmh/RunSleuthJmhBenchmarksFromIde.java | 2 +- benchmarks/src/test/jmeter/jmeter.properties | 2 +- .../cloud/sleuth/DefaultSpanNamer.java | 2 +- .../org/springframework/cloud/sleuth/Log.java | 2 +- .../cloud/sleuth/NoOpSpanReporter.java | 2 +- .../springframework/cloud/sleuth/Span.java | 2 +- .../cloud/sleuth/SpanExtractor.java | 2 +- .../cloud/sleuth/SpanInjector.java | 2 +- .../cloud/sleuth/SpanName.java | 2 +- .../cloud/sleuth/SpanNamer.java | 2 +- .../cloud/sleuth/SpanReporter.java | 2 +- .../cloud/sleuth/TraceCallable.java | 2 +- .../cloud/sleuth/TraceKeys.java | 2 +- .../cloud/sleuth/TraceRunnable.java | 2 +- .../async/AsyncDefaultAutoConfiguration.java | 5 + .../async/ExecutorBeanPostProcessor.java | 98 +++++++++++++++++++ .../LazyTraceThreadPoolTaskExecutor.java | 2 +- .../async/LocalComponentTraceCallable.java | 2 +- .../async/LocalComponentTraceRunnable.java | 2 +- .../async/SpanContinuingTraceCallable.java | 2 +- .../async/SpanContinuingTraceRunnable.java | 2 +- .../instrument/async/TraceAsyncAspect.java | 14 +-- .../async/TraceContinuingCallable.java | 2 +- .../SleuthHystrixConcurrencyStrategy.java | 2 +- .../messaging/MessagingSpanExtractor.java | 2 +- .../messaging/MessagingSpanInjector.java | 2 +- .../messaging/TraceMessageHeaders.java | 2 +- .../TraceSpanMessagingAutoConfiguration.java | 2 +- .../scheduling/TraceSchedulingAspect.java | 2 +- .../TraceSchedulingAutoConfiguration.java | 2 +- .../web/HttpServletRequestExtractor.java | 2 +- .../sleuth/instrument/web/ServletUtils.java | 2 +- .../sleuth/instrument/web/SsLogSetter.java | 2 +- .../sleuth/instrument/web/TraceFilter.java | 2 +- .../web/TraceHandlerInterceptor.java | 2 +- .../web/TraceHttpAutoConfiguration.java | 2 +- .../web/TraceHttpServletResponse.java | 2 +- .../instrument/web/TracePrintWriter.java | 2 +- .../web/TraceRequestAttributes.java | 2 +- .../web/TraceServletOutputStream.java | 2 +- .../web/TraceSpringDataBeanPostProcessor.java | 2 +- .../instrument/web/TraceWebMvcConfigurer.java | 2 +- .../AbstractTraceHttpRequestInterceptor.java | 2 +- .../web/client/HttpRequestInjector.java | 2 +- ...eAsyncClientHttpRequestFactoryWrapper.java | 2 +- .../TraceAsyncListenableTaskExecutor.java | 2 +- .../web/client/TraceAsyncRestTemplate.java | 2 +- .../client/TraceRestTemplateInterceptor.java | 2 +- .../TraceWebAsyncClientAutoConfiguration.java | 2 +- .../TraceWebClientAutoConfiguration.java | 2 +- .../feign/FeignContextBeanPostProcessor.java | 2 +- .../client/feign/FeignRequestInjector.java | 2 +- .../feign/FeignResponseHeadersHolder.java | 2 +- .../OkHttpFeignClientBeanPostProcessor.java | 2 +- .../web/client/feign/SleuthFeignBuilder.java | 2 +- .../feign/SleuthHystrixFeignBuilder.java | 2 +- .../web/client/feign/TraceFeignAspect.java | 2 +- .../web/client/feign/TraceFeignClient.java | 2 +- .../TraceFeignClientAutoConfiguration.java | 2 +- .../zuul/RequestBuilderContextInjector.java | 2 +- .../zuul/RequestContextInjector.java | 2 +- ...ceZuulHandlerMappingBeanPostProcessor.java | 2 +- .../cloud/sleuth/log/NoOpSpanLogger.java | 2 +- .../cloud/sleuth/log/SpanLogger.java | 2 +- .../metric/TraceMetricsAutoConfiguration.java | 2 +- .../sleuth/util/ArrayListSpanAccumulator.java | 2 +- .../cloud/sleuth/util/SpanNameUtil.java | 2 +- .../cloud/sleuth/DefaultSpanNamerTests.java | 2 +- .../cloud/sleuth/LogTests.java | 2 +- .../cloud/sleuth/SpanTests.java | 2 +- .../cloud/sleuth/assertions/ListOfSpans.java | 2 +- .../sleuth/assertions/ListOfSpansAssert.java | 2 +- .../cloud/sleuth/assertions/SpanAssert.java | 2 +- ...oConfigurationWithDisabledSleuthTests.java | 2 +- .../SpringCloudSleuthDocTests.java | 2 +- .../AsyncCustomAutoConfigurationTest.java | 2 +- .../async/LazyTraceAsyncCustomizerTest.java | 2 +- .../LocalComponentTraceCallableTest.java | 2 +- ...TraceableScheduledExecutorServiceTest.java | 2 +- .../async/issues/issue410/Issue410Tests.java | 2 +- .../async/issues/issue546/Issue546Tests.java | 2 +- .../HystrixAnnotationsIntegrationTests.java | 2 +- .../SleuthHystrixConcurrencyStrategyTest.java | 2 +- .../instrument/hystrix/TraceCommandTests.java | 2 +- .../MessagingSpanExtractorTests.java | 2 +- .../TraceWebSocketAutoConfigurationTest.java | 2 +- .../scheduling/TracingOnScheduledTests.java | 2 +- .../web/HttpServletRequestExtractorTests.java | 2 +- .../web/SkipPatternProviderConfigTest.java | 2 +- .../web/SpringDataInstrumentationTests.java | 2 +- ...raceCustomFilterResponseInjectorTests.java | 2 +- .../web/TraceFilterCustomExtractorTests.java | 2 +- ...terWebIntegrationMultipleFiltersTests.java | 56 ++++++++++- .../web/TraceFilterWebIntegrationTests.java | 2 +- .../web/TraceHandlerInterceptorTests.java | 2 +- .../web/TraceNoWebEnvironmentTests.java | 2 +- .../TraceAsyncListenableTaskExecutorTest.java | 2 +- ...stTemplateInterceptorIntegrationTests.java | 2 +- .../TraceRestTemplateInterceptorTests.java | 2 +- ...eWebAsyncClientAutoConfigurationTests.java | 2 +- .../WebClientDiscoveryExceptionTests.java | 2 +- .../exception/WebClientExceptionTests.java | 2 +- .../web/client/feign/FeignRetriesTests.java | 2 +- .../client/feign/TraceFeignClientTests.java | 2 +- .../feign/issues/issue307/Issue307Tests.java | 2 +- .../feign/issues/issue350/Issue350Tests.java | 2 +- .../feign/issues/issue362/Issue362Tests.java | 2 +- .../feign/issues/issue393/Issue393Tests.java | 2 +- .../feign/issues/issue502/Issue502Tests.java | 2 +- .../FeignClientServerErrorTests.java | 2 +- .../client/integration/WebClientTests.java | 2 +- .../sleuth/instrument/web/view/Issue469.java | 2 +- .../instrument/web/view/Issue469Tests.java | 2 +- ...aceRestClientRibbonCommandFactoryTest.java | 2 +- .../cloud/sleuth/log/Slf4JSpanLoggerTest.java | 2 +- .../TraceMetricsAutoConfigurationTests.java | 2 +- .../sleuth/sampler/IsTracingSamplerTest.java | 2 +- .../cloud/sleuth/util/ExceptionUtilsTest.java | 2 +- .../cloud/sleuth/util/SpanNameUtilTests.java | 2 +- .../spring-cloud-sleuth-sample-feign/pom.xml | 2 +- .../pom.xml | 2 +- .../spring-cloud-sleuth-sample-ribbon/pom.xml | 2 +- .../spring-cloud-sleuth-sample-stream/pom.xml | 2 +- .../pom.xml | 2 +- .../pom.xml | 2 +- .../pom.xml | 2 +- .../spring-cloud-sleuth-sample-zipkin/pom.xml | 2 +- .../spring-cloud-sleuth-sample/pom.xml | 2 +- .../TracerIgnoringChannelInterceptor.java | 2 +- .../cloud/sleuth/stream/ZipkinProperties.java | 2 +- .../DiscoveryClientHostLocatorTest.java | 2 +- .../stream/StreamSpanReporterTests.java | 2 +- .../TracerIgnoringChannelInterceptorTest.java | 2 +- .../zipkin/stream/documentation/Consumer.java | 2 +- spring-cloud-sleuth-zipkin/pom.xml | 2 +- .../zipkin/ZipkinAutoConfiguration.java | 2 +- .../DiscoveryClientEndpointLocatorTest.java | 2 +- .../zipkin/ZipkinSpanListenerTests.java | 2 +- 142 files changed, 296 insertions(+), 153 deletions(-) create mode 100644 spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/ExecutorBeanPostProcessor.java diff --git a/benchmarks/src/main/java/org/springframework/cloud/sleuth/benchmarks/app/SleuthBenchmarkingSpringApp.java b/benchmarks/src/main/java/org/springframework/cloud/sleuth/benchmarks/app/SleuthBenchmarkingSpringApp.java index 83eb20103..1bc7d1284 100644 --- a/benchmarks/src/main/java/org/springframework/cloud/sleuth/benchmarks/app/SleuthBenchmarkingSpringApp.java +++ b/benchmarks/src/main/java/org/springframework/cloud/sleuth/benchmarks/app/SleuthBenchmarkingSpringApp.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/benchmarks/src/main/java/org/springframework/cloud/sleuth/benchmarks/jmh/benchmarks/AsyncBenchmarks.java b/benchmarks/src/main/java/org/springframework/cloud/sleuth/benchmarks/jmh/benchmarks/AsyncBenchmarks.java index 4e35d5f10..b0b95fd3e 100644 --- a/benchmarks/src/main/java/org/springframework/cloud/sleuth/benchmarks/jmh/benchmarks/AsyncBenchmarks.java +++ b/benchmarks/src/main/java/org/springframework/cloud/sleuth/benchmarks/jmh/benchmarks/AsyncBenchmarks.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/benchmarks/src/main/java/org/springframework/cloud/sleuth/benchmarks/jmh/benchmarks/HttpFilterBenchmarks.java b/benchmarks/src/main/java/org/springframework/cloud/sleuth/benchmarks/jmh/benchmarks/HttpFilterBenchmarks.java index a42e5a607..578d4f673 100644 --- a/benchmarks/src/main/java/org/springframework/cloud/sleuth/benchmarks/jmh/benchmarks/HttpFilterBenchmarks.java +++ b/benchmarks/src/main/java/org/springframework/cloud/sleuth/benchmarks/jmh/benchmarks/HttpFilterBenchmarks.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/benchmarks/src/main/java/org/springframework/cloud/sleuth/benchmarks/jmh/benchmarks/RestTemplateBenchmark.java b/benchmarks/src/main/java/org/springframework/cloud/sleuth/benchmarks/jmh/benchmarks/RestTemplateBenchmark.java index 76b2aa2b1..a07161e7f 100644 --- a/benchmarks/src/main/java/org/springframework/cloud/sleuth/benchmarks/jmh/benchmarks/RestTemplateBenchmark.java +++ b/benchmarks/src/main/java/org/springframework/cloud/sleuth/benchmarks/jmh/benchmarks/RestTemplateBenchmark.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/benchmarks/src/test/java/org/springframework/cloud/sleuth/benchmarks/jmh/RunSleuthJmhBenchmarksFromIde.java b/benchmarks/src/test/java/org/springframework/cloud/sleuth/benchmarks/jmh/RunSleuthJmhBenchmarksFromIde.java index 3d95f4a5a..2fd00ed61 100644 --- a/benchmarks/src/test/java/org/springframework/cloud/sleuth/benchmarks/jmh/RunSleuthJmhBenchmarksFromIde.java +++ b/benchmarks/src/test/java/org/springframework/cloud/sleuth/benchmarks/jmh/RunSleuthJmhBenchmarksFromIde.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/benchmarks/src/test/jmeter/jmeter.properties b/benchmarks/src/test/jmeter/jmeter.properties index 921b80604..5aceab5fa 100644 --- a/benchmarks/src/test/jmeter/jmeter.properties +++ b/benchmarks/src/test/jmeter/jmeter.properties @@ -1,5 +1,5 @@ # -# Copyright 2013-2016 the original author or authors. +# Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/DefaultSpanNamer.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/DefaultSpanNamer.java index 0aa6043e4..2f077cfa9 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/DefaultSpanNamer.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/DefaultSpanNamer.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Log.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Log.java index a283f2f38..d3e392751 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Log.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Log.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/NoOpSpanReporter.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/NoOpSpanReporter.java index dd637ef78..377a366fe 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/NoOpSpanReporter.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/NoOpSpanReporter.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java index 4f793a3f1..14827c694 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/SpanExtractor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/SpanExtractor.java index 488110ef3..6c24f381e 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/SpanExtractor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/SpanExtractor.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/SpanInjector.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/SpanInjector.java index 5869f966a..f493b0000 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/SpanInjector.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/SpanInjector.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/SpanName.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/SpanName.java index e9ef5f3cb..f676fc0d8 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/SpanName.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/SpanName.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/SpanNamer.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/SpanNamer.java index 8da55acad..53a9fb781 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/SpanNamer.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/SpanNamer.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/SpanReporter.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/SpanReporter.java index 494bc4bb1..a72170c79 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/SpanReporter.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/SpanReporter.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/TraceCallable.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/TraceCallable.java index 44bc0e1e7..2e4d4b1df 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/TraceCallable.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/TraceCallable.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/TraceKeys.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/TraceKeys.java index 42468cd0a..3e644b14c 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/TraceKeys.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/TraceKeys.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/TraceRunnable.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/TraceRunnable.java index 688269354..ef883a1a9 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/TraceRunnable.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/TraceRunnable.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/AsyncDefaultAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/AsyncDefaultAutoConfiguration.java index fc4c7ac26..fbd79103a 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/AsyncDefaultAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/AsyncDefaultAutoConfiguration.java @@ -71,4 +71,9 @@ public class AsyncDefaultAutoConfiguration { return new TraceAsyncAspect(tracer, traceKeys, this.beanFactory); } + @Bean + public ExecutorBeanPostProcessor executorBeanPostProcessor() { + return new ExecutorBeanPostProcessor(this.beanFactory); + } + } \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/ExecutorBeanPostProcessor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/ExecutorBeanPostProcessor.java new file mode 100644 index 000000000..a95b918b7 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/ExecutorBeanPostProcessor.java @@ -0,0 +1,98 @@ +/* + * Copyright 2013-2017 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.async; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.concurrent.Executor; + +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; +import org.springframework.aop.framework.ProxyFactoryBean; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.util.ReflectionUtils; + +/** + * Bean post processor that wraps a call to an {@link Executor} either in a + * JDK or CGLIB proxy. Depending on whether the implementation has a final + * method or is final. + * + * @author Marcin Grzejszczak + * @since 1.1.4 + */ +class ExecutorBeanPostProcessor implements BeanPostProcessor { + + private final BeanFactory beanFactory; + + ExecutorBeanPostProcessor(BeanFactory beanFactory) { + this.beanFactory = beanFactory; + } + + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) + throws BeansException { + return bean; + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) + throws BeansException { + if (bean instanceof Executor && !(bean instanceof ThreadPoolTaskExecutor)) { + Method execute = ReflectionUtils.findMethod(bean.getClass(), "execute", Runnable.class); + boolean methodFinal = Modifier.isFinal(execute.getModifiers()); + boolean classFinal = Modifier.isFinal(bean.getClass().getModifiers()); + boolean cglibProxy = !methodFinal && !classFinal; + Executor executor = (Executor) bean; + ProxyFactoryBean factory = new ProxyFactoryBean(); + factory.setProxyTargetClass(cglibProxy); + factory.addAdvice(new ExecutorMethodInterceptor(executor, this.beanFactory)); + factory.setTarget(bean); + return factory.getObject(); + } + return bean; + } +} + +class ExecutorMethodInterceptor implements MethodInterceptor { + + private final Executor delegate; + private final BeanFactory beanFactory; + + ExecutorMethodInterceptor(Executor delegate, BeanFactory beanFactory) { + this.delegate = delegate; + this.beanFactory = beanFactory; + } + + @Override public Object invoke(MethodInvocation invocation) + throws Throwable { + LazyTraceExecutor executor = new LazyTraceExecutor(this.beanFactory, this.delegate); + Method methodOnTracedBean = getMethod(invocation, executor); + if (methodOnTracedBean != null) { + return methodOnTracedBean.invoke(executor, invocation.getArguments()); + } + return invocation.proceed(); + } + + private Method getMethod(MethodInvocation invocation, Object object) { + Method method = invocation.getMethod(); + return ReflectionUtils + .findMethod(object.getClass(), method.getName(), method.getParameterTypes()); + } +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/LazyTraceThreadPoolTaskExecutor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/LazyTraceThreadPoolTaskExecutor.java index 30d83f720..c3e46ce8f 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/LazyTraceThreadPoolTaskExecutor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/LazyTraceThreadPoolTaskExecutor.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/LocalComponentTraceCallable.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/LocalComponentTraceCallable.java index 804f0de8f..844165caf 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/LocalComponentTraceCallable.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/LocalComponentTraceCallable.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/LocalComponentTraceRunnable.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/LocalComponentTraceRunnable.java index 9a2bb279a..1ecb39b43 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/LocalComponentTraceRunnable.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/LocalComponentTraceRunnable.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/SpanContinuingTraceCallable.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/SpanContinuingTraceCallable.java index 1846a8f21..710e3d927 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/SpanContinuingTraceCallable.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/SpanContinuingTraceCallable.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/SpanContinuingTraceRunnable.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/SpanContinuingTraceRunnable.java index fd2401d91..47cf5eee0 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/SpanContinuingTraceRunnable.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/SpanContinuingTraceRunnable.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceAsyncAspect.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceAsyncAspect.java index 76aa0bf4c..2680baf2e 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceAsyncAspect.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceAsyncAspect.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. @@ -17,7 +17,6 @@ package org.springframework.cloud.sleuth.instrument.async; import java.lang.reflect.Method; -import java.util.concurrent.Executor; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; @@ -82,17 +81,6 @@ public class TraceAsyncAspect { return pjp.proceed(); } - @Around("execution (* java.util.concurrent.Executor.*(..))") - public Object traceExecutor(final ProceedingJoinPoint pjp) throws Throwable { - LazyTraceExecutor executor = new LazyTraceExecutor(this.beanFactory, - (Executor) pjp.getTarget()); - Method methodOnTracedBean = getMethod(pjp, executor); - if (methodOnTracedBean != null) { - return methodOnTracedBean.invoke(executor, pjp.getArgs()); - } - return pjp.proceed(); - } - private Method getMethod(ProceedingJoinPoint pjp, Object object) { MethodSignature signature = (MethodSignature) pjp.getSignature(); Method method = signature.getMethod(); diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceContinuingCallable.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceContinuingCallable.java index c3b48ef36..f8a056a21 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceContinuingCallable.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceContinuingCallable.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/hystrix/SleuthHystrixConcurrencyStrategy.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/hystrix/SleuthHystrixConcurrencyStrategy.java index 86b5486b7..059ca3b4c 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/hystrix/SleuthHystrixConcurrencyStrategy.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/hystrix/SleuthHystrixConcurrencyStrategy.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. 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 054332f28..66bfff90f 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 @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. 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 f867411ac..25bd85fbf 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 @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceMessageHeaders.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceMessageHeaders.java index 1bd173148..bed80f308 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceMessageHeaders.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceMessageHeaders.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceSpanMessagingAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceSpanMessagingAutoConfiguration.java index d72b2be80..a20e55128 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceSpanMessagingAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceSpanMessagingAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/scheduling/TraceSchedulingAspect.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/scheduling/TraceSchedulingAspect.java index 597b7cffc..75214ce20 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/scheduling/TraceSchedulingAspect.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/scheduling/TraceSchedulingAspect.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/scheduling/TraceSchedulingAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/scheduling/TraceSchedulingAutoConfiguration.java index a840eaae9..fbbcc5057 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/scheduling/TraceSchedulingAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/scheduling/TraceSchedulingAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/HttpServletRequestExtractor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/HttpServletRequestExtractor.java index 719fd96b9..ff467cd16 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/HttpServletRequestExtractor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/HttpServletRequestExtractor.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/ServletUtils.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/ServletUtils.java index b6f33a35a..6159920ae 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/ServletUtils.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/ServletUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/SsLogSetter.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/SsLogSetter.java index 0eefc1ab6..4504b04f8 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/SsLogSetter.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/SsLogSetter.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. 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 c38d849a2..0a6289d90 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 @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptor.java index 5f9054d9f..b4896059e 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHttpAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHttpAutoConfiguration.java index b638cf4a2..8c010bfd2 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHttpAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHttpAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHttpServletResponse.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHttpServletResponse.java index a02d49797..d423bfbcd 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHttpServletResponse.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHttpServletResponse.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TracePrintWriter.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TracePrintWriter.java index 96ea7855e..426ac04c7 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TracePrintWriter.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TracePrintWriter.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceRequestAttributes.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceRequestAttributes.java index 1b3ccfbb0..0fdb017c8 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceRequestAttributes.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceRequestAttributes.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceServletOutputStream.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceServletOutputStream.java index f5e4779ab..4acdaf9f9 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceServletOutputStream.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceServletOutputStream.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceSpringDataBeanPostProcessor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceSpringDataBeanPostProcessor.java index 98e2f42a6..993d35874 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceSpringDataBeanPostProcessor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceSpringDataBeanPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebMvcConfigurer.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebMvcConfigurer.java index c4b1861c3..58975d74a 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebMvcConfigurer.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebMvcConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/AbstractTraceHttpRequestInterceptor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/AbstractTraceHttpRequestInterceptor.java index f8df9b183..61621df50 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/AbstractTraceHttpRequestInterceptor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/AbstractTraceHttpRequestInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/HttpRequestInjector.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/HttpRequestInjector.java index b8185613b..57801f852 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/HttpRequestInjector.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/HttpRequestInjector.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncClientHttpRequestFactoryWrapper.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncClientHttpRequestFactoryWrapper.java index 49e26f6c4..cba70caa7 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncClientHttpRequestFactoryWrapper.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncClientHttpRequestFactoryWrapper.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncListenableTaskExecutor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncListenableTaskExecutor.java index bf2c4865d..53faec96b 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncListenableTaskExecutor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncListenableTaskExecutor.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncRestTemplate.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncRestTemplate.java index dd5d6374b..64d112c60 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncRestTemplate.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncRestTemplate.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptor.java index 5c7cc9c3e..016db41b3 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfiguration.java index d5fb8f2e6..1eeb58582 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebClientAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebClientAutoConfiguration.java index 6585b0b13..80c3c8a08 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebClientAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebClientAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignContextBeanPostProcessor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignContextBeanPostProcessor.java index d616696ac..f17909087 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignContextBeanPostProcessor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignContextBeanPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignRequestInjector.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignRequestInjector.java index f1323a35e..20c6da8ef 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignRequestInjector.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignRequestInjector.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignResponseHeadersHolder.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignResponseHeadersHolder.java index b08a04e70..e11f364e2 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignResponseHeadersHolder.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignResponseHeadersHolder.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/OkHttpFeignClientBeanPostProcessor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/OkHttpFeignClientBeanPostProcessor.java index 9379a26de..6d12b43da 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/OkHttpFeignClientBeanPostProcessor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/OkHttpFeignClientBeanPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/SleuthFeignBuilder.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/SleuthFeignBuilder.java index 6b34ab0c4..e56eb076d 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/SleuthFeignBuilder.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/SleuthFeignBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/SleuthHystrixFeignBuilder.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/SleuthHystrixFeignBuilder.java index 69096e0f2..86e91ff39 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/SleuthHystrixFeignBuilder.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/SleuthHystrixFeignBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignAspect.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignAspect.java index 8f0dcd8f5..0486d4575 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignAspect.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignAspect.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClient.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClient.java index 41f721087..383437461 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClient.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClient.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientAutoConfiguration.java index 258f5d991..1cb5fe30f 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/RequestBuilderContextInjector.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/RequestBuilderContextInjector.java index d5bc577a8..4c935e756 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/RequestBuilderContextInjector.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/RequestBuilderContextInjector.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/RequestContextInjector.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/RequestContextInjector.java index 341f036b6..1237ceb1f 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/RequestContextInjector.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/RequestContextInjector.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulHandlerMappingBeanPostProcessor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulHandlerMappingBeanPostProcessor.java index d6076e8c5..09da092a8 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulHandlerMappingBeanPostProcessor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulHandlerMappingBeanPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/NoOpSpanLogger.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/NoOpSpanLogger.java index 011012756..ff0a48bb8 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/NoOpSpanLogger.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/NoOpSpanLogger.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/SpanLogger.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/SpanLogger.java index df5aa09e2..4bed01f22 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/SpanLogger.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/SpanLogger.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/metric/TraceMetricsAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/metric/TraceMetricsAutoConfiguration.java index 55971834f..ef9127bc9 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/metric/TraceMetricsAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/metric/TraceMetricsAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/util/ArrayListSpanAccumulator.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/util/ArrayListSpanAccumulator.java index 2a412d167..8ceb50e11 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/util/ArrayListSpanAccumulator.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/util/ArrayListSpanAccumulator.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/util/SpanNameUtil.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/util/SpanNameUtil.java index 1aef47d1c..32220f107 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/util/SpanNameUtil.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/util/SpanNameUtil.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/DefaultSpanNamerTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/DefaultSpanNamerTests.java index 87c967674..18f27dd42 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/DefaultSpanNamerTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/DefaultSpanNamerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/LogTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/LogTests.java index c830c9e20..28b3ad40a 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/LogTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/LogTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/SpanTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/SpanTests.java index 3ab21ee9c..baf5d9d2d 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/SpanTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/SpanTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/ListOfSpans.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/ListOfSpans.java index cbec93f1c..bd79dfc11 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/ListOfSpans.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/ListOfSpans.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/ListOfSpansAssert.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/ListOfSpansAssert.java index 5326892f7..0926df4b4 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/ListOfSpansAssert.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/ListOfSpansAssert.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/SpanAssert.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/SpanAssert.java index f0f5bd5ef..1cd1bbc35 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/SpanAssert.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/SpanAssert.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfigurationWithDisabledSleuthTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfigurationWithDisabledSleuthTests.java index bd9789c48..eb730c50e 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfigurationWithDisabledSleuthTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfigurationWithDisabledSleuthTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/documentation/SpringCloudSleuthDocTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/documentation/SpringCloudSleuthDocTests.java index 6210ea484..a47e0e5ba 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/documentation/SpringCloudSleuthDocTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/documentation/SpringCloudSleuthDocTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/AsyncCustomAutoConfigurationTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/AsyncCustomAutoConfigurationTest.java index 2f73d8fdb..99de29cdf 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/AsyncCustomAutoConfigurationTest.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/AsyncCustomAutoConfigurationTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/LazyTraceAsyncCustomizerTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/LazyTraceAsyncCustomizerTest.java index 296dd36ea..3b791bcd5 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/LazyTraceAsyncCustomizerTest.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/LazyTraceAsyncCustomizerTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/LocalComponentTraceCallableTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/LocalComponentTraceCallableTest.java index c9760656e..ddf48c2e2 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/LocalComponentTraceCallableTest.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/LocalComponentTraceCallableTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/TraceableScheduledExecutorServiceTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/TraceableScheduledExecutorServiceTest.java index 8d0d4017a..8bc88179f 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/TraceableScheduledExecutorServiceTest.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/TraceableScheduledExecutorServiceTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue410/Issue410Tests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue410/Issue410Tests.java index 388ed3c3a..a88ab307d 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue410/Issue410Tests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue410/Issue410Tests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue546/Issue546Tests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue546/Issue546Tests.java index 60d29a2c7..4b7b562a5 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue546/Issue546Tests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue546/Issue546Tests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/hystrix/HystrixAnnotationsIntegrationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/hystrix/HystrixAnnotationsIntegrationTests.java index 208d25767..8f2b99b04 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/hystrix/HystrixAnnotationsIntegrationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/hystrix/HystrixAnnotationsIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/hystrix/SleuthHystrixConcurrencyStrategyTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/hystrix/SleuthHystrixConcurrencyStrategyTest.java index 6f9d831be..23501a38c 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/hystrix/SleuthHystrixConcurrencyStrategyTest.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/hystrix/SleuthHystrixConcurrencyStrategyTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/hystrix/TraceCommandTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/hystrix/TraceCommandTests.java index b9e03c4fd..8cccdf0af 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/hystrix/TraceCommandTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/hystrix/TraceCommandTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingSpanExtractorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingSpanExtractorTests.java index 5e9bb1cb9..d7f355193 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingSpanExtractorTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingSpanExtractorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/websocket/TraceWebSocketAutoConfigurationTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/websocket/TraceWebSocketAutoConfigurationTest.java index 729ee7ceb..c668d25cb 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/websocket/TraceWebSocketAutoConfigurationTest.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/websocket/TraceWebSocketAutoConfigurationTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/scheduling/TracingOnScheduledTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/scheduling/TracingOnScheduledTests.java index 0d4498548..dcf903017 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/scheduling/TracingOnScheduledTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/scheduling/TracingOnScheduledTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/HttpServletRequestExtractorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/HttpServletRequestExtractorTests.java index 04dd26f95..7495ac780 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/HttpServletRequestExtractorTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/HttpServletRequestExtractorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SkipPatternProviderConfigTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SkipPatternProviderConfigTest.java index 5dac81bea..cf113b2f9 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SkipPatternProviderConfigTest.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SkipPatternProviderConfigTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SpringDataInstrumentationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SpringDataInstrumentationTests.java index 126b952d2..e88627574 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SpringDataInstrumentationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SpringDataInstrumentationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceCustomFilterResponseInjectorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceCustomFilterResponseInjectorTests.java index 254eb5578..ee28557b7 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceCustomFilterResponseInjectorTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceCustomFilterResponseInjectorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterCustomExtractorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterCustomExtractorTests.java index b2956e533..f177885d0 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterCustomExtractorTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterCustomExtractorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterWebIntegrationMultipleFiltersTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterWebIntegrationMultipleFiltersTests.java index e08ac7126..6abfa389a 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterWebIntegrationMultipleFiltersTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterWebIntegrationMultipleFiltersTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. @@ -17,6 +17,8 @@ package org.springframework.cloud.sleuth.instrument.web; import java.io.IOException; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicReference; import javax.servlet.FilterChain; import javax.servlet.ServletException; @@ -28,6 +30,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.web.servlet.FilterRegistrationBean; @@ -60,6 +63,10 @@ public class TraceFilterWebIntegrationMultipleFiltersTests { @Autowired RestTemplate restTemplate; @Autowired Environment environment; @Autowired MyFilter myFilter; + // issue #550 + @Autowired @Qualifier("myExecutor") Executor myExecutor; + @Autowired @Qualifier("finalExecutor") Executor finalExecutor; + @Autowired MyExecutor cglibExecutor; @Before @After @@ -70,6 +77,10 @@ public class TraceFilterWebIntegrationMultipleFiltersTests { @Test public void should_register_trace_filter_before_the_custom_filter() { + this.myExecutor.execute(() -> System.out.println("foo")); + this.cglibExecutor.execute(() -> System.out.println("foo")); + this.finalExecutor.execute(() -> System.out.println("foo")); + this.restTemplate.getForObject("http://localhost:" + port() + "/", String.class); then(this.tracer.getCurrentSpan()).isNull(); @@ -85,11 +96,25 @@ public class TraceFilterWebIntegrationMultipleFiltersTests { @Configuration public static class Config { + // issue #550 + @Bean Executor myExecutor() { + return new MyExecutorWithFinalMethod(); + } + + // issue #550 + @Bean MyExecutor cglibExecutor() { + return new MyExecutor(); + } + + // issue #550 + @Bean MyFinalExecutor finalExecutor() { + return new MyFinalExecutor(); + } + @Bean Sampler alwaysSampler() { return new AlwaysSampler(); } - @Bean RestTemplate restTemplate() { RestTemplate restTemplate = new RestTemplate(); restTemplate.setErrorHandler(new DefaultResponseErrorHandler() { @@ -132,4 +157,31 @@ public class TraceFilterWebIntegrationMultipleFiltersTests { return span; } } + + static class MyExecutor implements Executor { + + private final Executor delegate = Executors.newSingleThreadExecutor(); + + @Override public void execute(Runnable command) { + this.delegate.execute(command); + } + } + + static class MyExecutorWithFinalMethod implements Executor { + + private final Executor delegate = Executors.newSingleThreadExecutor(); + + @Override public final void execute(Runnable command) { + this.delegate.execute(command); + } + } + + static final class MyFinalExecutor implements Executor { + + private final Executor delegate = Executors.newSingleThreadExecutor(); + + @Override public void execute(Runnable command) { + this.delegate.execute(command); + } + } } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterWebIntegrationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterWebIntegrationTests.java index 597a2a6cb..56008755f 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterWebIntegrationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterWebIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptorTests.java index dc761d7d2..fce7f0e06 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptorTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceNoWebEnvironmentTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceNoWebEnvironmentTests.java index afecfdede..51058a6df 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceNoWebEnvironmentTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceNoWebEnvironmentTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncListenableTaskExecutorTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncListenableTaskExecutorTest.java index 0e9f9dc61..d12f3d9a4 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncListenableTaskExecutorTest.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncListenableTaskExecutorTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptorIntegrationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptorIntegrationTests.java index 5726739a7..091b5d4e2 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptorIntegrationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptorIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptorTests.java index 179ec2fea..d32112ada 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptorTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfigurationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfigurationTests.java index fa350aa8d..7084dea45 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfigurationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/discoveryexception/WebClientDiscoveryExceptionTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/discoveryexception/WebClientDiscoveryExceptionTests.java index cdeea84bd..b77eac3cf 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/discoveryexception/WebClientDiscoveryExceptionTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/discoveryexception/WebClientDiscoveryExceptionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/exception/WebClientExceptionTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/exception/WebClientExceptionTests.java index b6d781a8e..b2ca66503 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/exception/WebClientExceptionTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/exception/WebClientExceptionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignRetriesTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignRetriesTests.java index ff83f8490..e95bd09b4 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignRetriesTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignRetriesTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientTests.java index ea781cd0c..6ee91b37c 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue307/Issue307Tests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue307/Issue307Tests.java index bd3eb5856..cd3ed754a 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue307/Issue307Tests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue307/Issue307Tests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue350/Issue350Tests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue350/Issue350Tests.java index 4d63cbcec..f492a7737 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue350/Issue350Tests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue350/Issue350Tests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue362/Issue362Tests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue362/Issue362Tests.java index b1780f45e..ab731612a 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue362/Issue362Tests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue362/Issue362Tests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue393/Issue393Tests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue393/Issue393Tests.java index 2ecd45ed0..0c8574325 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue393/Issue393Tests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue393/Issue393Tests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue502/Issue502Tests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue502/Issue502Tests.java index d8d6bb3e6..f282c43d4 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue502/Issue502Tests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue502/Issue502Tests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/servererrors/FeignClientServerErrorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/servererrors/FeignClientServerErrorTests.java index 1f680e34c..07eccb772 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/servererrors/FeignClientServerErrorTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/servererrors/FeignClientServerErrorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/integration/WebClientTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/integration/WebClientTests.java index cb68a11f0..69280127a 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/integration/WebClientTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/integration/WebClientTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/view/Issue469.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/view/Issue469.java index e73f7fdad..85d6ea631 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/view/Issue469.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/view/Issue469.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/view/Issue469Tests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/view/Issue469Tests.java index 7637d96cb..fa6b9fe90 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/view/Issue469Tests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/view/Issue469Tests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRestClientRibbonCommandFactoryTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRestClientRibbonCommandFactoryTest.java index a87ce8f78..1808a9d64 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRestClientRibbonCommandFactoryTest.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRestClientRibbonCommandFactoryTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/log/Slf4JSpanLoggerTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/log/Slf4JSpanLoggerTest.java index 76c84d70d..36656b6e3 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/log/Slf4JSpanLoggerTest.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/log/Slf4JSpanLoggerTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/metric/TraceMetricsAutoConfigurationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/metric/TraceMetricsAutoConfigurationTests.java index 990f94fb3..6d53a84c0 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/metric/TraceMetricsAutoConfigurationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/metric/TraceMetricsAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/sampler/IsTracingSamplerTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/sampler/IsTracingSamplerTest.java index e15f9d780..56aacd634 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/sampler/IsTracingSamplerTest.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/sampler/IsTracingSamplerTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/util/ExceptionUtilsTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/util/ExceptionUtilsTest.java index 13da88cc6..83f96e7ac 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/util/ExceptionUtilsTest.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/util/ExceptionUtilsTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/util/SpanNameUtilTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/util/SpanNameUtilTests.java index d531691ef..6a3054423 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/util/SpanNameUtilTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/util/SpanNameUtilTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml index ef88ada47..d5395718e 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml @@ -1,5 +1,5 @@ -