Spring实现统一捕获接口层异常与邮件警报

Java中提供的try{}catch{}finally{}来进行异常捕获,然后当系统业务复杂,代码量则多,为了排除系统错误,我们一般在接口层进行异常捕获,捕获到异常时打印日志,通过日志的方式来排除错误。系统越复杂,接口数量越多,如果我们对所有接口都try{}catch{}的话,那么工作量不仅会很大,以后系统维护难度也会变大。然后SpringMvc在框架层提供给了一种接口层统一处理异常的方法。

  • @ExceptionHandler:统一处理某一类异常,从而能够减少代码重复率和复杂度。
public abstract class AbstractController {

    protected Logger log = LoggerFactory.getLogger(this.getClass());

    @ExceptionHandler
    @ResponseBody
    public String handleException(HttpServletRequest request, HttpServletResponse response, Exception ex){
        if(!(ex instanceof NoNeedHandlerException)){
            log.error("{}:\n接口参数:{}\n{}", request.getRequestURI(), JSONObject.toJSONString(request.getParameterMap()),CommonMail.getFromException(ex));
            MonitorInfo monitorInfo = new MonitorInfo();
            monitorInfo.setMethodName(request.getRequestURI());
            monitorInfo.setParams(request.getParameterMap());
            monitorInfo.setException(ex);
            ExceptionMonitorHelper.monitor(monitorInfo);
        }else{
            log.info("{}:\n接口参数:{}\n{}", request.getRequestURI(), JSONObject.toJSONString(request.getParameterMap()), ex.getMessage());
        }
        if (ex instanceof HandlerException){
            HandlerException exception = (HandlerException)ex;
            return FmtResult(false, exception.getExceptionCode(), exception.getMessage(), "");
        }
        return FmtResult(false, ResultCode.CODE_ERROR, ResultCode.ERROR_MESSAGE, "");
    }
  }

只需要在handleException对异常进行进行处理即可,这样接口层的异常全部在这个地方统一处理,开发成本和维护成本大大减低。

  • 邮件监控与警报
    对于一些异常信息,一般都是通过打印日志的方式来记录,通过日志的方式去追溯错误。但是日志很多(尤其是日志打印不规范),很难定位,日志的及时性差,并不能及时通知到开发和运维人员线上错误,尤其是一些线上严重bug。在这里可以通过邮件警报的方式来对系统进行监控。
    具体实现如下:
/**
 * ZSQ 监控信息类
 */
public class MonitorInfo {

    private String methodName;

    private Map params;

    private Exception exception;

    public String getMethodName() {
        return methodName;
    }

    public void setMethodName(String methodName) {
        this.methodName = methodName;
    }

    public Map getParams() {
        return params;
    }

    public void setParams(Map params) {
        this.params = params;
    }

    public Exception getException() {
        return exception;
    }

    public void setException(Exception exception) {
        this.exception = exception;
    }
}
/**
 * ZSQ 2018年4月8日14:39:51
 * 异常监控类
 */
public class ExceptionMonitorHelper {

    private static final Logger logger = LoggerFactory.getLogger(ExceptionMonitorHelper.class);

    private static final LinkedBlockingQueue queue = new LinkedBlockingQueue<>(0xfff8);

    private Thread monitorThread;

    private volatile boolean isStop = false;

    public static String ENVIROMENT = null;


    public void start(){
        monitorThread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (!isStop) {
                    try {
                        //可以调用queue.drainTo来批量处理异常信息,提升读取速率,减少加锁和释放锁的次数
                        MonitorInfo moniterInfo = queue.take();
                        if (!(moniterInfo.getException() instanceof NoNeedHandlerException)) {
                            logger.debug("接口处理错误:{},{}", JSONObject.toJSONString(moniterInfo), moniterInfo.getException());
                            if (DEV_EMAILS != null) {
                                CommonMail.sendCodeWarnEmail(ENVIROMENT , moniterInfo, ExceptionLevel.SERIOUS);
                            }
                        }
                    } catch (Exception e) {
                        logger.error("exception monitor error {}", e);
                    }
                }
            }
        });
        monitorThread.setDaemon(true);//设置为守护线程 主线程退出时即退出
        monitorThread.start();
        logger.info("exception monitor start");
    }

    public void stop(){
        isStop = true;
        monitorThread.interrupt();
        try {
            monitorThread.join();
        } catch (InterruptedException e) {
            logger.error(e.getMessage(), e);
        }
    }

    public static void monitor(MonitorInfo failInfo){
        try {
            queue.put(failInfo);
        } catch (InterruptedException e) {
            logger.error("exception monitor put error{}", e);
        }
    }
}
/***
邮件工具类
**/
public class CommonMail {
    private static final String NAME = "网";
    private static final String USERNAME = "[email protected]";
    private static final String PASSWD = "Qi123456";
    private static String MAILHOST = "smtp.exmail.qq.com";// 发送邮件的主机
    private static int SMTPPORT = 465;
    private static boolean TLS = true;
    private static boolean DEBUG = true;
    private static boolean SSL = true;

    //开发者邮箱配置
    private static String DEV_USERNAME;
    private static String DEV_PASSWD;
    private static String DEV_NAME;
    public static String []DEV_EMAILS;

    static {
        MAILHOST = ConfigPropertyConfigurer.getContextProperty("mail.mailhost") == null ? "smtp.exmail.qq.com" : ConfigPropertyConfigurer.getContextProperty("mail.mailhost");
        SMTPPORT = ConfigPropertyConfigurer.getContextProperty("mail.smtpport") == null ? 465 : Integer.parseInt( ConfigPropertyConfigurer.getContextProperty("mail.smtpport"));
        TLS = ConfigPropertyConfigurer.getContextProperty("mail.tls") == null ? true : Boolean.parseBoolean(ConfigPropertyConfigurer.getContextProperty("mail.tls"));
        DEBUG = ConfigPropertyConfigurer.getContextProperty("mail.debug") == null ? false : Boolean.parseBoolean(ConfigPropertyConfigurer.getContextProperty("mail.debug"));
        SSL = ConfigPropertyConfigurer.getContextProperty("mail.ssl") == null ? false : Boolean.parseBoolean(ConfigPropertyConfigurer.getContextProperty("mail.ssl"));
        DEV_USERNAME = ConfigPropertyConfigurer.getContextProperty("dev.mail.username");
        DEV_PASSWD = ConfigPropertyConfigurer.getContextProperty("dev.mail.password");
        DEV_NAME = ConfigPropertyConfigurer.getContextProperty("dev.mail.name");
        DEV_EMAILS = ConfigPropertyConfigurer.getContextProperty("dev.mail.receivers").split(",");
    }

    public static void sendDevEmail(String[] receivers, String subject, String msg){
     System.setProperty("java.net.preferIPv4Stack", "true");
        final SimpleEmail email = new SimpleEmail();
        email.setTLS(TLS);
        email.setDebug(DEBUG);
        email.setSSL(SSL);
        email.setHostName(MAILHOST);
        email.setSmtpPort(SMTPPORT);
        email.setAuthenticator(new DefaultAuthenticator(DEV_USERNAME, DEV_PASSWD));
        try {
            email.setFrom(DEV_USERNAME, DEV_NAME);
            for(String receiver:receivers){
             email.addTo(receiver);
            }            
            email.setCharset("GB2312");
            email.setSubject(subject);
            email.setMsg(msg);
            new Thread(){           
                public void run() {
                    try {
                        email.send();
                    } catch (EmailException e) {                        
                        e.printStackTrace();
                    }   
                } 
            }.start();
        } catch (EmailException e) {
            e.printStackTrace();
        }
   } 

    public static void sendCodeWarnEmail(final String subject, final String msg, int type){    
        String sub = "系统警报";
        if(1 == type){
            sub += "严重错误";
        }else  if(2 == type){
            sub += "普通错误";
        }else  if(3 == type){
            sub += "非预期结果";
        }
        sub += subject;

        final String tsub = sub;        

        sendDevEmail(DEV_EMAILS, tsub, msg);         
    }

    public static void sendCodeWarnEmail(String enviroment, MonitorInfo monitorInfo, int type){
        String subject = "环境:"+(StringUtils.isBlank(enviroment)?"正式":enviroment)+"\n"+
                "方法名称:"+monitorInfo.getMethodName();

        String msg ="方法参数:\n";
        msg += (JSON.toJSON(monitorInfo.getParams())+"\n");
        msg += "异常信息:\n";
        msg += CommonMail.getFromException(monitorInfo.getException());
        CommonMail.sendCodeWarnEmail(subject, msg, type);
    }

    public static String getFromException(Exception e){
        StringWriter sw = new StringWriter();
        e.printStackTrace(new PrintWriter(sw, true));
        String str = sw.toString();
        return  str;
    }    
}

笔者只是抛砖引玉,大家有更好的方案或优化方法可以提出来,共同学习进步!!!Thank you!!!

你可能感兴趣的:(SpringMvc,异常监控)