Skip to content

Commit 5cdc267

Browse files
committed
Correlated WebClient log messages
Issue: SPR-16966
1 parent 8231066 commit 5cdc267

File tree

7 files changed

+65
-22
lines changed

7 files changed

+65
-22
lines changed

spring-web/src/main/java/org/springframework/web/server/ServerWebExchange.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ default <T> T getAttributeOrDefault(String name, T defaultValue) {
222222
* Return a log message prefix to use to correlate messages for this exchange.
223223
* The prefix is based on the value of the attribute {@link #LOG_ID_ATTRIBUTE}
224224
* along with some extra formatting so that the prefix can be conveniently
225-
* prepended with no further formatting no separatorns required.
225+
* prepended with no further formatting no separators required.
226226
* @return the log message prefix or an empty String if the
227227
* {@link #LOG_ID_ATTRIBUTE} is not set.
228228
* @since 5.1

spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ClientRequest.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,16 @@
4545
*/
4646
public interface ClientRequest {
4747

48+
/**
49+
* Name of {@link #attributes() attribute} whose value can be used to
50+
* correlate log messages for this request. Use {@link #logPrefix()} to
51+
* obtain a consistently formatted prefix based on this attribute.
52+
* @since 5.1
53+
* @see #logPrefix()
54+
*/
55+
String LOG_ID_ATTRIBUTE = ClientRequest.class.getName() + ".LOG_ID";
56+
57+
4858
/**
4959
* Return the HTTP method.
5060
*/
@@ -90,6 +100,17 @@ default Optional<Object> attribute(String name) {
90100
*/
91101
Map<String, Object> attributes();
92102

103+
/**
104+
* Return a log message prefix to use to correlate messages for this request.
105+
* The prefix is based on the value of the attribute {@link #LOG_ID_ATTRIBUTE}
106+
* along with some extra formatting so that the prefix can be conveniently
107+
* prepended with no further formatting no separators required.
108+
* @return the log message prefix or an empty String if the
109+
* {@link #LOG_ID_ATTRIBUTE} is not set.
110+
* @since 5.1
111+
*/
112+
String logPrefix();
113+
93114
/**
94115
* Writes this request to the given {@link ClientHttpRequest}.
95116
*

spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientRequestBuilder.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import org.springframework.util.CollectionUtils;
4040
import org.springframework.util.LinkedMultiValueMap;
4141
import org.springframework.util.MultiValueMap;
42+
import org.springframework.util.ObjectUtils;
4243
import org.springframework.web.reactive.function.BodyInserter;
4344
import org.springframework.web.reactive.function.BodyInserters;
4445

@@ -181,6 +182,9 @@ private static class BodyInserterRequest implements ClientRequest {
181182

182183
private final Map<String, Object> attributes;
183184

185+
private final String logPrefix;
186+
187+
184188
public BodyInserterRequest(HttpMethod method, URI url, HttpHeaders headers,
185189
MultiValueMap<String, String> cookies, BodyInserter<?, ? super ClientHttpRequest> body,
186190
Map<String, Object> attributes) {
@@ -191,8 +195,12 @@ public BodyInserterRequest(HttpMethod method, URI url, HttpHeaders headers,
191195
this.cookies = CollectionUtils.unmodifiableMultiValueMap(cookies);
192196
this.body = body;
193197
this.attributes = Collections.unmodifiableMap(attributes);
198+
199+
Object id = attributes.computeIfAbsent(LOG_ID_ATTRIBUTE, name -> ObjectUtils.getIdentityHexString(this));
200+
this.logPrefix = "[" + id + "] ";
194201
}
195202

203+
196204
@Override
197205
public HttpMethod method() {
198206
return this.method;
@@ -223,6 +231,11 @@ public Map<String, Object> attributes() {
223231
return this.attributes;
224232
}
225233

234+
@Override
235+
public String logPrefix() {
236+
return this.logPrefix;
237+
}
238+
226239
@Override
227240
public Mono<Void> writeTo(ClientHttpRequest request, ExchangeStrategies strategies) {
228241
HttpHeaders requestHeaders = request.getHeaders();
@@ -252,7 +265,7 @@ public Optional<ServerHttpRequest> serverRequest() {
252265
}
253266
@Override
254267
public Map<String, Object> hints() {
255-
return Hints.none();
268+
return Hints.from(Hints.LOG_PREFIX_HINT, logPrefix());
256269
}
257270
});
258271
}

spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientResponse.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,14 @@ class DefaultClientResponse implements ClientResponse {
5555

5656
private final ExchangeStrategies strategies;
5757

58+
private final String logPrefix;
5859

59-
public DefaultClientResponse(ClientHttpResponse response, ExchangeStrategies strategies) {
60+
61+
public DefaultClientResponse(ClientHttpResponse response, ExchangeStrategies strategies, String logPrefix) {
6062
this.response = response;
6163
this.strategies = strategies;
6264
this.headers = new DefaultHeaders();
65+
this.logPrefix = logPrefix;
6366
}
6467

6568

@@ -96,7 +99,7 @@ public Optional<ServerHttpResponse> serverResponse() {
9699
}
97100
@Override
98101
public Map<String, Object> hints() {
99-
return Hints.none();
102+
return Hints.from(Hints.LOG_PREFIX_HINT, logPrefix);
100103
}
101104
});
102105
}

spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientResponseBuilder.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -129,9 +129,14 @@ private void releaseBody() {
129129

130130
@Override
131131
public ClientResponse build() {
132-
ClientHttpResponse clientHttpResponse = new BuiltClientHttpResponse(
133-
this.statusCode, this.headers, this.cookies, this.body);
134-
return new DefaultClientResponse(clientHttpResponse, this.strategies);
132+
133+
ClientHttpResponse httpResponse =
134+
new BuiltClientHttpResponse(this.statusCode, this.headers, this.cookies, this.body);
135+
136+
// When building ClientResponse manually, the ClientRequest.logPrefix() has to be passed,
137+
// e.g. via ClientResponse.Builder, but this (builder) is not used currently.
138+
139+
return new DefaultClientResponse(httpResponse, this.strategies, "");
135140
}
136141

137142

spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ExchangeFunctions.java

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -91,18 +91,19 @@ public DefaultExchangeFunction(ClientHttpConnector connector, ExchangeStrategies
9191

9292

9393
@Override
94-
public Mono<ClientResponse> exchange(ClientRequest request) {
95-
Assert.notNull(request, "ClientRequest must not be null");
96-
HttpMethod httpMethod = request.method();
97-
URI url = request.url();
94+
public Mono<ClientResponse> exchange(ClientRequest clientRequest) {
95+
Assert.notNull(clientRequest, "ClientRequest must not be null");
96+
HttpMethod httpMethod = clientRequest.method();
97+
URI url = clientRequest.url();
98+
String logPrefix = clientRequest.logPrefix();
9899

99100
return this.connector
100-
.connect(httpMethod, url, httpRequest -> request.writeTo(httpRequest, this.strategies))
101-
.doOnRequest(n -> logRequest(request))
102-
.doOnCancel(() -> logger.debug("Cancel signal (to close connection)"))
103-
.map(response -> {
104-
logResponse(response);
105-
return new DefaultClientResponse(response, this.strategies);
101+
.connect(httpMethod, url, httpRequest -> clientRequest.writeTo(httpRequest, this.strategies))
102+
.doOnRequest(n -> logRequest(clientRequest))
103+
.doOnCancel(() -> logger.debug(logPrefix + "Cancel signal (to close connection)"))
104+
.map(httpResponse -> {
105+
logResponse(httpResponse, logPrefix);
106+
return new DefaultClientResponse(httpResponse, this.strategies, logPrefix);
106107
});
107108
}
108109

@@ -113,21 +114,21 @@ private void logRequest(ClientRequest request) {
113114
int index = formatted.indexOf("?");
114115
formatted = (index != -1 ? formatted.substring(0, index) : formatted);
115116
}
116-
logger.debug("HTTP " + request.method() + " " + formatted);
117+
logger.debug(request.logPrefix() + "HTTP " + request.method() + " " + formatted);
117118
}
118119
}
119120

120-
private void logResponse(ClientHttpResponse response) {
121+
private void logResponse(ClientHttpResponse response, String logPrefix) {
121122
if (logger.isDebugEnabled()) {
122123
int code = response.getRawStatusCode();
123124
HttpStatus status = HttpStatus.resolve(code);
124125
String message = "Response " + (status != null ? status : code);
125126
if (logger.isTraceEnabled()) {
126127
String headers = this.disableLoggingRequestDetails ? "" : ", headers=" + response.getHeaders();
127-
logger.trace(message + headers);
128+
logger.trace(logPrefix + message + headers);
128129
}
129130
else {
130-
logger.debug(message);
131+
logger.debug(logPrefix + message);
131132
}
132133
}
133134
}

spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultClientResponseTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ public class DefaultClientResponseTests {
6868
public void createMocks() {
6969
mockResponse = mock(ClientHttpResponse.class);
7070
mockExchangeStrategies = mock(ExchangeStrategies.class);
71-
defaultClientResponse = new DefaultClientResponse(mockResponse, mockExchangeStrategies);
71+
defaultClientResponse = new DefaultClientResponse(mockResponse, mockExchangeStrategies, "");
7272
}
7373

7474

0 commit comments

Comments
 (0)