Migrate to Brave 6 and Zipkin Reporter 3
Signed-off-by: Adrian Cole <adrian@tetrate.io> See gh-39049
This commit is contained in:
parent
3a565e4e4b
commit
4b0bed23b0
|
@ -30,7 +30,6 @@ import brave.context.slf4j.MDCScopeDecorator;
|
|||
import brave.propagation.CurrentTraceContext.ScopeDecorator;
|
||||
import brave.propagation.Propagation;
|
||||
import brave.propagation.Propagation.Factory;
|
||||
import brave.propagation.Propagation.KeyFactory;
|
||||
import io.micrometer.tracing.brave.bridge.BraveBaggageManager;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
|
@ -99,12 +98,11 @@ class BravePropagationConfigurations {
|
|||
return builder;
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private Factory createThrowAwayFactory() {
|
||||
return new Factory() {
|
||||
|
||||
@Override
|
||||
public <K> Propagation<K> create(KeyFactory<K> keyFactory) {
|
||||
public Propagation<String> get() {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -22,7 +22,6 @@ import java.util.List;
|
|||
import java.util.function.Predicate;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import brave.internal.propagation.StringPropagationAdapter;
|
||||
import brave.propagation.B3Propagation;
|
||||
import brave.propagation.Propagation;
|
||||
import brave.propagation.Propagation.Factory;
|
||||
|
@ -71,9 +70,8 @@ class CompositePropagationFactory extends Propagation.Factory {
|
|||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public <K> Propagation<K> create(Propagation.KeyFactory<K> keyFactory) {
|
||||
return StringPropagationAdapter.create(this.propagation, keyFactory);
|
||||
public Propagation<String> get() {
|
||||
return this.propagation;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -18,135 +18,81 @@ package org.springframework.boot.actuate.autoconfigure.tracing.zipkin;
|
|||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.util.Collections;
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
|
||||
import zipkin2.Call;
|
||||
import zipkin2.CheckResult;
|
||||
import zipkin2.codec.Encoding;
|
||||
import zipkin2.reporter.BytesMessageEncoder;
|
||||
import zipkin2.reporter.ClosedSenderException;
|
||||
import zipkin2.reporter.Sender;
|
||||
import zipkin2.reporter.BaseHttpSender;
|
||||
import zipkin2.reporter.BytesMessageSender;
|
||||
import zipkin2.reporter.Encoding;
|
||||
import zipkin2.reporter.HttpEndpointSupplier.Factory;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.util.unit.DataSize;
|
||||
|
||||
/**
|
||||
* A Zipkin {@link Sender} that uses an HTTP client to send JSON spans. Supports automatic
|
||||
* compression with gzip.
|
||||
* A Zipkin {@link BytesMessageSender} that uses an HTTP client to send JSON spans.
|
||||
* Supports automatic compression with gzip.
|
||||
*
|
||||
* @author Moritz Halbritter
|
||||
* @author Stefan Bratanov
|
||||
*/
|
||||
abstract class HttpSender extends Sender {
|
||||
abstract class HttpSender extends BaseHttpSender<URI, byte[]> {
|
||||
|
||||
private static final DataSize MESSAGE_MAX_SIZE = DataSize.ofKilobytes(512);
|
||||
/**
|
||||
* Only use gzip compression on data which is bigger than this in bytes.
|
||||
*/
|
||||
private static final DataSize COMPRESSION_THRESHOLD = DataSize.ofKilobytes(1);
|
||||
|
||||
private volatile boolean closed;
|
||||
|
||||
@Override
|
||||
public Encoding encoding() {
|
||||
return Encoding.JSON;
|
||||
HttpSender(Encoding encoding, Factory endpointSupplierFactory, String endpoint) {
|
||||
super(encoding, endpointSupplierFactory, endpoint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int messageMaxBytes() {
|
||||
return (int) MESSAGE_MAX_SIZE.toBytes();
|
||||
protected URI newEndpoint(String endpoint) {
|
||||
return URI.create(endpoint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int messageSizeInBytes(List<byte[]> encodedSpans) {
|
||||
return encoding().listSizeInBytes(encodedSpans);
|
||||
protected byte[] newBody(List<byte[]> list) {
|
||||
return this.encoding.encode(list);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int messageSizeInBytes(int encodedSizeInBytes) {
|
||||
return encoding().listSizeInBytes(encodedSizeInBytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CheckResult check() {
|
||||
try {
|
||||
sendSpans(Collections.emptyList()).execute();
|
||||
return CheckResult.OK;
|
||||
protected void postSpans(URI endpoint, byte[] body) throws IOException {
|
||||
HttpHeaders headers = getDefaultHeaders();
|
||||
if (needsCompression(body)) {
|
||||
body = compress(body);
|
||||
headers.set("Content-Encoding", "gzip");
|
||||
}
|
||||
catch (IOException | RuntimeException ex) {
|
||||
return CheckResult.failed(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
this.closed = true;
|
||||
postSpans(endpoint, headers, body);
|
||||
}
|
||||
|
||||
/**
|
||||
* The returned {@link HttpPostCall} will send span(s) as a POST to a zipkin endpoint
|
||||
* when executed.
|
||||
* @param batchedEncodedSpans list of encoded spans as a byte array
|
||||
* @return an instance of a Zipkin {@link Call} which can be executed
|
||||
* This will send span(s) as a POST to a zipkin endpoint.
|
||||
* @param endpoint the POST endpoint. For example, http://localhost:9411/api/v2/spans.
|
||||
* @param headers headers for the POST request
|
||||
* @param body list of possibly gzipped, encoded spans.
|
||||
*/
|
||||
protected abstract HttpPostCall sendSpans(byte[] batchedEncodedSpans);
|
||||
abstract void postSpans(URI endpoint, HttpHeaders headers, byte[] body);
|
||||
|
||||
@Override
|
||||
public Call<Void> sendSpans(List<byte[]> encodedSpans) {
|
||||
if (this.closed) {
|
||||
throw new ClosedSenderException();
|
||||
}
|
||||
return sendSpans(BytesMessageEncoder.JSON.encode(encodedSpans));
|
||||
HttpHeaders getDefaultHeaders() {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.set("b3", "0");
|
||||
headers.set("Content-Type", this.encoding.mediaType());
|
||||
return headers;
|
||||
}
|
||||
|
||||
abstract static class HttpPostCall extends Call.Base<Void> {
|
||||
private boolean needsCompression(byte[] body) {
|
||||
return body.length > COMPRESSION_THRESHOLD.toBytes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Only use gzip compression on data which is bigger than this in bytes.
|
||||
*/
|
||||
private static final DataSize COMPRESSION_THRESHOLD = DataSize.ofKilobytes(1);
|
||||
|
||||
private final byte[] body;
|
||||
|
||||
HttpPostCall(byte[] body) {
|
||||
this.body = body;
|
||||
private byte[] compress(byte[] input) throws IOException {
|
||||
ByteArrayOutputStream result = new ByteArrayOutputStream();
|
||||
try (GZIPOutputStream gzip = new GZIPOutputStream(result)) {
|
||||
gzip.write(input);
|
||||
}
|
||||
|
||||
protected byte[] getBody() {
|
||||
if (needsCompression()) {
|
||||
return compress(this.body);
|
||||
}
|
||||
return this.body;
|
||||
}
|
||||
|
||||
protected byte[] getUncompressedBody() {
|
||||
return this.body;
|
||||
}
|
||||
|
||||
protected HttpHeaders getDefaultHeaders() {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.set("b3", "0");
|
||||
headers.set("Content-Type", "application/json");
|
||||
if (needsCompression()) {
|
||||
headers.set("Content-Encoding", "gzip");
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
private boolean needsCompression() {
|
||||
return this.body.length > COMPRESSION_THRESHOLD.toBytes();
|
||||
}
|
||||
|
||||
private byte[] compress(byte[] input) {
|
||||
ByteArrayOutputStream result = new ByteArrayOutputStream();
|
||||
try (GZIPOutputStream gzip = new GZIPOutputStream(result)) {
|
||||
gzip.write(input);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new UncheckedIOException(ex);
|
||||
}
|
||||
return result.toByteArray();
|
||||
}
|
||||
|
||||
return result.toByteArray();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,14 +16,11 @@
|
|||
|
||||
package org.springframework.boot.actuate.autoconfigure.tracing.zipkin;
|
||||
|
||||
import zipkin2.Span;
|
||||
import zipkin2.codec.BytesEncoder;
|
||||
import zipkin2.codec.SpanBytesEncoder;
|
||||
import zipkin2.reporter.Sender;
|
||||
import zipkin2.reporter.BytesMessageSender;
|
||||
import zipkin2.reporter.Encoding;
|
||||
|
||||
import org.springframework.boot.actuate.autoconfigure.tracing.zipkin.ZipkinConfigurations.BraveConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.tracing.zipkin.ZipkinConfigurations.OpenTelemetryConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.tracing.zipkin.ZipkinConfigurations.ReporterConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.tracing.zipkin.ZipkinConfigurations.SenderConfiguration;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
|
@ -44,9 +41,8 @@ import org.springframework.context.annotation.Import;
|
|||
* @since 3.0.0
|
||||
*/
|
||||
@AutoConfiguration(after = RestTemplateAutoConfiguration.class)
|
||||
@ConditionalOnClass(Sender.class)
|
||||
@Import({ SenderConfiguration.class, ReporterConfiguration.class, BraveConfiguration.class,
|
||||
OpenTelemetryConfiguration.class })
|
||||
@ConditionalOnClass(BytesMessageSender.class)
|
||||
@Import({ SenderConfiguration.class, BraveConfiguration.class, OpenTelemetryConfiguration.class })
|
||||
@EnableConfigurationProperties(ZipkinProperties.class)
|
||||
public class ZipkinAutoConfiguration {
|
||||
|
||||
|
@ -58,8 +54,8 @@ public class ZipkinAutoConfiguration {
|
|||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public BytesEncoder<Span> spanBytesEncoder() {
|
||||
return SpanBytesEncoder.JSON_V2;
|
||||
public Encoding encoding(ZipkinProperties properties) {
|
||||
return properties.getEncoding();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,13 +16,19 @@
|
|||
|
||||
package org.springframework.boot.actuate.autoconfigure.tracing.zipkin;
|
||||
|
||||
import brave.Tag;
|
||||
import brave.Tags;
|
||||
import brave.handler.MutableSpan;
|
||||
import io.opentelemetry.exporter.zipkin.ZipkinSpanExporter;
|
||||
import zipkin2.Span;
|
||||
import zipkin2.codec.BytesEncoder;
|
||||
import zipkin2.reporter.AsyncReporter;
|
||||
import zipkin2.reporter.Reporter;
|
||||
import zipkin2.reporter.Sender;
|
||||
import zipkin2.reporter.brave.ZipkinSpanHandler;
|
||||
import zipkin2.reporter.BytesEncoder;
|
||||
import zipkin2.reporter.BytesMessageSender;
|
||||
import zipkin2.reporter.Encoding;
|
||||
import zipkin2.reporter.HttpEndpointSupplier;
|
||||
import zipkin2.reporter.HttpEndpointSuppliers;
|
||||
import zipkin2.reporter.SpanBytesEncoder;
|
||||
import zipkin2.reporter.brave.AsyncZipkinSpanHandler;
|
||||
import zipkin2.reporter.brave.MutableSpanBytesEncoder;
|
||||
import zipkin2.reporter.urlconnection.URLConnectionSender;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
|
@ -59,15 +65,20 @@ class ZipkinConfigurations {
|
|||
static class UrlConnectionSenderConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(Sender.class)
|
||||
URLConnectionSender urlConnectionSender(ZipkinProperties properties,
|
||||
ObjectProvider<ZipkinConnectionDetails> connectionDetailsProvider) {
|
||||
@ConditionalOnMissingBean(BytesMessageSender.class)
|
||||
URLConnectionSender urlConnectionSender(ZipkinProperties properties, Encoding encoding,
|
||||
ObjectProvider<ZipkinConnectionDetails> connectionDetailsProvider,
|
||||
ObjectProvider<HttpEndpointSupplier.Factory> endpointSupplierFactoryProvider) {
|
||||
ZipkinConnectionDetails connectionDetails = connectionDetailsProvider
|
||||
.getIfAvailable(() -> new PropertiesZipkinConnectionDetails(properties));
|
||||
HttpEndpointSupplier.Factory endpointSupplierFactory = endpointSupplierFactoryProvider
|
||||
.getIfAvailable(HttpEndpointSuppliers::constantFactory);
|
||||
URLConnectionSender.Builder builder = URLConnectionSender.newBuilder();
|
||||
builder.connectTimeout((int) properties.getConnectTimeout().toMillis());
|
||||
builder.readTimeout((int) properties.getReadTimeout().toMillis());
|
||||
builder.endpointSupplierFactory(endpointSupplierFactory);
|
||||
builder.endpoint(connectionDetails.getSpanEndpoint());
|
||||
builder.encoding(encoding);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
|
@ -79,17 +90,21 @@ class ZipkinConfigurations {
|
|||
static class RestTemplateSenderConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(Sender.class)
|
||||
ZipkinRestTemplateSender restTemplateSender(ZipkinProperties properties,
|
||||
@ConditionalOnMissingBean(BytesMessageSender.class)
|
||||
ZipkinRestTemplateSender restTemplateSender(ZipkinProperties properties, Encoding encoding,
|
||||
ObjectProvider<ZipkinRestTemplateBuilderCustomizer> customizers,
|
||||
ObjectProvider<ZipkinConnectionDetails> connectionDetailsProvider) {
|
||||
ObjectProvider<ZipkinConnectionDetails> connectionDetailsProvider,
|
||||
ObjectProvider<HttpEndpointSupplier.Factory> endpointSupplierFactoryProvider) {
|
||||
ZipkinConnectionDetails connectionDetails = connectionDetailsProvider
|
||||
.getIfAvailable(() -> new PropertiesZipkinConnectionDetails(properties));
|
||||
HttpEndpointSupplier.Factory endpointSupplierFactory = endpointSupplierFactoryProvider
|
||||
.getIfAvailable(HttpEndpointSuppliers::constantFactory);
|
||||
RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder()
|
||||
.setConnectTimeout(properties.getConnectTimeout())
|
||||
.setReadTimeout(properties.getReadTimeout());
|
||||
restTemplateBuilder = applyCustomizers(restTemplateBuilder, customizers);
|
||||
return new ZipkinRestTemplateSender(connectionDetails.getSpanEndpoint(), restTemplateBuilder.build());
|
||||
return new ZipkinRestTemplateSender(encoding, endpointSupplierFactory, connectionDetails.getSpanEndpoint(),
|
||||
restTemplateBuilder.build());
|
||||
}
|
||||
|
||||
private RestTemplateBuilder applyCustomizers(RestTemplateBuilder restTemplateBuilder,
|
||||
|
@ -111,42 +126,42 @@ class ZipkinConfigurations {
|
|||
static class WebClientSenderConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(Sender.class)
|
||||
ZipkinWebClientSender webClientSender(ZipkinProperties properties,
|
||||
@ConditionalOnMissingBean(BytesMessageSender.class)
|
||||
ZipkinWebClientSender webClientSender(ZipkinProperties properties, Encoding encoding,
|
||||
ObjectProvider<ZipkinWebClientBuilderCustomizer> customizers,
|
||||
ObjectProvider<ZipkinConnectionDetails> connectionDetailsProvider) {
|
||||
ObjectProvider<ZipkinConnectionDetails> connectionDetailsProvider,
|
||||
ObjectProvider<HttpEndpointSupplier.Factory> endpointSupplierFactoryProvider) {
|
||||
ZipkinConnectionDetails connectionDetails = connectionDetailsProvider
|
||||
.getIfAvailable(() -> new PropertiesZipkinConnectionDetails(properties));
|
||||
HttpEndpointSupplier.Factory endpointSupplierFactory = endpointSupplierFactoryProvider
|
||||
.getIfAvailable(HttpEndpointSuppliers::constantFactory);
|
||||
WebClient.Builder builder = WebClient.builder();
|
||||
customizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
|
||||
return new ZipkinWebClientSender(connectionDetails.getSpanEndpoint(), builder.build(),
|
||||
properties.getConnectTimeout().plus(properties.getReadTimeout()));
|
||||
return new ZipkinWebClientSender(encoding, endpointSupplierFactory, connectionDetails.getSpanEndpoint(),
|
||||
builder.build(), properties.getConnectTimeout().plus(properties.getReadTimeout()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class ReporterConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(Reporter.class)
|
||||
@ConditionalOnBean(Sender.class)
|
||||
AsyncReporter<Span> spanReporter(Sender sender, BytesEncoder<Span> encoder) {
|
||||
return AsyncReporter.builder(sender).build(encoder);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(ZipkinSpanHandler.class)
|
||||
@ConditionalOnClass(AsyncZipkinSpanHandler.class)
|
||||
static class BraveConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(value = MutableSpan.class, parameterizedContainer = BytesEncoder.class)
|
||||
BytesEncoder<MutableSpan> braveSpanEncoder(Encoding encoding,
|
||||
ObjectProvider<Tag<Throwable>> throwableTagProvider) {
|
||||
Tag<Throwable> throwableTag = throwableTagProvider.getIfAvailable(() -> Tags.ERROR);
|
||||
return MutableSpanBytesEncoder.create(encoding, throwableTag);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnBean(Reporter.class)
|
||||
@ConditionalOnBean(BytesMessageSender.class)
|
||||
@ConditionalOnEnabledTracing
|
||||
ZipkinSpanHandler zipkinSpanHandler(Reporter<Span> spanReporter) {
|
||||
return (ZipkinSpanHandler) ZipkinSpanHandler.newBuilder(spanReporter).build();
|
||||
AsyncZipkinSpanHandler asyncZipkinSpanHandler(BytesMessageSender sender,
|
||||
BytesEncoder<MutableSpan> braveSpanEncoder) {
|
||||
return AsyncZipkinSpanHandler.newBuilder(sender).build(braveSpanEncoder);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -155,12 +170,18 @@ class ZipkinConfigurations {
|
|||
@ConditionalOnClass(ZipkinSpanExporter.class)
|
||||
static class OpenTelemetryConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(value = Span.class, parameterizedContainer = BytesEncoder.class)
|
||||
BytesEncoder<Span> zipkinSpanEncoder(Encoding encoding) {
|
||||
return SpanBytesEncoder.forEncoding(encoding);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnBean(Sender.class)
|
||||
@ConditionalOnBean(BytesMessageSender.class)
|
||||
@ConditionalOnEnabledTracing
|
||||
ZipkinSpanExporter zipkinSpanExporter(BytesEncoder<Span> encoder, Sender sender) {
|
||||
return ZipkinSpanExporter.builder().setEncoder(encoder).setSender(sender).build();
|
||||
ZipkinSpanExporter zipkinSpanExporter(BytesMessageSender sender, BytesEncoder<Span> zipkinSpanEncoder) {
|
||||
return ZipkinSpanExporter.builder().setSender(sender).setEncoder(zipkinSpanEncoder).build();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,11 +16,17 @@
|
|||
|
||||
package org.springframework.boot.actuate.autoconfigure.tracing.zipkin;
|
||||
|
||||
import zipkin2.reporter.HttpEndpointSupplier;
|
||||
|
||||
import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails;
|
||||
|
||||
/**
|
||||
* Details required to establish a connection to a Zipkin server.
|
||||
*
|
||||
* <p>
|
||||
* Note: {@linkplain #getSpanEndpoint()} is only read once and passed to a bean of type
|
||||
* {@link HttpEndpointSupplier.Factory} which defaults to no-op (constant).
|
||||
*
|
||||
* @author Moritz Halbritter
|
||||
* @since 3.1.0
|
||||
*/
|
||||
|
|
|
@ -18,6 +18,8 @@ package org.springframework.boot.actuate.autoconfigure.tracing.zipkin;
|
|||
|
||||
import java.time.Duration;
|
||||
|
||||
import zipkin2.reporter.Encoding;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
|
@ -34,6 +36,11 @@ public class ZipkinProperties {
|
|||
*/
|
||||
private String endpoint = "http://localhost:9411/api/v2/spans";
|
||||
|
||||
/**
|
||||
* How to encode the POST body to the Zipkin API.
|
||||
*/
|
||||
private Encoding encoding = Encoding.JSON;
|
||||
|
||||
/**
|
||||
* Connection timeout for requests to Zipkin.
|
||||
*/
|
||||
|
@ -52,6 +59,14 @@ public class ZipkinProperties {
|
|||
this.endpoint = endpoint;
|
||||
}
|
||||
|
||||
public Encoding getEncoding() {
|
||||
return this.encoding;
|
||||
}
|
||||
|
||||
public void setEncoding(Encoding encoding) {
|
||||
this.encoding = encoding;
|
||||
}
|
||||
|
||||
public Duration getConnectTimeout() {
|
||||
return this.connectTimeout;
|
||||
}
|
||||
|
|
|
@ -16,10 +16,13 @@
|
|||
|
||||
package org.springframework.boot.actuate.autoconfigure.tracing.zipkin;
|
||||
|
||||
import zipkin2.Call;
|
||||
import zipkin2.Callback;
|
||||
import java.net.URI;
|
||||
|
||||
import zipkin2.reporter.Encoding;
|
||||
import zipkin2.reporter.HttpEndpointSupplier.Factory;
|
||||
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
|
@ -31,55 +34,18 @@ import org.springframework.web.client.RestTemplate;
|
|||
*/
|
||||
class ZipkinRestTemplateSender extends HttpSender {
|
||||
|
||||
private final String endpoint;
|
||||
|
||||
private final RestTemplate restTemplate;
|
||||
|
||||
ZipkinRestTemplateSender(String endpoint, RestTemplate restTemplate) {
|
||||
this.endpoint = endpoint;
|
||||
ZipkinRestTemplateSender(Encoding encoding, Factory endpointSupplierFactory, String endpoint,
|
||||
RestTemplate restTemplate) {
|
||||
super(encoding, endpointSupplierFactory, endpoint);
|
||||
this.restTemplate = restTemplate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpPostCall sendSpans(byte[] batchedEncodedSpans) {
|
||||
return new RestTemplateHttpPostCall(this.endpoint, batchedEncodedSpans, this.restTemplate);
|
||||
}
|
||||
|
||||
private static class RestTemplateHttpPostCall extends HttpPostCall {
|
||||
|
||||
private final String endpoint;
|
||||
|
||||
private final RestTemplate restTemplate;
|
||||
|
||||
RestTemplateHttpPostCall(String endpoint, byte[] body, RestTemplate restTemplate) {
|
||||
super(body);
|
||||
this.endpoint = endpoint;
|
||||
this.restTemplate = restTemplate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Call<Void> clone() {
|
||||
return new RestTemplateHttpPostCall(this.endpoint, getUncompressedBody(), this.restTemplate);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void doExecute() {
|
||||
HttpEntity<byte[]> request = new HttpEntity<>(getBody(), getDefaultHeaders());
|
||||
this.restTemplate.exchange(this.endpoint, HttpMethod.POST, request, Void.class);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doEnqueue(Callback<Void> callback) {
|
||||
try {
|
||||
doExecute();
|
||||
callback.onSuccess(null);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
callback.onError(ex);
|
||||
}
|
||||
}
|
||||
|
||||
void postSpans(URI endpoint, HttpHeaders headers, byte[] body) {
|
||||
HttpEntity<byte[]> request = new HttpEntity<>(body, headers);
|
||||
this.restTemplate.exchange(endpoint, HttpMethod.POST, request, Void.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,14 +16,13 @@
|
|||
|
||||
package org.springframework.boot.actuate.autoconfigure.tracing.zipkin;
|
||||
|
||||
import java.net.URI;
|
||||
import java.time.Duration;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
import zipkin2.Call;
|
||||
import zipkin2.Callback;
|
||||
import zipkin2.reporter.Encoding;
|
||||
import zipkin2.reporter.HttpEndpointSupplier.Factory;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
/**
|
||||
|
@ -34,68 +33,27 @@ import org.springframework.web.reactive.function.client.WebClient;
|
|||
*/
|
||||
class ZipkinWebClientSender extends HttpSender {
|
||||
|
||||
private final String endpoint;
|
||||
|
||||
private final WebClient webClient;
|
||||
|
||||
private final Duration timeout;
|
||||
|
||||
ZipkinWebClientSender(String endpoint, WebClient webClient, Duration timeout) {
|
||||
this.endpoint = endpoint;
|
||||
ZipkinWebClientSender(Encoding encoding, Factory endpointSupplierFactory, String endpoint, WebClient webClient,
|
||||
Duration timeout) {
|
||||
super(encoding, endpointSupplierFactory, endpoint);
|
||||
this.webClient = webClient;
|
||||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpPostCall sendSpans(byte[] batchedEncodedSpans) {
|
||||
return new WebClientHttpPostCall(this.endpoint, batchedEncodedSpans, this.webClient, this.timeout);
|
||||
}
|
||||
|
||||
private static class WebClientHttpPostCall extends HttpPostCall {
|
||||
|
||||
private final String endpoint;
|
||||
|
||||
private final WebClient webClient;
|
||||
|
||||
private final Duration timeout;
|
||||
|
||||
WebClientHttpPostCall(String endpoint, byte[] body, WebClient webClient, Duration timeout) {
|
||||
super(body);
|
||||
this.endpoint = endpoint;
|
||||
this.webClient = webClient;
|
||||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Call<Void> clone() {
|
||||
return new WebClientHttpPostCall(this.endpoint, getUncompressedBody(), this.webClient, this.timeout);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void doExecute() {
|
||||
sendRequest().block();
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doEnqueue(Callback<Void> callback) {
|
||||
sendRequest().subscribe((entity) -> callback.onSuccess(null), callback::onError);
|
||||
}
|
||||
|
||||
private Mono<ResponseEntity<Void>> sendRequest() {
|
||||
return this.webClient.post()
|
||||
.uri(this.endpoint)
|
||||
.headers(this::addDefaultHeaders)
|
||||
.bodyValue(getBody())
|
||||
.retrieve()
|
||||
.toBodilessEntity()
|
||||
.timeout(this.timeout);
|
||||
}
|
||||
|
||||
private void addDefaultHeaders(HttpHeaders headers) {
|
||||
headers.addAll(getDefaultHeaders());
|
||||
}
|
||||
|
||||
void postSpans(URI endpoint, HttpHeaders headers, byte[] body) {
|
||||
this.webClient.post()
|
||||
.uri(endpoint)
|
||||
.headers((h) -> h.addAll(headers))
|
||||
.bodyValue(body)
|
||||
.retrieve()
|
||||
.toBodilessEntity()
|
||||
.timeout(this.timeout)
|
||||
.block();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -21,7 +21,6 @@ import java.util.HashMap;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import brave.internal.propagation.StringPropagationAdapter;
|
||||
import brave.propagation.Propagation;
|
||||
import brave.propagation.TraceContext;
|
||||
import brave.propagation.TraceContextOrSamplingFlags;
|
||||
|
@ -143,9 +142,8 @@ class CompositePropagationFactoryTests {
|
|||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public <K> Propagation<K> create(Propagation.KeyFactory<K> keyFactory) {
|
||||
return StringPropagationAdapter.create(this, keyFactory);
|
||||
public Propagation<String> get() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -22,7 +22,6 @@ import brave.baggage.BaggagePropagation.FactoryBuilder;
|
|||
import brave.baggage.BaggagePropagationConfig;
|
||||
import brave.propagation.Propagation;
|
||||
import brave.propagation.Propagation.Factory;
|
||||
import brave.propagation.Propagation.KeyFactory;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
@ -50,11 +49,10 @@ class LocalBaggageFieldsTests {
|
|||
assertThat(LocalBaggageFields.empty().asList()).isEmpty();
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private static FactoryBuilder createBuilder() {
|
||||
return BaggagePropagation.newFactoryBuilder(new Factory() {
|
||||
@Override
|
||||
public <K> Propagation<K> create(KeyFactory<K> keyFactory) {
|
||||
public Propagation<String> get() {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright 2012-2024 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
|
||||
*
|
||||
* https://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.boot.actuate.autoconfigure.tracing.zipkin;
|
||||
|
||||
import zipkin2.reporter.Encoding;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.test.context.TestConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
/**
|
||||
* Configures the bean {@linkplain ZipkinAutoConfiguration} would from properties.
|
||||
*/
|
||||
@TestConfiguration(proxyBeanMethods = false)
|
||||
class DefaultEncodingConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
Encoding encoding() {
|
||||
return Encoding.JSON;
|
||||
}
|
||||
|
||||
}
|
|
@ -16,18 +16,16 @@
|
|||
|
||||
package org.springframework.boot.actuate.autoconfigure.tracing.zipkin;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import zipkin2.Call;
|
||||
import zipkin2.Callback;
|
||||
import zipkin2.codec.Encoding;
|
||||
import zipkin2.reporter.Sender;
|
||||
import zipkin2.reporter.BytesMessageSender;
|
||||
import zipkin2.reporter.Encoding;
|
||||
|
||||
class NoopSender extends Sender {
|
||||
class NoopSender extends BytesMessageSender.Base {
|
||||
|
||||
@Override
|
||||
public Encoding encoding() {
|
||||
return Encoding.JSON;
|
||||
NoopSender(Encoding encoding) {
|
||||
super(encoding);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -36,27 +34,11 @@ class NoopSender extends Sender {
|
|||
}
|
||||
|
||||
@Override
|
||||
public int messageSizeInBytes(List<byte[]> encodedSpans) {
|
||||
return encoding().listSizeInBytes(encodedSpans);
|
||||
public void send(List<byte[]> encodedSpans) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Call<Void> sendSpans(List<byte[]> encodedSpans) {
|
||||
return new Call.Base<>() {
|
||||
@Override
|
||||
public Call<Void> clone() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void doExecute() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doEnqueue(Callback<Void> callback) {
|
||||
}
|
||||
};
|
||||
public void close() throws IOException {
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -17,9 +17,7 @@
|
|||
package org.springframework.boot.actuate.autoconfigure.tracing.zipkin;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import zipkin2.Span;
|
||||
import zipkin2.codec.BytesEncoder;
|
||||
import zipkin2.codec.SpanBytesEncoder;
|
||||
import zipkin2.reporter.Encoding;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.test.context.FilteredClassLoader;
|
||||
|
@ -41,21 +39,21 @@ class ZipkinAutoConfigurationTests {
|
|||
|
||||
@Test
|
||||
void shouldSupplyBeans() {
|
||||
this.contextRunner.run((context) -> assertThat(context).hasSingleBean(BytesEncoder.class)
|
||||
this.contextRunner.run((context) -> assertThat(context).hasSingleBean(Encoding.class)
|
||||
.hasSingleBean(PropertiesZipkinConnectionDetails.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotSupplyBeansIfZipkinReporterIsMissing() {
|
||||
this.contextRunner.withClassLoader(new FilteredClassLoader("zipkin2.reporter"))
|
||||
.run((context) -> assertThat(context).doesNotHaveBean(BytesEncoder.class));
|
||||
.run((context) -> assertThat(context).doesNotHaveBean(Encoding.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldBackOffOnCustomBeans() {
|
||||
this.contextRunner.withUserConfiguration(CustomConfiguration.class).run((context) -> {
|
||||
assertThat(context).hasBean("customBytesEncoder");
|
||||
assertThat(context).hasSingleBean(BytesEncoder.class);
|
||||
assertThat(context).hasBean("customEncoding");
|
||||
assertThat(context).hasSingleBean(Encoding.class);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -90,8 +88,8 @@ class ZipkinAutoConfigurationTests {
|
|||
private static final class CustomConfiguration {
|
||||
|
||||
@Bean
|
||||
BytesEncoder<Span> customBytesEncoder() {
|
||||
return SpanBytesEncoder.JSON_V2;
|
||||
Encoding customEncoding() {
|
||||
return Encoding.PROTO3;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,11 +16,17 @@
|
|||
|
||||
package org.springframework.boot.actuate.autoconfigure.tracing.zipkin;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import brave.Tag;
|
||||
import brave.handler.MutableSpan;
|
||||
import brave.handler.SpanHandler;
|
||||
import brave.propagation.TraceContext;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import zipkin2.Span;
|
||||
import zipkin2.reporter.Reporter;
|
||||
import zipkin2.reporter.brave.ZipkinSpanHandler;
|
||||
import zipkin2.reporter.BytesEncoder;
|
||||
import zipkin2.reporter.BytesMessageSender;
|
||||
import zipkin2.reporter.Encoding;
|
||||
import zipkin2.reporter.brave.AsyncZipkinSpanHandler;
|
||||
|
||||
import org.springframework.boot.actuate.autoconfigure.tracing.zipkin.ZipkinConfigurations.BraveConfiguration;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
|
@ -40,61 +46,126 @@ import static org.mockito.Mockito.mock;
|
|||
class ZipkinConfigurationsBraveConfigurationTests {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(BraveConfiguration.class));
|
||||
.withConfiguration(AutoConfigurations.of(DefaultEncodingConfiguration.class, BraveConfiguration.class));
|
||||
|
||||
private final ApplicationContextRunner tracingDisabledContextRunner = this.contextRunner
|
||||
.withPropertyValues("management.tracing.enabled=false");
|
||||
|
||||
@Test
|
||||
void shouldSupplyBeans() {
|
||||
this.contextRunner.withUserConfiguration(ReporterConfiguration.class)
|
||||
.run((context) -> assertThat(context).hasSingleBean(ZipkinSpanHandler.class));
|
||||
this.contextRunner.withUserConfiguration(SenderConfiguration.class)
|
||||
.run((context) -> assertThat(context).hasSingleBean(AsyncZipkinSpanHandler.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotSupplySpanHandlerIfReporterIsMissing() {
|
||||
this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(ZipkinSpanHandler.class));
|
||||
this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(AsyncZipkinSpanHandler.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotSupplyIfZipkinReporterBraveIsNotOnClasspath() {
|
||||
// Note: Technically, Brave can work without zipkin-reporter. For example,
|
||||
// WavefrontSpanHandler doesn't require this to operate. If we remove this
|
||||
// dependency enforcement when WavefrontSpanHandler is in use, we can resolve
|
||||
// micrometer-metrics/tracing#509. We also need this for any configuration that
|
||||
// uses senders defined in the Spring Boot source tree, such as HttpSender.
|
||||
this.contextRunner.withClassLoader(new FilteredClassLoader("zipkin2.reporter.brave"))
|
||||
.withUserConfiguration(ReporterConfiguration.class)
|
||||
.run((context) -> assertThat(context).doesNotHaveBean(ZipkinSpanHandler.class));
|
||||
.withUserConfiguration(SenderConfiguration.class)
|
||||
.run((context) -> assertThat(context).doesNotHaveBean(AsyncZipkinSpanHandler.class));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldBackOffOnCustomBeans() {
|
||||
this.contextRunner.withUserConfiguration(ReporterConfiguration.class, CustomConfiguration.class)
|
||||
this.contextRunner.withUserConfiguration(SenderConfiguration.class, CustomConfiguration.class)
|
||||
.run((context) -> {
|
||||
assertThat(context).hasBean("customZipkinSpanHandler");
|
||||
assertThat(context).hasSingleBean(ZipkinSpanHandler.class);
|
||||
assertThat(context).hasBean("customAsyncZipkinSpanHandler");
|
||||
assertThat(context).hasSingleBean(AsyncZipkinSpanHandler.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSupplyZipkinSpanHandlerWithCustomSpanHandler() {
|
||||
this.contextRunner.withUserConfiguration(ReporterConfiguration.class, CustomSpanHandlerConfiguration.class)
|
||||
void shouldSupplyAsyncZipkinSpanHandlerWithCustomSpanHandler() {
|
||||
this.contextRunner.withUserConfiguration(SenderConfiguration.class, CustomSpanHandlerConfiguration.class)
|
||||
.run((context) -> {
|
||||
assertThat(context).hasBean("customSpanHandler");
|
||||
assertThat(context).hasSingleBean(ZipkinSpanHandler.class);
|
||||
assertThat(context).hasSingleBean(AsyncZipkinSpanHandler.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotSupplyZipkinSpanHandlerIfTracingIsDisabled() {
|
||||
this.tracingDisabledContextRunner.withUserConfiguration(ReporterConfiguration.class)
|
||||
.run((context) -> assertThat(context).doesNotHaveBean(ZipkinSpanHandler.class));
|
||||
void shouldNotSupplyAsyncZipkinSpanHandlerIfTracingIsDisabled() {
|
||||
this.tracingDisabledContextRunner.withUserConfiguration(SenderConfiguration.class)
|
||||
.run((context) -> assertThat(context).doesNotHaveBean(AsyncZipkinSpanHandler.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUseCustomEncoderBean() {
|
||||
this.contextRunner.withUserConfiguration(SenderConfiguration.class, CustomEncoderConfiguration.class)
|
||||
.run((context) -> {
|
||||
assertThat(context).hasSingleBean(AsyncZipkinSpanHandler.class);
|
||||
assertThat(context.getBean(AsyncZipkinSpanHandler.class)).extracting("spanReporter.encoder")
|
||||
.isInstanceOf(CustomMutableSpanEncoder.class)
|
||||
.extracting("encoding")
|
||||
.isEqualTo(Encoding.JSON);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUseCustomEncodingBean() {
|
||||
this.contextRunner
|
||||
.withUserConfiguration(SenderConfiguration.class, CustomEncodingConfiguration.class,
|
||||
CustomEncoderConfiguration.class)
|
||||
.run((context) -> {
|
||||
assertThat(context).hasSingleBean(AsyncZipkinSpanHandler.class);
|
||||
assertThat(context.getBean(AsyncZipkinSpanHandler.class)).extracting("encoding")
|
||||
.isEqualTo(Encoding.PROTO3);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUseDefaultThrowableTagBean() {
|
||||
this.contextRunner.withUserConfiguration(SenderConfiguration.class).run((context) -> {
|
||||
@SuppressWarnings("unchecked")
|
||||
BytesEncoder<MutableSpan> encoder = context.getBean(BytesEncoder.class);
|
||||
|
||||
MutableSpan span = new MutableSpan();
|
||||
span.traceId("1");
|
||||
span.id("1");
|
||||
span.tag("error", "true");
|
||||
span.error(new RuntimeException("ice cream"));
|
||||
|
||||
// default tag key name is "error", and doesn't overwrite
|
||||
assertThat(new String(encoder.encode(span), StandardCharsets.UTF_8)).isEqualTo(
|
||||
"{\"traceId\":\"0000000000000001\",\"id\":\"0000000000000001\",\"tags\":{\"error\":\"true\"}}");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUseCustomThrowableTagBean() {
|
||||
this.contextRunner.withUserConfiguration(SenderConfiguration.class, CustomThrowableTagConfiguration.class)
|
||||
.run((context) -> {
|
||||
@SuppressWarnings("unchecked")
|
||||
BytesEncoder<MutableSpan> encoder = context.getBean(BytesEncoder.class);
|
||||
|
||||
MutableSpan span = new MutableSpan();
|
||||
span.traceId("1");
|
||||
span.id("1");
|
||||
span.tag("error", "true");
|
||||
span.error(new RuntimeException("ice cream"));
|
||||
|
||||
// The custom throwable parser doesn't use the key "error" we can see both
|
||||
assertThat(new String(encoder.encode(span), StandardCharsets.UTF_8)).isEqualTo(
|
||||
"{\"traceId\":\"0000000000000001\",\"id\":\"0000000000000001\",\"tags\":{\"error\":\"true\",\"exception\":\"ice cream\"}}");
|
||||
});
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
private static final class ReporterConfiguration {
|
||||
private static final class SenderConfiguration {
|
||||
|
||||
@Bean
|
||||
@SuppressWarnings("unchecked")
|
||||
Reporter<Span> reporter() {
|
||||
return mock(Reporter.class);
|
||||
BytesMessageSender sender(Encoding encoding) {
|
||||
return new NoopSender(encoding);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -103,9 +174,23 @@ class ZipkinConfigurationsBraveConfigurationTests {
|
|||
private static final class CustomConfiguration {
|
||||
|
||||
@Bean
|
||||
@SuppressWarnings("unchecked")
|
||||
ZipkinSpanHandler customZipkinSpanHandler() {
|
||||
return (ZipkinSpanHandler) ZipkinSpanHandler.create(mock(Reporter.class));
|
||||
AsyncZipkinSpanHandler customAsyncZipkinSpanHandler() {
|
||||
return AsyncZipkinSpanHandler.create(new NoopSender(Encoding.JSON));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
private static final class CustomThrowableTagConfiguration {
|
||||
|
||||
@Bean
|
||||
Tag<Throwable> throwableTag() {
|
||||
return new Tag<Throwable>("exception") {
|
||||
@Override
|
||||
protected String parseValue(Throwable throwable, TraceContext traceContext) {
|
||||
return throwable.getMessage();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -120,4 +205,38 @@ class ZipkinConfigurationsBraveConfigurationTests {
|
|||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
private static final class CustomEncodingConfiguration {
|
||||
|
||||
@Bean
|
||||
Encoding encoding() {
|
||||
return Encoding.PROTO3;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
private static final class CustomEncoderConfiguration {
|
||||
|
||||
@Bean
|
||||
BytesEncoder<MutableSpan> encoder(Encoding encoding) {
|
||||
return new CustomMutableSpanEncoder(encoding);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private record CustomMutableSpanEncoder(Encoding encoding) implements BytesEncoder<MutableSpan> {
|
||||
|
||||
@Override
|
||||
public int sizeInBytes(MutableSpan span) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] encode(MutableSpan span) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -19,13 +19,12 @@ package org.springframework.boot.actuate.autoconfigure.tracing.zipkin;
|
|||
import io.opentelemetry.exporter.zipkin.ZipkinSpanExporter;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import zipkin2.Span;
|
||||
import zipkin2.codec.BytesEncoder;
|
||||
import zipkin2.codec.SpanBytesEncoder;
|
||||
import zipkin2.reporter.Sender;
|
||||
import zipkin2.reporter.BytesEncoder;
|
||||
import zipkin2.reporter.BytesMessageSender;
|
||||
import zipkin2.reporter.Encoding;
|
||||
|
||||
import org.springframework.boot.actuate.autoconfigure.tracing.zipkin.ZipkinConfigurations.OpenTelemetryConfiguration;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.test.context.FilteredClassLoader;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
@ -41,7 +40,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||
class ZipkinConfigurationsOpenTelemetryConfigurationTests {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(BaseConfiguration.class, OpenTelemetryConfiguration.class));
|
||||
.withConfiguration(AutoConfigurations.of(DefaultEncodingConfiguration.class, OpenTelemetryConfiguration.class));
|
||||
|
||||
private final ApplicationContextRunner tracingDisabledContextRunner = this.contextRunner
|
||||
.withPropertyValues("management.tracing.enabled=false");
|
||||
|
@ -79,12 +78,48 @@ class ZipkinConfigurationsOpenTelemetryConfigurationTests {
|
|||
.run((context) -> assertThat(context).doesNotHaveBean(ZipkinSpanExporter.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUseCustomEncoderBean() {
|
||||
this.contextRunner.withUserConfiguration(SenderConfiguration.class, CustomEncoderConfiguration.class)
|
||||
.run((context) -> {
|
||||
assertThat(context).hasSingleBean(ZipkinSpanExporter.class);
|
||||
assertThat(context.getBean(ZipkinSpanExporter.class)).extracting("encoder")
|
||||
.isInstanceOf(CustomSpanEncoder.class)
|
||||
.extracting("encoding")
|
||||
.isEqualTo(Encoding.JSON);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUseCustomEncodingBean() {
|
||||
this.contextRunner
|
||||
.withUserConfiguration(SenderConfiguration.class, CustomEncodingConfiguration.class,
|
||||
CustomEncoderConfiguration.class)
|
||||
.run((context) -> {
|
||||
assertThat(context).hasSingleBean(ZipkinSpanExporter.class);
|
||||
assertThat(context.getBean(ZipkinSpanExporter.class)).extracting("encoder")
|
||||
.isInstanceOf(CustomSpanEncoder.class)
|
||||
.extracting("encoding")
|
||||
.isEqualTo(Encoding.PROTO3);
|
||||
});
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
private static final class CustomEncodingConfiguration {
|
||||
|
||||
@Bean
|
||||
Encoding encoding() {
|
||||
return Encoding.PROTO3;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
private static final class SenderConfiguration {
|
||||
|
||||
@Bean
|
||||
Sender sender() {
|
||||
return new NoopSender();
|
||||
BytesMessageSender sender(Encoding encoding) {
|
||||
return new NoopSender(encoding);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -100,12 +135,25 @@ class ZipkinConfigurationsOpenTelemetryConfigurationTests {
|
|||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
private static final class BaseConfiguration {
|
||||
private static final class CustomEncoderConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
BytesEncoder<Span> spanBytesEncoder() {
|
||||
return SpanBytesEncoder.JSON_V2;
|
||||
BytesEncoder<Span> encoder(Encoding encoding) {
|
||||
return new CustomSpanEncoder(encoding);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
record CustomSpanEncoder(Encoding encoding) implements BytesEncoder<Span> {
|
||||
|
||||
@Override
|
||||
public int sizeInBytes(Span span) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] encode(Span span) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,98 +0,0 @@
|
|||
/*
|
||||
* Copyright 2012-2024 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
|
||||
*
|
||||
* https://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.boot.actuate.autoconfigure.tracing.zipkin;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import zipkin2.Span;
|
||||
import zipkin2.codec.BytesEncoder;
|
||||
import zipkin2.codec.SpanBytesEncoder;
|
||||
import zipkin2.reporter.Reporter;
|
||||
import zipkin2.reporter.Sender;
|
||||
|
||||
import org.springframework.boot.actuate.autoconfigure.tracing.zipkin.ZipkinConfigurations.ReporterConfiguration;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link ReporterConfiguration}.
|
||||
*
|
||||
* @author Moritz Halbritter
|
||||
*/
|
||||
class ZipkinConfigurationsReporterConfigurationTests {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(BaseConfiguration.class, ReporterConfiguration.class));
|
||||
|
||||
@Test
|
||||
void shouldSupplyBeans() {
|
||||
this.contextRunner.withUserConfiguration(SenderConfiguration.class)
|
||||
.run((context) -> assertThat(context).hasSingleBean(Reporter.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotSupplyReporterIfSenderIsMissing() {
|
||||
this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(Reporter.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldBackOffOnCustomBeans() {
|
||||
this.contextRunner.withUserConfiguration(SenderConfiguration.class, CustomConfiguration.class)
|
||||
.run((context) -> {
|
||||
assertThat(context).hasBean("customReporter");
|
||||
assertThat(context).hasSingleBean(Reporter.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
private static final class SenderConfiguration {
|
||||
|
||||
@Bean
|
||||
Sender sender() {
|
||||
return new NoopSender();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
private static final class CustomConfiguration {
|
||||
|
||||
@Bean
|
||||
@SuppressWarnings("unchecked")
|
||||
Reporter<Span> customReporter() {
|
||||
return mock(Reporter.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
private static final class BaseConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
BytesEncoder<Span> spanBytesEncoder() {
|
||||
return SpanBytesEncoder.JSON_V2;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -18,6 +18,7 @@ package org.springframework.boot.actuate.autoconfigure.tracing.zipkin;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import okhttp3.mockwebserver.MockResponse;
|
||||
|
@ -25,7 +26,8 @@ import okhttp3.mockwebserver.MockWebServer;
|
|||
import okhttp3.mockwebserver.RecordedRequest;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentMatchers;
|
||||
import zipkin2.reporter.Sender;
|
||||
import zipkin2.reporter.BytesMessageSender;
|
||||
import zipkin2.reporter.HttpEndpointSupplier;
|
||||
import zipkin2.reporter.urlconnection.URLConnectionSender;
|
||||
|
||||
import org.springframework.boot.actuate.autoconfigure.tracing.zipkin.ZipkinConfigurations.SenderConfiguration;
|
||||
|
@ -51,18 +53,18 @@ import static org.mockito.Mockito.mock;
|
|||
class ZipkinConfigurationsSenderConfigurationTests {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(SenderConfiguration.class));
|
||||
.withConfiguration(AutoConfigurations.of(DefaultEncodingConfiguration.class, SenderConfiguration.class));
|
||||
|
||||
private final ReactiveWebApplicationContextRunner reactiveContextRunner = new ReactiveWebApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(SenderConfiguration.class));
|
||||
.withConfiguration(AutoConfigurations.of(DefaultEncodingConfiguration.class, SenderConfiguration.class));
|
||||
|
||||
private final WebApplicationContextRunner servletContextRunner = new WebApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(SenderConfiguration.class));
|
||||
.withConfiguration(AutoConfigurations.of(DefaultEncodingConfiguration.class, SenderConfiguration.class));
|
||||
|
||||
@Test
|
||||
void shouldSupplyBeans() {
|
||||
this.contextRunner.run((context) -> {
|
||||
assertThat(context).hasSingleBean(Sender.class);
|
||||
assertThat(context).hasSingleBean(BytesMessageSender.class);
|
||||
assertThat(context).hasSingleBean(URLConnectionSender.class);
|
||||
assertThat(context).doesNotHaveBean(ZipkinRestTemplateSender.class);
|
||||
});
|
||||
|
@ -74,7 +76,7 @@ class ZipkinConfigurationsSenderConfigurationTests {
|
|||
.withClassLoader(new FilteredClassLoader("zipkin2.reporter.urlconnection"))
|
||||
.run((context) -> {
|
||||
assertThat(context).doesNotHaveBean(URLConnectionSender.class);
|
||||
assertThat(context).hasSingleBean(Sender.class);
|
||||
assertThat(context).hasSingleBean(BytesMessageSender.class);
|
||||
assertThat(context).hasSingleBean(ZipkinWebClientSender.class);
|
||||
then(context.getBean(ZipkinWebClientBuilderCustomizer.class)).should()
|
||||
.customize(ArgumentMatchers.any());
|
||||
|
@ -87,7 +89,7 @@ class ZipkinConfigurationsSenderConfigurationTests {
|
|||
.withClassLoader(new FilteredClassLoader("zipkin2.reporter.urlconnection"))
|
||||
.run((context) -> {
|
||||
assertThat(context).doesNotHaveBean(URLConnectionSender.class);
|
||||
assertThat(context).hasSingleBean(Sender.class);
|
||||
assertThat(context).hasSingleBean(BytesMessageSender.class);
|
||||
assertThat(context).hasSingleBean(ZipkinWebClientSender.class);
|
||||
});
|
||||
}
|
||||
|
@ -98,7 +100,7 @@ class ZipkinConfigurationsSenderConfigurationTests {
|
|||
.withClassLoader(new FilteredClassLoader("zipkin2.reporter.urlconnection"))
|
||||
.run((context) -> {
|
||||
assertThat(context).doesNotHaveBean(URLConnectionSender.class);
|
||||
assertThat(context).hasSingleBean(Sender.class);
|
||||
assertThat(context).hasSingleBean(BytesMessageSender.class);
|
||||
assertThat(context).hasSingleBean(ZipkinWebClientSender.class);
|
||||
});
|
||||
}
|
||||
|
@ -109,7 +111,7 @@ class ZipkinConfigurationsSenderConfigurationTests {
|
|||
.withClassLoader(new FilteredClassLoader(URLConnectionSender.class, WebClient.class))
|
||||
.run((context) -> {
|
||||
assertThat(context).doesNotHaveBean(URLConnectionSender.class);
|
||||
assertThat(context).hasSingleBean(Sender.class);
|
||||
assertThat(context).hasSingleBean(BytesMessageSender.class);
|
||||
assertThat(context).hasSingleBean(ZipkinRestTemplateSender.class);
|
||||
});
|
||||
}
|
||||
|
@ -120,7 +122,7 @@ class ZipkinConfigurationsSenderConfigurationTests {
|
|||
.withClassLoader(new FilteredClassLoader(URLConnectionSender.class, WebClient.class))
|
||||
.run((context) -> {
|
||||
assertThat(context).doesNotHaveBean(URLConnectionSender.class);
|
||||
assertThat(context).hasSingleBean(Sender.class);
|
||||
assertThat(context).hasSingleBean(BytesMessageSender.class);
|
||||
assertThat(context).hasSingleBean(ZipkinRestTemplateSender.class);
|
||||
});
|
||||
}
|
||||
|
@ -131,7 +133,7 @@ class ZipkinConfigurationsSenderConfigurationTests {
|
|||
.withClassLoader(new FilteredClassLoader(URLConnectionSender.class, WebClient.class))
|
||||
.run((context) -> {
|
||||
assertThat(context).doesNotHaveBean(URLConnectionSender.class);
|
||||
assertThat(context).hasSingleBean(Sender.class);
|
||||
assertThat(context).hasSingleBean(BytesMessageSender.class);
|
||||
assertThat(context).hasSingleBean(ZipkinRestTemplateSender.class);
|
||||
});
|
||||
}
|
||||
|
@ -140,7 +142,7 @@ class ZipkinConfigurationsSenderConfigurationTests {
|
|||
void shouldNotUseWebClientSenderIfNoBuilderIsAvailable() {
|
||||
this.reactiveContextRunner.run((context) -> {
|
||||
assertThat(context).doesNotHaveBean(ZipkinWebClientSender.class);
|
||||
assertThat(context).hasSingleBean(Sender.class);
|
||||
assertThat(context).hasSingleBean(BytesMessageSender.class);
|
||||
assertThat(context).hasSingleBean(URLConnectionSender.class);
|
||||
});
|
||||
}
|
||||
|
@ -149,7 +151,7 @@ class ZipkinConfigurationsSenderConfigurationTests {
|
|||
void shouldBackOffOnCustomBeans() {
|
||||
this.contextRunner.withUserConfiguration(CustomConfiguration.class).run((context) -> {
|
||||
assertThat(context).hasBean("customSender");
|
||||
assertThat(context).hasSingleBean(Sender.class);
|
||||
assertThat(context).hasSingleBean(BytesMessageSender.class);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -164,7 +166,7 @@ class ZipkinConfigurationsSenderConfigurationTests {
|
|||
.run((context) -> {
|
||||
assertThat(context).hasSingleBean(ZipkinRestTemplateSender.class);
|
||||
ZipkinRestTemplateSender sender = context.getBean(ZipkinRestTemplateSender.class);
|
||||
sender.sendSpans("spans".getBytes(StandardCharsets.UTF_8)).execute();
|
||||
sender.send(List.of("spans".getBytes(StandardCharsets.UTF_8)));
|
||||
RecordedRequest recordedRequest = mockWebServer.takeRequest(1, TimeUnit.SECONDS);
|
||||
assertThat(recordedRequest).isNotNull();
|
||||
assertThat(recordedRequest.getHeaders().get("x-dummy")).isEqualTo("dummy");
|
||||
|
@ -172,6 +174,32 @@ class ZipkinConfigurationsSenderConfigurationTests {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUseCustomHttpEndpointSupplierFactory() {
|
||||
this.contextRunner.withUserConfiguration(CustomHttpEndpointSupplierFactoryConfiguration.class)
|
||||
.run((context) -> assertThat(context.getBean(URLConnectionSender.class))
|
||||
.extracting("delegate.endpointSupplier")
|
||||
.isInstanceOf(CustomHttpEndpointSupplier.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUseCustomHttpEndpointSupplierFactoryWhenReactive() {
|
||||
this.reactiveContextRunner.withUserConfiguration(WebClientConfiguration.class)
|
||||
.withClassLoader(new FilteredClassLoader(URLConnectionSender.class))
|
||||
.withUserConfiguration(CustomHttpEndpointSupplierFactoryConfiguration.class)
|
||||
.run((context) -> assertThat(context.getBean(ZipkinWebClientSender.class)).extracting("endpointSupplier")
|
||||
.isInstanceOf(CustomHttpEndpointSupplier.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUseCustomHttpEndpointSupplierFactoryWhenRestTemplate() {
|
||||
this.contextRunner.withUserConfiguration(RestTemplateConfiguration.class)
|
||||
.withClassLoader(new FilteredClassLoader(URLConnectionSender.class, WebClient.class))
|
||||
.withUserConfiguration(CustomHttpEndpointSupplierFactoryConfiguration.class)
|
||||
.run((context) -> assertThat(context.getBean(ZipkinRestTemplateSender.class)).extracting("endpointSupplier")
|
||||
.isInstanceOf(CustomHttpEndpointSupplier.class));
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
private static final class RestTemplateConfiguration {
|
||||
|
||||
|
@ -196,8 +224,8 @@ class ZipkinConfigurationsSenderConfigurationTests {
|
|||
private static final class CustomConfiguration {
|
||||
|
||||
@Bean
|
||||
Sender customSender() {
|
||||
return mock(Sender.class);
|
||||
BytesMessageSender customSender() {
|
||||
return mock(BytesMessageSender.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -211,4 +239,35 @@ class ZipkinConfigurationsSenderConfigurationTests {
|
|||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
private static final class CustomHttpEndpointSupplierFactoryConfiguration {
|
||||
|
||||
@Bean
|
||||
HttpEndpointSupplier.Factory httpEndpointSupplier() {
|
||||
return new CustomHttpEndpointSupplierFactory();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final class CustomHttpEndpointSupplierFactory implements HttpEndpointSupplier.Factory {
|
||||
|
||||
@Override
|
||||
public HttpEndpointSupplier create(String endpoint) {
|
||||
return new CustomHttpEndpointSupplier(endpoint);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private record CustomHttpEndpointSupplier(String endpoint) implements HttpEndpointSupplier {
|
||||
|
||||
@Override
|
||||
public String get() {
|
||||
return this.endpoint;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -18,21 +18,14 @@ package org.springframework.boot.actuate.autoconfigure.tracing.zipkin;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.awaitility.Awaitility;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import zipkin2.Callback;
|
||||
import zipkin2.reporter.BytesMessageSender;
|
||||
import zipkin2.reporter.ClosedSenderException;
|
||||
import zipkin2.reporter.Sender;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
|
||||
/**
|
||||
|
@ -43,9 +36,9 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
|||
*/
|
||||
abstract class ZipkinHttpSenderTests {
|
||||
|
||||
protected Sender sender;
|
||||
protected BytesMessageSender sender;
|
||||
|
||||
abstract Sender createSender();
|
||||
abstract BytesMessageSender createSender();
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach() throws Exception {
|
||||
|
@ -58,55 +51,14 @@ abstract class ZipkinHttpSenderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void sendSpansShouldThrowIfCloseWasCalled() throws IOException {
|
||||
void sendShouldThrowIfCloseWasCalled() throws IOException {
|
||||
this.sender.close();
|
||||
assertThatExceptionOfType(ClosedSenderException.class)
|
||||
.isThrownBy(() -> this.sender.sendSpans(Collections.emptyList()));
|
||||
}
|
||||
|
||||
protected void makeRequest(List<byte[]> encodedSpans, boolean async) throws IOException {
|
||||
if (async) {
|
||||
CallbackResult callbackResult = makeAsyncRequest(encodedSpans);
|
||||
assertThat(callbackResult.success()).isTrue();
|
||||
}
|
||||
else {
|
||||
makeSyncRequest(encodedSpans);
|
||||
}
|
||||
}
|
||||
|
||||
protected CallbackResult makeAsyncRequest(List<byte[]> encodedSpans) {
|
||||
return makeAsyncRequest(this.sender, encodedSpans);
|
||||
}
|
||||
|
||||
protected CallbackResult makeAsyncRequest(Sender sender, List<byte[]> encodedSpans) {
|
||||
AtomicReference<CallbackResult> callbackResult = new AtomicReference<>();
|
||||
sender.sendSpans(encodedSpans).enqueue(new Callback<>() {
|
||||
@Override
|
||||
public void onSuccess(Void value) {
|
||||
callbackResult.set(new CallbackResult(true, null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable t) {
|
||||
callbackResult.set(new CallbackResult(false, t));
|
||||
}
|
||||
});
|
||||
return Awaitility.await().atMost(Duration.ofSeconds(5)).until(callbackResult::get, Objects::nonNull);
|
||||
}
|
||||
|
||||
protected void makeSyncRequest(List<byte[]> encodedSpans) throws IOException {
|
||||
makeSyncRequest(this.sender, encodedSpans);
|
||||
}
|
||||
|
||||
protected void makeSyncRequest(Sender sender, List<byte[]> encodedSpans) throws IOException {
|
||||
sender.sendSpans(encodedSpans).execute();
|
||||
.isThrownBy(() -> this.sender.send(Collections.emptyList()));
|
||||
}
|
||||
|
||||
protected byte[] toByteArray(String input) {
|
||||
return input.getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
record CallbackResult(boolean success, Throwable error) {
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -17,23 +17,25 @@
|
|||
package org.springframework.boot.actuate.autoconfigure.tracing.zipkin;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.Base64;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import zipkin2.CheckResult;
|
||||
import zipkin2.reporter.Sender;
|
||||
import zipkin2.reporter.BytesMessageSender;
|
||||
import zipkin2.reporter.Encoding;
|
||||
import zipkin2.reporter.HttpEndpointSupplier;
|
||||
import zipkin2.reporter.HttpEndpointSuppliers;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.test.web.client.MockRestServiceServer;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatException;
|
||||
import static org.springframework.test.web.client.match.MockRestRequestMatchers.content;
|
||||
import static org.springframework.test.web.client.match.MockRestRequestMatchers.header;
|
||||
|
@ -51,13 +53,23 @@ class ZipkinRestTemplateSenderTests extends ZipkinHttpSenderTests {
|
|||
|
||||
private static final String ZIPKIN_URL = "http://localhost:9411/api/v2/spans";
|
||||
|
||||
private RestTemplate restTemplate;
|
||||
|
||||
private MockRestServiceServer mockServer;
|
||||
|
||||
@Override
|
||||
Sender createSender() {
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
this.mockServer = MockRestServiceServer.createServer(restTemplate);
|
||||
return new ZipkinRestTemplateSender(ZIPKIN_URL, restTemplate);
|
||||
BytesMessageSender createSender() {
|
||||
this.restTemplate = new RestTemplate();
|
||||
this.mockServer = MockRestServiceServer.createServer(this.restTemplate);
|
||||
return createSender(Encoding.JSON);
|
||||
}
|
||||
|
||||
BytesMessageSender createSender(Encoding encoding) {
|
||||
return createSender(HttpEndpointSuppliers.constantFactory(), encoding);
|
||||
}
|
||||
|
||||
BytesMessageSender createSender(HttpEndpointSupplier.Factory endpointSupplierFactory, Encoding encoding) {
|
||||
return new ZipkinRestTemplateSender(encoding, endpointSupplierFactory, ZIPKIN_URL, this.restTemplate);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
|
@ -68,55 +80,65 @@ class ZipkinRestTemplateSenderTests extends ZipkinHttpSenderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void checkShouldSendEmptySpanList() {
|
||||
this.mockServer.expect(requestTo(ZIPKIN_URL))
|
||||
.andExpect(method(HttpMethod.POST))
|
||||
.andExpect(content().string("[]"))
|
||||
.andRespond(withStatus(HttpStatus.ACCEPTED));
|
||||
assertThat(this.sender.check()).isEqualTo(CheckResult.OK);
|
||||
}
|
||||
|
||||
@Test
|
||||
void checkShouldNotRaiseException() {
|
||||
this.mockServer.expect(requestTo(ZIPKIN_URL))
|
||||
.andExpect(method(HttpMethod.POST))
|
||||
.andRespond(withStatus(HttpStatus.INTERNAL_SERVER_ERROR));
|
||||
CheckResult result = this.sender.check();
|
||||
assertThat(result.ok()).isFalse();
|
||||
assertThat(result.error()).hasMessageContaining("500 Internal Server Error");
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = { true, false })
|
||||
void sendSpansShouldSendSpansToZipkin(boolean async) throws IOException {
|
||||
void sendShouldSendSpansToZipkin() throws IOException {
|
||||
this.mockServer.expect(requestTo(ZIPKIN_URL))
|
||||
.andExpect(method(HttpMethod.POST))
|
||||
.andExpect(content().contentType("application/json"))
|
||||
.andExpect(content().string("[span1,span2]"))
|
||||
.andRespond(withStatus(HttpStatus.ACCEPTED));
|
||||
makeRequest(List.of(toByteArray("span1"), toByteArray("span2")), async);
|
||||
this.sender.send(List.of(toByteArray("span1"), toByteArray("span2")));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = { true, false })
|
||||
void sendSpansShouldHandleHttpFailures(boolean async) {
|
||||
@Test
|
||||
void sendShouldSendSpansToZipkinInProto3() throws IOException {
|
||||
this.mockServer.expect(requestTo(ZIPKIN_URL))
|
||||
.andExpect(method(HttpMethod.POST))
|
||||
.andExpect(content().contentType("application/x-protobuf"))
|
||||
.andExpect(content().string("span1span2"))
|
||||
.andRespond(withStatus(HttpStatus.ACCEPTED));
|
||||
|
||||
try (BytesMessageSender sender = createSender(Encoding.PROTO3)) {
|
||||
sender.send(List.of(toByteArray("span1"), toByteArray("span2")));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This tests that a dynamic {@linkplain HttpEndpointSupplier} updates are visible to
|
||||
* {@link HttpSender#postSpans(URI, HttpHeaders, byte[])}.
|
||||
*/
|
||||
@Test
|
||||
void sendUsesDynamicEndpoint() throws Exception {
|
||||
this.mockServer.expect(requestTo(ZIPKIN_URL + "/1")).andRespond(withStatus(HttpStatus.ACCEPTED));
|
||||
this.mockServer.expect(requestTo(ZIPKIN_URL + "/2")).andRespond(withStatus(HttpStatus.ACCEPTED));
|
||||
|
||||
AtomicInteger suffix = new AtomicInteger();
|
||||
try (BytesMessageSender sender = createSender((e) -> new HttpEndpointSupplier() {
|
||||
@Override
|
||||
public String get() {
|
||||
return ZIPKIN_URL + "/" + suffix.incrementAndGet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
}, Encoding.JSON)) {
|
||||
sender.send(Collections.emptyList());
|
||||
sender.send(Collections.emptyList());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void sendShouldHandleHttpFailures() {
|
||||
this.mockServer.expect(requestTo(ZIPKIN_URL))
|
||||
.andExpect(method(HttpMethod.POST))
|
||||
.andRespond(withStatus(HttpStatus.INTERNAL_SERVER_ERROR));
|
||||
if (async) {
|
||||
CallbackResult callbackResult = makeAsyncRequest(Collections.emptyList());
|
||||
assertThat(callbackResult.success()).isFalse();
|
||||
assertThat(callbackResult.error()).isNotNull().hasMessageContaining("500 Internal Server Error");
|
||||
}
|
||||
else {
|
||||
assertThatException().isThrownBy(() -> makeSyncRequest(Collections.emptyList()))
|
||||
.withMessageContaining("500 Internal Server Error");
|
||||
}
|
||||
|
||||
assertThatException().isThrownBy(() -> this.sender.send(Collections.emptyList()))
|
||||
.withMessageContaining("500 Internal Server Error");
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = { true, false })
|
||||
void sendSpansShouldCompressData(boolean async) throws IOException {
|
||||
@Test
|
||||
void sendShouldCompressData() throws IOException {
|
||||
String uncompressed = "a".repeat(10000);
|
||||
// This is gzip compressed 10000 times 'a'
|
||||
byte[] compressed = Base64.getDecoder()
|
||||
|
@ -127,7 +149,7 @@ class ZipkinRestTemplateSenderTests extends ZipkinHttpSenderTests {
|
|||
.andExpect(content().contentType("application/json"))
|
||||
.andExpect(content().bytes(compressed))
|
||||
.andRespond(withStatus(HttpStatus.ACCEPTED));
|
||||
makeRequest(List.of(toByteArray(uncompressed)), async);
|
||||
this.sender.send(List.of(toByteArray(uncompressed)));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -17,12 +17,14 @@
|
|||
package org.springframework.boot.actuate.autoconfigure.tracing.zipkin;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.time.Duration;
|
||||
import java.util.Base64;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import okhttp3.mockwebserver.MockResponse;
|
||||
|
@ -33,11 +35,12 @@ import org.junit.jupiter.api.AfterAll;
|
|||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import zipkin2.CheckResult;
|
||||
import zipkin2.reporter.Sender;
|
||||
import zipkin2.reporter.BytesMessageSender;
|
||||
import zipkin2.reporter.Encoding;
|
||||
import zipkin2.reporter.HttpEndpointSupplier;
|
||||
import zipkin2.reporter.HttpEndpointSuppliers;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
@ -79,40 +82,25 @@ class ZipkinWebClientSenderTests extends ZipkinHttpSenderTests {
|
|||
}
|
||||
|
||||
@Override
|
||||
Sender createSender() {
|
||||
return createSender(Duration.ofSeconds(10));
|
||||
BytesMessageSender createSender() {
|
||||
return createSender(Encoding.JSON, Duration.ofSeconds(10));
|
||||
}
|
||||
|
||||
Sender createSender(Duration timeout) {
|
||||
ZipkinWebClientSender createSender(Encoding encoding, Duration timeout) {
|
||||
return createSender(HttpEndpointSuppliers.constantFactory(), encoding, timeout);
|
||||
}
|
||||
|
||||
ZipkinWebClientSender createSender(HttpEndpointSupplier.Factory endpointSupplierFactory, Encoding encoding,
|
||||
Duration timeout) {
|
||||
WebClient webClient = WebClient.builder().build();
|
||||
return new ZipkinWebClientSender(ZIPKIN_URL, webClient, timeout);
|
||||
return new ZipkinWebClientSender(encoding, endpointSupplierFactory, ZIPKIN_URL, webClient, timeout);
|
||||
}
|
||||
|
||||
@Test
|
||||
void checkShouldSendEmptySpanList() throws InterruptedException {
|
||||
mockBackEnd.enqueue(new MockResponse());
|
||||
assertThat(this.sender.check()).isEqualTo(CheckResult.OK);
|
||||
requestAssertions((request) -> {
|
||||
assertThat(request.getMethod()).isEqualTo("POST");
|
||||
assertThat(request.getBody().readUtf8()).isEqualTo("[]");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void checkShouldNotRaiseException() throws InterruptedException {
|
||||
mockBackEnd.enqueue(new MockResponse().setResponseCode(500));
|
||||
CheckResult result = this.sender.check();
|
||||
assertThat(result.ok()).isFalse();
|
||||
assertThat(result.error()).hasMessageContaining("500 Internal Server Error");
|
||||
requestAssertions((request) -> assertThat(request.getMethod()).isEqualTo("POST"));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = { true, false })
|
||||
void sendSpansShouldSendSpansToZipkin(boolean async) throws IOException, InterruptedException {
|
||||
void sendShouldSendSpansToZipkin() throws IOException, InterruptedException {
|
||||
mockBackEnd.enqueue(new MockResponse());
|
||||
List<byte[]> encodedSpans = List.of(toByteArray("span1"), toByteArray("span2"));
|
||||
makeRequest(encodedSpans, async);
|
||||
this.sender.send(encodedSpans);
|
||||
requestAssertions((request) -> {
|
||||
assertThat(request.getMethod()).isEqualTo("POST");
|
||||
assertThat(request.getHeader("Content-Type")).isEqualTo("application/json");
|
||||
|
@ -120,31 +108,66 @@ class ZipkinWebClientSenderTests extends ZipkinHttpSenderTests {
|
|||
});
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = { true, false })
|
||||
void sendSpansShouldHandleHttpFailures(boolean async) throws InterruptedException {
|
||||
@Test
|
||||
void sendShouldSendSpansToZipkinInProto3() throws IOException, InterruptedException {
|
||||
mockBackEnd.enqueue(new MockResponse());
|
||||
List<byte[]> encodedSpans = List.of(toByteArray("span1"), toByteArray("span2"));
|
||||
|
||||
try (BytesMessageSender sender = createSender(Encoding.PROTO3, Duration.ofSeconds(10))) {
|
||||
sender.send(encodedSpans);
|
||||
}
|
||||
|
||||
requestAssertions((request) -> {
|
||||
assertThat(request.getMethod()).isEqualTo("POST");
|
||||
assertThat(request.getHeader("Content-Type")).isEqualTo("application/x-protobuf");
|
||||
assertThat(request.getBody().readUtf8()).isEqualTo("span1span2");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This tests that a dynamic {@linkplain HttpEndpointSupplier} updates are visible to
|
||||
* {@link HttpSender#postSpans(URI, HttpHeaders, byte[])}.
|
||||
*/
|
||||
@Test
|
||||
void sendUsesDynamicEndpoint() throws Exception {
|
||||
mockBackEnd.enqueue(new MockResponse());
|
||||
mockBackEnd.enqueue(new MockResponse());
|
||||
|
||||
AtomicInteger suffix = new AtomicInteger();
|
||||
try (BytesMessageSender sender = createSender((e) -> new HttpEndpointSupplier() {
|
||||
@Override
|
||||
public String get() {
|
||||
return ZIPKIN_URL + "/" + suffix.incrementAndGet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
}, Encoding.JSON, Duration.ofSeconds(10))) {
|
||||
sender.send(Collections.emptyList());
|
||||
sender.send(Collections.emptyList());
|
||||
}
|
||||
|
||||
assertThat(mockBackEnd.takeRequest().getPath()).endsWith("/1");
|
||||
assertThat(mockBackEnd.takeRequest().getPath()).endsWith("/2");
|
||||
}
|
||||
|
||||
@Test
|
||||
void sendShouldHandleHttpFailures() throws InterruptedException {
|
||||
mockBackEnd.enqueue(new MockResponse().setResponseCode(500));
|
||||
if (async) {
|
||||
CallbackResult callbackResult = makeAsyncRequest(Collections.emptyList());
|
||||
assertThat(callbackResult.success()).isFalse();
|
||||
assertThat(callbackResult.error()).isNotNull().hasMessageContaining("500 Internal Server Error");
|
||||
}
|
||||
else {
|
||||
assertThatException().isThrownBy(() -> makeSyncRequest(Collections.emptyList()))
|
||||
.withMessageContaining("500 Internal Server Error");
|
||||
}
|
||||
assertThatException().isThrownBy(() -> this.sender.send(Collections.emptyList()))
|
||||
.withMessageContaining("500 Internal Server Error");
|
||||
requestAssertions((request) -> assertThat(request.getMethod()).isEqualTo("POST"));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = { true, false })
|
||||
void sendSpansShouldCompressData(boolean async) throws IOException, InterruptedException {
|
||||
@Test
|
||||
void sendShouldCompressData() throws IOException, InterruptedException {
|
||||
String uncompressed = "a".repeat(10000);
|
||||
// This is gzip compressed 10000 times 'a'
|
||||
byte[] compressed = Base64.getDecoder()
|
||||
.decode("H4sIAAAAAAAA/+3BMQ0AAAwDIKFLj/k3UR8NcA8AAAAAAAAAAAADUsAZfeASJwAA");
|
||||
mockBackEnd.enqueue(new MockResponse());
|
||||
makeRequest(List.of(toByteArray(uncompressed)), async);
|
||||
this.sender.send(List.of(toByteArray(uncompressed)));
|
||||
requestAssertions((request) -> {
|
||||
assertThat(request.getMethod()).isEqualTo("POST");
|
||||
assertThat(request.getHeader("Content-Type")).isEqualTo("application/json");
|
||||
|
@ -153,19 +176,12 @@ class ZipkinWebClientSenderTests extends ZipkinHttpSenderTests {
|
|||
});
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = { true, false })
|
||||
void shouldTimeout(boolean async) {
|
||||
Sender sender = createSender(Duration.ofMillis(1));
|
||||
MockResponse response = new MockResponse().setResponseCode(200).setHeadersDelay(100, TimeUnit.MILLISECONDS);
|
||||
mockBackEnd.enqueue(response);
|
||||
if (async) {
|
||||
CallbackResult callbackResult = makeAsyncRequest(sender, Collections.emptyList());
|
||||
assertThat(callbackResult.success()).isFalse();
|
||||
assertThat(callbackResult.error()).isInstanceOf(TimeoutException.class);
|
||||
}
|
||||
else {
|
||||
assertThatException().isThrownBy(() -> makeSyncRequest(sender, Collections.emptyList()))
|
||||
@Test
|
||||
void shouldTimeout() throws IOException {
|
||||
try (BytesMessageSender sender = createSender(Encoding.JSON, Duration.ofMillis(1))) {
|
||||
MockResponse response = new MockResponse().setResponseCode(200).setHeadersDelay(100, TimeUnit.MILLISECONDS);
|
||||
mockBackEnd.enqueue(response);
|
||||
assertThatException().isThrownBy(() -> sender.send(Collections.emptyList()))
|
||||
.withCauseInstanceOf(TimeoutException.class);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -109,7 +109,14 @@ bom {
|
|||
]
|
||||
}
|
||||
}
|
||||
library("Brave", "5.17.1") {
|
||||
library("Zipkin Reporter", "3.3.0") {
|
||||
group("io.zipkin.reporter2") {
|
||||
imports = [
|
||||
"zipkin-reporter-bom"
|
||||
]
|
||||
}
|
||||
}
|
||||
library("Brave", "6.0.1") {
|
||||
group("io.zipkin.brave") {
|
||||
imports = [
|
||||
"brave-bom"
|
||||
|
@ -1096,7 +1103,7 @@ bom {
|
|||
]
|
||||
}
|
||||
}
|
||||
library("OpenTelemetry", "1.33.0") {
|
||||
library("OpenTelemetry", "1.35.0") {
|
||||
group("io.opentelemetry") {
|
||||
imports = [
|
||||
"opentelemetry-bom"
|
||||
|
|
|
@ -70,7 +70,7 @@ public final class DockerImageNames {
|
|||
|
||||
private static final String REGISTRY_VERSION = "2.7.1";
|
||||
|
||||
private static final String ZIPKIN_VERSION = "2.24.1";
|
||||
private static final String ZIPKIN_VERSION = "3.0.6";
|
||||
|
||||
private DockerImageNames() {
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue