Added instrumentation of spring amqp components

with this change we're reusing Brave to add instrumentation to any existing RabbitTemplate

    fixes gh-883
This commit is contained in:
Marcin Grzejszczak
2018-03-06 14:14:55 -05:00
parent 2e974e8ce7
commit d83c9d1c31
8 changed files with 289 additions and 5 deletions

View File

@@ -272,7 +272,7 @@
<spring-cloud-stream.version>Elmhurst.BUILD-SNAPSHOT</spring-cloud-stream.version>
<spring-cloud-netflix.version>2.0.0.BUILD-SNAPSHOT</spring-cloud-netflix.version>
<spring-cloud-openfeign.version>2.0.0.BUILD-SNAPSHOT</spring-cloud-openfeign.version>
<brave.version>4.17.1</brave.version>
<brave.version>4.17.2</brave.version>
<!-- Version set until zipkin-junit gets defined in Brave BOM -->
<zipkin.version>2.5.1</zipkin.version>
<spring-security-boot-autoconfigure.version>2.0.0.RELEASE</spring-security-boot-autoconfigure.version>

View File

@@ -91,6 +91,11 @@
<artifactId>spring-integration-core</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
@@ -164,6 +169,10 @@
<groupId>io.zipkin.brave</groupId>
<artifactId>brave-instrumentation-spring-web</artifactId>
</dependency>
<dependency>
<groupId>io.zipkin.brave</groupId>
<artifactId>brave-instrumentation-spring-rabbit</artifactId>
</dependency>
<dependency>
<groupId>io.zipkin.brave</groupId>
<artifactId>brave-instrumentation-httpclient</artifactId>

View File

@@ -0,0 +1,81 @@
/*
* Copyright 2013-2018 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.messaging;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* @author Marcin Grzejszczak
* @since 2.0.0
*/
@ConfigurationProperties("spring.sleuth")
public class SleuthMessagingProperties {
private Integration integration = new Integration();
private Messaging messaging = new Messaging();
public Integration getIntegration() {
return this.integration;
}
public void setIntegration(Integration integration) {
this.integration = integration;
}
public Messaging getMessaging() {
return this.messaging;
}
public void setMessaging(Messaging messaging) {
this.messaging = messaging;
}
public static class Integration {
private boolean enabled;
public boolean isEnabled() {
return this.enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}
public static class Messaging {
private boolean enabled;
private String remoteServiceName = "mq-service";
public boolean isEnabled() {
return this.enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public String getRemoteServiceName() {
return this.remoteServiceName;
}
public void setRemoteServiceName(String remoteServiceName) {
this.remoteServiceName = remoteServiceName;
}
}
}

View File

@@ -0,0 +1,100 @@
/*
* Copyright 2013-2018 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.messaging;
import brave.Tracing;
import brave.spring.rabbit.SpringRabbitTracing;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
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.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration
* Auto-configuration} that registers a tracing instrumentation of
* messaging components.
*
* @author Marcin Grzejszczak
* @since 2.0.0
*/
@Configuration
@ConditionalOnBean(Tracing.class)
@AutoConfigureAfter({ TraceAutoConfiguration.class })
@ConditionalOnProperty(value = "spring.sleuth.messaging.enabled", matchIfMissing = true)
@EnableConfigurationProperties(SleuthMessagingProperties.class)
public class TraceMessagingAutoConfiguration {
@Configuration
@ConditionalOnClass(RabbitTemplate.class)
protected static class SleuthRabbitConfiguration {
@Bean
@ConditionalOnMissingBean
SpringRabbitTracing springRabbitTracing(Tracing tracing,
SleuthMessagingProperties properties) {
return SpringRabbitTracing.newBuilder(tracing)
.remoteServiceName(properties.getMessaging()
.getRemoteServiceName())
.build();
}
@Bean
// for tests
@ConditionalOnMissingBean
SleuthRabbitBeanPostProcessor sleuthRabbitBeanPostProcessor(BeanFactory beanFactory) {
return new SleuthRabbitBeanPostProcessor(beanFactory);
}
}
}
class SleuthRabbitBeanPostProcessor implements BeanPostProcessor {
private final BeanFactory beanFactory;
private SpringRabbitTracing tracing;
SleuthRabbitBeanPostProcessor(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
@Override public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
if (bean instanceof RabbitTemplate) {
return rabbitTracing()
.decorateRabbitTemplate((RabbitTemplate) bean);
} else if (bean instanceof SimpleRabbitListenerContainerFactory) {
return rabbitTracing()
.decorateSimpleRabbitListenerContainerFactory((SimpleRabbitListenerContainerFactory) bean);
}
return bean;
}
SpringRabbitTracing rabbitTracing() {
if (this.tracing == null) {
this.tracing = this.beanFactory.getBean(SpringRabbitTracing.class);
}
return this.tracing;
}
}

View File

@@ -22,7 +22,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.sleuth.TraceKeys;
import org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -43,7 +42,7 @@ import org.springframework.integration.config.GlobalChannelInterceptor;
@ConditionalOnBean(Tracing.class)
@AutoConfigureAfter({ TraceAutoConfiguration.class })
@ConditionalOnProperty(value = "spring.sleuth.integration.enabled", matchIfMissing = true)
@EnableConfigurationProperties(TraceKeys.class)
@EnableConfigurationProperties(SleuthMessagingProperties.class)
public class TraceSpringIntegrationAutoConfiguration {
@Bean

View File

@@ -17,6 +17,7 @@ org.springframework.cloud.sleuth.instrument.rxjava.RxJavaAutoConfiguration,\
org.springframework.cloud.sleuth.instrument.reactor.TraceReactorAutoConfiguration,\
org.springframework.cloud.sleuth.instrument.web.TraceWebFluxAutoConfiguration,\
org.springframework.cloud.sleuth.instrument.zuul.TraceZuulAutoConfiguration,\
org.springframework.cloud.sleuth.instrument.messaging.TraceMessagingAutoConfiguration,\
org.springframework.cloud.sleuth.instrument.messaging.TraceSpringIntegrationAutoConfiguration,\
org.springframework.cloud.sleuth.instrument.messaging.websocket.TraceWebSocketAutoConfiguration,\
org.springframework.cloud.sleuth.instrument.opentracing.OpentracingAutoConfiguration

View File

@@ -0,0 +1,95 @@
/*
* Copyright 2013-2018 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.messaging;
import brave.sampler.Sampler;
import brave.spring.rabbit.SpringRabbitTracing;
import com.rabbitmq.client.Channel;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.BDDMockito;
import org.mockito.Mock;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.Connection;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.cloud.sleuth.util.ArrayListSpanReporter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.junit4.SpringRunner;
import static org.assertj.core.api.BDDAssertions.then;
/**
* @author Marcin Grzejszczak
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = TraceMessagingAutoConfigurationTests.Config.class,
webEnvironment = SpringBootTest.WebEnvironment.NONE)
public class TraceMessagingAutoConfigurationTests {
@Autowired RabbitTemplate rabbitTemplate;
@Autowired ArrayListSpanReporter reporter;
@Autowired TestSleuthRabbitBeanPostProcessor postProcessor;
@Test
public void should_wrap_rabbit_template() {
then(this.rabbitTemplate).isNotNull();
then(this.postProcessor.rabbitTracingCalled).isTrue();
}
@Configuration
@EnableAutoConfiguration
protected static class Config {
@Bean Sampler sampler() {
return Sampler.ALWAYS_SAMPLE;
}
@Bean ArrayListSpanReporter reporter() {
return new ArrayListSpanReporter();
}
@Bean SleuthRabbitBeanPostProcessor postProcessor(BeanFactory beanFactory) {
return new TestSleuthRabbitBeanPostProcessor(beanFactory);
}
}
}
class TestSleuthRabbitBeanPostProcessor extends SleuthRabbitBeanPostProcessor {
boolean rabbitTracingCalled = false;
TestSleuthRabbitBeanPostProcessor(BeanFactory beanFactory) {
super(beanFactory);
}
@Override SpringRabbitTracing rabbitTracing() {
this.rabbitTracingCalled = true;
return super.rabbitTracing();
}
}

View File

@@ -106,8 +106,7 @@ public class WebClientExceptionTests {
then(this.tracer.tracer().currentSpan()).isNull();
then(this.reporter.getSpans()).isNotEmpty();
then(this.reporter.getSpans().get(0).tags().get("error"))
.contains("invalid.host.to.break.tests");
then(this.reporter.getSpans().get(0).tags()).containsKey("error");
}
Object[] parametersForShouldCloseSpanUponException() {