SpringBoot AOP異常日誌處理
阿新 • • 發佈:2021-08-06
最近公司的一個專案需要將異常日誌通過企業微信進行告警,由於訊息推送已經有異常處理平臺進行處理,現在只需要捕獲異常資訊,將資訊傳送到異常處理平臺就可以了。可以選擇的方案其實有兩種,一個是springboot其實有全域性異常處理,捕獲到異常後可以進行訊息推送。另一個就是通過AOP進行處理。因為全域性異常處理不夠靈活,比如不同的方法可能需要處理的異常型別不同,對不同的方法可能會有特殊的處理等等,最終選擇了使用AOP+註解的方式進行異常日誌的處理。
異常日誌註解
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface ExceptionLogService { /** * 系統模組名 * @return */ String moduleName() default ""; /** * 業務名稱 * @return */ String businessName() default ""; /** * 日誌字首 * @return */ String messagePrefix() default ""; /** * 需要推送訊息的異常型別 * @return */ Class<? extends Throwable>[] exception() default {}; }
引數moduleName
和businessName
主要是為了定位異常位置,可寫可不寫,messagePrefix
可以理解為對異常的說明,同時可以指定需要進行異常日誌推送的異常型別,如果不指定則所有的異常都會進行推送。
切面類
@Aspect @Component public class ExceptionLogServiceAspect implements Ordered { private final static Logger logger= LoggerFactory.getLogger(ExceptionLogServiceAspect.class); @Autowired MessageService messageService; @Pointcut("@annotation(com.test.ExceptionLogService)") public void exceptionLogService(){ } @Around("exceptionLogService()") public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { Object result=null; Throwable exception=null; MethodSignature signature =(MethodSignature) proceedingJoinPoint.getSignature(); ExceptionLogService annotation = signature.getMethod().getAnnotation(ExceptionLogService.class); String className = proceedingJoinPoint.getTarget().getClass().getSimpleName(); String methodName = signature.getName(); try { result = proceedingJoinPoint.proceed(); } catch (Throwable throwable) { //獲取需要推送訊息的異常型別 exception = getLogException(annotation, throwable); //構建訊息內容 String logContent = buildLogContent(annotation,className,methodName, exception); //推送訊息到異常處理平臺 messageService.sendErrorMessage(logContent); //訊息推送完成後將異常丟擲,因為對於異常有另外的AOP進行處理,所以只是處理異常日誌,然後直接丟擲 exception=throwable; }finally { if (exception!=null){ throw exception; } } return result; } /** * 根據ExceptionLogService 註解中的異常型別陣列獲取需要推送訊息的異常型別,如果陣列為空則所有的異常都進行訊息推送 * @param annotation * @param throwable * @return */ private Throwable getLogException( ExceptionLogService annotation,Throwable throwable){ Class<?>[] exceptions = annotation.exception(); Throwable exception=null; if (exceptions==null || exceptions.length==0){ exception=throwable; }else { for (Class<?> clazz : exceptions) { if (clazz.isInstance(throwable)){ exception=throwable; break; } } } return exception; } /** * 構建異常訊息內容 * @param annotation ExceptionLogService註解 * @param className 發生異常的類名稱 * @param methodName 發生異常的方法名稱 * @param throwable 丟擲異常 * @return 訊息內容 */ private String buildLogContent(ExceptionLogService annotation, String className, String methodName, Throwable throwable){ if (annotation==null||throwable==null){ return null; } String moduleName = annotation.moduleName(); String businessName = annotation.businessName(); String messagePrefix = annotation.messagePrefix(); StringBuilder stringBuilder = new StringBuilder(); if (StringUtils.isNotBlank(moduleName)){ stringBuilder.append(moduleName).append(" 模組,"); } if (StringUtils.isNotBlank(businessName)){ stringBuilder.append(businessName).append(" 業務"); } //如果異常訊息為空,展示異常型別 String message = throwable.getMessage(); boolean isNull=false; if (StringUtils.isBlank(message)){ message = throwable.getClass().getSimpleName(); isNull=true; } stringBuilder.append("發生異常:").append(StringUtils.isBlank(messagePrefix) ? "" : messagePrefix); stringBuilder.append(isNull ? " 異常型別為:" : " 異常訊息為:") .append(message).append("。異常位置:").append(className) .append(".class >>> ").append(methodName).append("()"); String logContent = stringBuilder.toString(); logger.error("AOP異常日誌:{}",logContent); return logContent; } @Override public int getOrder() { return 1; } }
異常訊息格式
moduleName 模組,businessName 業務發生異常,異常訊息為:異常訊息。異常位置:類名.class>>>方法名()
對於異常訊息為空的異常將展示異常的型別。
測試
@ExceptionLogService(moduleName = "測試模組",businessName = "test",exception = {NullPointerException.class}) @Override public String logServiceTest() throws Exception { logger.info("testLog"); throw new NullPointerException(); return "test"; }