Correlated WebClient log messages

Issue: SPR-16966
master
Rossen Stoyanchev 6 years ago
parent 82310660fd
commit 5cdc26770e
  1. 2
      spring-web/src/main/java/org/springframework/web/server/ServerWebExchange.java
  2. 21
      spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ClientRequest.java
  3. 15
      spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientRequestBuilder.java
  4. 7
      spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientResponse.java
  5. 11
      spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientResponseBuilder.java
  6. 29
      spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ExchangeFunctions.java
  7. 2
      spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultClientResponseTests.java

@ -222,7 +222,7 @@ public interface ServerWebExchange {
* Return a log message prefix to use to correlate messages for this exchange.
* The prefix is based on the value of the attribute {@link #LOG_ID_ATTRIBUTE}
* along with some extra formatting so that the prefix can be conveniently
* prepended with no further formatting no separatorns required.
* prepended with no further formatting no separators required.
* @return the log message prefix or an empty String if the
* {@link #LOG_ID_ATTRIBUTE} is not set.
* @since 5.1

@ -45,6 +45,16 @@ import org.springframework.web.reactive.function.BodyInserter;
*/
public interface ClientRequest {
/**
* Name of {@link #attributes() attribute} whose value can be used to
* correlate log messages for this request. Use {@link #logPrefix()} to
* obtain a consistently formatted prefix based on this attribute.
* @since 5.1
* @see #logPrefix()
*/
String LOG_ID_ATTRIBUTE = ClientRequest.class.getName() + ".LOG_ID";
/**
* Return the HTTP method.
*/
@ -90,6 +100,17 @@ public interface ClientRequest {
*/
Map<String, Object> attributes();
/**
* Return a log message prefix to use to correlate messages for this request.
* The prefix is based on the value of the attribute {@link #LOG_ID_ATTRIBUTE}
* along with some extra formatting so that the prefix can be conveniently
* prepended with no further formatting no separators required.
* @return the log message prefix or an empty String if the
* {@link #LOG_ID_ATTRIBUTE} is not set.
* @since 5.1
*/
String logPrefix();
/**
* Writes this request to the given {@link ClientHttpRequest}.
*

@ -39,6 +39,7 @@ import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.ObjectUtils;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.BodyInserters;
@ -181,6 +182,9 @@ final class DefaultClientRequestBuilder implements ClientRequest.Builder {
private final Map<String, Object> attributes;
private final String logPrefix;
public BodyInserterRequest(HttpMethod method, URI url, HttpHeaders headers,
MultiValueMap<String, String> cookies, BodyInserter<?, ? super ClientHttpRequest> body,
Map<String, Object> attributes) {
@ -191,8 +195,12 @@ final class DefaultClientRequestBuilder implements ClientRequest.Builder {
this.cookies = CollectionUtils.unmodifiableMultiValueMap(cookies);
this.body = body;
this.attributes = Collections.unmodifiableMap(attributes);
Object id = attributes.computeIfAbsent(LOG_ID_ATTRIBUTE, name -> ObjectUtils.getIdentityHexString(this));
this.logPrefix = "[" + id + "] ";
}
@Override
public HttpMethod method() {
return this.method;
@ -223,6 +231,11 @@ final class DefaultClientRequestBuilder implements ClientRequest.Builder {
return this.attributes;
}
@Override
public String logPrefix() {
return this.logPrefix;
}
@Override
public Mono<Void> writeTo(ClientHttpRequest request, ExchangeStrategies strategies) {
HttpHeaders requestHeaders = request.getHeaders();
@ -252,7 +265,7 @@ final class DefaultClientRequestBuilder implements ClientRequest.Builder {
}
@Override
public Map<String, Object> hints() {
return Hints.none();
return Hints.from(Hints.LOG_PREFIX_HINT, logPrefix());
}
});
}

@ -55,11 +55,14 @@ class DefaultClientResponse implements ClientResponse {
private final ExchangeStrategies strategies;
private final String logPrefix;
public DefaultClientResponse(ClientHttpResponse response, ExchangeStrategies strategies) {
public DefaultClientResponse(ClientHttpResponse response, ExchangeStrategies strategies, String logPrefix) {
this.response = response;
this.strategies = strategies;
this.headers = new DefaultHeaders();
this.logPrefix = logPrefix;
}
@ -96,7 +99,7 @@ class DefaultClientResponse implements ClientResponse {
}
@Override
public Map<String, Object> hints() {
return Hints.none();
return Hints.from(Hints.LOG_PREFIX_HINT, logPrefix);
}
});
}

@ -129,9 +129,14 @@ final class DefaultClientResponseBuilder implements ClientResponse.Builder {
@Override
public ClientResponse build() {
ClientHttpResponse clientHttpResponse = new BuiltClientHttpResponse(
this.statusCode, this.headers, this.cookies, this.body);
return new DefaultClientResponse(clientHttpResponse, this.strategies);
ClientHttpResponse httpResponse =
new BuiltClientHttpResponse(this.statusCode, this.headers, this.cookies, this.body);
// When building ClientResponse manually, the ClientRequest.logPrefix() has to be passed,
// e.g. via ClientResponse.Builder, but this (builder) is not used currently.
return new DefaultClientResponse(httpResponse, this.strategies, "");
}

@ -91,18 +91,19 @@ public abstract class ExchangeFunctions {
@Override
public Mono<ClientResponse> exchange(ClientRequest request) {
Assert.notNull(request, "ClientRequest must not be null");
HttpMethod httpMethod = request.method();
URI url = request.url();
public Mono<ClientResponse> exchange(ClientRequest clientRequest) {
Assert.notNull(clientRequest, "ClientRequest must not be null");
HttpMethod httpMethod = clientRequest.method();
URI url = clientRequest.url();
String logPrefix = clientRequest.logPrefix();
return this.connector
.connect(httpMethod, url, httpRequest -> request.writeTo(httpRequest, this.strategies))
.doOnRequest(n -> logRequest(request))
.doOnCancel(() -> logger.debug("Cancel signal (to close connection)"))
.map(response -> {
logResponse(response);
return new DefaultClientResponse(response, this.strategies);
.connect(httpMethod, url, httpRequest -> clientRequest.writeTo(httpRequest, this.strategies))
.doOnRequest(n -> logRequest(clientRequest))
.doOnCancel(() -> logger.debug(logPrefix + "Cancel signal (to close connection)"))
.map(httpResponse -> {
logResponse(httpResponse, logPrefix);
return new DefaultClientResponse(httpResponse, this.strategies, logPrefix);
});
}
@ -113,21 +114,21 @@ public abstract class ExchangeFunctions {
int index = formatted.indexOf("?");
formatted = (index != -1 ? formatted.substring(0, index) : formatted);
}
logger.debug("HTTP " + request.method() + " " + formatted);
logger.debug(request.logPrefix() + "HTTP " + request.method() + " " + formatted);
}
}
private void logResponse(ClientHttpResponse response) {
private void logResponse(ClientHttpResponse response, String logPrefix) {
if (logger.isDebugEnabled()) {
int code = response.getRawStatusCode();
HttpStatus status = HttpStatus.resolve(code);
String message = "Response " + (status != null ? status : code);
if (logger.isTraceEnabled()) {
String headers = this.disableLoggingRequestDetails ? "" : ", headers=" + response.getHeaders();
logger.trace(message + headers);
logger.trace(logPrefix + message + headers);
}
else {
logger.debug(message);
logger.debug(logPrefix + message);
}
}
}

@ -68,7 +68,7 @@ public class DefaultClientResponseTests {
public void createMocks() {
mockResponse = mock(ClientHttpResponse.class);
mockExchangeStrategies = mock(ExchangeStrategies.class);
defaultClientResponse = new DefaultClientResponse(mockResponse, mockExchangeStrategies);
defaultClientResponse = new DefaultClientResponse(mockResponse, mockExchangeStrategies, "");
}

Loading…
Cancel
Save