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!!!