diff --git a/docs/src/main/asciidoc/intro.adoc b/docs/src/main/asciidoc/intro.adoc index fa1ae62d3..0fe0cdd50 100644 --- a/docs/src/main/asciidoc/intro.adoc +++ b/docs/src/main/asciidoc/intro.adoc @@ -1,2 +1,13 @@ TODO: intro Spring Cloud Sleuth +=== Terminology + +Spring Cloud Sleuth borrows http://research.google.com/pubs/pub36356.html[Dapper's] terminology. + +*Span:* The basic unit of work. For example, sending an RPC is a new span, as is sending a response to an RPC. Span's are identified by a unique 64-bit ID for the span and another 64-bit ID for the trace the span is a part of. Spans also have other data, such as descriptions, key-value annotations, the ID of the span that caused them, and process ID's (normally IP address). + +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. + +*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/pom.xml b/pom.xml index 58da79494..a9823192e 100644 --- a/pom.xml +++ b/pom.xml @@ -27,8 +27,6 @@ spring-cloud-sleuth-core - spring-cloud-sleuth-correlation - spring-cloud-sleuth-zipkin spring-cloud-sleuth-sample docs @@ -148,7 +146,7 @@ org.projectlombok lombok - 1.12.6 + 1.16.4 provided @@ -156,6 +154,11 @@ hystrix-core 1.4.5 + + com.netflix.zuul + zuul-core + 1.0.28 + org.aspectj aspectjrt diff --git a/spring-cloud-sleuth-core/pom.xml b/spring-cloud-sleuth-core/pom.xml index edcea1b3f..e1ca6b9d9 100644 --- a/spring-cloud-sleuth-core/pom.xml +++ b/spring-cloud-sleuth-core/pom.xml @@ -42,6 +42,26 @@ spring-boot-starter-actuator true + + com.netflix.hystrix + hystrix-core + true + + + com.netflix.zuul + zuul-core + true + + + io.reactivex + rxjava + true + + + org.aspectj + aspectjrt + true + org.projectlombok lombok diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/ArrayListSpanAccumulator.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/ArrayListSpanAccumulator.java new file mode 100644 index 000000000..543ac1d04 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/ArrayListSpanAccumulator.java @@ -0,0 +1,21 @@ +package org.springframework.cloud.sleuth; + +import java.util.ArrayList; + +import lombok.Value; + +import org.springframework.cloud.sleuth.event.SpanStoppedEvent; +import org.springframework.context.ApplicationListener; + +/** + * @author Spencer Gibb + */ +@Value +public class ArrayListSpanAccumulator implements ApplicationListener { + private final ArrayList spans = new ArrayList<>(); + + @Override + public void onApplicationEvent(SpanStoppedEvent event) { + spans.add(event.getSpan()); + } +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/DefaultTrace.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/DefaultTrace.java new file mode 100644 index 000000000..7a7c10a7c --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/DefaultTrace.java @@ -0,0 +1,156 @@ +package org.springframework.cloud.sleuth; + +import static org.springframework.cloud.sleuth.Utils.error; + +import java.util.Collections; +import java.util.concurrent.Callable; + +import org.springframework.cloud.sleuth.event.SpanStartedEvent; +import org.springframework.cloud.sleuth.instrument.TraceCallable; +import org.springframework.cloud.sleuth.instrument.TraceRunnable; +import org.springframework.context.ApplicationEventPublisher; + +/** + * @author Spencer Gibb + */ +public class DefaultTrace implements Trace { + + private final Sampler defaultSampler; + + private final IdGenerator idGenerator; + + private final ApplicationEventPublisher publisher; + + public DefaultTrace(Sampler defaultSampler, IdGenerator idGenerator, + ApplicationEventPublisher publisher) { + this.defaultSampler = defaultSampler; + this.idGenerator = idGenerator; + this.publisher = publisher; + } + + @Override + public TraceScope startSpan(String name) { + return this.startSpan(name, defaultSampler); + } + + @Override + public TraceScope startSpan(String name, TraceInfo tinfo) { + if (tinfo == null) return doStart(null); + MilliSpan span = MilliSpan.builder() + .begin(System.currentTimeMillis()) + .name(name) + .traceId(tinfo.getTraceId()) + .spanId(idGenerator.create()) + .parent(tinfo.getSpanId()) + .build(); + return doStart(span); + } + + @Override + public TraceScope startSpan(String name, Span parent) { + if (parent == null) { + return startSpan(name); + } + Span currentSpan = getCurrentSpan(); + if ((currentSpan != null) && (currentSpan != parent)) { + error("HTrace client error: thread " + + Thread.currentThread().getName() + " tried to start a new Span " + + "with parent " + parent.toString() + ", but there is already a " + + "currentSpan " + currentSpan); + } + return doStart(createChild(parent, name)); + } + + @Override + public TraceScope startSpan(String name, Sampler s) { + return startSpan(name, s, null); + } + + @Override + public TraceScope startSpan(String name, Sampler s, T info) { + Span span = null; + if (TraceContextHolder.isTracing() || s.next(info)) { + span = createNew(name); + } + return doStart(span); + } + + protected Span createNew(String name) { + Span parent = getCurrentSpan(); + if (parent == null) { + return MilliSpan.builder() + .begin(System.currentTimeMillis()) + .name(name) + .traceId(idGenerator.create()) + .spanId(idGenerator.create()) + .build(); + } else { + return createChild(parent, name); + } + } + + protected Span createChild(Span parent, String childname) { + return MilliSpan.builder() + .begin(System.currentTimeMillis()) + .name(childname) + .traceId(parent.getTraceId()) + .parent(parent.getSpanId()) + .spanId(idGenerator.create()) + .processId(parent.getProcessId()) + .build(); + } + + protected TraceScope doStart(Span span) { + if (span != null) { + publisher.publishEvent(new SpanStartedEvent(this, span)); + } + return continueSpan(span); + } + + @Override + public TraceScope continueSpan(Span span) { + // Return an empty TraceScope that does nothing on close + if (span == null) return NullScope.INSTANCE; + Span oldSpan = getCurrentSpan(); + TraceContextHolder.setCurrentSpan(span); + return new TraceScope(this.publisher, span, oldSpan); + } + + protected Span getCurrentSpan() { + return TraceContextHolder.getCurrentSpan(); + } + + @Override + public void addKVAnnotation(String key, String value) { + Span s = getCurrentSpan(); + if (s != null) { + s.addKVAnnotation(key, value); + } + } + + /** + * Wrap the callable in a TraceCallable, if tracing. + * + * @return The callable provided, wrapped if tracing, 'callable' if not. + */ + @Override + public Callable wrap(Callable callable) { + if (TraceContextHolder.isTracing()) { + return new TraceCallable<>(this, callable, TraceContextHolder.getCurrentSpan()); + } + return callable; + } + + /** + * Wrap the runnable in a TraceRunnable, if tracing. + * + * @return The runnable provided, wrapped if tracing, 'runnable' if not. + */ + @Override + public Runnable wrap(Runnable runnable) { + if (TraceContextHolder.isTracing()) { + return new TraceRunnable(this, runnable, TraceContextHolder.getCurrentSpan()); + } + return runnable; + } +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/IdGenerator.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/IdGenerator.java new file mode 100644 index 000000000..41e66284f --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/IdGenerator.java @@ -0,0 +1,8 @@ +package org.springframework.cloud.sleuth; + +/** + * @author Spencer Gibb + */ +public interface IdGenerator { + String create(); +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/MilliSpan.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/MilliSpan.java new file mode 100644 index 000000000..858060afb --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/MilliSpan.java @@ -0,0 +1,67 @@ +package org.springframework.cloud.sleuth; + +import java.util.List; +import java.util.Map; + +import lombok.Builder; +import lombok.Singular; +import lombok.Value; +import lombok.experimental.NonFinal; + +/** + * @author Spencer Gibb + */ +@Value +@Builder +public class MilliSpan implements Span { + private long begin; + @NonFinal + private long end = 0; + private String name; + private String traceId; + @Singular + private List parents; + private String spanId; + private Map kVAnnotations; + private String processId; + @Singular + private List timelineAnnotations; + + @Override + public synchronized void stop() { + if (end == 0) { + if (begin == 0) { + throw new IllegalStateException("Span for " + name + + " has not been started"); + } + end = System.currentTimeMillis(); + } + } + + @Override + public synchronized long getAccumulatedMillis() { + if (begin == 0) { + return 0; + } + if (end > 0) { + return end - begin; + } + return System.currentTimeMillis() - begin; + } + + @Override + public synchronized boolean isRunning() { + return begin != 0 && end == 0; + } + + @Override + public void addKVAnnotation(String key, String value) { + kVAnnotations.put(key, value); + } + + @Override + public void addTimelineAnnotation(String msg) { + timelineAnnotations.add(new TimelineAnnotation(System.currentTimeMillis(), msg)); + } + +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/NullScope.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/NullScope.java new file mode 100644 index 000000000..82bad3bcf --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/NullScope.java @@ -0,0 +1,31 @@ +package org.springframework.cloud.sleuth; + +/** + * @author Spencer Gibb + */ +/** + * Singleton instance representing an empty {@link TraceScope}. + */ +public final class NullScope extends TraceScope { + + public static final TraceScope INSTANCE = new NullScope(); + + private NullScope() { + super(null, null, null); + } + + @Override + public Span detach() { + return null; + } + + @Override + public void close() { + return; + } + + @Override + public String toString() { + return "NullScope"; + } +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/RandomUuidGenerator.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/RandomUuidGenerator.java new file mode 100644 index 000000000..1a41f28e2 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/RandomUuidGenerator.java @@ -0,0 +1,14 @@ +package org.springframework.cloud.sleuth; + +import java.util.UUID; + +/** + * @author Spencer Gibb + */ +public class RandomUuidGenerator implements IdGenerator { + + @Override + public String create() { + return UUID.randomUUID().toString(); + } +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Sampler.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Sampler.java new file mode 100644 index 000000000..336aa6896 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Sampler.java @@ -0,0 +1,33 @@ +package org.springframework.cloud.sleuth; + +/** + * Extremely simple callback to determine the frequency that an action should be + * performed. + *

+ * 'T' is the object type you require to create a more advanced sampling + * function. For example if there is some RPC information in a 'Call' object, + * you might implement Sampler. Then when the RPC is received you can call + * one of the Trace.java functions that takes the extra 'info' parameter, which + * will be passed into the next function you implemented. + *

+ * For the example above, the next(T info) function may look like this + *

+ *

+ * public boolean next(T info) {
+ *   if (info == null) {
+ *     return false;
+ *   } else if (info.getName().equals("get")) {
+ *     return Math.random() > 0.5;
+ *   } else if (info.getName().equals("put")) {
+ *     return Math.random() > 0.25;
+ *   } else {
+ *     return false;
+ *   }
+ * }
+ * 
+ * 
+ * This would trace 50% of all gets, 75% of all puts and would not trace any other requests. + */ +public interface Sampler { + boolean next(T info); +} 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 new file mode 100644 index 000000000..14117f572 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java @@ -0,0 +1,99 @@ +package org.springframework.cloud.sleuth; + +import java.util.List; +import java.util.Map; + +/** + * Base interface for gathering and reporting statistics about a block of + * execution. + *

+ * Spans should form a directed acyclic graph structure. It should be possible + * to keep following the parents of a span until you arrive at a span with no + * parents.

+ */ +public interface Span { + /** + * The block has completed, stop the clock + */ + void stop(); + + /** + * Get the start time, in milliseconds + */ + long getBegin(); + + /** + * Get the stop time, in milliseconds + */ + long getEnd(); + + /** + * Return the total amount of time elapsed since start was called, if running, + * or difference between stop and start + */ + long getAccumulatedMillis(); + + /** + * Has the span been started and not yet stopped? + */ + boolean isRunning(); + + /** + * Return a textual name of this span.

+ *

+ * Will never be null. + */ + String getName(); + + /** + * A pseudo-unique (random) number assigned to this span instance.

+ *

+ * The spanId is immutable and cannot be changed. It is safe to access this + * from multiple threads. + */ + String getSpanId(); + + /** + * A pseudo-unique (random) number assigned to the trace associated with this + * span + */ + String getTraceId(); + + /** + * Returns the parent IDs of the span.

+ *

+ * The collection will be empty if there are no parents. + */ + List getParents(); + + /** + * Add a data annotation associated with this span + */ + void addKVAnnotation(String key, String value); + + /** + * Add a timeline annotation associated with this span + */ + void addTimelineAnnotation(String msg); + + /** + * Get data associated with this span (read only)

+ *

+ * Will never be null. + */ + Map getKVAnnotations(); + + /** + * Get any timeline annotations (read only)

+ *

+ * Will never be null. + */ + List getTimelineAnnotations(); + + /** + * Return a unique id for the process from which this Span originated.

+ *

+ * Will never be null. + */ + String getProcessId(); +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/TimelineAnnotation.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/TimelineAnnotation.java new file mode 100644 index 000000000..7c31d6845 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/TimelineAnnotation.java @@ -0,0 +1,12 @@ +package org.springframework.cloud.sleuth; + +import lombok.Data; + +/** + * @author Spencer Gibb + */ +@Data +public class TimelineAnnotation { + private final long time; + private final String msg; +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Trace.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Trace.java new file mode 100644 index 000000000..6c9f04116 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Trace.java @@ -0,0 +1,85 @@ +package org.springframework.cloud.sleuth; + +import java.util.concurrent.Callable; + +/** + * The Trace class is the primary way to interact with the library. It provides + * methods to create and manipulate spans. + * + * A 'Span' represents a length of time. It has many other attributes such as a + * name, ID, and even potentially a set of key/value strings attached to + * it. + * + * Each thread in your application has a single currently active currentSpan + * associated with it. When this is non-null, it represents the current + * operation that the thread is doing. Spans are NOT thread-safe, and must + * never be used by multiple threads at once. With care, it is possible to + * safely pass a Span object between threads, but in most cases this is not + * necessary. + * + * A 'TraceScope' can either be empty, or contain a Span. TraceScope objects + * implement the Java's Closeable interface. Similar to file descriptors, they + * must be closed after they are created. When a TraceScope contains a Span, + * this span is closed when the scope is closed. + * + * The 'startSpan' methods in this class do a few things: + *

    + *
  • Create a new Span which has this thread's currentSpan as one of its parents.
  • + *
  • Set currentSpan to the new Span.
  • + *
  • Create a TraceSpan object to manage the new Span.
  • + *
+ * + * Closing a TraceScope does a few things: + *
    + *
  • It closes the span which the scope was managing.
  • + *
  • Set currentSpan to the previous currentSpan (which may be null).
  • + *
+ */ +public interface Trace { + + String SPAN_ID_NAME = "Span-Id"; + String TRACE_ID_NAME = "Trace-Id"; + + /** + * Creates a new trace scope. + *

+ * If this thread has a currently active trace span, the trace scope we create + * here will contain a new span descending from the currently active span. + * If there is no currently active trace span, the trace scope we create will + * be empty. + * + * @param name The name field for the new span to create. + */ + TraceScope startSpan(String name); + + TraceScope startSpan(String name, TraceInfo tinfo); + + /** + * Creates a new trace scope. + *

+ * If this thread has a currently active trace span, it must be the 'parent' + * span that you pass in here as a parameter. The trace scope we create here + * will contain a new span which is a child of 'parent'. + * + * @param name The name field for the new span to create. + */ + TraceScope startSpan(String name, Span parent); + + TraceScope startSpan(String name, Sampler s); + + TraceScope startSpan(String name, Sampler s, T info); + + /** + * Pick up an existing span from another thread. + */ + TraceScope continueSpan(Span s); + + /** + * Adds a data annotation to the current span if tracing is currently on. + */ + void addKVAnnotation(String key, String value); + + Callable wrap(Callable callable); + + Runnable wrap(Runnable runnable); +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/TraceAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/TraceAutoConfiguration.java new file mode 100644 index 000000000..84ff1e3b6 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/TraceAutoConfiguration.java @@ -0,0 +1,33 @@ +package org.springframework.cloud.sleuth; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.cloud.sleuth.sampler.IsTracingSampler; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @author Spencer Gibb + */ +@Configuration +public class TraceAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + public IdGenerator idGenerator() { + return new RandomUuidGenerator(); + } + + @Bean + @ConditionalOnMissingBean + public Sampler defaultSampler() { + return new IsTracingSampler(); + } + + @Bean + @ConditionalOnMissingBean + public Trace trace(Sampler sampler, IdGenerator idGenerator, + ApplicationEventPublisher publisher) { + return new DefaultTrace(sampler, idGenerator, publisher); + } +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/TraceContextHolder.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/TraceContextHolder.java new file mode 100644 index 000000000..f544c1a9d --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/TraceContextHolder.java @@ -0,0 +1,27 @@ +package org.springframework.cloud.sleuth; + +import lombok.extern.apachecommons.CommonsLog; +import org.springframework.core.NamedThreadLocal; + +/** + * @author Spencer Gibb + */ +@CommonsLog +public class TraceContextHolder { + private static final ThreadLocal currentSpan = new NamedThreadLocal<>("Trace Context"); + + public static Span getCurrentSpan() { + return currentSpan.get(); + } + + public static void setCurrentSpan(Span span) { + if (log.isTraceEnabled()) { + log.trace("Setting current span " + span); + } + currentSpan.set(span); + } + + public static boolean isTracing() { + return currentSpan.get() != null; + } +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/TraceInfo.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/TraceInfo.java new file mode 100644 index 000000000..5f8485eab --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/TraceInfo.java @@ -0,0 +1,12 @@ +package org.springframework.cloud.sleuth; + +import lombok.Data; + +/** + * @author Spencer Gibb + */ +@Data +public class TraceInfo { + private final String traceId; + private final String spanId; +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/TraceScope.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/TraceScope.java new file mode 100644 index 000000000..695f913b0 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/TraceScope.java @@ -0,0 +1,86 @@ +package org.springframework.cloud.sleuth; + +import java.io.Closeable; + +import lombok.SneakyThrows; +import lombok.Value; +import lombok.experimental.NonFinal; + +import org.springframework.cloud.sleuth.event.SpanStoppedEvent; +import org.springframework.context.ApplicationEventPublisher; + +/** + * @author Spencer Gibb + */ +@Value +@NonFinal +public class TraceScope implements Closeable { + + private final ApplicationEventPublisher publisher; + + /** + * the span for this scope + */ + private final Span span; + + /** + * the span that was "current" before this scope was entered + */ + private final Span savedSpan; + + @NonFinal + private boolean detached = false; + + public TraceScope(ApplicationEventPublisher publisher, Span span, Span savedSpan) { + this.publisher = publisher; + this.span = span; + this.savedSpan = savedSpan; + } + + /** + * Remove this span as the current thread, but don't stop it yet or + * send it for collection. This is useful if the span object is then + * passed to another thread for use with Trace.continueTrace(). + * + * @return the same Span object + */ + public Span detach() { + if (detached) { + Utils.error("Tried to detach trace span " + span + " but " + + "it has already been detached."); + } + detached = true; + + Span cur = TraceContextHolder.getCurrentSpan(); + if (cur != span) { + Utils.error("Tried to detach trace span " + span + " but " + + "it is not the current span for the " + + Thread.currentThread().getName() + " thread. You have " + + "probably forgotten to close or detach " + cur); + } else { + TraceContextHolder.setCurrentSpan(savedSpan); + } + return span; + } + + @Override + @SneakyThrows + public void close() { + if (detached) { + return; + } + detached = true; + Span cur = TraceContextHolder.getCurrentSpan(); + if (cur != span) { + Utils.error("Tried to close trace span " + span + " but " + + "it is not the current span for the " + + Thread.currentThread().getName() + " thread. You have " + + "probably forgotten to close or detach " + cur); + } else { + span.stop(); + this.publisher.publishEvent(new SpanStoppedEvent(this, span)); + TraceContextHolder.setCurrentSpan(savedSpan); + } + } + +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Utils.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Utils.java new file mode 100644 index 000000000..09c4dbac5 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Utils.java @@ -0,0 +1,14 @@ +package org.springframework.cloud.sleuth; + +import lombok.extern.apachecommons.CommonsLog; + +/** + * @author Spencer Gibb + */ +@CommonsLog +public abstract class Utils { + public static void error(String msg) { + log.error(msg); + throw new RuntimeException(msg); + } +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/event/SpanStartedEvent.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/event/SpanStartedEvent.java new file mode 100644 index 000000000..49e5a7cdb --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/event/SpanStartedEvent.java @@ -0,0 +1,19 @@ +package org.springframework.cloud.sleuth.event; + +import lombok.Value; +import org.springframework.cloud.sleuth.Span; +import org.springframework.context.ApplicationEvent; + +/** + * @author Spencer Gibb + */ +@Value +public class SpanStartedEvent extends ApplicationEvent { + + private final Span span; + + public SpanStartedEvent(Object source, Span span) { + super(source); + this.span = span; + } +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/event/SpanStoppedEvent.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/event/SpanStoppedEvent.java new file mode 100644 index 000000000..5272bfc92 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/event/SpanStoppedEvent.java @@ -0,0 +1,19 @@ +package org.springframework.cloud.sleuth.event; + +import lombok.Value; +import org.springframework.cloud.sleuth.Span; +import org.springframework.context.ApplicationEvent; + +/** + * @author Spencer Gibb + */ +@Value +public class SpanStoppedEvent extends ApplicationEvent { + + private final Span span; + + public SpanStoppedEvent(Object source, Span span) { + super(source); + this.span = span; + } +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/TraceCallable.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/TraceCallable.java new file mode 100644 index 000000000..c2aaa1e1f --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/TraceCallable.java @@ -0,0 +1,47 @@ +package org.springframework.cloud.sleuth.instrument; + +import java.util.concurrent.Callable; + +import lombok.Value; + +import org.springframework.cloud.sleuth.Span; +import org.springframework.cloud.sleuth.Trace; +import org.springframework.cloud.sleuth.TraceScope; + +/** + * @author Spencer Gibb + */ +@Value +public class TraceCallable extends TraceDelegate> implements Callable { + + public TraceCallable(Trace trace, Callable delagate) { + super(trace, delagate); + } + + public TraceCallable(Trace trace, Callable delagate, Span parent) { + super(trace, delagate, parent); + } + + public TraceCallable(Trace trace, Callable delagate, Span parent, String name) { + super(trace, delagate, parent, name); + } + + @Override + public V call() throws Exception { + if (this.parent != null) { + TraceScope scope = startSpan(); + + try { + return this.delagate.call(); + } + finally { + scope.close(); + } + + } + else { + return this.delagate.call(); + } + } + +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/TraceDelegate.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/TraceDelegate.java new file mode 100644 index 000000000..458de89d4 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/TraceDelegate.java @@ -0,0 +1,40 @@ +package org.springframework.cloud.sleuth.instrument; + +import org.springframework.cloud.sleuth.Span; +import org.springframework.cloud.sleuth.Trace; +import org.springframework.cloud.sleuth.TraceContextHolder; +import org.springframework.cloud.sleuth.TraceScope; + +/** + * @author Spencer Gibb + */ +public abstract class TraceDelegate { + + protected final Trace trace; + protected final T delagate; + protected final Span parent; + protected final String name; + + public TraceDelegate(Trace trace, T delagate) { + this(trace, delagate, TraceContextHolder.getCurrentSpan(), null); + } + + public TraceDelegate(Trace trace, T delagate, Span parent) { + this(trace, delagate, parent, null); + } + + public TraceDelegate(Trace trace, T delagate, Span parent, String name) { + this.trace = trace; + this.delagate = delagate; + this.parent = parent; + this.name = name; + } + + protected TraceScope startSpan() { + return this.trace.startSpan(getSpanName(), this.parent); + } + + protected String getSpanName() { + return this.name == null ? Thread.currentThread().getName() : name; + } +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/TraceRunnable.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/TraceRunnable.java new file mode 100644 index 000000000..259360f6f --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/TraceRunnable.java @@ -0,0 +1,43 @@ +package org.springframework.cloud.sleuth.instrument; + +import lombok.Value; + +import org.springframework.cloud.sleuth.Span; +import org.springframework.cloud.sleuth.Trace; +import org.springframework.cloud.sleuth.TraceScope; + +/** + * @author Spencer Gibb + */ +@Value +public class TraceRunnable extends TraceDelegate implements Runnable { + + public TraceRunnable(Trace trace, Runnable delagate) { + super(trace, delagate); + } + + public TraceRunnable(Trace trace, Runnable delagate, Span parent) { + super(trace, delagate, parent); + } + + public TraceRunnable(Trace trace, Runnable delagate, Span parent, String name) { + super(trace, delagate, parent, name); + } + + @Override + public void run() { + if (this.parent != null) { + TraceScope scope = startSpan(); + + try { + this.delagate.run(); + } + finally { + scope.close(); + } + } + else { + this.delagate.run(); + } + } +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/circuitbreaker/TraceCommand.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/circuitbreaker/TraceCommand.java new file mode 100644 index 000000000..60fb09713 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/circuitbreaker/TraceCommand.java @@ -0,0 +1,59 @@ +package org.springframework.cloud.sleuth.instrument.circuitbreaker; + +import com.netflix.hystrix.HystrixCommand; +import com.netflix.hystrix.HystrixCommandGroupKey; +import com.netflix.hystrix.HystrixThreadPoolKey; +import org.springframework.cloud.sleuth.Trace; +import org.springframework.cloud.sleuth.TraceScope; + +/** + * Abstraction over {@code HystrixCommand} that wraps command execution with Trace setting + * + * @see HystrixCommand + * @see Trace + * + * @author Tomasz Nurkiewicz, 4financeIT + * @author Marcin Grzejszczak, 4financeIT + * @author Spencer Gibb + */ +public abstract class TraceCommand extends HystrixCommand { + + private Trace trace; + + protected TraceCommand(Trace trace, HystrixCommandGroupKey group) { + super(group); + this.trace = trace; + } + + protected TraceCommand(Trace trace, HystrixCommandGroupKey group, HystrixThreadPoolKey threadPool) { + super(group, threadPool); + this.trace = trace; + } + + protected TraceCommand(Trace trace, HystrixCommandGroupKey group, int executionIsolationThreadTimeoutInMilliseconds) { + super(group, executionIsolationThreadTimeoutInMilliseconds); + this.trace = trace; + } + + protected TraceCommand(Trace trace, HystrixCommandGroupKey group, HystrixThreadPoolKey threadPool, int executionIsolationThreadTimeoutInMilliseconds) { + super(group, threadPool, executionIsolationThreadTimeoutInMilliseconds); + this.trace = trace; + } + + protected TraceCommand(Trace trace, Setter setter) { + super(setter); + this.trace = trace; + } + + @Override + protected R run() throws Exception { + TraceScope scope = trace.startSpan(getCommandKey().name()); + try { + return doRun(); + } finally { + scope.close(); + } + } + + public abstract R doRun() throws Exception; +} 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 new file mode 100644 index 000000000..cfa788e7f --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/scheduling/TraceSchedulingAspect.java @@ -0,0 +1,39 @@ +package org.springframework.cloud.sleuth.instrument.scheduling; + +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.springframework.cloud.sleuth.Trace; +import org.springframework.cloud.sleuth.TraceScope; +import org.springframework.scheduling.annotation.Scheduled; + +/** + * Aspect that creates a new Span for running threads executing methods annotated with {@link Scheduled} annotation. + * For every execution of scheduled method a new trace will be started. + * + * @author Tomasz Nurkewicz, 4financeIT + * @author Michal Chmielarz, 4financeIT + * @author Marcin Grzejszczak, 4financeIT + * @author Spencer Gibb + * + * @see Trace + */ +@Aspect +public class TraceSchedulingAspect { + + private final Trace trace; + + public TraceSchedulingAspect(Trace trace) { + this.trace = trace; + } + + @Around("execution (@org.springframework.scheduling.annotation.Scheduled * *.*(..))") + public Object traceSceduledThread(final ProceedingJoinPoint pjp) throws Throwable { + TraceScope scope = trace.startSpan(pjp.toShortString()); + try { + return pjp.proceed(); + } finally { + scope.close(); + } + } +} 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 new file mode 100644 index 000000000..8444b4793 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/scheduling/TraceSchedulingAutoConfiguration.java @@ -0,0 +1,33 @@ +package org.springframework.cloud.sleuth.instrument.scheduling; + +/** + * @author Spencer Gibb + */ + +import org.aspectj.lang.ProceedingJoinPoint; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.cloud.sleuth.Trace; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.EnableAspectJAutoProxy; +import org.springframework.scheduling.annotation.EnableScheduling; + +/** + * Registers beans related to task scheduling. + * + * @see TraceSchedulingAspect + * + * @author Michal Chmielarz, 4financeIT + * @author Spencer Gibb + */ +@Configuration +@EnableScheduling +@EnableAspectJAutoProxy +@ConditionalOnClass(ProceedingJoinPoint.class) +public class TraceSchedulingAutoConfiguration { + + @Bean + public TraceSchedulingAspect traceSchedulingAspect(Trace trace) { + return new TraceSchedulingAspect(trace); + } +} 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 new file mode 100644 index 000000000..b6dee3321 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceFilter.java @@ -0,0 +1,118 @@ +/* + * Copyright 2012-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.web; + +import static org.springframework.cloud.sleuth.Trace.SPAN_ID_NAME; +import static org.springframework.cloud.sleuth.Trace.TRACE_ID_NAME; +import static org.springframework.util.StringUtils.hasText; + +import java.io.IOException; +import java.util.regex.Pattern; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.cloud.sleuth.Trace; +import org.springframework.cloud.sleuth.TraceInfo; +import org.springframework.cloud.sleuth.TraceScope; +import org.springframework.web.filter.OncePerRequestFilter; + +/** + * Filter that takes the value of the {@link Trace#SPAN_ID_NAME} and + * {@link Trace#TRACE_ID_NAME} header from either request or response and uses them to + * create a new span. + * + * @see Trace + * + * @author Jakub Nabrdalik, 4financeIT + * @author Tomasz Nurkiewicz, 4financeIT + * @author Marcin Grzejszczak, 4financeIT + * @author Spencer Gibb + */ +public class TraceFilter extends OncePerRequestFilter { + + public static final Pattern DEFAULT_SKIP_PATTERN = Pattern + .compile("/api-docs.*|/autoconfig|/configprops|/dump|/info|/metrics.*|/mappings|/trace|/swagger.*|.*\\.png|.*\\.css|.*\\.js|.*\\.html"); + + private final Trace trace; + private final Pattern skipPattern; + + public TraceFilter(Trace trace) { + this.trace = trace; + this.skipPattern = DEFAULT_SKIP_PATTERN; + } + + public TraceFilter(Trace trace, Pattern skipPattern) { + this.trace = trace; + this.skipPattern = skipPattern; + } + + @Override + protected void doFilterInternal(HttpServletRequest request, + HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + + String uri = hasText(request.getRequestURI()) ? request.getRequestURI() : ""; + boolean skip = skipPattern.matcher(uri).matches(); + + TraceScope traceScope = null; + if (!skip) { + String spanId = getHeader(request, response, SPAN_ID_NAME); + String traceId = getHeader(request, response, TRACE_ID_NAME); + if (hasText(spanId) && hasText(traceId)) { + + TraceInfo traceInfo = new TraceInfo(traceId, spanId); + // TODO: trace description? + traceScope = trace.startSpan("traceFilter", traceInfo); + // Send new span id back + addToResponseIfNotPresent(response, SPAN_ID_NAME, traceScope.getSpan() + .getSpanId()); + } + else { + traceScope = trace.startSpan("traceFilter"); + } + } + + try { + filterChain.doFilter(request, response); + } + finally { + if (traceScope != null) { + traceScope.close(); + } + } + } + + private String getHeader(HttpServletRequest request, HttpServletResponse response, + String name) { + String value = request.getHeader(name); + return hasText(value) ? value : response.getHeader(name); + } + + private void addToResponseIfNotPresent(HttpServletResponse response, String name, + String value) { + if (!hasText(response.getHeader(name))) { + response.addHeader(name, value); + } + } + + @Override + protected boolean shouldNotFilterAsyncDispatch() { + return false; + } +} 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..51c46efd8 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptor.java @@ -0,0 +1,45 @@ +package org.springframework.cloud.sleuth.instrument.web; + +import org.springframework.cloud.sleuth.Trace; +import org.springframework.cloud.sleuth.TraceScope; +import org.springframework.web.servlet.HandlerInterceptor; +import org.springframework.web.servlet.ModelAndView; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * @author Spencer Gibb + */ +public class TraceHandlerInterceptor implements HandlerInterceptor { + + private static final String ATTR_NAME = "__CURRENT_TRACE_HANDLER_TRACE_SCOPE_ATTR___"; + + private final Trace trace; + + public TraceHandlerInterceptor(Trace trace) { + this.trace = trace; + } + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + //TODO: get trace data from request? + //TODO: what is the description? + TraceScope scope = trace.startSpan("traceHandlerInterceptor"); + request.setAttribute(ATTR_NAME, scope); + return true; + } + + @Override + public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { + + } + + @Override + public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { + TraceScope scope = TraceScope.class.cast(request.getAttribute(ATTR_NAME)); + if (scope != null) { + scope.close(); + } + } +} 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 new file mode 100644 index 000000000..7ffe04d2b --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAspect.java @@ -0,0 +1,82 @@ +package org.springframework.cloud.sleuth.instrument.web; + +import java.util.concurrent.Callable; + +import lombok.extern.apachecommons.CommonsLog; + +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.Trace; +import org.springframework.cloud.sleuth.TraceContextHolder; +import org.springframework.cloud.sleuth.instrument.TraceCallable; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.client.RestOperations; + +/** + * Aspect that adds correlation id to + *

+ *

    + *
  • {@link RestController} annotated classes with public {@link Callable} methods
  • + *
  • {@link Controller} annotated classes with public {@link Callable} methods
  • + *
+ *

+ * For controllers an around aspect is created that wraps the {@link Callable#call()} + * method execution in {@link TraceCallable} + *

+ * + * @see RestController + * @see Controller + * @see RestOperations + * @see TraceCallable + * @see Trace + * + * @author Tomasz Nurkewicz, 4financeIT + * @author Marcin Grzejszczak, 4financeIT + * @author Michal Chmielarz, 4financeIT + * @author Spencer Gibb + */ +@Aspect +@CommonsLog +public class TraceWebAspect { + + private final Trace trace; + + public TraceWebAspect(Trace trace) { + this.trace = trace; + } + + @Pointcut("@target(org.springframework.web.bind.annotation.RestController)") + private void anyRestControllerAnnotated() { + } + + @Pointcut("@target(org.springframework.stereotype.Controller)") + private void anyControllerAnnotated() { + } + + @Pointcut("execution(public java.util.concurrent.Callable *(..))") + private void anyPublicMethodReturningCallable() { + } + + @Pointcut("(anyRestControllerAnnotated() || anyControllerAnnotated()) && anyPublicMethodReturningCallable()") + private void anyControllerOrRestControllerWithPublicAsyncMethod() { + } + + @Around("anyControllerOrRestControllerWithPublicAsyncMethod()") + @SuppressWarnings("unchecked") + public Object wrapWithCorrelationId(ProceedingJoinPoint pjp) throws Throwable { + Callable callable = (Callable) pjp.proceed(); + if (TraceContextHolder.isTracing()) { + log.debug("Wrapping callable with span [" + + TraceContextHolder.getCurrentSpan() + "]"); + + return new TraceCallable(this.trace, callable); + } + else { + return callable; + } + } + +} 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 new file mode 100644 index 000000000..321c75dd2 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java @@ -0,0 +1,95 @@ +/* + * Copyright 2012-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.web; + +import java.util.regex.Pattern; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.context.embedded.FilterRegistrationBean; +import org.springframework.cloud.sleuth.Trace; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.util.StringUtils; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; + +/** + * Registers beans that add tracing to requests + * + * @author Tomasz Nurkewicz, 4financeIT + * @author Marcin Grzejszczak, 4financeIT + * @author Michal Chmielarz, 4financeIT + * @author Spencer Gibb + */ +@Configuration +@ConditionalOnProperty(value = "spring.cloud.sleuth.trace.web.enabled", matchIfMissing = true) +@ConditionalOnWebApplication +public class TraceWebAutoConfiguration { + + /** + * Pattern for URLs that should be skipped in tracing + */ + @Value("${spring.cloud.sleuth.instrument.web.skipPattern:}") + private String skipPattern; + + @Autowired + private Trace trace; + + @Bean + @ConditionalOnMissingBean + public TraceWebAspect traceWebAspect() { + return new TraceWebAspect(trace); + } + + //TODO: I don't think TraceHandlerInterceptor is needed with TraceFilter + /*@Bean + @ConditionalOnMissingBean + public TraceHandlerInterceptor traceHandlerInterceptor() { + return new TraceHandlerInterceptor(trace); + } + + @Bean + public WebMvcConfigurerAdapter webMvcConfigurerAdapter( + TraceHandlerInterceptor handlerInterceptor) { + return new TraceWebConfigurer(handlerInterceptor); + } + + protected static class TraceWebConfigurer extends WebMvcConfigurerAdapter { + private TraceHandlerInterceptor interceptor; + + public TraceWebConfigurer(TraceHandlerInterceptor interceptor) { + this.interceptor = interceptor; + } + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(interceptor).addPathPatterns("/**"); + } + }*/ + + @Bean + @ConditionalOnMissingBean + public FilterRegistrationBean traceFilter() { + Pattern pattern = StringUtils.hasText(skipPattern) ? Pattern.compile(skipPattern) + : TraceFilter.DEFAULT_SKIP_PATTERN; + return new FilterRegistrationBean(new TraceFilter(trace, pattern)); + } + +} 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 new file mode 100644 index 000000000..3be3db292 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptor.java @@ -0,0 +1,57 @@ +/* + * Copyright 2012-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.web.client; + +import static org.springframework.cloud.sleuth.Trace.SPAN_ID_NAME; +import static org.springframework.cloud.sleuth.Trace.TRACE_ID_NAME; +import static org.springframework.cloud.sleuth.TraceContextHolder.getCurrentSpan; +import static org.springframework.cloud.sleuth.TraceContextHolder.isTracing; + +import java.io.IOException; + +import org.springframework.cloud.sleuth.Trace; +import org.springframework.http.HttpRequest; +import org.springframework.http.client.ClientHttpRequestExecution; +import org.springframework.http.client.ClientHttpRequestInterceptor; +import org.springframework.http.client.ClientHttpResponse; + +/** + * Interceptor that verifies whether the trance and span id has been set on the + * request and sets them if one or both of them are missing. + * + * @see org.springframework.web.client.RestTemplate + * @see Trace + * + * @author Marcin Grzejszczak, 4financeIT + * @author Spencer Gibb + */ +public class TraceRestTemplateInterceptor implements ClientHttpRequestInterceptor { + + @Override + public ClientHttpResponse intercept(HttpRequest request, byte[] body, + ClientHttpRequestExecution execution) throws IOException { + setHeader(request, SPAN_ID_NAME, getCurrentSpan().getSpanId()); + setHeader(request, TRACE_ID_NAME, getCurrentSpan().getTraceId()); + return execution.execute(request, body); + } + + public void setHeader(HttpRequest request, String name, String value) { + if (!request.getHeaders().containsKey(name) && isTracing()) { + request.getHeaders().add(name, value); + } + } + +} 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 new file mode 100644 index 000000000..c45cabd06 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebClientAutoConfiguration.java @@ -0,0 +1,50 @@ +package org.springframework.cloud.sleuth.instrument.web.client; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestTemplate; + +import javax.annotation.PostConstruct; + +/** + * @author Spencer Gibb + */ +@Configuration +@ConditionalOnProperty(value = "spring.cloud.sleuth.trace.web.client.enabled", matchIfMissing = true) +@ConditionalOnClass(RestTemplate.class) +public class TraceWebClientAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + public TraceRestTemplateInterceptor traceRestTemplateInterceptor() { + return new TraceRestTemplateInterceptor(); + } + + @Bean + @ConditionalOnMissingBean + public RestTemplate restTemplate() { + return new RestTemplate(); + } + + @Configuration + protected static class TraceInterceptorConfiguration { + + @Autowired(required = false) + private RestTemplate restTemplate; + + @Autowired + private TraceRestTemplateInterceptor traceRestTemplateInterceptor; + + @PostConstruct + public void init() { + if (restTemplate != null) { + restTemplate.getInterceptors().add(traceRestTemplateInterceptor); + } + } + } +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TracePostFilter.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TracePostFilter.java new file mode 100644 index 000000000..910dbadb3 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TracePostFilter.java @@ -0,0 +1,46 @@ +package org.springframework.cloud.sleuth.instrument.zuul; + +import org.springframework.cloud.sleuth.Trace; +import org.springframework.cloud.sleuth.TraceScope; + +import com.netflix.zuul.ZuulFilter; +import com.netflix.zuul.context.RequestContext; + +/** + * @author Spencer Gibb + */ +public class TracePostFilter extends ZuulFilter { + + private Trace trace; + + public TracePostFilter(Trace trace) { + this.trace = trace; + } + + @Override + public String filterType() { + return "post"; + } + + @Override + public int filterOrder() { + return 0; + } + + @Override + public boolean shouldFilter() { + return true; + } + + @Override + public Object run() { + TraceScope traceScope = (TraceScope) RequestContext.getCurrentContext().get( + "traceScope"); + + if (traceScope != null) { + traceScope.close(); + } + + return null; + } +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TracePreFilter.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TracePreFilter.java new file mode 100644 index 000000000..f48d377f6 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TracePreFilter.java @@ -0,0 +1,65 @@ +package org.springframework.cloud.sleuth.instrument.zuul; + +import static org.springframework.cloud.sleuth.Trace.SPAN_ID_NAME; +import static org.springframework.cloud.sleuth.Trace.TRACE_ID_NAME; +import static org.springframework.util.StringUtils.hasText; + +import javax.servlet.http.HttpServletRequest; + +import org.springframework.cloud.sleuth.Trace; +import org.springframework.cloud.sleuth.TraceInfo; +import org.springframework.cloud.sleuth.TraceScope; + +import com.netflix.zuul.ZuulFilter; +import com.netflix.zuul.context.RequestContext; + +/** + * @author Spencer Gibb + */ +public class TracePreFilter extends ZuulFilter { + + private Trace trace; + + public TracePreFilter(Trace trace) { + this.trace = trace; + } + + @Override + public String filterType() { + return "pre"; + } + + @Override + public int filterOrder() { + return 0; + } + + @Override + public boolean shouldFilter() { + return true; + } + + @Override + public Object run() { + RequestContext context = RequestContext.getCurrentContext(); + HttpServletRequest request = context.getRequest(); + + String spanId = request.getHeader(SPAN_ID_NAME); + String traceId = request.getHeader(TRACE_ID_NAME); + TraceScope traceScope = null; + if (hasText(spanId) && hasText(traceId)) { + + TraceInfo traceInfo = new TraceInfo(traceId, spanId); + // TODO: trace description? + traceScope = trace.startSpan("traceZuulFilter", traceInfo); + } + else { + traceScope = trace.startSpan("traceZuulFilter"); + + } + + context.set("traceScope", traceScope); + + return null; + } +} 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 new file mode 100644 index 000000000..18d542435 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulAutoConfiguration.java @@ -0,0 +1,28 @@ +package org.springframework.cloud.sleuth.instrument.zuul; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.cloud.sleuth.Trace; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import com.netflix.zuul.ZuulFilter; + +/** + * @author Spencer Gibb + */ +@Configuration +@ConditionalOnClass(ZuulFilter.class) +@ConditionalOnBean(Trace.class) +public class TraceZuulAutoConfiguration { + + @Bean + public TracePreFilter tracePreFilter(Trace trace) { + return new TracePreFilter(trace); + } + + @Bean + public TracePostFilter tracePostFilter(Trace trace) { + return new TracePostFilter(trace); + } +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/resttemplate/DefaultRestTemplateConfigurer.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/resttemplate/DefaultRestTemplateConfigurer.java deleted file mode 100644 index f7e33be80..000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/resttemplate/DefaultRestTemplateConfigurer.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2012-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.resttemplate; - -import org.springframework.http.client.ClientHttpRequestInterceptor; -import org.springframework.web.client.RestTemplate; - -import java.util.List; - -/** - * Default configure of {@code RestTemplate} that adds a list {@code ClientHttpRequestInterceptor} - * to {@code RestTemplate} - * - * @see ClientHttpRequestInterceptor - * @see RestTemplate - * - * @author Marcin Grzejszczak, 4financeIT - */ -public class DefaultRestTemplateConfigurer implements RestTemplateConfigurer { - - private final List clientHttpRequestInterceptors; - - public DefaultRestTemplateConfigurer(List clientHttpRequestInterceptors) { - this.clientHttpRequestInterceptors = clientHttpRequestInterceptors; - } - - @Override - public void modifyRestTemplate(RestTemplate restTemplate) { - for (ClientHttpRequestInterceptor clientHttpRequestInterceptor : clientHttpRequestInterceptors) { - restTemplate.getInterceptors().add(clientHttpRequestInterceptor); - } - } -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/resttemplate/RestTemplateConfigurer.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/resttemplate/RestTemplateConfigurer.java deleted file mode 100644 index 54b643d40..000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/resttemplate/RestTemplateConfigurer.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2012-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.resttemplate; - -import org.springframework.web.client.RestTemplate; - -/** - * Interface that allows to modify the {@code RestTemplate} parameters - * - * @see RestTemplate - * - * @author Marcin Grzejszczak, 4financeIT - */ -public interface RestTemplateConfigurer { - - void modifyRestTemplate(RestTemplate restTemplate); -} 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 deleted file mode 100644 index 662176624..000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/resttemplate/SleuthRestTemplateAutoConfiguration.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2012-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.resttemplate; - -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.autoconfigure.condition.ConditionalOnWebApplication; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.http.client.ClientHttpRequestInterceptor; -import org.springframework.web.client.RestTemplate; - -import java.util.ArrayList; -import java.util.List; - -/** - * - * Autoconfiguration that sets up a {@code RestTemplate} as a bean if one is not present - * and performs its additional modification via a {@code RestTemplateConfigurer}. - * - * @author Marcin Grzejszczak, 4financeIT - */ -@Configuration -@ConditionalOnWebApplication -@ConditionalOnProperty(value = "spring.cloud.sleuth.resttemplate.enabled", matchIfMissing = true) -public class SleuthRestTemplateAutoConfiguration { - - @Configuration - protected static class RestTemplateConfig { - - @Autowired - private List clientHttpRequestInterceptors = new ArrayList<>(); - - @Bean - @ConditionalOnMissingBean - public RestTemplate restTemplate() { - return new RestTemplate(); - } - - @Bean - @ConditionalOnMissingBean - public RestTemplateConfigurer restTemplateConfigurer(RestTemplate restTemplate) { - DefaultRestTemplateConfigurer configurer = new DefaultRestTemplateConfigurer(clientHttpRequestInterceptors); - configurer.modifyRestTemplate(restTemplate); - return configurer; - } - } - -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/sampler/AlwaysSampler.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/sampler/AlwaysSampler.java new file mode 100644 index 000000000..087912506 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/sampler/AlwaysSampler.java @@ -0,0 +1,13 @@ +package org.springframework.cloud.sleuth.sampler; + +import org.springframework.cloud.sleuth.Sampler; + +/** + * @author Spencer Gibb + */ +public class AlwaysSampler implements Sampler { + @Override + public boolean next(Object info) { + return true; + } +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/sampler/IsTracingSampler.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/sampler/IsTracingSampler.java new file mode 100644 index 000000000..3c8eaaf07 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/sampler/IsTracingSampler.java @@ -0,0 +1,15 @@ +package org.springframework.cloud.sleuth.sampler; + +import org.springframework.cloud.sleuth.Sampler; +import org.springframework.cloud.sleuth.TraceContextHolder; + +/** + * @author Spencer Gibb + */ +public class IsTracingSampler implements Sampler { + + @Override + public boolean next(Object info) { + return TraceContextHolder.getCurrentSpan() != null; + } +} 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..f17d0cba9 --- /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 Slf4jSpanStartedListener slf4jSpanStartedListener() { + return new Slf4jSpanStartedListener(); + } + + @Bean + public Slf4jSpanStoppedListener slf4jSpanStoppedListener() { + return new Slf4jSpanStoppedListener(); + } +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/slf4j/Slf4jSpanStartedListener.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/slf4j/Slf4jSpanStartedListener.java new file mode 100644 index 000000000..6653a4115 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/slf4j/Slf4jSpanStartedListener.java @@ -0,0 +1,25 @@ +package org.springframework.cloud.sleuth.slf4j; + +import lombok.extern.slf4j.Slf4j; + +import org.slf4j.MDC; +import org.springframework.cloud.sleuth.Span; +import org.springframework.cloud.sleuth.Trace; +import org.springframework.cloud.sleuth.event.SpanStartedEvent; +import org.springframework.context.ApplicationListener; + +/** + * @author Spencer Gibb + */ +@Slf4j +public class Slf4jSpanStartedListener implements ApplicationListener { + + @Override + public void onApplicationEvent(SpanStartedEvent event) { + Span span = event.getSpan(); + //TODO: what log level? + log.info("Starting span: {}", span); + MDC.put(Trace.SPAN_ID_NAME, span.getSpanId()); + MDC.put(Trace.TRACE_ID_NAME, span.getTraceId()); + } +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/slf4j/Slf4jSpanStoppedListener.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/slf4j/Slf4jSpanStoppedListener.java new file mode 100644 index 000000000..b5b12d053 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/slf4j/Slf4jSpanStoppedListener.java @@ -0,0 +1,24 @@ +package org.springframework.cloud.sleuth.slf4j; + +import static org.springframework.cloud.sleuth.Trace.SPAN_ID_NAME; +import static org.springframework.cloud.sleuth.Trace.TRACE_ID_NAME; + +import lombok.extern.slf4j.Slf4j; + +import org.slf4j.MDC; +import org.springframework.cloud.sleuth.event.SpanStoppedEvent; +import org.springframework.context.ApplicationListener; + +/** + * @author Spencer Gibb + */ +@Slf4j +public class Slf4jSpanStoppedListener implements ApplicationListener { + @Override + public void onApplicationEvent(SpanStoppedEvent event) { + //TODO: what should this log level be? + log.info("Received span: {}", event.getSpan()); + MDC.remove(SPAN_ID_NAME); + MDC.remove(TRACE_ID_NAME); + } +} 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..d1c8cd4aa 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,6 @@ # Auto Configuration org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ -org.springframework.cloud.sleuth.resttemplate.SleuthRestTemplateAutoConfiguration +org.springframework.cloud.sleuth.TraceAutoConfiguration,\ +org.springframework.cloud.sleuth.slf4j.SleuthSlf4jAutoConfiguration,\ +org.springframework.cloud.sleuth.instrument.web.TraceWebAutoConfiguration,\ +org.springframework.cloud.sleuth.instrument.web.client.TraceWebClientAutoConfiguration diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/DefaultTraceTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/DefaultTraceTests.java new file mode 100644 index 000000000..bd287981d --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/DefaultTraceTests.java @@ -0,0 +1,120 @@ +package org.springframework.cloud.sleuth; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.isA; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.springframework.cloud.sleuth.event.SpanStartedEvent; +import org.springframework.cloud.sleuth.event.SpanStoppedEvent; +import org.springframework.cloud.sleuth.sampler.AlwaysSampler; +import org.springframework.cloud.sleuth.sampler.IsTracingSampler; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationEventPublisher; + +/** + * @author Spencer Gibb + */ +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() { + ApplicationEventPublisher publisher = mock(ApplicationEventPublisher.class); + + DefaultTrace trace = new DefaultTrace(new IsTracingSampler(), + new RandomUuidGenerator(), publisher); + + TraceScope scope = trace.startSpan(CREATE_SIMPLE_TRACE, new AlwaysSampler()); + try { + importantWork1(trace); + } + finally { + scope.close(); + } + + verify(publisher, times(NUM_SPANS)).publishEvent(isA(SpanStartedEvent.class)); + verify(publisher, times(NUM_SPANS)).publishEvent(isA(SpanStoppedEvent.class)); + + ArgumentCaptor captor = ArgumentCaptor + .forClass(ApplicationEvent.class); + verify(publisher, atLeast(NUM_SPANS)).publishEvent(captor.capture()); + + List spans = new ArrayList<>(); + for (ApplicationEvent event : captor.getAllValues()) { + if (event instanceof SpanStoppedEvent) { + spans.add(((SpanStoppedEvent) event).getSpan()); + } + } + + 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); + Span grandChild = assertSpan(spans, child.getSpanId(), IMPORTANT_WORK_2); + + List gen4 = findSpans(spans, grandChild.getSpanId()); + assertThat("gen4 was non-empty", gen4.isEmpty(), is(true)); + } + + private Span assertSpan(List spans, String parentId, String name) { + List found = findSpans(spans, parentId); + assertThat("more than one span with parentId " + parentId, found.size(), is(1)); + Span span = found.get(0); + assertThat("name is wrong for span with parentId " + parentId, + span.getName(), is(name)); + return span; + } + + private List findSpans(List spans, String parentId) { + List found = new ArrayList<>(); + for (Span span : spans) { + if (parentId == null && span.getParents().isEmpty()) { + found.add(span); + } + else if (span.getParents().contains(parentId)) { + found.add(span); + } + } + return found; + } + + private void importantWork1(Trace trace) { + TraceScope cur = trace.startSpan(IMPORTANT_WORK_1); + try { + Thread.sleep((long) (50 * Math.random())); + importantWork2(trace); + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + finally { + cur.close(); + } + } + + private void importantWork2(Trace trace) { + TraceScope cur = trace.startSpan(IMPORTANT_WORK_2); + try { + Thread.sleep((long) (50 * Math.random())); + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + finally { + cur.close(); + } + } +} diff --git a/spring-cloud-sleuth-correlation/pom.xml b/spring-cloud-sleuth-correlation/pom.xml deleted file mode 100644 index 697ebf14a..000000000 --- a/spring-cloud-sleuth-correlation/pom.xml +++ /dev/null @@ -1,132 +0,0 @@ - - - 4.0.0 - - spring-cloud-sleuth-correlation - jar - Spring Cloud Sleuth Correlation - Spring Cloud Sleuth Correlation - - - org.springframework.cloud - spring-cloud-sleuth - 1.0.0.BUILD-SNAPSHOT - .. - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - - org.codehaus.gmavenplus - gmavenplus-plugin - - - maven-surefire-plugin - - - - - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.cloud - spring-cloud-sleuth-core - - - org.springframework.boot - spring-boot-starter-actuator - true - - - commons-lang - commons-lang - - - org.projectlombok - lombok - - provided - - - org.springframework.boot - spring-boot-starter-test - test - - - com.netflix.hystrix - hystrix-core - - - org.aspectj - aspectjrt - - - org.aspectj - aspectjweaver - runtime - - - io.reactivex - rxjava - - - - org.spockframework - spock-core - test - - - org.spockframework - spock-spring - test - - - cglib - cglib-nodep - test - - - org.objenesis - objenesis - test - - - org.hamcrest - hamcrest-core - test - - - org.codehaus.groovy - groovy-all - test - - - org.codehaus.gpars - gpars - 1.2.1 - test - - - com.github.tomakehurst - wiremock - 1.53 - test - - - com.github.stefanbirkner - system-rules - 1.9.0 - test - - - - diff --git a/spring-cloud-sleuth-correlation/src/main/java/org/springframework/cloud/sleuth/correlation/CorrelationIdAspect.java b/spring-cloud-sleuth-correlation/src/main/java/org/springframework/cloud/sleuth/correlation/CorrelationIdAspect.java deleted file mode 100644 index b73665aba..000000000 --- a/spring-cloud-sleuth-correlation/src/main/java/org/springframework/cloud/sleuth/correlation/CorrelationIdAspect.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright 2012-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.correlation; - -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.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.client.RestOperations; - -import java.lang.invoke.MethodHandles; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.Callable; - -import static org.springframework.cloud.sleuth.correlation.CorrelationIdHolder.CORRELATION_ID_HEADER; - -/** - * Aspect that adds correlation id to - *

- *

    - *
  • {@link RestController} annotated classes - * with public {@link Callable} methods
  • - *
  • {@link Controller} annotated classes - * with public {@link Callable} methods
  • - *
  • explicit {@link RestOperations}.exchange(..) method calls
  • - *
- *

- * For controllers an around aspect is created that wraps the {@link Callable#call()} method execution - * in {@link CorrelationIdUpdater#wrapCallableWithId(Callable)} - *

- * For {@link RestOperations} we are wrapping all executions of the - * exchange methods and we are extracting {@link HttpHeaders} from the passed {@link HttpEntity}. - * Next we are adding correlation id header {@link CorrelationIdHolder#CORRELATION_ID_HEADER} with - * the value taken from {@link CorrelationIdHolder}. Finally the method execution proceeds. - * - * @see RestController - * @see Controller - * @see RestOperations - * @see CorrelationIdHolder - * @see CorrelationIdFilter - * - * @author Tomasz Nurkewicz, 4financeIT - * @author Marcin Grzejszczak, 4financeIT - * @author Michal Chmielarz, 4financeIT - */ -@Aspect -public class CorrelationIdAspect { - private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); - - private static final int HTTP_ENTITY_PARAM_INDEX = 2; - - @Pointcut("@target(org.springframework.web.bind.annotation.RestController)") - private void anyRestControllerAnnotated() { - } - - @Pointcut("@target(org.springframework.stereotype.Controller)") - private void anyControllerAnnotated() { - } - - @Pointcut("execution(public java.util.concurrent.Callable *(..))") - private void anyPublicMethodReturningCallable() { - } - - @Pointcut("(anyRestControllerAnnotated() || anyControllerAnnotated()) && anyPublicMethodReturningCallable()") - private void anyControllerOrRestControllerWithPublicAsyncMethod() { - } - - @Around("anyControllerOrRestControllerWithPublicAsyncMethod()") - public Object wrapWithCorrelationId(ProceedingJoinPoint pjp) throws Throwable { - final Callable callable = (Callable) pjp.proceed(); - log.debug("Wrapping callable with correlation id [" + CorrelationIdHolder.get() + "]"); - return CorrelationIdUpdater.wrapCallableWithId(new Callable() { - @Override - public Object call() throws Exception { - return callable.call(); - } - }); - } - - @Pointcut("execution(public * org.springframework.web.client.RestOperations.exchange(..))") - private void anyExchangeRestOperationsMethod() { - } - - @Around("anyExchangeRestOperationsMethod()") - public Object wrapWithCorrelationIdForRestOperations(ProceedingJoinPoint pjp) throws Throwable { - String correlationId = CorrelationIdHolder.get(); - log.debug("Wrapping RestTemplate call with correlation id [" + correlationId + "]"); - HttpEntity httpEntity = (HttpEntity) pjp.getArgs()[HTTP_ENTITY_PARAM_INDEX]; - HttpEntity newHttpEntity = createNewHttpEntity(httpEntity, correlationId); - List newArgs = modifyHttpEntityInMethodArguments(pjp, newHttpEntity); - return pjp.proceed(newArgs.toArray()); - } - - @SuppressWarnings("unchecked") - private HttpEntity createNewHttpEntity(HttpEntity httpEntity, String correlationId) { - HttpHeaders newHttpHeaders = new HttpHeaders(); - newHttpHeaders.putAll(httpEntity.getHeaders()); - newHttpHeaders.add(CORRELATION_ID_HEADER, correlationId); - return new HttpEntity(httpEntity.getBody(), newHttpHeaders); - } - - private List modifyHttpEntityInMethodArguments(ProceedingJoinPoint pjp, HttpEntity newHttpEntity) { - List newArgs = new ArrayList<>(); - for (int i = 0; i < pjp.getArgs().length; i++) { - Object arg = pjp.getArgs()[i]; - if (i != HTTP_ENTITY_PARAM_INDEX) { - newArgs.add(i, arg); - } else { - newArgs.add(i, newHttpEntity); - } - } - return newArgs; - } -} diff --git a/spring-cloud-sleuth-correlation/src/main/java/org/springframework/cloud/sleuth/correlation/CorrelationIdAutoConfiguration.java b/spring-cloud-sleuth-correlation/src/main/java/org/springframework/cloud/sleuth/correlation/CorrelationIdAutoConfiguration.java deleted file mode 100644 index 51e71dfac..000000000 --- a/spring-cloud-sleuth-correlation/src/main/java/org/springframework/cloud/sleuth/correlation/CorrelationIdAutoConfiguration.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2012-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.correlation; - -import org.apache.commons.lang.StringUtils; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.AutoConfigureAfter; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.context.embedded.FilterRegistrationBean; -import org.springframework.cloud.sleuth.resttemplate.SleuthRestTemplateAutoConfiguration; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import java.util.regex.Pattern; - -/** - * Registers beans that add correlation id to requests - * - * @see CorrelationIdAspect - * @see CorrelationIdFilter - * - * @author Tomasz Nurkewicz, 4financeIT - * @author Marcin Grzejszczak, 4financeIT - * @author Michal Chmielarz, 4financeIT - */ -@Configuration -@ConditionalOnProperty(value = "spring.cloud.sleuth.correlation.enabled", matchIfMissing = true) -@AutoConfigureAfter(SleuthRestTemplateAutoConfiguration.class) -public class CorrelationIdAutoConfiguration { - - /** - * Pattern for URLs that should be skipped in correlationID setting - */ - @Value("${spring.cloud.sleuth.correlation.skipPattern:}") - private String skipPattern; - - @Bean - @ConditionalOnMissingBean - public CorrelationIdAspect correlationIdAspect() { - return new CorrelationIdAspect(); - } - - @Bean - @ConditionalOnMissingBean - public FilterRegistrationBean correlationHeaderFilter(UuidGenerator uuidGenerator) { - Pattern pattern = StringUtils.isBlank(skipPattern) ? Pattern.compile(skipPattern) : CorrelationIdFilter.DEFAULT_SKIP_PATTERN; - return new FilterRegistrationBean(new CorrelationIdFilter(uuidGenerator, pattern)); - } - - @Bean - @ConditionalOnMissingBean - public UuidGenerator uuidGenerator() { - return new UuidGenerator(); - } - - @Bean - public CorrelationIdSettingRestTemplateInterceptor correlationIdSettingRestTemplateInterceptor() { - return new CorrelationIdSettingRestTemplateInterceptor(); - } -} diff --git a/spring-cloud-sleuth-correlation/src/main/java/org/springframework/cloud/sleuth/correlation/CorrelationIdFilter.java b/spring-cloud-sleuth-correlation/src/main/java/org/springframework/cloud/sleuth/correlation/CorrelationIdFilter.java deleted file mode 100644 index 96a6aef07..000000000 --- a/spring-cloud-sleuth-correlation/src/main/java/org/springframework/cloud/sleuth/correlation/CorrelationIdFilter.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright 2012-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.correlation; - -import org.apache.commons.lang.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.slf4j.MDC; -import org.springframework.web.filter.OncePerRequestFilter; - -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.lang.invoke.MethodHandles; -import java.util.concurrent.Callable; -import java.util.regex.Pattern; - -import static org.springframework.cloud.sleuth.correlation.CorrelationIdHolder.CORRELATION_ID_HEADER; -import static org.springframework.util.StringUtils.hasText; - -/** - * Filter that takes the value of the {@link CorrelationIdHolder#CORRELATION_ID_HEADER} header - * from either request or response and sets it in the {@link CorrelationIdHolder}. It also provides - * that value in {@link MDC} logging related class so that logger prints the value of - * correlation id at each log. - * - * @see CorrelationIdHolder - * @see MDC - * - * @author Jakub Nabrdalik, 4financeIT - * @author Tomasz Nurkiewicz, 4financeIT - * @author Marcin Grzejszczak, 4financeIT - */ -public class CorrelationIdFilter extends OncePerRequestFilter { - private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); - public static final Pattern DEFAULT_SKIP_PATTERN = Pattern.compile("/api-docs.*|/autoconfig|/configprops|/dump|/info|/metrics.*|/mappings|/trace|/swagger.*|.*\\.png|.*\\.css|.*\\.js|.*\\.html"); - - private final Pattern skipCorrId; - private final UuidGenerator uuidGenerator; - - public CorrelationIdFilter() { - this.uuidGenerator = new UuidGenerator(); - this.skipCorrId = null; - } - - public CorrelationIdFilter(UuidGenerator uuidGenerator, Pattern skipCorrId) { - this.uuidGenerator = uuidGenerator; - this.skipCorrId = skipCorrId; - } - - @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { - setupCorrelationId(request, response); - try { - filterChain.doFilter(request, response); - } finally { - cleanupCorrelationId(); - } - } - - private void setupCorrelationId(HttpServletRequest request, HttpServletResponse response) { - String correlationIdFromRequest = getCorrelationIdFrom(request); - String correlationId = (hasText(correlationIdFromRequest)) ? correlationIdFromRequest : getCorrelationIdFrom(response); - if (!hasText(correlationId) && shouldGenerateCorrId(request)) { - correlationId = createNewCorrIdIfEmpty(); - } - CorrelationIdHolder.set(correlationId); - addCorrelationIdToResponseIfNotPresent(response, correlationId); - } - - private String getCorrelationIdFrom(final HttpServletResponse response) { - return withLoggingAs("response", new Callable() { - @Override - public String call() throws Exception { - return response.getHeader(CORRELATION_ID_HEADER); - } - }); - } - - private String getCorrelationIdFrom(final HttpServletRequest request) { - return withLoggingAs("request", new Callable() { - @Override - public String call() throws Exception { - return request.getHeader(CORRELATION_ID_HEADER); - } - }); - } - - private String withLoggingAs(String whereWasFound, Callable correlationIdGetter) { - String correlationId = tryToGetCorrelationId(correlationIdGetter); - if (hasText(correlationId)) { - MDC.put(CORRELATION_ID_HEADER, correlationId); - log.debug("Found correlationId in " + whereWasFound + ": " + correlationId); - } - return correlationId; - } - - private String tryToGetCorrelationId(Callable correlationIdGetter) { - try { - return correlationIdGetter.call(); - } catch (Exception e) { - log.error("Exception occurred while trying to retrieve request header", e); - return StringUtils.EMPTY; - } - } - - private String createNewCorrIdIfEmpty() { - String currentCorrId = uuidGenerator.create(); - MDC.put(CORRELATION_ID_HEADER, currentCorrId); - log.debug("Generating new correlationId: " + currentCorrId); - return currentCorrId; - } - - protected boolean shouldGenerateCorrId(HttpServletRequest request) { - final String i = request.getRequestURI(); - final String uri = StringUtils.defaultIfEmpty(i, StringUtils.EMPTY); - boolean skip = skipCorrId != null && skipCorrId.matcher(uri).matches(); - return !skip; - } - - private void addCorrelationIdToResponseIfNotPresent(HttpServletResponse response, String correlationId) { - if (!hasText(response.getHeader(CORRELATION_ID_HEADER))) { - response.addHeader(CORRELATION_ID_HEADER, correlationId); - } - } - - private void cleanupCorrelationId() { - MDC.remove(CORRELATION_ID_HEADER); - CorrelationIdHolder.remove(); - } - - @Override - protected boolean shouldNotFilterAsyncDispatch() { - return false; - } -} diff --git a/spring-cloud-sleuth-correlation/src/main/java/org/springframework/cloud/sleuth/correlation/CorrelationIdHolder.java b/spring-cloud-sleuth-correlation/src/main/java/org/springframework/cloud/sleuth/correlation/CorrelationIdHolder.java deleted file mode 100644 index 6474887a5..000000000 --- a/spring-cloud-sleuth-correlation/src/main/java/org/springframework/cloud/sleuth/correlation/CorrelationIdHolder.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2012-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.correlation; - -/** - * Component that stores correlation id using {@link ThreadLocal} - * - * @author Jakub Nabrdalik, 4financeIT - * @author Marcin Zajaczkowski, 4financeIT - */ -public class CorrelationIdHolder { - public static final String CORRELATION_ID_HEADER = "Correlation-Id"; - private static final ThreadLocal id = new ThreadLocal(); - - public static void set(String correlationId) { - id.set(correlationId); - } - - public static String get() { - return id.get(); - } - - public static void remove() { - id.remove(); - } -} diff --git a/spring-cloud-sleuth-correlation/src/main/java/org/springframework/cloud/sleuth/correlation/CorrelationIdSettingRestTemplateInterceptor.java b/spring-cloud-sleuth-correlation/src/main/java/org/springframework/cloud/sleuth/correlation/CorrelationIdSettingRestTemplateInterceptor.java deleted file mode 100644 index 3f1b6fcb8..000000000 --- a/spring-cloud-sleuth-correlation/src/main/java/org/springframework/cloud/sleuth/correlation/CorrelationIdSettingRestTemplateInterceptor.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2012-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.correlation; - -import lombok.SneakyThrows; -import org.springframework.http.HttpRequest; -import org.springframework.http.client.ClientHttpRequestExecution; -import org.springframework.http.client.ClientHttpRequestInterceptor; -import org.springframework.http.client.ClientHttpResponse; - -import java.io.IOException; - -/** - * Interceptor that verifies whether the correlation id has been - * set on the request and sets it if it's missing. - * - * @see org.springframework.web.client.RestTemplate - * @see CorrelationIdHolder - * - * @author Marcin Grzejszczak, 4financeIT - */ -public class CorrelationIdSettingRestTemplateInterceptor implements ClientHttpRequestInterceptor { - - @SneakyThrows - @Override - public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { - appendCorrelationIdToRequestIfMissing(request); - ClientHttpResponse response = null; - try { - response = execution.execute(request, body); - } catch (final Exception e) { - throw new RuntimeException(e); - } - return response; - } - - private void appendCorrelationIdToRequestIfMissing(HttpRequest request) { - if (!request.getHeaders().containsKey(CorrelationIdHolder.CORRELATION_ID_HEADER)) { - request.getHeaders().add(CorrelationIdHolder.CORRELATION_ID_HEADER, CorrelationIdHolder.get()); - } - } - -} diff --git a/spring-cloud-sleuth-correlation/src/main/java/org/springframework/cloud/sleuth/correlation/CorrelationIdUpdater.java b/spring-cloud-sleuth-correlation/src/main/java/org/springframework/cloud/sleuth/correlation/CorrelationIdUpdater.java deleted file mode 100644 index 0becfca32..000000000 --- a/spring-cloud-sleuth-correlation/src/main/java/org/springframework/cloud/sleuth/correlation/CorrelationIdUpdater.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright 2012-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.correlation; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.slf4j.MDC; -import org.springframework.util.StringUtils; - -import java.lang.invoke.MethodHandles; -import java.util.concurrent.Callable; - -import static org.springframework.cloud.sleuth.correlation.CorrelationIdHolder.CORRELATION_ID_HEADER; - -/** - * Class that takes care of updating all necessary components with new value - * of correlation id. - * It sets correlationId on {@link ThreadLocal} in {@link CorrelationIdHolder} - * and in {@link MDC}. - * - * @see CorrelationIdHolder - * @see MDC - * - * @author Jakub Nabrdalik, 4financeIT - * @author Michal Chmielarz, 4financeIT - */ -public class CorrelationIdUpdater { - - private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); - - public static void updateCorrelationId(String correlationId) { - if (StringUtils.hasText(correlationId)) { - log.debug("Updating correlationId with value: [" + correlationId + "]"); - CorrelationIdHolder.set(correlationId); - MDC.put(CORRELATION_ID_HEADER, correlationId); - } - } - - /** - * Temporarily updates correlation ID inside block of code. - * Makes sure previous ID is restored after block's execution - * - * @param temporaryCorrelationId - correlationID to passed to the executed block of code - * @param block Callable to be executed with new ID - * @return the result of Callable block execution - */ - public static T withId(String temporaryCorrelationId, Callable block) { - final String oldCorrelationId = CorrelationIdHolder.get(); - try { - updateCorrelationId(temporaryCorrelationId); - return block.call(); - } catch (RuntimeException e) { - logException(e); - throw e; - } catch (Exception e) { - logException(e); - throw new RuntimeException(e); - } finally { - updateCorrelationId(oldCorrelationId); - } - - } - - private static void logException(Throwable e) { - log.error("Exception occurred while trying to execute the function", e); - } - - /** - * Wraps given {@link Callable} with another {@link Callable Callable} propagating correlation ID inside nested - * Callable/Closure. - *

- *

- * Useful in a situation when a Callable should be executed in a separate thread, for example in aspects. - *

- *


-	 * @Around('...')
-	 * Object wrapWithCorrelationId(ProceedingJoinPoint pjp) throws Throwable {
-	 *     Callable callable = pjp.proceed() as Callable
-	 *     return CorrelationIdUpdater.wrapCallableWithId {
-	 *         callable.call()
-	 *     }
-	 * }
-	 * 
- *

- * Note: Passing only one input parameter currently is supported. - * - * @param block code block to execute in a thread with a correlation ID taken from the original thread - * @return wrapping block as Callable - */ - @SuppressWarnings("unchecked") - public static Callable wrapCallableWithId(final Callable block) { - final String temporaryCorrelationId = CorrelationIdHolder.get(); - // unchecked assignment due to groovyc issues with - return new Callable() { - @Override - public Object call() throws Exception { - final String oldCorrelationId = CorrelationIdHolder.get(); - try { - updateCorrelationId(temporaryCorrelationId); - return block.call(); - } finally { - updateCorrelationId(oldCorrelationId); - } - } - }; - } -} diff --git a/spring-cloud-sleuth-correlation/src/main/java/org/springframework/cloud/sleuth/correlation/UuidGenerator.java b/spring-cloud-sleuth-correlation/src/main/java/org/springframework/cloud/sleuth/correlation/UuidGenerator.java deleted file mode 100644 index e130097a8..000000000 --- a/spring-cloud-sleuth-correlation/src/main/java/org/springframework/cloud/sleuth/correlation/UuidGenerator.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2012-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.correlation; - -import java.util.UUID; - -/** - * Default Uuid generator - * - * @author extends HystrixCommand { - private final String clientCorrelationId = CorrelationIdHolder.get(); - - protected CorrelatedCommand(HystrixCommandGroupKey group) { - super(group); - } - - protected CorrelatedCommand(Setter setter) { - super(setter); - } - - @Override - protected final R run() throws Exception { - return CorrelationIdUpdater.withId(clientCorrelationId, new Callable() { - @Override - public R call() throws Exception { - return doRun(); - } - - }); - } - - public abstract R doRun() throws Exception; -} diff --git a/spring-cloud-sleuth-correlation/src/main/java/org/springframework/cloud/sleuth/correlation/scheduling/ScheduledTaskWithCorrelationIdAspect.java b/spring-cloud-sleuth-correlation/src/main/java/org/springframework/cloud/sleuth/correlation/scheduling/ScheduledTaskWithCorrelationIdAspect.java deleted file mode 100644 index 6af07d5a7..000000000 --- a/spring-cloud-sleuth-correlation/src/main/java/org/springframework/cloud/sleuth/correlation/scheduling/ScheduledTaskWithCorrelationIdAspect.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2012-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.correlation.scheduling; - -import org.aspectj.lang.ProceedingJoinPoint; -import org.aspectj.lang.annotation.Around; -import org.aspectj.lang.annotation.Aspect; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.cloud.sleuth.correlation.CorrelationIdUpdater; -import org.springframework.cloud.sleuth.correlation.UuidGenerator; -import org.springframework.scheduling.annotation.Scheduled; - -import java.lang.invoke.MethodHandles; -import java.util.concurrent.Callable; - -/** - * Aspect that sets correlationId for running threads executing methods annotated with {@link Scheduled} annotation. - * For every execution of scheduled method a new, i.e. unique one, value of correlationId will be set. - * - * @author Tomasz Nurkewicz, 4financeIT - * @author Michal Chmielarz, 4financeIT - * @author Marcin Grzejszczak, 4financeIT - * - * @see UuidGenerator - * @see CorrelationIdUpdater - */ -@Aspect -public class ScheduledTaskWithCorrelationIdAspect { - - private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); - - private final UuidGenerator uuidGenerator; - - public ScheduledTaskWithCorrelationIdAspect(UuidGenerator uuidGenerator) { - this.uuidGenerator = uuidGenerator; - } - - @Around("execution (@org.springframework.scheduling.annotation.Scheduled * *.*(..))") - public Object setNewCorrelationIdOnThread(final ProceedingJoinPoint pjp) throws Throwable { - String correlationId = uuidGenerator.create(); - return CorrelationIdUpdater.withId(correlationId, new Callable() { - @Override - public Object call() throws Exception { - try { - return pjp.proceed(); - } catch (Throwable throwable) { - log.error("Didn't manage to proceed with the pointcut", throwable); - throw new RuntimeException(throwable); - } - } - }); - } -} diff --git a/spring-cloud-sleuth-correlation/src/main/java/org/springframework/cloud/sleuth/correlation/scheduling/TaskSchedulingConfiguration.java b/spring-cloud-sleuth-correlation/src/main/java/org/springframework/cloud/sleuth/correlation/scheduling/TaskSchedulingConfiguration.java deleted file mode 100644 index e0ace7fbe..000000000 --- a/spring-cloud-sleuth-correlation/src/main/java/org/springframework/cloud/sleuth/correlation/scheduling/TaskSchedulingConfiguration.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2012-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.correlation.scheduling; - -import org.springframework.cloud.sleuth.correlation.UuidGenerator; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.EnableAspectJAutoProxy; -import org.springframework.scheduling.annotation.EnableScheduling; - -/** - * Registers beans related to task scheduling. - * - * @see ScheduledTaskWithCorrelationIdAspect - * - * @author Michal Chmielarz, 4financeIT - */ -@Configuration -@EnableScheduling -@EnableAspectJAutoProxy -public class TaskSchedulingConfiguration { - @Bean - public ScheduledTaskWithCorrelationIdAspect scheduledTaskPointcut(UuidGenerator uuidGenerator) { - return new ScheduledTaskWithCorrelationIdAspect(uuidGenerator); - } - -} diff --git a/spring-cloud-sleuth-correlation/src/main/resources/META-INF/spring.factories b/spring-cloud-sleuth-correlation/src/main/resources/META-INF/spring.factories deleted file mode 100644 index 387f34393..000000000 --- a/spring-cloud-sleuth-correlation/src/main/resources/META-INF/spring.factories +++ /dev/null @@ -1,3 +0,0 @@ -# Auto Configuration -org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ -org.springframework.cloud.sleuth.correlation.CorrelationIdAutoConfiguration diff --git a/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/CorrelationIdAspectISpec.groovy b/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/CorrelationIdAspectISpec.groovy deleted file mode 100644 index a5a368a2f..000000000 --- a/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/CorrelationIdAspectISpec.groovy +++ /dev/null @@ -1,118 +0,0 @@ -package org.springframework.cloud.sleuth.correlation -import groovy.transform.CompileStatic -import groovy.transform.PackageScope -import groovy.transform.TypeChecked -import org.hamcrest.Description -import org.hamcrest.TypeSafeMatcher -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.autoconfigure.EnableAutoConfiguration -import org.springframework.boot.test.SpringApplicationContextLoader -import org.springframework.cloud.sleuth.correlation.base.HttpMockServer -import org.springframework.cloud.sleuth.correlation.base.MvcCorrelationIdSettingIntegrationSpec -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration -import org.springframework.context.annotation.EnableAspectJAutoProxy -import org.springframework.http.MediaType -import org.springframework.scheduling.annotation.EnableAsync -import org.springframework.test.context.ContextConfiguration -import org.springframework.test.web.servlet.MvcResult -import org.springframework.test.web.servlet.request.MockMvcRequestBuilders -import org.springframework.test.web.servlet.result.MockMvcResultHandlers -import org.springframework.test.web.servlet.result.MockMvcResultMatchers -import org.springframework.web.bind.annotation.RequestMapping -import org.springframework.web.bind.annotation.RequestMethod -import org.springframework.web.bind.annotation.RestController -import org.springframework.web.client.RestTemplate - -import java.util.concurrent.Callable -import java.util.concurrent.TimeUnit - -import static com.github.tomakehurst.wiremock.client.WireMock.* -import static org.springframework.cloud.sleuth.correlation.CorrelationIdHolder.CORRELATION_ID_HEADER - -@ContextConfiguration(classes = [CorrelationIdAspectSpecConfiguration], loader = SpringApplicationContextLoader) -class CorrelationIdAspectISpec extends MvcCorrelationIdSettingIntegrationSpec { - - public static final String CORRELATION_ID_PATTERN = /^(?!\s*$).+/ - public static final TypeSafeMatcher hasCorrelationIdSet = new TypeSafeMatcher() { - @Override - protected boolean matchesSafely(String item) { - return item.matches(CORRELATION_ID_PATTERN) - } - - @Override - void describeTo(Description description) { - - } - } - - def "should set correlationId on header via aspect in synchronous call"() { - given: - stubInteraction(get(urlMatching('.*')), aResponse().withStatus(200)) - when: - mockMvc.perform(MockMvcRequestBuilders.get('/syncPing').accept(MediaType.TEXT_PLAIN)) - .andExpect(MockMvcResultMatchers.header().string(CORRELATION_ID_HEADER, hasCorrelationIdSet)) - then: - wireMock.verifyThat(getRequestedFor(urlMatching('.*')).withHeader(CORRELATION_ID_HEADER, matching(CORRELATION_ID_PATTERN))) - - } - - def "should set correlationId on header via aspect in asynchronous call"() { - given: - stubInteraction(get(urlMatching('.*')), aResponse().withStatus(200)) - when: - MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.get('/asyncPing').accept(MediaType.TEXT_PLAIN)) - .andExpect(MockMvcResultMatchers.request().asyncStarted()) - .andReturn() - and: - mvcResult.getAsyncResult(TimeUnit.SECONDS.toMillis(2)) - then: - mockMvc.perform(MockMvcRequestBuilders.asyncDispatch(mvcResult)). - andDo(MockMvcResultHandlers.print()). - andExpect(MockMvcResultMatchers.status().isOk()). - andExpect(MockMvcResultMatchers.header().string(CORRELATION_ID_HEADER, hasCorrelationIdSet)) - and: - wireMock.verifyThat(getRequestedFor(urlMatching('.*')).withHeader(CORRELATION_ID_HEADER, matching(CORRELATION_ID_PATTERN))) - } - - @CompileStatic - @Configuration - @EnableAsync - @EnableAutoConfiguration - @EnableAspectJAutoProxy(proxyTargetClass = true) - static class CorrelationIdAspectSpecConfiguration { - @Bean - AspectTestingController aspectTestingController() { - return new AspectTestingController() - } - } - - @RestController - @TypeChecked - @PackageScope - static class AspectTestingController { - - @Autowired - private HttpMockServer httpMockServer - @Autowired - private RestTemplate restTemplate - - @RequestMapping(value = "/syncPing", method = RequestMethod.GET, produces = MediaType.TEXT_PLAIN_VALUE) - String syncPing() { - callWiremockAndReturnOk() - } - - @RequestMapping(value = "/asyncPing", method = RequestMethod.GET, produces = MediaType.TEXT_PLAIN_VALUE) - Callable asyncPing() { - return { - callWiremockAndReturnOk() - } - } - - private String callWiremockAndReturnOk() { - restTemplate.getForObject("http://localhost:${httpMockServer.port()}", String) - return "OK" - } - } - -} diff --git a/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/CorrelationIdFilterISpec.groovy b/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/CorrelationIdFilterISpec.groovy deleted file mode 100644 index f825632aa..000000000 --- a/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/CorrelationIdFilterISpec.groovy +++ /dev/null @@ -1,57 +0,0 @@ -package org.springframework.cloud.sleuth.correlation - -import org.slf4j.MDC -import org.springframework.boot.test.SpringApplicationContextLoader -import org.springframework.cloud.sleuth.correlation.base.BaseConfiguration -import org.springframework.cloud.sleuth.correlation.base.MvcCorrelationIdSettingIntegrationSpec -import org.springframework.http.MediaType -import org.springframework.test.context.ContextConfiguration -import org.springframework.test.web.servlet.MvcResult -import org.springframework.test.web.servlet.request.MockMvcRequestBuilders - -@ContextConfiguration(classes = [BaseConfiguration, CorrelationIdAutoConfiguration], loader = SpringApplicationContextLoader) -class CorrelationIdFilterISpec extends MvcCorrelationIdSettingIntegrationSpec { - - def "should create and return correlationId in HTTP header"() { - when: - MvcResult mvcResult = sendPingWithoutCorrelationId() - - then: - getCorrelationIdFromResponseHeader(mvcResult) != null - } - - def "when correlationId is sent, should not create a new one, but return the existing one instead"() { - given: - String passedCorrelationId = "passedCorId" - - when: - MvcResult mvcResult = sendPingWithCorrelationId(passedCorrelationId) - - then: - getCorrelationIdFromResponseHeader(mvcResult) == passedCorrelationId - } - - def "should clean up MDC after the call"() { - given: - String passedCorrelationId = "passedCorId" - - when: - sendPingWithCorrelationId(passedCorrelationId) - - then: - MDC.get(CorrelationIdHolder.CORRELATION_ID_HEADER) == null - } - - private MvcResult sendPingWithCorrelationId(String passedCorrelationId) { - mockMvc.perform(MockMvcRequestBuilders.get('/ping').accept(MediaType.TEXT_PLAIN) - .header(CorrelationIdHolder.CORRELATION_ID_HEADER, passedCorrelationId)).andReturn() - } - - private MvcResult sendPingWithoutCorrelationId() { - mockMvc.perform(MockMvcRequestBuilders.get('/ping').accept(MediaType.TEXT_PLAIN)).andReturn() - } - - private String getCorrelationIdFromResponseHeader(MvcResult mvcResult) { - mvcResult.response.getHeader(CorrelationIdHolder.CORRELATION_ID_HEADER) - } -} diff --git a/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/CorrelationIdFilterSkipPatternSpec.groovy b/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/CorrelationIdFilterSkipPatternSpec.groovy deleted file mode 100644 index b4841a435..000000000 --- a/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/CorrelationIdFilterSkipPatternSpec.groovy +++ /dev/null @@ -1,65 +0,0 @@ -package org.springframework.cloud.sleuth.correlation - -import spock.lang.Specification -import spock.lang.Unroll - -import javax.servlet.FilterChain -import javax.servlet.http.HttpServletRequest -import javax.servlet.http.HttpServletResponse - -@Unroll -class CorrelationIdFilterSkipPatternSpec extends Specification { - - CorrelationIdFilter filter = new CorrelationIdFilter(Stub(UuidGenerator), CorrelationIdFilter.DEFAULT_SKIP_PATTERN) - - def 'should skip meaningless URIs like #uri'() { - given: - HttpServletResponse responseMock = Mock(HttpServletResponse) - HttpServletRequest requestMock = Mock(HttpServletRequest) - and: - requestMock.getRequestURI() >> uri - when: - filter.doFilter(requestMock, responseMock, Stub(FilterChain)) - then: - 0 * responseMock.addHeader(CorrelationIdHolder.CORRELATION_ID_HEADER, _ as String) - where: - uri | _ - '/api-docs' | _ - '/api-docs/default' | _ - '/swagger' | _ - '/trace' | _ - '/metrics' | _ - '/metrics/foo' | _ - '/mappings' | _ - '/autoconfig' | _ - '/configprops' | _ - '/info' | _ - '/dump' | _ - '/swagger/foo' | _ - '/foo.js' | _ - '/foo/bar.png' | _ - '/foo/bar.html' | _ - '/foo/bar.css' | _ - '/foo/bar.js' | _ - '/foo/bar.js' | _ - } - - def 'should not skip #uri'() { - given: - HttpServletResponse responseMock = Mock(HttpServletResponse) - HttpServletRequest requestMock = Mock(HttpServletRequest) - and: - requestMock.getRequestURI() >> uri - when: - filter.doFilter(requestMock, responseMock, Stub(FilterChain)) - then: - 1 * responseMock.addHeader(CorrelationIdHolder.CORRELATION_ID_HEADER, _ as String) - where: - uri | _ - '/business/api-docs' | _ - '/business/swagger/foo' | _ - '/foo.js/service' | _ - } - - -} diff --git a/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/CorrelationIdUpdaterSpec.groovy b/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/CorrelationIdUpdaterSpec.groovy deleted file mode 100644 index b6cd61c7f..000000000 --- a/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/CorrelationIdUpdaterSpec.groovy +++ /dev/null @@ -1,66 +0,0 @@ -package org.springframework.cloud.sleuth.correlation - -import groovyx.gpars.GParsPool -import spock.lang.Specification - -import java.util.concurrent.Callable -import java.util.concurrent.ExecutorService -import java.util.concurrent.Executors -import java.util.concurrent.TimeUnit - -class CorrelationIdUpdaterSpec extends Specification { - - def cleanup() { - CorrelationIdHolder.remove() - } - - def "correlation ID should not be propagated to other thread by default"() { - given: - CorrelationIdUpdater.updateCorrelationId('A') - expect: - GParsPool.withPool(1) { - ["1"].eachParallel { - assert CorrelationIdHolder.get() == null - } - } - } - - def "should propagate correlation ID into nested Callable"() { - given: - ExecutorService threadPool = Executors.newFixedThreadPool(1) - CorrelationIdUpdater.updateCorrelationId('A') - Callable callable = new CorrelationIdTestCallable() - when: - Callable wrappedCallable = CorrelationIdUpdater.wrapCallableWithId(callable) - String nestedCorrelationId = threadPool.submit(wrappedCallable).get(1, TimeUnit.SECONDS) - then: - nestedCorrelationId == 'A' - cleanup: - threadPool.shutdown() - } - - def "should restore previous correlation ID after Callable execution in other thread"() { - given: - ExecutorService threadPool = Executors.newFixedThreadPool(1) - CorrelationIdUpdater.updateCorrelationId('A') - Callable callable = new CorrelationIdTestCallable() - and: - threadPool.submit({ CorrelationIdHolder.set('B') }).get(1, TimeUnit.SECONDS) - when: - threadPool.submit(CorrelationIdUpdater.wrapCallableWithId(callable)).get(1, TimeUnit.SECONDS) - then: - def restoredCorrelationId = threadPool.submit({ - CorrelationIdHolder.get() - } as Callable).get(1, TimeUnit.SECONDS) - restoredCorrelationId == 'B' - cleanup: - threadPool.shutdown() - } - - private static class CorrelationIdTestCallable implements Callable { - @Override - String call() throws Exception { - CorrelationIdHolder.get() - } - } -} diff --git a/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/base/BaseConfiguration.groovy b/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/base/BaseConfiguration.groovy deleted file mode 100644 index d3fc8744f..000000000 --- a/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/base/BaseConfiguration.groovy +++ /dev/null @@ -1,17 +0,0 @@ -package org.springframework.cloud.sleuth.correlation.base - -import groovy.transform.CompileStatic -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration -import org.springframework.context.support.PropertySourcesPlaceholderConfigurer - -@CompileStatic -@Configuration -class BaseConfiguration { - - @Bean - static PropertySourcesPlaceholderConfigurer placeholderConfigurer() { - return new PropertySourcesPlaceholderConfigurer() - } - -} diff --git a/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/base/HttpMockServer.groovy b/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/base/HttpMockServer.groovy deleted file mode 100755 index 629190b34..000000000 --- a/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/base/HttpMockServer.groovy +++ /dev/null @@ -1,31 +0,0 @@ -package org.springframework.cloud.sleuth.correlation.base - -import com.github.tomakehurst.wiremock.WireMockServer -import groovy.transform.CompileStatic - -/** - * Custom implementation of {@link WireMockServer} that by default registers itself at port - * {@link HttpMockServer#DEFAULT_PORT}. - * - * @see WireMockServer - */ -@CompileStatic -class HttpMockServer extends WireMockServer { - - public static final int DEFAULT_PORT = 8030 - - HttpMockServer(int port) { - super(port) - } - - HttpMockServer() { - super(DEFAULT_PORT) - } - - void shutdownServer() { - if (isRunning()) { - stop() - } - shutdown() - } -} diff --git a/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/base/MockServerConfiguration.groovy b/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/base/MockServerConfiguration.groovy deleted file mode 100755 index 7e9a97c61..000000000 --- a/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/base/MockServerConfiguration.groovy +++ /dev/null @@ -1,25 +0,0 @@ -package org.springframework.cloud.sleuth.correlation.base - -import groovy.transform.CompileStatic -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration -import org.springframework.util.SocketUtils - -/** - * Configuration that registers {@link HttpMockServer} as a Spring bean. Takes care - * of graceful shutdown process. - * - * @see HttpMockServer - */ -@CompileStatic -@Configuration -class MockServerConfiguration { - - @Bean(destroyMethod = 'shutdownServer') - HttpMockServer httpMockServer() { - HttpMockServer httpMockServer = new HttpMockServer(SocketUtils.findAvailableTcpPort()) - httpMockServer.start() - return httpMockServer - } - -} diff --git a/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/base/MvcCorrelationIdSettingIntegrationSpec.groovy b/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/base/MvcCorrelationIdSettingIntegrationSpec.groovy deleted file mode 100644 index 6c55d7e19..000000000 --- a/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/base/MvcCorrelationIdSettingIntegrationSpec.groovy +++ /dev/null @@ -1,13 +0,0 @@ -package org.springframework.cloud.sleuth.correlation.base - -import org.springframework.cloud.sleuth.correlation.CorrelationIdFilter -import org.springframework.test.web.servlet.setup.ConfigurableMockMvcBuilder - -class MvcCorrelationIdSettingIntegrationSpec extends org.springframework.cloud.sleuth.correlation.base.MvcWiremockIntegrationSpec { - - @Override - protected void configureMockMvcBuilder(ConfigurableMockMvcBuilder mockMvcBuilder) { - super.configureMockMvcBuilder(mockMvcBuilder) - mockMvcBuilder.addFilter(new CorrelationIdFilter()) - } -} diff --git a/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/base/MvcIntegrationSpec.groovy b/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/base/MvcIntegrationSpec.groovy deleted file mode 100755 index 4cd63fd9e..000000000 --- a/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/base/MvcIntegrationSpec.groovy +++ /dev/null @@ -1,45 +0,0 @@ -package org.springframework.cloud.sleuth.correlation.base - -import groovy.transform.CompileStatic -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.context.ApplicationContext -import org.springframework.test.context.web.WebAppConfiguration -import org.springframework.test.web.servlet.MockMvc -import org.springframework.test.web.servlet.setup.ConfigurableMockMvcBuilder -import org.springframework.test.web.servlet.setup.MockMvcBuilders -import org.springframework.web.context.WebApplicationContext -import spock.lang.Specification - -/** - * Base for specifications that use Spring's {@link MockMvc}. Provides also {@link WebApplicationContext}, - * {@link ApplicationContext}. The latter you can use to specify what - * kind of address should be returned for a given dependency name. - * - * @see WebApplicationContext - * @see ApplicationContext - */ -@CompileStatic -@WebAppConfiguration -abstract class MvcIntegrationSpec extends Specification { - - @Autowired - protected WebApplicationContext webApplicationContext - @Autowired - protected ApplicationContext applicationContext - - protected MockMvc mockMvc - - void setup() { - ConfigurableMockMvcBuilder mockMvcBuilder = MockMvcBuilders.webAppContextSetup(webApplicationContext) - configureMockMvcBuilder(mockMvcBuilder) - mockMvc = mockMvcBuilder.build() - } - - /** - * Override in a subclass to modify mockMvcBuilder configuration (e.g. add filter). - * - * The method from super class should be called. - */ - protected void configureMockMvcBuilder(ConfigurableMockMvcBuilder mockMvcBuilder) { - } -} diff --git a/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/base/MvcWiremockIntegrationSpec.groovy b/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/base/MvcWiremockIntegrationSpec.groovy deleted file mode 100755 index 5e84ff830..000000000 --- a/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/base/MvcWiremockIntegrationSpec.groovy +++ /dev/null @@ -1,37 +0,0 @@ -package org.springframework.cloud.sleuth.correlation.base - -import com.github.tomakehurst.wiremock.client.MappingBuilder -import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder -import com.github.tomakehurst.wiremock.client.WireMock -import groovy.transform.CompileStatic -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.test.context.ContextConfiguration - -/** - * Base specification for tests that use Wiremock as HTTP server stub. - * By extending this specification you gain a bean with {@link HttpMockServer} and a {@link WireMock} - * instance that you can stub by using {@link MvcWiremockIntegrationSpec#stubInteraction(com.github.tomakehurst.wiremock.client.MappingBuilder, com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder)} - * - * @see MockServerConfiguration - * @see WireMock - * @see HttpMockServer - * @see MvcIntegrationSpec - */ -@CompileStatic -@ContextConfiguration(classes = [MockServerConfiguration]) -abstract class MvcWiremockIntegrationSpec extends MvcIntegrationSpec { - - @Autowired - protected HttpMockServer httpMockServer - protected WireMock wireMock - - void setup() { - wireMock = new WireMock('localhost', httpMockServer.port()) - wireMock.resetToDefaultMappings() - } - - protected void stubInteraction(MappingBuilder mapping, ResponseDefinitionBuilder response) { - wireMock.register(mapping.willReturn(response)) - } - -} diff --git a/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/hystrix/CorrelatedCommandSpec.groovy b/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/hystrix/CorrelatedCommandSpec.groovy deleted file mode 100644 index e97c65761..000000000 --- a/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/hystrix/CorrelatedCommandSpec.groovy +++ /dev/null @@ -1,40 +0,0 @@ -package org.springframework.cloud.sleuth.correlation.hystrix - -import com.netflix.hystrix.HystrixCommand -import com.netflix.hystrix.HystrixCommandGroupKey -import org.springframework.cloud.sleuth.correlation.CorrelationIdHolder -import org.springframework.cloud.sleuth.correlation.CorrelationIdUpdater -import spock.lang.Specification - -class CorrelatedCommandSpec extends Specification { - - public static final String CORRELATION_ID = 'A' - - def 'should run Hystrix command with client correlation ID'() { - given: - CorrelationIdUpdater.updateCorrelationId(CORRELATION_ID) - def command = new CorrelatedCommand(HystrixCommand.Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey(""))) { - String doRun() { - return CorrelationIdHolder.get() - } - } - when: - def result = command.execute() - then: - result == CORRELATION_ID - } - - def 'should run Hystrix command in different thread'() { - given: - def command = new CorrelatedCommand(HystrixCommand.Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey(""))) { - String doRun() { - return Thread.currentThread().name - } - } - when: - def threadName = command.execute() - then: - Thread.currentThread().name != threadName - } - -} diff --git a/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/scheduling/CorrelationIdOnScheduledMethodISpec.groovy b/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/scheduling/CorrelationIdOnScheduledMethodISpec.groovy deleted file mode 100644 index 1d58c1c50..000000000 --- a/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/scheduling/CorrelationIdOnScheduledMethodISpec.groovy +++ /dev/null @@ -1,24 +0,0 @@ -package org.springframework.cloud.sleuth.correlation.scheduling - -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.cloud.sleuth.correlation.base.BaseConfiguration -import org.springframework.cloud.sleuth.correlation.CorrelationIdAutoConfiguration -import org.springframework.test.context.ContextConfiguration -import spock.lang.Specification -import spock.util.concurrent.PollingConditions - -@ContextConfiguration(classes = [TaskSchedulingConfiguration, ScheduledBeanConfiguration, CorrelationIdAutoConfiguration, BaseConfiguration]) -class CorrelationIdOnScheduledMethodISpec extends Specification { - - @Autowired - TestBeanWithScheduledMethod beanWithScheduledMethod - - def "should have correlationId set after scheduled method has been called"() { - PollingConditions conditions = new PollingConditions(timeout: 1.5, initialDelay: 0.1, factor: 1.05) - expect: - conditions.eventually { - beanWithScheduledMethod.correlationId != null - } - } - -} diff --git a/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/scheduling/ScheduledBeanConfiguration.groovy b/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/scheduling/ScheduledBeanConfiguration.groovy deleted file mode 100644 index 7bddf1cdb..000000000 --- a/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/scheduling/ScheduledBeanConfiguration.groovy +++ /dev/null @@ -1,14 +0,0 @@ -package org.springframework.cloud.sleuth.correlation.scheduling - -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration - -@Configuration -class ScheduledBeanConfiguration { - - @Bean - TestBeanWithScheduledMethod testBeanWithScheduledMethod() { - return new TestBeanWithScheduledMethod() - } - -} diff --git a/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/scheduling/TestBeanWithScheduledMethod.groovy b/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/scheduling/TestBeanWithScheduledMethod.groovy deleted file mode 100644 index 66eb128ed..000000000 --- a/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/scheduling/TestBeanWithScheduledMethod.groovy +++ /dev/null @@ -1,15 +0,0 @@ -package org.springframework.cloud.sleuth.correlation.scheduling - -import org.springframework.cloud.sleuth.correlation.CorrelationIdHolder -import org.springframework.scheduling.annotation.Scheduled - -class TestBeanWithScheduledMethod { - - String correlationId - - @Scheduled(fixedDelay = 50L) - void scheduledMethod() { - correlationId = CorrelationIdHolder.get() - } - -} diff --git a/spring-cloud-sleuth-sample/pom.xml b/spring-cloud-sleuth-sample/pom.xml index 766232a85..4b6422b47 100644 --- a/spring-cloud-sleuth-sample/pom.xml +++ b/spring-cloud-sleuth-sample/pom.xml @@ -47,7 +47,11 @@ org.springframework.cloud - spring-cloud-sleuth-zipkin + spring-cloud-sleuth-core + + + org.springframework.boot + spring-boot-starter-aop 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..8897123cf 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 @@ -1,64 +1,45 @@ package org.springframework.cloud.sleuth.sample; -import java.util.Random; - -import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; -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.context.ApplicationListener; +import org.springframework.cloud.sleuth.Sampler; +import org.springframework.cloud.sleuth.sampler.AlwaysSampler; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.client.RestTemplate; +import org.springframework.context.annotation.EnableAspectJAutoProxy; +import org.springframework.scheduling.annotation.EnableAsync; /** * @author Spencer Gibb */ @Configuration @EnableAutoConfiguration -@RestController +@EnableAspectJAutoProxy(proxyTargetClass = true) +@EnableAsync @Slf4j -public class SampleApplication implements ApplicationListener { +public class SampleApplication { public static final String CLIENT_NAME = "testApp"; - @Autowired - private RestTemplate restTemplate; - private int port; - - @SneakyThrows - @RequestMapping("/") - public String hi() { - final Random random = new Random(); - Thread.sleep(random.nextInt(1000)); - - String s = restTemplate.getForObject("http://localhost:" + port + "/hi2", String.class); - return "hi/"+s; + @Bean + public Sampler defaultSampler() { + return new AlwaysSampler(); } - @SneakyThrows - @RequestMapping("/hi2") - public String hi2() { - final Random random = new Random(); - Thread.sleep(random.nextInt(1000)); - return "hi2"; - } + @Bean + public SampleController sampleController() { + return new SampleController(); + } 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(); - } } diff --git a/spring-cloud-sleuth-sample/src/main/java/org/springframework/cloud/sleuth/sample/SampleController.java b/spring-cloud-sleuth-sample/src/main/java/org/springframework/cloud/sleuth/sample/SampleController.java new file mode 100644 index 000000000..242ce39e0 --- /dev/null +++ b/spring-cloud-sleuth-sample/src/main/java/org/springframework/cloud/sleuth/sample/SampleController.java @@ -0,0 +1,82 @@ +package org.springframework.cloud.sleuth.sample; + +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.embedded.EmbeddedServletContainerInitializedEvent; +import org.springframework.cloud.sleuth.Span; +import org.springframework.cloud.sleuth.Trace; +import org.springframework.cloud.sleuth.TraceContextHolder; +import org.springframework.cloud.sleuth.TraceScope; +import org.springframework.cloud.sleuth.sampler.AlwaysSampler; +import org.springframework.context.ApplicationListener; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.client.RestTemplate; + +import java.util.Random; +import java.util.concurrent.Callable; + +/** + * @author Spencer Gibb + */ +@Slf4j +@RestController +class SampleController implements + ApplicationListener { + @Autowired + private RestTemplate restTemplate; + @Autowired + private Trace trace; + private int port; + + @SneakyThrows + @RequestMapping("/") + public String hi() { + final Random random = new Random(); + Thread.sleep(random.nextInt(1000)); + + String s = restTemplate.getForObject("http://localhost:" + port + "/hi2", + String.class); + return "hi/" + s; + } + + @RequestMapping("/call") + public Callable call() { + return new Callable() { + @Override + public String call() throws Exception { + Span currentSpan = TraceContextHolder.getCurrentSpan(); + return "async hi: "+currentSpan; + } + }; + } + + + @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; + } + + @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..1698a3b29 --- /dev/null +++ b/spring-cloud-sleuth-sample/src/main/resources/logback.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file