From 6ee8da1cd1a0f52078ad7b0876e222b24291798b Mon Sep 17 00:00:00 2001 From: Francis Dong Date: Thu, 19 Nov 2020 16:52:34 +0800 Subject: [PATCH] #15 return exception information while failed to execute script --- js/context.js | 6 ++ .../we/exception/ExecuteScriptException.java | 67 +++++++++++++++++++ .../filter/FilterExceptionHandlerConfig.java | 18 +++++ .../java/we/filter/FizzGatewayFilter.java | 2 +- src/main/java/we/fizz/AggregateResult.java | 12 +++- src/main/java/we/fizz/Pipeline.java | 18 ++--- src/main/java/we/fizz/StepContext.java | 30 +++++++++ src/main/java/we/fizz/input/RequestInput.java | 14 ++-- src/main/java/we/fizz/input/ScriptHelper.java | 8 ++- src/main/java/we/legacy/RespEntity.java | 9 +++ src/main/java/we/util/WebUtils.java | 5 ++ 11 files changed, 170 insertions(+), 19 deletions(-) create mode 100644 src/main/java/we/exception/ExecuteScriptException.java diff --git a/js/context.js b/js/context.js index 7e278e0..0a31511 100644 --- a/js/context.js +++ b/js/context.js @@ -8,6 +8,12 @@ var context = { elapsedTimes: [{ [actionName]: 123, // 操作名称:耗时 }], + + // exception info + exceptionMessage: "", + exceptionStacks: "", + exceptionData: "", // such as script source code that cause exception + // 客户输入和接口的返回结果 input: { diff --git a/src/main/java/we/exception/ExecuteScriptException.java b/src/main/java/we/exception/ExecuteScriptException.java new file mode 100644 index 0000000..98bffd9 --- /dev/null +++ b/src/main/java/we/exception/ExecuteScriptException.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2020 the original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package we.exception; + +import we.fizz.StepContext; + +/** + * @author Francis + */ +public class ExecuteScriptException extends RuntimeException { + + private StepContext stepContext; + + private Object data; + + public ExecuteScriptException(String message, StepContext stepContext, Object data) { + super(message); + this.data = data; + this.stepContext = stepContext; + this.stepContext.setExceptionInfo(this, data); + } + + public ExecuteScriptException(Throwable cause, StepContext stepContext, Object data) { + super("execute script failed: " + cause.getMessage(), cause); + this.data = data; + this.stepContext = stepContext; + this.setStackTrace(cause.getStackTrace()); + this.stepContext.setExceptionInfo(this, data); + } + + /** + * + */ + private static final long serialVersionUID = 1L; + + public Object getData() { + return data; + } + + public void setData(Object data) { + this.data = data; + } + + public StepContext getStepContext() { + return stepContext; + } + + public void setStepContext(StepContext stepContext) { + this.stepContext = stepContext; + } + +} diff --git a/src/main/java/we/filter/FilterExceptionHandlerConfig.java b/src/main/java/we/filter/FilterExceptionHandlerConfig.java index 30b77aa..ac8eea1 100644 --- a/src/main/java/we/filter/FilterExceptionHandlerConfig.java +++ b/src/main/java/we/filter/FilterExceptionHandlerConfig.java @@ -29,8 +29,11 @@ import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.WebExceptionHandler; import reactor.core.publisher.Mono; +import we.exception.ExecuteScriptException; import we.exception.RedirectException; import we.exception.StopAndResponseException; +import we.legacy.RespEntity; +import we.util.JacksonUtils; import we.util.WebUtils; /** @@ -60,6 +63,21 @@ public class FilterExceptionHandlerConfig { resp.getHeaders().setLocation(URI.create(ex.getRedirectUrl())); return Mono.empty(); } + } + if (t instanceof ExecuteScriptException) { + ExecuteScriptException ex = (ExecuteScriptException) t; + ServerHttpResponse resp = exchange.getResponse(); + resp.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); + + RespEntity rs = null; + String reqId = exchange.getRequest().getId(); + if (ex.getStepContext() != null && ex.getStepContext().returnContext()) { + rs = new RespEntity(HttpStatus.INTERNAL_SERVER_ERROR.value(), t.getMessage(), reqId, ex.getStepContext()); + return resp.writeWith(Mono.just(resp.bufferFactory().wrap(JacksonUtils.writeValueAsString(rs).getBytes()))); + }else { + rs = new RespEntity(HttpStatus.INTERNAL_SERVER_ERROR.value(), t.getMessage(), reqId); + return resp.writeWith(Mono.just(resp.bufferFactory().wrap(rs.toString().getBytes()))); + } } Mono vm = WebUtils.responseError(exchange, filterExceptionHandler, HttpStatus.INTERNAL_SERVER_ERROR.value(), t.getMessage(), t); return vm; diff --git a/src/main/java/we/filter/FizzGatewayFilter.java b/src/main/java/we/filter/FizzGatewayFilter.java index 7c4860f..d6c4fc4 100644 --- a/src/main/java/we/filter/FizzGatewayFilter.java +++ b/src/main/java/we/filter/FizzGatewayFilter.java @@ -74,7 +74,7 @@ public class FizzGatewayFilter implements WebFilter { ServerHttpRequest request = exchange.getRequest(); ServerHttpResponse serverHttpResponse = exchange.getResponse(); - String path = WebUtils.PATH_PREFIX + WebUtils.getServiceId(exchange) + WebUtils.getReqPath(exchange); + String path = WebUtils.getPathPrefix(exchange) + WebUtils.getServiceId(exchange) + WebUtils.getReqPath(exchange); String method = request.getMethodValue(); AggregateResource aggregateResource = configLoader.matchAggregateResource(method, path); if (aggregateResource == null) { diff --git a/src/main/java/we/fizz/AggregateResult.java b/src/main/java/we/fizz/AggregateResult.java index 3f8abaa..72e4f50 100644 --- a/src/main/java/we/fizz/AggregateResult.java +++ b/src/main/java/we/fizz/AggregateResult.java @@ -29,9 +29,11 @@ import org.springframework.util.MultiValueMap; public class AggregateResult { private MultiValueMap headers; - + private Map body; + private StepContext stepContext; + public MultiValueMap getHeaders() { return headers; } @@ -48,4 +50,12 @@ public class AggregateResult { this.body = body; } + public StepContext getStepContext() { + return stepContext; + } + + public void setStepContext(StepContext stepContext) { + this.stepContext = stepContext; + } + } diff --git a/src/main/java/we/fizz/Pipeline.java b/src/main/java/we/fizz/Pipeline.java index 87baf7c..3fa6564 100644 --- a/src/main/java/we/fizz/Pipeline.java +++ b/src/main/java/we/fizz/Pipeline.java @@ -36,12 +36,14 @@ import com.alibaba.fastjson.JSON; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import we.exception.ExecuteScriptException; import we.fizz.input.ClientInputConfig; import we.fizz.input.Input; import we.fizz.input.InputConfig; import we.fizz.input.PathMapping; import we.fizz.input.ScriptHelper; import we.flume.clients.log4j2appender.LogService; +import we.util.JacksonUtils; import we.util.JsonSchemaUtils; import we.util.MapUtil; @@ -55,7 +57,7 @@ import we.util.MapUtil; public class Pipeline { private static final Logger LOGGER = LoggerFactory.getLogger(Pipeline.class); private LinkedList steps = new LinkedList(); - private StepContext stepContext= new StepContext<>(); + private StepContext stepContext = new StepContext<>(); public void addStep(Step step) { steps.add(step); } @@ -184,8 +186,8 @@ public class Pipeline { stepResponse.setResult(stepBody); } } catch (ScriptException e) { - LOGGER.warn("execute script failed, {}", e); - throw new RuntimeException("execute script failed"); + LOGGER.warn("execute script failed, {}", JacksonUtils.writeValueAsString(scriptCfg), e); + throw new ExecuteScriptException(e, stepContext, scriptCfg); } } } @@ -238,8 +240,8 @@ public class Pipeline { body.putAll((Map) respBody); } } catch (ScriptException e) { - LOGGER.warn("execute script failed, {}", e); - throw new RuntimeException("execute script failed"); + LOGGER.warn("execute script failed, {}", JacksonUtils.writeValueAsString(scriptCfg), e); + throw new ExecuteScriptException(e, stepContext, scriptCfg); } } response.put("body", body); @@ -249,7 +251,7 @@ public class Pipeline { Map respBody = (Map) response.get("body"); // 测试模式返回StepContext if(stepContext.returnContext()) { - respBody.put("_context", stepContext); + respBody.put(stepContext.CONTEXT_FIELD, stepContext); } aggResult.setBody((Map) response.get("body")); @@ -302,8 +304,8 @@ public class Pipeline { return errorList; } } catch (ScriptException e) { - LOGGER.warn("execute script failed", e); - throw new RuntimeException("execute script failed"); + LOGGER.warn("execute script failed, {}", JacksonUtils.writeValueAsString(scriptValidate), e); + throw new ExecuteScriptException(e, stepContext, scriptValidate); } } } diff --git a/src/main/java/we/fizz/StepContext.java b/src/main/java/we/fizz/StepContext.java index 342ad0c..dd8d296 100644 --- a/src/main/java/we/fizz/StepContext.java +++ b/src/main/java/we/fizz/StepContext.java @@ -39,6 +39,13 @@ public class StepContext extends ConcurrentHashMap { public static final String ELAPSED_TIMES = "elapsedTimes"; public static final String DEBUG = "debug"; public static final String RETURN_CONTEXT = "returnContext"; + // context field in response body + public static final String CONTEXT_FIELD = "_context"; + + // exception info + public static final String EXCEPTION_MESSAGE = "exceptionMessage"; + public static final String EXCEPTION_STACKS = "exceptionStacks"; + public static final String EXCEPTION_DATA = "exceptionData"; public void setDebug(Boolean debug) { this.put((K)DEBUG, (V)debug); @@ -68,6 +75,29 @@ public class StepContext extends ConcurrentHashMap { return Boolean.valueOf((String)getInputReqHeader(RETURN_CONTEXT)); } + /** + * set exception information + * @param cause exception + * @param exceptionData data that cause the exception, such as script source code, etc. + */ + public void setExceptionInfo(Throwable cause, Object exceptionData) { + this.put((K) EXCEPTION_MESSAGE, (V) cause.getMessage()); + this.put((K) EXCEPTION_DATA, (V) exceptionData); + + StackTraceElement[] stacks = cause.getStackTrace(); + if (stacks != null && stacks.length > 0) { + String[] arr = new String[stacks.length]; + for (int i = 0; i < stacks.length; i++) { + StackTraceElement ste = stacks[i]; + StringBuffer sb = new StringBuffer(); + sb.append(ste.getClassName()).append(".").append(ste.getMethodName()).append("(") + .append(ste.getFileName()).append(":").append(ste.getLineNumber()).append(")"); + arr[i] = sb.toString(); + } + this.put((K) EXCEPTION_STACKS, (V) arr); + } + } + public synchronized void addElapsedTime(String actionName, Long milliSeconds) { List> elapsedTimes = (List>) this.get(ELAPSED_TIMES); if (elapsedTimes == null) { diff --git a/src/main/java/we/fizz/input/RequestInput.java b/src/main/java/we/fizz/input/RequestInput.java index 0494390..bb1ea25 100644 --- a/src/main/java/we/fizz/input/RequestInput.java +++ b/src/main/java/we/fizz/input/RequestInput.java @@ -38,10 +38,12 @@ import com.alibaba.fastjson.JSON; import reactor.core.publisher.Mono; import we.FizzAppContext; import we.constants.CommonConstants; +import we.exception.ExecuteScriptException; import we.fizz.StepContext; import we.fizz.StepResponse; import we.flume.clients.log4j2appender.LogService; import we.proxy.FizzWebClient; +import we.util.JacksonUtils; import we.util.MapUtil; /** @@ -131,8 +133,8 @@ public class RequestInput extends Input { } } catch (ScriptException e) { LogService.setBizId(inputContext.getStepContext().getTraceId()); - LOGGER.warn("execute script failed, {}", e); - throw new RuntimeException("execute script failed"); + LOGGER.warn("execute script failed, {}", JacksonUtils.writeValueAsString(scriptCfg), e); + throw new ExecuteScriptException(e, stepContext, scriptCfg); } } request.put("body", body); @@ -187,8 +189,8 @@ public class RequestInput extends Input { } } catch (ScriptException e) { LogService.setBizId(inputContext.getStepContext().getTraceId()); - LOGGER.warn("execute script failed, {}", e); - throw new RuntimeException("execute script failed"); + LOGGER.warn("execute script failed, {}", JacksonUtils.writeValueAsString(scriptCfg), e); + throw new ExecuteScriptException(e, stepContext, scriptCfg); } } response.put("body", body); @@ -247,8 +249,8 @@ public class RequestInput extends Input { return needRun != null ? needRun : Boolean.TRUE; } catch (ScriptException e) { LogService.setBizId(inputContext.getStepContext().getTraceId()); - LOGGER.warn("execute script failed", e); - throw new RuntimeException("execute script failed"); + LOGGER.warn("execute script failed, {}", JacksonUtils.writeValueAsString(condition), e); + throw new ExecuteScriptException(e, stepContext, condition); } } diff --git a/src/main/java/we/fizz/input/ScriptHelper.java b/src/main/java/we/fizz/input/ScriptHelper.java index bd8ca9a..545ae46 100644 --- a/src/main/java/we/fizz/input/ScriptHelper.java +++ b/src/main/java/we/fizz/input/ScriptHelper.java @@ -32,9 +32,11 @@ import com.alibaba.fastjson.JSON; import org.springframework.util.StringUtils; import we.constants.CommonConstants; +import we.exception.ExecuteScriptException; import we.exception.RedirectException; import we.exception.StopAndResponseException; import we.fizz.StepContext; +import we.util.JacksonUtils; import we.util.Script; import we.util.ScriptUtils; @@ -118,8 +120,8 @@ public class ScriptHelper { PathMapping.setByPath(target, entry.getKey(), execute(scriptCfg, ctxNode, stepContext, clazz)); } } catch (ScriptException e) { - LOGGER.warn("execute script failed, {}", e); - throw new RuntimeException("execute script failed"); + LOGGER.warn("execute script failed, {}", JacksonUtils.writeValueAsString(scriptCfg), e); + throw new ExecuteScriptException(e, stepContext, scriptCfg); } } if(starEntryKey != null) { @@ -145,7 +147,7 @@ public class ScriptHelper { // 测试模式返回StepContext if (stepContext.returnContext()) { - rs.put("_context", stepContext); + rs.put(stepContext.CONTEXT_FIELD, stepContext); } // exception diff --git a/src/main/java/we/legacy/RespEntity.java b/src/main/java/we/legacy/RespEntity.java index 4a81034..a85d7af 100644 --- a/src/main/java/we/legacy/RespEntity.java +++ b/src/main/java/we/legacy/RespEntity.java @@ -37,12 +37,21 @@ public class RespEntity { public String message; public String reqId; + + public Object _context; public RespEntity(int code, String msg, @Nullable String reqId) { msgCode = code; message = msg; this.reqId = reqId; } + + public RespEntity(int code, String msg, @Nullable String reqId, Object stepContext) { + msgCode = code; + message = msg; + this.reqId = reqId; + this._context = stepContext; + } private static final String resb = "$resb"; diff --git a/src/main/java/we/util/WebUtils.java b/src/main/java/we/util/WebUtils.java index 43e43ea..775d10d 100644 --- a/src/main/java/we/util/WebUtils.java +++ b/src/main/java/we/util/WebUtils.java @@ -120,6 +120,11 @@ public abstract class WebUtils { } return svc; } + + public static String getPathPrefix(ServerWebExchange exchange) { + String p = exchange.getRequest().getPath().value(); + return p.substring(0, p.indexOf(getServiceId(exchange))); + } public static Mono getDirectResponse(ServerWebExchange exchange) { return (Mono) exchange.getAttributes().get(WebUtils.directResponse);