diff --git a/docs/src/main/asciidoc/_configprops.adoc b/docs/src/main/asciidoc/_configprops.adoc index 44d2be68d..48122995d 100644 --- a/docs/src/main/asciidoc/_configprops.adoc +++ b/docs/src/main/asciidoc/_configprops.adoc @@ -62,6 +62,7 @@ |spring.sleuth.web.webclient.enabled | `true` | Enable tracing instrumentation for WebClient. |spring.zipkin.activemq.message-max-bytes | `100000` | Maximum number of bytes for a given message with spans sent to Zipkin over ActiveMQ. |spring.zipkin.activemq.queue | `zipkin` | Name of the ActiveMQ queue where spans should be sent to Zipkin. +|spring.zipkin.api-path | | The API path to append to baseUrl (above) as suffix. This applies if you use other monitoring tools, such as New Relic. The trace API doesn't need the API path, so you can set it to blank ("") in the configuration. |spring.zipkin.base-url | `http://localhost:9411/` | URL of the zipkin query server instance. You can also provide the service id of the Zipkin server if Zipkin's registered in service discovery (e.g. https://zipkinserver/). |spring.zipkin.compression.enabled | `false` | |spring.zipkin.discovery-client-enabled | | If set to {@code false}, will treat the {@link ZipkinProperties#baseUrl} as a URL always. diff --git a/docs/src/main/asciidoc/project-features.adoc b/docs/src/main/asciidoc/project-features.adoc index 63201f262..fb84d25c7 100644 --- a/docs/src/main/asciidoc/project-features.adoc +++ b/docs/src/main/asciidoc/project-features.adoc @@ -316,6 +316,13 @@ object, you will have to create a bean of `zipkin2.reporter.Sender` type. } ---- +By default, api path will be set to `api/v2/spans` or `api/v1/spans` depending on the encoder version. If you want to use a custom api path, you can configure it using the following property (empty case, set ""): + +[source,yaml] +---- +spring.zipkin.api-path: v2/path2 +---- + [[features-zipkin-custom-service-name]] === Custom service name diff --git a/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/zipkin2/ZipkinRestTemplateSenderConfiguration.java b/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/zipkin2/ZipkinRestTemplateSenderConfiguration.java index 46bbfeec7..8fa5f2358 100644 --- a/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/zipkin2/ZipkinRestTemplateSenderConfiguration.java +++ b/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/zipkin2/ZipkinRestTemplateSenderConfiguration.java @@ -54,7 +54,7 @@ class ZipkinRestTemplateSenderConfiguration { ZipkinUrlExtractor extractor) { RestTemplate restTemplate = new ZipkinRestTemplateWrapper(zipkin, extractor); restTemplate = zipkinRestTemplateCustomizer.customizeTemplate(restTemplate); - return new RestTemplateSender(restTemplate, zipkin.getBaseUrl(), zipkin.getEncoder()); + return new RestTemplateSender(restTemplate, zipkin.getBaseUrl(), zipkin.getApiPath(), zipkin.getEncoder()); } @Bean diff --git a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin2/RestTemplateSender.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin2/RestTemplateSender.java index f8d0ec224..72db2db33 100644 --- a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin2/RestTemplateSender.java +++ b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin2/RestTemplateSender.java @@ -19,6 +19,7 @@ package org.springframework.cloud.sleuth.zipkin2; import java.io.IOException; import java.net.URI; import java.util.List; +import java.util.Objects; import zipkin2.Call; import zipkin2.Callback; @@ -59,20 +60,28 @@ public class RestTemplateSender extends Sender { */ transient boolean closeCalled; + @Deprecated public RestTemplateSender(RestTemplate restTemplate, String baseUrl, BytesEncoder encoder) { + this(restTemplate, baseUrl, "", encoder); + } + + public RestTemplateSender(RestTemplate restTemplate, String baseUrl, String apiPath, BytesEncoder encoder) { this.restTemplate = restTemplate; this.encoding = encoder.encoding(); if (encoder.equals(JSON_V2)) { this.mediaType = MediaType.APPLICATION_JSON; - this.url = baseUrl + (baseUrl.endsWith("/") ? "" : "/") + "api/v2/spans"; + this.url = buildUrlWithCustomPathIfNecessary(baseUrl, apiPath, + baseUrl + (baseUrl.endsWith("/") ? "" : "/") + "api/v2/spans"); } else if (this.encoding == Encoding.PROTO3) { this.mediaType = MediaType.parseMediaType("application/x-protobuf"); - this.url = baseUrl + (baseUrl.endsWith("/") ? "" : "/") + "api/v2/spans"; + this.url = buildUrlWithCustomPathIfNecessary(baseUrl, apiPath, + baseUrl + (baseUrl.endsWith("/") ? "" : "/") + "api/v2/spans"); } else if (this.encoding == Encoding.JSON) { this.mediaType = MediaType.APPLICATION_JSON; - this.url = baseUrl + (baseUrl.endsWith("/") ? "" : "/") + "api/v1/spans"; + this.url = buildUrlWithCustomPathIfNecessary(baseUrl, apiPath, + baseUrl + (baseUrl.endsWith("/") ? "" : "/") + "api/v1/spans"); } else { throw new UnsupportedOperationException("Unsupported encoding: " + this.encoding.name()); @@ -80,6 +89,16 @@ public class RestTemplateSender extends Sender { this.messageEncoder = BytesMessageEncoder.forEncoding(this.encoding); } + private String buildUrlWithCustomPathIfNecessary(final String baseUrl, final String customApiPath, + final String defaultUrl) { + if (Objects.nonNull(customApiPath)) { + return baseUrl + + (baseUrl.endsWith("/") || customApiPath.startsWith("/") || customApiPath.isEmpty() ? "" : "/") + + customApiPath; + } + return defaultUrl; + } + @Override public Encoding encoding() { return this.encoding; diff --git a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin2/ZipkinProperties.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin2/ZipkinProperties.java index de1faccbe..14d150819 100644 --- a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin2/ZipkinProperties.java +++ b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin2/ZipkinProperties.java @@ -37,6 +37,13 @@ public class ZipkinProperties { */ private String baseUrl = "http://localhost:9411/"; + /** + * The API path to append to baseUrl (above) as suffix. This applies if you use other + * monitoring tools, such as New Relic. The trace API doesn't need the API path, so + * you can set it to blank ("") in the configuration. + */ + private String apiPath = null; + /** * If set to {@code false}, will treat the {@link ZipkinProperties#baseUrl} as a URL * always. @@ -84,6 +91,14 @@ public class ZipkinProperties { this.baseUrl = baseUrl; } + public String getApiPath() { + return this.apiPath; + } + + public void setApiPath(String apiPath) { + this.apiPath = apiPath; + } + public boolean isEnabled() { return this.enabled; } diff --git a/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin2/RestTemplateSenderTest.java b/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin2/RestTemplateSenderTest.java index 1be367ee7..bc201c16a 100644 --- a/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin2/RestTemplateSenderTest.java +++ b/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin2/RestTemplateSenderTest.java @@ -50,7 +50,7 @@ public class RestTemplateSenderTest { String endpoint = this.server.url("/api/v2/spans").toString(); - RestTemplateSender sender = new RestTemplateSender(new RestTemplate(), this.endpoint, JSON_V2); + RestTemplateSender sender = new RestTemplateSender(new RestTemplate(), this.endpoint, null, JSON_V2); @AfterEach void clean() throws IOException { @@ -74,7 +74,7 @@ public class RestTemplateSenderTest { @Test public void proto3() throws Exception { this.server.enqueue(new MockResponse()); - this.sender = new RestTemplateSender(new RestTemplate(), this.endpoint, PROTO3); + this.sender = new RestTemplateSender(new RestTemplate(), this.endpoint, "", PROTO3); send(SPAN).execute(); @@ -85,6 +85,37 @@ public class RestTemplateSenderTest { assertThat(request.getBody().readByteArray()).containsExactly(SpanBytesEncoder.PROTO3.encode(SPAN)); } + @Test + public void testWhereApiIsSetNonEmpty() { + final String mockedApiPath = "/test/v2"; + final RestTemplateSender senderWithMockedApiPath = new RestTemplateSender(new RestTemplate(), this.endpoint, + mockedApiPath, JSON_V2); + + assertThat(senderWithMockedApiPath.toString()) + .isEqualTo("RestTemplateSender{" + this.endpoint + mockedApiPath + "}"); + } + + @Test + public void testWhereApiIsSetToEmpty() { + final String mockedApiPath = ""; + final RestTemplateSender senderWithMockedApiPath = new RestTemplateSender(new RestTemplate(), this.endpoint, + mockedApiPath, JSON_V2); + + assertThat(senderWithMockedApiPath.toString()).isEqualTo("RestTemplateSender{" + this.endpoint + "}"); + } + + /** + * The output of toString() on {@link Sender} implementations appears in thread names + * created by {@link AsyncZipkinSpanHandler}. Since thread names are likely to be + * exposed in logs and other monitoring tools, care should be taken to ensure the + * toString() output is a reasonable length and does not contain sensitive + * information. + */ + @Test + public void toStringContainsOnlySenderTypeAndEndpoint() { + assertThat(sender.toString()).isEqualTo("RestTemplateSender{" + this.endpoint + "/api/v2/spans}"); + } + Call send(Span... spans) { SpanBytesEncoder bytesEncoder = this.sender.encoding() == Encoding.JSON ? SpanBytesEncoder.JSON_V2 : SpanBytesEncoder.PROTO3;