以前編程不喜歡對每個功能、重要的方法進行日誌的格式化輸出,即使要輸出,也沒按照規範進行輸出。前段時間,隨着我們平
臺用戶量不斷增加,出現些問題。當用戶遇到問題,就給我們客服打電話。然後客服毫無疑問就來找我們。當我們接收到這樣問題的時候,
我們首先要定位是什麼原因造成的。當時就是因爲自己方法中輸出的日誌很少,而且不規範,所以根本找不到具體原因,雖然最後問題解
決了,但是花費很多不必要的時間。
那麼我們怎樣使用好日誌這一利器呢?
1.寫好日誌:
我們先來看一下糟糕的日誌打印:
@ResponseBody
@RequestMapping(value = "unbind.do", method = RequestMethod.POST)
public Object unbind(String bankId, String memberId) {
if (StringUtils.isBlank(bankId) || StringUtils.isBlank(memberId)) {
return new Result(false, "解綁參數不合法!");
}
try {
authPayTwoService.unbind(bankId, memberId);
} catch (AppException e) {
LOG.info("認證支付2.0(unbind)失敗:{}",e);
return new Result(false, e.getMessage());
} catch (Exception e) {
LOG.info("認證支付2.0(unbind)]失敗:{}", e);
return new Result(false, "解綁銀行卡失敗,請重試");
}
return new Result(true, "解綁銀行卡成功!");
}
上面代碼是嚴格不符合規範的,每個公司都有自己的打印要求。首先日誌的打印必須是以logger.error、logger.warn或logger.info
的方式打印出來。日誌打印格式應包含[系統來源] 錯誤描述 [關鍵信息],日誌信息要能打印出能看懂的信息,有前因和後果。方法的入參和
出參也要考慮打印出來。
好的日誌格式打印:
@ResponseBody @RequestMapping(value = "unbind.do", method = RequestMethod.POST) public Object unbind(String bankId, String memberId) { LOG.info("[解綁銀行卡--認證支付2.0][params:bankId=" + bankId + ",memberId=" + memberId + "]"); if (StringUtils.isBlank(bankId) || StringUtils.isBlank(memberId)) { return new Result(false, "解綁參數不合法!"); } try { authPayTwoService.unbind(bankId, memberId); } catch (AppException e) { LOG.error("[解綁銀行卡--認證支付2.0(unbind)-mas][error:{}",e); return new Result(false, e.getMessage()); } catch (Exception e) { LOG.error("[解綁銀行卡--認證支付2.0(unbind)][error:{}",e); return new Result(false, "解綁銀行卡失敗,請重試"); } return new Result(true, "解綁銀行卡成功!"); }
從上面的代碼可以很輕鬆的定位方法、傳入的參數及異常是調用接口拋出的異常還是系統級的異常。
出格式也有很大的關係,希望看到此文的朋友們多多優化自己的日誌和輸出格式。2.良好的日誌輸出格式
最近測試同事採用兩百併發對項目進行測試。
格式優化前:
經過測試查看每秒的併發量平均只有110多如下圖:
通過查看jvm 虛擬機發現很多日誌線程出現BLOCKED,如下:
"http-saoma%2F192.168.6.162-8097-132" daemon prio=10 tid=0x00002aaab88e4800 nid=0x2c8e runnable [0x0000000045274000] java.lang.Thread.State: BLOCKED at java.lang.Throwable.getStackTraceElement(Native Method) at java.lang.Throwable.getOurStackTrace(Throwable.java:591) - locked <0x00000007691390d0> (a java.lang.Throwable) at java.lang.Throwable.getStackTrace(Throwable.java:582) at sun.reflect.GeneratedMethodAccessor29.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:597) at org.apache.log4j.spi.LocationInfo.<init>(LocationInfo.java:139) at org.apache.log4j.spi.LoggingEvent.getLocationInformation(LoggingEvent.java:253) at org.apache.log4j.helpers.PatternParser$LocationPatternConverter.convert(PatternParser.java:500) at org.apache.log4j.helpers.PatternConverter.format(PatternConverter.java:65) at org.apache.log4j.PatternLayout.format(PatternLayout.java:506) at org.apache.log4j.WriterAppender.subAppend(WriterAppender.java:310) at org.apache.log4j.DailyRollingFileAppender.subAppend(DailyRollingFileAppender.java:369) at org.apache.log4j.WriterAppender.append(WriterAppender.java:162) at org.apache.log4j.AppenderSkeleton.doAppend(AppenderSkeleton.java:251) - locked <0x0000000780fb1e00> (a org.apache.log4j.DailyRollingFileAppender) at org.apache.log4j.helpers.AppenderAttachableImpl.appendLoopOnAppenders(AppenderAttachableImpl.java:66) at org.apache.log4j.Category.callAppenders(Category.java:206) - locked <0x00000007800020a0> (a org.apache.log4j.spi.RootLogger) at org.apache.log4j.Category.forcedLog(Category.java:391) at org.apache.log4j.Category.log(Category.java:856) at org.slf4j.impl.Log4jLoggerAdapter.error(Log4jLoggerAdapter.java:497)
我們定位org.apache.log4j.spi.LocationInfo類的代碼如下:String s; // Protect against multiple access to sw. synchronized(sw) { t.printStackTrace(pw); s = sw.toString(); sw.getBuffer().setLength(0); } //System.out.println("s is ["+s+"]."); int ibegin, iend; // Given the current structure of the package, the line // containing "org.apache.log4j.Category." should be printed just // before the caller. // This method of searching may not be fastest but it's safer // than counting the stack depth which is not guaranteed to be // constant across JVM implementations. ibegin = s.lastIndexOf(fqnOfCallingClass);
從上面可以看出在該方法中用了synchronized鎖,然後又通過打印堆棧來獲取行號。那麼肯定會影響到性能的,我們通過看
此時log4j.properties日誌文件配置的輸出格式爲:
%d %-5p %c:%L [%t] - %m%n
很明顯就是%l輸出行號的問題,那麼我們把%l去掉結果會不會好很多呢?
把log4j.properties文件中輸出格式改爲:
%d %-5p %c [%t] - %m%n
輸出格式優化後:
再看每秒的併發量如下圖:
從圖中我們可以看出併發量提高了40多,同時jvm線程日誌中java.lang.Thread.State: BLOCKED也沒有了。看來程序的性能和日誌的輸