1. 程式人生 > 其它 >SpringBoot AOP異常日誌處理

SpringBoot AOP異常日誌處理

最近公司的一個專案需要將異常日誌通過企業微信進行告警,由於訊息推送已經有異常處理平臺進行處理,現在只需要捕獲異常資訊,將資訊傳送到異常處理平臺就可以了。可以選擇的方案其實有兩種,一個是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 {};
}

引數moduleNamebusinessName主要是為了定位異常位置,可寫可不寫,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";
    }