Split out @Async instrumentation into its own package

There was an ordering error in the conditions as well, causing the
wrong async customizers to be registered. Another issue was the
servlet async processing (request atribute not cached in all branches)
resulting in multiple spans per request.
This commit is contained in:
Dave Syer
2015-08-11 18:11:00 +01:00
parent cfd6fe6bb4
commit 37d7b5bedb
19 changed files with 178 additions and 79 deletions

View File

@@ -56,6 +56,7 @@ public class MilliSpan implements Span {
}
//for serialization
@SuppressWarnings("unused")
private MilliSpan() {
this.begin = 0;
this.name = null;

View File

@@ -28,6 +28,7 @@ public class TimelineAnnotation {
private final long time;
private final String msg;
@SuppressWarnings("unused")
private TimelineAnnotation() {
this.time = 0;
this.msg = null;

View File

@@ -17,6 +17,7 @@
package org.springframework.cloud.sleuth.autoconfig;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.sleuth.IdGenerator;
import org.springframework.cloud.sleuth.RandomUuidGenerator;
import org.springframework.cloud.sleuth.Sampler;
@@ -31,6 +32,7 @@ import org.springframework.context.annotation.Configuration;
* @author Spencer Gibb
*/
@Configuration
@ConditionalOnProperty(value="spring.sleuth.enabled", matchIfMissing=true)
public class TraceAutoConfiguration {
@Bean

View File

@@ -0,0 +1,57 @@
/*
* Copyright 2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.sleuth.instrument.async;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.sleuth.instrument.scheduling.TraceSchedulingAutoConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
@Configuration
@ConditionalOnBean(AsyncConfigurer.class)
@AutoConfigureBefore(AsyncDefaultAutoConfiguration.class)
@ConditionalOnProperty(value = "spring.sleuth.async.enabled", matchIfMissing = true)
@AutoConfigureAfter(TraceSchedulingAutoConfiguration.class)
public class AsyncCustomAutoConfiguration implements BeanPostProcessor {
@Autowired
private BeanFactory beanFactory;
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
if (bean instanceof AsyncConfigurer) {
AsyncConfigurer configurer = (AsyncConfigurer) bean;
return new LazyTraceAsyncCustomizer(this.beanFactory, configurer);
}
return bean;
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright 2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.sleuth.instrument.async;
import java.util.concurrent.Executor;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.sleuth.Trace;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.AsyncConfigurerSupport;
import org.springframework.scheduling.annotation.EnableAsync;
@EnableAsync
@Configuration
@ConditionalOnMissingBean(AsyncConfigurer.class)
@ConditionalOnProperty(value = "spring.sleuth.async.enabled", matchIfMissing = true)
@ConditionalOnBean(Trace.class)
@AutoConfigureAfter(AsyncCustomAutoConfiguration.class)
public class AsyncDefaultAutoConfiguration extends AsyncConfigurerSupport {
@Autowired
private BeanFactory beanFactory;
@Override
public Executor getAsyncExecutor() {
return new LazyTraceExecutor(this.beanFactory, new SimpleAsyncTaskExecutor());
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.cloud.sleuth.instrument.scheduling;
package org.springframework.cloud.sleuth.instrument.async;
import java.util.concurrent.Executor;
@@ -22,7 +22,6 @@ import lombok.RequiredArgsConstructor;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.cloud.sleuth.Trace;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.AsyncConfigurerSupport;
@@ -33,16 +32,12 @@ import org.springframework.scheduling.annotation.AsyncConfigurerSupport;
@RequiredArgsConstructor
public class LazyTraceAsyncCustomizer extends AsyncConfigurerSupport {
private Trace trace;
private final BeanFactory beanFactory;
private final AsyncConfigurer delegate;
@Override
public Executor getAsyncExecutor() {
if (this.trace == null) {
this.trace = this.beanFactory.getBean(Trace.class);
}
return new TraceExecutor(this.trace, this.delegate.getAsyncExecutor());
return new LazyTraceExecutor(this.beanFactory, this.delegate.getAsyncExecutor());
}
@Override

View File

@@ -14,12 +14,14 @@
* limitations under the License.
*/
package org.springframework.cloud.sleuth.instrument.scheduling;
package org.springframework.cloud.sleuth.instrument.async;
import java.util.concurrent.Executor;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.cloud.sleuth.Trace;
import org.springframework.cloud.sleuth.instrument.TraceRunnable;
@@ -28,13 +30,22 @@ import org.springframework.cloud.sleuth.instrument.TraceRunnable;
*
*/
@RequiredArgsConstructor
public class TraceExecutor implements Executor {
public class LazyTraceExecutor implements Executor {
private final Trace trace;
private Trace trace;
private final BeanFactory beanFactory;
private final Executor delegate;
@Override
public void execute(Runnable command) {
if (this.trace == null) {
try {
this.trace = this.beanFactory.getBean(Trace.class);
}
catch (NoSuchBeanDefinitionException e) {
this.delegate.execute(command);
}
}
this.delegate.execute(new TraceRunnable(this.trace, command));
}

View File

@@ -16,8 +16,12 @@
package org.springframework.cloud.sleuth.instrument.integration;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.sleuth.Trace;
import org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.config.GlobalChannelInterceptor;
@@ -27,6 +31,8 @@ import org.springframework.integration.config.GlobalChannelInterceptor;
*/
@Configuration
@ConditionalOnClass(GlobalChannelInterceptor.class)
@ConditionalOnBean(Trace.class)
@AutoConfigureAfter(TraceAutoConfiguration.class)
public class TraceSpringIntegrationAutoConfiguration {
@Bean

View File

@@ -20,25 +20,16 @@ package org.springframework.cloud.sleuth.instrument.scheduling;
* @author Spencer Gibb
*/
import java.util.concurrent.Executor;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.sleuth.Trace;
import org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.AsyncConfigurerSupport;
import org.springframework.scheduling.annotation.EnableAsync;
/**
* Registers beans related to task scheduling.
@@ -51,6 +42,8 @@ import org.springframework.scheduling.annotation.EnableAsync;
@Configuration
@EnableAspectJAutoProxy
@ConditionalOnProperty(value = "spring.sleuth.schedule.enabled", matchIfMissing = true)
@ConditionalOnBean(Trace.class)
@AutoConfigureAfter(TraceAutoConfiguration.class)
public class TraceSchedulingAutoConfiguration {
@ConditionalOnClass(ProceedingJoinPoint.class)
@@ -59,44 +52,4 @@ public class TraceSchedulingAutoConfiguration {
return new TraceSchedulingAspect(trace);
}
@EnableAsync
@Configuration
@ConditionalOnMissingBean(AsyncConfigurer.class)
protected static class AsyncDefaultConfiguration extends AsyncConfigurerSupport {
@Autowired
private Trace trace;
@Override
public Executor getAsyncExecutor() {
return new TraceExecutor(this.trace, new SimpleAsyncTaskExecutor());
}
}
@Configuration
@ConditionalOnBean(AsyncConfigurer.class)
protected static class AsyncCustomConfiguration implements BeanPostProcessor {
@Autowired
private BeanFactory beanFactory;
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
if (bean instanceof AsyncConfigurer) {
AsyncConfigurer configurer = (AsyncConfigurer) bean;
return new LazyTraceAsyncCustomizer(this.beanFactory, configurer);
}
return bean;
}
}
}

View File

@@ -121,6 +121,7 @@ public class TraceFilter extends OncePerRequestFilter {
}
else {
traceScope = this.trace.startSpan(name);
request.setAttribute(TRACE_REQUEST_ATTR, traceScope);
}
}
@@ -131,13 +132,12 @@ public class TraceFilter extends OncePerRequestFilter {
filterChain.doFilter(request, response);
}
finally {
if (request.isAsyncSupported() && request.isAsyncStarted()) {
//TODO: howto deal with response annotations and async?
if (isAsyncStarted(request) || request.isAsyncStarted()) {
//TODO: how to deal with response annotations and async?
return;
}
if (traceScope != null) {
addResponseAnnotations(response);
traceScope.close();
}
}

View File

@@ -19,11 +19,14 @@ import java.util.regex.Pattern;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.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.cloud.sleuth.autoconfig.TraceAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.StringUtils;
@@ -39,6 +42,8 @@ import org.springframework.util.StringUtils;
@Configuration
@ConditionalOnProperty(value = "spring.sleuth.web.enabled", matchIfMissing = true)
@ConditionalOnWebApplication
@ConditionalOnBean(Trace.class)
@AutoConfigureAfter(TraceAutoConfiguration.class)
public class TraceWebAutoConfiguration {
/**

View File

@@ -21,9 +21,13 @@ import java.util.Collection;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.sleuth.Trace;
import org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@@ -34,6 +38,8 @@ import org.springframework.web.client.RestTemplate;
@Configuration
@ConditionalOnProperty(value = "spring.sleuth.client.enabled", matchIfMissing = true)
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(Trace.class)
@AutoConfigureAfter(TraceAutoConfiguration.class)
public class TraceWebClientAutoConfiguration {
@Bean

View File

@@ -32,7 +32,6 @@ import com.fasterxml.jackson.databind.ObjectMapper;
* @author Spencer Gibb
*/
@CommonsLog
@Order(Ordered.LOWEST_PRECEDENCE)
@Data
public class JsonLogSpanListener {
@@ -41,16 +40,17 @@ public class JsonLogSpanListener {
private final ObjectMapper objectMapper = new ObjectMapper();
public JsonLogSpanListener() {
prefix = "[span]";
suffix = "[endspan]";
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
this.prefix = "[span]";
this.suffix = "[endspan]";
this.objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
@SneakyThrows
@EventListener(SpanStoppedEvent.class)
@Order(Ordered.LOWEST_PRECEDENCE-10)
public void stop(SpanStoppedEvent event) {
log.info(prefix + objectMapper.writeValueAsString(event.getSpan()) +
suffix);
log.info(this.prefix + this.objectMapper.writeValueAsString(event.getSpan()) +
this.suffix);
}
}

View File

@@ -18,8 +18,12 @@ package org.springframework.cloud.sleuth.log;
import org.apache.commons.logging.Log;
import org.slf4j.MDC;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.sleuth.Trace;
import org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -27,6 +31,8 @@ import org.springframework.context.annotation.Configuration;
* @author Spencer Gibb
*/
@Configuration
@ConditionalOnBean(Trace.class)
@AutoConfigureAfter(TraceAutoConfiguration.class)
public class SleuthLogAutoConfiguration {
@Configuration

View File

@@ -33,10 +33,10 @@ import org.springframework.core.annotation.Order;
* @author Spencer Gibb
*/
@Slf4j
@Order(Ordered.LOWEST_PRECEDENCE)
public class Slf4jSpanListener {
@EventListener(SpanStartedEvent.class)
@Order(Ordered.LOWEST_PRECEDENCE)
public void start(SpanStartedEvent event) {
Span span = event.getSpan();
MDC.put(Trace.SPAN_ID_NAME, span.getSpanId());
@@ -49,6 +49,7 @@ public class Slf4jSpanListener {
}
@EventListener(SpanStoppedEvent.class)
@Order(Ordered.LOWEST_PRECEDENCE)
public void stop(SpanStoppedEvent event) {
//TODO: what should this log level be?
log.info("Stopped span: {}", event.getSpan());

View File

@@ -3,6 +3,8 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration,\
org.springframework.cloud.sleuth.log.SleuthLogAutoConfiguration,\
org.springframework.cloud.sleuth.instrument.integration.TraceSpringIntegrationAutoConfiguration,\
org.springframework.cloud.sleuth.instrument.async.AsyncCustomAutoConfiguration,\
org.springframework.cloud.sleuth.instrument.async.AsyncDefaultAutoConfiguration,\
org.springframework.cloud.sleuth.instrument.scheduling.TraceSchedulingAutoConfiguration,\
org.springframework.cloud.sleuth.instrument.web.TraceWebAutoConfiguration,\
org.springframework.cloud.sleuth.instrument.web.client.TraceWebClientAutoConfiguration

View File

@@ -16,8 +16,6 @@
package org.springframework.cloud.sleuth.sample;
import com.github.kristofa.brave.LoggingSpanCollectorImpl;
import com.github.kristofa.brave.SpanCollector;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
@@ -27,6 +25,9 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.scheduling.annotation.EnableAsync;
import com.github.kristofa.brave.LoggingSpanCollectorImpl;
import com.github.kristofa.brave.SpanCollector;
/**
* @author Spencer Gibb
*/

View File

@@ -39,7 +39,7 @@ import org.springframework.web.client.RestTemplate;
*/
@Slf4j
@RestController
class SampleController implements
public class SampleController implements
ApplicationListener<EmbeddedServletContainerInitializedEvent> {
@Autowired
private RestTemplate restTemplate;

View File

@@ -49,7 +49,6 @@ import com.twitter.zipkin.gen.zipkinCoreConstants;
* @author Spencer Gibb
*/
@CommonsLog
@Order(0)
public class ZipkinSpanListener {
private SpanCollector spanCollector;
@@ -63,6 +62,7 @@ public class ZipkinSpanListener {
}
@EventListener
@Order(0)
public void start(SpanStartedEvent event) {
if (event.getParent()!=null && event.getParent().isRemote()) {
event.getParent().addTimelineAnnotation(zipkinCoreConstants.SERVER_RECV);
@@ -71,17 +71,20 @@ public class ZipkinSpanListener {
}
@EventListener
@Order(0)
public void clientSend(ClientSentEvent event) {
event.getSpan().addTimelineAnnotation(zipkinCoreConstants.CLIENT_SEND);
}
@EventListener
@Order(0)
public void clientReceive(ClientReceivedEvent event) {
event.getSpan().addTimelineAnnotation(zipkinCoreConstants.CLIENT_RECV);
}
@EventListener
public void start(SpanStoppedEvent event) {
@Order(0)
public void stop(SpanStoppedEvent event) {
if (event.getParent()!=null && event.getParent().isRemote()) {
event.getParent().addTimelineAnnotation(zipkinCoreConstants.SERVER_SEND);
this.spanCollector.collect(convert(event.getParent()));
@@ -93,10 +96,9 @@ public class ZipkinSpanListener {
/**
* Converts a given Sleuth span to a Zipkin Span.
* <ul>
* <li>First set the start annotation. [CS, SR], depending whether it is a client service or not.
* <li>Set other id's, etc [TraceId's etc]
* <li>Set id's, etc [TraceId's etc]
* <li>Create timeline annotations based on data from HTrace Span object.
* <li>Create binary annotations based on data from HTrace Span object.
* <li>Set the last annotation. [SS, CR]
* </ul>
*/
public com.twitter.zipkin.gen.Span convert(Span span) {