start to move instrumenting code to s-c-s-core

This commit is contained in:
Spencer Gibb
2015-06-23 20:25:41 -06:00
parent 7bb89e893f
commit 36da106c1b
13 changed files with 546 additions and 4 deletions

View File

@@ -42,6 +42,21 @@
<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>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>

View File

@@ -1,6 +1,6 @@
package org.springframework.cloud.sleuth.slf4j;
import static org.springframework.cloud.sleuth.slf4j.Slf4jSpanStartListener.SPAN_ID_NAME;
import static org.springframework.cloud.sleuth.trace.Trace.SPAN_ID_NAME;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;

View File

@@ -1,22 +1,22 @@
package org.springframework.cloud.sleuth.slf4j;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.cloud.sleuth.trace.Span;
import org.springframework.cloud.sleuth.trace.SpanStartListener;
import org.springframework.cloud.sleuth.trace.Trace;
/**
* @author Spencer Gibb
*/
@Slf4j
public class Slf4jSpanStartListener implements SpanStartListener {
//TODO: Where to put span id name?
public static final String SPAN_ID_NAME = "Span-Id";
@Override
public void startSpan(Span span) {
//TODO: what log level?
log.info("Starting span with id: [{}]", span.getSpanId());
MDC.put(SPAN_ID_NAME, span.getSpanId());
MDC.put(Trace.SPAN_ID_NAME, span.getSpanId());
}
}

View File

@@ -35,6 +35,9 @@ package org.springframework.cloud.sleuth.trace;
*/
public interface Trace {
String SPAN_ID_NAME = "Span-Id";
String TRACE_ID_NAME = "Trace-Id";
/**
* Creates a new trace scope.
* <p/>

View File

@@ -0,0 +1,59 @@
package org.springframework.cloud.sleuth.trace.intercept.circuitbreaker;
import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.HystrixThreadPoolKey;
import org.springframework.cloud.sleuth.trace.Trace;
import org.springframework.cloud.sleuth.trace.TraceScope;
/**
* Abstraction over {@code HystrixCommand} that wraps command execution with Trace setting
*
* @see HystrixCommand
* @see CorrelationIdUpdater
*
* @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;
}

View File

@@ -0,0 +1,39 @@
package org.springframework.cloud.sleuth.trace.intercept.scheduling;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.cloud.sleuth.trace.Trace;
import org.springframework.cloud.sleuth.trace.TraceScope;
import org.springframework.scheduling.annotation.Scheduled;
/**
* 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
* @author Spencer Gibb
*
* @see org.springframework.cloud.sleuth.trace.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 setNewCorrelationIdOnThread(final ProceedingJoinPoint pjp) throws Throwable {
TraceScope scope = trace.startSpan(pjp.toShortString());
try {
return pjp.proceed();
} finally {
scope.close();
}
}
}

View File

@@ -0,0 +1,30 @@
package org.springframework.cloud.sleuth.trace.intercept.scheduling;
/**
* @author Spencer Gibb
*/
import org.springframework.cloud.sleuth.trace.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
public class TraceSchedulingAutoConfiguration {
@Bean
public TraceSchedulingAspect traceSchedulingAspect(Trace trace) {
return new TraceSchedulingAspect(trace);
}
}

View File

@@ -0,0 +1,122 @@
/*
* 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.trace.intercept.web;
import static org.springframework.cloud.sleuth.trace.Trace.SPAN_ID_NAME;
import static org.springframework.util.StringUtils.hasText;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.util.Collections;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.cloud.sleuth.trace.MilliSpan;
import org.springframework.cloud.sleuth.trace.Span;
import org.springframework.cloud.sleuth.trace.Trace;
import org.springframework.cloud.sleuth.trace.TraceScope;
import org.springframework.web.filter.OncePerRequestFilter;
/**
* 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 Trace
* @see MDC
*
* @author Jakub Nabrdalik, 4financeIT
* @author Tomasz Nurkiewicz, 4financeIT
* @author Marcin Grzejszczak, 4financeIT
* @author Spencer Gibb
*/
public class TraceFilter 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 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 spanIdFromRequest = getSpanIdFrom(request);
String spanId = (hasText(spanIdFromRequest)) ? spanIdFromRequest
: getSpanIdFrom(response);
TraceScope traceScope = null;
if (spanId != null) {
addCorrelationIdToResponseIfNotPresent(response, spanId);
Span span = MilliSpan.builder().traceId("") // FIXME get traceId from request
.parents(Collections.singletonList(spanId))
// TODO: use parent() when lombok plugin supports it
.build();
traceScope = trace.startSpan("traceFilter", span);
}
else {
traceScope = trace.startSpan("traceFilter");
}
try {
filterChain.doFilter(request, response);
}
finally {
traceScope.close();
}
}
private String getSpanIdFrom(final HttpServletResponse response) {
return response.getHeader(SPAN_ID_NAME);
}
private String getSpanIdFrom(final HttpServletRequest request) {
return request.getHeader(SPAN_ID_NAME);
}
private void addCorrelationIdToResponseIfNotPresent(HttpServletResponse response,
String spanId) {
if (!hasText(response.getHeader(SPAN_ID_NAME))) {
response.addHeader(SPAN_ID_NAME, spanId);
}
}
@Override
protected boolean shouldNotFilterAsyncDispatch() {
return false;
}
}

View File

@@ -0,0 +1,125 @@
package org.springframework.cloud.sleuth.trace.intercept.web;
import static org.springframework.cloud.sleuth.trace.Trace.SPAN_ID_NAME;
import java.util.ArrayList;
import java.util.List;
import lombok.extern.apachecommons.CommonsLog;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.cloud.sleuth.trace.Trace;
import org.springframework.cloud.sleuth.trace.TraceScope;
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;
/**
* Aspect that adds correlation id to
* <p/>
* <ul>
* <li>{@link RestController} annotated classes</li>
* <li>{@link Controller} annotated classes</li>
* <li>explicit {@link RestOperations}.exchange(..) method calls</li>
* </ul>
* <p/>
* For controllers an around aspect is created that create a {@link Span} for each call.
* <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 span id header * {@link Trace#SPAN_ID_NAME} with the value taken
* from the current Span. Finally the method execution proceeds.
*
* @see RestController
* @see Controller
* @see RestOperations
* @see Trace
*
* @author Tomasz Nurkewicz, 4financeIT
* @author Marcin Grzejszczak, 4financeIT
* @author Michal Chmielarz, 4financeIT
* @author Spencer Gibb
*/
@CommonsLog
public class TraceWebAspect {
private final Trace trace;
public TraceWebAspect(Trace trace) {
this.trace = trace;
}
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("anyRestControllerAnnotated() || anyControllerAnnotated()")
private void anyControllerOrRestController() {
}
@Around("anyControllerOrRestController()")
public Object wrapWithCorrelationId(ProceedingJoinPoint pjp) throws Throwable {
TraceScope scope = trace.startSpan(pjp.toShortString());
try {
return pjp.proceed();
}
finally {
scope.close();
}
}
@Pointcut("execution(public * org.springframework.web.client.RestOperations.exchange(..))")
private void anyExchangeRestOperationsMethod() {
}
@Around("anyExchangeRestOperationsMethod()")
public Object wrapWithCorrelationIdForRestOperations(ProceedingJoinPoint pjp)
throws Throwable {
TraceScope scope = trace.startSpan(pjp.toShortString());
try {
String spanId = scope.getSpan().getSpanId();
//TODO: set traceId on restTemplate call as well
log.debug("Wrapping RestTemplate call with span id [" + spanId + "]");
HttpEntity httpEntity = (HttpEntity) pjp.getArgs()[HTTP_ENTITY_PARAM_INDEX];
HttpEntity newHttpEntity = createNewHttpEntity(httpEntity, spanId);
List<Object> newArgs = modifyHttpEntityInMethodArguments(pjp, newHttpEntity);
return pjp.proceed(newArgs.toArray());
}
finally {
scope.close();
}
}
@SuppressWarnings("unchecked")
private HttpEntity createNewHttpEntity(HttpEntity httpEntity, String correlationId) {
HttpHeaders newHttpHeaders = new HttpHeaders();
newHttpHeaders.putAll(httpEntity.getHeaders());
newHttpHeaders.add(SPAN_ID_NAME, 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;
}
}

View File

@@ -0,0 +1,66 @@
/*
* 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.trace.intercept.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.Trace;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.StringUtils;
/**
* 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 correlationID setting
*/
@Value("${spring.cloud.sleuth.trace.web.skipPattern:}")
private String skipPattern;
@Autowired
private Trace trace;
@Bean
@ConditionalOnMissingBean
public TraceWebAspect traceWebAspect() {
return new TraceWebAspect(trace);
}
@Bean
@ConditionalOnMissingBean
public FilterRegistrationBean correlationHeaderFilter() {
Pattern pattern = StringUtils.hasText(skipPattern) ? Pattern.compile(skipPattern)
: TraceFilter.DEFAULT_SKIP_PATTERN;
return new FilterRegistrationBean(new TraceFilter(trace, pattern));
}
}

View File

@@ -0,0 +1,55 @@
/*
* 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.trace.intercept.web.client;
import static org.springframework.cloud.sleuth.trace.Trace.SPAN_ID_NAME;
import static org.springframework.cloud.sleuth.trace.Trace.TRACE_ID_NAME;
import java.io.IOException;
import org.springframework.cloud.sleuth.trace.SpanHolder;
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 org.springframework.cloud.sleuth.trace.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 {
if (!request.getHeaders().containsKey(SPAN_ID_NAME)) {
request.getHeaders().add(SPAN_ID_NAME,
SpanHolder.getCurrentSpan().getSpanId());
}
if (!request.getHeaders().containsKey(TRACE_ID_NAME)) {
request.getHeaders().add(TRACE_ID_NAME,
SpanHolder.getCurrentSpan().getSpanId());
}
return execution.execute(request, body);
}
}

View File

@@ -0,0 +1,26 @@
package org.springframework.cloud.sleuth.trace.intercept.web.client;
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.cloud.sleuth.resttemplate.SleuthRestTemplateAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
/**
* @author Spencer Gibb
*/
@Configuration
@ConditionalOnProperty(value = "spring.cloud.sleuth.trace.web.client.enabled", matchIfMissing = true)
@ConditionalOnClass(RestTemplate.class)
@AutoConfigureAfter(SleuthRestTemplateAutoConfiguration.class)
public class TraceWebClientAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public TraceRestTemplateInterceptor traceRestTemplateInterceptor() {
return new TraceRestTemplateInterceptor();
}
}

View File

@@ -2,4 +2,6 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.sleuth.resttemplate.SleuthRestTemplateAutoConfiguration,\
org.springframework.cloud.sleuth.trace.TraceAutoConfiguration,\
org.springframework.cloud.sleuth.trace.intercept.web.TraceWebAutoConfiguration,\
org.springframework.cloud.sleuth.trace.intercept.web.client.TraceWebClientAutoConfiguration,\
org.springframework.cloud.sleuth.slf4j.SleuthSlf4jAutoConfiguration