1. 程式人生 > >0731列印異常的堆疊資訊

0731列印異常的堆疊資訊

讓logger語句記錄異常的堆疊資訊

前言

補個日誌。

其實CSND也有類似的文章,但是我也有思考過,所以我也想記錄一下。我們直接用logger.info("異常資訊為:"+e)或者logger.info(e.getMessage())只能記錄到異常的描述資訊,卻沒有其異常具體發生在哪一行程式碼。這樣即使通過日誌發現出現了異常,也沒法馬上定位問題。因此就催生了一個想法,是否能像在idea本地跑程式時出現未捕獲的異常時,控制檯能打印出完整的錯誤堆疊資訊,把這個資訊記錄到logger語句中。

描述

寫介面時,為了方便後期檢視日誌定位問題,我對service的logger日誌進行了改造,將所有的logger語句拼接成一條記錄,然後再打印出來。因此會在service層最外層使用try catch finally。這樣一個請求的日誌就能統一出現,不會由於多個請求導致的日誌穿插記錄。日誌如:

[2018-06-26 17:09:54.038] -- [http-nio-8079-exec-27] -- [INFO] -- [OrderServiceImpl.java:513 >>>> Method = query] -- [Content = 
********【1121訂單查詢】標識:useruuid==d86a732d-2da6-11e8-xxx-xxxx,source==1,phone==135705xxxxx,orderNo==201806260000xxxxx
********【1121訂單查詢】入參:com.uroad.etc.dto.OrderInput@6ab68cb3[useruuid=d86a732d-2
da6-11e8-xxx-xxxx,orderNo=2018062600xxxx,source=1] ********【1121訂單查詢】請求:request decode data:1121110802CCF5B968C014E702xxxxx ********【1121訂單查詢】響應:response deceode data:90001A4B0D01060F103136303832323xxxxxx ********【1121訂單查詢】updateTradStateAndPushMsgByCardNo 更新表結果:1,cardNo:1608227140xxx ********【1121訂單查詢】出參:com.uroad.etc
.dto.OrderOutput@42879e4b[tradeText=2018-06-26 17:09:31,tradeDate=2018xxx,tradTime=170xxx,orderNo=201806260000003xxx,tradMoney=1,tradState=3,serialNo=<null>,cardNo=160822714xxx,terminalNo=<null>,billState=<null>,orderType=1,cardType=<null>,plateNo=<null>,payChannel=6]]

但這樣做有個不好的地方,就是當出現異常的時候,雖然捕獲了,也列印了異常資訊,但是異常資訊不能準確定位錯誤資訊,會像這樣:

[2018-07-31 21:41:04] -- [main] -- [INFO] -- [TestException.java:17 >>>> Method = main] -- [Content = 
異常資訊為:java.lang.NullPointerException]

假如service層程式碼簡單,那麼可以很快就定位出出現異常的地方。可是假如service層稍稍複雜些,程式碼稍稍多一些,就沒法迅速的找處是哪個地方出現異常。

像我們在本地測試的時候,當出現了意料之外的執行時異常,控制檯是能打印出完整的錯誤資訊,像這樣:

public static void main(String[] args) {
    String s = "1";
    if(s.equals("1")){
        s=null;
    }
    s.equals("1");
}

控制檯是能打印出完整資訊的:

Exception in thread "main" java.lang.NullPointerException
    at TestException.main(TestException.java:14)

這樣我們就能一下子就知道錯誤是在TestException的第14行,即s.equals("1)這裡,那我們就能推出是物件s是空物件。

但是由於我在service層的最外層使用了try catch,即我的程式碼是像這樣的:

public static void main(String[] args) {
    try {
        String s = "1";
        if(s.equals("1")){
            s=null;
        }
        s.equals("1");
    }catch (Exception e){
        logger.info("異常資訊為:" + e);
    }
}

這樣,當出現異常,異常資訊為:

[2018-07-31 21:46:04] -- [main] -- [INFO] -- [TestException.java:17 >>>> Method = main] -- [Content = 
異常資訊為:java.lang.NullPointerException]

這樣,只能知道是第17行的logger語句列印的資訊。但是正如我前面所說我在service層使用了try catch finally來處理日誌,所以只有一個logger語句,所以沒法知道錯誤發生在哪裡。

解決方法

public static String toStackTrace(Exception e)
{
    StringWriter sw = new StringWriter();
    PrintWriter pw = new PrintWriter(sw);

    try
    {
        e.printStackTrace(pw);
        return sw.toString();
    }
    catch(Exception e1)
    {
        return "";
    }
}

只是,不知道這樣會不會很佔記憶體。很佔空間。

解決方法2

這樣也能打印出錯誤位置:logger.info(e.getMessage(),e);

輸出如:

[2018-07-31 21:52:57] -- [main] -- [INFO] -- [TestException.java:17 >>>> Method = main] -- [Content = 
null]
java.lang.NullPointerException: null
    at TestException.main(TestException.java:15)

這樣也行:

logger.info("出現異常啦:",e);

但是這樣就必須得一個單獨的logger語句。

可以通過執行緒號去追蹤。。。

最優解決方法

找到了個新方法,不需要改JVM配置,也不需要用Write流:

public static String logClzInfo(Exception e) {
    StringBuffer sb = new StringBuffer();
    sb.append(e.getClass() + " " + e.getMessage() + "\n");
    StackTraceElement[] stackTraceElement = e.getStackTrace();
    for (StackTraceElement traceElement : stackTraceElement) {
        sb.append("\tat " + traceElement + "\n");
    }
    return sb.toString();
}

這樣,就能在catch中通過引數e獲取到完整的錯誤資訊了。這樣一來,以後出了問題,通過日誌,就能一下找到問題所在了。