diff --git a/spring-web/src/main/java/org/springframework/web/server/ServerWebExchange.java b/spring-web/src/main/java/org/springframework/web/server/ServerWebExchange.java index 045ce06e76..78031b4c9d 100644 --- a/spring-web/src/main/java/org/springframework/web/server/ServerWebExchange.java +++ b/spring-web/src/main/java/org/springframework/web/server/ServerWebExchange.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 diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ClientRequest.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ClientRequest.java index 50301b7710..04e5edb1de 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ClientRequest.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ClientRequest.java @@ -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 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}. * diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientRequestBuilder.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientRequestBuilder.java index 015c56a6d5..25a29e6a85 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientRequestBuilder.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientRequestBuilder.java @@ -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 attributes; + private final String logPrefix; + + public BodyInserterRequest(HttpMethod method, URI url, HttpHeaders headers, MultiValueMap cookies, BodyInserter body, Map 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 writeTo(ClientHttpRequest request, ExchangeStrategies strategies) { HttpHeaders requestHeaders = request.getHeaders(); @@ -252,7 +265,7 @@ final class DefaultClientRequestBuilder implements ClientRequest.Builder { } @Override public Map hints() { - return Hints.none(); + return Hints.from(Hints.LOG_PREFIX_HINT, logPrefix()); } }); } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientResponse.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientResponse.java index ea74a3835f..b2515eacdb 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientResponse.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientResponse.java @@ -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 hints() { - return Hints.none(); + return Hints.from(Hints.LOG_PREFIX_HINT, logPrefix); } }); } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientResponseBuilder.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientResponseBuilder.java index 1320c20d32..0e7ead9a7e 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientResponseBuilder.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientResponseBuilder.java @@ -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, ""); } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ExchangeFunctions.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ExchangeFunctions.java index 9e3d87e52a..98ed13b6fb 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ExchangeFunctions.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ExchangeFunctions.java @@ -91,18 +91,19 @@ public abstract class ExchangeFunctions { @Override - public Mono exchange(ClientRequest request) { - Assert.notNull(request, "ClientRequest must not be null"); - HttpMethod httpMethod = request.method(); - URI url = request.url(); + public Mono 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); } } } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultClientResponseTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultClientResponseTests.java index e13b64fcab..cd06a3c09b 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultClientResponseTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultClientResponseTests.java @@ -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, ""); }