Sping boot 实现全局拦截异常处理并存储

Sping boot 实现全局拦截异常处理并存储,让异常定位更方便!

前言

在项目中实际运行中会出现一些意外bug,通常情况是用户通过客服电话进行反馈,为了提升用户的满意度和快速定位异常信息,在操作发生Exception时记录、报错类名. 方法名. 行号、堆栈信息、当前操作人、traceId、接口url、接口参数等等。

正文

  1. 创建ExceptionAdvice类使用@RestControllerAdvice注解配合@ExceptionHandler(value = Exception.class)拦截所有Controller抛出的Exception
@Slf4j
@RestControllerAdvice
public class ExceptionAdvice {

    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;

    @ExceptionHandler(value = Exception.class)
    public ApiResultDTO catchBusinessException(Exception e, HttpServletRequest request) {
        Throwable cause = e.getCause();
        StackTraceElement[] stackTrace;
        if (null != cause) {
            stackTrace = cause.getStackTrace();
        } else {
            stackTrace = e.getStackTrace();
        }

        List<StackTraceElement> stackTraceList = Arrays.stream(stackTrace).filter(st -> st.getClassName().startsWith("com.boss")).collect(Collectors.toList());
        String className;
        String methodName;
        Integer lineNumber;
        if (CollUtil.isNotEmpty(stackTraceList)) {
            className = stackTraceList.get(0).getClassName();
            methodName = stackTraceList.get(0).getMethodName();
            lineNumber = stackTraceList.get(0).getLineNumber();
            stackTraceList.stream().forEach(ex -> log.error(
                    "######## uri:{} traceId:{} class:{}, method:{}, line:{} ########",
                    request.getRequestURI(),
                    MDC.get(ConstantConfig.TRACE_ID),
                    ex.getClassName(),
                    ex.getMethodName(),
                    ex.getLineNumber()
            ));

            ErrorLogEventDTO errorLogEventDTO = new ErrorLogEventDTO();
            errorLogEventDTO.setUser(BudgetUtils.getCurrentUser());
            errorLogEventDTO.setTraceId(MDC.get(ConstantConfig.TRACE_ID));
            errorLogEventDTO.setClassMethod(className + "." + methodName + "." + lineNumber);
            errorLogEventDTO.setUrl(request.getRequestURI());
            errorLogEventDTO.setErrorType("1");
            errorLogEventDTO.setErrorStack(JSON.toJSONString(stackTraceList));
            errorLogEventDTO.setResponse(ExceptionUtil.getRootCauseMessage(e));
            if ("POST".equalsIgnoreCase(request.getMethod()) && request instanceof ContentCachingRequestWrapper) {
                ContentCachingRequestWrapper wrapper = (ContentCachingRequestWrapper) request;
                String jsonParamsStr = StrUtil.str(wrapper.getContentAsByteArray(), wrapper.getCharacterEncoding());
                errorLogEventDTO.setParams(jsonParamsStr.replace("\n", "").replace(" ", ""));
            } else if ("GET".equalsIgnoreCase(request.getMethod())) {
                errorLogEventDTO.setParams(request.getQueryString());
            }
            applicationEventPublisher.publishEvent(new ErrorLogEvent(errorLogEventDTO));
        }
        String rootCauseMessage = ExceptionUtil.getRootCauseMessage(e);
        String unUseStr = "Exception";
        if (rootCauseMessage.contains(unUseStr)) {
            int i = rootCauseMessage.lastIndexOf(unUseStr);
            rootCauseMessage = rootCauseMessage.substring(++i + unUseStr.length());
        }
        return ApiResultDTO.error(ApiResultDTO.RS_118000,
                "[" + rootCauseMessage + "]",
                e
        );
    }

    @ExceptionHandler(value = {MethodArgumentNotValidException.class,})
    public ApiResultDTO<Object> methodArgumentNotValidExceptionHandler(HttpServletRequest request, MethodArgumentNotValidException e) {
        log.error("-->request_uri: {}, {}", request.getRequestURI(), e);
        Map<String, String> data = new HashMap<>();
        e.getBindingResult().getFieldErrors().forEach(fieldError ->
                data.put(fieldError.getField(), fieldError.getDefaultMessage()));
        String s = null;
        try {
            s = JsonUtil.mapToJson(data);
        } catch (JsonProcessingException ex) {
            log.error("-->JsonUtil.mapToJson() happened exception, e = ", ex);
        }
        return ApiResultDTO.error(ApiResultDTO.RS_118000,
                "参数不合法 | " + s
        );
    }

}

  1. 拿到请求参数这里get方法可以直接从HttpServletRequest.getQueryString中获取,但是post请求拿不到
  2. 解决post参数拿不到的问题,创建 RequestWrapperFilter
/**
 * 通过过滤器将ServerletRequest封装成ContentCachingRequestWrapper,body被读取后,会被它缓存。
 * @version 1.0
 * @date 2024-03-28 16:04
 */
@Component
public class RequestWrapperFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
        filterChain.doFilter(new ContentCachingRequestWrapper(httpServletRequest), httpServletResponse);
    }
}

  1. 通过RequestWrapperFilter 拿到post请求的参数
ContentCachingRequestWrapper wrapper = (ContentCachingRequestWrapper) request;
                String jsonParamsStr = StrUtil.str(wrapper.getContentAsByteArray(), wrapper.getCharacterEncoding());

  1. 通过ApplicationEvent时间发布异步进行存储处理

表结构设计

COLUMN_NAMECOMMENTSTC.DATA_TYPE||‘(’||DATA_LENGTH||‘)’
ID主键IDVARCHAR(64)
TRACE_ID跟踪ID(若有)VARCHAR(64)
FISCAL_YEAR预算年度VARCHAR(8)
PARAMS调用参数(若有)TEXT(2147483647)
URL调用参数(若有)VARCHAR(256)
ERROR_TYPE类型CHAR(1)
ERROR_STACK错误堆栈TEXT(2147483647)
TIME_CONSUMING耗时INT(4)
OPERATOR操作人VARCHAR(64)
HAPPEN_TIME发生时间VARCHAR(25)
HAPPEN_PLACE发生位置VARCHAR2(512)
API_URL第三方api的URL(若有)VARCHAR2(512)
API_PARAMS第三方api参数(若有)VARCHAR2(8188)
API_RESPONSE第三方api返回值(若有)VARCHAR2(512)
HASH_VALUEhash值VARCHAR(64)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值