Merge branch '2.2.x'

This commit is contained in:
Marcin Grzejszczak
2020-03-03 19:44:56 +01:00
22 changed files with 679 additions and 125 deletions

View File

@@ -838,12 +838,38 @@ Sleuth will search for beans of those types and automatically apply customizatio
=== 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.sampler.SamplerFunction<HttpRequest>` and name
the bean `sleuthHttpClientSampler` for client sampler and
`sleuthHttpServerSampler` for server sampler.
==== Data Policy
The default span data policy for HTTP requests is described in Brave:
https://github.com/openzipkin/brave/tree/master/instrumentation/http#span-data-policy
To add different data to the span, you need to register a bean of type
`brave.http.HttpRequestParser` or `brave.http.HttpResponseParser` based on when
the data is collected.
The bean names correspond to the request or response side, and whether it is
a client or server. For example, `sleuthHttpClientRequestParser` changes what
is collected before a client request is sent to the server.
For your convenience `@HttpClientRequestParser`, `@HttpClientResponseParser`
and corresponding server annotations can be used to inject the proper beans
or to reference the bean names via their static String `NAME` fields.
Here's an example adding the HTTP url in addition to defaults:
[source,java]
----
@Configuration
class Config {
include::{project-root}/tests/spring-cloud-sleuth-instrumentation-mvc-tests/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterWebIntegrationTests.java[tags=custom_parser,indent=2]
}
----
==== Sampling
If client /server sampling is required, just register a bean of type
`brave.sampler.SamplerFunction<HttpRequest>` and name the bean
`sleuthHttpClientSampler` for client sampler and `sleuthHttpServerSampler`
for server sampler.
For your convenience the `@HttpClientSampler` and `@HttpServerSampler`
annotations can be used to inject the proper beans or to reference the bean

View File

@@ -0,0 +1,51 @@
/*
* Copyright 2013-2019 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.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 brave.http.HttpRequestParser;
import brave.http.HttpTracing;
import org.springframework.beans.factory.annotation.Qualifier;
/**
* Annotate a client {@link HttpRequestParser} that should be injected to
* {@link HttpTracing.Builder#clientRequestParser(HttpRequestParser)}.
*
* @see Qualifier
* @since 2.2.2
*/
@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE,
ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Qualifier(HttpClientRequestParser.NAME)
public @interface HttpClientRequestParser {
/**
* Default name for Sleuth HTTP client request parser.
*/
String NAME = "sleuthHttpClientRequestParser";
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright 2013-2019 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.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 brave.http.HttpResponseParser;
import brave.http.HttpTracing;
import org.springframework.beans.factory.annotation.Qualifier;
/**
* Annotate a client {@link HttpResponseParser} that should be injected to
* {@link HttpTracing.Builder#clientResponseParser(HttpResponseParser)}.
*
* @see Qualifier
* @since 2.2.2
*/
@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE,
ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Qualifier(HttpClientResponseParser.NAME)
public @interface HttpClientResponseParser {
/**
* Default name for Sleuth HTTP client response parser.
*/
String NAME = "sleuthHttpClientResponseParser";
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright 2013-2019 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.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 brave.http.HttpRequestParser;
import brave.http.HttpTracing;
import org.springframework.beans.factory.annotation.Qualifier;
/**
* Annotate a server {@link HttpRequestParser} that should be injected to
* {@link HttpTracing.Builder#serverRequestParser(HttpRequestParser)}.
*
* @see Qualifier
* @since 2.2.2
*/
@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE,
ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Qualifier(HttpServerRequestParser.NAME)
public @interface HttpServerRequestParser {
/**
* Default name for Sleuth HTTP server request parser.
*/
String NAME = "sleuthHttpServerRequestParser";
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright 2013-2019 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.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 brave.http.HttpResponseParser;
import brave.http.HttpTracing;
import org.springframework.beans.factory.annotation.Qualifier;
/**
* Annotate a server {@link HttpResponseParser} that should be injected to
* {@link HttpTracing.Builder#serverResponseParser(HttpResponseParser)}.
*
* @see Qualifier
* @since 2.2.2
*/
@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE,
ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Qualifier(HttpServerResponseParser.NAME)
public @interface HttpServerResponseParser {
/**
* Default name for Sleuth HTTP server response parser.
*/
String NAME = "sleuthHttpServerResponseParser";
}

View File

@@ -19,11 +19,10 @@ package org.springframework.cloud.sleuth.instrument.web;
import java.util.ArrayList;
import java.util.List;
import brave.ErrorParser;
import brave.Tracing;
import brave.http.HttpClientParser;
import brave.http.HttpRequest;
import brave.http.HttpServerParser;
import brave.http.HttpRequestParser;
import brave.http.HttpResponseParser;
import brave.http.HttpTracing;
import brave.http.HttpTracingCustomizer;
import brave.sampler.SamplerFunction;
@@ -61,17 +60,47 @@ public class TraceHttpAutoConfiguration {
@ConditionalOnMissingBean
// NOTE: stable bean name as might be used outside sleuth
HttpTracing httpTracing(Tracing tracing, SkipPatternProvider provider,
HttpClientParser clientParser, HttpServerParser serverParser,
@Nullable @HttpClientRequestParser HttpRequestParser httpClientRequestParser,
@Nullable @HttpClientResponseParser HttpResponseParser httpClientResponseParser,
@Nullable brave.http.HttpClientParser clientParser,
@Nullable @HttpServerRequestParser HttpRequestParser httpServerRequestParser,
@Nullable @HttpServerResponseParser HttpResponseParser httpServerResponseParser,
@Nullable brave.http.HttpServerParser serverParser,
@HttpClientSampler SamplerFunction<HttpRequest> httpClientSampler,
@Nullable @HttpServerSampler SamplerFunction<HttpRequest> httpServerSampler) {
SamplerFunction<HttpRequest> combinedSampler = combineUserProvidedSamplerWithSkipPatternSampler(
httpServerSampler, provider);
HttpTracing.Builder builder = HttpTracing.newBuilder(tracing)
.clientParser(clientParser).serverParser(serverParser)
.clientSampler(httpClientSampler).serverSampler(combinedSampler);
if (httpClientRequestParser != null || httpClientResponseParser != null) {
if (httpClientRequestParser != null) {
builder.clientRequestParser(httpClientRequestParser);
}
if (httpClientResponseParser != null) {
builder.clientResponseParser(httpClientResponseParser);
}
}
else if (clientParser != null) { // consider deprecated last
builder.clientParser(clientParser);
}
if (httpServerRequestParser != null || httpServerResponseParser != null) {
if (httpServerRequestParser != null) {
builder.serverRequestParser(httpServerRequestParser);
}
if (httpServerResponseParser != null) {
builder.serverResponseParser(httpServerResponseParser);
}
}
else if (serverParser != null) { // consider deprecated last
builder.serverParser(serverParser);
}
for (HttpTracingCustomizer customizer : this.httpTracingCustomizers) {
customizer.customize(builder);
}
return builder.build();
}
@@ -86,23 +115,6 @@ public class TraceHttpAutoConfiguration {
return new CompositeHttpSampler(skipPatternSampler, serverSampler);
}
@Bean
@ConditionalOnMissingBean
HttpClientParser httpClientParser(ErrorParser errorParser) {
return new HttpClientParser() {
@Override
protected ErrorParser errorParser() {
return errorParser;
}
};
}
@Bean
@ConditionalOnMissingBean
HttpServerParser defaultHttpServerParser() {
return new HttpServerParser();
}
@Bean
@ConditionalOnMissingBean(name = HttpClientSampler.NAME)
SamplerFunction<HttpRequest> sleuthHttpClientSampler(

View File

@@ -152,7 +152,7 @@ class HttpClientBeanPostProcessor implements BeanPostProcessor {
// Start a new client span with the appropriate parent
TraceContext parent = req.currentContext().getOrDefault(TraceContext.class,
null);
WrappedHttpClientRequest request = new WrappedHttpClientRequest(req);
HttpClientRequestWrapper request = new HttpClientRequestWrapper(req);
clientSpan = handler().handleSendWithParent(request, parent);
parseConnectionAddress(connection, clientSpan);
@@ -240,18 +240,18 @@ class HttpClientBeanPostProcessor implements BeanPostProcessor {
if (clientSpan == null) {
return; // Unexpected. In the handle method, without a span to finish!
}
WrappedHttpClientResponse response = resp != null
? new WrappedHttpClientResponse(resp) : null;
HttpClientResponseWrapper response = resp != null
? new HttpClientResponseWrapper(resp) : null;
handler().handleReceive(response, error, clientSpan);
}
}
static final class WrappedHttpClientRequest extends brave.http.HttpClientRequest {
static final class HttpClientRequestWrapper extends brave.http.HttpClientRequest {
final HttpClientRequest delegate;
WrappedHttpClientRequest(HttpClientRequest delegate) {
HttpClientRequestWrapper(HttpClientRequest delegate) {
this.delegate = delegate;
}
@@ -287,11 +287,11 @@ class HttpClientBeanPostProcessor implements BeanPostProcessor {
}
static final class WrappedHttpClientResponse extends brave.http.HttpClientResponse {
static final class HttpClientResponseWrapper extends brave.http.HttpClientResponse {
final HttpClientResponse delegate;
WrappedHttpClientResponse(HttpClientResponse delegate) {
HttpClientResponseWrapper(HttpClientResponse delegate) {
this.delegate = delegate;
}

View File

@@ -23,6 +23,8 @@ import java.util.function.Function;
import brave.Span;
import brave.http.HttpClientHandler;
import brave.http.HttpClientRequest;
import brave.http.HttpClientResponse;
import brave.http.HttpTracing;
import brave.propagation.CurrentTraceContext;
import brave.propagation.CurrentTraceContext.Scope;
@@ -122,7 +124,7 @@ final class TraceExchangeFilterFunction implements ExchangeFilterFunction {
final Function<? super Publisher<DataBuffer>, ? extends Publisher<DataBuffer>> scopePassingTransformer;
// Lazy initialized fields
HttpClientHandler<brave.http.HttpClientRequest, brave.http.HttpClientResponse> handler;
HttpClientHandler<HttpClientRequest, HttpClientResponse> handler;
CurrentTraceContext currentTraceContext;
@@ -148,7 +150,7 @@ final class TraceExchangeFilterFunction implements ExchangeFilterFunction {
return this.currentTraceContext;
}
HttpClientHandler<brave.http.HttpClientRequest, brave.http.HttpClientResponse> handler() {
HttpClientHandler<HttpClientRequest, HttpClientResponse> handler() {
if (this.handler == null) {
this.handler = HttpClientHandler.create(this.httpTracing.get());
}
@@ -161,7 +163,7 @@ final class TraceExchangeFilterFunction implements ExchangeFilterFunction {
final ClientRequest request;
final HttpClientHandler<brave.http.HttpClientRequest, brave.http.HttpClientResponse> handler;
final HttpClientHandler<HttpClientRequest, HttpClientResponse> handler;
final CurrentTraceContext currentTraceContext;
@@ -185,7 +187,7 @@ final class TraceExchangeFilterFunction implements ExchangeFilterFunction {
Context context = subscriber.currentContext();
HttpClientRequest wrapper = new HttpClientRequest(request);
ClientRequestWrapper wrapper = new ClientRequestWrapper(request);
Span span = handler.handleSendWithParent(wrapper, parent);
if (log.isDebugEnabled()) {
log.debug("HttpClientHandler::handleSend: " + span);
@@ -209,7 +211,7 @@ final class TraceExchangeFilterFunction implements ExchangeFilterFunction {
final Span clientSpan;
final HttpClientHandler<brave.http.HttpClientRequest, brave.http.HttpClientResponse> handler;
final HttpClientHandler<HttpClientRequest, HttpClientResponse> handler;
final Function<? super Publisher<DataBuffer>, ? extends Publisher<DataBuffer>> scopePassingTransformer;
@@ -308,20 +310,20 @@ final class TraceExchangeFilterFunction implements ExchangeFilterFunction {
}
void handleReceive(@Nullable ClientResponse res, @Nullable Throwable error) {
HttpClientResponse response = res != null ? new HttpClientResponse(res)
ClientResponseWrapper response = res != null ? new ClientResponseWrapper(res)
: null;
this.handler.handleReceive(response, error, clientSpan);
}
}
private static final class HttpClientRequest extends brave.http.HttpClientRequest {
private static final class ClientRequestWrapper extends HttpClientRequest {
final ClientRequest delegate;
final ClientRequest.Builder builder;
HttpClientRequest(ClientRequest delegate) {
ClientRequestWrapper(ClientRequest delegate) {
this.delegate = delegate;
this.builder = ClientRequest.from(delegate);
}
@@ -362,11 +364,11 @@ final class TraceExchangeFilterFunction implements ExchangeFilterFunction {
}
static final class HttpClientResponse extends brave.http.HttpClientResponse {
static final class ClientResponseWrapper extends HttpClientResponse {
final ClientResponse delegate;
HttpClientResponse(ClientResponse delegate) {
ClientResponseWrapper(ClientResponse delegate) {
this.delegate = delegate;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.
@@ -76,7 +76,7 @@ final class TraceFeignObjectWrapper {
private Object instrumentedFeignLoadBalancerClient(Object bean) {
if (AopUtils.getTargetClass(bean).equals(FeignBlockingLoadBalancerClient.class)) {
FeignBlockingLoadBalancerClient client = ((FeignBlockingLoadBalancerClient) bean);
FeignBlockingLoadBalancerClient client = ProxyUtils.getTargetObject(bean);
return new TraceFeignBlockingLoadBalancerClient(
(Client) new TraceFeignObjectWrapper(this.beanFactory)
.wrap(client.getDelegate()),

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.
@@ -25,10 +25,12 @@ import java.util.LinkedHashMap;
import java.util.Map;
import brave.Span;
import brave.Tracer;
import brave.http.HttpClientHandler;
import brave.http.HttpClientRequest;
import brave.http.HttpClientResponse;
import brave.http.HttpTracing;
import brave.propagation.Propagation;
import brave.propagation.CurrentTraceContext;
import brave.propagation.CurrentTraceContext.Scope;
import feign.Client;
import feign.Request;
import feign.Response;
@@ -36,6 +38,7 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.util.ProxyUtils;
import org.springframework.lang.Nullable;
/**
* Feign client wrapper.
@@ -47,37 +50,14 @@ final class TracingFeignClient implements Client {
private static final Log log = LogFactory.getLog(TracingFeignClient.class);
static final Propagation.Setter<Map<String, Collection<String>>, String> SETTER = new Propagation.Setter<Map<String, Collection<String>>, String>() {
@Override
public void put(Map<String, Collection<String>> carrier, String key,
String value) {
if (!carrier.containsKey(key)) {
carrier.put(key, Collections.singletonList(value));
if (log.isTraceEnabled()) {
log.trace("Added key [" + key + "] and header value [" + value + "]");
}
}
else {
if (log.isTraceEnabled()) {
log.trace("Key [" + key + "] already there in the headers");
}
}
}
@Override
public String toString() {
return "Map::set";
}
};
final Tracer tracer;
final CurrentTraceContext currentTraceContext;
final Client delegate;
final HttpClientHandler<brave.http.HttpClientRequest, brave.http.HttpClientResponse> handler;
final HttpClientHandler<HttpClientRequest, HttpClientResponse> handler;
TracingFeignClient(HttpTracing httpTracing, Client delegate) {
this.tracer = httpTracing.tracing().tracer();
this.currentTraceContext = httpTracing.tracing().currentTraceContext();
this.handler = HttpClientHandler.create(httpTracing);
Client delegateTarget = ProxyUtils.getTargetObject(delegate);
this.delegate = delegateTarget instanceof TracingFeignClient
@@ -90,29 +70,27 @@ final class TracingFeignClient implements Client {
@Override
public Response execute(Request req, Request.Options options) throws IOException {
HttpClientRequest request = new HttpClientRequest(req);
RequestWrapper request = new RequestWrapper(req);
Span span = this.handler.handleSend(request);
if (log.isDebugEnabled()) {
log.debug("Handled send of " + span);
}
HttpClientResponse response = null;
Response res = null;
Throwable error = null;
try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(span)) {
Response res = this.delegate.execute(request.build(), options);
if (res != null) {
response = new HttpClientResponse(res);
}
else { // possibly null on bad implementation or mocks
response = new HttpClientResponse(
Response.builder().request(req).build());
try (Scope ws = this.currentTraceContext.newScope(span.context())) {
res = this.delegate.execute(request.build(), options);
if (res == null) { // possibly null on bad implementation or mocks
res = Response.builder().request(req).build();
}
return res;
}
catch (IOException | RuntimeException | Error e) {
catch (Throwable e) {
error = e;
throw e;
}
finally {
ResponseWrapper response = res != null
? new ResponseWrapper(request, res, error) : null;
this.handler.handleReceive(response, error, span);
if (log.isDebugEnabled()) {
@@ -121,20 +99,22 @@ final class TracingFeignClient implements Client {
}
}
void handleSendAndReceive(Span span, Request request, Response response,
Throwable error) {
this.handler.handleSend(new HttpClientRequest(request), span);
this.handler.handleReceive(
response != null ? new HttpClientResponse(response) : null, error, span);
void handleSendAndReceive(Span span, Request req, @Nullable Response res,
@Nullable Throwable error) {
RequestWrapper request = new RequestWrapper(req);
this.handler.handleSend(request, span);
ResponseWrapper response = res != null ? new ResponseWrapper(request, res, error)
: null;
this.handler.handleReceive(response, error, span);
}
static final class HttpClientRequest extends brave.http.HttpClientRequest {
static final class RequestWrapper extends HttpClientRequest {
final Request delegate;
Map<String, Collection<String>> headers;
HttpClientRequest(Request delegate) {
RequestWrapper(Request delegate) {
this.delegate = delegate;
}
@@ -202,22 +182,41 @@ final class TracingFeignClient implements Client {
}
static final class HttpClientResponse extends brave.http.HttpClientResponse {
static final class ResponseWrapper extends HttpClientResponse {
final Response delegate;
final RequestWrapper request;
HttpClientResponse(Response delegate) {
this.delegate = delegate;
final Response response;
@Nullable
final Throwable error;
ResponseWrapper(RequestWrapper request, Response response,
@Nullable Throwable error) {
this.request = request;
this.response = response;
this.error = error;
}
@Override
public Object unwrap() {
return delegate;
return response;
}
@Override
public RequestWrapper request() {
return request;
}
@Override
@Nullable
public Throwable error() {
return error;
}
@Override
public int statusCode() {
return delegate.status();
return response.status();
}
}

View File

@@ -17,6 +17,8 @@
package org.springframework.cloud.sleuth.instrument.web;
import brave.http.HttpRequest;
import brave.http.HttpRequestParser;
import brave.http.HttpResponseParser;
import brave.http.HttpTracing;
import brave.sampler.SamplerFunction;
import org.junit.Test;
@@ -85,6 +87,86 @@ public class TraceHttpAutoConfigurationTests {
};
}
@Test
public void defaultHttpClientParser() {
contextRunner().run((context) -> {
HttpRequestParser clientRequestParser = context.getBean(HttpTracing.class)
.clientRequestParser();
HttpResponseParser clientResponseParser = context.getBean(HttpTracing.class)
.clientResponseParser();
then(clientRequestParser).isInstanceOf(HttpRequestParser.Default.class);
then(clientResponseParser).isInstanceOf(HttpResponseParser.Default.class);
});
}
@Test
public void configuresUserProvidedHttpClientParser() {
contextRunner().withUserConfiguration(HttpClientParserConfig.class)
.run((context) -> {
HttpRequestParser clientRequestParser = context
.getBean(HttpTracing.class).clientRequestParser();
HttpResponseParser clientResponseParser = context
.getBean(HttpTracing.class).clientResponseParser();
then(clientRequestParser)
.isSameAs(HttpClientParserConfig.REQUEST_PARSER);
then(clientResponseParser)
.isSameAs(HttpClientParserConfig.RESPONSE_PARSER);
});
}
@Test
public void defaultHttpServerParser() {
contextRunner().run((context) -> {
HttpRequestParser serverRequestParser = context.getBean(HttpTracing.class)
.serverRequestParser();
HttpResponseParser serverResponseParser = context.getBean(HttpTracing.class)
.serverResponseParser();
then(serverRequestParser).isInstanceOf(HttpRequestParser.Default.class);
then(serverResponseParser).isInstanceOf(HttpResponseParser.Default.class);
});
}
@Test
public void configuresUserProvidedHttpServerParser() {
contextRunner().withUserConfiguration(HttpServerParserConfig.class)
.run((context) -> {
HttpRequestParser serverRequestParser = context
.getBean(HttpTracing.class).serverRequestParser();
HttpResponseParser serverResponseParser = context
.getBean(HttpTracing.class).serverResponseParser();
then(serverRequestParser)
.isSameAs(HttpServerParserConfig.REQUEST_PARSER);
then(serverResponseParser)
.isSameAs(HttpServerParserConfig.RESPONSE_PARSER);
});
}
/**
* Shows bean aliases work to configure the same instance for both client and server
*/
@Test
public void configuresUserProvidedHttpClientAndServerParser() {
contextRunner().withUserConfiguration(HttpParserConfig.class).run((context) -> {
HttpRequestParser serverRequestParser = context.getBean(HttpTracing.class)
.serverRequestParser();
HttpResponseParser serverResponseParser = context.getBean(HttpTracing.class)
.serverResponseParser();
HttpRequestParser clientRequestParser = context.getBean(HttpTracing.class)
.clientRequestParser();
HttpResponseParser clientResponseParser = context.getBean(HttpTracing.class)
.clientResponseParser();
then(clientRequestParser).isSameAs(HttpParserConfig.REQUEST_PARSER);
then(clientResponseParser).isSameAs(HttpParserConfig.RESPONSE_PARSER);
then(serverRequestParser).isSameAs(HttpParserConfig.REQUEST_PARSER);
then(serverResponseParser).isSameAs(HttpParserConfig.RESPONSE_PARSER);
});
}
private ApplicationContextRunner contextRunner(String... propertyValues) {
return new ApplicationContextRunner().withPropertyValues(propertyValues)
.withConfiguration(AutoConfigurations.of(TraceAutoConfiguration.class,
@@ -117,3 +199,63 @@ class HttpServerSamplerConfig {
}
}
@Configuration
class HttpClientParserConfig {
static final HttpRequestParser REQUEST_PARSER = (r, c, s) -> {
};
static final HttpResponseParser RESPONSE_PARSER = (r, c, s) -> {
};
@Bean(HttpClientRequestParser.NAME)
HttpRequestParser sleuthHttpClientRequestParser() {
return REQUEST_PARSER;
}
@Bean(HttpClientResponseParser.NAME)
HttpResponseParser sleuthHttpClientResponseParser() {
return RESPONSE_PARSER;
}
}
@Configuration
class HttpServerParserConfig {
static final HttpRequestParser REQUEST_PARSER = (r, c, s) -> {
};
static final HttpResponseParser RESPONSE_PARSER = (r, c, s) -> {
};
@Bean(HttpServerRequestParser.NAME)
HttpRequestParser sleuthHttpServerRequestParser() {
return REQUEST_PARSER;
}
@Bean(HttpServerResponseParser.NAME)
HttpResponseParser sleuthHttpServerResponseParser() {
return RESPONSE_PARSER;
}
}
@Configuration
class HttpParserConfig {
static final HttpRequestParser REQUEST_PARSER = (r, c, s) -> {
};
static final HttpResponseParser RESPONSE_PARSER = (r, c, s) -> {
};
@Bean(name = { HttpClientRequestParser.NAME, HttpServerRequestParser.NAME })
HttpRequestParser sleuthHttpServerRequestParser() {
return REQUEST_PARSER;
}
@Bean(name = { HttpClientResponseParser.NAME, HttpServerResponseParser.NAME })
HttpResponseParser sleuthHttpServerResponseParser() {
return RESPONSE_PARSER;
}
}

View File

@@ -20,7 +20,7 @@ import org.assertj.core.api.BDDAssertions;
import org.junit.Test;
import org.mockito.BDDMockito;
import org.springframework.cloud.sleuth.instrument.web.client.TraceExchangeFilterFunction.HttpClientResponse;
import org.springframework.cloud.sleuth.instrument.web.client.TraceExchangeFilterFunction.ClientResponseWrapper;
import org.springframework.web.reactive.function.client.ClientResponse;
public class TraceExchangeFilterFunctionHttpClientResponseTests {
@@ -29,7 +29,7 @@ public class TraceExchangeFilterFunctionHttpClientResponseTests {
public void should_return_0_when_invalid_status_code_is_returned() {
ClientResponse clientResponse = BDDMockito.mock(ClientResponse.class);
BDDMockito.given(clientResponse.rawStatusCode()).willReturn(-1);
HttpClientResponse response = new HttpClientResponse(clientResponse);
ClientResponseWrapper response = new ClientResponseWrapper(clientResponse);
Integer statusCode = response.statusCode();
@@ -40,7 +40,7 @@ public class TraceExchangeFilterFunctionHttpClientResponseTests {
public void should_return_status_code_when_valid_status_code_is_returned() {
ClientResponse clientResponse = BDDMockito.mock(ClientResponse.class);
BDDMockito.given(clientResponse.rawStatusCode()).willReturn(200);
HttpClientResponse response = new HttpClientResponse(clientResponse);
ClientResponseWrapper response = new ClientResponseWrapper(clientResponse);
Integer statusCode = response.statusCode();

View File

@@ -22,7 +22,6 @@ import java.util.HashMap;
import java.util.concurrent.atomic.AtomicInteger;
import brave.Tracing;
import brave.http.HttpClientParser;
import brave.http.HttpTracing;
import brave.propagation.StrictScopeDecorator;
import brave.propagation.ThreadLocalCurrentTraceContext;
@@ -69,8 +68,7 @@ public class FeignRetriesTests {
.addScopeDecorator(StrictScopeDecorator.create()).build())
.spanReporter(this.reporter).build();
HttpTracing httpTracing = HttpTracing.newBuilder(this.tracing)
.clientParser(new HttpClientParser()).build();
HttpTracing httpTracing = HttpTracing.newBuilder(this.tracing).build();
@Before
@After

View File

@@ -19,7 +19,6 @@ package org.springframework.cloud.sleuth.instrument.web.client.feign;
import java.io.IOException;
import brave.Tracing;
import brave.http.HttpClientParser;
import brave.http.HttpTracing;
import brave.propagation.StrictScopeDecorator;
import brave.propagation.ThreadLocalCurrentTraceContext;
@@ -57,8 +56,7 @@ public class TraceFeignAspectTests {
.addScopeDecorator(StrictScopeDecorator.create()).build())
.build();
HttpTracing httpTracing = HttpTracing.newBuilder(this.tracing)
.clientParser(new HttpClientParser()).build();
HttpTracing httpTracing = HttpTracing.newBuilder(this.tracing).build();
TraceFeignAspect traceFeignAspect;

View File

@@ -24,7 +24,6 @@ import java.util.List;
import brave.Span;
import brave.Tracer;
import brave.Tracing;
import brave.http.HttpClientParser;
import brave.http.HttpTracing;
import brave.propagation.StrictScopeDecorator;
import brave.propagation.ThreadLocalCurrentTraceContext;
@@ -59,8 +58,7 @@ public class TracingFeignClientTests {
Tracer tracer = this.tracing.tracer();
HttpTracing httpTracing = HttpTracing.newBuilder(this.tracing)
.clientParser(new HttpClientParser()).build();
HttpTracing httpTracing = HttpTracing.newBuilder(this.tracing).build();
@Mock
Client client;

View File

@@ -19,13 +19,13 @@ package sample;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
@SpringBootApplication
@EnableWebSocketMessageBroker
public class SampleWebsocketApplication extends AbstractWebSocketMessageBrokerConfigurer {
public class SampleWebsocketApplication implements WebSocketMessageBrokerConfigurer {
public static void main(String[] args) {
SpringApplication.run(SampleWebsocketApplication.class, args);

View File

@@ -60,6 +60,18 @@ https://www.w3.org/2001/XMLSchema-instance ">
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream</artifactId>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream</artifactId>
<type>test-jar</type>
<scope>test</scope>
<classifier>test-binder</classifier>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>

View File

@@ -65,6 +65,7 @@ public class TraceContextPropagationChannelInterceptorTests {
public void testSpanPropagation() {
Span span = this.tracing.tracer().nextSpan().name("http:testSendMessage").start();
String expectedSpanId = SpanUtil.idToHex(span.context().spanId());
try (Tracer.SpanInScope ws = this.tracing.tracer().withSpanInScope(span)) {
this.channel.send(MessageBuilder.withPayload("hi").build());
}
@@ -72,6 +73,10 @@ public class TraceContextPropagationChannelInterceptorTests {
span.finish();
}
assertThatNewSpanIdWasSetOnMessage(expectedSpanId);
}
private void assertThatNewSpanIdWasSetOnMessage(String expectedSpanId) {
Message<?> message = this.channel.receive(0);
assertThat(message).as("message was null").isNotNull();
@@ -88,7 +93,6 @@ public class TraceContextPropagationChannelInterceptorTests {
String.class);
assertThat(parentId).as("parentId was not equal to parent's id")
.isEqualTo(this.reporter.getSpans().get(0).id());
}
@Configuration

View File

@@ -0,0 +1,120 @@
/*
* Copyright 2013-2019 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.cloud.sleuth.instrument.messaging;
import brave.Span;
import brave.Tracer;
import brave.Tracing;
import brave.sampler.Sampler;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.sleuth.instrument.util.SpanUtil;
import org.springframework.cloud.sleuth.util.ArrayListSpanReporter;
import org.springframework.cloud.stream.binder.test.OutputDestination;
import org.springframework.cloud.stream.binder.test.TestChannelBinderConfiguration;
import org.springframework.cloud.stream.function.StreamBridge;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.Message;
import org.springframework.test.annotation.DirtiesContext;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Spencer Gibb
*/
@SpringBootTest(classes = TraceStreamChannelInterceptorTests.App.class,
properties = "spring.cloud.stream.source=testSupplier")
@DirtiesContext
public class TraceStreamChannelInterceptorTests {
@Autowired
private OutputDestination channel;
@Autowired
private Tracing tracing;
@Autowired
private StreamBridge streamBridge;
@Autowired
private ArrayListSpanReporter reporter;
@AfterEach
public void close() {
this.reporter.clear();
}
@Test
public void testSpanPropagationViaBridge() {
Span span = this.tracing.tracer().nextSpan().name("http:testSendMessage").start();
String expectedSpanId = SpanUtil.idToHex(span.context().spanId());
try (Tracer.SpanInScope ws = this.tracing.tracer().withSpanInScope(span)) {
this.streamBridge.send("testSupplier-out-0", "hi");
}
finally {
span.finish();
}
assertThatNewSpanIdWasSetOnMessage(expectedSpanId);
}
private void assertThatNewSpanIdWasSetOnMessage(String expectedSpanId) {
Message<?> message = this.channel.receive(0);
assertThat(message).as("message was null").isNotNull();
String spanId = message.getHeaders().get(TraceMessageHeaders.SPAN_ID_NAME,
String.class);
assertThat(spanId).as("spanId was equal to parent's id")
.isNotEqualTo(expectedSpanId);
String traceId = message.getHeaders().get(TraceMessageHeaders.TRACE_ID_NAME,
String.class);
assertThat(traceId).as("traceId was null").isNotNull();
String parentId = message.getHeaders().get(TraceMessageHeaders.PARENT_ID_NAME,
String.class);
// [0] - producer
// [1] - http:testsendmessage
assertThat(parentId).as("parentId was not equal to parent's id")
.isEqualTo(this.reporter.getSpans().get(1).id());
}
@Configuration
@EnableAutoConfiguration
@ImportAutoConfiguration(TestChannelBinderConfiguration.class)
static class App {
@Bean
Sampler testSampler() {
return Sampler.ALWAYS_SAMPLE;
}
@Bean
ArrayListSpanReporter reporter() {
return new ArrayListSpanReporter();
}
}
}

View File

@@ -19,7 +19,6 @@ package org.springframework.cloud.sleuth.instrument.messaging.issues.issue_943;
import brave.sampler.Sampler;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
@@ -30,8 +29,7 @@ import org.springframework.integration.config.EnableIntegration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
@EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class,
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class,
HibernateJpaAutoConfiguration.class })
@ImportResource("classpath:beans/applicationContext.xml")
@EnableIntegration

View File

@@ -24,6 +24,7 @@ import java.util.stream.Collectors;
import brave.Tracing;
import brave.http.HttpRequest;
import brave.http.HttpRequestParser;
import brave.sampler.Sampler;
import brave.sampler.SamplerFunction;
import org.assertj.core.api.BDDAssertions;
@@ -66,9 +67,6 @@ public class TraceFilterWebIntegrationTests {
@Rule
public OutputCaptureRule capture = new OutputCaptureRule();
@Autowired
Tracing tracer;
@Autowired
ArrayListSpanReporter accumulator;
@@ -85,6 +83,16 @@ public class TraceFilterWebIntegrationTests {
this.accumulator.clear();
}
@Test
public void should_tag_url() {
new RestTemplate().getForObject("http://localhost:" + port() + "/good",
String.class);
then(Tracing.current().tracer().currentSpan()).isNull();
then(this.accumulator.getSpans()).hasSize(1);
then(this.accumulator.getSpans().get(0).tags()).containsKey("http.url");
}
@Test
public void should_not_create_a_span_for_error_controller() {
try {
@@ -146,7 +154,12 @@ public class TraceFilterWebIntegrationTests {
public static class Config {
@Bean
ExceptionThrowingController controller() {
GoodController goodController() {
return new GoodController();
}
@Bean
ExceptionThrowingController badController() {
return new ExceptionThrowingController();
}
@@ -160,6 +173,19 @@ public class TraceFilterWebIntegrationTests {
return Sampler.ALWAYS_SAMPLE;
}
// tag::custom_parser[]
@Bean(name = { HttpClientRequestParser.NAME, HttpServerRequestParser.NAME })
HttpRequestParser sleuthHttpServerRequestParser() {
return (req, context, span) -> {
HttpRequestParser.DEFAULT.parse(req, context, span);
String url = req.url();
if (url != null) {
span.tag("http.url", url);
}
};
}
// end::custom_parser[]
// tag::custom_server_sampler[]
@Bean(name = HttpServerSampler.NAME)
SamplerFunction<HttpRequest> myHttpSampler(SkipPatternProvider provider) {
@@ -188,6 +214,16 @@ public class TraceFilterWebIntegrationTests {
}
@RestController
public static class GoodController {
@RequestMapping("/good")
public String beGood() {
return "good";
}
}
@RestController
public static class ExceptionThrowingController {

View File

@@ -28,6 +28,7 @@ import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.reactive.ClientHttpConnector;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.ClientResponse;
@@ -118,9 +119,13 @@ public class WebClientBraveTests
}
@Bean
WebClient.Builder webClientBuilder(HttpClient httpClient) {
return WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(httpClient));
ClientHttpConnector clientHttpConnector(HttpClient httpClient) {
return new ReactorClientHttpConnector(httpClient);
}
@Bean
WebClient.Builder webClientBuilder(ClientHttpConnector clientHttpConnector) {
return WebClient.builder().clientConnector(clientHttpConnector);
}
}