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:
committed by
GitHub
parent
43a8bcfe2e
commit
9641d4ef98
@@ -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>
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user