Correlated WebClient log messages
Issue: SPR-16966
This commit is contained in:
@@ -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, "");
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user