From b48e60e33709da398db5b1d25c84121a4e26b61c Mon Sep 17 00:00:00 2001 From: wangli <274027703@qq.com> Date: Tue, 25 Jun 2024 18:54:36 +0800 Subject: [PATCH] =?UTF-8?q?feat(REQ-2516):=20=E4=B8=B4=E6=97=B6=E6=8F=90?= =?UTF-8?q?=E4=BA=A4,=E6=9C=AA=E6=B5=8B=E9=80=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ExceptionHandlerAutoConfiguration.java | 6 - .../support/GlobalExceptionHandler.java | 128 +++++++++++++++- .../BasicRecordExceptionHandler.java | 137 ------------------ .../FeignRecordExceptionInterceptor.java | 20 ++- .../filter/BasicRecordExceptionFilter.java | 5 +- 5 files changed, 137 insertions(+), 159 deletions(-) delete mode 100644 axzo-common-web/src/main/java/cn.axzo.framework.web/exception/BasicRecordExceptionHandler.java diff --git a/axzo-common-autoconfigure/src/main/java/cn/axzo/framework/autoconfigure/web/exception/ExceptionHandlerAutoConfiguration.java b/axzo-common-autoconfigure/src/main/java/cn/axzo/framework/autoconfigure/web/exception/ExceptionHandlerAutoConfiguration.java index 67c2290..7627737 100644 --- a/axzo-common-autoconfigure/src/main/java/cn/axzo/framework/autoconfigure/web/exception/ExceptionHandlerAutoConfiguration.java +++ b/axzo-common-autoconfigure/src/main/java/cn/axzo/framework/autoconfigure/web/exception/ExceptionHandlerAutoConfiguration.java @@ -8,7 +8,6 @@ import cn.axzo.framework.autoconfigure.web.exception.resolver.internal.RequestRe import cn.axzo.framework.autoconfigure.web.exception.support.GlobalErrorController; import cn.axzo.framework.autoconfigure.web.exception.support.GlobalExceptionHandler; import cn.axzo.framework.domain.web.result.Result; -import cn.axzo.framework.web.exception.BasicRecordExceptionHandler; import cn.axzo.framework.web.feign.FeignRecordExceptionInterceptor; import cn.axzo.framework.web.filter.BasicRecordExceptionFilter; import feign.RequestInterceptor; @@ -199,11 +198,6 @@ public class ExceptionHandlerAutoConfiguration implements WebMvcConfigurer { return new FeignRecordExceptionInterceptor(); } - @Bean - public BasicRecordExceptionHandler basicRecordExceptionHandler() { - return new BasicRecordExceptionHandler(); - } - @Bean @ConditionalOnClass(MDC.class) public OncePerRequestFilter oncePerRequestFilter() { diff --git a/axzo-common-autoconfigure/src/main/java/cn/axzo/framework/autoconfigure/web/exception/support/GlobalExceptionHandler.java b/axzo-common-autoconfigure/src/main/java/cn/axzo/framework/autoconfigure/web/exception/support/GlobalExceptionHandler.java index b5739d8..3deba1a 100644 --- a/axzo-common-autoconfigure/src/main/java/cn/axzo/framework/autoconfigure/web/exception/support/GlobalExceptionHandler.java +++ b/axzo-common-autoconfigure/src/main/java/cn/axzo/framework/autoconfigure/web/exception/support/GlobalExceptionHandler.java @@ -10,7 +10,14 @@ import cn.axzo.framework.domain.web.result.Result; import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.exc.InvalidFormatException; +import com.google.common.collect.Lists; +import lombok.Data; +import lombok.Getter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; import org.springframework.beans.TypeMismatchException; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.web.ErrorProperties; import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.web.servlet.error.ErrorAttributes; @@ -19,6 +26,8 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.stereotype.Controller; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; import org.springframework.validation.BindException; import org.springframework.validation.FieldError; import org.springframework.web.bind.MethodArgumentNotValidException; @@ -34,12 +43,22 @@ import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExcep import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; +import java.util.stream.Collectors; +import static cn.axzo.framework.domain.web.result.ApiResult.err; +import static cn.axzo.framework.web.filter.BasicRecordExceptionFilter.MICRO_SERVER_RECORD_ERROR_FILTER_PACKAGE_VALUE; +import static cn.axzo.framework.web.filter.BasicRecordExceptionFilter.MICRO_SERVER_RECORD_ERROR_GET_PARAM_NAME; import static java.lang.String.format; import static jodd.util.StringUtil.isNotBlank; +import static org.springframework.http.HttpHeaders.CACHE_CONTROL; /** * @Description spring mvc全局异常捕获 @@ -48,6 +67,7 @@ import static jodd.util.StringUtil.isNotBlank; **/ @RestControllerAdvice(annotations = {Controller.class, RestController.class}) public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { + private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class); private final List> handlers; @@ -55,6 +75,18 @@ public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { private final ErrorAttributes errorAttributes; + public static final String CTX_LOG_ID = "ctxLogId"; + /** + * 多设置一个key = TraceId, value为traceId的变量到MDC. 以兼容目前的logback-spring.xml的配置 + */ + public static final String TRACE_ID_IN_MDC = "traceId"; + + + @Value("${axzo.framework.debugging:X-Metadata-Tag}") + private String recordExceptionHeaderName; + @Value("${spring.application.name:}") + private String applicationName; + public GlobalExceptionHandler(List> handlers, ErrorAttributes errorAttributes, ServerProperties properties) { @@ -85,7 +117,7 @@ public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { } // 3.响应 - return ApiResult.err(Integer.parseInt(code), message); + return err(Integer.parseInt(code), message); }); return super.handleExceptionInternal(ex, result, headers, status, request); @@ -111,7 +143,7 @@ public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { } // 3.响应 - return ApiResult.err(Integer.parseInt(code), message); + return err(Integer.parseInt(code), message); }); return super.handleExceptionInternal(ex, result, headers, status, request); } @@ -136,7 +168,7 @@ public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { } // 3.响应 - return ApiResult.err(Integer.parseInt(code), message); + return err(Integer.parseInt(code), message); }); return super.handleExceptionInternal(ex, result, headers, status, request); } @@ -162,7 +194,7 @@ public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { } // 3.响应 - return ApiResult.err(Integer.parseInt(code), message); + return err(Integer.parseInt(code), message); }); return super.handleExceptionInternal(ex, result, headers, status, request); } @@ -185,7 +217,7 @@ public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { message = "Required parameter '" + field + "' is not present"; // 3.响应 - return ApiResult.err(Integer.parseInt(code), message); + return err(Integer.parseInt(code), message); }); return super.handleExceptionInternal(ex, result, headers, status, request); } @@ -230,7 +262,7 @@ public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { } // 3.响应 - return ApiResult.err(Integer.parseInt(code), message); + return err(Integer.parseInt(code), message); }); return super.handleExceptionInternal(ex, result, headers, status, request); } @@ -242,6 +274,9 @@ public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { @ExceptionHandler(Throwable.class) public Result handleAllException(HttpServletRequest request, HttpServletResponse response, Throwable e) { + if (shouldRecordException(request)) { + return buildResult(request, response, e); + } Map attributes = Requests.from(request).getErrorAttributes(errorAttributes, errorProperties); return _handleException(request, response, e, attributes) .orElseGet(() -> handlers.stream() @@ -252,6 +287,16 @@ public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { ); } + private Result buildResult(HttpServletRequest request, HttpServletResponse response, Throwable e) { + response.setHeader(CACHE_CONTROL, "no-store"); + response.setStatus(251); + ApiResult err = err(251, "discovery server internal error"); + Map recordExceptionMap = new HashMap<>(); + recordExceptionMap.put(StringUtils.hasText(applicationName) ? applicationName : "not applicationName found", rebuildThrowable(e, request)); + err.setData(recordExceptionMap); + return err; + } + private Optional _handleException(WebRequest webRequest, Throwable e) { Map attributes = Requests.from(webRequest).getErrorAttributes(errorAttributes, errorProperties); if (webRequest instanceof ServletWebRequest) { @@ -279,10 +324,81 @@ public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { } } + private boolean shouldRecordException(HttpServletRequest request) { + String headerValue = request.getHeader(recordExceptionHeaderName); + if (!StringUtils.hasText(headerValue)) { + headerValue = MDC.get(recordExceptionHeaderName); + } + log.info("recordException HeaderName: {}", headerValue); + return Boolean.parseBoolean(headerValue); + } + private boolean shouldFilter(ExceptionResultHandler handler, Throwable e) { if (handler.isRecursive()) { return handler.getExceptionClass().isAssignableFrom(e.getClass()); } return handler.getExceptionClass() == e.getClass(); } + + private ExceptionWrapper rebuildThrowable(Throwable throwable, HttpServletRequest request) { + return new ExceptionWrapper(throwable, request); + } + + @Getter + static class ExceptionWrapper implements Serializable { + private final String traceId = getOutsideTraceId(); + private final String causeMessage; + private final List stackTrace = new ArrayList<>(); + + public ExceptionWrapper(Throwable throwable, HttpServletRequest request) { + this.causeMessage = throwable.getMessage(); + init(throwable, request); + } + + private void init(Throwable throwable, HttpServletRequest request) { + List elements = Arrays.stream(throwable.getStackTrace()).collect(Collectors.toList()); + if (CollectionUtils.isEmpty(elements)) { + return; + } + String[] parameterValues = request.getParameterValues(MICRO_SERVER_RECORD_ERROR_GET_PARAM_NAME); + List filterPackageNames = Lists.newArrayList(MICRO_SERVER_RECORD_ERROR_FILTER_PACKAGE_VALUE); + if (Objects.nonNull(parameterValues) && parameterValues.length > 0) { + filterPackageNames = Arrays.asList(parameterValues); + } else if (StringUtils.hasText(MDC.get(MICRO_SERVER_RECORD_ERROR_GET_PARAM_NAME))) { + filterPackageNames = Arrays.asList(StringUtils.commaDelimitedListToStringArray(MDC.get(MICRO_SERVER_RECORD_ERROR_GET_PARAM_NAME))); + } + for (StackTraceElement e : elements) { + if (StringUtils.hasText(e.getClassName())) { + filterPackageNames.stream().filter(i -> e.getClassName().contains(i)).findAny().ifPresent(t -> { + StackTraceWrapper wrapper = new StackTraceWrapper(); + wrapper.setClassName(e.getClassName()); + wrapper.setMethodName(e.getMethodName()); + wrapper.setLineNumber(e.getLineNumber()); + stackTrace.add(wrapper.toString()); + }); + } + } + } + } + + @Data + static class StackTraceWrapper implements Serializable { + private String className; + private String methodName; + private Integer lineNumber; + + @Override + public String toString() { + return className + "#" + methodName + ":" + lineNumber; + } + } + + + private static String getOutsideTraceId() { + String res = MDC.get(TRACE_ID_IN_MDC); + if (Objects.isNull(res)) { + res = MDC.get(CTX_LOG_ID); + } + return res; + } } diff --git a/axzo-common-web/src/main/java/cn.axzo.framework.web/exception/BasicRecordExceptionHandler.java b/axzo-common-web/src/main/java/cn.axzo.framework.web/exception/BasicRecordExceptionHandler.java deleted file mode 100644 index ab61b7e..0000000 --- a/axzo-common-web/src/main/java/cn.axzo.framework.web/exception/BasicRecordExceptionHandler.java +++ /dev/null @@ -1,137 +0,0 @@ -package cn.axzo.framework.web.exception; - -import cn.axzo.framework.domain.web.result.ApiResult; -import com.google.common.collect.Lists; -import lombok.Data; -import lombok.Getter; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.slf4j.MDC; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.core.PriorityOrdered; -import org.springframework.util.CollectionUtils; -import org.springframework.util.StringUtils; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.bind.annotation.ResponseBody; -import org.springframework.web.bind.annotation.RestControllerAdvice; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.stream.Collectors; - -/** - * 最高优先级的异常处理器,主要是记录应用的异常,并抛出 - * - * @author wangli - * @since 2024/6/24 09:43 - */ -@RestControllerAdvice -@ResponseBody -public final class BasicRecordExceptionHandler implements PriorityOrdered { - private static final Logger log = LoggerFactory.getLogger(BasicRecordExceptionHandler.class); - public static final String CTX_LOG_ID = "ctxLogId"; - /** - * 多设置一个key = TraceId, value为traceId的变量到MDC. 以兼容目前的logback-spring.xml的配置 - */ - public static final String TRACE_ID_IN_MDC = "traceId"; - public static final String MICRO_SERVER_RECORD_ERROR_HEADER_NAME = "X-Metadata-Tag"; - public static final String MICRO_SERVER_RECORD_ERROR_GET_PARAM_NAME = "pkg"; - public static final String MICRO_SERVER_RECORD_ERROR_FILTER_PACKAGE_VALUE = "cn.axzo"; - - - @Value("${axzo.framework.debugging:X-Metadata-Tag}") - private String recordExceptionHeaderName; - @Value("${spring.application.name:}") - private String applicationName; - - @Override - public int getOrder() { - return Integer.MIN_VALUE; - } - - @ExceptionHandler(value = Throwable.class) - public ApiResult globalException(HttpServletRequest request, HttpServletResponse response, Throwable e) throws Throwable { - String headerValue = request.getHeader(recordExceptionHeaderName); - if (!StringUtils.hasText(headerValue)) { - headerValue = MDC.get(recordExceptionHeaderName); - } - log.debug("recordException HeaderName: {}", headerValue); - if (Boolean.parseBoolean(headerValue)) { - ApiResult error = ApiResult.err(500, "discovery server internal error"); - Map recordExceptionMap = new HashMap<>(); - recordExceptionMap.put(StringUtils.hasText(applicationName) ? applicationName : "not applicationName found", e); - error.setData(rebuildThrowable(e, request)); - return error; - } - // 重新抛出 - throw e; - } - - private ExceptionWrapper rebuildThrowable(Throwable throwable, HttpServletRequest request) { - return new ExceptionWrapper(throwable, request); - } - - @Getter - static class ExceptionWrapper implements Serializable { - private final String traceId = getOutsideTraceId(); - private final String causeMessage; - private final List stackTrace = new ArrayList<>(); - - public ExceptionWrapper(Throwable throwable, HttpServletRequest request) { - this.causeMessage = throwable.getMessage(); - init(throwable, request); - } - - private void init(Throwable throwable, HttpServletRequest request) { - List elements = Arrays.stream(throwable.getStackTrace()).collect(Collectors.toList()); - if (CollectionUtils.isEmpty(elements)) { - return; - } - String[] parameterValues = request.getParameterValues(MICRO_SERVER_RECORD_ERROR_GET_PARAM_NAME); - List filterPackageNames = Lists.newArrayList(MICRO_SERVER_RECORD_ERROR_FILTER_PACKAGE_VALUE); - if (Objects.nonNull(parameterValues) && parameterValues.length > 0) { - filterPackageNames = Arrays.asList(parameterValues); - } else if (StringUtils.hasText(MDC.get(MICRO_SERVER_RECORD_ERROR_GET_PARAM_NAME))) { - filterPackageNames = Arrays.asList(StringUtils.commaDelimitedListToStringArray(MDC.get(MICRO_SERVER_RECORD_ERROR_GET_PARAM_NAME))); - } - for (StackTraceElement e : elements) { - if (StringUtils.hasText(e.getClassName())) { - filterPackageNames.stream().filter(i -> e.getClassName().contains(i)).findAny().ifPresent(t -> { - StackTraceWrapper wrapper = new StackTraceWrapper(); - wrapper.setClassName(e.getClassName()); - wrapper.setMethodName(e.getMethodName()); - wrapper.setLineNumber(e.getLineNumber()); - stackTrace.add(wrapper.toString()); - }); - } - } - } - } - - @Data - static class StackTraceWrapper implements Serializable { - private String className; - private String methodName; - private Integer lineNumber; - - @Override - public String toString() { - return className + "#" + methodName + ":" + lineNumber; - } - } - - private static String getOutsideTraceId() { - String res = MDC.get(TRACE_ID_IN_MDC); - if (Objects.isNull(res)) { - res = MDC.get(CTX_LOG_ID); - } - return res; - } -} diff --git a/axzo-common-web/src/main/java/cn.axzo.framework.web/feign/FeignRecordExceptionInterceptor.java b/axzo-common-web/src/main/java/cn.axzo.framework.web/feign/FeignRecordExceptionInterceptor.java index 18487e8..4a9ca6d 100644 --- a/axzo-common-web/src/main/java/cn.axzo.framework.web/feign/FeignRecordExceptionInterceptor.java +++ b/axzo-common-web/src/main/java/cn.axzo.framework.web/feign/FeignRecordExceptionInterceptor.java @@ -12,8 +12,9 @@ import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; import java.util.Objects; -import static cn.axzo.framework.web.exception.BasicRecordExceptionHandler.MICRO_SERVER_RECORD_ERROR_GET_PARAM_NAME; -import static cn.axzo.framework.web.exception.BasicRecordExceptionHandler.MICRO_SERVER_RECORD_ERROR_HEADER_NAME; +import static cn.axzo.framework.web.filter.BasicRecordExceptionFilter.MICRO_SERVER_RECORD_ERROR_GET_PARAM_NAME; +import static cn.axzo.framework.web.filter.BasicRecordExceptionFilter.MICRO_SERVER_RECORD_ERROR_HEADER_NAME; + /** * 用于 Feign 调用时,传递外部 HTTP 请求中携带的特殊 Header @@ -27,9 +28,6 @@ public class FeignRecordExceptionInterceptor implements RequestInterceptor { @Override public void apply(RequestTemplate template) { HttpServletRequest originalRequest = getOriginalRequest(); - if (Objects.isNull(originalRequest)) { - return; - } setRequestParams(template, originalRequest); @@ -37,7 +35,10 @@ public class FeignRecordExceptionInterceptor implements RequestInterceptor { } private void setRequestParams(RequestTemplate template, HttpServletRequest originalRequest) { - String[] packageNames = originalRequest.getParameterValues(MICRO_SERVER_RECORD_ERROR_GET_PARAM_NAME); + String[] packageNames = null; + if(Objects.nonNull(originalRequest)) { + packageNames = originalRequest.getParameterValues(MICRO_SERVER_RECORD_ERROR_GET_PARAM_NAME); + } if (Objects.isNull(packageNames) || packageNames.length == 0) { packageNames = StringUtils.commaDelimitedListToStringArray(MDC.get(MICRO_SERVER_RECORD_ERROR_GET_PARAM_NAME)); } @@ -45,8 +46,11 @@ public class FeignRecordExceptionInterceptor implements RequestInterceptor { } private static void setXMetaDataTag(RequestTemplate template, HttpServletRequest originalRequest) { - // 需要传递外部传入的标识 - String headerValue = originalRequest.getHeader(MICRO_SERVER_RECORD_ERROR_HEADER_NAME); + String headerValue = null; + if(Objects.nonNull(originalRequest)) { + // 需要传递外部传入的标识 + headerValue = originalRequest.getHeader(MICRO_SERVER_RECORD_ERROR_HEADER_NAME); + } if (!StringUtils.hasText(headerValue)) { headerValue = MDC.get(MICRO_SERVER_RECORD_ERROR_HEADER_NAME); } diff --git a/axzo-common-web/src/main/java/cn.axzo.framework.web/filter/BasicRecordExceptionFilter.java b/axzo-common-web/src/main/java/cn.axzo.framework.web/filter/BasicRecordExceptionFilter.java index 8529a05..0f18ee2 100644 --- a/axzo-common-web/src/main/java/cn.axzo.framework.web/filter/BasicRecordExceptionFilter.java +++ b/axzo-common-web/src/main/java/cn.axzo.framework.web/filter/BasicRecordExceptionFilter.java @@ -12,8 +12,6 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; -import static cn.axzo.framework.web.exception.BasicRecordExceptionHandler.MICRO_SERVER_RECORD_ERROR_GET_PARAM_NAME; -import static cn.axzo.framework.web.exception.BasicRecordExceptionHandler.MICRO_SERVER_RECORD_ERROR_HEADER_NAME; /** * TODO @@ -22,6 +20,9 @@ import static cn.axzo.framework.web.exception.BasicRecordExceptionHandler.MICRO_ * @since 2024/6/25 09:40 */ public class BasicRecordExceptionFilter extends OncePerRequestFilter implements PriorityOrdered { + public static final String MICRO_SERVER_RECORD_ERROR_HEADER_NAME = "X-Metadata-Tag"; + public static final String MICRO_SERVER_RECORD_ERROR_GET_PARAM_NAME = "pkg"; + public static final String MICRO_SERVER_RECORD_ERROR_FILTER_PACKAGE_VALUE = "cn.axzo"; @Override public int getOrder() { return Ordered.HIGHEST_PRECEDENCE;