From 5afbc75ecb16dafa9e608f85ab17093776f24594 Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Tue, 23 Jun 2015 16:11:42 -0600 Subject: [PATCH] Added SpanStartListener and slf4j SpanStartListener and SpanReceiver --- pom.xml | 1 + .../SleuthRestTemplateAutoConfiguration.java | 2 +- .../slf4j/SleuthSlf4jAutoConfiguration.java | 24 +++++++ .../cloud/sleuth/slf4j/Slf4jSpanReceiver.java | 29 ++++++++ .../sleuth/slf4j/Slf4jSpanStartListener.java | 22 ++++++ .../cloud/sleuth/trace/DefaultTrace.java | 21 ++++-- .../cloud/sleuth/trace/SpanStartListener.java | 9 +++ .../cloud/sleuth/trace/Trace.java | 2 - .../sleuth/trace/TraceAutoConfiguration.java | 34 +++++++++ .../cloud/sleuth/trace/TraceScope.java | 1 + .../main/resources/META-INF/spring.factories | 4 +- .../cloud/sleuth/trace/DefaultTraceTests.java | 27 +++++-- spring-cloud-sleuth-sample/pom.xml | 2 +- .../sleuth/sample/SampleApplication.java | 71 ++++++++++++------- .../src/main/resources/logback.xml | 18 +++++ 15 files changed, 229 insertions(+), 38 deletions(-) create mode 100644 spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/slf4j/SleuthSlf4jAutoConfiguration.java create mode 100644 spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/slf4j/Slf4jSpanReceiver.java create mode 100644 spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/slf4j/Slf4jSpanStartListener.java create mode 100644 spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/trace/SpanStartListener.java create mode 100644 spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/trace/TraceAutoConfiguration.java create mode 100644 spring-cloud-sleuth-sample/src/main/resources/logback.xml diff --git a/pom.xml b/pom.xml index 96e8782b4..8ab271c7d 100644 --- a/pom.xml +++ b/pom.xml @@ -27,6 +27,7 @@ spring-cloud-sleuth-core + spring-cloud-sleuth-slf4j spring-cloud-sleuth-correlation spring-cloud-sleuth-zipkin spring-cloud-sleuth-sample diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/resttemplate/SleuthRestTemplateAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/resttemplate/SleuthRestTemplateAutoConfiguration.java index 662176624..40d1d2e0f 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/resttemplate/SleuthRestTemplateAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/resttemplate/SleuthRestTemplateAutoConfiguration.java @@ -42,7 +42,7 @@ public class SleuthRestTemplateAutoConfiguration { @Configuration protected static class RestTemplateConfig { - @Autowired + @Autowired(required = false) private List clientHttpRequestInterceptors = new ArrayList<>(); @Bean diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/slf4j/SleuthSlf4jAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/slf4j/SleuthSlf4jAutoConfiguration.java new file mode 100644 index 000000000..ea0781be7 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/slf4j/SleuthSlf4jAutoConfiguration.java @@ -0,0 +1,24 @@ +package org.springframework.cloud.sleuth.slf4j; + +import org.slf4j.MDC; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @author Spencer Gibb + */ +@Configuration +@ConditionalOnClass(MDC.class) +public class SleuthSlf4jAutoConfiguration { + + @Bean + public Slf4jSpanStartListener slf4jSpanStartListener() { + return new Slf4jSpanStartListener(); + } + + @Bean + public Slf4jSpanReceiver slf4jSpanReceiver() { + return new Slf4jSpanReceiver(); + } +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/slf4j/Slf4jSpanReceiver.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/slf4j/Slf4jSpanReceiver.java new file mode 100644 index 000000000..064b94c4f --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/slf4j/Slf4jSpanReceiver.java @@ -0,0 +1,29 @@ +package org.springframework.cloud.sleuth.slf4j; + +import static org.springframework.cloud.sleuth.slf4j.Slf4jSpanStartListener.SPAN_ID_NAME; + +import lombok.extern.slf4j.Slf4j; +import org.slf4j.MDC; +import org.springframework.cloud.sleuth.trace.Span; +import org.springframework.cloud.sleuth.trace.SpanReceiver; + +import javax.annotation.PostConstruct; +import java.io.IOException; + +/** + * @author Spencer Gibb + */ +@Slf4j +public class Slf4jSpanReceiver implements SpanReceiver { + @Override + public void receiveSpan(Span span) { + //TODO: what should this log level be? + log.info("Received span {}", span); + MDC.remove(SPAN_ID_NAME); + } + + @PostConstruct + @Override + public void close() throws IOException { + } +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/slf4j/Slf4jSpanStartListener.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/slf4j/Slf4jSpanStartListener.java new file mode 100644 index 000000000..e68a1df72 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/slf4j/Slf4jSpanStartListener.java @@ -0,0 +1,22 @@ +package org.springframework.cloud.sleuth.slf4j; + +import lombok.extern.slf4j.Slf4j; +import org.slf4j.MDC; +import org.springframework.cloud.sleuth.trace.Span; +import org.springframework.cloud.sleuth.trace.SpanStartListener; + +/** + * @author Spencer Gibb + */ +@Slf4j +public class Slf4jSpanStartListener implements SpanStartListener { + //TODO: Where to put span id name? + public static final String SPAN_ID_NAME = "Span-Id"; + + @Override + public void startSpan(Span span) { + //TODO: what log level? + log.info("Starting span with id: [{}]", span.getSpanId()); + MDC.put(SPAN_ID_NAME, span.getSpanId()); + } +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/trace/DefaultTrace.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/trace/DefaultTrace.java index d2e2efaf6..2eff2ffa2 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/trace/DefaultTrace.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/trace/DefaultTrace.java @@ -16,12 +16,15 @@ public class DefaultTrace implements Trace { private final IdGenerator idGenerator; + private final Collection spanStartListeners; private final Collection spanReceivers; public DefaultTrace(Sampler defaultSampler, IdGenerator idGenerator, + Collection spanStartListeners, Collection spanReceivers) { this.defaultSampler = defaultSampler; this.idGenerator = idGenerator; + this.spanStartListeners = spanStartListeners; this.spanReceivers = spanReceivers; } @@ -32,7 +35,7 @@ public class DefaultTrace implements Trace { @Override public TraceScope startSpan(String description, TraceInfo tinfo) { - if (tinfo == null) return continueSpan(null); + if (tinfo == null) return doStart(null); MilliSpan span = MilliSpan.builder() .begin(System.currentTimeMillis()) .description(description) @@ -41,7 +44,7 @@ public class DefaultTrace implements Trace { .parents(Collections.singletonList(tinfo.getSpanId())) //TODO: when lombok plugin supports @Singular parent(tinfo.getSpanId()). .build(); - return continueSpan(span); + return doStart(span); } @Override @@ -56,7 +59,7 @@ public class DefaultTrace implements Trace { "with parent " + parent.toString() + ", but there is already a " + "currentSpan " + currentSpan); } - return continueSpan(createChild(parent, description)); + return doStart(createChild(parent, description)); } @Override @@ -70,7 +73,7 @@ public class DefaultTrace implements Trace { if (isTracing() || s.next(info)) { span = createNew(description); } - return continueSpan(span); + return doStart(span); } protected Span createNew(String description) { @@ -99,6 +102,15 @@ public class DefaultTrace implements Trace { build(); } + protected TraceScope doStart(Span span) { + if (span != null) { + for (SpanStartListener listener : spanStartListeners) { + listener.startSpan(span); + } + } + return continueSpan(span); + } + @Override public TraceScope continueSpan(Span span) { // Return an empty TraceScope that does nothing on close @@ -125,6 +137,7 @@ public class DefaultTrace implements Trace { return getCurrentSpan() != null; } + //TODO: rename? this is the end of a Span lifecycle @Override public void deliver(Span span) { for (SpanReceiver receiver : spanReceivers) { diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/trace/SpanStartListener.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/trace/SpanStartListener.java new file mode 100644 index 000000000..276f2dfa8 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/trace/SpanStartListener.java @@ -0,0 +1,9 @@ +package org.springframework.cloud.sleuth.trace; + +/** + * @author Spencer Gibb + */ +//TODO: rename? +public interface SpanStartListener { + void startSpan(Span span); +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/trace/Trace.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/trace/Trace.java index 91019c609..00dbc0e6f 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/trace/Trace.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/trace/Trace.java @@ -1,7 +1,5 @@ package org.springframework.cloud.sleuth.trace; -import java.util.Collection; - /** * The Trace class is the primary way to interact with the library. It provides * methods to create and manipulate spans. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/trace/TraceAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/trace/TraceAutoConfiguration.java new file mode 100644 index 000000000..71dad2f7e --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/trace/TraceAutoConfiguration.java @@ -0,0 +1,34 @@ +package org.springframework.cloud.sleuth.trace; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.cloud.sleuth.IdGenerator; +import org.springframework.cloud.sleuth.RandomUuidGenerator; +import org.springframework.cloud.sleuth.trace.sampler.IsTracingSampler; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.Collection; + +/** + * @author Spencer Gibb + */ +@Configuration +public class TraceAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + public IdGenerator idGenerator() { + return new RandomUuidGenerator(); + } + + @Bean + public Sampler defaultSampler() { + return new IsTracingSampler(); + } + + @Bean + @ConditionalOnMissingBean + public Trace trace(Sampler sampler, IdGenerator idGenerator, Collection listeners, Collection receivers) { + return new DefaultTrace(sampler, idGenerator, listeners, receivers); + } +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/trace/TraceScope.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/trace/TraceScope.java index 3e0d74d54..3de9135ad 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/trace/TraceScope.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/trace/TraceScope.java @@ -72,6 +72,7 @@ public class TraceScope implements Closeable { "probably forgotten to close or detach " + cur); } else { span.stop(); + //TODO: use ApplicationEvents here? trace.deliver(span); SpanHolder.setCurrentSpan(savedSpan); } 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 9d7ec500c..7b7f733f7 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 @@ -1,3 +1,5 @@ # Auto Configuration org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ -org.springframework.cloud.sleuth.resttemplate.SleuthRestTemplateAutoConfiguration +org.springframework.cloud.sleuth.resttemplate.SleuthRestTemplateAutoConfiguration,\ +org.springframework.cloud.sleuth.trace.TraceAutoConfiguration,\ +org.springframework.cloud.sleuth.slf4j.SleuthSlf4jAutoConfiguration diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/trace/DefaultTraceTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/trace/DefaultTraceTests.java index 5613543a6..05a8b4da7 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/trace/DefaultTraceTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/trace/DefaultTraceTests.java @@ -1,7 +1,6 @@ package org.springframework.cloud.sleuth.trace; import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.notNullValue; import static org.junit.Assert.assertThat; @@ -9,6 +8,8 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import lombok.Data; + import org.junit.Test; import org.springframework.cloud.sleuth.RandomUuidGenerator; import org.springframework.cloud.sleuth.trace.receiver.ArrayListSpanReceiver; @@ -23,14 +24,19 @@ public class DefaultTraceTests { public static final String CREATE_SIMPLE_TRACE = "createSimpleTrace"; public static final String IMPORTANT_WORK_1 = "important work 1"; public static final String IMPORTANT_WORK_2 = "important work 2"; + public static final int NUM_SPANS = 3; @Test public void tracingWorks() { ArrayListSpanReceiver spanReceiver = new ArrayListSpanReceiver(); + ListSpanStartListener listener = new ListSpanStartListener(); + List startListeners = Collections + . singletonList(listener); List spanReceivers = Collections . singletonList(spanReceiver); + DefaultTrace trace = new DefaultTrace(new IsTracingSampler(), - new RandomUuidGenerator(), spanReceivers); + new RandomUuidGenerator(), startListeners, spanReceivers); TraceScope scope = trace.startSpan(CREATE_SIMPLE_TRACE, new AlwaysSampler()); try { @@ -40,10 +46,13 @@ public class DefaultTraceTests { scope.close(); } + List startedSpans = listener.getSpans(); + assertThat("startedSpans was null", startedSpans, is(notNullValue())); + assertThat("startedSpans was wrong size", startedSpans.size(), is(NUM_SPANS)); + List spans = spanReceiver.getSpans(); assertThat("spans was null", spans, is(notNullValue())); - assertThat("spans was empty", spans.isEmpty(), not(true)); - assertThat("spans was wrong size", spans.size(), is(3)); + assertThat("spans was wrong size", spans.size(), is(NUM_SPANS)); Span root = assertSpan(spans, null, CREATE_SIMPLE_TRACE); Span child = assertSpan(spans, root.getSpanId(), IMPORTANT_WORK_1); @@ -101,4 +110,14 @@ public class DefaultTraceTests { cur.close(); } } + + @Data + class ListSpanStartListener implements SpanStartListener { + private ArrayList spans = new ArrayList<>(); + + @Override + public void startSpan(Span span) { + spans.add(span); + } + } } diff --git a/spring-cloud-sleuth-sample/pom.xml b/spring-cloud-sleuth-sample/pom.xml index 766232a85..012281b99 100644 --- a/spring-cloud-sleuth-sample/pom.xml +++ b/spring-cloud-sleuth-sample/pom.xml @@ -47,7 +47,7 @@ org.springframework.cloud - spring-cloud-sleuth-zipkin + spring-cloud-sleuth-core org.projectlombok diff --git a/spring-cloud-sleuth-sample/src/main/java/org/springframework/cloud/sleuth/sample/SampleApplication.java b/spring-cloud-sleuth-sample/src/main/java/org/springframework/cloud/sleuth/sample/SampleApplication.java index ace29542f..1abd724c2 100644 --- a/spring-cloud-sleuth-sample/src/main/java/org/springframework/cloud/sleuth/sample/SampleApplication.java +++ b/spring-cloud-sleuth-sample/src/main/java/org/springframework/cloud/sleuth/sample/SampleApplication.java @@ -9,6 +9,9 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.context.embedded.EmbeddedServletContainerInitializedEvent; +import org.springframework.cloud.sleuth.trace.Trace; +import org.springframework.cloud.sleuth.trace.TraceScope; +import org.springframework.cloud.sleuth.trace.sampler.AlwaysSampler; import org.springframework.context.ApplicationListener; import org.springframework.context.annotation.Configuration; import org.springframework.web.bind.annotation.RequestMapping; @@ -22,43 +25,61 @@ import org.springframework.web.client.RestTemplate; @EnableAutoConfiguration @RestController @Slf4j -public class SampleApplication implements ApplicationListener { +public class SampleApplication implements + ApplicationListener { public static final String CLIENT_NAME = "testApp"; - @Autowired - private RestTemplate restTemplate; - private int port; + @Autowired + private RestTemplate restTemplate; + @Autowired + private Trace trace; + private int port; - @SneakyThrows - @RequestMapping("/") + @SneakyThrows + @RequestMapping("/") public String hi() { - final Random random = new Random(); - Thread.sleep(random.nextInt(1000)); + final Random random = new Random(); + Thread.sleep(random.nextInt(1000)); - String s = restTemplate.getForObject("http://localhost:" + port + "/hi2", String.class); - return "hi/"+s; + String s = restTemplate.getForObject("http://localhost:" + port + "/hi2", + String.class); + return "hi/" + s; } - @SneakyThrows - @RequestMapping("/hi2") - public String hi2() { - final Random random = new Random(); - Thread.sleep(random.nextInt(1000)); - return "hi2"; - } + @SneakyThrows + @RequestMapping("/hi2") + public String hi2() { + final Random random = new Random(); + Thread.sleep(random.nextInt(1000)); + return "hi2"; + } + + @SneakyThrows + @RequestMapping("/traced") + public String traced() { + TraceScope scope = trace.startSpan("customTraceEndpoint", new AlwaysSampler()); + final Random random = new Random(); + int millis = random.nextInt(1000); + log.info("Sleeping for {} millis", millis); + Thread.sleep(millis); + + String s = restTemplate.getForObject("http://localhost:" + port + "/hi2", String.class); + scope.close(); + return "hi/"+s; + } public static void main(String[] args) { SpringApplication.run(SampleApplication.class, args); } - /*@Bean - public SpanCollector spanCollector() { - return new LoggingSpanCollectorImpl(); - }*/ + /* + * @Bean public SpanCollector spanCollector() { return new LoggingSpanCollectorImpl(); + * } + */ - @Override - public void onApplicationEvent(EmbeddedServletContainerInitializedEvent event) { - port = event.getEmbeddedServletContainer().getPort(); - } + @Override + public void onApplicationEvent(EmbeddedServletContainerInitializedEvent event) { + port = event.getEmbeddedServletContainer().getPort(); + } } diff --git a/spring-cloud-sleuth-sample/src/main/resources/logback.xml b/spring-cloud-sleuth-sample/src/main/resources/logback.xml new file mode 100644 index 000000000..6ae7c6632 --- /dev/null +++ b/spring-cloud-sleuth-sample/src/main/resources/logback.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file