diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/MilliSpan.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/MilliSpan.java index 858060afb..3a361e350 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/MilliSpan.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/MilliSpan.java @@ -1,5 +1,7 @@ package org.springframework.cloud.sleuth; +import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -22,10 +24,10 @@ public class MilliSpan implements Span { @Singular private List parents; private String spanId; - private Map kVAnnotations; + private Map kVAnnotations = new LinkedHashMap<>(); private String processId; @Singular - private List timelineAnnotations; + private List timelineAnnotations = new ArrayList<>(); @Override public synchronized void stop() { diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceFilter.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceFilter.java index 65b34ea9f..f43913d87 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceFilter.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceFilter.java @@ -51,7 +51,7 @@ import org.springframework.web.util.UrlPathHelper; public class TraceFilter extends OncePerRequestFilter { public static final Pattern DEFAULT_SKIP_PATTERN = Pattern - .compile("/api-docs.*|/autoconfig|/configprops|/dump|/info|/metrics.*|/mappings|/trace|/swagger.*|.*\\.png|.*\\.css|.*\\.js|.*\\.html"); + .compile("/api-docs.*|/autoconfig|/configprops|/dump|/info|/metrics.*|/mappings|/trace|/swagger.*|.*\\.png|.*\\.css|.*\\.js|.*\\.html|/favicon.ico"); private final Trace trace; private final Pattern skipPattern; @@ -79,7 +79,7 @@ public class TraceFilter extends OncePerRequestFilter { if (!skip) { String spanId = getHeader(request, response, SPAN_ID_NAME); String traceId = getHeader(request, response, TRACE_ID_NAME); - String name = "traceFilter" + this.urlPathHelper.getPathWithinApplication(request); + String name = this.urlPathHelper.getPathWithinApplication(request); if (hasText(spanId) && hasText(traceId)) { TraceInfo traceInfo = new TraceInfo(traceId, spanId); diff --git a/spring-cloud-sleuth-sample/pom.xml b/spring-cloud-sleuth-sample/pom.xml index 854965d41..eb0ae237b 100644 --- a/spring-cloud-sleuth-sample/pom.xml +++ b/spring-cloud-sleuth-sample/pom.xml @@ -56,6 +56,10 @@ org.springframework.boot spring-boot-starter-aop + + org.springframework.boot + spring-boot-starter-actuator + org.projectlombok lombok diff --git a/spring-cloud-sleuth-sample/src/main/java/org/springframework/cloud/sleuth/sample/SampleApplication.java b/spring-cloud-sleuth-sample/src/main/java/org/springframework/cloud/sleuth/sample/SampleApplication.java index b115ee441..9a0f08aec 100644 --- a/spring-cloud-sleuth-sample/src/main/java/org/springframework/cloud/sleuth/sample/SampleApplication.java +++ b/spring-cloud-sleuth-sample/src/main/java/org/springframework/cloud/sleuth/sample/SampleApplication.java @@ -1,5 +1,7 @@ package org.springframework.cloud.sleuth.sample; +import com.github.kristofa.brave.LoggingSpanCollectorImpl; +import com.github.kristofa.brave.SpanCollector; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.cloud.sleuth.Sampler; @@ -34,9 +36,6 @@ public class SampleApplication { SpringApplication.run(SampleApplication.class, args); } - /* - * @Bean public SpanCollector spanCollector() { return new LoggingSpanCollectorImpl(); - * } - */ + //@Bean public SpanCollector spanCollector() { return new LoggingSpanCollectorImpl(); } } diff --git a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/SleuthTracer.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/SleuthTracer.java new file mode 100644 index 000000000..9a9682255 --- /dev/null +++ b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/SleuthTracer.java @@ -0,0 +1,206 @@ +package org.springframework.cloud.sleuth.zipkin; + +import java.io.UnsupportedEncodingException; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import lombok.extern.apachecommons.CommonsLog; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.web.ServerProperties; +import org.springframework.cloud.sleuth.Span; +import org.springframework.cloud.sleuth.TimelineAnnotation; + +import com.github.kristofa.brave.SpanCollector; +import com.twitter.zipkin.gen.Annotation; +import com.twitter.zipkin.gen.AnnotationType; +import com.twitter.zipkin.gen.BinaryAnnotation; +import com.twitter.zipkin.gen.Endpoint; +import com.twitter.zipkin.gen.zipkinCoreConstants; +import org.springframework.cloud.sleuth.event.SpanStoppedEvent; +import org.springframework.context.event.EventListener; + +/** + * @author Spencer Gibb + */ +@CommonsLog +public class SleuthTracer { + + private SpanCollector spanCollector; + @Value("${spring.application.name:application}") + private String appName; + @Autowired + private ServerProperties serverProperties; + + public SleuthTracer(SpanCollector spanCollector) { + this.spanCollector = spanCollector; + } + + @EventListener + public void start(SpanStoppedEvent event) { + this.spanCollector.collect(convert(event.getSpan())); + } + + /** + * Converts a given HTrace span to a Zipkin Span. + *
    + *
  • First set the start annotation. [CS, SR], depending whether it is a client service or not. + *
  • Set other id's, etc [TraceId's etc] + *
  • Create binary annotations based on data from HTrace Span object. + *
  • Set the last annotation. [SS, CR] + *
+ */ + public com.twitter.zipkin.gen.Span convert(Span span) { + com.twitter.zipkin.gen.Span zipkinSpan = new com.twitter.zipkin.gen.Span(); + + String serviceName = getServiceName(span); + int address = getAddress(); + Integer port = getPort(); + + Endpoint ep = new Endpoint(address, port.shortValue(), serviceName); + List annotationList = createZipkinAnnotations(span, ep); + List binaryAnnotationList = createZipkinBinaryAnnotations(span, ep); + zipkinSpan.setTrace_id(hash(span.getTraceId())); + if (span.getParents().size() > 0) { + if (span.getParents().size() > 1) { + log.error("zipkin doesn't support spans with multiple parents. Omitting " + + "other parents for " + span); + } + zipkinSpan.setParent_id(hash(span.getParents().get(0))); + } + zipkinSpan.setId(hash(span.getSpanId())); + zipkinSpan.setName(span.getName()); + zipkinSpan.setAnnotations(annotationList); + zipkinSpan.setBinary_annotations(binaryAnnotationList); + return zipkinSpan; + } + + public Integer getPort() { + Integer port; + if (serverProperties.getPort() != null) { + port = serverProperties.getPort(); + } else { + port = 8080; //TODO: support random port + } + return port; + } + + public int getAddress() { + String address; + if (serverProperties.getAddress() != null) { + address = serverProperties.getAddress().getHostAddress(); + } else { + address = "127.0.0.1"; //TODO: get address from config + } + return ipAddressToInt(address); + } + + public String getServiceName(Span span) { + String serviceName; + if (span.getProcessId() != null) { + serviceName = span.getProcessId().toLowerCase(); + } else { + serviceName = appName; + } + return serviceName; + } + + + private int ipAddressToInt(final String ip) { + InetAddress inetAddress = null; + try { + inetAddress = InetAddress.getByName(ip); + } catch (final UnknownHostException e) { + throw new IllegalArgumentException(e); + } + return ByteBuffer.wrap(inetAddress.getAddress()).getInt(); + } + + /** + * Add annotations from the sleuth Span. + */ + private List createZipkinAnnotations(Span span, + Endpoint ep) { + List annotationList = new ArrayList<>(); + + int duration = (int)(span.getEnd() - span.getBegin()); + + // add first zipkin annotation. + annotationList.add(createZipkinAnnotation(zipkinCoreConstants.CLIENT_SEND, span.getBegin(), 0, ep, true)); + annotationList.add(createZipkinAnnotation(zipkinCoreConstants.SERVER_RECV, span.getBegin(), 0, ep, true)); + // add sleuth time annotation + for (TimelineAnnotation ta : span.getTimelineAnnotations()) { + annotationList.add(createZipkinAnnotation(ta.getMsg(), ta.getTime(), 0, ep, true)); + } + // add last zipkin annotation + annotationList.add(createZipkinAnnotation(zipkinCoreConstants.SERVER_SEND, span.getEnd(), duration, ep, false)); + annotationList.add(createZipkinAnnotation(zipkinCoreConstants.CLIENT_RECV, span.getEnd(), duration, ep, false)); + return annotationList; + } + + /** + * Creates a list of Annotations that are present in sleuth Span object. + * + * @return list of Annotations that could be added to Zipkin Span. + */ + private List createZipkinBinaryAnnotations(Span span, + Endpoint ep) { + List l = new ArrayList<>(); + for (Map.Entry e : span.getKVAnnotations().entrySet()) { + BinaryAnnotation binaryAnn = new BinaryAnnotation(); + binaryAnn.setAnnotation_type(AnnotationType.BYTES); + binaryAnn.setKey(e.getKey()); + try { + binaryAnn.setValue(e.getValue().getBytes("UTF-8")); + } catch (UnsupportedEncodingException ex) { + log.error("Error encoding string as UTF-8", ex); + } + binaryAnn.setHost(ep); + l.add(binaryAnn); + } + return l; + } + + /** + * Create an annotation with the correct times and endpoint. + * + * @param value Annotation value + * @param time timestamp will be extracted + * @param ep the endopint this annotation will be associated with. + * @param sendRequest use the first or last timestamp. + */ + private static Annotation createZipkinAnnotation(String value, long time, int duration, + Endpoint ep, boolean sendRequest) { + Annotation annotation = new Annotation(); + annotation.setHost(ep); + + // Zipkin is in microseconds + if (sendRequest) { + annotation.setTimestamp(time * 1000); + } else { + annotation.setTimestamp(time * 1000); + } + + if (duration > 0) { + annotation.setDuration(duration * 1000); + } + annotation.setValue(value); + return annotation; + } + + private static long hash(String string) { + long h = 1125899906842597L; + int len = string.length(); + + for (int i = 0; i < len; i++) { + h = 31 * h + string.charAt(i); + } + return h; + } + +} diff --git a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinAutoConfiguration.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinAutoConfiguration.java index 92cb234d7..72a5c1131 100644 --- a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinAutoConfiguration.java +++ b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinAutoConfiguration.java @@ -4,6 +4,7 @@ import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; @@ -64,10 +65,17 @@ public class ZipkinAutoConfiguration { } @Bean + @ConditionalOnProperty(value = "spring.cloud.sleuth.zipkin.braveTracer.enabled", matchIfMissing = true) public ZipkinSpanListener zipkinTrace(ServerTracer serverTracer) { return new ZipkinSpanListener(serverTracer); } + @Bean + @ConditionalOnProperty(value = "spring.cloud.sleuth.zipkin.braveTracer.enabled", havingValue = "false") + public SleuthTracer sleuthTracer(SpanCollector spanCollector) { + return new SleuthTracer(spanCollector); + } + @Configuration protected static class InterceptorConfig { diff --git a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListener.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListener.java index 7c8bd20a0..18a0702ed 100644 --- a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListener.java +++ b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListener.java @@ -35,37 +35,30 @@ public class ZipkinSpanListener { protected void preTrace(Span context) { final TraceData traceData = getTraceData(context); - if (Boolean.FALSE.equals(traceData.getShouldBeSampled())) { - this.serverTracer.setStateNoTracing(); - log.debug("Received indication that we should NOT trace."); + final String spanName = getSpanName(context, traceData); + if (traceData.getTraceId() != null && traceData.getSpanId() != null) { + + log.debug("Received span information as part of request."); + this.serverTracer.setStateCurrentTrace(traceData.getTraceId(), + traceData.getSpanId(), traceData.getParentSpanId(), spanName); } else { - final String spanName = getSpanName(context, traceData); - if (traceData.getTraceId() != null && traceData.getSpanId() != null) { - - log.debug("Received span information as part of request."); - this.serverTracer.setStateCurrentTrace(traceData.getTraceId(), - traceData.getSpanId(), traceData.getParentSpanId(), spanName); - } - else { - log.debug("Received no span state."); - this.serverTracer.setStateUnknown(spanName); - } - this.serverTracer.setServerReceived(); + log.debug("Received no span state."); + this.serverTracer.setStateUnknown(spanName); } + this.serverTracer.setServerReceived(); } protected TraceData getTraceData(Span context) { TraceData trace = new TraceData(); trace.setTraceId(hash(context.getTraceId())); trace.setSpanId(hash(context.getSpanId())); - trace.setShouldBeSampled(true); trace.setSpanName(context.getName()); if (!context.getParents().isEmpty()) { trace.setParentSpanId(hash(context.getParents().iterator().next())); } return trace; - }; + } protected String getSpanName(Span context, TraceData traceData) { return context.getName(); @@ -83,8 +76,12 @@ public class ZipkinSpanListener { } } - protected ServerTracer getServerTracer() { - return this.serverTracer; + @Data + private static class TraceData { + private Long traceId; + private Long spanId; + private Long parentSpanId; + private String spanName; } private static long hash(String string) { @@ -97,12 +94,4 @@ public class ZipkinSpanListener { return h; } - @Data - private static class TraceData { - private Long traceId; - private Long spanId; - private Long parentSpanId; - private Boolean shouldBeSampled; - private String spanName; - } }