Implement mechanism to apply custom api path (#1859)
* Implement mechanism to apply custom api path to the sleuth server baseUrl. This comes in handy when another monitoring tool is being used, for instance new relic, which doesn't not require path in its tracing api. * Fix up documentation on the ZipkinProperties * Update the main documentation for api path * Add missing comma
This commit is contained in:
@@ -1050,6 +1050,13 @@ If you want to find Zipkin through service discovery, you can pass the Zipkin's
|
||||
spring.zipkin.baseUrl: https://zipkinserver/
|
||||
----
|
||||
|
||||
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.apiPath: v2/path2
|
||||
----
|
||||
|
||||
To disable this feature just set `spring.zipkin.discoveryClientEnabled` to `false.
|
||||
|
||||
When the Discovery Client feature is enabled, Sleuth uses
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ package org.springframework.cloud.sleuth.zipkin2.sender;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import zipkin2.Call;
|
||||
import zipkin2.Callback;
|
||||
@@ -54,21 +55,24 @@ final class RestTemplateSender extends Sender {
|
||||
*/
|
||||
transient boolean closeCalled;
|
||||
|
||||
RestTemplateSender(RestTemplate restTemplate, String baseUrl,
|
||||
RestTemplateSender(RestTemplate restTemplate, String baseUrl, String apiPath,
|
||||
BytesEncoder<Span> encoder) {
|
||||
this.restTemplate = restTemplate;
|
||||
this.encoding = encoder.encoding();
|
||||
if (encoder.equals(JSON_V2)) {
|
||||
this.mediaType = MediaType.APPLICATION_JSON;
|
||||
this.url = baseUrl + (baseUrl.endsWith("/") ? "" : "/") + "api/v2/spans";
|
||||
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(
|
||||
@@ -77,6 +81,16 @@ final 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;
|
||||
|
||||
@@ -64,7 +64,7 @@ class ZipkinRestTemplateSenderConfiguration {
|
||||
RestTemplate restTemplate = new ZipkinRestTemplateWrapper(zipkin, this.extractor);
|
||||
restTemplate = zipkinRestTemplateCustomizer.customizeTemplate(restTemplate);
|
||||
return new RestTemplateSender(restTemplate, zipkin.getBaseUrl(),
|
||||
zipkin.getEncoder());
|
||||
zipkin.getApiPath(), zipkin.getEncoder());
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -106,6 +106,60 @@ public class ZipkinAutoConfigurationTests {
|
||||
then(request.getBody().readUtf8()).contains("localEndpoint");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void useCustomApiPathIfSetEmpty() throws InterruptedException {
|
||||
this.context = new AnnotationConfigApplicationContext();
|
||||
|
||||
environment().setProperty("spring.zipkin.base-url",
|
||||
this.server.url("/").toString());
|
||||
environment().setProperty("spring.zipkin.api-path", "");
|
||||
|
||||
this.context.register(ZipkinAutoConfiguration.class,
|
||||
PropertyPlaceholderAutoConfiguration.class, TraceAutoConfiguration.class,
|
||||
Config.class, ZipkinBackwardsCompatibilityAutoConfiguration.class);
|
||||
this.context.refresh();
|
||||
Span span = this.context.getBean(Tracing.class).tracer().nextSpan().name("foo")
|
||||
.tag("foo", "bar").start();
|
||||
|
||||
span.finish();
|
||||
|
||||
Awaitility.await().untilAsserted(
|
||||
() -> then(this.server.getRequestCount()).isGreaterThan(1));
|
||||
// first request is for health check
|
||||
this.server.takeRequest();
|
||||
// second request is the span one
|
||||
RecordedRequest request = this.server.takeRequest();
|
||||
then(request.getPath()).isEqualTo("/");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void useCustomApiPathIfSetNonEmpty() throws InterruptedException {
|
||||
final String testPath = "test/v2";
|
||||
|
||||
this.context = new AnnotationConfigApplicationContext();
|
||||
|
||||
environment().setProperty("spring.zipkin.base-url",
|
||||
this.server.url("").toString());
|
||||
environment().setProperty("spring.zipkin.api-path", testPath);
|
||||
|
||||
this.context.register(ZipkinAutoConfiguration.class,
|
||||
PropertyPlaceholderAutoConfiguration.class, TraceAutoConfiguration.class,
|
||||
Config.class, ZipkinBackwardsCompatibilityAutoConfiguration.class);
|
||||
this.context.refresh();
|
||||
Span span = this.context.getBean(Tracing.class).tracer().nextSpan().name("foo")
|
||||
.tag("foo", "bar").start();
|
||||
|
||||
span.finish();
|
||||
|
||||
Awaitility.await().untilAsserted(
|
||||
() -> then(this.server.getRequestCount()).isGreaterThan(1));
|
||||
// first request is for health check
|
||||
this.server.takeRequest();
|
||||
// second request is the span one
|
||||
RecordedRequest request = this.server.takeRequest();
|
||||
then(request.getPath()).isEqualTo("/" + testPath);
|
||||
}
|
||||
|
||||
private MockEnvironment environment() {
|
||||
this.context.setEnvironment(this.environment);
|
||||
return this.environment;
|
||||
|
||||
@@ -54,7 +54,7 @@ public class RestTemplateSenderTest {
|
||||
String baseUrl = "http://localhost:" + this.server.getPort();
|
||||
|
||||
RestTemplateSender sender = new RestTemplateSender(new RestTemplate(), this.baseUrl,
|
||||
JSON_V2);
|
||||
null, JSON_V2);
|
||||
|
||||
/**
|
||||
* Tests that json is not manipulated as a side-effect of using rest template.
|
||||
@@ -73,7 +73,8 @@ public class RestTemplateSenderTest {
|
||||
@Test
|
||||
public void proto3() throws Exception {
|
||||
this.server.enqueue(new MockResponse());
|
||||
this.sender = new RestTemplateSender(new RestTemplate(), this.baseUrl, PROTO3);
|
||||
this.sender = new RestTemplateSender(new RestTemplate(), this.baseUrl, "",
|
||||
PROTO3);
|
||||
|
||||
send(SPAN).execute();
|
||||
|
||||
@@ -85,6 +86,26 @@ public class RestTemplateSenderTest {
|
||||
.containsExactly(SpanBytesEncoder.PROTO3.encode(SPAN));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWhereApiIsSetNonEmpty() {
|
||||
final String mockedApiPath = "/test/v2";
|
||||
final RestTemplateSender senderWithMockedApiPath = new RestTemplateSender(
|
||||
new RestTemplate(), this.baseUrl, mockedApiPath, JSON_V2);
|
||||
|
||||
assertThat(senderWithMockedApiPath.toString())
|
||||
.isEqualTo("RestTemplateSender{" + baseUrl + mockedApiPath + "}");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWhereApiIsSetToEmpty() {
|
||||
final String mockedApiPath = "";
|
||||
final RestTemplateSender senderWithMockedApiPath = new RestTemplateSender(
|
||||
new RestTemplate(), this.baseUrl, mockedApiPath, JSON_V2);
|
||||
|
||||
assertThat(senderWithMockedApiPath.toString())
|
||||
.isEqualTo("RestTemplateSender{" + baseUrl + "}");
|
||||
}
|
||||
|
||||
/**
|
||||
* The output of toString() on {@link Sender} implementations appears in thread names
|
||||
* created by {@link AsyncZipkinSpanHandler}. Since thread names are likely to be
|
||||
|
||||
Reference in New Issue
Block a user