接著上一篇講。
一個異常日誌處理的例子:
丟擲異常的地方為:
try{
... ...//省略N行
}catch( Exception e){
throw new RuntimeException ("jar檔案載入異常!name="+jarFileName, e);
}
列印異常的地方為:
logger.error(e, "類檔案載入失敗");
這是很正常的手法,列印出堆疊資訊,並附加上一些額外資訊。
最初,異常堆疊資訊是這樣的:
(堆疊資訊有點多,大家快速往下面看……)
類檔案載入失敗 java.lang.RuntimeException: jar檔案載入異常!D:\opt\app\WebSphere\output\report\file\loadclass\照片.jar at com.proj.report.common.util.ClassLoaderUtils.loadJar(ClassLoaderUtils.java:123) at com.proj.report.backmanage.addin.control.ClassLoadController.loadClass(ClassLoadController.java:41) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:597) at org.zollty.framework.mvc.support.HandlerMetaInfo.invokeMethod(HandlerMetaInfo.java:34) at org.zollty.framework.mvc.handler.support.ControllerHandler.invoke(ControllerHandler.java:60) at org.zollty.framework.mvc.handler.support.HandlerChainImpl.doNext(HandlerChainImpl.java:50) at com.proj.report.backmanage.interceptor.AccessRightInterceptor.dispose(AccessRightInterceptor.java:62) at sun.reflect.GeneratedMethodAccessor43.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:597) at org.zollty.framework.mvc.support.HandlerMetaInfo.invokeMethod(HandlerMetaInfo.java:34) at org.zollty.framework.mvc.handler.support.InterceptorHandler.invoke(InterceptorHandler.java:58) at org.zollty.framework.mvc.handler.support.HandlerChainImpl.doNext(HandlerChainImpl.java:50) at com.proj.report.backmanage.interceptor.AdminRightInterceptor.dispose(AdminRightInterceptor.java:56) at sun.reflect.GeneratedMethodAccessor42.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:597) at org.zollty.framework.mvc.support.HandlerMetaInfo.invokeMethod(HandlerMetaInfo.java:34) at org.zollty.framework.mvc.handler.support.InterceptorHandler.invoke(InterceptorHandler.java:58) at org.zollty.framework.mvc.handler.support.HandlerChainImpl.doNext(HandlerChainImpl.java:50) at org.zollty.framework.mvc.servlet.DispatcherController.handleRequest(DispatcherController.java:52) at org.zollty.framework.mvc.DispatcherServlet.dispatcher(DispatcherServlet.java:69) at org.zollty.framework.mvc.DispatcherServlet.doPost(DispatcherServlet.java:33) at javax.servlet.http.HttpServlet.service(HttpServlet.java:637) at javax.servlet.http.HttpServlet.service(HttpServlet.java:717) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:298) at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:857) at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:588) at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:489) at java.lang.Thread.run(Thread.java:619) Caused by: java.util.zip.ZipException: error in opening zip file at java.util.zip.ZipFile.open(Native Method) at java.util.zip.ZipFile.(ZipFile.java:114) at java.util.jar.JarFile.(JarFile.java:133) at java.util.jar.JarFile.(JarFile.java:70) at com.proj.report.common.util.ClassLoaderUtils.loadJar(ClassLoaderUtils.java:120) ... 39 more
這是一個很簡單的報錯,但是堆疊卻是一大堆,讓人茫然。其實,我想說的是,這個堆疊資訊還算少的,在Websphere下,報錯資訊還要多,我們的錯誤日誌檔案,通常都是1分鐘幾M甚至幾十M,我下載的日誌檔案有800多M的。
經過我對很多日誌檔案的分析,發現其中90%的報錯資訊是冗餘的。
經過我對異常的特殊處理,現在錯誤日誌變成了如下形式:
16:07:59,415 ERROR ClassLoadController:29 - 類檔案載入失敗 |- java.lang.RuntimeException: jar檔案載入異常!D:\opt\app\WebSphere\output\report\file\loadclass\照片.jar
at com.proj.report.common.util.ClassLoaderUtils.loadJar(ClassLoaderUtils.java:123)
at com.proj.report.backmanage.addin.control.ClassLoadController.loadClass(ClassLoadController.java:41)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
... 6 more
at com.proj.report.backmanage.interceptor.AccessRightInterceptor.dispose(AccessRightInterceptor.java:62)
... 6 more
at com.proj.report.backmanage.interceptor.AdminRightInterceptor.dispose(AdminRightInterceptor.java:56)
... 23 more
Caused by: java.util.zip.ZipException: error in opening zip file
at java.util.zip.ZipFile.open(Native Method)
at java.util.zip.ZipFile.(ZipFile.java:114)
at java.util.jar.JarFile.(JarFile.java:133)
... 1 more
at com.proj.report.common.util.ClassLoaderUtils.loadJar(ClassLoaderUtils.java:120)
... 39 more
注意看,上面的錯誤日誌少了很多,某些地方用“... 6 more”“... 23 more”代替了。
這樣精簡之後,對於錯誤的定位就清晰多了。
例如,我來分析一下上面的日誌:
1)首先,讀它的錯誤資訊:
類檔案載入失敗 |- java.lang.RuntimeException: jar檔案載入異常!D:\opt\app\WebSphere\output\report\file\loadclass\照片.jar
我瞭解錯誤的概況“類檔案載入失敗了”,進一步原因是“照片.jar這個jar檔案載入異常”
2)再看緊跟著錯誤資訊的下面三行錯誤經過的類路徑:
com.proj.report.common.util.ClassLoaderUtils.loadJar(ClassLoaderUtils.java:123)
at com.proj.report.backmanage.addin.control.ClassLoadController.loadClass(ClassLoadController.java:41)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
從這三行資料,我得知,這個異常資訊是從ClassLoaderUtils.loadJar(ClassLoaderUtils.java:123)這個地方被new出來的。可以猜測,這個地方有如下一個語句:throw new RuntimeException()。又根據下面有Caused by: java.util.zip.ZipException語句,所以斷定,真正的寫法是throw new RuntimeException(e),其中這個e的型別是java.util.zip.ZipException。
在這之後的資料,是程式呼叫的軌跡,大概就是經過Http進入到了攔截器和Servlet,進而到了Action(Controller),我只記錄了一些核心的軌跡資訊。一般來說,這些軌跡資訊是毫無用處的。你甚至可以全部去掉。只保留專案包名“com.proj.report.*”開頭的資訊即可。
再看Caused by語句,這個才是錯誤的真正原始出處,可以看到,是
java.util.zip.ZipException: error in opening zip file
at java.util.zip.ZipFile.open(Native Method)
也就是說,最底層、最源頭的錯誤,是從ZipFile.open(Native Method)裡面new出來的。錯誤資訊是:error in opening zip file
再接著往後面看,看到了最後,是:
com.proj.report.common.util.ClassLoaderUtils.loadJar(ClassLoaderUtils.java:120)
... 39 more
這代表,在我們自己寫的程式中,錯誤的最源頭是
ClassLoaderUtils.loadJar(ClassLoaderUtils.java:120)
是從這個工具類的第120行丟擲來的。
那麼,完整的錯誤軌跡,錯誤的原因和錯誤出處,我們都是非常清楚了。而且,根據上面資訊,我還可以分析出程式呼叫的軌跡:(一般來說,沒必要,用處不大)
經過上層程式的層層呼叫,到達了:
ClassLoadController.loadClass(xx)
然後呼叫了:
ClassLoaderUtils.loadJar(xx)
在這個方法中的如下之處:
ClassLoaderUtils.loadJar(ClassLoaderUtils.java:120)
即120行,丟擲了錯誤資訊。
這就是核心的程式執行軌跡,至於loadClass之上的軌跡和loadJar之後的軌跡,是其他API的執行軌跡,屬於我們無法控制的範圍,故列印出來意義不大。當然,我寫的這個異常處理工具,是可以設定的,例如,如果你想看看SpringAPI的執行軌跡,你可以在不排除它的包名,比如org.springframework.mvc。
這樣還不夠,我還寫了一個更強大的工具:ExceptionWrapper,重寫了Throwable的方法。從底層去改造異常資訊。說實話,SUN JDK裡面的那個Throwable設計得並不好,之前我一直在IBM JDK上做測試,沒發現問題,換到SUN JDK就出問題了。詳見我寫的一篇文章介紹:
http://www.cnblogs.com/zollty/p/3396252.html
像Open JDK 和GUN JDK,都是改進過的。我寫的這個ExceptionWrapper,可以替代JDK自帶的RuntimeException使用。功能更強大。以後我會專門寫一篇文章來介紹這個工具。
用ExceptionWrapper包裝後,最終的錯誤堆疊資訊為:
類檔案載入失敗 |- jar檔案載入異常!D:\opt\was\file\loadclass\照片.jar
Caused by: java.util.zip.ZipException: error in opening zip file
at java.util.zip.ZipFile.open(Native Method)
at java.util.zip.ZipFile.(ZipFile.java:114)
at java.util.jar.JarFile.(JarFile.java:133)
... 1 more
at com.proj.report.common.util.ClassLoaderUtils.loadJar(ClassLoaderUtils.java:122)
at com.proj.report.backmanage.addin.control.ClassLoadController.loadClass(ClassLoadController.java:41)
... 7 more
at com.proj.report.backmanage.interceptor.AccessRightInterceptor.dispose(AccessRightInterceptor.java:62)
... 7 more
at com.proj.report.backmanage.interceptor.AdminRightInterceptor.dispose(AdminRightInterceptor.java:56)
這樣,我滿意了。從上面的堆疊資訊中,我能很清晰的看到錯誤的原因和報錯的地方,以及程式執行的軌跡,幾乎接近完美。