Possibility to customize HTTP Client for Zipkin calls

* Added RestTemplate for Zipkin
* Added ZipkinRestTemplateCustomizer to allow possibility to customize RestTemplate
* Addded DefaultZipkinRestTemplateCustomizer with GZip compression interceptor
This commit is contained in:
Marcin Grzejszczak
2016-06-28 12:11:40 +02:00
committed by GitHub
parent 43a8bcfe2e
commit 9641d4ef98
6 changed files with 162 additions and 49 deletions

View File

@@ -37,6 +37,10 @@
<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>

View File

@@ -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.zipkin;
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());
}
}
}

View File

@@ -1,12 +1,9 @@
package org.springframework.cloud.sleuth.zipkin;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.Flushable;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URI;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.LinkedList;
@@ -16,9 +13,14 @@ import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.zip.GZIPOutputStream;
import org.apache.commons.logging.Log;
import org.springframework.cloud.sleuth.metric.SpanMetricReporter;
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.RestClientException;
import org.springframework.web.client.RestTemplate;
import zipkin.Codec;
import zipkin.Span;
@@ -37,23 +39,23 @@ public final class HttpZipkinSpanReporter
.getLog(HttpZipkinSpanReporter.class);
private static final Charset UTF_8 = Charset.forName("UTF-8");
private final RestTemplate restTemplate;
private final String url;
private final BlockingQueue<Span> pending = new LinkedBlockingQueue<>(1000);
private final Flusher flusher; // Nullable for testing
private final boolean compressionEnabled;
private final SpanMetricReporter spanMetricReporter;
/**
* @param restTemplate {@link RestTemplate} used for sending requests to Zipkin
* @param baseUrl URL of the zipkin query server instance. Like: http://localhost:9411/
* @param flushInterval in seconds. 0 implies spans are {@link #flush() flushed} externally.
* @param compressionEnabled compress spans using gzip before posting to the zipkin server.
* @param spanMetricReporter service to count number of accepted / dropped spans
*/
public HttpZipkinSpanReporter(String baseUrl, int flushInterval, boolean compressionEnabled,
public HttpZipkinSpanReporter(RestTemplate restTemplate, String baseUrl, int flushInterval,
SpanMetricReporter spanMetricReporter) {
this.restTemplate = restTemplate;
this.url = baseUrl + (baseUrl.endsWith("/") ? "" : "/") + "api/v1/spans";
this.flusher = flushInterval > 0 ? new Flusher(this, flushInterval) : null;
this.compressionEnabled = compressionEnabled;
this.spanMetricReporter = spanMetricReporter;
}
@@ -95,7 +97,7 @@ public final class HttpZipkinSpanReporter
try {
postSpans(json);
}
catch (IOException e) {
catch (RestClientException e) {
if (log.isDebugEnabled()) { // don't pollute logs unless debug is on.
// TODO: logger test
log.debug(
@@ -128,34 +130,11 @@ public final class HttpZipkinSpanReporter
}
}
void postSpans(byte[] json) throws IOException {
// intentionally not closing the connection, so as to use keep-alives
HttpURLConnection connection = (HttpURLConnection) new URL(this.url).openConnection();
connection.setRequestMethod("POST");
connection.addRequestProperty("Content-Type", "application/json");
if (this.compressionEnabled) {
connection.addRequestProperty("Content-Encoding", "gzip");
ByteArrayOutputStream gzipped = new ByteArrayOutputStream();
try (GZIPOutputStream compressor = new GZIPOutputStream(gzipped)) {
compressor.write(json);
}
json = gzipped.toByteArray();
}
connection.setDoOutput(true);
connection.setFixedLengthStreamingMode(json.length);
connection.getOutputStream().write(json);
try (InputStream in = connection.getInputStream()) {
while (in.read() != -1); // skip
}
catch (IOException e) {
try (InputStream err = connection.getErrorStream()) {
if (err != null) { // possible, if the connection was dropped
while (err.read() != -1); // skip
}
}
throw e;
}
void postSpans(byte[] json) {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
RequestEntity<byte[]> requestEntity = new RequestEntity<>(json, httpHeaders, HttpMethod.POST, URI.create(this.url));
this.restTemplate.exchange(requestEntity, String.class);
}
/**

View File

@@ -34,15 +34,23 @@ 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.web.client.RestTemplate;
/**
* {@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})
@@ -52,9 +60,18 @@ public class ZipkinAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public ZipkinSpanReporter reporter(SpanMetricReporter spanMetricReporter, ZipkinProperties zipkin) {
return new HttpZipkinSpanReporter(zipkin.getBaseUrl(), zipkin.getFlushInterval(),
zipkin.getCompression().isEnabled(), spanMetricReporter);
public ZipkinSpanReporter reporter(SpanMetricReporter spanMetricReporter, ZipkinProperties zipkin,
ZipkinRestTemplateCustomizer zipkinRestTemplateCustomizer) {
RestTemplate restTemplate = new RestTemplate();
zipkinRestTemplateCustomizer.customize(restTemplate);
return new HttpZipkinSpanReporter(restTemplate, zipkin.getBaseUrl(), zipkin.getFlushInterval(),
spanMetricReporter);
}
@Bean
@ConditionalOnMissingBean
public ZipkinRestTemplateCustomizer zipkinRestTemplateCustomizer(ZipkinProperties zipkinProperties) {
return new DefaultZipkinRestTemplateCustomizer(zipkinProperties);
}
@Bean

View File

@@ -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.zipkin;
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);
}

View File

@@ -4,8 +4,9 @@ import org.junit.Rule;
import org.junit.Test;
import org.springframework.cloud.sleuth.metric.CounterServiceBasedSpanMetricReporter;
import org.springframework.cloud.sleuth.metric.SpanMetricReporter;
import zipkin.Span;
import org.springframework.web.client.RestTemplate;
import zipkin.Span;
import zipkin.junit.HttpFailure;
import zipkin.junit.ZipkinRule;
@@ -18,11 +19,10 @@ public class HttpZipkinSpanReporterTest {
InMemorySpanCounter inMemorySpanCounter = new InMemorySpanCounter();
SpanMetricReporter spanMetricReporter = new CounterServiceBasedSpanMetricReporter("accepted", "dropped",
this.inMemorySpanCounter);
RestTemplate restTemplate = defaultRestTemplate();
HttpZipkinSpanReporter reporter = new HttpZipkinSpanReporter(
this.zipkin.httpUrl(),
HttpZipkinSpanReporter reporter = new HttpZipkinSpanReporter(restTemplate, this.zipkin.httpUrl(),
0, // so that tests can drive flushing explicitly
false, // disable compression
this.spanMetricReporter
);
@@ -68,10 +68,8 @@ public class HttpZipkinSpanReporterTest {
@Test
public void postsCompressedSpans() throws Exception {
this.reporter = new HttpZipkinSpanReporter(
this.zipkin.httpUrl(),
this.reporter = new HttpZipkinSpanReporter(restTemplateWithCompression(), this.zipkin.httpUrl(),
0, // so that tests can drive flushing explicitly
false, // enable compression
this.spanMetricReporter
);
@@ -116,4 +114,20 @@ public class HttpZipkinSpanReporterTest {
static Span span(long traceId, String spanName) {
return Span.builder().traceId(traceId).id(traceId).name(spanName).build();
}
private RestTemplate restTemplate(ZipkinProperties zipkinProperties) {
RestTemplate restTemplate = new RestTemplate();
new DefaultZipkinRestTemplateCustomizer(zipkinProperties).customize(restTemplate);
return restTemplate;
}
private RestTemplate defaultRestTemplate() {
return restTemplate(new ZipkinProperties());
}
private RestTemplate restTemplateWithCompression() {
ZipkinProperties zipkinProperties = new ZipkinProperties();
zipkinProperties.getCompression().setEnabled(true);
return restTemplate(zipkinProperties);
}
}