Adding the spring.instance_id tag

without this tag it's impossible to discern from which server was the given span originated
    with this change we're adding a tag in which we're passing the instance id value. The value can be taken either from Cloud Foundry or from a concatanation of some local properties like instance_id / application name / application port etc.

    fixes #369
This commit is contained in:
Marcin Grzejszczak
2017-01-03 14:20:56 +01:00
parent f085aae3b6
commit 9d8b2a279f
9 changed files with 207 additions and 57 deletions

View File

@@ -36,6 +36,10 @@
<artifactId>spring-boot-starter-actuator</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-commons</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>

View File

@@ -134,6 +134,11 @@ public class Span {
*/
public static final String SPAN_PEER_SERVICE_TAG_NAME = "peer.service";
/**
* ID of the instance from which the span was originated.
*/
public static final String INSTANCEID = "spring.instance_id";
private final long begin;
private long end = 0;
private final String name;

View File

@@ -183,6 +183,7 @@ public class DefaultTracerTests {
then(span).hasATag("key", "value").hasLoggedAnEvent("event");
then(continuedSpan).hasATag("key", "value").hasLoggedAnEvent("event");
then(span).isEqualTo(continuedSpan);
tracer.close(span);
}
private Span assertSpan(List<Span> spans, Long parentId, String name) {

View File

@@ -37,6 +37,7 @@ import org.springframework.cloud.stream.config.ChannelBindingAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.env.Environment;
import org.springframework.integration.config.GlobalChannelInterceptor;
import org.springframework.integration.scheduling.PollerMetadata;
import org.springframework.messaging.support.ChannelInterceptor;
@@ -76,8 +77,8 @@ public class SleuthStreamAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public StreamSpanReporter sleuthStreamSpanReporter(HostLocator endpointLocator,
SpanMetricReporter spanMetricReporter) {
return new StreamSpanReporter(endpointLocator, spanMetricReporter);
SpanMetricReporter spanMetricReporter, Environment environment) {
return new StreamSpanReporter(endpointLocator, spanMetricReporter, environment);
}
@Bean(name = StreamSpanReporter.POLLER)

View File

@@ -17,17 +17,20 @@
package org.springframework.cloud.sleuth.stream;
import java.lang.invoke.MethodHandles;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.commons.util.IdUtils;
import org.springframework.cloud.sleuth.Log;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.SpanReporter;
import org.springframework.cloud.sleuth.metric.SpanMetricReporter;
import org.springframework.core.env.Environment;
import org.springframework.integration.annotation.InboundChannelAdapter;
import org.springframework.integration.annotation.MessageEndpoint;
import org.springframework.integration.annotation.Poller;
@@ -41,7 +44,11 @@ import org.springframework.integration.annotation.Poller;
@MessageEndpoint
public class StreamSpanReporter implements SpanReporter {
private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass());
private static final org.apache.commons.logging.Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass());
private static final List<String> RPC_EVENTS = Arrays.asList(
Span.CLIENT_RECV, Span.CLIENT_SEND, Span.SERVER_RECV, Span.SERVER_SEND
);
/**
* Bean name for the
@@ -53,10 +60,19 @@ public class StreamSpanReporter implements SpanReporter {
private BlockingQueue<Span> queue = new LinkedBlockingQueue<>(1000);
private final HostLocator endpointLocator;
private final SpanMetricReporter spanMetricReporter;
private final Environment environment;
public StreamSpanReporter(HostLocator endpointLocator, SpanMetricReporter spanMetricReporter) {
@Deprecated
public StreamSpanReporter(HostLocator endpointLocator,
SpanMetricReporter spanMetricReporter) {
this(endpointLocator, spanMetricReporter, null);
}
public StreamSpanReporter(HostLocator endpointLocator,
SpanMetricReporter spanMetricReporter, Environment environment) {
this.endpointLocator = endpointLocator;
this.spanMetricReporter = spanMetricReporter;
this.environment = environment;
}
public void setQueue(BlockingQueue<Span> queue) {
@@ -84,6 +100,9 @@ public class StreamSpanReporter implements SpanReporter {
public void report(Span span) {
if (span.isExportable()) {
try {
if (this.environment != null) {
processLogs(span);
}
this.queue.add(span);
} catch (Exception e) {
this.spanMetricReporter.incrementDroppedSpans(1);
@@ -97,4 +116,13 @@ public class StreamSpanReporter implements SpanReporter {
}
}
}
private void processLogs(Span span) {
for (Log spanLog : span.logs()) {
if (RPC_EVENTS.contains(spanLog.getEvent())) {
span.tag(Span.INSTANCEID, IdUtils.getDefaultInstanceId(this.environment));
}
}
}
}

View File

@@ -16,27 +16,34 @@
package org.springframework.cloud.sleuth.stream;
import java.util.Map;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.Mockito;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.metric.SpanMetricReporter;
import org.springframework.mock.env.MockEnvironment;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.then;
/**
* @author Marcin Grzejszczak
*/
@RunWith(MockitoJUnitRunner.class)
public class StreamSpanReporterTests {
@Mock HostLocator endpointLocator;
@Mock SpanMetricReporter spanMetricReporter;
@InjectMocks StreamSpanReporter reporter;
HostLocator endpointLocator = Mockito.mock(HostLocator.class);
SpanMetricReporter spanMetricReporter = Mockito.mock(SpanMetricReporter.class);
MockEnvironment mockEnvironment = new MockEnvironment();
StreamSpanReporter reporter;
@Before
public void setup() {
this.reporter = new StreamSpanReporter(this.endpointLocator, this.spanMetricReporter, this.mockEnvironment);
}
@Test
public void should_not_throw_an_exception_when_queue_size_is_exceeded() throws Exception {
@@ -46,7 +53,43 @@ public class StreamSpanReporterTests {
this.reporter.report(Span.builder().name("bar").exportable(true).build());
then(spanMetricReporter).should().incrementDroppedSpans(1);
then(this.spanMetricReporter).should().incrementDroppedSpans(1);
}
@Test
@SuppressWarnings("unchecked")
public void should_append_client_serviceid_when_span_has_rpc_event() throws Exception {
LinkedBlockingQueue<Span> queue = new LinkedBlockingQueue<>(1000);
this.reporter.setQueue(queue);
this.mockEnvironment.setProperty("vcap.application.instance_id", "foo");
Span span = Span.builder().name("bar").exportable(true).build();
span.logEvent(Span.CLIENT_RECV);
this.reporter.report(span);
assertThat(queue).isNotEmpty();
assertThat(queue.poll())
.extracting(Span::tags)
.extracting(o -> ((Map<String, String>) o).get(Span.INSTANCEID))
.containsExactly("foo");
}
@Test
@SuppressWarnings("unchecked")
public void should_not_append_server_serviceid_when_span_has_rpc_event_and_there_is_no_environment() throws Exception {
this.reporter = new StreamSpanReporter(this.endpointLocator, this.spanMetricReporter, null);
LinkedBlockingQueue<Span> queue = new LinkedBlockingQueue<>(1000);
this.reporter.setQueue(queue);
Span span = Span.builder().name("bar").exportable(true).build();
span.logEvent(Span.CLIENT_SEND);
this.reporter.report(span);
assertThat(queue).isNotEmpty();
assertThat(queue.poll())
.extracting(Span::tags)
.filteredOn(o -> ((Map<String, String>) o).containsKey(Span.INSTANCEID))
.isNullOrEmpty();
}
}

View File

@@ -34,7 +34,7 @@ import org.springframework.cloud.sleuth.sampler.PercentageBasedSampler;
import org.springframework.cloud.sleuth.sampler.SamplerProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
/**
* {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration Auto-configuration}
@@ -64,8 +64,9 @@ public class ZipkinAutoConfiguration {
}
@Bean
public SpanReporter zipkinSpanListener(ZipkinSpanReporter reporter, EndpointLocator endpointLocator) {
return new ZipkinSpanListener(reporter, endpointLocator);
public SpanReporter zipkinSpanListener(ZipkinSpanReporter reporter, EndpointLocator endpointLocator,
Environment environment) {
return new ZipkinSpanListener(reporter, endpointLocator, environment);
}
@Configuration

View File

@@ -21,9 +21,11 @@ import java.util.Arrays;
import java.util.List;
import java.util.Map;
import org.springframework.cloud.commons.util.IdUtils;
import org.springframework.cloud.sleuth.Log;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.SpanReporter;
import org.springframework.core.env.Environment;
import org.springframework.util.StringUtils;
import zipkin.Annotation;
@@ -41,13 +43,17 @@ public class ZipkinSpanListener implements SpanReporter {
private static final List<String> ZIPKIN_START_EVENTS = Arrays.asList(
Constants.CLIENT_RECV, Constants.SERVER_RECV
);
private static final List<String> RPC_EVENTS = Arrays.asList(
Constants.CLIENT_RECV, Constants.CLIENT_SEND, Constants.SERVER_RECV, Constants.SERVER_SEND
);
private static final org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory
.getLog(ZipkinSpanListener.class);
private static final Charset UTF_8 = Charset.forName("UTF-8");
private static final byte[] UNKNOWN_BYTES = "unknown".getBytes(UTF_8);
private ZipkinSpanReporter reporter;
private final ZipkinSpanReporter reporter;
private final Environment environment;
/**
* Endpoint is the visible IP address of this service, the port it is listening on and
* the service name from discovery.
@@ -55,9 +61,16 @@ public class ZipkinSpanListener implements SpanReporter {
// Visible for testing
EndpointLocator endpointLocator;
@Deprecated
public ZipkinSpanListener(ZipkinSpanReporter reporter, EndpointLocator endpointLocator) {
this(reporter, endpointLocator, null);
}
public ZipkinSpanListener(ZipkinSpanReporter reporter, EndpointLocator endpointLocator,
Environment environment) {
this.reporter = reporter;
this.endpointLocator = endpointLocator;
this.environment = environment;
}
/**
@@ -76,15 +89,10 @@ public class ZipkinSpanListener implements SpanReporter {
zipkin.Span convert(Span span) {
zipkin.Span.Builder zipkinSpan = zipkin.Span.builder();
// A zipkin span without any annotations cannot be queried, add special "lc" to avoid that.
if (notClientOrServer(span)) {
ensureLocalComponent(span, zipkinSpan);
}
addZipkinAnnotations(zipkinSpan, span, this.endpointLocator.local());
addZipkinBinaryAnnotations(zipkinSpan, span, this.endpointLocator.local());
if (hasClientSend(span)) {
ensureServerAddr(span, zipkinSpan);
}
Endpoint endpoint = this.endpointLocator.local();
processLogs(span, zipkinSpan, endpoint);
addZipkinAnnotations(zipkinSpan, span, endpoint);
addZipkinBinaryAnnotations(zipkinSpan, span, endpoint);
// In the RPC span model, the client owns the timestamp and duration of the span. If we
// were propagated an id, we can assume that we shouldn't report timestamp or duration,
// rather let the client do that. Worst case we were propagated an unreported ID and
@@ -134,22 +142,40 @@ public class ZipkinSpanListener implements SpanReporter {
}
}
private boolean notClientOrServer(Span span) {
// Instead of going through the list of logs multiple times we're doing it only once
private void processLogs(Span span, zipkin.Span.Builder zipkinSpan, Endpoint endpoint) {
boolean notClientOrServer = true;
boolean hasClientSend = false;
boolean instanceIdToTag = false;
for (Log log : span.logs()) {
if (RPC_EVENTS.contains(log.getEvent())) {
instanceIdToTag = true;
}
if (ZIPKIN_START_EVENTS.contains(log.getEvent())) {
return false;
notClientOrServer = false;
}
if (Constants.CLIENT_SEND.equals(log.getEvent())) {
hasClientSend = !span.tags().containsKey(Constants.SERVER_ADDR);
}
}
return true;
if (notClientOrServer) {
// A zipkin span without any annotations cannot be queried, add special "lc" to avoid that.
ensureLocalComponent(span, zipkinSpan);
}
if (hasClientSend) {
ensureServerAddr(span, zipkinSpan);
}
if (instanceIdToTag && this.environment != null) {
setInstanceIdIfPresent(zipkinSpan, endpoint, Span.INSTANCEID);
}
}
private boolean hasClientSend(Span span) {
for (Log log : span.logs()) {
if (Constants.CLIENT_SEND.equals(log.getEvent())) {
return !span.tags().containsKey(Constants.SERVER_ADDR);
}
private void setInstanceIdIfPresent(zipkin.Span.Builder zipkinSpan,
Endpoint endpoint, String key) {
String property = IdUtils.getDefaultInstanceId(this.environment);
if (StringUtils.hasText(property)) {
addZipkinBinaryAnnotation(key, property, endpoint, zipkinSpan);
}
return false;
}
/**
@@ -172,15 +198,20 @@ public class ZipkinSpanListener implements SpanReporter {
private void addZipkinBinaryAnnotations(zipkin.Span.Builder zipkinSpan,
Span span, Endpoint ep) {
for (Map.Entry<String, String> e : span.tags().entrySet()) {
BinaryAnnotation binaryAnn = BinaryAnnotation.builder()
.type(BinaryAnnotation.Type.STRING)
.key(e.getKey())
.value(e.getValue().getBytes(UTF_8))
.endpoint(ep).build();
zipkinSpan.addBinaryAnnotation(binaryAnn);
addZipkinBinaryAnnotation(e.getKey(), e.getValue(), ep, zipkinSpan);
}
}
private void addZipkinBinaryAnnotation(String key, String value, Endpoint ep,
zipkin.Span.Builder zipkinSpan) {
BinaryAnnotation binaryAnn = BinaryAnnotation.builder()
.type(BinaryAnnotation.Type.STRING)
.key(key)
.value(value.getBytes(UTF_8))
.endpoint(ep).build();
zipkinSpan.addBinaryAnnotation(binaryAnn);
}
/**
* There could be instrumentation delay between span creation and the
* semantic start of the span (client send). When there's a difference,

View File

@@ -34,6 +34,8 @@ import org.springframework.cloud.sleuth.zipkin.ZipkinSpanListenerTests.TestConfi
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.mock.env.MockEnvironment;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import zipkin.Constants;
@@ -52,7 +54,10 @@ public class ZipkinSpanListenerTests {
@Autowired Tracer tracer;
@Autowired ApplicationContext application;
@Autowired TestConfiguration test;
@Autowired ZipkinSpanListener spanReporter;
@Autowired ZipkinSpanListener spanListener;
@Autowired ZipkinSpanReporter spanReporter;
@Autowired MockEnvironment mockEnvironment;
@Autowired EndpointLocator endpointLocator;
@PostConstruct
public void init() {
@@ -69,7 +74,7 @@ public class ZipkinSpanListenerTests {
span.logEvent("hystrix/retry"); // System.currentTimeMillis
span.stop();
zipkin.Span result = this.spanReporter.convert(span);
zipkin.Span result = this.spanListener.convert(span);
assertThat(result.timestamp)
.isEqualTo(span.getBegin() * 1000);
@@ -90,7 +95,7 @@ public class ZipkinSpanListenerTests {
Thread.sleep(20);
span.stop();
zipkin.Span result = this.spanReporter.convert(span);
zipkin.Span result = this.spanListener.convert(span);
assertThat(result.timestamp)
.isEqualTo(span.getBegin() * 1000);
@@ -107,7 +112,7 @@ public class ZipkinSpanListenerTests {
@Test
public void doesntSetDurationWhenStillRunning() {
Span span = Span.builder().traceId(1L).name("http:api").build();
zipkin.Span result = this.spanReporter.convert(span);
zipkin.Span result = this.spanListener.convert(span);
assertThat(result.timestamp)
.isGreaterThan(0); // sanity check it did start
@@ -124,7 +129,7 @@ public class ZipkinSpanListenerTests {
@Test
public void doesntSetTimestampOrDurationWhenRemote() {
this.parent.stop();
zipkin.Span result = this.spanReporter.convert(this.parent);
zipkin.Span result = this.spanListener.convert(this.parent);
assertThat(result.timestamp)
.isNull();
@@ -138,10 +143,10 @@ public class ZipkinSpanListenerTests {
this.parent.logEvent("hystrix/retry");
this.parent.tag("spring-boot/version", "1.3.1.RELEASE");
zipkin.Span result = this.spanReporter.convert(this.parent);
zipkin.Span result = this.spanListener.convert(this.parent);
assertThat(result.annotations.get(0).endpoint)
.isEqualTo(this.spanReporter.endpointLocator.local());
.isEqualTo(this.spanListener.endpointLocator.local());
assertThat(result.binaryAnnotations.get(0).endpoint)
.isEqualTo(result.annotations.get(0).endpoint);
}
@@ -149,7 +154,7 @@ public class ZipkinSpanListenerTests {
/** zipkin's Endpoint.serviceName should never be null. */
@Test
public void localEndpointIncludesServiceName() {
assertThat(this.spanReporter.endpointLocator.local().serviceName)
assertThat(this.spanListener.endpointLocator.local().serviceName)
.isNotEmpty();
}
@@ -172,7 +177,7 @@ public class ZipkinSpanListenerTests {
Span context = this.tracer.createSpan("http:child", this.parent);
context.logEvent(Span.CLIENT_SEND);
logServerReceived(this.parent);
logServerSent(this.spanReporter, this.parent);
logServerSent(this.spanListener, this.parent);
this.tracer.close(context);
assertEquals(2, this.test.zipkinSpans.size());
}
@@ -195,7 +200,7 @@ public class ZipkinSpanListenerTests {
this.parent.logEvent("hystrix/retry");
this.parent.stop();
zipkin.Span result = this.spanReporter.convert(this.parent);
zipkin.Span result = this.spanListener.convert(this.parent);
assertThat(result.binaryAnnotations)
.extracting(input -> input.key)
@@ -208,7 +213,7 @@ public class ZipkinSpanListenerTests {
this.parent.tag(Span.SPAN_PEER_SERVICE_TAG_NAME, "fooservice");
this.parent.stop();
zipkin.Span result = this.spanReporter.convert(this.parent);
zipkin.Span result = this.spanListener.convert(this.parent);
assertThat(result.binaryAnnotations)
.filteredOn("key", Constants.SERVER_ADDR)
@@ -220,7 +225,7 @@ public class ZipkinSpanListenerTests {
this.parent.logEvent(Constants.CLIENT_SEND);
this.parent.stop();
zipkin.Span result = this.spanReporter.convert(this.parent);
zipkin.Span result = this.spanListener.convert(this.parent);
assertThat(result.binaryAnnotations)
.filteredOn("key", Constants.SERVER_ADDR)
@@ -231,7 +236,7 @@ public class ZipkinSpanListenerTests {
public void converts128BitTraceId() {
Span span = Span.builder().traceIdHigh(1L).traceId(2L).spanId(3L).name("foo").build();
zipkin.Span result = this.spanReporter.convert(span);
zipkin.Span result = this.spanListener.convert(span);
assertThat(result.traceIdHigh).isEqualTo(span.getTraceIdHigh());
assertThat(result.traceId).isEqualTo(span.getTraceId());
@@ -243,7 +248,7 @@ public class ZipkinSpanListenerTests {
this.parent.tag(Span.SPAN_PEER_SERVICE_TAG_NAME, "fooservice");
this.parent.stop();
zipkin.Span result = this.spanReporter.convert(this.parent);
zipkin.Span result = this.spanListener.convert(this.parent);
assertThat(result.binaryAnnotations)
.filteredOn("key", Constants.SERVER_ADDR)
@@ -255,11 +260,38 @@ public class ZipkinSpanListenerTests {
public void shouldNotReportToZipkinWhenSpanIsNotExportable() {
Span span = Span.builder().exportable(false).build();
this.spanReporter.report(span);
this.spanListener.report(span);
assertThat(this.test.zipkinSpans).isEmpty();
}
@Test
public void shouldAddClientServiceIdTagWhenSpanContainsRpcEvent() {
this.parent.logEvent(Span.CLIENT_SEND);
this.mockEnvironment.setProperty("vcap.application.instance_id", "foo");
zipkin.Span result = this.spanListener.convert(this.parent);
assertThat(result.binaryAnnotations)
.filteredOn("key", Span.INSTANCEID)
.extracting(input -> input.value)
.containsOnly("foo".getBytes());
}
@Test
public void shouldNotAddAnyServiceIdTagWhenSpanContainsRpcEventAndThereIsNoEnvironment() {
this.parent.logEvent(Span.CLIENT_RECV);
ZipkinSpanListener spanListener = new ZipkinSpanListener(this.spanReporter,
this.endpointLocator, null);
zipkin.Span result = spanListener.convert(this.parent);
assertThat(result.binaryAnnotations)
.filteredOn("key", Span.INSTANCEID)
.extracting(input -> input.value)
.isEmpty();
}
@Configuration
@EnableAutoConfiguration
protected static class TestConfiguration {
@@ -276,6 +308,10 @@ public class ZipkinSpanListenerTests {
return this.zipkinSpans::add;
}
@Bean @Primary MockEnvironment mockEnvironment() {
return new MockEnvironment();
}
}
}