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:
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user