考虑对JDK的底层堆栈信息进行处理,一种是重写JDK的Throwable,另一种是在原错误堆栈信息上进行“二次加工”。目前这两种方式我都实现了,效果非常好。
这篇文章主要记录对错误堆栈进行“二次加工”的实现过程。
从大量的实际错误日志分析出发:
首先,追根溯源,找到错误是从哪个地方new出来的。
例如
com.jfinal.plugin.activerecord.ActiveRecordException: java.lang.ClassCastException: com.alibaba.druid.sql.visitor.SQLEvalVisitorImpl cannot be cast to com.alibaba.druid.sql.dialect.sqlserver.visitor.SQLServerASTVisitor
at com.jfinal.plugin.activerecord.Db.paginate(Db.java:554)
at com.jfinal.plugin.activerecord.Db.paginate(Db.java:593)
at com.simple.sqpt.services.FWCXService.doGetHouseInfo(FWCXService.java:172)
那么是在Db.paginate(Db.java:554)这一行new出来的,后面有:
Caused by: java.lang.ClassCastException: com.alibaba.druid.sql.visitor.SQLEvalVisitorImpl cannot be cast to com.alibaba.druid.sql.dialect.sqlserver.visitor.SQLServerASTVisitor
at com.alibaba.druid.sql.dialect.sqlserver.ast.SQLServerSelectQueryBlock.accept0(SQLServerSelectQueryBlock.java:39)
at com.alibaba.druid.sql.ast.SQLObjectImpl.accept(SQLObjectImpl.java:40)
说明这个new出来的错误,是包裹了另外一个错误,可能是有new ActiveRecordException(e);然后这个e是一个ClassCastException类型错误
因为只有一个Caused by,可以肯定,这个(ClassCastException) e 是源头,是从:
SQLServerSelectQueryBlock.accept0(SQLServerSelectQueryBlock.java:39)这里new出来的。
应用的栈信息可能夹在中间,比如:
org.springframework.jdbc.CannotGetJdbcConnectionException: Could not get JDBC Connection; nested exception is com.ibm.websphere.ce.cm.StaleConnectionException: The Network Adapter could not establish the connectionDSRA0010E: SQL State = 61000, Error Code = 20
at org.springframework.jdbc.datasource.DataSourceUtils.getConnection(DataSourceUtils.java:82)
at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:577)
at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:641)
at org.springframework.jdbc.core.JdbcTemplate.queryForInt(JdbcTemplate.java:759)
at com.proj.webc.persist.framework.BaseDAO.queryForCount(BaseDAO.java:142)
at com.proj.webc.persist.office.dao.OfficeDAO.queryBackUsersCountByCondition(OfficeDAO.java:734)
at com.proj.webc.logic.office.OfficeManagerImpl.queryCountOfficeByCondition(OfficeManagerImpl.java:79)
at com.proj.webc.present.orderquery.action.FrontOrderQueryAction.doAction(FrontOrderQueryAction.java:118)
at com.proj.webc.framework.BaseAction.execute(BaseAction.java:205)
at sun.reflect.GeneratedMethodAccessor81.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Method.java:600)
at com.opensymphony.xwork2.DefaultActionInvocation.invokeAction(DefaultActionInvocation.java:450)
at com.opensymphony.xwork2.DefaultActionInvocation.invokeActionOnly(DefaultActionInvocation.java:289)
像这种,其实我们只能操纵应用之前的错误信息,应用之后的,超出了我们的管理范围。 比如上面这个例子,在com.proj.**之前的org.springframework.**是我们可以管理的, 但是后面的sun.reflect.**,java.lang.**,com.opensymphony.**都超出了应用的可视范围。 (除非从源头控制,比如com.opensymphony.**的源码,最上层,应该是webContainer,例如websphere下,最外层的栈信息都是com.ibm.**开头)
分析上面的错误信息,我们可以操作的最外层,是 com.proj.webc.framework.BaseAction.execute(BaseAction.java) 我们可以在这个地方做工作,把它管辖之下的错误信息进行整理,例如可以把 org.springframework.**这种信息都过滤掉。
如果是用log工具,我们就有控制权。
综上分析,对于最上层,紧跟着错误信息的地方,只需要追溯到错误new出来之前的前几个轨迹即可。 一般来说,只需要跟踪和new出来错误的那个类在同一个api包之中的,比如上面这个例子, 只需要跟踪到第二行 com.jfinal.plugin.activerecord.Db.paginate(Db.java:593)即可。
对于Caused by,是非常核心的错误出处,一般是在应用之中的,可采取和上面一样的处理方式,如果是调用第三方api抛出的错误,如上面那个例子。 其实捕获栈信息的意义不是很大,但是也可以少量捕捉一些,比如一前一尾。但是尾巴不好判断,所以考虑到性能,只捕捉前面2行就够意思了。后面的话 要逐行判断,如果是本应用的类,则捕捉。
捕捉到最后,是三个点: ... 39 more 这样,Caused by就处理完成了。如果后面还有Caused by,也用该方法继续捕捉。
综上,其实就一个方法: 检测非 at开头的字符串,记录其信息。(可设定长度大小,比如小于1000)
对于 at开头的字符串,判断是否为本应用+特定api的前缀,是则保留。
特殊处理: 保留紧跟着非 at开头的字符串的2行栈信息,
保留出现本应用+特定api前缀那一行的前3行信息。
算法复杂度,假设有100行,一行比较30次char(用startWith),总共for循环100次,每次比较一个startWith,
每次循环假设还做一个5个if的判断,那么一共比较的次数为500+3000=3500,对CPU来说,应该不成问题。