Removes spring-cloud-sleuth-zipkin-stream (#756)

Fixes #727
This commit is contained in:
Adrian Cole
2017-10-22 22:55:51 +03:00
committed by GitHub
parent 2a0b09477b
commit e56a5a9b3f
14 changed files with 1 additions and 1165 deletions

View File

@@ -31,7 +31,6 @@
<module>spring-cloud-sleuth-zipkin</module>
<module>spring-cloud-sleuth-zipkin2</module>
<module>spring-cloud-sleuth-stream</module>
<module>spring-cloud-sleuth-zipkin-stream</module>
<module>spring-cloud-starter-sleuth</module>
<module>spring-cloud-starter-zipkin</module>
<module>spring-cloud-starter-zipkin2</module>

View File

@@ -26,3 +26,4 @@ NOTE: You can see the zipkin spans without the UI (in logs) if you run the sampl
image::{github-raw}/docs/src/main/asciidoc/images/zipkin-trace-screenshot.png[Sample Zipkin Screenshot]
> The fact that the first trace in says "testSleuthMessaging" seems to be a bug in the UI (it has some annotations from that service, but it originates in the "testSleuthRibbon" service).

View File

@@ -1,34 +0,0 @@
# Running Zipkin Server
There are 3 parts to Zipkin: the instrumented client apps, the backend database and the Zipkin server. The database for this implementation is MySQL.
> There is a running instance on PWS: http://zipkin-web.cfapps.io. It is backed by a `zipkin-server` with a MySQL backend and RabbitMQ (Spring Cloud Stream) for span transport.
## Instrumenting Apps
Depend on [Spring Cloud Sleuth Stream](https://github.com/spring-cloud-spring-cloud-sleuth). Bind to a rabbit service (or redis if you prefer - normal Spring Cloud Stream process).
## Zipkin Server
Depend on `spring-cloud-sleuth-zipkin-stream` and enable the server:
```java
@SpringBootApplication
@EnableZipkinStreamServer
public class ZipkinStreamServerApplication {
public static void main(String[] args) throws Exception {
SpringApplication.run(ZipkinStreamServerApplication.class, args);
}
}
```
Zipkin has a web UI, which is enabled by default when you depend on `io.zipkin.java:zipkin-autoconfigure-ui`.
Bind to MySQL and the same Stream service that you did in the apps (rabbit, redis, kafka). Set `spring.datasource.initialize=true` the first time you start to initialize the database.
Uses the `zipkin-server` library from the [OSS](https://github.com/openzipkin/zipkin-java) as well as `spring-cloud-sleuth-stream`.
> NOTE: running in the "test" profile you don't need MySQL (the span store is in memory). You could even run in PWS without MySQL.

View File

@@ -1,162 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-cloud-sleuth-zipkin-stream</artifactId>
<packaging>jar</packaging>
<name>spring-cloud-sleuth-zipkin-stream</name>
<description>Spring Boot Zipkin Server</description>
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth</artifactId>
<version>2.0.0.BUILD-SNAPSHOT</version>
</parent>
<properties>
<docker.image.prefix>springio</docker.image.prefix>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-stream</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-jmx</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cloud-connectors</artifactId>
</dependency>
<dependency>
<groupId>io.zipkin.java</groupId>
<artifactId>zipkin-server</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.zipkin.java</groupId>
<artifactId>zipkin-autoconfigure-storage-mysql</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-test-support</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>${project.basedir}/src/main/resources</directory>
</resource>
<resource>
<directory>${project.build.directory}/generated-resources</directory>
</resource>
</resources>
<plugins>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<executions>
<execution>
<!-- Serves *only* to filter the Dockerfile so it can get an absolute
path for the project -->
<id>copy-resources</id>
<phase>validate</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${basedir}/target/generated-docker</outputDirectory>
<resources>
<resource>
<directory>src/main/docker</directory>
<filtering>true</filtering>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.spotify</groupId>
<artifactId>docker-maven-plugin</artifactId>
<version>0.3.8</version>
<configuration>
<imageName>${docker.image.prefix}/${project.artifactId}</imageName>
<dockerDirectory>${basedir}/target/generated-docker</dockerDirectory>
<resources>
<resource>
<targetPath>/</targetPath>
<directory>${project.build.directory}</directory>
<include>${project.build.finalName}-exec.jar</include>
</resource>
</resources>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -1,191 +0,0 @@
/*
* Copyright 2016 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.zipkin.stream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.apache.commons.logging.Log;
import org.springframework.cloud.sleuth.Span;
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;
import zipkin.Span.Builder;
/**
* This converts sleuth spans to zipkin ones, skipping invalid or unsampled.
*
* @author Adrian Cole
*
* @since 1.0.0
*/
final class ConvertToZipkinSpanList {
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(ConvertToZipkinSpanList.class);
static List<zipkin.Span> convert(Spans input) {
Host host = input.getHost();
List<zipkin.Span> result = new ArrayList<>(input.getSpans().size());
for (Span span : input.getSpans()) {
if (!span.getName().equals("message:" + SleuthSink.INPUT)) {
result.add(convert(span, host));
}
else {
log.warn("Message tracing cycle detected for: " + input);
}
}
return result;
}
/**
* Converts a given Sleuth span to a Zipkin Span.
* <ul>
* <li>Set ids, etc
* <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) {
//TODO: Consider adding support for the debug flag (related to #496)
Builder zipkinSpan = zipkin.Span.builder();
Endpoint ep = Endpoint.builder()
.serviceName(host.getServiceName())
.ipv4(host.getIpv4())
.port(host.getPort() != null ? host.getPort() : 0).build();
// A zipkin span without any annotations cannot be queried, add special "lc" to
// avoid that.
if (notClientOrServer(span)) {
ensureLocalComponent(span, zipkinSpan, ep);
}
ZipkinMessageListener.addZipkinAnnotations(zipkinSpan, span, ep);
ZipkinMessageListener.addZipkinBinaryAnnotations(zipkinSpan, span, ep);
if (hasClientSend(span)) {
ensureServerAddr(span, zipkinSpan);
}
// 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
// Zipkin backfills timestamp and duration.
if (!span.isRemote()) {
if (Boolean.TRUE.equals(span.isShared())) {
// don't report server-side timestamp on shared spans
zipkinSpan.timestamp(null).duration(null);
} else {
zipkinSpan.timestamp(span.getBegin() * 1000);
if (!span.isRunning()) { // duration is authoritative, only write when the span stopped
zipkinSpan.duration(calculateDurationInMicros(span));
}
}
}
zipkinSpan.traceIdHigh(span.getTraceIdHigh());
zipkinSpan.traceId(span.getTraceId());
if (span.getParents().size() > 0) {
if (span.getParents().size() > 1) {
if (log.isDebugEnabled()) {
log.debug(
"zipkin doesn't support spans with multiple parents. Omitting "
+ "other parents for " + span);
}
}
zipkinSpan.parentId(span.getParents().get(0));
}
zipkinSpan.id(span.getSpanId());
if (StringUtils.hasText(span.getName())) {
zipkinSpan.name(span.getName());
}
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, Builder zipkinSpan) {
if (span.tags().containsKey(Span.SPAN_PEER_SERVICE_TAG_NAME)) {
Endpoint endpoint = Endpoint.builder().serviceName(span.tags().get(
Span.SPAN_PEER_SERVICE_TAG_NAME)).build();
zipkinSpan.addBinaryAnnotation(
BinaryAnnotation.address(Constants.SERVER_ADDR, endpoint));
}
}
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;
}
/**
* There could be instrumentation delay between span creation and the
* semantic start of the span (client send). When there's a difference,
* spans look confusing. Ex users expect duration to be client
* receive - send, but it is a little more than that. Rather than have
* to teach each user about the possibility of instrumentation overhead,
* we truncate absolute duration (span finish - create) to semantic
* duration (client receive - send)
*/
private static long calculateDurationInMicros(Span span) {
org.springframework.cloud.sleuth.Log clientSend = hasLog(Span.CLIENT_SEND, span);
org.springframework.cloud.sleuth.Log clientReceived = hasLog(Span.CLIENT_RECV, span);
if (clientSend != null && clientReceived != null) {
return (clientReceived.getTimestamp() - clientSend.getTimestamp()) * 1000;
}
return span.getAccumulatedMicros();
}
private static org.springframework.cloud.sleuth.Log hasLog(String logName, Span span) {
for (org.springframework.cloud.sleuth.Log log : span.logs()) {
if (logName.equals(log.getEvent())) {
return log;
}
}
return null;
}
}

View File

@@ -1,45 +0,0 @@
/**
* Copyright 2015 The OpenZipkin 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.zipkin.stream;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.cloud.sleuth.stream.SleuthSink;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.context.annotation.Import;
import zipkin.server.EnableZipkinServer;
/**
* When enabled, instrumented apps will transport spans over a
* Spring Cloud Stream, for example RabbitMQ.
*
* @author Dave Syer
* @since 1.0.0
*
* @see ZipkinMessageListener
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@EnableBinding(SleuthSink.class)
@EnableZipkinServer
@Import(ZipkinMessageListener.class)
public @interface EnableZipkinStreamServer {
}

View File

@@ -1,76 +0,0 @@
/*
* Copyright 2015 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.zipkin.stream;
import java.util.HashMap;
import java.util.Map;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.cloud.sleuth.stream.SleuthSink;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;
/**
* {@link EnvironmentPostProcessor} that sets the default properties for Sleuth Zipkin
* Stream.
*
* @author Dave Syer
* @since 1.0.0
*/
public class StreamEnvironmentPostProcessor implements EnvironmentPostProcessor {
private static final String PROPERTY_SOURCE_NAME = "defaultProperties";
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment,
SpringApplication application) {
Map<String, Object> map = new HashMap<String, Object>();
// Clearing the content type on the inbound channel means that the payload
// of inbound messages can be coerced by a `@StreamListener` method to
// its argument type based on the 'contentType' header of the inbound message.
// Necessary to be done explicitly because the property is set by by
// org.springframework.cloud.sleuth.stream.StreamEnvironmentPostProcessor to
// 'application/json' for outbound channels
map.put("spring.cloud.stream.bindings." + SleuthSink.INPUT + ".content-type",
"");
addOrReplace(environment.getPropertySources(), map);
}
private void addOrReplace(MutablePropertySources propertySources,
Map<String, Object> map) {
MapPropertySource target = null;
if (propertySources.contains(PROPERTY_SOURCE_NAME)) {
PropertySource<?> source = propertySources.get(PROPERTY_SOURCE_NAME);
if (source instanceof MapPropertySource) {
target = (MapPropertySource) source;
for (String key : map.keySet()) {
target.getSource().put(key, map.get(key));
}
}
}
if (target == null) {
target = new MapPropertySource(PROPERTY_SOURCE_NAME, map);
}
if (!propertySources.contains(PROPERTY_SOURCE_NAME)) {
propertySources.addLast(target);
}
}
}

View File

@@ -1,129 +0,0 @@
package org.springframework.cloud.sleuth.zipkin.stream;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.cloud.sleuth.Log;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.stream.SleuthSink;
import org.springframework.cloud.sleuth.stream.Spans;
import org.springframework.cloud.sleuth.zipkin.stream.ZipkinMessageListener.NotSleuthStreamClient;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.integration.annotation.MessageEndpoint;
import zipkin.Annotation;
import zipkin.BinaryAnnotation;
import zipkin.BinaryAnnotation.Type;
import zipkin.Endpoint;
import zipkin.Span.Builder;
import zipkin.collector.Collector;
import zipkin.collector.CollectorMetrics;
import zipkin.collector.CollectorSampler;
import zipkin.storage.Callback;
import zipkin.storage.StorageComponent;
import java.io.UnsupportedEncodingException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* A message listener that is turned on if Sleuth Stream is disabled.
* Asynchronously stores the received spans using {@link Collector}.
*
* @author Dave Syer
* @since 1.0.0
*
* @see NotSleuthStreamClient
*/
@MessageEndpoint
@Conditional(NotSleuthStreamClient.class)
public class ZipkinMessageListener {
private static final org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory
.getLog(ZipkinMessageListener.class);
static final String UNKNOWN_PROCESS_ID = "unknown";
final Collector collector;
/** lazy so transient storage errors don't crash bootstrap */
@Lazy
@Autowired
ZipkinMessageListener(StorageComponent storage, CollectorSampler sampler,
CollectorMetrics metrics) {
this.collector = Collector.builder(getClass())
.storage(storage)
.sampler(sampler)
.metrics(metrics.forTransport("stream")).build();
}
@StreamListener(SleuthSink.INPUT)
public void sink(Spans input) {
List<zipkin.Span> converted = ConvertToZipkinSpanList.convert(input);
this.collector.accept(converted, Callback.NOOP);
}
/**
* Add annotations from the sleuth Span.
*/
static void addZipkinAnnotations(Builder zipkinSpan, Span span, Endpoint endpoint) {
for (Log ta : span.logs()) {
Annotation zipkinAnnotation = Annotation.builder()
.endpoint(endpoint)
.timestamp(ta.getTimestamp() * 1000) // Zipkin is in microseconds
.value(ta.getEvent())
.build();
zipkinSpan.addAnnotation(zipkinAnnotation);
}
}
/**
* Adds binary annotations from the sleuth Span
*/
static void addZipkinBinaryAnnotations(Builder zipkinSpan, Span span,
Endpoint endpoint) {
for (Map.Entry<String, String> e : span.tags().entrySet()) {
BinaryAnnotation.Builder binaryAnn = BinaryAnnotation.builder();
binaryAnn.type(Type.STRING);
binaryAnn.key(e.getKey());
try {
binaryAnn.value(e.getValue().getBytes("UTF-8"));
}
catch (UnsupportedEncodingException ex) {
log.error("Error encoding string as UTF-8", ex);
}
binaryAnn.endpoint(endpoint);
zipkinSpan.addBinaryAnnotation(binaryAnn.build());
}
}
protected static class NotSleuthStreamClient extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
Environment environment = context.getEnvironment();
if ("true".equals(environment
.resolvePlaceholders("${spring.sleuth.stream.enabled:}"))) {
return ConditionOutcome
.noMatch("Found spring.sleuth.stream.enabled=true");
}
if (environment instanceof ConfigurableEnvironment) {
ConfigurableEnvironment configurable = (ConfigurableEnvironment) environment;
configurable.getPropertySources()
.addLast(
new MapPropertySource("spring.sleuth.stream",
Collections.<String, Object>singletonMap(
"spring.sleuth.stream.enabled",
"false")));
}
return ConditionOutcome.match("Not found: spring.sleuth.stream.enabled");
}
}
}

View File

@@ -1,3 +0,0 @@
# Environment Post Processor
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.cloud.sleuth.zipkin.stream.StreamEnvironmentPostProcessor

View File

@@ -1,262 +0,0 @@
/*
* Copyright 2016 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.zipkin.stream;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import org.assertj.core.api.Condition;
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.Endpoint;
import static org.assertj.core.api.Assertions.assertThat;
public class ConvertToZipkinSpanListTests {
Host host = new Host("myservice", "1.2.3.4", 8080);
@Test
public void skipsInputSpans() {
Spans spans = new Spans(this.host,
Collections.singletonList(span("sleuth")));
List<zipkin.Span> result = ConvertToZipkinSpanList.convert(spans);
assertThat(result).isEmpty();
}
@Test
public void nullEndpointPort() {
this.host.setPort(0);
Span span = span("sleuth");
span.logEvent(Constants.CLIENT_SEND);
zipkin.Span result = ConvertToZipkinSpanList.convert(span, host);
assertThat(result).isNotNull();
}
@Test
public void retainsValidSpans() {
Spans spans = new Spans(this.host,
Arrays.asList(span("foo"), span("bar"), span("baz")));
List<zipkin.Span> result = ConvertToZipkinSpanList.convert(spans);
assertThat(result).extracting(s -> s.name).containsExactly(
"message:foo", "message:bar", "message:baz");
}
@Test
public void appendsLocalComponentTagIfNoZipkinLogIsPresent() {
Spans spans = new Spans(this.host, Collections.singletonList(span("foo")));
List<zipkin.Span> result = ConvertToZipkinSpanList.convert(spans);
assertThat(result)
.flatExtracting(s -> s.binaryAnnotations)
.extracting(input -> input.key)
.contains(Constants.LOCAL_COMPONENT);
}
@Test
public void appendServerAddressTagIfClientLogIsPresentWhenPeerServiceIsPresent() {
Span span = span("foo");
span.logEvent(Constants.CLIENT_SEND);
span.tag(Span.SPAN_PEER_SERVICE_TAG_NAME, "myservice");
Spans spans = new Spans(this.host, Collections.singletonList(span));
List<zipkin.Span> result = ConvertToZipkinSpanList.convert(spans);
assertThat(result)
.hasSize(1)
.flatExtracting(input1 -> input1.binaryAnnotations)
.filteredOn("key", Constants.SERVER_ADDR)
.extracting(input -> input.endpoint)
.hasSize(1)
.has(new Condition<List<? extends Endpoint>>() {
@Override public boolean matches(List<? extends Endpoint> value) {
Endpoint endpoint = value.get(0);
return endpoint.serviceName.equals("myservice") && endpoint.ipv4 == 0;
}
});
}
@Test
public void doesNotAppendServerAddressTagIfClientLogIsPresent() {
Span span = span("foo");
span.logEvent(Constants.CLIENT_SEND);
Spans spans = new Spans(this.host, Collections.singletonList(span));
List<zipkin.Span> result = ConvertToZipkinSpanList.convert(spans);
assertThat(result)
.hasSize(1)
.flatExtracting(input1 -> input1.binaryAnnotations)
.filteredOn("key", Constants.SERVER_ADDR)
.isEmpty();
}
@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));
List<zipkin.Span> result = ConvertToZipkinSpanList.convert(spans);
assertThat(result)
.hasSize(1)
.flatExtracting(input1 -> input1.binaryAnnotations)
.filteredOn("key", Constants.SERVER_ADDR)
.extracting(input -> input.endpoint.serviceName)
.contains("barservice");
}
/** Sleuth timestamps are millisecond granularity while zipkin is microsecond. */
@Test
public void convertsTimestampToMicrosecondsAndSetsDurationToAccumulatedMicros() {
long start = System.currentTimeMillis();
Span span = span("foo");
span.logEvent(Constants.CLIENT_SEND);
span.stop();
Spans spans = new Spans(this.host, Collections.singletonList(span));
zipkin.Span result = ConvertToZipkinSpanList.convert(spans).get(0);
assertThat(result.timestamp)
.isEqualTo(span.getBegin() * 1000);
assertThat(result.duration)
.isEqualTo(span.getAccumulatedMicros());
assertThat(result.annotations.get(0).timestamp)
.isGreaterThanOrEqualTo(start * 1000)
.isLessThanOrEqualTo(System.currentTimeMillis() * 1000);
}
@Test
public void setsTheDurationToTheDifferenceBetweenCRandCS()
throws InterruptedException {
Span span = span("foo");
span.logEvent(Span.CLIENT_SEND);
Thread.sleep(10);
span.logEvent(Span.CLIENT_RECV);
Thread.sleep(20);
span.stop();
Spans spans = new Spans(this.host, Collections.singletonList(span));
zipkin.Span result = ConvertToZipkinSpanList.convert(spans).get(0);
assertThat(result.timestamp)
.isEqualTo(span.getBegin() * 1000);
long clientSendTimestamp = span.logs().stream().filter(log -> Span.CLIENT_SEND.equals(log.getEvent()))
.findFirst().get().getTimestamp();
long clientRecvTimestamp = span.logs().stream().filter(log -> Span.CLIENT_RECV.equals(log.getEvent()))
.findFirst().get().getTimestamp();
assertThat(result.duration)
.isNotEqualTo(span.getAccumulatedMicros())
.isEqualTo((clientRecvTimestamp - clientSendTimestamp) * 1000);
}
/** Zipkin's duration should only be set when the span is finished. */
@Test
public void doesntSetDurationWhenStillRunning() {
Span running = Span.builder().traceId(1L).name("http:child").build();
Spans spans = new Spans(this.host, Collections.singletonList(running));
zipkin.Span result = ConvertToZipkinSpanList.convert(spans).get(0);
assertThat(result.timestamp)
.isGreaterThan(0); // sanity check it did start
assertThat(result.duration)
.isNull();
}
/**
* 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
* Zipkin backfills timestamp and duration.
*/
@Test
public void doesntSetTimestampOrDurationWhenRemote() {
Span span = span("foo", true);
Spans spans = new Spans(this.host, Collections.singletonList(span));
zipkin.Span result = ConvertToZipkinSpanList.convert(spans).get(0);
assertThat(result.timestamp)
.isNull();
assertThat(result.duration)
.isNull();
}
@Test
public void converts128BitTraceId() {
Span span = Span.builder().traceIdHigh(1L).traceId(2L).spanId(3L).name("foo").build();
Spans spans = new Spans(this.host, Collections.singletonList(span));
zipkin.Span result = ConvertToZipkinSpanList.convert(spans).get(0);
assertThat(result.traceIdHigh).isEqualTo(span.getTraceIdHigh());
assertThat(result.traceId).isEqualTo(span.getTraceId());
}
@Test
public void shouldRemoveTimestampAndDurationForNonRemoteSharedSpan() {
Span span = Span.builder()
.name("foo")
.exportable(false)
.remote(false)
.shared(true)
.build();
Spans spans = new Spans(this.host, Collections.singletonList(span));
zipkin.Span result = ConvertToZipkinSpanList.convert(spans).get(0);
assertThat(result.duration).isNull();
assertThat(result.timestamp).isNull();
}
@Test
public void shouldNotRemoveTimestampAndDurationForNonRemoteNonSharedSpan() {
Span span = Span.builder()
.name("foo")
.exportable(false)
.remote(false)
.shared(false)
.build();
span.stop();
Spans spans = new Spans(this.host, Collections.singletonList(span));
zipkin.Span result = ConvertToZipkinSpanList.convert(spans).get(0);
assertThat(result.duration).isNotNull();
assertThat(result.timestamp).isNotNull();
}
Span span(String name) {
return span(name, false);
}
Span span(String name, boolean remote) {
Long id = new Random().nextLong();
return Span.builder().begin(1).end(3).name("message:" + name).traceId(id).spanId(id)
.remote(remote).processId("process").build();
}
}

View File

@@ -1,110 +0,0 @@
/*
* Copyright 2013-2015 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.zipkin.stream;
import org.junit.Test;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.stream.Host;
import zipkin.BinaryAnnotation;
import zipkin.Endpoint;
import static org.assertj.core.api.Assertions.assertThat;
public class ZipkinMessageListenerTests {
Span span = Span.builder().begin(1).end(3).name("http:name").traceId(1L).spanId(2L).remote(true)
.exportable(true).processId("process").build();
Host host = new Host("myservice", "1.2.3.4", 8080);
Endpoint endpoint = Endpoint.builder()
.serviceName("myservice")
.ipv4(1 << 24 | 2 << 16 | 3 << 8 | 4)
.port(8080).build();
/**
* 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
* Zipkin backfills timestamp and duration.
*/
@Test
public void doesntSetTimestampOrDurationWhenRemote() {
this.span.stop();
zipkin.Span result = ConvertToZipkinSpanList.convert(this.span, this.host);
assertThat(result.timestamp)
.isNull();
assertThat(result.duration)
.isNull();
}
/** Sleuth timestamps are millisecond granularity while zipkin is microsecond. */
@Test
public void convertsTimestampAndDurationToMicroseconds() {
Span span = Span.builder().begin(1).end(3).name("http:name").traceId(1L).spanId(2L).remote(false)
.exportable(true).processId("process").build();
long start = System.currentTimeMillis();
span.logEvent("hystrix/retry"); // System.currentTimeMillis
zipkin.Span result = ConvertToZipkinSpanList.convert(span, this.host);
assertThat(result.timestamp)
.isEqualTo(span.getBegin() * 1000);
assertThat(result.duration)
.isEqualTo((span.getEnd() - span.getBegin()) * 1000);
assertThat(result.annotations.get(0).timestamp)
.isGreaterThanOrEqualTo(start * 1000)
.isLessThanOrEqualTo(System.currentTimeMillis() * 1000);
}
/** Sleuth host corresponds to annotation/binaryAnnotation.host in zipkin. */
@Test
public void annotationsIncludeHost() {
this.span.logEvent("hystrix/retry");
this.span.tag("spring-boot/version", "1.3.1.RELEASE");
zipkin.Span result = ConvertToZipkinSpanList.convert(this.span, this.host);
assertThat(result.annotations.get(0).endpoint)
.isEqualTo(this.endpoint);
assertThat(result.binaryAnnotations.get(0).endpoint)
.isEqualTo(result.annotations.get(0).endpoint);
}
/**
* In zipkin, the service context is attached to annotations. Sleuth spans
* that have no annotations will get an "lc" one, which allows them to be
* queryable in zipkin by service name.
*/
@Test
public void spanWithoutAnnotationsLogsComponent() {
zipkin.Span result = ConvertToZipkinSpanList.convert(this.span, this.host);
assertThat(result.binaryAnnotations).hasSize(1);
assertThat(result.binaryAnnotations.get(0)).isEqualToComparingFieldByField(
BinaryAnnotation.create("lc", this.span.getProcessId(), this.endpoint));
}
// TODO: "unknown" bc process id, documented as not nullable, is null in some tests.
@Test
public void nullProcessIdCoercesToUnknownServiceName() {
Span noProcessId = Span.builder().traceId(1L).name("http:parent").remote(true).build();
zipkin.Span result = ConvertToZipkinSpanList.convert(noProcessId, this.host);
assertThat(result.binaryAnnotations)
.containsOnly(BinaryAnnotation.create("lc", "unknown", this.endpoint));
}
}

View File

@@ -1,110 +0,0 @@
package org.springframework.cloud.sleuth.zipkin.stream;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.cloud.sleuth.stream.SleuthSink;
import org.springframework.cloud.sleuth.zipkin.stream.ZipkinServerApplicationTests.ZipkinStreamServerApplication;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import zipkin.collector.CollectorMetrics;
import zipkin.collector.CollectorSampler;
import zipkin.internal.V2StorageComponent;
import zipkin.server.ZipkinHttpCollector;
import zipkin.server.ZipkinQueryApiV1;
import zipkin.storage.StorageComponent;
import zipkin2.storage.InMemoryStorage;
import static org.junit.Assert.assertEquals;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = ZipkinStreamServerApplication.class, properties = {
"spring.datasource.initialize=true" }, webEnvironment = WebEnvironment.RANDOM_PORT)
@ActiveProfiles("test")
public class ZipkinServerApplicationTests {
@Autowired
private StorageComponent storage;
@Test
public void contextLoads() {
int count = this.storage.spanStore().getServiceNames().size();
assertEquals(0, count);
}
@SpringBootApplication
@EnableBoot2CompatibleZipkinServer
public static class ZipkinStreamServerApplication {
public static void main(String[] args) throws Exception {
new SpringApplicationBuilder(ZipkinStreamServerApplication.class)
.profiles("test").properties("spring.datasource.initialize=true")
.run(args);
}
}
}
// TODO: Zipkin Server is not Boot 2.0 compatible
//@EnableZipkinStreamServer
@EnableBinding(SleuthSink.class)
@Import({ZipkinMessageListener.class,
Boot2ZipkinCompatibleConfig.class,
ZipkinQueryApiV1.class,
ZipkinHttpCollector.class})
@interface EnableBoot2CompatibleZipkinServer {
}
// CollectorMetrics bean definition from `ZipkinServerConfiguration`
// is not Boot 2.0 compatible
@Configuration
class Boot2ZipkinCompatibleConfig {
@Bean CollectorMetrics collectorMetrics() {
CollectorMetrics mock = Mockito.mock(CollectorMetrics.class);
Mockito.when(mock.forTransport(Mockito.anyString())).thenReturn(mock);
return mock;
}
@Bean
@ConditionalOnMissingBean(CollectorSampler.class)
CollectorSampler traceIdSampler(@Value("${zipkin.collector.sample-rate:1.0}") float rate) {
return CollectorSampler.create(rate);
}
/**
* This is a special-case configuration if there's no StorageComponent of any kind. In-Mem can
* supply both read apis, so we add two beans here.
*/
@Configuration
// "matchIfMissing = true" ensures this is used when there's no configured storage type
@ConditionalOnProperty(name = "zipkin.storage.type", havingValue = "mem", matchIfMissing = true)
@ConditionalOnMissingBean(StorageComponent.class)
static class InMemoryConfiguration {
@Bean StorageComponent storage(
@Value("${zipkin.storage.strict-trace-id:true}") boolean strictTraceId,
@Value("${zipkin.storage.mem.max-spans:500000}") int maxSpans) {
return V2StorageComponent.create(InMemoryStorage.newBuilder()
.strictTraceId(strictTraceId)
.maxSpanCount(maxSpans)
.build());
}
@Bean InMemoryStorage v2Storage(V2StorageComponent component) {
return (InMemoryStorage) component.delegate();
}
}
}

View File

@@ -1,37 +0,0 @@
/*
* Copyright 2013-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.zipkin.stream.documentation;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.sleuth.zipkin.stream.EnableZipkinStreamServer;
/**
* Test class to be embedded in the Zipkin Consumer part of
* {@code docs/src/main/asciidoc/spring-cloud-sleuth.adoc}
*
* @author Marcin Grzejszczak
*/
// tag::zipkin_consumer[]
@SpringBootApplication
@EnableZipkinStreamServer
public class Consumer {
public static void main(String[] args) {
SpringApplication.run(Consumer.class, args);
}
}
// end::zipkin_consumer[]

View File

@@ -1,5 +0,0 @@
drop table zipkin_spans;
drop table zipkin_annotations;
drop table zipkin_binary_annotations;
drop table zipkin_dependencies;
drop table zipkin_dependency_links;