Configure Reactor instrumentation to wrap onLastOperator or onEachOperator

with this feature we would allow user to pick whether they instrument on each or on last operator.

fixes gh-1478
This commit is contained in:
Marcin Grzejszczak
2019-10-30 13:09:30 +01:00
parent 759ac87481
commit cd2e2c2d49
6 changed files with 191 additions and 12 deletions

View File

@@ -24,7 +24,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
* @author Marcin Grzejszczak
* @since 2.0.2
*/
@ConfigurationProperties("spring.sleuth.reactor.enabled")
@ConfigurationProperties("spring.sleuth.reactor")
public class SleuthReactorProperties {
/**
@@ -32,6 +32,14 @@ public class SleuthReactorProperties {
*/
private boolean enabled = true;
/**
* When true decorates on each operator, will be less performing, but logging will
* always contain the tracing entries in each operator. When false decorates on last
* operator, will be more performing, but logging might not always contain the tracing
* entries.
*/
private boolean decorateOnEach = true;
public boolean isEnabled() {
return this.enabled;
}
@@ -40,4 +48,12 @@ public class SleuthReactorProperties {
this.enabled = enabled;
}
public boolean isDecorateOnEach() {
return this.decorateOnEach;
}
public void setDecorateOnEach(boolean decorateOnEach) {
this.decorateOnEach = decorateOnEach;
}
}

View File

@@ -27,6 +27,7 @@ import reactor.core.scheduler.Schedulers;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
@@ -35,11 +36,18 @@ 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.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.context.scope.refresh.RefreshScope;
import org.springframework.cloud.context.scope.refresh.RefreshScopeRefreshedEvent;
import org.springframework.cloud.sleuth.instrument.async.TraceableScheduledExecutorService;
import org.springframework.cloud.sleuth.instrument.web.TraceWebFluxAutoConfiguration;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.ConfigurableEnvironment;
import static org.springframework.cloud.sleuth.instrument.reactor.TraceReactorAutoConfiguration.TraceReactorConfiguration.SLEUTH_TRACE_REACTOR_KEY;
/**
* {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration
@@ -53,6 +61,7 @@ import org.springframework.context.annotation.Configuration;
@ConditionalOnProperty(value = "spring.sleuth.reactor.enabled", matchIfMissing = true)
@ConditionalOnClass(Mono.class)
@AutoConfigureAfter(TraceWebFluxAutoConfiguration.class)
@EnableConfigurationProperties(SleuthReactorProperties.class)
public class TraceReactorAutoConfiguration {
static final String SLEUTH_REACTOR_EXECUTOR_SERVICE_KEY = "sleuth";
@@ -66,6 +75,32 @@ public class TraceReactorAutoConfiguration {
private static final Log log = LogFactory.getLog(TraceReactorConfiguration.class);
@Autowired
BeanFactory beanFactory;
@PreDestroy
public void cleanupHooks() {
if (log.isTraceEnabled()) {
log.trace("Cleaning up hooks");
}
SleuthReactorProperties reactorProperties = this.beanFactory
.getBean(SleuthReactorProperties.class);
if (reactorProperties.isDecorateOnEach()) {
if (log.isTraceEnabled()) {
log.trace("Resetting onEach operator instrumentation");
}
Hooks.resetOnEachOperator(SLEUTH_TRACE_REACTOR_KEY);
}
else {
if (log.isTraceEnabled()) {
log.trace("Resetting onLast operator instrumentation");
}
Hooks.resetOnLastOperator(SLEUTH_TRACE_REACTOR_KEY);
}
Schedulers
.removeExecutorServiceDecorator(SLEUTH_REACTOR_EXECUTOR_SERVICE_KEY);
}
@Bean
// for tests
@ConditionalOnMissingBean
@@ -79,23 +114,67 @@ public class TraceReactorAutoConfiguration {
return new HookRegisteringBeanDefinitionRegistryPostProcessor(context);
}
@PreDestroy
public void cleanupHooks() {
if (log.isTraceEnabled()) {
log.trace("Cleaning up hooks");
@Configuration
@ConditionalOnClass(RefreshScope.class)
static class HooksRefresherConfiguration {
@Bean
HooksRefresher hooksRefresher(SleuthReactorProperties reactorProperties,
ConfigurableApplicationContext context) {
return new HooksRefresher(reactorProperties, context);
}
Hooks.resetOnEachOperator(SLEUTH_TRACE_REACTOR_KEY);
Schedulers
.removeExecutorServiceDecorator(SLEUTH_REACTOR_EXECUTOR_SERVICE_KEY);
}
}
}
class HooksRefresher implements ApplicationListener<RefreshScopeRefreshedEvent> {
private static final Log log = LogFactory.getLog(HooksRefresher.class);
private final SleuthReactorProperties reactorProperties;
private final ConfigurableApplicationContext context;
HooksRefresher(SleuthReactorProperties reactorProperties,
ConfigurableApplicationContext context) {
this.reactorProperties = reactorProperties;
this.context = context;
}
@Override
public void onApplicationEvent(RefreshScopeRefreshedEvent event) {
if (log.isDebugEnabled()) {
log.debug("Context refreshed, will reset hooks and then re-register them");
}
Hooks.resetOnEachOperator(SLEUTH_TRACE_REACTOR_KEY);
Hooks.resetOnLastOperator(SLEUTH_TRACE_REACTOR_KEY);
if (this.reactorProperties.isDecorateOnEach()) {
if (log.isTraceEnabled()) {
log.trace("Decorating onEach operator instrumentation");
}
Hooks.onEachOperator(SLEUTH_TRACE_REACTOR_KEY,
ReactorSleuth.scopePassingSpanOperator(this.context));
}
else {
if (log.isTraceEnabled()) {
log.trace("Decorating onLast operator instrumentation");
}
Hooks.onLastOperator(SLEUTH_TRACE_REACTOR_KEY,
ReactorSleuth.scopePassingSpanOperator(this.context));
}
}
}
class HookRegisteringBeanDefinitionRegistryPostProcessor
implements BeanDefinitionRegistryPostProcessor {
private static final Log log = LogFactory
.getLog(HookRegisteringBeanDefinitionRegistryPostProcessor.class);
private final ConfigurableApplicationContext context;
HookRegisteringBeanDefinitionRegistryPostProcessor(
@@ -115,9 +194,23 @@ class HookRegisteringBeanDefinitionRegistryPostProcessor
}
void setupHooks(BeanFactory beanFactory) {
Hooks.onEachOperator(
TraceReactorAutoConfiguration.TraceReactorConfiguration.SLEUTH_TRACE_REACTOR_KEY,
ReactorSleuth.scopePassingSpanOperator(this.context));
ConfigurableEnvironment environment = this.context.getEnvironment();
boolean decorateOnEach = environment.getProperty(
"spring.sleuth.reactor.decorate-on-each", Boolean.class, true);
if (decorateOnEach) {
if (log.isTraceEnabled()) {
log.trace("Decorating onEach operator instrumentation");
}
Hooks.onEachOperator(SLEUTH_TRACE_REACTOR_KEY,
ReactorSleuth.scopePassingSpanOperator(this.context));
}
else {
if (log.isTraceEnabled()) {
log.trace("Decorating onLast operator instrumentation");
}
Hooks.onLastOperator(SLEUTH_TRACE_REACTOR_KEY,
ReactorSleuth.scopePassingSpanOperator(this.context));
}
Schedulers.setExecutorServiceDecorator(
TraceReactorAutoConfiguration.SLEUTH_REACTOR_EXECUTOR_SERVICE_KEY,
(scheduler,

View File

@@ -18,9 +18,14 @@ package org.springframework.cloud.sleuth.instrument.reactor;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import reactor.core.publisher.Hooks;
import reactor.core.scheduler.Schedulers;
import org.springframework.context.ConfigurableApplicationContext;
import static org.springframework.cloud.sleuth.instrument.reactor.TraceReactorAutoConfiguration.SLEUTH_REACTOR_EXECUTOR_SERVICE_KEY;
import static org.springframework.cloud.sleuth.instrument.reactor.TraceReactorAutoConfiguration.TraceReactorConfiguration.SLEUTH_TRACE_REACTOR_KEY;
/**
* @author Marcin Grzejszczak
*/
@@ -37,7 +42,9 @@ public final class TraceReactorAutoConfigurationAccessorConfiguration {
if (log.isTraceEnabled()) {
log.trace("Cleaning up hooks");
}
new TraceReactorAutoConfiguration.TraceReactorConfiguration().cleanupHooks();
Hooks.resetOnEachOperator(SLEUTH_TRACE_REACTOR_KEY);
Hooks.resetOnLastOperator(SLEUTH_TRACE_REACTOR_KEY);
Schedulers.removeExecutorServiceDecorator(SLEUTH_REACTOR_EXECUTOR_SERVICE_KEY);
}
public static void setup(ConfigurableApplicationContext context) {

View File

@@ -37,6 +37,7 @@ import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.test.rule.OutputCapture;
import org.springframework.cloud.context.refresh.ContextRefresher;
import org.springframework.cloud.sleuth.DisableWebFluxSecurity;
import org.springframework.cloud.sleuth.instrument.reactor.Issue866Configuration;
import org.springframework.cloud.sleuth.instrument.reactor.TraceReactorAutoConfigurationAccessorConfiguration;
@@ -84,6 +85,35 @@ public class FlatMapTests {
"security.basic.enabled=false",
"management.security.enabled=false")
.run();
assertReactorTracing(context);
}
@Test
public void should_work_with_flat_maps_with_on_last_operator_instrumentation() {
// given
ConfigurableApplicationContext context = new SpringApplicationBuilder(
FlatMapTests.TestConfiguration.class, Issue866Configuration.class)
.web(WebApplicationType.REACTIVE)
.properties("server.port=0", "spring.jmx.enabled=false",
"spring.sleuth.reactor.decorate-on-each=false",
"spring.application.name=TraceWebFlux2Tests",
"security.basic.enabled=false",
"management.security.enabled=false")
.run();
assertReactorTracing(context);
try {
System.setProperty("spring.sleuth.reactor.decorate-on-each", "true");
// trigger context refreshed
context.getBean(ContextRefresher.class).refresh();
assertReactorTracing(context);
}
finally {
System.clearProperty("spring.sleuth.reactor.decorate-on-each");
}
}
private void assertReactorTracing(ConfigurableApplicationContext context) {
ArrayListSpanReporter accumulator = context.getBean(ArrayListSpanReporter.class);
int port = context.getBean(Environment.class).getProperty("local.server.port",
Integer.class);