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

标签: SpringMvc 异常监控 邮件警报
36人阅读 评论(0) 收藏 举报
分类:

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<String, ?> params;

    private Exception exception;

    public String getMethodName() {
        return methodName;
    }

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

    public Map<String, ?> getParams() {
        return params;
    }

    public void setParams(Map<String, ?> 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<MonitorInfo> 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 = "system@qq.com";
    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!!!

查看评论

Spring MVC使用之统一异常捕获

在使用spring mvc开发时,在Controller中一定会遇到各种异常,如果在每个可能出现异常的地方都对异常进行捕获,这样不仅工作量大不利于提高开发效率,还会对项目的维护产生负面影响,还好spr...
  • u010351766
  • u010351766
  • 2016年07月09日 11:06
  • 2230

Spring MVC 全局异常处理-RESTAPI接口返回统一JSON格式-自定义异常处理--404异常捕捉

Spring MVC 全局异常处理-RESTAPI接口返回统一JSON格式-自定义异常处理写之前大概两周草草的将一些代码保存在草稿箱,今天有空来看,结果都没有了【怨念】—重新整理一下了 —–【转载请标...
  • github_38659047
  • github_38659047
  • 2017年06月08日 17:41
  • 741

springAOP实现的异常日志记录+异常邮件发送+权限控制

一般我们的异常都会抛出到控制层,如果使用struts2也就是action。然后try{//正确代码实现}catch{//在里面记录错误日志},这样咋一看是不错,代码很完美。但是如果项目中有成千上万个项...
  • qq_27966627
  • qq_27966627
  • 2016年03月08日 17:10
  • 1217

Spring Cloud实战小贴士:Zuul统一异常处理(一)

在上一篇《Spring Cloud源码分析(四)Zuul:核心过滤器》一文中,我们详细介绍了Spring Cloud Zuul中自己实现的一些核心过滤器,以及这些过滤器在请求生命周期中的不同作用。我们...
  • dyc87112
  • dyc87112
  • 2017年06月26日 14:11
  • 1957

Spring统一异常管理

在基于Spring、SpringMVC的Java Web项目,我们需要处理各层抛出的异常,并对其进行处理,而不能让这种异常直接抛到页面,造成非常不好的用户体验。 一般方式是,在编码过程中,认为会...
  • u013248535
  • u013248535
  • 2017年04月11日 14:46
  • 804

springboot统一异常处理机制

1.自定义异常的处理 在controller层捕获的自定义异常时,可以实现@ControllerAdvice注解捕获。 自定义异常也区分不同调用和ajax调用,可以区别对待,返回不同的结果。 1.1当...
  • eyugod
  • eyugod
  • 2017年08月24日 11:30
  • 3665

SpringBoot之统一异常处理

我们在做Web应用的时候,请求处理过程中发生错误是非常常见的情况。 Spring Boot提供了一个默认的映射:/error,当处理中抛出异常之后,会转到该请求中处理,并且该请求有一个全局的错误...
  • qq_35439782
  • qq_35439782
  • 2017年12月11日 10:34
  • 258

基于springframework中HandlerExceptionResolver接口的统一异常处理

系统中异常类型有哪些? 包括预期可能发生的异常、运行时异常(RuntimeException),运行时异常不是预期会发生的。 针对预期可能发生的异常,在代码手动处理异常可以try/catc...
  • a1837634447
  • a1837634447
  • 2017年03月29日 17:49
  • 256

一头扎进springboot之捕获全局异常

我们在写项目的过程中,遇到各种各样的异常都是很常见的,但是作为开发人员,是肯定不能将程序的异常暴露给用户的,我们需要对其进行友好提示,那么在springboot中,我们可以使用注解,在一个类中,把一些...
  • qiuqiu_qiuqiu123
  • qiuqiu_qiuqiu123
  • 2017年11月09日 16:02
  • 233

Spring MVC REST异常处理最佳实践(上)

如果你已经使用 Spring 来构建你的应用,并且你需要提供 REST API, 那么 Spring MVC 会是你编写 REST 端一个很好的选择。 然而,由于 Spring MVC 常被用来构建...
  • qq_18895659
  • qq_18895659
  • 2016年11月08日 10:02
  • 1241
    个人资料
    等级:
    访问量: 5万+
    积分: 527
    排名: 9万+
    文章存档
    打赏
    最新评论