Skip to content

Commit 9ab8bd0

Browse files
committed
Improved logging in functional web framework
This commit improves predicate and route logging in the functional web framework.
1 parent 63f2611 commit 9ab8bd0

File tree

7 files changed

+181
-27
lines changed

7 files changed

+181
-27
lines changed

spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultServerRequest.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,11 @@ public <T> Optional<T> attribute(String name) {
127127
return this.exchange.getAttribute(name);
128128
}
129129

130+
@Override
131+
public Map<String, Object> attributes() {
132+
return this.exchange.getAttributes();
133+
}
134+
130135
@Override
131136
public List<String> queryParams(String name) {
132137
List<String> queryParams = request().getQueryParams().get(name);
@@ -152,6 +157,10 @@ ServerWebExchange exchange() {
152157
return this.exchange;
153158
}
154159

160+
@Override
161+
public String toString() {
162+
return String.format("%s %s", method(), path());
163+
}
155164

156165
private class DefaultHeaders implements Headers {
157166

@@ -205,6 +214,11 @@ public List<String> header(String headerName) {
205214
public HttpHeaders asHttpHeaders() {
206215
return HttpHeaders.readOnlyHttpHeaders(delegate());
207216
}
217+
218+
@Override
219+
public String toString() {
220+
return delegate().toString();
221+
}
208222
}
209223

210224
}

spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RequestPredicate.java

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,13 @@ public boolean test(ServerRequest t) {
5757
}
5858

5959
@Override
60-
public ServerRequest subRequest(ServerRequest request) {
61-
return other.subRequest(RequestPredicate.this.subRequest(request));
60+
public ServerRequest nestRequest(ServerRequest request) {
61+
return other.nestRequest(RequestPredicate.this.nestRequest(request));
62+
}
63+
64+
@Override
65+
public String toString() {
66+
return String.format("(%s && %s)", RequestPredicate.this, other);
6267
}
6368
};
6469
}
@@ -76,16 +81,47 @@ default RequestPredicate negate() {
7681
* Returns a composed request predicate that tests against both this predicate OR the {@code other} predicate.
7782
* When evaluating the composed predicate, if this predicate is {@code true}, then the {@code other} predicate
7883
* is not evaluated.
79-
8084
* @param other a predicate that will be logically-ORed with this predicate
8185
* @return a predicate composed of this predicate OR the {@code other} predicate
8286
*/
8387
default RequestPredicate or(RequestPredicate other) {
8488
Assert.notNull(other, "'other' must not be null");
85-
return (t) -> test(t) || other.test(t);
89+
return new RequestPredicate() {
90+
@Override
91+
public boolean test(ServerRequest t) {
92+
return RequestPredicate.this.test(t) || other.test(t);
93+
}
94+
95+
@Override
96+
public ServerRequest nestRequest(ServerRequest request) {
97+
if (RequestPredicate.this.test(request)) {
98+
return RequestPredicate.this.nestRequest(request);
99+
}
100+
else if (other.test(request)) {
101+
return other.nestRequest(request);
102+
}
103+
else {
104+
throw new IllegalStateException("Neither " + RequestPredicate.this.toString() +
105+
" nor " + other + "matches");
106+
}
107+
}
108+
109+
@Override
110+
public String toString() {
111+
return String.format("(%s || %s)", RequestPredicate.this, other);
112+
}
113+
};
86114
}
87115

88-
default ServerRequest subRequest(ServerRequest request) {
116+
/**
117+
* Transforms the given request into a request used for a nested route. For instance, a
118+
* path-based predicate can return a {@code ServerRequest} with a nested path.
119+
* <p>Default implementation returns the given path.
120+
* @param request the request to be nested
121+
* @return the nested request
122+
* @see RouterFunctions#nest(RequestPredicate, RouterFunction)
123+
*/
124+
default ServerRequest nestRequest(ServerRequest request) {
89125
return request;
90126
}
91127
}

spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RequestPredicates.java

Lines changed: 87 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,16 @@
2626
import java.util.function.Function;
2727
import java.util.function.Predicate;
2828

29+
import org.apache.commons.logging.Log;
30+
import org.apache.commons.logging.LogFactory;
2931
import reactor.core.publisher.Flux;
3032
import reactor.core.publisher.Mono;
3133

3234
import org.springframework.http.HttpMethod;
3335
import org.springframework.http.MediaType;
3436
import org.springframework.http.server.reactive.ServerHttpRequest;
3537
import org.springframework.util.Assert;
38+
import org.springframework.util.StringUtils;
3639
import org.springframework.web.reactive.function.BodyExtractor;
3740
import org.springframework.web.server.WebSession;
3841
import org.springframework.web.util.UriUtils;
@@ -48,6 +51,8 @@
4851
*/
4952
public abstract class RequestPredicates {
5053

54+
private static final Log logger = LogFactory.getLog(RequestPredicates.class);
55+
5156
private static final PathPatternParser DEFAULT_PATTERN_PARSER = new PathPatternParser();
5257

5358
/**
@@ -119,11 +124,21 @@ public static RequestPredicate headers(Predicate<ServerRequest.Headers> headersP
119124
public static RequestPredicate contentType(MediaType... mediaTypes) {
120125
Assert.notEmpty(mediaTypes, "'mediaTypes' must not be empty");
121126
Set<MediaType> mediaTypeSet = new HashSet<>(Arrays.asList(mediaTypes));
122-
return headers(headers -> {
123-
MediaType contentType = headers.contentType().orElse(MediaType.APPLICATION_OCTET_STREAM);
124-
return mediaTypeSet.stream()
125-
.anyMatch(mediaType -> mediaType.includes(contentType));
127+
return headers(new Predicate<ServerRequest.Headers>() {
128+
@Override
129+
public boolean test(ServerRequest.Headers headers) {
130+
MediaType contentType =
131+
headers.contentType().orElse(MediaType.APPLICATION_OCTET_STREAM);
132+
boolean match = mediaTypeSet.stream()
133+
.anyMatch(mediaType -> mediaType.includes(contentType));
134+
traceMatch("Content-Type", mediaTypeSet, contentType, match);
135+
return match;
136+
}
126137

138+
@Override
139+
public String toString() {
140+
return String.format("Content-Type: %s", mediaTypeSet);
141+
}
127142
});
128143
}
129144

@@ -138,12 +153,23 @@ public static RequestPredicate contentType(MediaType... mediaTypes) {
138153
public static RequestPredicate accept(MediaType... mediaTypes) {
139154
Assert.notEmpty(mediaTypes, "'mediaTypes' must not be empty");
140155
Set<MediaType> mediaTypeSet = new HashSet<>(Arrays.asList(mediaTypes));
141-
return headers(headers -> {
142-
List<MediaType> acceptedMediaTypes = headers.accept();
143-
MediaType.sortBySpecificityAndQuality(acceptedMediaTypes);
144-
return acceptedMediaTypes.stream()
145-
.anyMatch(acceptedMediaType -> mediaTypeSet.stream()
146-
.anyMatch(acceptedMediaType::isCompatibleWith));
156+
return headers(new Predicate<ServerRequest.Headers>() {
157+
@Override
158+
public boolean test(ServerRequest.Headers headers) {
159+
List<MediaType> acceptedMediaTypes = headers.accept();
160+
MediaType.sortBySpecificityAndQuality(acceptedMediaTypes);
161+
boolean match = acceptedMediaTypes.stream()
162+
.anyMatch(acceptedMediaType -> mediaTypeSet.stream()
163+
.anyMatch(acceptedMediaType::isCompatibleWith));
164+
traceMatch("Accept", mediaTypeSet, acceptedMediaTypes, match);
165+
return match;
166+
}
167+
168+
@Override
169+
public String toString() {
170+
return String.format("Accept: %s", mediaTypeSet);
171+
}
172+
147173
});
148174
}
149175

@@ -238,7 +264,11 @@ public static RequestPredicate OPTIONS(String pattern) {
238264
*/
239265
public static RequestPredicate pathExtension(String extension) {
240266
Assert.notNull(extension, "'extension' must not be null");
241-
return pathExtension(extension::equalsIgnoreCase);
267+
return pathExtension(pathExtension -> {
268+
boolean match = extension.equalsIgnoreCase(pathExtension);
269+
traceMatch("Extension", extension, pathExtension, match);
270+
return match;
271+
});
242272
}
243273

244274
/**
@@ -289,6 +319,14 @@ public static RequestPredicate queryParam(String name, Predicate<String> predica
289319
};
290320
}
291321

322+
private static void traceMatch(String prefix, Object desired, Object actual, boolean match) {
323+
if (logger.isTraceEnabled()) {
324+
String message = String.format("%s \"%s\" %s against value \"%s\"",
325+
prefix, desired, match ? "matches" : "does not match", actual);
326+
logger.trace(message);
327+
}
328+
}
329+
292330

293331
private static class HttpMethodPredicate implements RequestPredicate {
294332

@@ -301,7 +339,14 @@ public HttpMethodPredicate(HttpMethod httpMethod) {
301339

302340
@Override
303341
public boolean test(ServerRequest request) {
304-
return this.httpMethod == request.method();
342+
boolean match = this.httpMethod == request.method();
343+
traceMatch("Method", this.httpMethod, request.method(), match);
344+
return match;
345+
}
346+
347+
@Override
348+
public String toString() {
349+
return this.httpMethod.toString();
305350
}
306351
}
307352

@@ -317,12 +362,11 @@ public PathPatternPredicate(PathPattern pattern) {
317362
@Override
318363
public boolean test(ServerRequest request) {
319364
String path = request.path();
320-
if (this.pattern.matches(path)) {
321-
if (request instanceof DefaultServerRequest) {
322-
DefaultServerRequest defaultRequest = (DefaultServerRequest) request;
323-
Map<String, String> uriTemplateVariables = this.pattern.matchAndExtract(path);
324-
defaultRequest.exchange().getAttributes().put(RouterFunctions.URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVariables);
325-
}
365+
boolean match = this.pattern.matches(path);
366+
traceMatch("Pattern", this.pattern.getPatternString(), path, match);
367+
if (match) {
368+
Map<String, String> uriTemplateVariables = this.pattern.matchAndExtract(path);
369+
request.attributes().put(RouterFunctions.URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVariables);
326370
return true;
327371
}
328372
else {
@@ -331,11 +375,19 @@ public boolean test(ServerRequest request) {
331375
}
332376

333377
@Override
334-
public ServerRequest subRequest(ServerRequest request) {
378+
public ServerRequest nestRequest(ServerRequest request) {
335379
String requestPath = request.path();
336380
String subPath = this.pattern.extractPathWithinPattern(requestPath);
381+
if (!subPath.startsWith("/")) {
382+
subPath = "/" + subPath;
383+
}
337384
return new SubPathServerRequestWrapper(request, subPath);
338385
}
386+
387+
@Override
388+
public String toString() {
389+
return this.pattern.getPatternString();
390+
}
339391
}
340392

341393
private static class HeadersPredicate implements RequestPredicate {
@@ -351,6 +403,11 @@ public HeadersPredicate(Predicate<ServerRequest.Headers> headersPredicate) {
351403
public boolean test(ServerRequest request) {
352404
return this.headersPredicate.test(request.headers());
353405
}
406+
407+
@Override
408+
public String toString() {
409+
return this.headersPredicate.toString();
410+
}
354411
}
355412

356413
private static class SubPathServerRequestWrapper implements ServerRequest {
@@ -409,6 +466,11 @@ public <T> Optional<T> attribute(String name) {
409466
return this.request.attribute(name);
410467
}
411468

469+
@Override
470+
public Map<String, Object> attributes() {
471+
return this.request.attributes();
472+
}
473+
412474
@Override
413475
public Optional<String> queryParam(String name) {
414476
return this.request.queryParam(name);
@@ -433,5 +495,11 @@ public Map<String, String> pathVariables() {
433495
public Mono<WebSession> session() {
434496
return this.request.session();
435497
}
498+
499+
@Override
500+
public String toString() {
501+
return String.format("%s %s", method(), path());
502+
}
503+
436504
}
437505
}

spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunctions.java

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
import java.util.Map;
2020
import java.util.function.Function;
2121

22+
import org.apache.commons.logging.Log;
23+
import org.apache.commons.logging.LogFactory;
2224
import reactor.core.publisher.Mono;
2325

2426
import org.springframework.core.io.Resource;
@@ -52,6 +54,8 @@
5254
*/
5355
public abstract class RouterFunctions {
5456

57+
private static final Log logger = LogFactory.getLog(RouterFunctions.class);
58+
5559
/**
5660
* Name of the {@link ServerWebExchange} attribute that contains the {@link ServerRequest}.
5761
*/
@@ -90,7 +94,18 @@ public static <T extends ServerResponse> RouterFunction<T> route(RequestPredicat
9094
Assert.notNull(predicate, "'predicate' must not be null");
9195
Assert.notNull(handlerFunction, "'handlerFunction' must not be null");
9296

93-
return request -> predicate.test(request) ? Mono.just(handlerFunction) : Mono.empty();
97+
return request -> {
98+
if (predicate.test(request)) {
99+
if (logger.isDebugEnabled()) {
100+
logger.debug(String.format("Predicate \"%s\" matches against \"%s\"",
101+
predicate, request));
102+
}
103+
return Mono.just(handlerFunction);
104+
}
105+
else {
106+
return Mono.empty();
107+
}
108+
};
94109
}
95110

96111
/**
@@ -124,7 +139,11 @@ public static <T extends ServerResponse> RouterFunction<T> nest(RequestPredicate
124139

125140
return request -> {
126141
if (predicate.test(request)) {
127-
ServerRequest subRequest = predicate.subRequest(request);
142+
if (logger.isDebugEnabled()) {
143+
logger.debug(String.format("Nested predicate \"%s\" matches against \"%s\"",
144+
predicate, request));
145+
}
146+
ServerRequest subRequest = predicate.nestRequest(request);
128147
return routerFunction.route(subRequest);
129148
}
130149
else {

spring-webflux/src/main/java/org/springframework/web/reactive/function/server/ServerRequest.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,12 @@ default String path() {
113113
*/
114114
<T> Optional<T> attribute(String name);
115115

116+
/**
117+
* Return a mutable map of request attributes.
118+
* @return the request attributes
119+
*/
120+
Map<String, Object> attributes();
121+
116122
/**
117123
* Return the first query parameter with the given name, if present.
118124
* @param name the parameter name

spring-webflux/src/main/java/org/springframework/web/reactive/function/server/support/ServerRequestWrapper.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,11 @@ public <T> Optional<T> attribute(String name) {
114114
return this.delegate.attribute(name);
115115
}
116116

117+
@Override
118+
public Map<String, Object> attributes() {
119+
return this.delegate.attributes();
120+
}
121+
117122
@Override
118123
public Optional<String> queryParam(String name) {
119124
return this.delegate.queryParam(name);

0 commit comments

Comments
 (0)