Added support for customization of HttpTracing
with this change you can define beans that will get added / set to the `HttpTracing` bean
fixes gh-886
This commit is contained in:
@@ -762,11 +762,31 @@ Running the preceding method with a value of `15` leads to setting a tag with a
|
||||
|
||||
//=== Spring Integration
|
||||
|
||||
// TODO: Update this
|
||||
=== HTTP
|
||||
|
||||
// === HTTP
|
||||
If a customization of client / server parsing of the HTTP related spans is required,
|
||||
just register a bean of type `brave.http.HttpClientParser` or
|
||||
`brave.http.HttpServerParser`. If client /server sampling is required, just
|
||||
register a bean of type `brave.http.HttpSampler` and name the bean
|
||||
`sleuthClientSampler` for client sampler and `sleuthServerSampler` for server sampler.
|
||||
For your convenience the `@ClientSampler` and `@ServerSampler`
|
||||
annotations can be used to inject the proper beans or to
|
||||
reference the bean names via their static String `NAME` fields.
|
||||
|
||||
// TODO: Update this
|
||||
Check out Brave's code to see an example of how to make a path-based sampler
|
||||
https://github.com/openzipkin/brave/tree/master/instrumentation/http#sampling-policy
|
||||
|
||||
If you want to completely rewrite the `HttpTracing` bean you can use the `SkipPatternProvider`
|
||||
interface to retrieve the URL `Pattern` for spans that should be not sampled. Below you can see
|
||||
an example of usage of `SkipPatternProvider` inside a server side, `HttpSampler`.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Configuration
|
||||
class Config {
|
||||
include::../../../../spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterWebIntegrationTests.java[tags=custom_server_sampler,indent=2]
|
||||
}
|
||||
----
|
||||
|
||||
=== `TraceFilter`
|
||||
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.sleuth.instrument.web;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
|
||||
/**
|
||||
* Annotate a client {@link brave.http.HttpSampler} that hsould be
|
||||
* injected to {@link brave.http.HttpTracing}
|
||||
*
|
||||
* @author Marcin Grzejszczak
|
||||
* @since 2.0.0
|
||||
* @see Qualifier
|
||||
*/
|
||||
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Inherited
|
||||
@Documented
|
||||
@Qualifier(ClientSampler.NAME)
|
||||
public @interface ClientSampler {
|
||||
|
||||
String NAME = "sleuthClientSampler";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.sleuth.instrument.web;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
|
||||
/**
|
||||
* Annotate a server {@link brave.http.HttpSampler} that hsould be
|
||||
* injected to {@link brave.http.HttpTracing}
|
||||
*
|
||||
* @author Marcin Grzejszczak
|
||||
* @since 2.0.0
|
||||
* @see Qualifier
|
||||
*/
|
||||
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Inherited
|
||||
@Documented
|
||||
@Qualifier(ServerSampler.NAME)
|
||||
public @interface ServerSampler {
|
||||
String NAME = "sleuthServerSampler";
|
||||
}
|
||||
@@ -19,11 +19,11 @@ package org.springframework.cloud.sleuth.instrument.web;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Internal interface to describe patterns to skip tracing
|
||||
* Provides a URL {@link Pattern} for spans that should be not sampled.
|
||||
*
|
||||
* @author Marcin Grzejszczak
|
||||
* @since 2.0.0
|
||||
*/
|
||||
interface SkipPatternProvider {
|
||||
public interface SkipPatternProvider {
|
||||
Pattern skipPattern();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.sleuth.instrument.web;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* @author Marcin Grzejszczak
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@ConfigurationProperties("spring.sleuth.http.legacy")
|
||||
public class SleuthHttpLegacyProperties {
|
||||
private boolean enabled;
|
||||
|
||||
public boolean isEnabled() {
|
||||
return this.enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
}
|
||||
@@ -17,12 +17,17 @@
|
||||
package org.springframework.cloud.sleuth.instrument.web;
|
||||
|
||||
import brave.Tracing;
|
||||
import brave.http.HttpAdapter;
|
||||
import brave.http.HttpClientParser;
|
||||
import brave.http.HttpSampler;
|
||||
import brave.http.HttpServerParser;
|
||||
import brave.http.HttpTracing;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.sleuth.ErrorParser;
|
||||
import org.springframework.cloud.sleuth.TraceKeys;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@@ -39,28 +44,94 @@ import org.springframework.context.annotation.Configuration;
|
||||
@ConditionalOnBean(Tracing.class)
|
||||
@ConditionalOnProperty(name = "spring.sleuth.http.enabled", havingValue = "true", matchIfMissing = true)
|
||||
@AutoConfigureAfter(TraceWebAutoConfiguration.class)
|
||||
@EnableConfigurationProperties(SleuthHttpLegacyProperties.class)
|
||||
public class TraceHttpAutoConfiguration {
|
||||
|
||||
@Autowired HttpClientParser clientParser;
|
||||
@Autowired HttpServerParser serverParser;
|
||||
@Autowired @ClientSampler HttpSampler clientSampler;
|
||||
@Autowired(required = false) @ServerSampler HttpSampler serverSampler;
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
// NOTE: stable bean name as might be used outside sleuth
|
||||
HttpTracing httpTracing(
|
||||
@Value("${spring.sleuth.http.legacy.enabled:false}") boolean legacyEnabled,
|
||||
Tracing tracing,
|
||||
TraceKeys traceKeys,
|
||||
ErrorParser errorParser,
|
||||
SkipPatternProvider provider
|
||||
) {
|
||||
if (legacyEnabled) {
|
||||
return HttpTracing.newBuilder(tracing)
|
||||
.clientParser(new SleuthHttpClientParser(traceKeys))
|
||||
.serverParser(new SleuthHttpServerParser(traceKeys, errorParser))
|
||||
.serverSampler(new SleuthHttpSampler(provider))
|
||||
.build();
|
||||
}
|
||||
return HttpTracing
|
||||
.newBuilder(tracing)
|
||||
.serverSampler(new SleuthHttpSampler(provider))
|
||||
SkipPatternProvider provider) {
|
||||
HttpSampler serverSampler = combineUserProvidedSamplerWithSkipPatternSampler(
|
||||
provider);
|
||||
return HttpTracing.newBuilder(tracing)
|
||||
.clientParser(this.clientParser)
|
||||
.serverParser(this.serverParser)
|
||||
.clientSampler(this.clientSampler)
|
||||
.serverSampler(serverSampler)
|
||||
.build();
|
||||
}
|
||||
|
||||
private HttpSampler combineUserProvidedSamplerWithSkipPatternSampler(
|
||||
SkipPatternProvider provider) {
|
||||
HttpSampler serverSampler = this.serverSampler;
|
||||
SleuthHttpSampler skipPatternSampler = new SleuthHttpSampler(provider);
|
||||
if (serverSampler == null) {
|
||||
return skipPatternSampler;
|
||||
}
|
||||
return new CompositeHttpSampler(skipPatternSampler, serverSampler);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(name = "spring.sleuth.http.legacy.enabled", havingValue = "true")
|
||||
HttpClientParser sleuthHttpClientParser(TraceKeys traceKeys) {
|
||||
return new SleuthHttpClientParser(traceKeys);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(name = "spring.sleuth.http.legacy.enabled",
|
||||
havingValue = "false", matchIfMissing = true)
|
||||
@ConditionalOnMissingBean
|
||||
HttpClientParser httpClientParser() {
|
||||
return new HttpClientParser();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(name = "spring.sleuth.http.legacy.enabled", havingValue = "true")
|
||||
HttpServerParser sleuthHttpServerParser(TraceKeys traceKeys, ErrorParser errorParser) {
|
||||
return new SleuthHttpServerParser(traceKeys, errorParser);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(name = "spring.sleuth.http.legacy.enabled",
|
||||
havingValue = "false", matchIfMissing = true)
|
||||
@ConditionalOnMissingBean
|
||||
HttpServerParser defaultHttpServerParser() {
|
||||
return new HttpServerParser();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(name = ClientSampler.NAME)
|
||||
HttpSampler sleuthClientSampler() {
|
||||
return HttpSampler.TRACE_ID;
|
||||
}
|
||||
}
|
||||
|
||||
class CompositeHttpSampler extends HttpSampler {
|
||||
|
||||
private final HttpSampler left, right;
|
||||
|
||||
CompositeHttpSampler(HttpSampler left, HttpSampler right) {
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
}
|
||||
|
||||
@Override public <Req> Boolean trySample(HttpAdapter<Req, ?> adapter, Req request) {
|
||||
// If either decision is false, return false
|
||||
Boolean leftDecision = this.left.trySample(adapter, request);
|
||||
if (Boolean.FALSE.equals(leftDecision)) return false;
|
||||
Boolean rightDecision = this.right.trySample(adapter, request);
|
||||
if (Boolean.FALSE.equals(rightDecision)) return false;
|
||||
// If either decision is null, return the other
|
||||
if (leftDecision == null) return rightDecision;
|
||||
if (rightDecision == null) return leftDecision;
|
||||
// Neither are null and at least one is true
|
||||
return leftDecision && rightDecision;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.sleuth.instrument.web;
|
||||
|
||||
import brave.http.HttpAdapter;
|
||||
import brave.http.HttpSampler;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import static org.assertj.core.api.BDDAssertions.then;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class CompositeHttpSamplerTests {
|
||||
|
||||
@Mock HttpAdapter adapter;
|
||||
@Mock HttpSampler left, right;
|
||||
HttpSampler sampler;
|
||||
Object request = new Object();
|
||||
|
||||
@Before
|
||||
public void init(){
|
||||
this.sampler = new CompositeHttpSampler(left, right);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_return_null_on_both_null() {
|
||||
given(this.left.trySample(this.adapter, this.request)).willReturn(null);
|
||||
given(this.right.trySample(this.adapter, this.request)).willReturn(null);
|
||||
|
||||
then(this.sampler.trySample(this.adapter, this.request)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_return_false_on_any_false() {
|
||||
given(this.left.trySample(this.adapter, this.request)).willReturn(false);
|
||||
given(this.right.trySample(this.adapter, this.request)).willReturn(null);
|
||||
|
||||
then(this.sampler.trySample(this.adapter, this.request)).isFalse();
|
||||
|
||||
given(this.left.trySample(this.adapter, this.request)).willReturn(null);
|
||||
given(this.right.trySample(this.adapter, this.request)).willReturn(false);
|
||||
|
||||
then(this.sampler.trySample(this.adapter, this.request)).isFalse();
|
||||
|
||||
given(this.left.trySample(this.adapter, this.request)).willReturn(false);
|
||||
given(this.right.trySample(this.adapter, this.request)).willReturn(true);
|
||||
|
||||
then(this.sampler.trySample(this.adapter, this.request)).isFalse();
|
||||
|
||||
given(this.left.trySample(this.adapter, this.request)).willReturn(true);
|
||||
given(this.right.trySample(this.adapter, this.request)).willReturn(false);
|
||||
|
||||
then(this.sampler.trySample(this.adapter, this.request)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_return_true_on_both_true() {
|
||||
given(this.left.trySample(this.adapter, this.request)).willReturn(true);
|
||||
given(this.right.trySample(this.adapter, this.request)).willReturn(true);
|
||||
|
||||
then(this.sampler.trySample(this.adapter, this.request)).isTrue();
|
||||
}
|
||||
}
|
||||
@@ -19,9 +19,12 @@ package org.springframework.cloud.sleuth.instrument.web;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import brave.Tracing;
|
||||
import brave.http.HttpAdapter;
|
||||
import brave.http.HttpSampler;
|
||||
import brave.sampler.Sampler;
|
||||
import org.assertj.core.api.BDDAssertions;
|
||||
import org.junit.After;
|
||||
@@ -62,6 +65,7 @@ public class TraceFilterWebIntegrationTests {
|
||||
|
||||
@Autowired Tracing tracer;
|
||||
@Autowired ArrayListSpanReporter accumulator;
|
||||
@Autowired @ServerSampler HttpSampler sampler;
|
||||
@Autowired Environment environment;
|
||||
@Rule public OutputCapture capture = new OutputCapture();
|
||||
|
||||
@@ -115,6 +119,11 @@ public class TraceFilterWebIntegrationTests {
|
||||
then(this.accumulator.getSpans().get(0).tags()).containsEntry("http.status_code", "400");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_inject_http_sampler() {
|
||||
then(this.sampler).isNotNull();
|
||||
}
|
||||
|
||||
private int port() {
|
||||
return this.environment.getProperty("local.server.port", Integer.class);
|
||||
}
|
||||
@@ -135,6 +144,23 @@ public class TraceFilterWebIntegrationTests {
|
||||
return Sampler.ALWAYS_SAMPLE;
|
||||
}
|
||||
|
||||
// tag::custom_server_sampler[]
|
||||
@Bean(name = ServerSampler.NAME)
|
||||
HttpSampler myHttpSampler(SkipPatternProvider provider) {
|
||||
Pattern pattern = provider.skipPattern();
|
||||
return new HttpSampler() {
|
||||
|
||||
@Override public <Req> Boolean trySample(HttpAdapter<Req, ?> adapter, Req request) {
|
||||
String url = adapter.path(request);
|
||||
boolean shouldSkip = pattern.matcher(url).matches();
|
||||
if (shouldSkip) {
|
||||
return false;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
// end::custom_server_sampler[]
|
||||
|
||||
@Bean RestTemplate restTemplate() {
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
|
||||
Reference in New Issue
Block a user