Extra information in WebFlux stacktraces
Use the checkpoint operator at various places in WebFlux to insert information that Reactor then uses to enrich exceptions, via suppressed exceptions, when error signals flow through the operator. Closes gh-22105
This commit is contained in:
@@ -21,6 +21,7 @@ import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.StringJoiner;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
@@ -89,6 +90,8 @@ public class HandlerMethod {
|
||||
@Nullable
|
||||
private volatile List<Annotation[][]> interfaceParameterAnnotations;
|
||||
|
||||
private final String description;
|
||||
|
||||
|
||||
/**
|
||||
* Create an instance from a bean instance and a method.
|
||||
@@ -103,6 +106,7 @@ public class HandlerMethod {
|
||||
this.bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
|
||||
this.parameters = initMethodParameters();
|
||||
evaluateResponseStatus();
|
||||
this.description = initDescription(this.beanType, this.method);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -119,6 +123,7 @@ public class HandlerMethod {
|
||||
this.bridgedMethod = BridgeMethodResolver.findBridgedMethod(this.method);
|
||||
this.parameters = initMethodParameters();
|
||||
evaluateResponseStatus();
|
||||
this.description = initDescription(this.beanType, this.method);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -141,6 +146,7 @@ public class HandlerMethod {
|
||||
this.bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
|
||||
this.parameters = initMethodParameters();
|
||||
evaluateResponseStatus();
|
||||
this.description = initDescription(this.beanType, this.method);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -156,6 +162,7 @@ public class HandlerMethod {
|
||||
this.parameters = handlerMethod.parameters;
|
||||
this.responseStatus = handlerMethod.responseStatus;
|
||||
this.responseStatusReason = handlerMethod.responseStatusReason;
|
||||
this.description = handlerMethod.description;
|
||||
this.resolvedFromHandlerMethod = handlerMethod.resolvedFromHandlerMethod;
|
||||
}
|
||||
|
||||
@@ -174,6 +181,7 @@ public class HandlerMethod {
|
||||
this.responseStatus = handlerMethod.responseStatus;
|
||||
this.responseStatusReason = handlerMethod.responseStatusReason;
|
||||
this.resolvedFromHandlerMethod = handlerMethod;
|
||||
this.description = handlerMethod.description;
|
||||
}
|
||||
|
||||
private MethodParameter[] initMethodParameters() {
|
||||
@@ -198,6 +206,14 @@ public class HandlerMethod {
|
||||
}
|
||||
}
|
||||
|
||||
private static String initDescription(Class<?> beanType, Method method) {
|
||||
StringJoiner joiner = new StringJoiner(", ", "(", ")");
|
||||
for (Class<?> paramType : method.getParameterTypes()) {
|
||||
joiner.add(paramType.getSimpleName());
|
||||
}
|
||||
return beanType.getName() + "#" + method.getName() + joiner.toString();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the bean for this handler method.
|
||||
@@ -389,7 +405,7 @@ public class HandlerMethod {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.method.toGenericString();
|
||||
return this.description;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -53,7 +53,7 @@ public class DefaultWebFilterChain implements WebFilterChain {
|
||||
private final WebFilter currentFilter;
|
||||
|
||||
@Nullable
|
||||
private final DefaultWebFilterChain next;
|
||||
private final DefaultWebFilterChain chain;
|
||||
|
||||
|
||||
/**
|
||||
@@ -68,7 +68,7 @@ public class DefaultWebFilterChain implements WebFilterChain {
|
||||
this.handler = handler;
|
||||
DefaultWebFilterChain chain = initChain(filters, handler);
|
||||
this.currentFilter = chain.currentFilter;
|
||||
this.next = chain.next;
|
||||
this.chain = chain.chain;
|
||||
}
|
||||
|
||||
private static DefaultWebFilterChain initChain(List<WebFilter> filters, WebHandler handler) {
|
||||
@@ -84,12 +84,12 @@ public class DefaultWebFilterChain implements WebFilterChain {
|
||||
* Private constructor to represent one link in the chain.
|
||||
*/
|
||||
private DefaultWebFilterChain(List<WebFilter> allFilters, WebHandler handler,
|
||||
@Nullable WebFilter currentFilter, @Nullable DefaultWebFilterChain next) {
|
||||
@Nullable WebFilter currentFilter, @Nullable DefaultWebFilterChain chain) {
|
||||
|
||||
this.allFilters = allFilters;
|
||||
this.currentFilter = currentFilter;
|
||||
this.handler = handler;
|
||||
this.next = next;
|
||||
this.chain = chain;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -117,9 +117,14 @@ public class DefaultWebFilterChain implements WebFilterChain {
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange) {
|
||||
return Mono.defer(() ->
|
||||
this.currentFilter != null && this.next != null ?
|
||||
this.currentFilter.filter(exchange, this.next) :
|
||||
this.currentFilter != null && this.chain != null ?
|
||||
invokeFilter(this.currentFilter, this.chain, exchange) :
|
||||
this.handler.handle(exchange));
|
||||
}
|
||||
|
||||
private Mono<Void> invokeFilter(WebFilter current, DefaultWebFilterChain chain, ServerWebExchange exchange) {
|
||||
return current.filter(exchange, chain)
|
||||
.checkpoint(current.getClass().getName() + " [DefaultWebFilterChain]");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -22,6 +22,9 @@ import java.util.List;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.WebExceptionHandler;
|
||||
import org.springframework.web.server.WebHandler;
|
||||
@@ -41,7 +44,10 @@ public class ExceptionHandlingWebHandler extends WebHandlerDecorator {
|
||||
|
||||
public ExceptionHandlingWebHandler(WebHandler delegate, List<WebExceptionHandler> handlers) {
|
||||
super(delegate);
|
||||
this.exceptionHandlers = Collections.unmodifiableList(new ArrayList<>(handlers));
|
||||
List<WebExceptionHandler> handlersToUse = new ArrayList<>();
|
||||
handlersToUse.add(new CheckpointInsertingHandler());
|
||||
handlersToUse.addAll(handlers);
|
||||
this.exceptionHandlers = Collections.unmodifiableList(handlersToUse);
|
||||
}
|
||||
|
||||
|
||||
@@ -71,4 +77,24 @@ public class ExceptionHandlingWebHandler extends WebHandlerDecorator {
|
||||
return completion;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* WebExceptionHandler to insert a checkpoint with current URL information.
|
||||
* Must be the first in order to ensure we catch the error signal before
|
||||
* the exception is handled and e.g. turned into an error response.
|
||||
* @since 5.2
|
||||
*/
|
||||
private static class CheckpointInsertingHandler implements WebExceptionHandler {
|
||||
|
||||
@Override
|
||||
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
|
||||
ServerHttpRequest request = exchange.getRequest();
|
||||
String rawQuery = request.getURI().getRawQuery();
|
||||
String query = StringUtils.hasText(rawQuery) ? "?" + rawQuery : "";
|
||||
HttpMethod httpMethod = request.getMethod();
|
||||
String description = "HTTP " + httpMethod + " \"" + request.getPath() + query + "\"";
|
||||
return Mono.error(ex).checkpoint(description + " [ExceptionHandlingWebHandler]").cast(Void.class);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user