1
pom.xml
1
pom.xml
@@ -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>
|
||||
|
||||
@@ -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).
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
# Environment Post Processor
|
||||
org.springframework.boot.env.EnvironmentPostProcessor=\
|
||||
org.springframework.cloud.sleuth.zipkin.stream.StreamEnvironmentPostProcessor
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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[]
|
||||
@@ -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;
|
||||
Reference in New Issue
Block a user