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:
committed by
Brian Clozel
parent
396336fdcc
commit
c03ccb2e6c
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user