Merge pull request #4 from spring-cloud-incubator/correlation-abstraction2
Tracing abstraction
This commit is contained in:
@@ -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.
|
||||
|
||||
|
||||
|
||||
9
pom.xml
9
pom.xml
@@ -27,8 +27,6 @@
|
||||
|
||||
<modules>
|
||||
<module>spring-cloud-sleuth-core</module>
|
||||
<module>spring-cloud-sleuth-correlation</module>
|
||||
<module>spring-cloud-sleuth-zipkin</module>
|
||||
<module>spring-cloud-sleuth-sample</module>
|
||||
<module>docs</module>
|
||||
</modules>
|
||||
@@ -148,7 +146,7 @@
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.12.6</version>
|
||||
<version>1.16.4</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -156,6 +154,11 @@
|
||||
<artifactId>hystrix-core</artifactId>
|
||||
<version>1.4.5</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.netflix.zuul</groupId>
|
||||
<artifactId>zuul-core</artifactId>
|
||||
<version>1.0.28</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.aspectj</groupId>
|
||||
<artifactId>aspectjrt</artifactId>
|
||||
|
||||
@@ -42,6 +42,26 @@
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.netflix.hystrix</groupId>
|
||||
<artifactId>hystrix-core</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.netflix.zuul</groupId>
|
||||
<artifactId>zuul-core</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.reactivex</groupId>
|
||||
<artifactId>rxjava</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.aspectj</groupId>
|
||||
<artifactId>aspectjrt</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
|
||||
@@ -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<SpanStoppedEvent> {
|
||||
private final ArrayList<Span> spans = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(SpanStoppedEvent event) {
|
||||
spans.add(event.getSpan());
|
||||
}
|
||||
}
|
||||
@@ -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 <T> TraceScope startSpan(String name, Sampler<T> s) {
|
||||
return startSpan(name, s, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> TraceScope startSpan(String name, Sampler<T> 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 <V> Callable<V> wrap(Callable<V> 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package org.springframework.cloud.sleuth;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
public interface IdGenerator {
|
||||
String create();
|
||||
}
|
||||
@@ -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<String> parents;
|
||||
private String spanId;
|
||||
private Map<String, String> kVAnnotations;
|
||||
private String processId;
|
||||
@Singular
|
||||
private List<TimelineAnnotation> 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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package org.springframework.cloud.sleuth;
|
||||
|
||||
/**
|
||||
* Extremely simple callback to determine the frequency that an action should be
|
||||
* performed.
|
||||
* <p/>
|
||||
* '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<Call>. 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.
|
||||
* <p/>
|
||||
* For the example above, the next(T info) function may look like this
|
||||
* <p/>
|
||||
* <pre>
|
||||
* <code>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;
|
||||
* }
|
||||
* }
|
||||
* </code>
|
||||
* </pre>
|
||||
* This would trace 50% of all gets, 75% of all puts and would not trace any other requests.
|
||||
*/
|
||||
public interface Sampler<T> {
|
||||
boolean next(T info);
|
||||
}
|
||||
@@ -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.
|
||||
* <p/>
|
||||
* 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.<p/>
|
||||
*/
|
||||
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.<p/>
|
||||
* <p/>
|
||||
* Will never be null.
|
||||
*/
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* A pseudo-unique (random) number assigned to this span instance.<p/>
|
||||
* <p/>
|
||||
* 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.<p/>
|
||||
* <p/>
|
||||
* The collection will be empty if there are no parents.
|
||||
*/
|
||||
List<String> 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)<p/>
|
||||
* <p/>
|
||||
* Will never be null.
|
||||
*/
|
||||
Map<String, String> getKVAnnotations();
|
||||
|
||||
/**
|
||||
* Get any timeline annotations (read only)<p/>
|
||||
* <p/>
|
||||
* Will never be null.
|
||||
*/
|
||||
List<TimelineAnnotation> getTimelineAnnotations();
|
||||
|
||||
/**
|
||||
* Return a unique id for the process from which this Span originated.<p/>
|
||||
* <p/>
|
||||
* Will never be null.
|
||||
*/
|
||||
String getProcessId();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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:
|
||||
* <ul>
|
||||
* <li>Create a new Span which has this thread's currentSpan as one of its parents.</li>
|
||||
* <li>Set currentSpan to the new Span.</li>
|
||||
* <li>Create a TraceSpan object to manage the new Span.</li>
|
||||
* </ul>
|
||||
*
|
||||
* Closing a TraceScope does a few things:
|
||||
* <ul>
|
||||
* <li>It closes the span which the scope was managing.</li>
|
||||
* <li>Set currentSpan to the previous currentSpan (which may be null).</li>
|
||||
* </ul>
|
||||
*/
|
||||
public interface Trace {
|
||||
|
||||
String SPAN_ID_NAME = "Span-Id";
|
||||
String TRACE_ID_NAME = "Trace-Id";
|
||||
|
||||
/**
|
||||
* Creates a new trace scope.
|
||||
* <p/>
|
||||
* 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.
|
||||
* <p/>
|
||||
* 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);
|
||||
|
||||
<T> TraceScope startSpan(String name, Sampler<T> s);
|
||||
|
||||
<T> TraceScope startSpan(String name, Sampler<T> 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);
|
||||
|
||||
<V> Callable<V> wrap(Callable<V> callable);
|
||||
|
||||
Runnable wrap(Runnable runnable);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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<Span> 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<V> extends TraceDelegate<Callable<V>> implements Callable<V> {
|
||||
|
||||
public TraceCallable(Trace trace, Callable<V> delagate) {
|
||||
super(trace, delagate);
|
||||
}
|
||||
|
||||
public TraceCallable(Trace trace, Callable<V> delagate, Span parent) {
|
||||
super(trace, delagate, parent);
|
||||
}
|
||||
|
||||
public TraceCallable(Trace trace, Callable<V> 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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<T> {
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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<Runnable> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<R> extends HystrixCommand<R> {
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
* <p/>
|
||||
* <ul>
|
||||
* <li>{@link RestController} annotated classes with public {@link Callable} methods</li>
|
||||
* <li>{@link Controller} annotated classes with public {@link Callable} methods</li>
|
||||
* </ul>
|
||||
* <p/>
|
||||
* For controllers an around aspect is created that wraps the {@link Callable#call()}
|
||||
* method execution in {@link TraceCallable}
|
||||
* <p/>
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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<ClientHttpRequestInterceptor> clientHttpRequestInterceptors;
|
||||
|
||||
public DefaultRestTemplateConfigurer(List<ClientHttpRequestInterceptor> clientHttpRequestInterceptors) {
|
||||
this.clientHttpRequestInterceptors = clientHttpRequestInterceptors;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void modifyRestTemplate(RestTemplate restTemplate) {
|
||||
for (ClientHttpRequestInterceptor clientHttpRequestInterceptor : clientHttpRequestInterceptors) {
|
||||
restTemplate.getInterceptors().add(clientHttpRequestInterceptor);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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<ClientHttpRequestInterceptor> 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.springframework.cloud.sleuth.sampler;
|
||||
|
||||
import org.springframework.cloud.sleuth.Sampler;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
public class AlwaysSampler implements Sampler<Object> {
|
||||
@Override
|
||||
public boolean next(Object info) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -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<Object> {
|
||||
|
||||
@Override
|
||||
public boolean next(Object info) {
|
||||
return TraceContextHolder.getCurrentSpan() != null;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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<SpanStartedEvent> {
|
||||
|
||||
@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());
|
||||
}
|
||||
}
|
||||
@@ -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<SpanStoppedEvent> {
|
||||
@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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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<ApplicationEvent> captor = ArgumentCaptor
|
||||
.forClass(ApplicationEvent.class);
|
||||
verify(publisher, atLeast(NUM_SPANS)).publishEvent(captor.capture());
|
||||
|
||||
List<Span> 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<Span> gen4 = findSpans(spans, grandChild.getSpanId());
|
||||
assertThat("gen4 was non-empty", gen4.isEmpty(), is(true));
|
||||
}
|
||||
|
||||
private Span assertSpan(List<Span> spans, String parentId, String name) {
|
||||
List<Span> 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<Span> findSpans(List<Span> spans, String parentId) {
|
||||
List<Span> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>spring-cloud-sleuth-correlation</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>Spring Cloud Sleuth Correlation</name>
|
||||
<description>Spring Cloud Sleuth Correlation</description>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-sleuth</artifactId>
|
||||
<version>1.0.0.BUILD-SNAPSHOT</version>
|
||||
<relativePath>..</relativePath>
|
||||
</parent>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.gmavenplus</groupId>
|
||||
<artifactId>gmavenplus-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-sleuth-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-lang</groupId>
|
||||
<artifactId>commons-lang</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<!-- Only needed at compile time -->
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.netflix.hystrix</groupId>
|
||||
<artifactId>hystrix-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.aspectj</groupId>
|
||||
<artifactId>aspectjrt</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.aspectj</groupId>
|
||||
<artifactId>aspectjweaver</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.reactivex</groupId>
|
||||
<artifactId>rxjava</artifactId>
|
||||
</dependency>
|
||||
<!-- Spock -->
|
||||
<dependency>
|
||||
<groupId>org.spockframework</groupId>
|
||||
<artifactId>spock-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.spockframework</groupId>
|
||||
<artifactId>spock-spring</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cglib</groupId>
|
||||
<artifactId>cglib-nodep</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.objenesis</groupId>
|
||||
<artifactId>objenesis</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hamcrest</groupId>
|
||||
<artifactId>hamcrest-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.groovy</groupId>
|
||||
<artifactId>groovy-all</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.gpars</groupId>
|
||||
<artifactId>gpars</artifactId>
|
||||
<version>1.2.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.tomakehurst</groupId>
|
||||
<artifactId>wiremock</artifactId>
|
||||
<version>1.53</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.stefanbirkner</groupId>
|
||||
<artifactId>system-rules</artifactId>
|
||||
<version>1.9.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -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
|
||||
* <p/>
|
||||
* <ul>
|
||||
* <li>{@link RestController} annotated classes
|
||||
* with public {@link Callable} methods</li>
|
||||
* <li>{@link Controller} annotated classes
|
||||
* with public {@link Callable} methods</li>
|
||||
* <li>explicit {@link RestOperations}.exchange(..) method calls</li>
|
||||
* </ul>
|
||||
* <p/>
|
||||
* For controllers an around aspect is created that wraps the {@link Callable#call()} method execution
|
||||
* in {@link CorrelationIdUpdater#wrapCallableWithId(Callable)}
|
||||
* <p/>
|
||||
* For {@link RestOperations} we are wrapping all executions of the
|
||||
* <b>exchange</b> 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<Object> 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<Object> modifyHttpEntityInMethodArguments(ProceedingJoinPoint pjp, HttpEntity newHttpEntity) {
|
||||
List<Object> 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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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<String>() {
|
||||
@Override
|
||||
public String call() throws Exception {
|
||||
return response.getHeader(CORRELATION_ID_HEADER);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private String getCorrelationIdFrom(final HttpServletRequest request) {
|
||||
return withLoggingAs("request", new Callable<String>() {
|
||||
@Override
|
||||
public String call() throws Exception {
|
||||
return request.getHeader(CORRELATION_ID_HEADER);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private String withLoggingAs(String whereWasFound, Callable<String> 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<String> 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;
|
||||
}
|
||||
}
|
||||
@@ -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<String> id = new ThreadLocal<String>();
|
||||
|
||||
public static void set(String correlationId) {
|
||||
id.set(correlationId);
|
||||
}
|
||||
|
||||
public static String get() {
|
||||
return id.get();
|
||||
}
|
||||
|
||||
public static void remove() {
|
||||
id.remove();
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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> T withId(String temporaryCorrelationId, Callable<T> 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.
|
||||
* <p/>
|
||||
* <p/>
|
||||
* Useful in a situation when a Callable should be executed in a separate thread, for example in aspects.
|
||||
* <p/>
|
||||
* <pre><code>
|
||||
* @Around('...')
|
||||
* Object wrapWithCorrelationId(ProceedingJoinPoint pjp) throws Throwable {
|
||||
* Callable callable = pjp.proceed() as Callable
|
||||
* return CorrelationIdUpdater.wrapCallableWithId {
|
||||
* callable.call()
|
||||
* }
|
||||
* }
|
||||
* </code></pre>
|
||||
* <p/>
|
||||
* <b>Note</b>: 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 <T> Callable<T> wrapCallableWithId(final Callable<T> block) {
|
||||
final String temporaryCorrelationId = CorrelationIdHolder.get();
|
||||
// unchecked assignment due to groovyc issues with <T>
|
||||
return new Callable() {
|
||||
@Override
|
||||
public Object call() throws Exception {
|
||||
final String oldCorrelationId = CorrelationIdHolder.get();
|
||||
try {
|
||||
updateCorrelationId(temporaryCorrelationId);
|
||||
return block.call();
|
||||
} finally {
|
||||
updateCorrelationId(oldCorrelationId);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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 <a href="http://www.4financeit.com>4financeIT</a>
|
||||
*/
|
||||
public class UuidGenerator {
|
||||
public String create() {
|
||||
return UUID.randomUUID().toString();
|
||||
}
|
||||
}
|
||||
@@ -1,57 +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.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 java.util.concurrent.Callable;
|
||||
|
||||
/**
|
||||
* Abstraction over {@code HystrixCommand} that wraps command execution with CorrelationID setting
|
||||
*
|
||||
* @see HystrixCommand
|
||||
* @see CorrelationIdUpdater
|
||||
*
|
||||
* @author Tomasz Nurkiewicz, 4financeIT
|
||||
* @author Marcin Grzejszczak, 4financeIT
|
||||
*/
|
||||
public abstract class CorrelatedCommand<R> extends HystrixCommand<R> {
|
||||
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<R>() {
|
||||
@Override
|
||||
public R call() throws Exception {
|
||||
return doRun();
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
public abstract R doRun() throws Exception;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
# Auto Configuration
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
org.springframework.cloud.sleuth.correlation.CorrelationIdAutoConfiguration
|
||||
@@ -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<String> hasCorrelationIdSet = new TypeSafeMatcher<String>() {
|
||||
@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<String> asyncPing() {
|
||||
return {
|
||||
callWiremockAndReturnOk()
|
||||
}
|
||||
}
|
||||
|
||||
private String callWiremockAndReturnOk() {
|
||||
restTemplate.getForObject("http://localhost:${httpMockServer.port()}", String)
|
||||
return "OK"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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' | _
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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<String> callable = new CorrelationIdTestCallable()
|
||||
when:
|
||||
Callable<String> 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<String> 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<String> {
|
||||
@Override
|
||||
String call() throws Exception {
|
||||
CorrelationIdHolder.get()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<String>(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<String>(HystrixCommand.Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey(""))) {
|
||||
String doRun() {
|
||||
return Thread.currentThread().name
|
||||
}
|
||||
}
|
||||
when:
|
||||
def threadName = command.execute()
|
||||
then:
|
||||
Thread.currentThread().name != threadName
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -47,7 +47,11 @@
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-sleuth-zipkin</artifactId>
|
||||
<artifactId>spring-cloud-sleuth-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
|
||||
@@ -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<EmbeddedServletContainerInitializedEvent> {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<EmbeddedServletContainerInitializedEvent> {
|
||||
@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<String> call() {
|
||||
return new Callable<String>() {
|
||||
@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();
|
||||
}
|
||||
}
|
||||
18
spring-cloud-sleuth-sample/src/main/resources/logback.xml
Normal file
18
spring-cloud-sleuth-sample/src/main/resources/logback.xml
Normal file
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<include resource="org/springframework/boot/logging/logback/defaults.xml" />
|
||||
<property name="LOG_FILE" value="${LOG_FILE:-${LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}/}spring.log}"/>
|
||||
|
||||
<property name="CONSOLE_LOG_PATTERN" value="%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%5p) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%X{Trace-Id:-} %X{Span-Id:-}]){yellow} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n%wex"/>
|
||||
<property name="FILE_LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %5p ${PID:- } --- [%X{Trace-Id:-} %X{Span-Id:-}] [%t] %-40.40logger{39} : %m%n%wex"/>
|
||||
|
||||
<include resource="org/springframework/boot/logging/logback/console-appender.xml" />
|
||||
<include resource="org/springframework/boot/logging/logback/file-appender.xml" />
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="CONSOLE" />
|
||||
<appender-ref ref="FILE" />
|
||||
</root>
|
||||
<logger name="org.springframework.web" level="DEBUG"/>
|
||||
|
||||
</configuration>
|
||||
Reference in New Issue
Block a user