前言
在项目中实际运行中会出现一些意外bug,通常情况是用户通过客服电话进行反馈,为了提升用户的满意度和快速定位异常信息,在操作发生Exception时记录、报错类名. 方法名. 行号、堆栈信息、当前操作人、traceId、接口url、接口参数等等。
正文
- 创建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
);
}
}
- 拿到请求参数这里get方法可以直接从HttpServletRequest.getQueryString中获取,但是post请求拿不到
- 解决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);
}
}
- 通过RequestWrapperFilter 拿到post请求的参数
ContentCachingRequestWrapper wrapper = (ContentCachingRequestWrapper) request;
String jsonParamsStr = StrUtil.str(wrapper.getContentAsByteArray(), wrapper.getCharacterEncoding());
- 通过ApplicationEvent时间发布异步进行存储处理
表结构设计
COLUMN_NAME | COMMENTS | TC.DATA_TYPE||‘(’||DATA_LENGTH||‘)’ |
---|---|---|
ID | 主键ID | VARCHAR(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_VALUE | hash值 | VARCHAR(64) |