自定义 Zuul 异常
Customizing Zuul Exception
我在 Zuul 中有一个场景,URL 路由的服务也可能已关闭。因此,响应主体在 JSON 主体响应中抛出 500 HTTP 状态和 ZuulException。
{
"timestamp": 1459973637928,
"status": 500,
"error": "Internal Server Error",
"exception": "com.netflix.zuul.exception.ZuulException",
"message": "Forwarding error"
}
我只想自定义或删除 JSON 响应,并可能更改 HTTP 状态代码。
我尝试使用@ControllerAdvice 创建一个异常处理程序,但该异常未被处理程序捕获。
更新:
所以我扩展了 Zuul Filter 我可以看到它在执行错误后进入 运行 方法然后我该如何更改响应。以下是我到目前为止得到的。我在某处读到有关 SendErrorFilter 的信息,但我该如何实现它以及它有什么作用?
public class CustomFilter extends ZuulFilter {
@Override
public String filterType() {
return "post";
}
@Override
public int filterOrder() {
return 1;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
final RequestContext ctx = RequestContext.getCurrentContext();
final HttpServletResponse response = ctx.getResponse();
if (HttpStatus.INTERNAL_SERVER_ERROR.value() == ctx.getResponse().getStatus()) {
try {
response.sendError(404, "Error Error"); //trying to change the response will need to throw a JSON body.
} catch (final IOException e) {
e.printStackTrace();
} ;
}
return null;
}
将此添加到具有 @EnableZuulProxy
的 class
@Bean
public CustomFilter customFilter() {
return new CustomFilter();
}
转发通常由过滤器完成,在这种情况下,请求甚至不会到达控制器。这可以解释为什么你的 @ControllerAdvice 不起作用。
如果您在控制器中转发,@ControllerAdvice 应该可以工作。
检查 spring 是否创建了带有 @ControllerAdvice 注释的 class 实例。为此,在 class 中放置一个断点并查看它是否被命中。
也在转发应该发生的控制器方法中添加一个断点。可能是您不小心调用了另一个控制器方法而不是检查?
这些步骤应该可以帮助您解决问题。
在用@ControllerAdvice 注释的class 中添加一个用@ExceptionHandler(Exception.class) 注释的ExceptionHandler 方法,它应该捕获每个异常。
编辑:
您可以尝试添加自己的过滤器来转换 Zuulfilter 返回的错误响应。在那里您可以根据需要更改响应。
这里解释了如何自定义错误响应:
exception handling for filter in spring
正确放置过滤器可能有点棘手。
不确定正确的位置,但您应该了解过滤器的顺序和处理异常的位置。
如果将它放在 Zuulfilter 之前,则必须在调用 doFilter() 之后编写错误处理代码。
如果将它放在 Zuulfilter 之后,则必须在调用 doFilter() 之前编写错误处理代码。
在 doFilter() 之前和之后的过滤器中添加断点可能有助于找到正确的位置。
我们终于成功了 [由我的一位同事编码]:-
public class CustomErrorFilter extends ZuulFilter {
private static final Logger LOG = LoggerFactory.getLogger(CustomErrorFilter.class);
@Override
public String filterType() {
return "post";
}
@Override
public int filterOrder() {
return -1; // Needs to run before SendErrorFilter which has filterOrder == 0
}
@Override
public boolean shouldFilter() {
// only forward to errorPath if it hasn't been forwarded to already
return RequestContext.getCurrentContext().containsKey("error.status_code");
}
@Override
public Object run() {
try {
RequestContext ctx = RequestContext.getCurrentContext();
Object e = ctx.get("error.exception");
if (e != null && e instanceof ZuulException) {
ZuulException zuulException = (ZuulException)e;
LOG.error("Zuul failure detected: " + zuulException.getMessage(), zuulException);
// Remove error code to prevent further error handling in follow up filters
ctx.remove("error.status_code");
// Populate context with new response values
ctx.setResponseBody(“Overriding Zuul Exception Body”);
ctx.getResponse().setContentType("application/json");
ctx.setResponseStatusCode(500); //Can set any error code as excepted
}
}
catch (Exception ex) {
LOG.error("Exception filtering in custom error filter", ex);
ReflectionUtils.rethrowRuntimeException(ex);
}
return null;
}
}
我遇到了同样的问题,并且能够以更简单的方式解决它
只要把这个放到你的过滤器run()
方法中
if (<your condition>) {
ZuulException zuulException = new ZuulException("User message", statusCode, "Error Details message");
throw new ZuulRuntimeException(zuulException);
}
和 SendErrorFilter
将向用户发送包含所需 statusCode
的消息。
Exception 模式中的这个 Exception 看起来不太好,但它在这里有效。
以下是使用@ControllerAdvice 执行此操作的步骤:
- 首先添加一个
error
类型的过滤器,让它在zuul本身的SendErrorFilter
之前运行。
- 确保从
RequestContext
中删除与异常关联的键以防止 SendErrorFilter
执行。
- 使用
RequestDispatcher
将请求转发给 ErrorController
-- 解释如下。
- 添加一个@RestController class 并使其扩展
AbstractErrorController
,并再次重新抛出异常(在使用 (key, exception) 执行新错误过滤器的步骤中添加它),从控制器中的 RequestContext
获取它。
异常现在将在您的@ControllerAdvice 中捕获class。
Zuul RequestContext 不包含 中提到的 error.exception
。
更新 Zuul 错误过滤器:
@Component
public class ErrorFilter extends ZuulFilter {
private static final Logger LOG = LoggerFactory.getLogger(ErrorFilter.class);
private static final String FILTER_TYPE = "error";
private static final String THROWABLE_KEY = "throwable";
private static final int FILTER_ORDER = -1;
@Override
public String filterType() {
return FILTER_TYPE;
}
@Override
public int filterOrder() {
return FILTER_ORDER;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
final RequestContext context = RequestContext.getCurrentContext();
final Object throwable = context.get(THROWABLE_KEY);
if (throwable instanceof ZuulException) {
final ZuulException zuulException = (ZuulException) throwable;
LOG.error("Zuul failure detected: " + zuulException.getMessage());
// remove error code to prevent further error handling in follow up filters
context.remove(THROWABLE_KEY);
// populate context with new response values
context.setResponseBody("Overriding Zuul Exception Body");
context.getResponse().setContentType("application/json");
// can set any error code as excepted
context.setResponseStatusCode(503);
}
return null;
}
}
The simplest solution is to follow first 4 steps.
1. Create your own CustomErrorController extends
AbstractErrorController which will not allow the
BasicErrorController to be called.
2. Customize according to your need refer below method from
BasicErrorController.
<pre><code>
@RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
Map<String, Object> body = getErrorAttributes(request,
isIncludeStackTrace(request, MediaType.ALL));
HttpStatus status = getStatus(request);
return new ResponseEntity<>(body, status);
}
</pre></code>
4. You can control whether you want exception / stack trace to be printed or not can do as mentioned below:
<pre><code>
server.error.includeException=false
server.error.includeStacktrace=ON_TRACE_PARAM
</pre></code>
====================================================
5. If you want all together different error response re-throw your custom exception from your CustomErrorController and implement the Advice class as mentioned below:
<pre><code>
@Controller
@Slf4j
public class CustomErrorController extends BasicErrorController {
public CustomErrorController(ErrorAttributes errorAttributes, ServerProperties serverProperties,
List<ErrorViewResolver> errorViewResolvers) {
super(errorAttributes, serverProperties.getError(), errorViewResolvers);
log.info("Created");
}
@Override
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL));
HttpStatus status = getStatus(request);
throw new CustomErrorException(String.valueOf(status.value()), status.getReasonPhrase(), body);
}
}
@ControllerAdvice
public class GenericExceptionHandler {
// Exception handler annotation invokes a method when a specific exception
// occurs. Here we have invoked Exception.class since we
// don't have a specific exception scenario.
@ExceptionHandler(CustomException.class)
@ResponseBody
public ErrorListWsDTO customExceptionHandle(
final HttpServletRequest request,
final HttpServletResponse response,
final CustomException exception) {
LOG.info("Exception Handler invoked");
ErrorListWsDTO errorData = null;
errorData = prepareResponse(response, exception);
response.setStatus(Integer.parseInt(exception.getCode()));
return errorData;
}
/**
* Prepare error response for BAD Request
*
* @param response
* @param exception
* @return
*/
private ErrorListWsDTO prepareResponse(final HttpServletResponse response,
final AbstractException exception) {
final ErrorListWsDTO errorListData = new ErrorListWsDTO();
final List<ErrorWsDTO> errorList = new ArrayList<>();
response.setStatus(HttpStatus.BAD_REQUEST.value());
final ErrorWsDTO errorData = prepareErrorData("500",
"FAILURE", exception.getCause().getMessage());
errorList.add(errorData);
errorListData.setErrors(errorList);
return errorListData;
}
/**
* This method is used to prepare error data
*
* @param code
* error code
* @param status
* status can be success or failure
* @param exceptionMsg
* message description
* @return ErrorDTO
*/
private ErrorWsDTO prepareErrorData(final String code, final String status,
final String exceptionMsg) {
final ErrorWsDTO errorDTO = new ErrorWsDTO();
errorDTO.setReason(code);
errorDTO.setType(status);
errorDTO.setMessage(exceptionMsg);
return errorDTO;
}
}
</pre></code>
这对我有用。 RestExceptionResponse 是在@ControllerAdvice 中使用的 class,因此在内部 ZuulExceptions 的情况下我们有相同的异常响应。
@Component
@Log4j
public class CustomZuulErrorFilter extends ZuulFilter {
private static final String SEND_ERROR_FILTER_RAN = "sendErrorFilter.ran";
@Override
public String filterType() {
return ERROR_TYPE;
}
@Override
public int filterOrder() {
return SEND_ERROR_FILTER_ORDER - 1; // Needs to run before SendErrorFilter which has filterOrder == 0
}
@Override
public boolean shouldFilter() {
RequestContext ctx = RequestContext.getCurrentContext();
Throwable ex = ctx.getThrowable();
return ex instanceof ZuulException && !ctx.getBoolean(SEND_ERROR_FILTER_RAN, false);
}
@Override
public Object run() {
try {
RequestContext ctx = RequestContext.getCurrentContext();
ZuulException ex = (ZuulException) ctx.getThrowable();
// log this as error
log.error(StackTracer.toString(ex));
String requestUri = ctx.containsKey(REQUEST_URI_KEY) ? ctx.get(REQUEST_URI_KEY).toString() : "/";
RestExceptionResponse exceptionResponse = new RestExceptionResponse(HttpStatus.INTERNAL_SERVER_ERROR, ex, requestUri);
// Populate context with new response values
ctx.setResponseStatusCode(500);
this.writeResponseBody(ctx.getResponse(), exceptionResponse);
ctx.set(SEND_ERROR_FILTER_RAN, true);
}
catch (Exception ex) {
log.error(StackTracer.toString(ex));
ReflectionUtils.rethrowRuntimeException(ex);
}
return null;
}
private void writeResponseBody(HttpServletResponse response, Object body) throws IOException {
response.setContentType("application/json");
try (PrintWriter writer = response.getWriter()) {
writer.println(new JSonSerializer().toJson(body));
}
}
}
输出如下所示:
{
"timestamp": "2020-08-10 16:18:16.820",
"status": 500,
"error": "Internal Server Error",
"path": "/service",
"exception": {
"message": "Filter threw Exception",
"exceptionClass": "com.netflix.zuul.exception.ZuulException",
"superClasses": [
"com.netflix.zuul.exception.ZuulException",
"java.lang.Exception",
"java.lang.Throwable",
"java.lang.Object"
],
"stackTrace": null,
"cause": {
"message": "com.netflix.zuul.exception.ZuulException: Forwarding error",
"exceptionClass": "org.springframework.cloud.netflix.zuul.util.ZuulRuntimeException",
"superClasses": [
"org.springframework.cloud.netflix.zuul.util.ZuulRuntimeException",
"java.lang.RuntimeException",
"java.lang.Exception",
"java.lang.Throwable",
"java.lang.Object"
],
"stackTrace": null,
"cause": {
"message": "Forwarding error",
"exceptionClass": "com.netflix.zuul.exception.ZuulException",
"superClasses": [
"com.netflix.zuul.exception.ZuulException",
"java.lang.Exception",
"java.lang.Throwable",
"java.lang.Object"
],
"stackTrace": null,
"cause": {
"message": "Load balancer does not have available server for client: template-scalable-service",
"exceptionClass": "com.netflix.client.ClientException",
"superClasses": [
"com.netflix.client.ClientException",
"java.lang.Exception",
"java.lang.Throwable",
"java.lang.Object"
],
"stackTrace": null,
"cause": null
}
}
}
}
}
我在 Zuul 中有一个场景,URL 路由的服务也可能已关闭。因此,响应主体在 JSON 主体响应中抛出 500 HTTP 状态和 ZuulException。
{
"timestamp": 1459973637928,
"status": 500,
"error": "Internal Server Error",
"exception": "com.netflix.zuul.exception.ZuulException",
"message": "Forwarding error"
}
我只想自定义或删除 JSON 响应,并可能更改 HTTP 状态代码。
我尝试使用@ControllerAdvice 创建一个异常处理程序,但该异常未被处理程序捕获。
更新:
所以我扩展了 Zuul Filter 我可以看到它在执行错误后进入 运行 方法然后我该如何更改响应。以下是我到目前为止得到的。我在某处读到有关 SendErrorFilter 的信息,但我该如何实现它以及它有什么作用?
public class CustomFilter extends ZuulFilter {
@Override
public String filterType() {
return "post";
}
@Override
public int filterOrder() {
return 1;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
final RequestContext ctx = RequestContext.getCurrentContext();
final HttpServletResponse response = ctx.getResponse();
if (HttpStatus.INTERNAL_SERVER_ERROR.value() == ctx.getResponse().getStatus()) {
try {
response.sendError(404, "Error Error"); //trying to change the response will need to throw a JSON body.
} catch (final IOException e) {
e.printStackTrace();
} ;
}
return null;
}
将此添加到具有 @EnableZuulProxy
的 class@Bean
public CustomFilter customFilter() {
return new CustomFilter();
}
转发通常由过滤器完成,在这种情况下,请求甚至不会到达控制器。这可以解释为什么你的 @ControllerAdvice 不起作用。
如果您在控制器中转发,@ControllerAdvice 应该可以工作。 检查 spring 是否创建了带有 @ControllerAdvice 注释的 class 实例。为此,在 class 中放置一个断点并查看它是否被命中。
也在转发应该发生的控制器方法中添加一个断点。可能是您不小心调用了另一个控制器方法而不是检查?
这些步骤应该可以帮助您解决问题。
在用@ControllerAdvice 注释的class 中添加一个用@ExceptionHandler(Exception.class) 注释的ExceptionHandler 方法,它应该捕获每个异常。
编辑: 您可以尝试添加自己的过滤器来转换 Zuulfilter 返回的错误响应。在那里您可以根据需要更改响应。
这里解释了如何自定义错误响应:
exception handling for filter in spring
正确放置过滤器可能有点棘手。 不确定正确的位置,但您应该了解过滤器的顺序和处理异常的位置。
如果将它放在 Zuulfilter 之前,则必须在调用 doFilter() 之后编写错误处理代码。
如果将它放在 Zuulfilter 之后,则必须在调用 doFilter() 之前编写错误处理代码。
在 doFilter() 之前和之后的过滤器中添加断点可能有助于找到正确的位置。
我们终于成功了 [由我的一位同事编码]:-
public class CustomErrorFilter extends ZuulFilter {
private static final Logger LOG = LoggerFactory.getLogger(CustomErrorFilter.class);
@Override
public String filterType() {
return "post";
}
@Override
public int filterOrder() {
return -1; // Needs to run before SendErrorFilter which has filterOrder == 0
}
@Override
public boolean shouldFilter() {
// only forward to errorPath if it hasn't been forwarded to already
return RequestContext.getCurrentContext().containsKey("error.status_code");
}
@Override
public Object run() {
try {
RequestContext ctx = RequestContext.getCurrentContext();
Object e = ctx.get("error.exception");
if (e != null && e instanceof ZuulException) {
ZuulException zuulException = (ZuulException)e;
LOG.error("Zuul failure detected: " + zuulException.getMessage(), zuulException);
// Remove error code to prevent further error handling in follow up filters
ctx.remove("error.status_code");
// Populate context with new response values
ctx.setResponseBody(“Overriding Zuul Exception Body”);
ctx.getResponse().setContentType("application/json");
ctx.setResponseStatusCode(500); //Can set any error code as excepted
}
}
catch (Exception ex) {
LOG.error("Exception filtering in custom error filter", ex);
ReflectionUtils.rethrowRuntimeException(ex);
}
return null;
}
}
我遇到了同样的问题,并且能够以更简单的方式解决它
只要把这个放到你的过滤器run()
方法中
if (<your condition>) {
ZuulException zuulException = new ZuulException("User message", statusCode, "Error Details message");
throw new ZuulRuntimeException(zuulException);
}
和 SendErrorFilter
将向用户发送包含所需 statusCode
的消息。
Exception 模式中的这个 Exception 看起来不太好,但它在这里有效。
以下是使用@ControllerAdvice 执行此操作的步骤:
- 首先添加一个
error
类型的过滤器,让它在zuul本身的SendErrorFilter
之前运行。 - 确保从
RequestContext
中删除与异常关联的键以防止SendErrorFilter
执行。 - 使用
RequestDispatcher
将请求转发给ErrorController
-- 解释如下。 - 添加一个@RestController class 并使其扩展
AbstractErrorController
,并再次重新抛出异常(在使用 (key, exception) 执行新错误过滤器的步骤中添加它),从控制器中的RequestContext
获取它。
异常现在将在您的@ControllerAdvice 中捕获class。
Zuul RequestContext 不包含 error.exception
。
更新 Zuul 错误过滤器:
@Component
public class ErrorFilter extends ZuulFilter {
private static final Logger LOG = LoggerFactory.getLogger(ErrorFilter.class);
private static final String FILTER_TYPE = "error";
private static final String THROWABLE_KEY = "throwable";
private static final int FILTER_ORDER = -1;
@Override
public String filterType() {
return FILTER_TYPE;
}
@Override
public int filterOrder() {
return FILTER_ORDER;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
final RequestContext context = RequestContext.getCurrentContext();
final Object throwable = context.get(THROWABLE_KEY);
if (throwable instanceof ZuulException) {
final ZuulException zuulException = (ZuulException) throwable;
LOG.error("Zuul failure detected: " + zuulException.getMessage());
// remove error code to prevent further error handling in follow up filters
context.remove(THROWABLE_KEY);
// populate context with new response values
context.setResponseBody("Overriding Zuul Exception Body");
context.getResponse().setContentType("application/json");
// can set any error code as excepted
context.setResponseStatusCode(503);
}
return null;
}
}
The simplest solution is to follow first 4 steps.
1. Create your own CustomErrorController extends
AbstractErrorController which will not allow the
BasicErrorController to be called.
2. Customize according to your need refer below method from
BasicErrorController.
<pre><code>
@RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
Map<String, Object> body = getErrorAttributes(request,
isIncludeStackTrace(request, MediaType.ALL));
HttpStatus status = getStatus(request);
return new ResponseEntity<>(body, status);
}
</pre></code>
4. You can control whether you want exception / stack trace to be printed or not can do as mentioned below:
<pre><code>
server.error.includeException=false
server.error.includeStacktrace=ON_TRACE_PARAM
</pre></code>
====================================================
5. If you want all together different error response re-throw your custom exception from your CustomErrorController and implement the Advice class as mentioned below:
<pre><code>
@Controller
@Slf4j
public class CustomErrorController extends BasicErrorController {
public CustomErrorController(ErrorAttributes errorAttributes, ServerProperties serverProperties,
List<ErrorViewResolver> errorViewResolvers) {
super(errorAttributes, serverProperties.getError(), errorViewResolvers);
log.info("Created");
}
@Override
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL));
HttpStatus status = getStatus(request);
throw new CustomErrorException(String.valueOf(status.value()), status.getReasonPhrase(), body);
}
}
@ControllerAdvice
public class GenericExceptionHandler {
// Exception handler annotation invokes a method when a specific exception
// occurs. Here we have invoked Exception.class since we
// don't have a specific exception scenario.
@ExceptionHandler(CustomException.class)
@ResponseBody
public ErrorListWsDTO customExceptionHandle(
final HttpServletRequest request,
final HttpServletResponse response,
final CustomException exception) {
LOG.info("Exception Handler invoked");
ErrorListWsDTO errorData = null;
errorData = prepareResponse(response, exception);
response.setStatus(Integer.parseInt(exception.getCode()));
return errorData;
}
/**
* Prepare error response for BAD Request
*
* @param response
* @param exception
* @return
*/
private ErrorListWsDTO prepareResponse(final HttpServletResponse response,
final AbstractException exception) {
final ErrorListWsDTO errorListData = new ErrorListWsDTO();
final List<ErrorWsDTO> errorList = new ArrayList<>();
response.setStatus(HttpStatus.BAD_REQUEST.value());
final ErrorWsDTO errorData = prepareErrorData("500",
"FAILURE", exception.getCause().getMessage());
errorList.add(errorData);
errorListData.setErrors(errorList);
return errorListData;
}
/**
* This method is used to prepare error data
*
* @param code
* error code
* @param status
* status can be success or failure
* @param exceptionMsg
* message description
* @return ErrorDTO
*/
private ErrorWsDTO prepareErrorData(final String code, final String status,
final String exceptionMsg) {
final ErrorWsDTO errorDTO = new ErrorWsDTO();
errorDTO.setReason(code);
errorDTO.setType(status);
errorDTO.setMessage(exceptionMsg);
return errorDTO;
}
}
</pre></code>
这对我有用。 RestExceptionResponse 是在@ControllerAdvice 中使用的 class,因此在内部 ZuulExceptions 的情况下我们有相同的异常响应。
@Component
@Log4j
public class CustomZuulErrorFilter extends ZuulFilter {
private static final String SEND_ERROR_FILTER_RAN = "sendErrorFilter.ran";
@Override
public String filterType() {
return ERROR_TYPE;
}
@Override
public int filterOrder() {
return SEND_ERROR_FILTER_ORDER - 1; // Needs to run before SendErrorFilter which has filterOrder == 0
}
@Override
public boolean shouldFilter() {
RequestContext ctx = RequestContext.getCurrentContext();
Throwable ex = ctx.getThrowable();
return ex instanceof ZuulException && !ctx.getBoolean(SEND_ERROR_FILTER_RAN, false);
}
@Override
public Object run() {
try {
RequestContext ctx = RequestContext.getCurrentContext();
ZuulException ex = (ZuulException) ctx.getThrowable();
// log this as error
log.error(StackTracer.toString(ex));
String requestUri = ctx.containsKey(REQUEST_URI_KEY) ? ctx.get(REQUEST_URI_KEY).toString() : "/";
RestExceptionResponse exceptionResponse = new RestExceptionResponse(HttpStatus.INTERNAL_SERVER_ERROR, ex, requestUri);
// Populate context with new response values
ctx.setResponseStatusCode(500);
this.writeResponseBody(ctx.getResponse(), exceptionResponse);
ctx.set(SEND_ERROR_FILTER_RAN, true);
}
catch (Exception ex) {
log.error(StackTracer.toString(ex));
ReflectionUtils.rethrowRuntimeException(ex);
}
return null;
}
private void writeResponseBody(HttpServletResponse response, Object body) throws IOException {
response.setContentType("application/json");
try (PrintWriter writer = response.getWriter()) {
writer.println(new JSonSerializer().toJson(body));
}
}
}
输出如下所示:
{
"timestamp": "2020-08-10 16:18:16.820",
"status": 500,
"error": "Internal Server Error",
"path": "/service",
"exception": {
"message": "Filter threw Exception",
"exceptionClass": "com.netflix.zuul.exception.ZuulException",
"superClasses": [
"com.netflix.zuul.exception.ZuulException",
"java.lang.Exception",
"java.lang.Throwable",
"java.lang.Object"
],
"stackTrace": null,
"cause": {
"message": "com.netflix.zuul.exception.ZuulException: Forwarding error",
"exceptionClass": "org.springframework.cloud.netflix.zuul.util.ZuulRuntimeException",
"superClasses": [
"org.springframework.cloud.netflix.zuul.util.ZuulRuntimeException",
"java.lang.RuntimeException",
"java.lang.Exception",
"java.lang.Throwable",
"java.lang.Object"
],
"stackTrace": null,
"cause": {
"message": "Forwarding error",
"exceptionClass": "com.netflix.zuul.exception.ZuulException",
"superClasses": [
"com.netflix.zuul.exception.ZuulException",
"java.lang.Exception",
"java.lang.Throwable",
"java.lang.Object"
],
"stackTrace": null,
"cause": {
"message": "Load balancer does not have available server for client: template-scalable-service",
"exceptionClass": "com.netflix.client.ClientException",
"superClasses": [
"com.netflix.client.ClientException",
"java.lang.Exception",
"java.lang.Throwable",
"java.lang.Object"
],
"stackTrace": null,
"cause": null
}
}
}
}
}