Adds Zipkin v2 support (#720)
This adds the ability to use zipkin v2 endpoints natively. It also allows wiring of standard zipkin reporters and senders.
This commit is contained in:
committed by
Marcin Grzejszczak
parent
08a526c61b
commit
4ddf929294
2
pom.xml
2
pom.xml
@@ -29,11 +29,13 @@
|
||||
<module>spring-cloud-sleuth-dependencies</module>
|
||||
<module>spring-cloud-sleuth-core</module>
|
||||
<module>spring-cloud-sleuth-zipkin</module>
|
||||
<module>spring-cloud-sleuth-zipkin2</module>
|
||||
<module>spring-cloud-sleuth-stream</module>
|
||||
<module>spring-cloud-sleuth-reactor</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>
|
||||
<module>spring-cloud-sleuth-samples</module>
|
||||
<module>docs</module>
|
||||
</modules>
|
||||
|
||||
@@ -14,8 +14,9 @@
|
||||
<name>spring-cloud-sleuth-dependencies</name>
|
||||
<description>Spring Cloud Sleuth Dependencies</description>
|
||||
<properties>
|
||||
<zipkin.version>1.28.0</zipkin.version>
|
||||
<zipkin-reporter.version>1.0.0</zipkin-reporter.version>
|
||||
<zipkin.version>2.1.0</zipkin.version>
|
||||
<zipkin-reporter.version>1.1.1</zipkin-reporter.version>
|
||||
<zipkin-reporter2.version>2.0.2</zipkin-reporter2.version>
|
||||
</properties>
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
@@ -34,6 +35,11 @@
|
||||
<artifactId>spring-cloud-sleuth-zipkin</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-sleuth-zipkin2</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-sleuth-stream</artifactId>
|
||||
@@ -49,6 +55,11 @@
|
||||
<artifactId>spring-cloud-starter-zipkin</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-zipkin2</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-sleuth</artifactId>
|
||||
@@ -59,6 +70,11 @@
|
||||
<artifactId>zipkin</artifactId>
|
||||
<version>${zipkin.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.zipkin.zipkin2</groupId>
|
||||
<artifactId>zipkin</artifactId>
|
||||
<version>${zipkin.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.zipkin.java</groupId>
|
||||
<artifactId>zipkin-server</artifactId>
|
||||
@@ -84,6 +100,11 @@
|
||||
<artifactId>zipkin-reporter</artifactId>
|
||||
<version>${zipkin-reporter.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.zipkin.reporter2</groupId>
|
||||
<artifactId>zipkin-reporter</artifactId>
|
||||
<version>${zipkin-reporter2.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
<profiles>
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<module>spring-cloud-sleuth-sample-websocket</module>
|
||||
<module>spring-cloud-sleuth-sample-feign</module>
|
||||
<module>spring-cloud-sleuth-sample-ribbon</module>
|
||||
<module>spring-cloud-sleuth-sample-zipkin</module>
|
||||
<module>spring-cloud-sleuth-sample-zipkin2</module>
|
||||
<module>spring-cloud-sleuth-sample-stream</module>
|
||||
<module>spring-cloud-sleuth-sample-zipkin-stream</module>
|
||||
</modules>
|
||||
@@ -59,12 +59,17 @@
|
||||
<dependency>
|
||||
<groupId>io.zipkin.java</groupId>
|
||||
<artifactId>zipkin</artifactId>
|
||||
<version>1.28.0</version>
|
||||
<version>2.1.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.zipkin.zipkin2</groupId>
|
||||
<artifactId>zipkin</artifactId>
|
||||
<version>2.1.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.zipkin.java</groupId>
|
||||
<artifactId>zipkin-server</artifactId>
|
||||
<version>1.28.0</version>
|
||||
<version>2.1.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
@@ -19,10 +19,10 @@
|
||||
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-sample-zipkin</artifactId>
|
||||
<artifactId>spring-cloud-sleuth-sample-zipkin2</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>spring-cloud-sleuth-sample-zipkin</name>
|
||||
<description>Spring Cloud Sleuth Sample</description>
|
||||
<name>spring-cloud-sleuth-sample-zipkin2</name>
|
||||
<description>Spring Cloud Sleuth Sample Zipkin v2</description>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
@@ -77,7 +77,7 @@
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-sleuth-zipkin</artifactId>
|
||||
<artifactId>spring-cloud-sleuth-zipkin2</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
@@ -111,7 +111,7 @@
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>okhttp</artifactId>
|
||||
<version>3.6.0</version>
|
||||
<version>3.9.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
@@ -21,11 +21,12 @@ import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.cloud.sleuth.zipkin.ZipkinSpanReporter;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.EnableAspectJAutoProxy;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import zipkin2.Span;
|
||||
import zipkin2.reporter.Reporter;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
@@ -49,13 +50,8 @@ public class SampleZipkinApplication {
|
||||
// Use this for debugging (or if there is no Zipkin server running on port 9411)
|
||||
@Bean
|
||||
@ConditionalOnProperty(value = "sample.zipkin.enabled", havingValue = "false")
|
||||
public ZipkinSpanReporter spanCollector() {
|
||||
return new ZipkinSpanReporter() {
|
||||
@Override
|
||||
public void report(zipkin.Span span) {
|
||||
log.info(String.format("Reporting span [%s]", span));
|
||||
}
|
||||
};
|
||||
public Reporter<Span> spanReporter() {
|
||||
return Reporter.CONSOLE;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -32,7 +32,7 @@ import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.cloud.sleuth.zipkin.ZipkinProperties;
|
||||
import org.springframework.cloud.sleuth.zipkin2.ZipkinProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
133
spring-cloud-sleuth-zipkin2/pom.xml
Normal file
133
spring-cloud-sleuth-zipkin2/pom.xml
Normal file
@@ -0,0 +1,133 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<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-zipkin2</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>Spring Cloud Sleuth Zipkin v2</name>
|
||||
<description>Spring Cloud Sleuth Zipkin v2</description>
|
||||
<build>
|
||||
<plugins>
|
||||
<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>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-sleuth</artifactId>
|
||||
<version>1.3.0.BUILD-SNAPSHOT</version>
|
||||
<relativePath>..</relativePath>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-sleuth-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-commons</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-actuator</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-logging</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.zipkin.zipkin2</groupId>
|
||||
<artifactId>zipkin</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.zipkin.reporter2</groupId>
|
||||
<artifactId>zipkin-reporter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-messaging</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.zipkin.java</groupId>
|
||||
<artifactId>zipkin-junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.awaitility</groupId>
|
||||
<artifactId>awaitility</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!-- otherwise spring boot's version of okhttp kicks zipkin-junit's deps out of alignment -->
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>okhttp</artifactId>
|
||||
<version>3.9.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>mockwebserver</artifactId>
|
||||
<version>3.9.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.aspectj</groupId>
|
||||
<artifactId>aspectjrt</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.aspectj</groupId>
|
||||
<artifactId>aspectjweaver</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* 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.zipkin2;
|
||||
|
||||
import java.lang.invoke.MethodHandles;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.boot.autoconfigure.web.ServerProperties;
|
||||
import org.springframework.boot.context.embedded.EmbeddedServletContainerInitializedEvent;
|
||||
import org.springframework.cloud.client.serviceregistry.Registration;
|
||||
import org.springframework.cloud.commons.util.InetUtils;
|
||||
import org.springframework.cloud.commons.util.InetUtilsProperties;
|
||||
import org.springframework.context.EnvironmentAware;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.util.StringUtils;
|
||||
import zipkin2.Endpoint;
|
||||
|
||||
/**
|
||||
* {@link EndpointLocator} implementation that:
|
||||
*
|
||||
* <ul>
|
||||
* <li><b>serviceName</b> - from {@link ServerProperties} or {@link Registration}</li>
|
||||
* <li><b>ip</b> - from {@link ServerProperties}</li>
|
||||
* <li><b>port</b> - from lazily assigned port or {@link ServerProperties}</li>
|
||||
* </ul>
|
||||
*
|
||||
* You can override the name using {@link ZipkinProperties.Service#setName(String)}
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public class DefaultEndpointLocator implements EndpointLocator, EnvironmentAware {
|
||||
|
||||
private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass());
|
||||
private static final String IP_ADDRESS_PROP_NAME = "spring.cloud.client.ipAddress";
|
||||
|
||||
private final Registration registration;
|
||||
private final ServerProperties serverProperties;
|
||||
private final String appName;
|
||||
private final InetUtils inetUtils;
|
||||
private final ZipkinProperties zipkinProperties;
|
||||
private Integer port;
|
||||
private Environment environment;
|
||||
|
||||
public DefaultEndpointLocator(Registration registration, ServerProperties serverProperties,
|
||||
String appName, ZipkinProperties zipkinProperties, InetUtils inetUtils) {
|
||||
this.registration = registration;
|
||||
this.serverProperties = serverProperties;
|
||||
this.appName = appName;
|
||||
this.zipkinProperties = zipkinProperties;
|
||||
if (inetUtils == null) {
|
||||
this.inetUtils = new InetUtils(new InetUtilsProperties());
|
||||
} else {
|
||||
this.inetUtils = inetUtils;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Endpoint local() {
|
||||
String serviceName = getLocalServiceName();
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Span will contain serviceName [" + serviceName + "]");
|
||||
}
|
||||
Endpoint.Builder builder = Endpoint.newBuilder()
|
||||
.serviceName(serviceName)
|
||||
.port(getPort());
|
||||
return addAddress(builder).build();
|
||||
}
|
||||
|
||||
private String getLocalServiceName() {
|
||||
if (StringUtils.hasText(this.zipkinProperties.getService().getName())) {
|
||||
return this.zipkinProperties.getService().getName();
|
||||
} else if (this.registration != null) {
|
||||
try {
|
||||
return this.registration.getServiceId();
|
||||
} catch (RuntimeException e) {
|
||||
log.warn("error getting service name from registration", e);
|
||||
}
|
||||
}
|
||||
return this.appName;
|
||||
}
|
||||
|
||||
@EventListener(EmbeddedServletContainerInitializedEvent.class)
|
||||
public void grabPort(EmbeddedServletContainerInitializedEvent event) {
|
||||
this.port = event.getEmbeddedServletContainer().getPort();
|
||||
}
|
||||
|
||||
private Integer getPort() {
|
||||
if (this.port!=null) {
|
||||
return this.port;
|
||||
}
|
||||
Integer port;
|
||||
if (this.serverProperties!=null && this.serverProperties.getPort() != null && this.serverProperties.getPort() > 0) {
|
||||
port = this.serverProperties.getPort();
|
||||
}
|
||||
else {
|
||||
port = 8080;
|
||||
}
|
||||
return port;
|
||||
}
|
||||
|
||||
private Endpoint.Builder addAddress(Endpoint.Builder builder) {
|
||||
if (this.serverProperties != null && this.serverProperties.getAddress() != null
|
||||
&& builder.parseIp(this.serverProperties.getAddress())) {
|
||||
return builder;
|
||||
}
|
||||
else if (this.environment != null && this.environment.containsProperty(IP_ADDRESS_PROP_NAME)
|
||||
&& builder.parseIp(this.environment.getProperty(IP_ADDRESS_PROP_NAME, String.class))) {
|
||||
return builder;
|
||||
}
|
||||
else {
|
||||
return builder.ip(this.inetUtils.findFirstNonLoopbackAddress());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnvironment(Environment environment) {
|
||||
this.environment = environment;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2013-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.zipkin2;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
|
||||
import org.springframework.http.HttpRequest;
|
||||
import org.springframework.http.client.ClientHttpRequestExecution;
|
||||
import org.springframework.http.client.ClientHttpRequestInterceptor;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
/**
|
||||
* Default {@link ZipkinRestTemplateCustomizer} that provides the GZip compression if
|
||||
* {@link ZipkinProperties#compression} is enabled.
|
||||
*
|
||||
* @author Marcin Grzejszczak
|
||||
*
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public class DefaultZipkinRestTemplateCustomizer implements ZipkinRestTemplateCustomizer {
|
||||
private final ZipkinProperties zipkinProperties;
|
||||
|
||||
public DefaultZipkinRestTemplateCustomizer(
|
||||
ZipkinProperties zipkinProperties) {
|
||||
this.zipkinProperties = zipkinProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void customize(RestTemplate restTemplate) {
|
||||
if (this.zipkinProperties.getCompression().isEnabled()) {
|
||||
restTemplate.getInterceptors().add(0, new GZipInterceptor());
|
||||
}
|
||||
}
|
||||
|
||||
private class GZipInterceptor implements ClientHttpRequestInterceptor {
|
||||
|
||||
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws
|
||||
IOException {
|
||||
request.getHeaders().add("Content-Encoding", "gzip");
|
||||
ByteArrayOutputStream gzipped = new ByteArrayOutputStream();
|
||||
try (GZIPOutputStream compressor = new GZIPOutputStream(gzipped)) {
|
||||
compressor.write(body);
|
||||
}
|
||||
return execution.execute(request, gzipped.toByteArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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.zipkin2;
|
||||
|
||||
import zipkin2.Endpoint;
|
||||
|
||||
/**
|
||||
* Strategy for locating a zipkin {@linkplain Endpoint} for the current process.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public interface EndpointLocator {
|
||||
|
||||
Endpoint local();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package org.springframework.cloud.sleuth.zipkin2;
|
||||
|
||||
import org.springframework.cloud.sleuth.metric.SpanMetricReporter;
|
||||
|
||||
import zipkin2.reporter.ReporterMetrics;
|
||||
|
||||
final class ReporterMetricsAdapter implements ReporterMetrics {
|
||||
private final SpanMetricReporter spanMetricReporter;
|
||||
|
||||
public ReporterMetricsAdapter(SpanMetricReporter spanMetricReporter) {
|
||||
this.spanMetricReporter = spanMetricReporter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void incrementMessages() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void incrementMessagesDropped(Throwable throwable) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void incrementSpans(int i) {
|
||||
this.spanMetricReporter.incrementAcceptedSpans(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void incrementSpanBytes(int i) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void incrementMessageBytes(int i) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void incrementSpansDropped(int i) {
|
||||
this.spanMetricReporter.incrementDroppedSpans(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateQueuedSpans(int i) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateQueuedBytes(int i) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package org.springframework.cloud.sleuth.zipkin2;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.RequestEntity;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import zipkin2.Call;
|
||||
import zipkin2.Callback;
|
||||
import zipkin2.CheckResult;
|
||||
import zipkin2.Span;
|
||||
import zipkin2.codec.BytesEncoder;
|
||||
import zipkin2.codec.Encoding;
|
||||
import zipkin2.reporter.BytesMessageEncoder;
|
||||
import zipkin2.reporter.Sender;
|
||||
import zipkin2.reporter.internal.BaseCall;
|
||||
|
||||
import static zipkin2.codec.SpanBytesEncoder.JSON_V2;
|
||||
|
||||
final class RestTemplateSender extends Sender {
|
||||
final RestTemplate restTemplate;
|
||||
final String url;
|
||||
|
||||
final Encoding encoding;
|
||||
final MediaType mediaType;
|
||||
final BytesMessageEncoder messageEncoder;
|
||||
|
||||
RestTemplateSender(RestTemplate restTemplate, String baseUrl, BytesEncoder<Span> encoder) {
|
||||
this.restTemplate = restTemplate;
|
||||
this.encoding = encoder.encoding();
|
||||
if (encoder.equals(JSON_V2)) {
|
||||
this.mediaType = MediaType.APPLICATION_JSON;
|
||||
this.url = baseUrl + (baseUrl.endsWith("/") ? "" : "/") + "api/v2/spans";
|
||||
} else if (this.encoding == Encoding.JSON) {
|
||||
this.mediaType = MediaType.APPLICATION_JSON;
|
||||
this.url = baseUrl + (baseUrl.endsWith("/") ? "" : "/") + "api/v1/spans";
|
||||
} else {
|
||||
throw new UnsupportedOperationException("Unsupported encoding: " + this.encoding.name());
|
||||
}
|
||||
this.messageEncoder = BytesMessageEncoder.forEncoding(this.encoding);
|
||||
}
|
||||
|
||||
@Override public Encoding encoding() {
|
||||
return this.encoding;
|
||||
}
|
||||
|
||||
@Override public int messageMaxBytes() {
|
||||
// This will drop a span larger than 5MiB. Note: values like 512KiB benchmark better.
|
||||
return 5 * 1024 * 1024;
|
||||
}
|
||||
|
||||
@Override public int messageSizeInBytes(List<byte[]> spans) {
|
||||
return encoding().listSizeInBytes(spans);
|
||||
}
|
||||
|
||||
/**
|
||||
* close is typically called from a different thread
|
||||
*/
|
||||
transient boolean closeCalled;
|
||||
|
||||
@Override public Call<Void> sendSpans(List<byte[]> encodedSpans) {
|
||||
if (this.closeCalled)
|
||||
throw new IllegalStateException("close");
|
||||
return new HttpPostCall(this.messageEncoder.encode(encodedSpans));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an empty json message to the configured endpoint.
|
||||
*/
|
||||
@Override public CheckResult check() {
|
||||
try {
|
||||
post(new byte[] { '[', ']' });
|
||||
return CheckResult.OK;
|
||||
}
|
||||
catch (Exception e) {
|
||||
return CheckResult.failed(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void close() {
|
||||
this.closeCalled = true;
|
||||
}
|
||||
|
||||
void post(byte[] json) {
|
||||
HttpHeaders httpHeaders = new HttpHeaders();
|
||||
httpHeaders.setContentType(this.mediaType);
|
||||
RequestEntity<byte[]> requestEntity = new RequestEntity<>(json, httpHeaders,
|
||||
HttpMethod.POST, URI.create(this.url));
|
||||
this.restTemplate.exchange(requestEntity, String.class);
|
||||
}
|
||||
|
||||
class HttpPostCall extends BaseCall<Void> {
|
||||
private final byte[] message;
|
||||
|
||||
HttpPostCall(byte[] message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
@Override protected Void doExecute() throws IOException {
|
||||
post(this.message);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override protected void doEnqueue(Callback<Void> callback) {
|
||||
try {
|
||||
post(this.message);
|
||||
callback.onSuccess(null);
|
||||
}
|
||||
catch (RuntimeException | Error e) {
|
||||
callback.onError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public Call<Void> clone() {
|
||||
return new HttpPostCall(this.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
/*
|
||||
* 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.zipkin2;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.autoconfigure.web.ServerProperties;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.discovery.DiscoveryClient;
|
||||
import org.springframework.cloud.client.serviceregistry.Registration;
|
||||
import org.springframework.cloud.commons.util.InetUtils;
|
||||
import org.springframework.cloud.sleuth.Sampler;
|
||||
import org.springframework.cloud.sleuth.SpanAdjuster;
|
||||
import org.springframework.cloud.sleuth.SpanReporter;
|
||||
import org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration;
|
||||
import org.springframework.cloud.sleuth.metric.SpanMetricReporter;
|
||||
import org.springframework.cloud.sleuth.sampler.PercentageBasedSampler;
|
||||
import org.springframework.cloud.sleuth.sampler.SamplerProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.web.client.RequestCallback;
|
||||
import org.springframework.web.client.ResponseExtractor;
|
||||
import org.springframework.web.client.RestClientException;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import zipkin2.Span;
|
||||
import zipkin2.reporter.AsyncReporter;
|
||||
import zipkin2.reporter.Reporter;
|
||||
import zipkin2.reporter.Sender;
|
||||
|
||||
/**
|
||||
* {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration Auto-configuration}
|
||||
* enables reporting to Zipkin via HTTP. Has a default {@link Sampler} set as
|
||||
* {@link PercentageBasedSampler}.
|
||||
*
|
||||
* The {@link ZipkinRestTemplateCustomizer} allows you to customize the {@link RestTemplate}
|
||||
* that is used to send Spans to Zipkin. Its default implementation - {@link DefaultZipkinRestTemplateCustomizer}
|
||||
* adds the GZip compression.
|
||||
*
|
||||
* @author Spencer Gibb
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @see PercentageBasedSampler
|
||||
* @see ZipkinRestTemplateCustomizer
|
||||
* @see DefaultZipkinRestTemplateCustomizer
|
||||
*/
|
||||
@Configuration
|
||||
@EnableConfigurationProperties({ZipkinProperties.class, SamplerProperties.class})
|
||||
@ConditionalOnProperty(value = "spring.zipkin.enabled", matchIfMissing = true)
|
||||
@AutoConfigureBefore(TraceAutoConfiguration.class)
|
||||
public class ZipkinAutoConfiguration {
|
||||
|
||||
@Autowired(required = false) List<SpanAdjuster> spanAdjusters = new ArrayList<>();
|
||||
@Autowired ZipkinUrlExtractor extractor;
|
||||
|
||||
/**
|
||||
* Accepts a sender so you can plug-in any standard one. Returns a Reporter so you can also
|
||||
* replace with a standard one.
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public Reporter<Span> reporter(
|
||||
SpanMetricReporter spanMetricReporter,
|
||||
ZipkinProperties zipkin,
|
||||
Sender sender
|
||||
) {
|
||||
return AsyncReporter.builder(sender)
|
||||
.queuedMaxSpans(1000) // historical constraint. Note: AsyncReporter supports memory bounds
|
||||
.messageTimeout(zipkin.getMessageTimeout(), TimeUnit.SECONDS)
|
||||
.metrics(new ReporterMetricsAdapter(spanMetricReporter))
|
||||
.build(zipkin.getEncoder());
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public Sender restTemplateSender(ZipkinProperties zipkin,
|
||||
ZipkinRestTemplateCustomizer zipkinRestTemplateCustomizer) {
|
||||
RestTemplate restTemplate = new ZipkinRestTemplateWrapper(zipkin, this.extractor);
|
||||
zipkinRestTemplateCustomizer.customize(restTemplate);
|
||||
return new RestTemplateSender(restTemplate, zipkin.getBaseUrl(), zipkin.getEncoder());
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnClass(DiscoveryClient.class)
|
||||
static class DiscoveryClientZipkinUrlExtractorConfiguration {
|
||||
|
||||
@Autowired(required = false) DiscoveryClient discoveryClient;
|
||||
|
||||
@Bean
|
||||
ZipkinUrlExtractor zipkinUrlExtractor() {
|
||||
final DiscoveryClient discoveryClient = this.discoveryClient;
|
||||
return new ZipkinUrlExtractor() {
|
||||
@Override
|
||||
public URI zipkinUrl(ZipkinProperties zipkinProperties) {
|
||||
if (discoveryClient != null) {
|
||||
URI uri = URI.create(zipkinProperties.getBaseUrl());
|
||||
String host = uri.getHost();
|
||||
List<ServiceInstance> instances = discoveryClient.getInstances(host);
|
||||
if (!instances.isEmpty()) {
|
||||
return instances.get(0).getUri();
|
||||
}
|
||||
}
|
||||
return URI.create(zipkinProperties.getBaseUrl());
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnMissingClass("org.springframework.cloud.client.discovery.DiscoveryClient")
|
||||
static class DefaultZipkinUrlExtractorConfiguration {
|
||||
@Bean
|
||||
ZipkinUrlExtractor zipkinUrlExtractor() {
|
||||
return new ZipkinUrlExtractor() {
|
||||
@Override
|
||||
public URI zipkinUrl(ZipkinProperties zipkinProperties) {
|
||||
return URI.create(zipkinProperties.getBaseUrl());
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public ZipkinRestTemplateCustomizer zipkinRestTemplateCustomizer(ZipkinProperties zipkinProperties) {
|
||||
return new DefaultZipkinRestTemplateCustomizer(zipkinProperties);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public Sampler defaultTraceSampler(SamplerProperties config) {
|
||||
return new PercentageBasedSampler(config);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SpanReporter zipkinSpanListener(Reporter<Span> reporter, EndpointLocator endpointLocator,
|
||||
Environment environment) {
|
||||
return new ZipkinSpanListener(reporter, endpointLocator, environment, this.spanAdjusters);
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnMissingBean(EndpointLocator.class)
|
||||
@ConditionalOnProperty(value = "spring.zipkin.locator.discovery.enabled", havingValue = "false", matchIfMissing = true)
|
||||
protected static class DefaultEndpointLocatorConfiguration {
|
||||
|
||||
@Autowired(required=false)
|
||||
private ServerProperties serverProperties;
|
||||
|
||||
@Autowired
|
||||
private ZipkinProperties zipkinProperties;
|
||||
|
||||
@Autowired(required=false)
|
||||
private InetUtils inetUtils;
|
||||
|
||||
@Value("${spring.application.name:unknown}")
|
||||
private String appName;
|
||||
|
||||
@Bean
|
||||
public EndpointLocator zipkinEndpointLocator() {
|
||||
return new DefaultEndpointLocator(null, this.serverProperties, this.appName,
|
||||
this.zipkinProperties, this.inetUtils);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnClass(Registration.class)
|
||||
@ConditionalOnMissingBean(EndpointLocator.class)
|
||||
@ConditionalOnProperty(value = "spring.zipkin.locator.discovery.enabled", havingValue = "true")
|
||||
protected static class RegistrationEndpointLocatorConfiguration {
|
||||
|
||||
@Autowired(required=false)
|
||||
private ServerProperties serverProperties;
|
||||
|
||||
@Autowired
|
||||
private ZipkinProperties zipkinProperties;
|
||||
|
||||
@Autowired(required=false)
|
||||
private InetUtils inetUtils;
|
||||
|
||||
@Value("${spring.application.name:unknown}")
|
||||
private String appName;
|
||||
|
||||
@Autowired(required=false)
|
||||
private Registration registration;
|
||||
|
||||
@Bean
|
||||
public EndpointLocator zipkinEndpointLocator() {
|
||||
return new DefaultEndpointLocator(this.registration, this.serverProperties, this.appName,
|
||||
this.zipkinProperties, this.inetUtils);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal interface to provide a way to retrieve Zipkin URI. If there's no discovery client
|
||||
* then this value will be taken from the properties. Otherwise host will be assumed to
|
||||
* be a service id.
|
||||
*/
|
||||
interface ZipkinUrlExtractor {
|
||||
URI zipkinUrl(ZipkinProperties zipkinProperties);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves at runtime where the Zipkin server is. If there's no discovery client then
|
||||
* {@link URI} from the properties is taken. Otherwise service discovery is pinged
|
||||
* for current Zipkin address.
|
||||
*/
|
||||
class ZipkinRestTemplateWrapper extends RestTemplate {
|
||||
|
||||
private static final Log log = LogFactory.getLog(ZipkinRestTemplateWrapper.class);
|
||||
|
||||
private final ZipkinProperties zipkinProperties;
|
||||
private final ZipkinUrlExtractor extractor;
|
||||
|
||||
ZipkinRestTemplateWrapper(ZipkinProperties zipkinProperties,
|
||||
ZipkinUrlExtractor extractor) {
|
||||
this.zipkinProperties = zipkinProperties;
|
||||
this.extractor = extractor;
|
||||
}
|
||||
|
||||
@Override protected <T> T doExecute(URI originalUrl, HttpMethod method,
|
||||
RequestCallback requestCallback,
|
||||
ResponseExtractor<T> responseExtractor) throws RestClientException {
|
||||
URI uri = this.extractor.zipkinUrl(this.zipkinProperties);
|
||||
URI newUri = resolvedZipkinUri(originalUrl, uri);
|
||||
return super.doExecute(newUri, method, requestCallback, responseExtractor);
|
||||
}
|
||||
|
||||
private URI resolvedZipkinUri(URI originalUrl, URI resolvedZipkinUri) {
|
||||
try {
|
||||
return new URI(resolvedZipkinUri.getScheme(), resolvedZipkinUri.getUserInfo(),
|
||||
resolvedZipkinUri.getHost(), resolvedZipkinUri.getPort(), originalUrl.getPath(),
|
||||
originalUrl.getQuery(), originalUrl.getFragment());
|
||||
} catch (URISyntaxException e) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Failed to create the new URI from original [" + originalUrl + "] and new one [" + resolvedZipkinUri + "]");
|
||||
}
|
||||
return originalUrl;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
* 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.zipkin2;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
import zipkin2.codec.SpanBytesEncoder;
|
||||
|
||||
/**
|
||||
* Zipkin settings
|
||||
*
|
||||
* @author Spencer Gibb
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@ConfigurationProperties("spring.zipkin")
|
||||
public class ZipkinProperties {
|
||||
/** URL of the zipkin query server instance. You can also provide
|
||||
* the service id of the Zipkin server if Zipkin's registered in
|
||||
* service discovery (e.g. http://zipkinserver/)
|
||||
*/
|
||||
private String baseUrl = "http://localhost:9411/";
|
||||
/**
|
||||
* Enables sending spans to Zipkin
|
||||
*/
|
||||
private boolean enabled = true;
|
||||
/**
|
||||
* Timeout in seconds before pending spans will be sent in batches to Zipkin
|
||||
*/
|
||||
private int messageTimeout = 1;
|
||||
/**
|
||||
* Encoding type of spans sent to Zipkin. Set to {@link SpanBytesEncoder#JSON_V1} if your server
|
||||
* is not recent.
|
||||
*/
|
||||
private SpanBytesEncoder encoder = SpanBytesEncoder.JSON_V2;
|
||||
|
||||
/**
|
||||
* Configuration related to compressions of spans sent to Zipkin
|
||||
*/
|
||||
private Compression compression = new Compression();
|
||||
|
||||
private Service service = new Service();
|
||||
|
||||
private Locator locator = new Locator();
|
||||
|
||||
public Locator getLocator() {
|
||||
return this.locator;
|
||||
}
|
||||
|
||||
public String getBaseUrl() {
|
||||
return this.baseUrl;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return this.enabled;
|
||||
}
|
||||
|
||||
public int getMessageTimeout() {
|
||||
return this.messageTimeout;
|
||||
}
|
||||
|
||||
public Compression getCompression() {
|
||||
return this.compression;
|
||||
}
|
||||
|
||||
public Service getService() {
|
||||
return this.service;
|
||||
}
|
||||
|
||||
public void setBaseUrl(String baseUrl) {
|
||||
this.baseUrl = baseUrl;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public void setMessageTimeout(int messageTimeout) {
|
||||
this.messageTimeout = messageTimeout;
|
||||
}
|
||||
|
||||
public void setCompression(Compression compression) {
|
||||
this.compression = compression;
|
||||
}
|
||||
|
||||
public void setService(Service service) {
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
public void setLocator(Locator locator) {
|
||||
this.locator = locator;
|
||||
}
|
||||
|
||||
public SpanBytesEncoder getEncoder() {
|
||||
return this.encoder;
|
||||
}
|
||||
|
||||
public void setEncoder(SpanBytesEncoder encoder) {
|
||||
this.encoder = encoder;
|
||||
}
|
||||
|
||||
/** When enabled, spans are gzipped before sent to the zipkin server */
|
||||
public static class Compression {
|
||||
|
||||
private boolean enabled = false;
|
||||
|
||||
public boolean isEnabled() {
|
||||
return this.enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
}
|
||||
|
||||
/** When set will override the default {@code spring.application.name} value of the service id */
|
||||
public static class Service {
|
||||
|
||||
/** The name of the service, from which the Span was sent via HTTP, that should appear in Zipkin */
|
||||
private String name;
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
/** Configuration related to locating of the host name from service discovery.
|
||||
* This property is NOT related to finding Zipkin via Service Disovery.
|
||||
* To do so use the {@link ZipkinProperties#baseUrl} property with the
|
||||
* service name set inside the URL.
|
||||
*/
|
||||
public static class Locator {
|
||||
|
||||
private Discovery discovery;
|
||||
|
||||
public Discovery getDiscovery() {
|
||||
return this.discovery;
|
||||
}
|
||||
|
||||
public void setDiscovery(Discovery discovery) {
|
||||
this.discovery = discovery;
|
||||
}
|
||||
|
||||
public static class Discovery {
|
||||
|
||||
/** Enabling of locating the host name via service discovery */
|
||||
private boolean enabled;
|
||||
|
||||
public boolean isEnabled() {
|
||||
return this.enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2013-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.zipkin2;
|
||||
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
/**
|
||||
* Implementations customize the {@link RestTemplate} used to report spans to Zipkin.
|
||||
* For example, they can add an additional header needed by their environment.
|
||||
*
|
||||
* <p>Implementors must gzip according to {@link ZipkinProperties.Compression},
|
||||
* for example by using the {@link DefaultZipkinRestTemplateCustomizer}.
|
||||
*
|
||||
* @author Marcin Grzejszczak
|
||||
*
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public interface ZipkinRestTemplateCustomizer {
|
||||
|
||||
void customize(RestTemplate restTemplate);
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
/*
|
||||
* 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.zipkin2;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.cloud.commons.util.IdUtils;
|
||||
import org.springframework.cloud.sleuth.Log;
|
||||
import org.springframework.cloud.sleuth.Span;
|
||||
import org.springframework.cloud.sleuth.SpanAdjuster;
|
||||
import org.springframework.cloud.sleuth.SpanReporter;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.util.StringUtils;
|
||||
import zipkin2.Endpoint;
|
||||
import zipkin2.reporter.Reporter;
|
||||
|
||||
/**
|
||||
* Listener of Sleuth events. Reports to Zipkin via {@link Reporter}.
|
||||
*/
|
||||
public class ZipkinSpanListener implements SpanReporter {
|
||||
private static final org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory
|
||||
.getLog(ZipkinSpanListener.class);
|
||||
|
||||
private final Reporter<zipkin2.Span> reporter;
|
||||
private final Environment environment;
|
||||
private final List<SpanAdjuster> spanAdjusters;
|
||||
/**
|
||||
* Endpoint is the visible IP address of this service, the port it is listening on and
|
||||
* the service name from discovery.
|
||||
*/
|
||||
// Visible for testing
|
||||
final EndpointLocator endpointLocator;
|
||||
|
||||
public ZipkinSpanListener(Reporter<zipkin2.Span> reporter, EndpointLocator endpointLocator,
|
||||
Environment environment, List<SpanAdjuster> spanAdjusters) {
|
||||
this.reporter = reporter;
|
||||
this.endpointLocator = endpointLocator;
|
||||
this.environment = environment;
|
||||
this.spanAdjusters = spanAdjusters;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 tags based on data from Span object.
|
||||
* </ul>
|
||||
*/
|
||||
// Visible for testing
|
||||
zipkin2.Span convert(Span span) {
|
||||
//TODO: Consider adding support for the debug flag (related to #496)
|
||||
Span convertedSpan = span;
|
||||
for (SpanAdjuster adjuster : this.spanAdjusters) {
|
||||
convertedSpan = adjuster.adjust(span);
|
||||
}
|
||||
zipkin2.Span.Builder zipkinSpan = zipkin2.Span.newBuilder();
|
||||
zipkinSpan.localEndpoint(this.endpointLocator.local());
|
||||
processLogs(convertedSpan, zipkinSpan);
|
||||
addZipkinTags(zipkinSpan, convertedSpan);
|
||||
if (zipkinSpan.kind() != null && this.environment != null) {
|
||||
setInstanceIdIfPresent(zipkinSpan, Span.INSTANCEID);
|
||||
}
|
||||
zipkinSpan.shared(convertedSpan.isShared());
|
||||
zipkinSpan.timestamp(convertedSpan.getBegin() * 1000L);
|
||||
if (!convertedSpan.isRunning()) { // duration is authoritative, only write when the span stopped
|
||||
zipkinSpan.duration(calculateDurationInMicros(convertedSpan));
|
||||
}
|
||||
zipkinSpan.traceId(convertedSpan.traceIdString());
|
||||
if (convertedSpan.getParents().size() > 0) {
|
||||
if (convertedSpan.getParents().size() > 1) {
|
||||
log.error("Zipkin doesn't support spans with multiple parents. Omitting "
|
||||
+ "other parents for " + convertedSpan);
|
||||
}
|
||||
zipkinSpan.parentId(Span.idToHex(convertedSpan.getParents().get(0)));
|
||||
}
|
||||
zipkinSpan.id(Span.idToHex(convertedSpan.getSpanId()));
|
||||
if (StringUtils.hasText(convertedSpan.getName())) {
|
||||
zipkinSpan.name(convertedSpan.getName());
|
||||
}
|
||||
return zipkinSpan.build();
|
||||
}
|
||||
|
||||
// Instead of going through the list of logs multiple times we're doing it only once
|
||||
void processLogs(Span span, zipkin2.Span.Builder zipkinSpan) {
|
||||
for (Log log : span.logs()) {
|
||||
String event = log.getEvent();
|
||||
long micros = log.getTimestamp() * 1000L;
|
||||
// don't add redundant annotations to the output
|
||||
if (event.length() == 2) {
|
||||
if (event.equals("cs")) {
|
||||
zipkinSpan.kind(zipkin2.Span.Kind.CLIENT);
|
||||
} else if (event.equals("sr")) {
|
||||
zipkinSpan.kind(zipkin2.Span.Kind.SERVER);
|
||||
} else if (event.equals("ss")) {
|
||||
zipkinSpan.kind(zipkin2.Span.Kind.SERVER);
|
||||
} else if (event.equals("cr")) {
|
||||
zipkinSpan.kind(zipkin2.Span.Kind.CLIENT);
|
||||
} else if (event.equals("ms")) {
|
||||
zipkinSpan.kind(zipkin2.Span.Kind.PRODUCER);
|
||||
} else if (event.equals("mr")) {
|
||||
zipkinSpan.kind(zipkin2.Span.Kind.CONSUMER);
|
||||
} else {
|
||||
zipkinSpan.addAnnotation(micros, event);
|
||||
}
|
||||
} else {
|
||||
zipkinSpan.addAnnotation(micros, event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setInstanceIdIfPresent(zipkin2.Span.Builder zipkinSpan, String key) {
|
||||
String property = defaultInstanceId();
|
||||
if (StringUtils.hasText(property)) {
|
||||
zipkinSpan.putTag(key, property);
|
||||
}
|
||||
}
|
||||
|
||||
String defaultInstanceId() {
|
||||
return IdUtils.getDefaultInstanceId(this.environment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds tags from the sleuth Span
|
||||
*/
|
||||
private void addZipkinTags(zipkin2.Span.Builder zipkinSpan, Span span) {
|
||||
Endpoint.Builder remoteEndpoint = Endpoint.newBuilder();
|
||||
boolean shouldAddRemote = false;
|
||||
// don't add redundant tags to the output
|
||||
for (Map.Entry<String, String> e : span.tags().entrySet()) {
|
||||
String key = e.getKey();
|
||||
if (key.equals("peer.service")) {
|
||||
shouldAddRemote = true;
|
||||
remoteEndpoint.serviceName(e.getValue());
|
||||
} else if (key.equals("peer.ipv4") || key.equals("peer.ipv6")) {
|
||||
shouldAddRemote = true;
|
||||
remoteEndpoint.ip(e.getValue());
|
||||
} else if (key.equals("peer.port")) {
|
||||
shouldAddRemote = true;
|
||||
try {
|
||||
remoteEndpoint.port(Integer.parseInt(e.getValue()));
|
||||
} catch (NumberFormatException ignored) {
|
||||
}
|
||||
} else {
|
||||
zipkinSpan.putTag(e.getKey(), e.getValue());
|
||||
}
|
||||
}
|
||||
if (shouldAddRemote) {
|
||||
zipkinSpan.remoteEndpoint(remoteEndpoint.build());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 long calculateDurationInMicros(Span span) {
|
||||
Log clientSend = hasLog(Span.CLIENT_SEND, span);
|
||||
Log clientReceived = hasLog(Span.CLIENT_RECV, span);
|
||||
if (clientSend != null && clientReceived != null) {
|
||||
return (clientReceived.getTimestamp() - clientSend.getTimestamp()) * 1000;
|
||||
}
|
||||
return span.getAccumulatedMicros();
|
||||
}
|
||||
|
||||
private Log hasLog(String logName, Span span) {
|
||||
for (Log log : span.logs()) {
|
||||
if (logName.equals(log.getEvent())) {
|
||||
return log;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void report(Span span) {
|
||||
if (span.isExportable()) {
|
||||
this.reporter.report(convert(span));
|
||||
} else {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("The span " + span + " will not be sent to Zipkin due to sampling");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# Auto Configuration
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
org.springframework.cloud.sleuth.zipkin2.ZipkinAutoConfiguration
|
||||
@@ -0,0 +1,176 @@
|
||||
package org.springframework.cloud.sleuth.zipkin2;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.ServerProperties;
|
||||
import org.springframework.cloud.client.serviceregistry.Registration;
|
||||
import org.springframework.cloud.commons.util.InetUtils;
|
||||
import org.springframework.cloud.commons.util.InetUtilsProperties;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Matcin Wielgus
|
||||
*/
|
||||
public class DefaultEndpointLocatorConfigurationTest {
|
||||
|
||||
@Test
|
||||
public void endpointLocatorShouldDefaultToServerPropertiesEndpointLocator() {
|
||||
ConfigurableApplicationContext ctxt = new SpringApplication(
|
||||
EmptyConfiguration.class).run("--spring.jmx.enabled=false");
|
||||
assertThat(ctxt.getBean(EndpointLocator.class))
|
||||
.isInstanceOf(DefaultEndpointLocator.class);
|
||||
ctxt.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void endpointLocatorShouldDefaultToServerPropertiesEndpointLocatorEvenWhenDiscoveryClientPresent() {
|
||||
ConfigurableApplicationContext ctxt = new SpringApplication(
|
||||
ConfigurationWithRegistration.class).run("--spring.jmx.enabled=false");
|
||||
assertThat(ctxt.getBean(EndpointLocator.class))
|
||||
.isInstanceOf(DefaultEndpointLocator.class);
|
||||
ctxt.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void endpointLocatorShouldRespectExistingEndpointLocator() {
|
||||
ConfigurableApplicationContext ctxt = new SpringApplication(
|
||||
ConfigurationWithCustomLocator.class).run("--spring.jmx.enabled=false");
|
||||
assertThat(ctxt.getBean(EndpointLocator.class))
|
||||
.isSameAs(ConfigurationWithCustomLocator.locator);
|
||||
ctxt.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void endpointLocatorShouldSetServiceNameToServiceId() {
|
||||
ConfigurableApplicationContext ctxt = new SpringApplication(
|
||||
ConfigurationWithRegistration.class).run("--spring.jmx.enabled=false",
|
||||
"--spring.zipkin.locator.discovery.enabled=true");
|
||||
assertThat(ctxt.getBean(EndpointLocator.class).local().serviceName())
|
||||
.isEqualTo("from-registration");
|
||||
ctxt.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void endpointLocatorShouldAcceptServiceNameOverride() {
|
||||
ConfigurableApplicationContext ctxt = new SpringApplication(
|
||||
ConfigurationWithRegistration.class).run("--spring.jmx.enabled=false",
|
||||
"--spring.zipkin.locator.discovery.enabled=true",
|
||||
"--spring.zipkin.service.name=foo");
|
||||
assertThat(ctxt.getBean(EndpointLocator.class).local().serviceName())
|
||||
.isEqualTo("foo");
|
||||
ctxt.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void endpointLocatorShouldRespectExistingEndpointLocatorEvenWhenAskedToBeDiscovery() {
|
||||
ConfigurableApplicationContext ctxt = new SpringApplication(
|
||||
ConfigurationWithRegistration.class,
|
||||
ConfigurationWithCustomLocator.class).run("--spring.jmx.enabled=false",
|
||||
"--spring.zipkin.locator.discovery.enabled=true");
|
||||
assertThat(ctxt.getBean(EndpointLocator.class))
|
||||
.isSameAs(ConfigurationWithCustomLocator.locator);
|
||||
ctxt.close();
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableAutoConfiguration
|
||||
public static class EmptyConfiguration {
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableAutoConfiguration
|
||||
public static class ConfigurationWithRegistration {
|
||||
@Bean public Registration getRegistration() {
|
||||
return () -> "from-registration";
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableAutoConfiguration
|
||||
public static class ConfigurationWithCustomLocator {
|
||||
static EndpointLocator locator = Mockito.mock(EndpointLocator.class);
|
||||
|
||||
@Bean public EndpointLocator getEndpointLocator() {
|
||||
return locator;
|
||||
}
|
||||
}
|
||||
public static final byte[] ADDRESS1234 = { 1, 2, 3, 4 };
|
||||
|
||||
@Test
|
||||
public void portDefaultsTo8080() throws UnknownHostException {
|
||||
DefaultEndpointLocator locator = new DefaultEndpointLocator(null,
|
||||
new ServerProperties(), "unknown", new ZipkinProperties(),
|
||||
localAddress(ADDRESS1234));
|
||||
|
||||
assertThat(locator.local().port()).isEqualTo(8080);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void portFromServerProperties() throws UnknownHostException {
|
||||
ServerProperties properties = new ServerProperties();
|
||||
properties.setPort(1234);
|
||||
|
||||
DefaultEndpointLocator locator = new DefaultEndpointLocator(null,
|
||||
properties, "unknown", new ZipkinProperties(),localAddress(ADDRESS1234));
|
||||
|
||||
assertThat(locator.local().port()).isEqualTo(1234);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void portDefaultsToLocalhost() throws UnknownHostException {
|
||||
DefaultEndpointLocator locator = new DefaultEndpointLocator(null,
|
||||
new ServerProperties(), "unknown", new ZipkinProperties(), localAddress(ADDRESS1234));
|
||||
|
||||
assertThat(locator.local().ipv4()).isEqualTo("1.2.3.4");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hostFromServerPropertiesIp() throws UnknownHostException {
|
||||
ServerProperties properties = new ServerProperties();
|
||||
properties.setAddress(InetAddress.getByAddress(ADDRESS1234));
|
||||
|
||||
DefaultEndpointLocator locator = new DefaultEndpointLocator(null,
|
||||
properties, "unknown", new ZipkinProperties(),
|
||||
localAddress(new byte[] { 4, 4, 4, 4 }));
|
||||
|
||||
assertThat(locator.local().ipv4()).isEqualTo("1.2.3.4");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void appNameFromProperties() throws UnknownHostException {
|
||||
ServerProperties properties = new ServerProperties();
|
||||
ZipkinProperties zipkinProperties = new ZipkinProperties();
|
||||
zipkinProperties.getService().setName("foo");
|
||||
|
||||
DefaultEndpointLocator locator = new DefaultEndpointLocator(null,
|
||||
properties, "unknown", zipkinProperties,localAddress(ADDRESS1234));
|
||||
|
||||
assertThat(locator.local().serviceName()).isEqualTo("foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void negativePortFromServerProperties() throws UnknownHostException {
|
||||
ServerProperties properties = new ServerProperties();
|
||||
properties.setPort(-1);
|
||||
|
||||
DefaultEndpointLocator locator = new DefaultEndpointLocator(null,
|
||||
properties, "unknown", new ZipkinProperties(),localAddress(ADDRESS1234));
|
||||
|
||||
assertThat(locator.local().port()).isEqualTo(8080);
|
||||
}
|
||||
|
||||
private InetUtils localAddress(byte[] address) throws UnknownHostException {
|
||||
InetUtils mocked = Mockito.spy(new InetUtils(new InetUtilsProperties()));
|
||||
Mockito.when(mocked.findFirstNonLoopbackAddress())
|
||||
.thenReturn(InetAddress.getByAddress(address));
|
||||
return mocked;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* 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.zipkin2;
|
||||
|
||||
import okhttp3.mockwebserver.MockWebServer;
|
||||
import okhttp3.mockwebserver.RecordedRequest;
|
||||
import org.awaitility.Awaitility;
|
||||
import org.junit.After;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.cloud.sleuth.Span;
|
||||
import org.springframework.cloud.sleuth.SpanReporter;
|
||||
import org.springframework.cloud.sleuth.metric.TraceMetricsAutoConfiguration;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
|
||||
import static org.assertj.core.api.BDDAssertions.then;
|
||||
import static org.springframework.boot.test.util.EnvironmentTestUtils.addEnvironment;
|
||||
|
||||
/**
|
||||
* Not using {@linkplain SpringBootTest} as we need to change properties per test.
|
||||
*/
|
||||
public class ZipkinAutoConfigurationTests {
|
||||
|
||||
@Rule public ExpectedException thrown = ExpectedException.none();
|
||||
@Rule public MockWebServer server = new MockWebServer();
|
||||
Span span =
|
||||
Span.builder().traceIdHigh(1L).traceId(2L).spanId(3L).name("foo").tag("foo", "bar").build();
|
||||
|
||||
AnnotationConfigApplicationContext context;
|
||||
|
||||
@After
|
||||
public void close() {
|
||||
if (context != null) {
|
||||
context.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void defaultsToV2Endpoint() throws Exception {
|
||||
context = new AnnotationConfigApplicationContext();
|
||||
addEnvironment(context, "spring.zipkin.base-url:" + server.url("/"));
|
||||
context.register(
|
||||
PropertyPlaceholderAutoConfiguration.class,
|
||||
TraceMetricsAutoConfiguration.class,
|
||||
ZipkinAutoConfiguration.class);
|
||||
context.refresh();
|
||||
|
||||
SpanReporter spanReporter = context.getBean(SpanReporter.class);
|
||||
spanReporter.report(span);
|
||||
|
||||
Awaitility.await().untilAsserted(() -> then(server.getRequestCount()).isGreaterThan(0));
|
||||
|
||||
RecordedRequest request = server.takeRequest();
|
||||
then(request.getPath()).isEqualTo("/api/v2/spans");
|
||||
then(request.getBody().readUtf8()).contains("localEndpoint");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void encoderDirectsEndpoint() throws Exception {
|
||||
context = new AnnotationConfigApplicationContext();
|
||||
addEnvironment(
|
||||
context, "spring.zipkin.base-url:" + server.url("/"), "spring.zipkin.encoder:JSON_V1");
|
||||
context.register(
|
||||
PropertyPlaceholderAutoConfiguration.class,
|
||||
TraceMetricsAutoConfiguration.class,
|
||||
ZipkinAutoConfiguration.class);
|
||||
context.refresh();
|
||||
|
||||
SpanReporter spanReporter = context.getBean(SpanReporter.class);
|
||||
spanReporter.report(span);
|
||||
|
||||
Awaitility.await().untilAsserted(() -> then(server.getRequestCount()).isGreaterThan(0));
|
||||
|
||||
RecordedRequest request = server.takeRequest();
|
||||
then(request.getPath()).isEqualTo("/api/v1/spans");
|
||||
then(request.getBody().readUtf8()).contains("binaryAnnotations");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package org.springframework.cloud.sleuth.zipkin2;
|
||||
|
||||
import static org.assertj.core.api.BDDAssertions.then;
|
||||
import static org.springframework.cloud.sleuth.zipkin2.ZipkinDiscoveryClientTests.ZIPKIN_RULE;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.awaitility.Awaitility;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.discovery.DiscoveryClient;
|
||||
import org.springframework.cloud.sleuth.Span;
|
||||
import org.springframework.cloud.sleuth.SpanReporter;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
import zipkin.junit.ZipkinRule;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(classes = ZipkinDiscoveryClientTests.Config.class,
|
||||
properties = "spring.zipkin.baseUrl=http://zipkin/")
|
||||
public class ZipkinDiscoveryClientTests {
|
||||
|
||||
@ClassRule public static ZipkinRule ZIPKIN_RULE = new ZipkinRule();
|
||||
|
||||
@Autowired SpanReporter spanReporter;
|
||||
|
||||
@Test
|
||||
public void shouldUseDiscoveryClientToFindZipkinUrlIfPresent() throws Exception {
|
||||
Span span = Span.builder().traceIdHigh(1L).traceId(2L).spanId(3L).name("foo")
|
||||
.build();
|
||||
|
||||
this.spanReporter.report(span);
|
||||
|
||||
Awaitility.await().untilAsserted(() -> then(ZIPKIN_RULE.httpRequestCount()).isGreaterThan(0));
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableAutoConfiguration
|
||||
static class Config {
|
||||
@Bean
|
||||
DiscoveryClient client() {
|
||||
return new ZipkinDiscoveryClient();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class ZipkinDiscoveryClient implements DiscoveryClient {
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServiceInstance getLocalServiceInstance() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ServiceInstance> getInstances(String s) {
|
||||
if ("zipkin".equals(s)) {
|
||||
return Collections.singletonList(new ServiceInstance() {
|
||||
@Override
|
||||
public String getServiceId() {
|
||||
return "zipkin";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHost() {
|
||||
return "localhost";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPort() {
|
||||
return URI.create(ZIPKIN_RULE.httpUrl()).getPort();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSecure() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI getUri() {
|
||||
return URI.create(ZIPKIN_RULE.httpUrl());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getMetadata() {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getServices() {
|
||||
return Collections.singletonList("zipkin");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,397 @@
|
||||
/*
|
||||
* 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.zipkin2;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.cloud.sleuth.Sampler;
|
||||
import org.springframework.cloud.sleuth.Span;
|
||||
import org.springframework.cloud.sleuth.SpanAdjuster;
|
||||
import org.springframework.cloud.sleuth.SpanReporter;
|
||||
import org.springframework.cloud.sleuth.Tracer;
|
||||
import org.springframework.cloud.sleuth.sampler.AlwaysSampler;
|
||||
import org.springframework.cloud.sleuth.zipkin2.ZipkinSpanListenerTests.TestConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.mock.env.MockEnvironment;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import zipkin2.Endpoint;
|
||||
import zipkin2.reporter.Reporter;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.entry;
|
||||
import static org.assertj.core.api.BDDAssertions.then;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
@SpringBootTest(classes = TestConfiguration.class)
|
||||
@RunWith(SpringRunner.class)
|
||||
public class ZipkinSpanListenerTests {
|
||||
|
||||
@Autowired Tracer tracer;
|
||||
@Autowired TestConfiguration test;
|
||||
@Autowired ZipkinSpanListener spanListener;
|
||||
@Autowired Reporter<zipkin2.Span> spanReporter;
|
||||
@Autowired MockEnvironment mockEnvironment;
|
||||
@Autowired EndpointLocator endpointLocator;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
this.test.zipkinSpans.clear();
|
||||
}
|
||||
|
||||
Span parent = Span.builder().traceId(1L).name("http:parent").remote(true).build();
|
||||
|
||||
/** Sleuth timestamps are millisecond granularity while zipkin is microsecond. */
|
||||
@Test
|
||||
public void convertsTimestampToMicrosecondsAndSetsDurationToAccumulatedMicros() {
|
||||
Span span = Span.builder().traceId(1L).name("http:api").build();
|
||||
long start = System.currentTimeMillis();
|
||||
span.logEvent("hystrix/retry"); // System.currentTimeMillis
|
||||
span.stop();
|
||||
|
||||
zipkin2.Span result = this.spanListener.convert(span);
|
||||
|
||||
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.builder().traceId(1L).name("http:api").build();
|
||||
span.logEvent(Span.CLIENT_SEND);
|
||||
Thread.sleep(10);
|
||||
span.logEvent(Span.CLIENT_RECV);
|
||||
Thread.sleep(20);
|
||||
span.stop();
|
||||
|
||||
zipkin2.Span result = this.spanListener.convert(span);
|
||||
|
||||
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 span = Span.builder().traceId(1L).name("http:api").build();
|
||||
zipkin2.Span result = this.spanListener.convert(span);
|
||||
|
||||
assertThat(result.timestamp())
|
||||
.isGreaterThan(0); // sanity check it did start
|
||||
assertThat(result.duration())
|
||||
.isNull();
|
||||
}
|
||||
|
||||
/** Sleuth host corresponds to localEndpoint in zipkin. */
|
||||
@Test
|
||||
public void spanIncludesLocalEndpoint() {
|
||||
this.parent.logEvent("hystrix/retry");
|
||||
this.parent.tag("spring-boot/version", "1.3.1.RELEASE");
|
||||
|
||||
zipkin2.Span result = this.spanListener.convert(this.parent);
|
||||
|
||||
assertThat(result.localEndpoint())
|
||||
.isEqualTo(this.spanListener.endpointLocator.local());
|
||||
}
|
||||
|
||||
/** zipkin's Endpoint.serviceName should never be null. */
|
||||
@Test
|
||||
public void localEndpointIncludesServiceName() {
|
||||
assertThat(this.spanListener.endpointLocator.local().serviceName())
|
||||
.isNotEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void spanWithoutAnnotationsStillHasEndpoint() {
|
||||
Span context = this.tracer.createSpan("http:foo");
|
||||
this.tracer.close(context);
|
||||
assertEquals(1, this.test.zipkinSpans.size());
|
||||
assertThat(this.test.zipkinSpans.get(0).localEndpoint())
|
||||
.isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rpcAnnotations() {
|
||||
Span context = this.tracer.createSpan("http:child", this.parent);
|
||||
context.logEvent(Span.CLIENT_SEND);
|
||||
logServerReceived(this.parent);
|
||||
logServerSent(this.spanListener, this.parent);
|
||||
this.tracer.close(context);
|
||||
assertEquals(2, this.test.zipkinSpans.size());
|
||||
}
|
||||
|
||||
void logServerReceived(Span parent) {
|
||||
if (parent != null && parent.isRemote()) {
|
||||
parent.logEvent(Span.SERVER_RECV);
|
||||
}
|
||||
}
|
||||
|
||||
void logServerSent(SpanReporter spanReporter, Span parent) {
|
||||
if (parent != null && parent.isRemote()) {
|
||||
parent.logEvent(Span.SERVER_SEND);
|
||||
spanReporter.report(parent);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void localComponentNotNeeded() {
|
||||
this.parent.logEvent("hystrix/retry");
|
||||
this.parent.stop();
|
||||
|
||||
zipkin2.Span result = this.spanListener.convert(this.parent);
|
||||
|
||||
assertThat(result.tags())
|
||||
.isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addsRemoteEndpointWhenClientLogIsPresentAndPeerServiceIsPresent() {
|
||||
this.parent.logEvent("cs");
|
||||
this.parent.tag(Span.SPAN_PEER_SERVICE_TAG_NAME, "fooservice");
|
||||
this.parent.stop();
|
||||
|
||||
zipkin2.Span result = this.spanListener.convert(this.parent);
|
||||
|
||||
assertThat(result.remoteEndpoint())
|
||||
.isEqualTo(Endpoint.newBuilder().serviceName("fooservice").build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doesNotAddRemoteEndpointTagIfClientLogIsPresent() {
|
||||
this.parent.logEvent("cs");
|
||||
this.parent.stop();
|
||||
|
||||
zipkin2.Span result = this.spanListener.convert(this.parent);
|
||||
|
||||
assertThat(result.remoteEndpoint())
|
||||
.isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void converts128BitTraceId() {
|
||||
Span span = Span.builder().traceIdHigh(1L).traceId(2L).spanId(3L).name("foo").build();
|
||||
|
||||
zipkin2.Span result = this.spanListener.convert(span);
|
||||
|
||||
assertThat(result.traceId())
|
||||
.isEqualTo("00000000000000010000000000000002");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReuseServerAddressTag() {
|
||||
this.parent.logEvent("cs");
|
||||
this.parent.tag(Span.SPAN_PEER_SERVICE_TAG_NAME, "fooservice");
|
||||
this.parent.stop();
|
||||
|
||||
zipkin2.Span result = this.spanListener.convert(this.parent);
|
||||
|
||||
assertThat(result.remoteEndpoint())
|
||||
.isEqualTo(Endpoint.newBuilder().serviceName("fooservice").build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldNotReportToZipkinWhenSpanIsNotExportable() {
|
||||
Span span = Span.builder().exportable(false).build();
|
||||
|
||||
this.spanListener.report(span);
|
||||
|
||||
assertThat(this.test.zipkinSpans).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldAddClientServiceIdTagWhenSpanContainsRpcEvent() {
|
||||
this.parent.logEvent(Span.CLIENT_SEND);
|
||||
this.mockEnvironment.setProperty("vcap.application.instance_id", "foo");
|
||||
|
||||
zipkin2.Span result = this.spanListener.convert(this.parent);
|
||||
|
||||
assertThat(result.tags())
|
||||
.containsExactly(entry(Span.INSTANCEID, "foo"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldNotAddAnyServiceIdTagWhenSpanContainsRpcEventAndThereIsNoEnvironment() {
|
||||
this.parent.logEvent(Span.CLIENT_RECV);
|
||||
ZipkinSpanListener spanListener = new ZipkinSpanListener(this.spanReporter,
|
||||
this.endpointLocator, null, new ArrayList<>());
|
||||
|
||||
zipkin2.Span result = spanListener.convert(this.parent);
|
||||
|
||||
assertThat(result.tags())
|
||||
.isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_adjust_span_before_reporting_it() {
|
||||
this.parent.logEvent(Span.CLIENT_RECV);
|
||||
ZipkinSpanListener spanListener = new ZipkinSpanListener(this.spanReporter,
|
||||
this.endpointLocator, null, Collections.<SpanAdjuster>singletonList(
|
||||
span -> Span.builder().from(span).name("foo").build())) {
|
||||
@Override String defaultInstanceId() {
|
||||
return "foo";
|
||||
}
|
||||
};
|
||||
|
||||
zipkin2.Span result = spanListener.convert(this.parent);
|
||||
|
||||
assertThat(result.name()).isEqualTo("foo");
|
||||
}
|
||||
|
||||
/** Zipkin will take care of processing the shared flag wrt timestamp authority */
|
||||
@Test
|
||||
public void setsSharedFlag() {
|
||||
Span span = Span.builder()
|
||||
.name("foo")
|
||||
.exportable(false)
|
||||
.remote(false)
|
||||
.shared(true)
|
||||
.build();
|
||||
span.stop();
|
||||
|
||||
zipkin2.Span result = this.spanListener.convert(span);
|
||||
|
||||
assertThat(result.duration()).isNotNull();
|
||||
assertThat(result.timestamp()).isNotNull();
|
||||
assertThat(result.shared()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_create_server_span() {
|
||||
Span span = tracer.createSpan("get");
|
||||
span.logEvent("sr");
|
||||
span.logEvent("ss");
|
||||
span.stop();
|
||||
|
||||
zipkin2.Span result = this.spanListener.convert(span);
|
||||
|
||||
then(result.kind()).isEqualTo(zipkin2.Span.Kind.SERVER);
|
||||
then(result.annotations()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_create_client_span() {
|
||||
Span span = tracer.createSpan("redis");
|
||||
span.logEvent("cs");
|
||||
span.logEvent("cr");
|
||||
span.stop();
|
||||
|
||||
zipkin2.Span result = this.spanListener.convert(span);
|
||||
|
||||
then(result.kind()).isEqualTo(zipkin2.Span.Kind.CLIENT);
|
||||
then(result.annotations()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_create_produceer_span() {
|
||||
Span span = tracer.createSpan("produce");
|
||||
span.logEvent("ms");
|
||||
span.stop();
|
||||
|
||||
zipkin2.Span result = this.spanListener.convert(span);
|
||||
|
||||
then(result.kind()).isEqualTo(zipkin2.Span.Kind.PRODUCER);
|
||||
then(result.annotations()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_create_consumer_span() {
|
||||
Span span = tracer.createSpan("consume");
|
||||
span.logEvent("mr");
|
||||
span.stop();
|
||||
|
||||
zipkin2.Span result = this.spanListener.convert(span);
|
||||
|
||||
then(result.kind()).isEqualTo(zipkin2.Span.Kind.CONSUMER);
|
||||
then(result.annotations()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_change_the_service_name_in_zipkin_to_the_manually_provided_one() {
|
||||
// tag::service_name[]
|
||||
Span span = tracer.createSpan("redis");
|
||||
try {
|
||||
span.tag("redis.op", "get");
|
||||
span.tag("lc", "redis");
|
||||
span.logEvent("cs");
|
||||
// call redis service e.g
|
||||
// return (SomeObj) redisTemplate.opsForHash().get("MYHASH", someObjKey);
|
||||
} finally {
|
||||
span.tag("peer.service", "redis");
|
||||
span.tag("peer.ipv4", "1.2.3.4");
|
||||
span.tag("peer.port", "1234");
|
||||
span.logEvent("cr");
|
||||
span.stop();
|
||||
}
|
||||
// end::service_name[]
|
||||
|
||||
zipkin2.Span result = this.spanListener.convert(span);
|
||||
|
||||
then(result.remoteEndpoint())
|
||||
.isEqualTo(Endpoint.newBuilder().serviceName("redis").ip("1.2.3.4").port(1234).build());
|
||||
then(result.tags())
|
||||
.doesNotContainKeys("peer.service", "peer.ipv4", "peer.port");
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableAutoConfiguration
|
||||
protected static class TestConfiguration {
|
||||
|
||||
private List<zipkin2.Span> zipkinSpans = new ArrayList<>();
|
||||
|
||||
@Bean
|
||||
public Sampler sampler() {
|
||||
return new AlwaysSampler();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Reporter<zipkin2.Span> reporter() {
|
||||
return this.zipkinSpans::add;
|
||||
}
|
||||
|
||||
@Bean @Primary MockEnvironment mockEnvironment() {
|
||||
return new MockEnvironment();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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.zipkin2;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration(classes = ZipkinWithDisabledSleuthTests.Config.class)
|
||||
@TestPropertySource(properties = "spring.sleuth.enabled=false")
|
||||
public class ZipkinWithDisabledSleuthTests {
|
||||
|
||||
@Test public void shouldStartContext() {
|
||||
|
||||
}
|
||||
|
||||
@EnableAutoConfiguration
|
||||
static class Config {
|
||||
}
|
||||
}
|
||||
27
spring-cloud-starter-zipkin2/pom.xml
Normal file
27
spring-cloud-starter-zipkin2/pom.xml
Normal file
@@ -0,0 +1,27 @@
|
||||
<?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>
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-sleuth</artifactId>
|
||||
<version>1.3.0.BUILD-SNAPSHOT</version>
|
||||
<relativePath>..</relativePath>
|
||||
</parent>
|
||||
<artifactId>spring-cloud-starter-zipkin2</artifactId>
|
||||
<name>spring-cloud-starter-zipkin2</name>
|
||||
<description>Spring Cloud Starter Zipkin v2</description>
|
||||
<properties>
|
||||
<main.basedir>${basedir}/../..</main.basedir>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-sleuth</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-sleuth-zipkin2</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1 @@
|
||||
provides: spring-platform-netflix-core, eureka-client
|
||||
Reference in New Issue
Block a user