Propagate context in reactive HTTP server and client

Prior to this commit, the ServerHttpObservationFilter would not add
the current observation as a key in the Reactor context, preventing
from being used or propagated during the HTTP exchange handling.

Also, the client instrumentation in `DefaultWebClient` would start
the observation once the request is fully formed and immutable,
preventing the context from being propagated through HTTP request
headers.

This commit fixes both uses cases now by:

* adding the current observation as a key in the reactor context
  on the server side
* using the `ClientRequest.Builder` as a Carrier on the client side

Closes gh-29388
This commit is contained in:
Marcin Grzejszczak
2022-10-26 20:22:47 +02:00
committed by Brian Clozel
parent 396336fdcc
commit c03ccb2e6c
7 changed files with 111 additions and 25 deletions

View File

@@ -27,21 +27,24 @@ import org.springframework.lang.Nullable;
* @author Brian Clozel
* @since 6.0
*/
public class ClientRequestObservationContext extends RequestReplySenderContext<ClientRequest, ClientResponse> {
public class ClientRequestObservationContext extends RequestReplySenderContext<ClientRequest.Builder, ClientResponse> {
@Nullable
private String uriTemplate;
private boolean aborted;
@Nullable
private ClientRequest builtRequest;
public ClientRequestObservationContext() {
super(ClientRequestObservationContext::setRequestHeader);
}
private static void setRequestHeader(@Nullable ClientRequest request, String name, String value) {
private static void setRequestHeader(@Nullable ClientRequest.Builder request, String name, String value) {
if (request != null) {
request.headers().set(name, value);
request.header(name, value);
}
}
@@ -75,4 +78,18 @@ public class ClientRequestObservationContext extends RequestReplySenderContext<C
void setAborted(boolean aborted) {
this.aborted = aborted;
}
/**
* Return the built request.
*/
public ClientRequest getBuiltRequest() {
return this.builtRequest;
}
/**
* Set the built request.
*/
public void setBuiltRequest(ClientRequest builtRequest) {
this.builtRequest = builtRequest;
}
}

View File

@@ -79,7 +79,7 @@ public class DefaultClientRequestObservationConvention implements ClientRequestO
@Override
public String getContextualName(ClientRequestObservationContext context) {
return "http " + context.getCarrier().method().name().toLowerCase();
return "http " + context.getBuiltRequest().method().name().toLowerCase();
}
@Override
@@ -95,8 +95,8 @@ public class DefaultClientRequestObservationConvention implements ClientRequestO
}
protected KeyValue method(ClientRequestObservationContext context) {
if (context.getCarrier() != null) {
return KeyValue.of(ClientHttpObservationDocumentation.LowCardinalityKeyNames.METHOD, context.getCarrier().method().name());
if (context.getBuiltRequest() != null) {
return KeyValue.of(ClientHttpObservationDocumentation.LowCardinalityKeyNames.METHOD, context.getBuiltRequest().method().name());
}
else {
return METHOD_NONE;
@@ -143,15 +143,15 @@ public class DefaultClientRequestObservationConvention implements ClientRequestO
}
protected KeyValue httpUrl(ClientRequestObservationContext context) {
if (context.getCarrier() != null) {
return KeyValue.of(ClientHttpObservationDocumentation.HighCardinalityKeyNames.HTTP_URL, context.getCarrier().url().toASCIIString());
if (context.getBuiltRequest() != null) {
return KeyValue.of(ClientHttpObservationDocumentation.HighCardinalityKeyNames.HTTP_URL, context.getBuiltRequest().url().toASCIIString());
}
return HTTP_URL_NONE;
}
protected KeyValue clientName(ClientRequestObservationContext context) {
if (context.getCarrier() != null && context.getCarrier().url().getHost() != null) {
return KeyValue.of(ClientHttpObservationDocumentation.HighCardinalityKeyNames.CLIENT_NAME, context.getCarrier().url().getHost());
if (context.getBuiltRequest() != null && context.getBuiltRequest().url().getHost() != null) {
return KeyValue.of(ClientHttpObservationDocumentation.HighCardinalityKeyNames.CLIENT_NAME, context.getBuiltRequest().url().getHost());
}
return CLIENT_NAME_NONE;
}

View File

@@ -75,6 +75,11 @@ class DefaultWebClient implements WebClient {
private static final DefaultClientRequestObservationConvention DEFAULT_OBSERVATION_CONVENTION = new DefaultClientRequestObservationConvention();
/**
* Aligned with ObservationThreadLocalAccessor#KEY from micrometer-core.
*/
private static final String MICROMETER_OBSERVATION = "micrometer.observation";
private final ExchangeFunction exchangeFunction;
private final UriBuilderFactory uriBuilderFactory;
@@ -450,14 +455,19 @@ class DefaultWebClient implements WebClient {
@SuppressWarnings("deprecation")
public Mono<ClientResponse> exchange() {
ClientRequestObservationContext observationContext = new ClientRequestObservationContext();
ClientRequest request = (this.inserter != null ?
initRequestBuilder().body(this.inserter).build() :
initRequestBuilder().build());
return Mono.defer(() -> {
ClientRequest.Builder requestBuilder = this.inserter != null ?
initRequestBuilder().body(this.inserter) :
initRequestBuilder();
return Mono.deferContextual(contextView -> {
Observation observation = ClientHttpObservationDocumentation.HTTP_REQUEST.observation(observationConvention,
DEFAULT_OBSERVATION_CONVENTION, () -> observationContext, observationRegistry).start();
observationContext.setCarrier(request);
DEFAULT_OBSERVATION_CONVENTION, () -> observationContext, observationRegistry);
observationContext.setCarrier(requestBuilder);
observation
.parentObservation(contextView.getOrDefault(MICROMETER_OBSERVATION, null))
.start();
ClientRequest request = requestBuilder.build();
observationContext.setUriTemplate((String) request.attribute(URI_TEMPLATE_ATTRIBUTE).orElse(null));
observationContext.setBuiltRequest(request);
Mono<ClientResponse> responseMono = exchangeFunction.exchange(request)
.checkpoint("Request to " + this.httpMethod.name() + " " + this.uri + " [DefaultWebClient]")
.switchIfEmpty(NO_HTTP_CLIENT_RESPONSE_ERROR);