Merge branch 'master' into 2.0.x
This commit is contained in:
@@ -27,8 +27,7 @@ public class HeaderBasedMessagingExtractor implements MessagingSpanTextMapExtrac
|
||||
if (spanIdMissing) {
|
||||
carrier.put(TraceMessageHeaders.SPAN_ID_NAME, traceId);
|
||||
}
|
||||
} else if (!hasHeader(carrier, TraceMessageHeaders.SPAN_ID_NAME)
|
||||
|| !hasHeader(carrier, TraceMessageHeaders.TRACE_ID_NAME)) {
|
||||
} else if (spanIdMissing) {
|
||||
return null;
|
||||
// TODO: Consider throwing IllegalArgumentException;
|
||||
}
|
||||
|
||||
@@ -22,9 +22,12 @@ import java.util.Map;
|
||||
|
||||
import org.springframework.cloud.sleuth.SpanTextMap;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
|
||||
import org.springframework.messaging.support.MessageBuilder;
|
||||
import org.springframework.messaging.support.MessageHeaderAccessor;
|
||||
import org.springframework.messaging.support.NativeMessageHeaderAccessor;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -35,9 +38,9 @@ import org.springframework.util.StringUtils;
|
||||
*/
|
||||
class MessagingTextMap implements SpanTextMap {
|
||||
|
||||
private final MessageBuilder delegate;
|
||||
private final MessageBuilder<?> delegate;
|
||||
|
||||
public MessagingTextMap(MessageBuilder delegate) {
|
||||
public MessagingTextMap(MessageBuilder<?> delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@@ -46,7 +49,9 @@ class MessagingTextMap implements SpanTextMap {
|
||||
Map<String, String> map = new HashMap<>();
|
||||
for (Map.Entry<String, Object> entry : this.delegate.build().getHeaders()
|
||||
.entrySet()) {
|
||||
map.put(entry.getKey(), String.valueOf(entry.getValue()));
|
||||
if (!NativeMessageHeaderAccessor.NATIVE_HEADERS.equals(entry.getKey())) {
|
||||
map.put(entry.getKey(), String.valueOf(entry.getValue()));
|
||||
}
|
||||
}
|
||||
return map.entrySet().iterator();
|
||||
}
|
||||
@@ -61,10 +66,23 @@ class MessagingTextMap implements SpanTextMap {
|
||||
MessageHeaderAccessor accessor = MessageHeaderAccessor
|
||||
.getMutableAccessor(initialMessage);
|
||||
accessor.setHeader(key, value);
|
||||
if (accessor instanceof NativeMessageHeaderAccessor) {
|
||||
NativeMessageHeaderAccessor nativeAccessor = (NativeMessageHeaderAccessor) accessor;
|
||||
if (accessor instanceof SimpMessageHeaderAccessor) {
|
||||
SimpMessageHeaderAccessor nativeAccessor = (SimpMessageHeaderAccessor) accessor;
|
||||
nativeAccessor.setNativeHeader(key, value);
|
||||
}
|
||||
else if (accessor.getHeader(NativeMessageHeaderAccessor.NATIVE_HEADERS) != null) {
|
||||
if (accessor.getHeader(
|
||||
NativeMessageHeaderAccessor.NATIVE_HEADERS) instanceof MultiValueMap) {
|
||||
MultiValueMap<String, String> map = (MultiValueMap<String, String>) accessor
|
||||
.getHeader(NativeMessageHeaderAccessor.NATIVE_HEADERS);
|
||||
map.add(key, value);
|
||||
}
|
||||
}
|
||||
else {
|
||||
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
|
||||
accessor.setHeader(NativeMessageHeaderAccessor.NATIVE_HEADERS, map);
|
||||
map.add(key, value);
|
||||
}
|
||||
this.delegate.copyHeaders(accessor.toMessageHeaders());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,14 +17,15 @@
|
||||
package org.springframework.cloud.sleuth.instrument.messaging;
|
||||
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.cloud.sleuth.Log;
|
||||
import org.springframework.cloud.sleuth.Span;
|
||||
import org.springframework.cloud.sleuth.sampler.NeverSampler;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.MessagingException;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
import org.springframework.messaging.MessagingException;
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
import org.springframework.messaging.support.MessageBuilder;
|
||||
import org.springframework.messaging.support.MessageHeaderAccessor;
|
||||
@@ -46,17 +47,23 @@ public class TraceChannelInterceptor extends AbstractTraceChannelInterceptor {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterSendCompletion(Message<?> message, MessageChannel channel, boolean sent, Exception ex) {
|
||||
Span currentSpan = getTracer().getCurrentSpan();
|
||||
public void afterSendCompletion(Message<?> message, MessageChannel channel,
|
||||
boolean sent, Exception ex) {
|
||||
Message<?> retrievedMessage = getMessage(message);
|
||||
MessageBuilder<?> messageBuilder = MessageBuilder.fromMessage(retrievedMessage);
|
||||
Span currentSpan = getTracer().isTracing() ? getTracer().getCurrentSpan()
|
||||
: buildSpan(new MessagingTextMap(messageBuilder));
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Completed sending and current span is " + currentSpan);
|
||||
}
|
||||
getTracer().continueSpan(currentSpan);
|
||||
if (containsServerReceived(currentSpan)) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Marking span with server send");
|
||||
}
|
||||
currentSpan.logEvent(Span.SERVER_SEND);
|
||||
} else if (currentSpan != null) {
|
||||
}
|
||||
else if (currentSpan != null) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Marking span with client received");
|
||||
}
|
||||
@@ -93,6 +100,8 @@ public class TraceChannelInterceptor extends AbstractTraceChannelInterceptor {
|
||||
MessageBuilder<?> messageBuilder = MessageBuilder.fromMessage(retrievedMessage);
|
||||
Span parentSpan = getTracer().isTracing() ? getTracer().getCurrentSpan()
|
||||
: buildSpan(new MessagingTextMap(messageBuilder));
|
||||
// Do not continue the parent (assume that this is handled by caller)
|
||||
// getTracer().continueSpan(parentSpan);
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Parent span is " + parentSpan);
|
||||
}
|
||||
@@ -101,12 +110,14 @@ public class TraceChannelInterceptor extends AbstractTraceChannelInterceptor {
|
||||
log.debug("Name of the span will be [" + name + "]");
|
||||
}
|
||||
Span span = startSpan(parentSpan, name, message);
|
||||
if (message.getHeaders().containsKey(TraceMessageHeaders.MESSAGE_SENT_FROM_CLIENT)) {
|
||||
if (message.getHeaders()
|
||||
.containsKey(TraceMessageHeaders.MESSAGE_SENT_FROM_CLIENT)) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Marking span with server received");
|
||||
}
|
||||
span.logEvent(Span.SERVER_RECV);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Marking span with client send");
|
||||
}
|
||||
@@ -119,7 +130,7 @@ public class TraceChannelInterceptor extends AbstractTraceChannelInterceptor {
|
||||
return new GenericMessage<>(message.getPayload(), headers.getMessageHeaders());
|
||||
}
|
||||
|
||||
private Message getMessage(Message<?> message) {
|
||||
private Message<?> getMessage(Message<?> message) {
|
||||
Object payload = message.getPayload();
|
||||
if (payload instanceof MessagingException) {
|
||||
MessagingException e = (MessagingException) payload;
|
||||
@@ -132,7 +143,8 @@ public class TraceChannelInterceptor extends AbstractTraceChannelInterceptor {
|
||||
if (span != null) {
|
||||
return getTracer().createSpan(name, span);
|
||||
}
|
||||
if (Span.SPAN_NOT_SAMPLED.equals(message.getHeaders().get(TraceMessageHeaders.SAMPLED_NAME))) {
|
||||
if (Span.SPAN_NOT_SAMPLED
|
||||
.equals(message.getHeaders().get(TraceMessageHeaders.SAMPLED_NAME))) {
|
||||
return getTracer().createSpan(name, NeverSampler.INSTANCE);
|
||||
}
|
||||
return getTracer().createSpan(name);
|
||||
@@ -141,7 +153,10 @@ public class TraceChannelInterceptor extends AbstractTraceChannelInterceptor {
|
||||
@Override
|
||||
public Message<?> beforeHandle(Message<?> message, MessageChannel channel,
|
||||
MessageHandler handler) {
|
||||
Span spanFromHeader = getTracer().getCurrentSpan();
|
||||
Message<?> retrievedMessage = getMessage(message);
|
||||
MessageBuilder<?> messageBuilder = MessageBuilder.fromMessage(retrievedMessage);
|
||||
Span spanFromHeader = getTracer().isTracing() ? getTracer().getCurrentSpan()
|
||||
: buildSpan(new MessagingTextMap(messageBuilder));
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Continuing span " + spanFromHeader + " before handling message");
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import org.springframework.cloud.sleuth.instrument.messaging.TraceChannelInterce
|
||||
import org.springframework.cloud.sleuth.instrument.messaging.TraceSpanMessagingAutoConfiguration;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.messaging.simp.config.ChannelRegistration;
|
||||
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
|
||||
import org.springframework.web.socket.config.annotation.DelegatingWebSocketMessageBrokerConfiguration;
|
||||
@@ -53,6 +54,11 @@ public class TraceWebSocketAutoConfiguration
|
||||
// The user must register their own endpoints
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureMessageBroker(MessageBrokerRegistry registry) {
|
||||
registry.configureBrokerChannel().setInterceptors(new TraceChannelInterceptor(this.beanFactory));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureClientOutboundChannel(ChannelRegistration registration) {
|
||||
registration.setInterceptors(new TraceChannelInterceptor(this.beanFactory));
|
||||
|
||||
@@ -6,11 +6,12 @@ import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.cloud.sleuth.Span;
|
||||
import org.springframework.cloud.sleuth.SpanTextMap;
|
||||
import org.springframework.cloud.sleuth.TraceKeys;
|
||||
|
||||
import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then;
|
||||
import static org.assertj.core.api.BDDAssertions.then;
|
||||
|
||||
/**
|
||||
* @author Marcin Grzejszczak
|
||||
@@ -19,7 +20,6 @@ public class HeaderBasedMessagingInjectorTests {
|
||||
|
||||
HeaderBasedMessagingInjector injector = new HeaderBasedMessagingInjector(new TraceKeys());
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void should_not_override_already_existing_headers() throws Exception {
|
||||
Span span = Span.builder()
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2016-2017 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 java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.support.MessageBuilder;
|
||||
import org.springframework.messaging.support.NativeMessageHeaderAccessor;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class MessagingTextMapTests {
|
||||
|
||||
@Test
|
||||
public void vanilla() {
|
||||
MessageBuilder<String> builder = MessageBuilder.withPayload("foo");
|
||||
MessagingTextMap map = new MessagingTextMap(builder);
|
||||
map.put("foo", "bar");
|
||||
Set<String> keys = new HashSet<>();
|
||||
map.forEach(entry -> keys.add(entry.getKey()));
|
||||
assertThat(keys).contains("foo");
|
||||
@SuppressWarnings("unchecked")
|
||||
MultiValueMap<String, String> natives = (MultiValueMap<String, String>) builder.build().getHeaders().get(NativeMessageHeaderAccessor.NATIVE_HEADERS);
|
||||
assertThat(natives).containsKey("foo");
|
||||
assertThat(keys).doesNotContain(NativeMessageHeaderAccessor.NATIVE_HEADERS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nativeHeadersAlreadyExist() {
|
||||
MessageBuilder<String> builder = MessageBuilder.withPayload("foo").setHeader(
|
||||
NativeMessageHeaderAccessor.NATIVE_HEADERS, new LinkedMultiValueMap<>());
|
||||
MessagingTextMap map = new MessagingTextMap(builder);
|
||||
map.put("foo", "bar");
|
||||
Set<String> keys = new HashSet<>();
|
||||
map.forEach(entry -> keys.add(entry.getKey()));
|
||||
assertThat(keys).contains("foo");
|
||||
@SuppressWarnings("unchecked")
|
||||
MultiValueMap<String, String> natives = (MultiValueMap<String, String>) builder.build().getHeaders().get(NativeMessageHeaderAccessor.NATIVE_HEADERS);
|
||||
assertThat(natives).containsKey("foo");
|
||||
assertThat(keys).doesNotContain(NativeMessageHeaderAccessor.NATIVE_HEADERS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nativeHeaders() {
|
||||
Message<String> message = MessageBuilder.withPayload("foo").build();
|
||||
MessageBuilder<String> builder = MessageBuilder.fromMessage(message)
|
||||
.setHeaders(NativeMessageHeaderAccessor.getMutableAccessor(message));
|
||||
MessagingTextMap map = new MessagingTextMap(builder);
|
||||
map.put("foo", "bar");
|
||||
Set<String> keys = new HashSet<>();
|
||||
map.forEach(entry -> keys.add(entry.getKey()));
|
||||
assertThat(keys).contains("foo");
|
||||
@SuppressWarnings("unchecked")
|
||||
MultiValueMap<String, String> natives = (MultiValueMap<String, String>) builder.build().getHeaders().get(NativeMessageHeaderAccessor.NATIVE_HEADERS);
|
||||
assertThat(natives).containsKey("foo");
|
||||
assertThat(keys).doesNotContain(NativeMessageHeaderAccessor.NATIVE_HEADERS);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,11 +18,15 @@ package org.springframework.cloud.sleuth.instrument.messaging;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
@@ -40,11 +44,11 @@ import org.springframework.cloud.sleuth.util.ExceptionUtils;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.integration.channel.DirectChannel;
|
||||
import org.springframework.integration.channel.ExecutorChannel;
|
||||
import org.springframework.integration.core.MessagingTemplate;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.MessageDeliveryException;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
import org.springframework.messaging.MessagingException;
|
||||
import org.springframework.messaging.support.ChannelInterceptor;
|
||||
@@ -55,6 +59,7 @@ import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.util.SerializationUtils;
|
||||
|
||||
import static org.assertj.core.api.BDDAssertions.then;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then;
|
||||
|
||||
@@ -72,6 +77,10 @@ public class TraceChannelInterceptorTests implements MessageHandler {
|
||||
@Qualifier("tracedChannel")
|
||||
private DirectChannel tracedChannel;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("tracedExecutorChannel")
|
||||
private ExecutorChannel executorChannel;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("ignoredChannel")
|
||||
private DirectChannel ignoredChannel;
|
||||
@@ -88,9 +97,12 @@ public class TraceChannelInterceptorTests implements MessageHandler {
|
||||
private Message<?> message;
|
||||
|
||||
private Span span;
|
||||
|
||||
private CountDownLatch latch = new CountDownLatch(1);
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message<?> message) throws MessagingException {
|
||||
this.latch.countDown();
|
||||
this.message = message;
|
||||
this.span = TestSpanContextHolder.getCurrentSpan();
|
||||
if (message.getHeaders().containsKey("THROW_EXCEPTION")) {
|
||||
@@ -101,6 +113,7 @@ public class TraceChannelInterceptorTests implements MessageHandler {
|
||||
@Before
|
||||
public void init() {
|
||||
this.tracedChannel.subscribe(this);
|
||||
this.executorChannel.subscribe(this);
|
||||
this.ignoredChannel.subscribe(this);
|
||||
this.accumulator.getSpans().clear();
|
||||
}
|
||||
@@ -110,6 +123,7 @@ public class TraceChannelInterceptorTests implements MessageHandler {
|
||||
then(ExceptionUtils.getLastException()).isNull();
|
||||
TestSpanContextHolder.removeCurrentSpan();
|
||||
this.tracedChannel.unsubscribe(this);
|
||||
this.executorChannel.unsubscribe(this);
|
||||
this.ignoredChannel.unsubscribe(this);
|
||||
this.accumulator.getSpans().clear();
|
||||
}
|
||||
@@ -126,6 +140,19 @@ public class TraceChannelInterceptorTests implements MessageHandler {
|
||||
then(this.span.isExportable()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void executableSpanCreation() throws Exception {
|
||||
this.executorChannel.send(MessageBuilder.withPayload("hi")
|
||||
.setHeader(TraceMessageHeaders.SAMPLED_NAME, Span.SPAN_NOT_SAMPLED).build());
|
||||
this.latch.await(1, TimeUnit.SECONDS);
|
||||
assertNotNull("message was null", this.message);
|
||||
|
||||
String spanId = this.message.getHeaders().get(TraceMessageHeaders.SPAN_ID_NAME, String.class);
|
||||
then(spanId).isNotNull();
|
||||
then(TestSpanContextHolder.getCurrentSpan()).isNull();
|
||||
then(this.span.isExportable()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void messageHeadersStillMutable() {
|
||||
this.tracedChannel.send(MessageBuilder.withPayload("hi")
|
||||
@@ -351,6 +378,11 @@ public class TraceChannelInterceptorTests implements MessageHandler {
|
||||
return new ArrayListSpanAccumulator();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ExecutorChannel tracedExecutorChannel() {
|
||||
return new ExecutorChannel(Executors.newSingleThreadExecutor());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DirectChannel tracedChannel() {
|
||||
return new DirectChannel();
|
||||
|
||||
@@ -39,20 +39,23 @@ import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
|
||||
* @author Marcin Grzejszczak
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(classes = TraceWebSocketAutoConfigurationTest.Config.class)
|
||||
public class TraceWebSocketAutoConfigurationTest {
|
||||
@SpringBootTest(classes = TraceWebSocketAutoConfigurationTests.Config.class)
|
||||
public class TraceWebSocketAutoConfigurationTests {
|
||||
|
||||
@Autowired
|
||||
DelegatingWebSocketMessageBrokerConfiguration delegatingWebSocketMessageBrokerConfiguration;
|
||||
|
||||
@Test
|
||||
public void should_register_interceptors_for_inbound_and_outbound_channels() {
|
||||
public void should_register_interceptors_for_all_channels() {
|
||||
then(this.delegatingWebSocketMessageBrokerConfiguration.clientInboundChannel()
|
||||
.getInterceptors())
|
||||
.hasAtLeastOneElementOfType(TraceChannelInterceptor.class);
|
||||
then(this.delegatingWebSocketMessageBrokerConfiguration.clientOutboundChannel()
|
||||
.getInterceptors())
|
||||
.hasAtLeastOneElementOfType(TraceChannelInterceptor.class);
|
||||
then(this.delegatingWebSocketMessageBrokerConfiguration.brokerChannel()
|
||||
.getInterceptors())
|
||||
.hasAtLeastOneElementOfType(TraceChannelInterceptor.class);
|
||||
}
|
||||
|
||||
@EnableAutoConfiguration
|
||||
@@ -2,7 +2,7 @@
|
||||
<configuration>
|
||||
<include resource="org/springframework/boot/logging/logback/base.xml"/>
|
||||
<logger name="org.springframework.cloud.sleuth" level="TRACE"/>
|
||||
<logger name="org.springframework.boot.autoconfigure.logging" level="DEBUG"/>
|
||||
<logger name="org.springframework.boot.autoconfigure.logging" level="INFO"/>
|
||||
<logger name="org.springframework.cloud.sleuth.log" level="DEBUG"/>
|
||||
<logger name="org.springframework.cloud.sleuth.trace" level="DEBUG"/>
|
||||
<logger name="org.springframework.cloud.sleuth.instrument.rxjava" level="DEBUG"/>
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
<name>spring-cloud-sleuth-dependencies</name>
|
||||
<description>Spring Cloud Sleuth Dependencies</description>
|
||||
<properties>
|
||||
<zipkin.version>2.2.0</zipkin.version>
|
||||
<zipkin-reporter.version>1.1.1</zipkin-reporter.version>
|
||||
<zipkin-reporter2.version>2.1.1</zipkin-reporter2.version>
|
||||
<zipkin.version>2.2.1</zipkin.version>
|
||||
<zipkin-reporter.version>1.1.2</zipkin-reporter.version>
|
||||
<zipkin-reporter2.version>2.1.3</zipkin-reporter2.version>
|
||||
</properties>
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
@@ -105,6 +105,30 @@
|
||||
<artifactId>zipkin-reporter</artifactId>
|
||||
<version>${zipkin-reporter2.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.zipkin.reporter2</groupId>
|
||||
<artifactId>zipkin-sender-kafka11</artifactId>
|
||||
<version>${zipkin-reporter2.version}</version>
|
||||
<exclusions>
|
||||
<!-- assigned with spring-kafka -->
|
||||
<exclusion>
|
||||
<groupId>org.apache.kafka</groupId>
|
||||
<artifactId>kafka-clients</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.zipkin.reporter2</groupId>
|
||||
<artifactId>zipkin-sender-amqp-client</artifactId>
|
||||
<version>${zipkin-reporter2.version}</version>
|
||||
<exclusions>
|
||||
<!-- assigned with spring-rabbit -->
|
||||
<exclusion>
|
||||
<groupId>com.rabbitmq</groupId>
|
||||
<artifactId>amqp-client</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
<profiles>
|
||||
|
||||
@@ -59,17 +59,17 @@
|
||||
<dependency>
|
||||
<groupId>io.zipkin.java</groupId>
|
||||
<artifactId>zipkin</artifactId>
|
||||
<version>2.2.0</version>
|
||||
<version>2.2.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.zipkin.zipkin2</groupId>
|
||||
<artifactId>zipkin</artifactId>
|
||||
<version>2.2.0</version>
|
||||
<version>2.2.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.zipkin.java</groupId>
|
||||
<artifactId>zipkin-server</artifactId>
|
||||
<version>2.2.0</version>
|
||||
<version>2.2.1</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
@@ -73,6 +73,24 @@
|
||||
<groupId>io.zipkin.reporter2</groupId>
|
||||
<artifactId>zipkin-reporter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.zipkin.reporter2</groupId>
|
||||
<artifactId>zipkin-sender-kafka11</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.kafka</groupId>
|
||||
<artifactId>spring-kafka</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.zipkin.reporter2</groupId>
|
||||
<artifactId>zipkin-sender-amqp-client</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.amqp</groupId>
|
||||
<artifactId>spring-rabbit</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-messaging</artifactId>
|
||||
|
||||
@@ -16,25 +16,17 @@
|
||||
|
||||
package org.springframework.cloud.sleuth.zipkin2;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.autoconfigure.web.ServerProperties;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.discovery.DiscoveryClient;
|
||||
import org.springframework.cloud.client.serviceregistry.Registration;
|
||||
import org.springframework.cloud.commons.util.InetUtils;
|
||||
import org.springframework.cloud.sleuth.Sampler;
|
||||
@@ -44,13 +36,11 @@ import org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration;
|
||||
import org.springframework.cloud.sleuth.metric.SpanMetricReporter;
|
||||
import org.springframework.cloud.sleuth.sampler.PercentageBasedSampler;
|
||||
import org.springframework.cloud.sleuth.sampler.SamplerProperties;
|
||||
import org.springframework.cloud.sleuth.zipkin2.sender.ZipkinSenderConfigurationImportSelector;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.web.client.RequestCallback;
|
||||
import org.springframework.web.client.ResponseExtractor;
|
||||
import org.springframework.web.client.RestClientException;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import zipkin2.Span;
|
||||
import zipkin2.reporter.AsyncReporter;
|
||||
@@ -77,10 +67,10 @@ import zipkin2.reporter.Sender;
|
||||
@EnableConfigurationProperties({ZipkinProperties.class, SamplerProperties.class})
|
||||
@ConditionalOnProperty(value = "spring.zipkin.enabled", matchIfMissing = true)
|
||||
@AutoConfigureBefore(TraceAutoConfiguration.class)
|
||||
@Import(ZipkinSenderConfigurationImportSelector.class)
|
||||
public class ZipkinAutoConfiguration {
|
||||
|
||||
@Autowired(required = false) List<SpanAdjuster> spanAdjusters = new ArrayList<>();
|
||||
@Autowired ZipkinUrlExtractor extractor;
|
||||
|
||||
/**
|
||||
* Accepts a sender so you can plug-in any standard one. Returns a Reporter so you can also
|
||||
@@ -100,55 +90,6 @@ public class ZipkinAutoConfiguration {
|
||||
.build(zipkin.getEncoder());
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public Sender restTemplateSender(ZipkinProperties zipkin,
|
||||
ZipkinRestTemplateCustomizer zipkinRestTemplateCustomizer) {
|
||||
RestTemplate restTemplate = new ZipkinRestTemplateWrapper(zipkin, this.extractor);
|
||||
zipkinRestTemplateCustomizer.customize(restTemplate);
|
||||
return new RestTemplateSender(restTemplate, zipkin.getBaseUrl(), zipkin.getEncoder());
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnClass(DiscoveryClient.class)
|
||||
static class DiscoveryClientZipkinUrlExtractorConfiguration {
|
||||
|
||||
@Autowired(required = false) DiscoveryClient discoveryClient;
|
||||
|
||||
@Bean
|
||||
ZipkinUrlExtractor zipkinUrlExtractor() {
|
||||
final DiscoveryClient discoveryClient = this.discoveryClient;
|
||||
return new ZipkinUrlExtractor() {
|
||||
@Override
|
||||
public URI zipkinUrl(ZipkinProperties zipkinProperties) {
|
||||
if (discoveryClient != null) {
|
||||
URI uri = URI.create(zipkinProperties.getBaseUrl());
|
||||
String host = uri.getHost();
|
||||
List<ServiceInstance> instances = discoveryClient.getInstances(host);
|
||||
if (!instances.isEmpty()) {
|
||||
return instances.get(0).getUri();
|
||||
}
|
||||
}
|
||||
return URI.create(zipkinProperties.getBaseUrl());
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnMissingClass("org.springframework.cloud.client.discovery.DiscoveryClient")
|
||||
static class DefaultZipkinUrlExtractorConfiguration {
|
||||
@Bean
|
||||
ZipkinUrlExtractor zipkinUrlExtractor() {
|
||||
return new ZipkinUrlExtractor() {
|
||||
@Override
|
||||
public URI zipkinUrl(ZipkinProperties zipkinProperties) {
|
||||
return URI.create(zipkinProperties.getBaseUrl());
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public ZipkinRestTemplateCustomizer zipkinRestTemplateCustomizer(ZipkinProperties zipkinProperties) {
|
||||
@@ -164,7 +105,7 @@ public class ZipkinAutoConfiguration {
|
||||
@Bean
|
||||
public SpanReporter zipkinSpanListener(Reporter<Span> reporter, EndpointLocator endpointLocator,
|
||||
Environment environment) {
|
||||
return new ZipkinSpanListener(reporter, endpointLocator, environment, this.spanAdjusters);
|
||||
return new ZipkinSpanReporter(reporter, endpointLocator, environment, this.spanAdjusters);
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@@ -220,52 +161,3 @@ public class ZipkinAutoConfiguration {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal interface to provide a way to retrieve Zipkin URI. If there's no discovery client
|
||||
* then this value will be taken from the properties. Otherwise host will be assumed to
|
||||
* be a service id.
|
||||
*/
|
||||
interface ZipkinUrlExtractor {
|
||||
URI zipkinUrl(ZipkinProperties zipkinProperties);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves at runtime where the Zipkin server is. If there's no discovery client then
|
||||
* {@link URI} from the properties is taken. Otherwise service discovery is pinged
|
||||
* for current Zipkin address.
|
||||
*/
|
||||
class ZipkinRestTemplateWrapper extends RestTemplate {
|
||||
|
||||
private static final Log log = LogFactory.getLog(ZipkinRestTemplateWrapper.class);
|
||||
|
||||
private final ZipkinProperties zipkinProperties;
|
||||
private final ZipkinUrlExtractor extractor;
|
||||
|
||||
ZipkinRestTemplateWrapper(ZipkinProperties zipkinProperties,
|
||||
ZipkinUrlExtractor extractor) {
|
||||
this.zipkinProperties = zipkinProperties;
|
||||
this.extractor = extractor;
|
||||
}
|
||||
|
||||
@Override protected <T> T doExecute(URI originalUrl, HttpMethod method,
|
||||
RequestCallback requestCallback,
|
||||
ResponseExtractor<T> responseExtractor) throws RestClientException {
|
||||
URI uri = this.extractor.zipkinUrl(this.zipkinProperties);
|
||||
URI newUri = resolvedZipkinUri(originalUrl, uri);
|
||||
return super.doExecute(newUri, method, requestCallback, responseExtractor);
|
||||
}
|
||||
|
||||
private URI resolvedZipkinUri(URI originalUrl, URI resolvedZipkinUri) {
|
||||
try {
|
||||
return new URI(resolvedZipkinUri.getScheme(), resolvedZipkinUri.getUserInfo(),
|
||||
resolvedZipkinUri.getHost(), resolvedZipkinUri.getPort(), originalUrl.getPath(),
|
||||
originalUrl.getQuery(), originalUrl.getFragment());
|
||||
} catch (URISyntaxException e) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Failed to create the new URI from original [" + originalUrl + "] and new one [" + resolvedZipkinUri + "]");
|
||||
}
|
||||
return originalUrl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,9 +32,9 @@ import zipkin2.reporter.Reporter;
|
||||
/**
|
||||
* Listener of Sleuth events. Reports to Zipkin via {@link Reporter}.
|
||||
*/
|
||||
public class ZipkinSpanListener implements SpanReporter {
|
||||
public class ZipkinSpanReporter implements SpanReporter {
|
||||
private static final org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory
|
||||
.getLog(ZipkinSpanListener.class);
|
||||
.getLog(ZipkinSpanReporter.class);
|
||||
|
||||
private final Reporter<zipkin2.Span> reporter;
|
||||
private final Environment environment;
|
||||
@@ -46,7 +46,7 @@ public class ZipkinSpanListener implements SpanReporter {
|
||||
// Visible for testing
|
||||
final EndpointLocator endpointLocator;
|
||||
|
||||
public ZipkinSpanListener(Reporter<zipkin2.Span> reporter, EndpointLocator endpointLocator,
|
||||
public ZipkinSpanReporter(Reporter<zipkin2.Span> reporter, EndpointLocator endpointLocator,
|
||||
Environment environment, List<SpanAdjuster> spanAdjusters) {
|
||||
this.reporter = reporter;
|
||||
this.endpointLocator = endpointLocator;
|
||||
@@ -202,4 +202,9 @@ public class ZipkinSpanListener implements SpanReporter {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(){
|
||||
return "ZipkinSpanReporter(" + this.reporter + ")";
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.springframework.cloud.sleuth.zipkin2;
|
||||
package org.springframework.cloud.sleuth.zipkin2.sender;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
@@ -0,0 +1,51 @@
|
||||
package org.springframework.cloud.sleuth.zipkin2.sender;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.apache.kafka.common.serialization.ByteArraySerializer;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
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.kafka.KafkaProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import zipkin2.reporter.Sender;
|
||||
import zipkin2.reporter.kafka11.KafkaSender;
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnClass(ByteArraySerializer.class)
|
||||
@ConditionalOnBean(KafkaProperties.class)
|
||||
@ConditionalOnMissingBean(Sender.class)
|
||||
@Conditional(ZipkinSenderCondition.class)
|
||||
class ZipkinKafkaSenderConfiguration {
|
||||
@Value("${spring.zipkin.kafka.topic:zipkin}")
|
||||
private String topic;
|
||||
|
||||
@Bean Sender kafkaSender(KafkaProperties config) {
|
||||
Map<String, Object> properties = config.buildProducerProperties();
|
||||
properties.put("key.serializer", ByteArraySerializer.class.getName());
|
||||
properties.put("value.serializer", ByteArraySerializer.class.getName());
|
||||
// Kafka expects the input to be a String, but KafkaProperties returns a list
|
||||
Object bootstrapServers = properties.get("bootstrap.servers");
|
||||
if (bootstrapServers instanceof List) {
|
||||
properties.put("bootstrap.servers", join((List) bootstrapServers));
|
||||
}
|
||||
return KafkaSender.newBuilder()
|
||||
.topic(this.topic)
|
||||
.overrides(properties)
|
||||
.build();
|
||||
}
|
||||
|
||||
static String join(List<?> parts) {
|
||||
StringBuilder to = new StringBuilder();
|
||||
for (int i = 0, length = parts.size(); i < length; i++) {
|
||||
to.append(parts.get(i));
|
||||
if (i + 1 < length) {
|
||||
to.append(',');
|
||||
}
|
||||
}
|
||||
return to.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package org.springframework.cloud.sleuth.zipkin2.sender;
|
||||
|
||||
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.amqp.RabbitProperties;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import zipkin2.reporter.Sender;
|
||||
import zipkin2.reporter.amqp.RabbitMQSender;
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnBean(CachingConnectionFactory.class)
|
||||
@ConditionalOnMissingBean(Sender.class)
|
||||
@Conditional(ZipkinSenderCondition.class)
|
||||
class ZipkinRabbitSenderConfiguration {
|
||||
@Value("${spring.zipkin.rabbitmq.queue:zipkin}")
|
||||
private String queue;
|
||||
|
||||
@Bean Sender rabbitSender(CachingConnectionFactory connectionFactory, RabbitProperties config) {
|
||||
return RabbitMQSender.newBuilder()
|
||||
.connectionFactory(connectionFactory.getRabbitConnectionFactory())
|
||||
.queue(this.queue)
|
||||
.addresses(config.determineAddresses())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
package org.springframework.cloud.sleuth.zipkin2.sender;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.List;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.discovery.DiscoveryClient;
|
||||
import org.springframework.cloud.sleuth.zipkin2.ZipkinProperties;
|
||||
import org.springframework.cloud.sleuth.zipkin2.ZipkinRestTemplateCustomizer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.web.client.RequestCallback;
|
||||
import org.springframework.web.client.ResponseExtractor;
|
||||
import org.springframework.web.client.RestClientException;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import zipkin2.reporter.Sender;
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnMissingBean(Sender.class)
|
||||
@Conditional(ZipkinSenderCondition.class)
|
||||
class ZipkinRestTemplateSenderConfiguration {
|
||||
@Autowired ZipkinUrlExtractor extractor;
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public Sender restTemplateSender(ZipkinProperties zipkin,
|
||||
ZipkinRestTemplateCustomizer zipkinRestTemplateCustomizer) {
|
||||
RestTemplate restTemplate = new ZipkinRestTemplateWrapper(zipkin, this.extractor);
|
||||
zipkinRestTemplateCustomizer.customize(restTemplate);
|
||||
return new RestTemplateSender(restTemplate, zipkin.getBaseUrl(), zipkin.getEncoder());
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnMissingClass("org.springframework.cloud.client.discovery.DiscoveryClient")
|
||||
static class DefaultZipkinUrlExtractorConfiguration {
|
||||
@Bean
|
||||
ZipkinUrlExtractor zipkinUrlExtractor() {
|
||||
return new ZipkinUrlExtractor() {
|
||||
@Override
|
||||
public URI zipkinUrl(ZipkinProperties zipkinProperties) {
|
||||
return URI.create(zipkinProperties.getBaseUrl());
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnClass(DiscoveryClient.class)
|
||||
static class DiscoveryClientZipkinUrlExtractorConfiguration {
|
||||
|
||||
@Autowired(required = false) DiscoveryClient discoveryClient;
|
||||
|
||||
@Bean
|
||||
ZipkinUrlExtractor zipkinUrlExtractor() {
|
||||
final DiscoveryClient discoveryClient = this.discoveryClient;
|
||||
return new ZipkinUrlExtractor() {
|
||||
@Override
|
||||
public URI zipkinUrl(ZipkinProperties zipkinProperties) {
|
||||
if (discoveryClient != null) {
|
||||
URI uri = URI.create(zipkinProperties.getBaseUrl());
|
||||
String host = uri.getHost();
|
||||
List<ServiceInstance> instances = discoveryClient.getInstances(host);
|
||||
if (!instances.isEmpty()) {
|
||||
return instances.get(0).getUri();
|
||||
}
|
||||
}
|
||||
return URI.create(zipkinProperties.getBaseUrl());
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves at runtime where the Zipkin server is. If there's no discovery client then {@link URI}
|
||||
* from the properties is taken. Otherwise service discovery is pinged for current Zipkin address.
|
||||
*/
|
||||
class ZipkinRestTemplateWrapper extends RestTemplate {
|
||||
|
||||
private static final Log log = LogFactory.getLog(ZipkinRestTemplateWrapper.class);
|
||||
|
||||
private final ZipkinProperties zipkinProperties;
|
||||
private final ZipkinUrlExtractor extractor;
|
||||
|
||||
ZipkinRestTemplateWrapper(ZipkinProperties zipkinProperties,
|
||||
ZipkinUrlExtractor extractor) {
|
||||
this.zipkinProperties = zipkinProperties;
|
||||
this.extractor = extractor;
|
||||
}
|
||||
|
||||
@Override protected <T> T doExecute(URI originalUrl, HttpMethod method,
|
||||
RequestCallback requestCallback,
|
||||
ResponseExtractor<T> responseExtractor) throws RestClientException {
|
||||
URI uri = this.extractor.zipkinUrl(this.zipkinProperties);
|
||||
URI newUri = resolvedZipkinUri(originalUrl, uri);
|
||||
return super.doExecute(newUri, method, requestCallback, responseExtractor);
|
||||
}
|
||||
|
||||
private URI resolvedZipkinUri(URI originalUrl, URI resolvedZipkinUri) {
|
||||
try {
|
||||
return new URI(resolvedZipkinUri.getScheme(), resolvedZipkinUri.getUserInfo(),
|
||||
resolvedZipkinUri.getHost(), resolvedZipkinUri.getPort(), originalUrl.getPath(),
|
||||
originalUrl.getQuery(), originalUrl.getFragment());
|
||||
} catch (URISyntaxException e) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Failed to create the new URI from original ["
|
||||
+ originalUrl
|
||||
+ "] and new one ["
|
||||
+ resolvedZipkinUri
|
||||
+ "]");
|
||||
}
|
||||
return originalUrl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal interface to provide a way to retrieve Zipkin URI. If there's no discovery client then
|
||||
* this value will be taken from the properties. Otherwise host will be assumed to be a service id.
|
||||
*/
|
||||
interface ZipkinUrlExtractor {
|
||||
URI zipkinUrl(ZipkinProperties zipkinProperties);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package org.springframework.cloud.sleuth.zipkin2.sender;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
|
||||
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
|
||||
import org.springframework.boot.bind.RelaxedPropertyResolver;
|
||||
import org.springframework.context.annotation.ConditionContext;
|
||||
import org.springframework.core.type.AnnotatedTypeMetadata;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.core.type.ClassMetadata;
|
||||
|
||||
import static org.springframework.cloud.sleuth.zipkin2.sender.ZipkinSenderConfigurationImportSelector.getType;
|
||||
|
||||
/** Attach this to any new sender configuration. */
|
||||
class ZipkinSenderCondition extends SpringBootCondition {
|
||||
|
||||
@Override
|
||||
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata md) {
|
||||
String sourceClass = "";
|
||||
if (md instanceof ClassMetadata) {
|
||||
sourceClass = ((ClassMetadata) md).getClassName();
|
||||
}
|
||||
ConditionMessage.Builder message = ConditionMessage.forCondition("ZipkinSender", sourceClass);
|
||||
RelaxedPropertyResolver resolver = new RelaxedPropertyResolver(
|
||||
context.getEnvironment(), "spring.zipkin.sender.");
|
||||
if (!resolver.containsProperty("type")) {
|
||||
return ConditionOutcome.match(message.because("automatic sender type"));
|
||||
}
|
||||
|
||||
String senderType = getType(((AnnotationMetadata) md).getClassName());
|
||||
String value = resolver.getProperty("type");
|
||||
if (value.equals(senderType)) {
|
||||
return ConditionOutcome.match(message.because(value + " sender type"));
|
||||
}
|
||||
return ConditionOutcome.noMatch(message.because(value + " sender type"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package org.springframework.cloud.sleuth.zipkin2.sender;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import org.springframework.context.annotation.ImportSelector;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
|
||||
public class ZipkinSenderConfigurationImportSelector implements ImportSelector {
|
||||
|
||||
static final Map<String, String> MAPPINGS;
|
||||
|
||||
// Classes below must be annotated with @Conditional(ZipkinSenderCondition.class)
|
||||
static {
|
||||
// Mappings in descending priority (highest is last)
|
||||
Map<String, String> mappings = new LinkedHashMap<>();
|
||||
mappings.put("rabbit", ZipkinRabbitSenderConfiguration.class.getName());
|
||||
mappings.put("kafka", ZipkinKafkaSenderConfiguration.class.getName());
|
||||
mappings.put("web", ZipkinRestTemplateSenderConfiguration.class.getName());
|
||||
MAPPINGS = Collections.unmodifiableMap(mappings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
|
||||
return MAPPINGS.values().toArray(new String[0]);
|
||||
}
|
||||
|
||||
static String getType(String configurationClassName) {
|
||||
for (Map.Entry<String, String> entry : MAPPINGS.entrySet()) {
|
||||
if (entry.getValue().equals(configurationClassName)) {
|
||||
return entry.getKey();
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException("Unknown configuration class " + configurationClassName);
|
||||
}
|
||||
}
|
||||
@@ -23,13 +23,18 @@ import org.junit.After;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.kafka.KafkaProperties;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.cloud.sleuth.Span;
|
||||
import org.springframework.cloud.sleuth.SpanReporter;
|
||||
import org.springframework.cloud.sleuth.metric.TraceMetricsAutoConfiguration;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import zipkin2.reporter.amqp.RabbitMQSender;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.BDDAssertions.then;
|
||||
import static org.springframework.boot.test.util.EnvironmentTestUtils.addEnvironment;
|
||||
|
||||
@@ -92,4 +97,78 @@ public class ZipkinAutoConfigurationTests {
|
||||
then(request.getPath()).isEqualTo("/api/v1/spans");
|
||||
then(request.getBody().readUtf8()).contains("binaryAnnotations");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void overrideRabbitMQQueue() throws Exception {
|
||||
context = new AnnotationConfigApplicationContext();
|
||||
addEnvironment(context, "spring.zipkin.rabbitmq.queue:zipkin2");
|
||||
context.register(
|
||||
PropertyPlaceholderAutoConfiguration.class,
|
||||
TraceMetricsAutoConfiguration.class,
|
||||
RabbitAutoConfiguration.class,
|
||||
ZipkinAutoConfiguration.class);
|
||||
context.refresh();
|
||||
|
||||
SpanReporter spanReporter = context.getBean(SpanReporter.class);
|
||||
assertThat(spanReporter).extracting("reporter.sender.queue")
|
||||
.contains("zipkin2");
|
||||
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void overrideKafkaTopic() throws Exception {
|
||||
context = new AnnotationConfigApplicationContext();
|
||||
addEnvironment(context, "spring.zipkin.kafka.topic:zipkin2");
|
||||
context.register(
|
||||
PropertyPlaceholderAutoConfiguration.class,
|
||||
TraceMetricsAutoConfiguration.class,
|
||||
KafkaAutoConfiguration.class,
|
||||
ZipkinAutoConfiguration.class);
|
||||
context.refresh();
|
||||
|
||||
SpanReporter spanReporter = context.getBean(SpanReporter.class);
|
||||
assertThat(spanReporter).extracting("reporter.sender.topic")
|
||||
.contains("zipkin2");
|
||||
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canOverrideBySender() throws Exception {
|
||||
context = new AnnotationConfigApplicationContext();
|
||||
addEnvironment(context, "spring.zipkin.sender.type:web");
|
||||
context.register(
|
||||
PropertyPlaceholderAutoConfiguration.class,
|
||||
TraceMetricsAutoConfiguration.class,
|
||||
RabbitAutoConfiguration.class,
|
||||
KafkaAutoConfiguration.class,
|
||||
ZipkinAutoConfiguration.class);
|
||||
context.refresh();
|
||||
|
||||
SpanReporter spanReporter = context.getBean(SpanReporter.class);
|
||||
assertThat(spanReporter).extracting("reporter.sender").allSatisfy(
|
||||
s -> assertThat(s.getClass().getSimpleName()).isEqualTo("RestTemplateSender")
|
||||
);
|
||||
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rabbitWinsWhenKafkaPresent() throws Exception {
|
||||
context = new AnnotationConfigApplicationContext();
|
||||
context.register(
|
||||
PropertyPlaceholderAutoConfiguration.class,
|
||||
TraceMetricsAutoConfiguration.class,
|
||||
RabbitAutoConfiguration.class,
|
||||
KafkaAutoConfiguration.class,
|
||||
ZipkinAutoConfiguration.class);
|
||||
context.refresh();
|
||||
|
||||
SpanReporter spanReporter = context.getBean(SpanReporter.class);
|
||||
assertThat(spanReporter).extracting("reporter.sender")
|
||||
.allSatisfy(s -> assertThat(s).isInstanceOf(RabbitMQSender.class));
|
||||
|
||||
context.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,9 @@ import zipkin.junit.ZipkinRule;
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(classes = ZipkinDiscoveryClientTests.Config.class,
|
||||
properties = {"spring.zipkin.baseUrl=http://zipkin/",
|
||||
"spring.cloud.discovery.client.composite-indicator.enabled=false"})
|
||||
"spring.cloud.discovery.client.composite-indicator.enabled=false",
|
||||
"spring.zipkin.sender.type=web" // override default priority which picks rabbit due to classpath
|
||||
})
|
||||
public class ZipkinDiscoveryClientTests {
|
||||
|
||||
@ClassRule public static ZipkinRule ZIPKIN_RULE = new ZipkinRule();
|
||||
@@ -112,4 +114,4 @@ class ZipkinDiscoveryClient implements DiscoveryClient {
|
||||
return Collections.singletonList("zipkin");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ import org.springframework.cloud.sleuth.SpanAdjuster;
|
||||
import org.springframework.cloud.sleuth.SpanReporter;
|
||||
import org.springframework.cloud.sleuth.Tracer;
|
||||
import org.springframework.cloud.sleuth.sampler.AlwaysSampler;
|
||||
import org.springframework.cloud.sleuth.zipkin2.ZipkinSpanListenerTests.TestConfiguration;
|
||||
import org.springframework.cloud.sleuth.zipkin2.ZipkinSpanReporterTests.TestConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
@@ -52,12 +52,12 @@ import static org.junit.Assert.assertEquals;
|
||||
*/
|
||||
@SpringBootTest(classes = TestConfiguration.class)
|
||||
@RunWith(SpringRunner.class)
|
||||
public class ZipkinSpanListenerTests {
|
||||
public class ZipkinSpanReporterTests {
|
||||
|
||||
@Autowired Tracer tracer;
|
||||
@Autowired TestConfiguration test;
|
||||
@Autowired ZipkinSpanListener spanListener;
|
||||
@Autowired Reporter<zipkin2.Span> spanReporter;
|
||||
@Autowired ZipkinSpanReporter spanReporter;
|
||||
@Autowired Reporter<zipkin2.Span> zipkinReporter;
|
||||
@Autowired MockEnvironment mockEnvironment;
|
||||
@Autowired EndpointLocator endpointLocator;
|
||||
|
||||
@@ -76,7 +76,7 @@ public class ZipkinSpanListenerTests {
|
||||
span.logEvent("hystrix/retry"); // System.currentTimeMillis
|
||||
span.stop();
|
||||
|
||||
zipkin2.Span result = this.spanListener.convert(span);
|
||||
zipkin2.Span result = this.spanReporter.convert(span);
|
||||
|
||||
assertThat(result.timestamp())
|
||||
.isEqualTo(span.getBegin() * 1000);
|
||||
@@ -97,7 +97,7 @@ public class ZipkinSpanListenerTests {
|
||||
Thread.sleep(20);
|
||||
span.stop();
|
||||
|
||||
zipkin2.Span result = this.spanListener.convert(span);
|
||||
zipkin2.Span result = this.spanReporter.convert(span);
|
||||
|
||||
assertThat(result.timestamp()).isEqualTo(span.getBegin() * 1000);
|
||||
long clientSendTimestamp = span.logs().stream()
|
||||
@@ -114,7 +114,7 @@ public class ZipkinSpanListenerTests {
|
||||
@Test
|
||||
public void doesntSetDurationWhenStillRunning() {
|
||||
Span span = Span.builder().traceId(1L).name("http:api").build();
|
||||
zipkin2.Span result = this.spanListener.convert(span);
|
||||
zipkin2.Span result = this.spanReporter.convert(span);
|
||||
|
||||
assertThat(result.timestamp())
|
||||
.isGreaterThan(0); // sanity check it did start
|
||||
@@ -128,16 +128,16 @@ public class ZipkinSpanListenerTests {
|
||||
this.parent.logEvent("hystrix/retry");
|
||||
this.parent.tag("spring-boot/version", "1.3.1.RELEASE");
|
||||
|
||||
zipkin2.Span result = this.spanListener.convert(this.parent);
|
||||
zipkin2.Span result = this.spanReporter.convert(this.parent);
|
||||
|
||||
assertThat(result.localEndpoint())
|
||||
.isEqualTo(this.spanListener.endpointLocator.local());
|
||||
.isEqualTo(this.spanReporter.endpointLocator.local());
|
||||
}
|
||||
|
||||
/** zipkin's Endpoint.serviceName should never be null. */
|
||||
@Test
|
||||
public void localEndpointIncludesServiceName() {
|
||||
assertThat(this.spanListener.endpointLocator.local().serviceName())
|
||||
assertThat(this.spanReporter.endpointLocator.local().serviceName())
|
||||
.isNotEmpty();
|
||||
}
|
||||
|
||||
@@ -155,7 +155,7 @@ public class ZipkinSpanListenerTests {
|
||||
Span context = this.tracer.createSpan("http:child", this.parent);
|
||||
context.logEvent(Span.CLIENT_SEND);
|
||||
logServerReceived(this.parent);
|
||||
logServerSent(this.spanListener, this.parent);
|
||||
logServerSent(this.spanReporter, this.parent);
|
||||
this.tracer.close(context);
|
||||
assertEquals(2, this.test.zipkinSpans.size());
|
||||
}
|
||||
@@ -178,7 +178,7 @@ public class ZipkinSpanListenerTests {
|
||||
this.parent.logEvent("hystrix/retry");
|
||||
this.parent.stop();
|
||||
|
||||
zipkin2.Span result = this.spanListener.convert(this.parent);
|
||||
zipkin2.Span result = this.spanReporter.convert(this.parent);
|
||||
|
||||
assertThat(result.tags())
|
||||
.isEmpty();
|
||||
@@ -190,7 +190,7 @@ public class ZipkinSpanListenerTests {
|
||||
this.parent.tag(Span.SPAN_PEER_SERVICE_TAG_NAME, "fooservice");
|
||||
this.parent.stop();
|
||||
|
||||
zipkin2.Span result = this.spanListener.convert(this.parent);
|
||||
zipkin2.Span result = this.spanReporter.convert(this.parent);
|
||||
|
||||
assertThat(result.remoteEndpoint())
|
||||
.isEqualTo(Endpoint.newBuilder().serviceName("fooservice").build());
|
||||
@@ -201,7 +201,7 @@ public class ZipkinSpanListenerTests {
|
||||
this.parent.logEvent("cs");
|
||||
this.parent.stop();
|
||||
|
||||
zipkin2.Span result = this.spanListener.convert(this.parent);
|
||||
zipkin2.Span result = this.spanReporter.convert(this.parent);
|
||||
|
||||
assertThat(result.remoteEndpoint())
|
||||
.isNull();
|
||||
@@ -211,7 +211,7 @@ public class ZipkinSpanListenerTests {
|
||||
public void converts128BitTraceId() {
|
||||
Span span = Span.builder().traceIdHigh(1L).traceId(2L).spanId(3L).name("foo").build();
|
||||
|
||||
zipkin2.Span result = this.spanListener.convert(span);
|
||||
zipkin2.Span result = this.spanReporter.convert(span);
|
||||
|
||||
assertThat(result.traceId())
|
||||
.isEqualTo("00000000000000010000000000000002");
|
||||
@@ -223,7 +223,7 @@ public class ZipkinSpanListenerTests {
|
||||
this.parent.tag(Span.SPAN_PEER_SERVICE_TAG_NAME, "fooservice");
|
||||
this.parent.stop();
|
||||
|
||||
zipkin2.Span result = this.spanListener.convert(this.parent);
|
||||
zipkin2.Span result = this.spanReporter.convert(this.parent);
|
||||
|
||||
assertThat(result.remoteEndpoint())
|
||||
.isEqualTo(Endpoint.newBuilder().serviceName("fooservice").build());
|
||||
@@ -233,7 +233,7 @@ public class ZipkinSpanListenerTests {
|
||||
public void shouldNotReportToZipkinWhenSpanIsNotExportable() {
|
||||
Span span = Span.builder().exportable(false).build();
|
||||
|
||||
this.spanListener.report(span);
|
||||
this.spanReporter.report(span);
|
||||
|
||||
assertThat(this.test.zipkinSpans).isEmpty();
|
||||
}
|
||||
@@ -243,7 +243,7 @@ public class ZipkinSpanListenerTests {
|
||||
this.parent.logEvent(Span.CLIENT_SEND);
|
||||
this.mockEnvironment.setProperty("vcap.application.instance_id", "foo");
|
||||
|
||||
zipkin2.Span result = this.spanListener.convert(this.parent);
|
||||
zipkin2.Span result = this.spanReporter.convert(this.parent);
|
||||
|
||||
assertThat(result.tags())
|
||||
.containsExactly(entry(Span.INSTANCEID, "foo"));
|
||||
@@ -252,7 +252,7 @@ public class ZipkinSpanListenerTests {
|
||||
@Test
|
||||
public void shouldNotAddAnyServiceIdTagWhenSpanContainsRpcEventAndThereIsNoEnvironment() {
|
||||
this.parent.logEvent(Span.CLIENT_RECV);
|
||||
ZipkinSpanListener spanListener = new ZipkinSpanListener(this.spanReporter,
|
||||
ZipkinSpanReporter spanListener = new ZipkinSpanReporter(this.zipkinReporter,
|
||||
this.endpointLocator, null, new ArrayList<>());
|
||||
|
||||
zipkin2.Span result = spanListener.convert(this.parent);
|
||||
@@ -264,7 +264,7 @@ public class ZipkinSpanListenerTests {
|
||||
@Test
|
||||
public void should_adjust_span_before_reporting_it() {
|
||||
this.parent.logEvent(Span.CLIENT_RECV);
|
||||
ZipkinSpanListener spanListener = new ZipkinSpanListener(this.spanReporter,
|
||||
ZipkinSpanReporter spanListener = new ZipkinSpanReporter(this.zipkinReporter,
|
||||
this.endpointLocator, null, Collections.<SpanAdjuster>singletonList(
|
||||
span -> Span.builder().from(span).name("foo").build())) {
|
||||
@Override String defaultInstanceId() {
|
||||
@@ -288,7 +288,7 @@ public class ZipkinSpanListenerTests {
|
||||
.build();
|
||||
span.stop();
|
||||
|
||||
zipkin2.Span result = this.spanListener.convert(span);
|
||||
zipkin2.Span result = this.spanReporter.convert(span);
|
||||
|
||||
assertThat(result.duration()).isNotNull();
|
||||
assertThat(result.timestamp()).isNotNull();
|
||||
@@ -302,7 +302,7 @@ public class ZipkinSpanListenerTests {
|
||||
span.logEvent("ss");
|
||||
span.stop();
|
||||
|
||||
zipkin2.Span result = this.spanListener.convert(span);
|
||||
zipkin2.Span result = this.spanReporter.convert(span);
|
||||
|
||||
then(result.kind()).isEqualTo(zipkin2.Span.Kind.SERVER);
|
||||
then(result.annotations()).isEmpty();
|
||||
@@ -315,7 +315,7 @@ public class ZipkinSpanListenerTests {
|
||||
span.logEvent("cr");
|
||||
span.stop();
|
||||
|
||||
zipkin2.Span result = this.spanListener.convert(span);
|
||||
zipkin2.Span result = this.spanReporter.convert(span);
|
||||
|
||||
then(result.kind()).isEqualTo(zipkin2.Span.Kind.CLIENT);
|
||||
then(result.annotations()).isEmpty();
|
||||
@@ -327,7 +327,7 @@ public class ZipkinSpanListenerTests {
|
||||
span.logEvent("ms");
|
||||
span.stop();
|
||||
|
||||
zipkin2.Span result = this.spanListener.convert(span);
|
||||
zipkin2.Span result = this.spanReporter.convert(span);
|
||||
|
||||
then(result.kind()).isEqualTo(zipkin2.Span.Kind.PRODUCER);
|
||||
then(result.annotations()).isEmpty();
|
||||
@@ -339,7 +339,7 @@ public class ZipkinSpanListenerTests {
|
||||
span.logEvent("mr");
|
||||
span.stop();
|
||||
|
||||
zipkin2.Span result = this.spanListener.convert(span);
|
||||
zipkin2.Span result = this.spanReporter.convert(span);
|
||||
|
||||
then(result.kind()).isEqualTo(zipkin2.Span.Kind.CONSUMER);
|
||||
then(result.annotations()).isEmpty();
|
||||
@@ -364,7 +364,7 @@ public class ZipkinSpanListenerTests {
|
||||
}
|
||||
// end::service_name[]
|
||||
|
||||
zipkin2.Span result = this.spanListener.convert(span);
|
||||
zipkin2.Span result = this.spanReporter.convert(span);
|
||||
|
||||
then(result.remoteEndpoint())
|
||||
.isEqualTo(Endpoint.newBuilder().serviceName("redis").ip("1.2.3.4").port(1234).build());
|
||||
Reference in New Issue
Block a user