Merge pull request #195 from spring-cloud/issues_#191_missing_span_name

Issues #191 missing span name
This commit is contained in:
Marcin Grzejszczak
2016-03-02 15:18:14 +01:00
5 changed files with 211 additions and 27 deletions

View File

@@ -64,6 +64,10 @@ public class Span {
public static final String SPAN_ID_NAME = "X-Span-Id";
public static final String SPAN_EXPORT_NAME = "X-Span-Export";
public static final String SPAN_LOCAL_COMPONENT_TAG_NAME = "lc";
/**
* <a href="https://github.com/opentracing/opentracing-go/blob/master/ext/tags.go">As in Open Tracing</a>
*/
public static final String SPAN_PEER_SERVICE_TAG_NAME = "peer.service";
private final long begin;
private long end = 0;

View File

@@ -15,7 +15,9 @@
*/
package org.springframework.cloud.sleuth.zipkin.stream;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import org.apache.commons.logging.Log;
@@ -24,6 +26,7 @@ import org.springframework.cloud.sleuth.stream.Host;
import org.springframework.cloud.sleuth.stream.SleuthSink;
import org.springframework.cloud.sleuth.stream.Spans;
import org.springframework.util.StringUtils;
import zipkin.BinaryAnnotation;
import zipkin.Constants;
import zipkin.Endpoint;
@@ -38,6 +41,9 @@ import zipkin.Span.Builder;
* @since 1.0.0
*/
final class SamplingZipkinSpanIterator implements Iterator<zipkin.Span> {
private static final List<String> ZIPKIN_START_EVENTS = Arrays.asList(
Constants.CLIENT_RECV, Constants.SERVER_RECV
);
private static final Log log = org.apache.commons.logging.LogFactory
.getLog(SamplingZipkinSpanIterator.class);
@@ -99,6 +105,10 @@ final class SamplingZipkinSpanIterator implements Iterator<zipkin.Span> {
* <li>Create timeline annotations based on data from Span object.
* <li>Create binary annotations based on data from Span object.
* </ul>
*
* When logging {@link Constants#CLIENT_SEND}, instrumentation should also log the {@link Constants#SERVER_ADDR}
* Check <a href="https://github.com/openzipkin/zipkin-java/blob/master/zipkin/src/main/java/zipkin/Constants.java#L28">Zipkin code</a>
* for more information
*/
// VisibleForTesting
static zipkin.Span convert(Span span, Host host) {
@@ -109,18 +119,14 @@ final class SamplingZipkinSpanIterator implements Iterator<zipkin.Span> {
// A zipkin span without any annotations cannot be queried, add special "lc" to
// avoid that.
if (span.logs().isEmpty() && span.tags().isEmpty()) {
String processId = span.getProcessId() != null
? span.getProcessId().toLowerCase()
: ZipkinMessageListener.UNKNOWN_PROCESS_ID;
zipkinSpan.addBinaryAnnotation(
BinaryAnnotation.create(Constants.LOCAL_COMPONENT, processId, ep));
if (notClientOrServer(span)) {
ensureLocalComponent(span, zipkinSpan, ep);
}
else {
ZipkinMessageListener.addZipkinAnnotations(zipkinSpan, span, ep);
ZipkinMessageListener.addZipkinBinaryAnnotations(zipkinSpan, span, ep);
ZipkinMessageListener.addZipkinAnnotations(zipkinSpan, span, ep);
ZipkinMessageListener.addZipkinBinaryAnnotations(zipkinSpan, span, ep);
if (hasClientSend(span)) {
ensureServerAddr(span, zipkinSpan, ep);
}
zipkinSpan.timestamp(span.getBegin() * 1000);
zipkinSpan.duration(span.getAccumulatedMillis() * 1000);
zipkinSpan.traceId(span.getTraceId());
@@ -138,4 +144,43 @@ final class SamplingZipkinSpanIterator implements Iterator<zipkin.Span> {
}
return zipkinSpan.build();
}
private static void ensureLocalComponent(Span span, Builder zipkinSpan,
Endpoint ep) {
if (span.tags().containsKey(Constants.LOCAL_COMPONENT)) {
return;
}
String processId = span.getProcessId() != null
? span.getProcessId().toLowerCase()
: ZipkinMessageListener.UNKNOWN_PROCESS_ID;
zipkinSpan.addBinaryAnnotation(
BinaryAnnotation.create(Constants.LOCAL_COMPONENT, processId, ep));
}
private static void ensureServerAddr(Span span, zipkin.Span.Builder zipkinSpan,
Endpoint ep) {
String serviceName = span.tags().containsKey(Span.SPAN_PEER_SERVICE_TAG_NAME) ?
span.tags().get(Span.SPAN_PEER_SERVICE_TAG_NAME) : ep.serviceName;
zipkinSpan.addBinaryAnnotation(BinaryAnnotation.address(Constants.SERVER_ADDR,
Endpoint.create(serviceName, ep.ipv4, ep.port)));
}
private static boolean notClientOrServer(Span span) {
for (org.springframework.cloud.sleuth.Log log : span.logs()) {
if (ZIPKIN_START_EVENTS.contains(log.getEvent())) {
return false;
}
}
return true;
}
private static boolean hasClientSend(Span span) {
for (org.springframework.cloud.sleuth.Log log : span.logs()) {
if (Constants.CLIENT_SEND.equals(log.getEvent())) {
return !span.tags().containsKey(Constants.SERVER_ADDR);
}
}
return false;
}
}

View File

@@ -25,6 +25,8 @@ import org.junit.Test;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.stream.Host;
import org.springframework.cloud.sleuth.stream.Spans;
import zipkin.Constants;
import zipkin.Sampler;
import static org.assertj.core.api.Assertions.assertThat;
@@ -76,6 +78,54 @@ public class SamplingZipkinSpanIteratorTests {
"message:foo", "message:baz");
}
@Test
public void appendsLocalComponentTagIfNoZipkinLogIsPresent() {
Spans spans = new Spans(this.host, Collections.singletonList(span("foo")));
Iterator<zipkin.Span> result = new SamplingZipkinSpanIterator(
Sampler.create(1.0f), spans);
assertThat(result)
.flatExtracting(s -> s.binaryAnnotations)
.extracting(input -> input.key)
.contains(Constants.LOCAL_COMPONENT);
}
@Test
public void appendsServerAddressTagIfClientLogIsPresent() {
Span span = span("foo");
span.logEvent(Constants.CLIENT_SEND);
Spans spans = new Spans(this.host, Collections.singletonList(span));
Iterator<zipkin.Span> result = new SamplingZipkinSpanIterator(
Sampler.create(1.0f), spans);
assertThat(result)
.hasSize(1)
.flatExtracting(input1 -> input1.binaryAnnotations)
.filteredOn("key", Constants.SERVER_ADDR)
.extracting(input -> input.endpoint.serviceName)
.contains("myservice");
}
@Test
public void shouldReuseServerAddressTag() {
Span span = span("foo");
span.logEvent(Constants.CLIENT_SEND);
span.tag(Span.SPAN_PEER_SERVICE_TAG_NAME, "barservice");
Spans spans = new Spans(this.host, Collections.singletonList(span));
Iterator<zipkin.Span> result = new SamplingZipkinSpanIterator(
Sampler.create(1.0f), spans);
assertThat(result)
.hasSize(1)
.flatExtracting(input1 -> input1.binaryAnnotations)
.filteredOn("key", Constants.SERVER_ADDR)
.extracting(input -> input.endpoint.serviceName)
.contains("barservice");
}
Span span(String name) {
Long id = new Random().nextLong();
return new Span(1, 3, "message:" + name, id, Collections.<Long>emptyList(), id, true, true,

View File

@@ -17,6 +17,8 @@
package org.springframework.cloud.sleuth.zipkin;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import org.springframework.cloud.sleuth.Log;
@@ -44,6 +46,10 @@ import zipkin.Endpoint;
* @since 1.0.0
*/
public class ZipkinSpanListener {
private static final List<String> ZIPKIN_START_EVENTS = Arrays.asList(
Constants.CLIENT_RECV, Constants.SERVER_RECV
);
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");
@@ -122,27 +128,24 @@ public class ZipkinSpanListener {
* <li>Create timeline annotations based on data from Span object.
* <li>Create binary annotations based on data from Span object.
* </ul>
*
* When logging {@link Constants#CLIENT_SEND}, instrumentation should also log the {@link Constants#SERVER_ADDR}
* Check <a href="https://github.com/openzipkin/zipkin-java/blob/master/zipkin/src/main/java/zipkin/Constants.java#L28">Zipkin code</a>
* for more information
*/
// Visible for testing
zipkin.Span convert(Span span) {
zipkin.Span.Builder zipkinSpan = new zipkin.Span.Builder();
// A zipkin span without any annotations cannot be queried, add special "lc" to avoid that.
if (span.logs().isEmpty() && span.tags().isEmpty()) {
byte[] processId = span.getProcessId() != null
? span.getProcessId().toLowerCase().getBytes(UTF_8)
: UNKNOWN_BYTES;
BinaryAnnotation component = new BinaryAnnotation.Builder()
.type(BinaryAnnotation.Type.STRING)
.key("lc") // LOCAL_COMPONENT
.value(processId)
.endpoint(this.localEndpoint).build();
zipkinSpan.addBinaryAnnotation(component);
} else {
addZipkinAnnotations(zipkinSpan, span, this.localEndpoint);
addZipkinBinaryAnnotations(zipkinSpan, span, this.localEndpoint);
if (notClientOrServer(span)) {
ensureLocalComponent(span, zipkinSpan);
}
addZipkinAnnotations(zipkinSpan, span, this.localEndpoint);
addZipkinBinaryAnnotations(zipkinSpan, span, this.localEndpoint);
if (hasClientSend(span)) {
ensureServerAddr(span, zipkinSpan);
}
zipkinSpan.timestamp(span.getBegin() * 1000L);
zipkinSpan.duration(span.getAccumulatedMillis() * 1000L);
zipkinSpan.traceId(span.getTraceId());
@@ -160,6 +163,46 @@ public class ZipkinSpanListener {
return zipkinSpan.build();
}
private void ensureLocalComponent(Span span, zipkin.Span.Builder zipkinSpan) {
if (span.tags().containsKey(Constants.LOCAL_COMPONENT)) {
return;
}
byte[] processId = span.getProcessId() != null
? span.getProcessId().toLowerCase().getBytes(UTF_8)
: UNKNOWN_BYTES;
BinaryAnnotation component = new BinaryAnnotation.Builder()
.type(BinaryAnnotation.Type.STRING)
.key("lc") // LOCAL_COMPONENT
.value(processId)
.endpoint(this.localEndpoint).build();
zipkinSpan.addBinaryAnnotation(component);
}
private void ensureServerAddr(Span span, zipkin.Span.Builder zipkinSpan) {
String serviceName = span.tags().containsKey(Span.SPAN_PEER_SERVICE_TAG_NAME) ?
span.tags().get(Span.SPAN_PEER_SERVICE_TAG_NAME) : this.localEndpoint.serviceName;
zipkinSpan.addBinaryAnnotation(BinaryAnnotation.address(Constants.SERVER_ADDR,
Endpoint.create(serviceName, this.localEndpoint.ipv4, this.localEndpoint.port)));
}
private boolean notClientOrServer(Span span) {
for (Log log : span.logs()) {
if (ZIPKIN_START_EVENTS.contains(log.getEvent())) {
return false;
}
}
return true;
}
private boolean hasClientSend(Span span) {
for (org.springframework.cloud.sleuth.Log log : span.logs()) {
if (Constants.CLIENT_SEND.equals(log.getEvent())) {
return !span.tags().containsKey(Constants.SERVER_ADDR);
}
}
return false;
}
/**
* Add annotations from the sleuth Span.
*/
@@ -178,13 +221,13 @@ public class ZipkinSpanListener {
* Adds binary annotation from the sleuth Span
*/
private void addZipkinBinaryAnnotations(zipkin.Span.Builder zipkinSpan,
Span span, Endpoint endpoint) {
Span span, Endpoint ep) {
for (Map.Entry<String, String> e : span.tags().entrySet()) {
BinaryAnnotation binaryAnn = new BinaryAnnotation.Builder()
.type(BinaryAnnotation.Type.STRING)
.key(e.getKey())
.value(e.getValue().getBytes(UTF_8))
.endpoint(endpoint).build();
.endpoint(ep).build();
zipkinSpan.addBinaryAnnotation(binaryAnn);
}
}

View File

@@ -16,10 +16,11 @@
package org.springframework.cloud.sleuth.zipkin;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.PostConstruct;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
@@ -41,6 +42,8 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import zipkin.Constants;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals;
@@ -135,6 +138,45 @@ public class ZipkinSpanListenerTests {
assertEquals(2, this.test.spans.size());
}
@Test
public void appendsLocalComponentTagIfNoZipkinLogIsPresent() {
this.parent.logEvent("hystrix/retry");
this.parent.stop();
zipkin.Span result = this.listener.convert(this.parent);
assertThat(result.binaryAnnotations)
.extracting(input -> input.key)
.contains(Constants.LOCAL_COMPONENT);
}
@Test
public void appendsServerAddressTagIfClientLogIsPresent() {
this.parent.logEvent(Constants.CLIENT_SEND);
this.parent.stop();
zipkin.Span result = this.listener.convert(this.parent);
assertThat(result.binaryAnnotations)
.filteredOn("key", Constants.SERVER_ADDR)
.extracting(input -> input.endpoint.serviceName)
.containsOnly("unknown");
}
@Test
public void shouldReuseServerAddressTag() {
this.parent.logEvent(Constants.CLIENT_SEND);
this.parent.tag(Span.SPAN_PEER_SERVICE_TAG_NAME, "fooservice");
this.parent.stop();
zipkin.Span result = this.listener.convert(this.parent);
assertThat(result.binaryAnnotations)
.filteredOn("key", Constants.SERVER_ADDR)
.extracting(input -> input.endpoint.serviceName)
.containsOnly("fooservice");
}
@Configuration
@Import({ ZipkinTestConfiguration.class, ZipkinAutoConfiguration.class, TraceAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class })