Skip to content

Commit b0d5013

Browse files
committed
Add logPrefix information to ErrorAttributes and whitelabel error page in WebFlux.
1 parent 6519e85 commit b0d5013

File tree

5 files changed

+76
-46
lines changed

5 files changed

+76
-46
lines changed

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/error/AbstractErrorWebExceptionHandler.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,10 +209,11 @@ protected Mono<ServerResponse> renderDefaultErrorView(
209209
Date timestamp = (Date) error.get("timestamp");
210210
Object message = error.get("message");
211211
Object trace = error.get("trace");
212+
Object logPrefix = error.get("logPrefix");
212213
builder.append("<html><body><h1>Whitelabel Error Page</h1>").append(
213214
"<p>This application has no configured error view, so you are seeing this as a fallback.</p>")
214215
.append("<div id='created'>").append(timestamp).append("</div>")
215-
.append("<div>There was an unexpected error (type=")
216+
.append(logPrefix).append("<div>There was an unexpected error (type=")
216217
.append(htmlEscape(error.get("error"))).append(", status=")
217218
.append(htmlEscape(error.get("status"))).append(").</div>");
218219
if (message != null) {

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/error/DefaultErrorWebExceptionHandler.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,6 @@ protected Mono<ServerResponse> renderErrorView(ServerRequest request) {
134134
protected Mono<ServerResponse> renderErrorResponse(ServerRequest request) {
135135
boolean includeStackTrace = isIncludeStackTrace(request, MediaType.ALL);
136136
Map<String, Object> error = getErrorAttributes(request, includeStackTrace);
137-
HttpStatus errorStatus = getHttpStatus(error);
138137
return ServerResponse.status(getHttpStatus(error))
139138
.contentType(MediaType.APPLICATION_JSON_UTF8)
140139
.body(BodyInserters.fromObject(error));

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/error/DefaultErrorWebExceptionHandlerIntegrationTests.java

Lines changed: 63 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration;
3030
import org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration;
3131
import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration;
32+
import org.springframework.boot.test.context.assertj.AssertableReactiveWebApplicationContext;
3233
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
3334
import org.springframework.boot.testsupport.rule.OutputCapture;
3435
import org.springframework.context.annotation.Configuration;
@@ -42,6 +43,8 @@
4243
import org.springframework.web.bind.annotation.RestController;
4344
import org.springframework.web.server.ResponseStatusException;
4445
import org.springframework.web.server.ServerWebExchange;
46+
import org.springframework.web.server.WebFilter;
47+
import org.springframework.web.server.WebFilterChain;
4548

4649
import static org.assertj.core.api.Assertions.assertThat;
4750
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@@ -57,6 +60,8 @@ public class DefaultErrorWebExceptionHandlerIntegrationTests {
5760
@Rule
5861
public OutputCapture outputCapture = new OutputCapture();
5962

63+
private final LogIdFilter logIdFilter = new LogIdFilter();
64+
6065
private ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner()
6166
.withConfiguration(AutoConfigurations.of(
6267
ReactiveWebServerFactoryAutoConfiguration.class,
@@ -71,15 +76,15 @@ public class DefaultErrorWebExceptionHandlerIntegrationTests {
7176
@Test
7277
public void jsonError() {
7378
this.contextRunner.run((context) -> {
74-
WebTestClient client = WebTestClient.bindToApplicationContext(context)
75-
.build();
79+
WebTestClient client = getWebClient(context);
7680
client.get().uri("/").exchange().expectStatus()
7781
.isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR).expectBody()
7882
.jsonPath("status").isEqualTo("500").jsonPath("error")
7983
.isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase())
8084
.jsonPath("path").isEqualTo(("/")).jsonPath("message")
8185
.isEqualTo("Expected!").jsonPath("exception").doesNotExist()
82-
.jsonPath("trace").doesNotExist();
86+
.jsonPath("trace").doesNotExist().jsonPath("logPrefix")
87+
.isEqualTo(this.logIdFilter.getLogId());
8388
this.outputCapture.expect(Matchers.allOf(
8489
containsString("500 Server Error for HTTP GET \"/\""),
8590
containsString("java.lang.IllegalStateException: Expected!")));
@@ -89,20 +94,19 @@ public void jsonError() {
8994
@Test
9095
public void notFound() {
9196
this.contextRunner.run((context) -> {
92-
WebTestClient client = WebTestClient.bindToApplicationContext(context)
93-
.build();
97+
WebTestClient client = getWebClient(context);
9498
client.get().uri("/notFound").exchange().expectStatus().isNotFound()
9599
.expectBody().jsonPath("status").isEqualTo("404").jsonPath("error")
96100
.isEqualTo(HttpStatus.NOT_FOUND.getReasonPhrase()).jsonPath("path")
97-
.isEqualTo(("/notFound")).jsonPath("exception").doesNotExist();
101+
.isEqualTo(("/notFound")).jsonPath("exception").doesNotExist()
102+
.jsonPath("logPrefix").isEqualTo(this.logIdFilter.getLogId());
98103
});
99104
}
100105

101106
@Test
102107
public void htmlError() {
103108
this.contextRunner.run((context) -> {
104-
WebTestClient client = WebTestClient.bindToApplicationContext(context)
105-
.build();
109+
WebTestClient client = getWebClient(context);
106110
String body = client.get().uri("/").accept(MediaType.TEXT_HTML).exchange()
107111
.expectStatus().isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR)
108112
.expectHeader().contentType(MediaType.TEXT_HTML)
@@ -117,14 +121,14 @@ public void htmlError() {
117121
@Test
118122
public void bindingResultError() {
119123
this.contextRunner.run((context) -> {
120-
WebTestClient client = WebTestClient.bindToApplicationContext(context)
121-
.build();
124+
WebTestClient client = getWebClient(context);
122125
client.post().uri("/bind").contentType(MediaType.APPLICATION_JSON)
123126
.syncBody("{}").exchange().expectStatus().isBadRequest().expectBody()
124127
.jsonPath("status").isEqualTo("400").jsonPath("error")
125128
.isEqualTo(HttpStatus.BAD_REQUEST.getReasonPhrase()).jsonPath("path")
126129
.isEqualTo(("/bind")).jsonPath("exception").doesNotExist()
127-
.jsonPath("errors").isArray().jsonPath("message").isNotEmpty();
130+
.jsonPath("errors").isArray().jsonPath("message").isNotEmpty()
131+
.jsonPath("logPrefix").isEqualTo(this.logIdFilter.getLogId());
128132
});
129133
}
130134

@@ -134,63 +138,62 @@ public void includeStackTraceOnParam() {
134138
.withPropertyValues("server.error.include-exception=true",
135139
"server.error.include-stacktrace=on-trace-param")
136140
.run((context) -> {
137-
WebTestClient client = WebTestClient.bindToApplicationContext(context)
138-
.build();
141+
WebTestClient client = getWebClient(context);
139142
client.get().uri("/?trace=true").exchange().expectStatus()
140143
.isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR).expectBody()
141144
.jsonPath("status").isEqualTo("500").jsonPath("error")
142145
.isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase())
143146
.jsonPath("exception")
144147
.isEqualTo(IllegalStateException.class.getName())
145-
.jsonPath("trace").exists();
148+
.jsonPath("trace").exists().jsonPath("logPrefix")
149+
.isEqualTo(this.logIdFilter.getLogId());
146150
});
147151
}
148152

149153
@Test
150-
public void alwaysIncludeStackTrace() throws Exception {
154+
public void alwaysIncludeStackTrace() {
151155
this.contextRunner.withPropertyValues("server.error.include-exception=true",
152156
"server.error.include-stacktrace=always").run((context) -> {
153-
WebTestClient client = WebTestClient.bindToApplicationContext(context)
154-
.build();
157+
WebTestClient client = getWebClient(context);
155158
client.get().uri("/?trace=false").exchange().expectStatus()
156159
.isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR).expectBody()
157160
.jsonPath("status").isEqualTo("500").jsonPath("error")
158161
.isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase())
159162
.jsonPath("exception")
160163
.isEqualTo(IllegalStateException.class.getName())
161-
.jsonPath("trace").exists();
164+
.jsonPath("trace").exists().jsonPath("logPrefix")
165+
.isEqualTo(this.logIdFilter.getLogId());
162166
});
163167
}
164168

165169
@Test
166170
public void neverIncludeStackTrace() {
167171
this.contextRunner.withPropertyValues("server.error.include-exception=true",
168172
"server.error.include-stacktrace=never").run((context) -> {
169-
WebTestClient client = WebTestClient.bindToApplicationContext(context)
170-
.build();
173+
WebTestClient client = getWebClient(context);
171174
client.get().uri("/?trace=true").exchange().expectStatus()
172175
.isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR).expectBody()
173176
.jsonPath("status").isEqualTo("500").jsonPath("error")
174177
.isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase())
175178
.jsonPath("exception")
176179
.isEqualTo(IllegalStateException.class.getName())
177-
.jsonPath("trace").doesNotExist();
178-
180+
.jsonPath("trace").doesNotExist().jsonPath("logPrefix")
181+
.exists();
179182
});
180183
}
181184

182185
@Test
183186
public void statusException() {
184187
this.contextRunner.withPropertyValues("server.error.include-exception=true")
185188
.run((context) -> {
186-
WebTestClient client = WebTestClient.bindToApplicationContext(context)
187-
.build();
189+
WebTestClient client = getWebClient(context);
188190
client.get().uri("/badRequest").exchange().expectStatus()
189191
.isBadRequest().expectBody().jsonPath("status")
190192
.isEqualTo("400").jsonPath("error")
191193
.isEqualTo(HttpStatus.BAD_REQUEST.getReasonPhrase())
192194
.jsonPath("exception")
193-
.isEqualTo(ResponseStatusException.class.getName());
195+
.isEqualTo(ResponseStatusException.class.getName())
196+
.jsonPath("logPrefix").isEqualTo(this.logIdFilter.getLogId());
194197
});
195198
}
196199

@@ -200,14 +203,14 @@ public void defaultErrorView() {
200203
.withPropertyValues("spring.mustache.prefix=classpath:/unknown/",
201204
"server.error.include-stacktrace=always")
202205
.run((context) -> {
203-
WebTestClient client = WebTestClient.bindToApplicationContext(context)
204-
.build();
206+
WebTestClient client = getWebClient(context);
205207
String body = client.get().uri("/").accept(MediaType.TEXT_HTML)
206208
.exchange().expectStatus()
207209
.isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR).expectHeader()
208210
.contentType(MediaType.TEXT_HTML).expectBody(String.class)
209211
.returnResult().getResponseBody();
210212
assertThat(body).contains("Whitelabel Error Page")
213+
.contains(this.logIdFilter.getLogId())
211214
.contains("<div>Expected!</div>").contains(
212215
"<div style='white-space:pre-wrap;'>java.lang.IllegalStateException");
213216
});
@@ -218,14 +221,14 @@ public void escapeHtmlInDefaultErrorView() {
218221
this.contextRunner
219222
.withPropertyValues("spring.mustache.prefix=classpath:/unknown/")
220223
.run((context) -> {
221-
WebTestClient client = WebTestClient.bindToApplicationContext(context)
222-
.build();
224+
WebTestClient client = getWebClient(context);
223225
String body = client.get().uri("/html").accept(MediaType.TEXT_HTML)
224226
.exchange().expectStatus()
225227
.isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR).expectHeader()
226228
.contentType(MediaType.TEXT_HTML).expectBody(String.class)
227229
.returnResult().getResponseBody();
228230
assertThat(body).contains("Whitelabel Error Page")
231+
.contains(this.logIdFilter.getLogId())
229232
.doesNotContain("<script>").contains("&lt;script&gt;");
230233
});
231234
}
@@ -235,22 +238,21 @@ public void testExceptionWithNullMessage() {
235238
this.contextRunner
236239
.withPropertyValues("spring.mustache.prefix=classpath:/unknown/")
237240
.run((context) -> {
238-
WebTestClient client = WebTestClient.bindToApplicationContext(context)
239-
.build();
241+
WebTestClient client = getWebClient(context);
240242
String body = client.get().uri("/notfound")
241243
.accept(MediaType.TEXT_HTML).exchange().expectStatus()
242244
.isNotFound().expectHeader().contentType(MediaType.TEXT_HTML)
243245
.expectBody(String.class).returnResult().getResponseBody();
244246
assertThat(body).contains("Whitelabel Error Page")
247+
.contains(this.logIdFilter.getLogId())
245248
.contains("type=Not Found, status=404");
246249
});
247250
}
248251

249252
@Test
250253
public void responseCommitted() {
251254
this.contextRunner.run((context) -> {
252-
WebTestClient client = WebTestClient.bindToApplicationContext(context)
253-
.build();
255+
WebTestClient client = getWebClient(context);
254256
assertThatExceptionOfType(RuntimeException.class)
255257
.isThrownBy(
256258
() -> client.get().uri("/commit").exchange().expectStatus())
@@ -263,8 +265,7 @@ public void responseCommitted() {
263265
public void whitelabelDisabled() {
264266
this.contextRunner.withPropertyValues("server.error.whitelabel.enabled=false",
265267
"spring.mustache.prefix=classpath:/unknown/").run((context) -> {
266-
WebTestClient client = WebTestClient.bindToApplicationContext(context)
267-
.build();
268+
WebTestClient client = getWebClient(context);
268269
client.get().uri("/notfound").accept(MediaType.TEXT_HTML).exchange()
269270
.expectStatus().isNotFound().expectBody().isEmpty();
270271
});
@@ -276,8 +277,7 @@ public void exactStatusTemplateErrorPage() {
276277
.withPropertyValues("server.error.whitelabel.enabled=false",
277278
"spring.mustache.prefix=" + getErrorTemplatesLocation())
278279
.run((context) -> {
279-
WebTestClient client = WebTestClient.bindToApplicationContext(context)
280-
.build();
280+
WebTestClient client = getWebClient(context);
281281
String body = client.get().uri("/notfound")
282282
.accept(MediaType.TEXT_HTML).exchange().expectStatus()
283283
.isNotFound().expectBody(String.class).returnResult()
@@ -292,8 +292,7 @@ public void seriesStatusTemplateErrorPage() {
292292
.withPropertyValues("server.error.whitelabel.enabled=false",
293293
"spring.mustache.prefix=" + getErrorTemplatesLocation())
294294
.run((context) -> {
295-
WebTestClient client = WebTestClient.bindToApplicationContext(context)
296-
.build();
295+
WebTestClient client = getWebClient(context);
297296
String body = client.get().uri("/badRequest")
298297
.accept(MediaType.TEXT_HTML).exchange().expectStatus()
299298
.isBadRequest().expectBody(String.class).returnResult()
@@ -302,21 +301,41 @@ public void seriesStatusTemplateErrorPage() {
302301
});
303302
}
304303

305-
private String getErrorTemplatesLocation() {
306-
String packageName = getClass().getPackage().getName();
307-
return "classpath:/" + packageName.replace('.', '/') + "/templates/";
304+
private WebTestClient getWebClient(AssertableReactiveWebApplicationContext context) {
305+
return WebTestClient.bindToApplicationContext(context).webFilter(this.logIdFilter)
306+
.build();
308307
}
309308

310309
@Test
311310
public void invalidAcceptMediaType() {
312311
this.contextRunner.run((context) -> {
313-
WebTestClient client = WebTestClient.bindToApplicationContext(context)
314-
.build();
312+
WebTestClient client = getWebClient(context);
315313
client.get().uri("/notfound").header("Accept", "v=3.0").exchange()
316314
.expectStatus().isEqualTo(HttpStatus.NOT_FOUND);
317315
});
318316
}
319317

318+
private String getErrorTemplatesLocation() {
319+
String packageName = getClass().getPackage().getName();
320+
return "classpath:/" + packageName.replace('.', '/') + "/templates/";
321+
}
322+
323+
private static final class LogIdFilter implements WebFilter {
324+
325+
private String logId;
326+
327+
@Override
328+
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
329+
this.logId = exchange.getLogPrefix();
330+
return chain.filter(exchange);
331+
}
332+
333+
String getLogId() {
334+
return this.logId;
335+
}
336+
337+
}
338+
320339
@Configuration
321340
public static class Application {
322341

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/reactive/error/DefaultErrorAttributes.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ public Map<String, Object> getErrorAttributes(ServerRequest request,
8686
errorAttributes.put("status", errorStatus.value());
8787
errorAttributes.put("error", errorStatus.getReasonPhrase());
8888
errorAttributes.put("message", determineMessage(error));
89+
errorAttributes.put("logPrefix", request.exchange().getLogPrefix());
8990
handleException(errorAttributes, determineException(error), includeStackTrace);
9091
return errorAttributes;
9192
}

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/reactive/error/DefaultErrorAttributesTests.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,16 @@ public void includePath() {
206206
assertThat(attributes.get("path")).isEqualTo("/test");
207207
}
208208

209+
@Test
210+
public void includeLogPrefix() {
211+
MockServerHttpRequest request = MockServerHttpRequest.get("/test").build();
212+
ServerRequest serverRequest = buildServerRequest(request, NOT_FOUND);
213+
Map<String, Object> attributes = this.errorAttributes
214+
.getErrorAttributes(serverRequest, false);
215+
assertThat(attributes.get("logPrefix"))
216+
.isEqualTo(serverRequest.exchange().getLogPrefix());
217+
}
218+
209219
@Test
210220
public void extractBindingResultErrors() throws Exception {
211221
Method method = getClass().getMethod("method", String.class);

0 commit comments

Comments
 (0)