0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

java日志框架 java日志配置等需要注意的幾個(gè)方面

jf_ro2CN3Fa ? 來源:小郭的技術(shù)筆記 ? 作者:小郭的技術(shù)筆記 ? 2022-11-15 16:13 ? 次閱讀

前言

1. 日志框架眾多,兼容問題

2. 日志配置復(fù)雜,容易出錯(cuò)

3. 日志異步亂用,導(dǎo)致日志錯(cuò)亂

4. DefaultErrorHandler存在線程安全問題

5. 需要禁止的一些操作

6. 需要注意的一些操作

7. 系統(tǒng)異常處理原則和實(shí)踐

總結(jié)

前言

在日常開發(fā)中,如果你是一名后端開發(fā)人員,想必應(yīng)該非常清楚在應(yīng)用系統(tǒng)運(yùn)行期間,打印日志有多么重要。

它不但能夠記錄服務(wù)運(yùn)行情況及軌跡,還有助于提升故障排查及定位問題的效率,甚至還可以對(duì)其進(jìn)行分析及監(jiān)控,洞察系統(tǒng)隱患,提前預(yù)警防范。

如果輸出的日志是毫無價(jià)值的,或者是存在問題的時(shí)候,會(huì)給我我們帶來很多不必要的麻煩。

今天和大家聊一聊日志,希望大家看完之后能夠得到一些幫助。

1. 日志框架眾多,兼容問題

我們會(huì)接觸到不同的日志框架,很多人都認(rèn)為打印日志是一件簡(jiǎn)單的事情,但不合理的日志打印會(huì)給我們排查問題帶來困擾。

常用的日志框架:Logbcak、Log4J、Log4j2、commons-logging

在使用過程中會(huì)去調(diào)用他們各自的API,完成日志信息的記錄,各種各樣的API就造成了很混亂的感覺,同時(shí)還要避免同時(shí)使用他們,會(huì)造成死循環(huán)。為了解決這個(gè)問題應(yīng)該采用日志門面。

什么是日志門面?

日志門面,是門面模式的一個(gè)典型的應(yīng)用。

門面模式(Facade Pattern) ,也稱之為外觀模式,其核心為:外部與一個(gè)子系統(tǒng)的通信必須通過一個(gè)統(tǒng)一的外觀對(duì)象進(jìn)行, 使得子系統(tǒng)更易于使用。

解決方案: SLF4J(Simple Logging Facade For Java


org.projectlombok
lombok
true



org.slf4j
slf4j-api
1.6.1

這樣做的最大好處,就是業(yè)務(wù)層的開發(fā)不需要關(guān)心底層日志框架的實(shí)現(xiàn)及細(xì)節(jié),在編碼的時(shí)候也不需要考慮日后更換框架所帶來的成本。這也是門面模式所帶來的好處。

2. 日志配置復(fù)雜,容易出錯(cuò)

在使用日志框架的時(shí)候,我們會(huì)根據(jù)一些定制化的不同級(jí)別的日志輸出,進(jìn)行自定義的變更配置,下面整理了下我們需要注意的一些點(diǎn)。

(1)錯(cuò)誤配置LevelFilter造成日志重復(fù)記錄


 
INFO

問題分析:

我們可以跟進(jìn)一下源碼來定位下,為什么沒有過濾INFO的日志信息

publicAbstractMatcherFilter(){
this.onMatch=FilterReply.NEUTRAL;
this.onMismatch=FilterReply.NEUTRAL;
}

默認(rèn)是NEUTRAL,默認(rèn)交給下一個(gè)過濾器處理,否則調(diào)用onMismatch定義的處理方式處理,默認(rèn)也是交給下一個(gè)過濾器處理

大概的意思就明白了,因?yàn)槲覀儧]有配置 onMatch 和 onMismatc h,所以默認(rèn)交給了下一個(gè)過濾器執(zhí)行。

解決方案:

由于沒有配置 onMatch 和 onMismatch 屬性,導(dǎo)致一直交給下一個(gè)過濾器處理,

只要加上配置就可以解決

 
DENY
 
ACCEPT

3. 日志異步亂用,導(dǎo)致日志錯(cuò)亂

什么情況下我們會(huì)考慮使用異步,那肯定是為了提高性能瓶頸,特別是針對(duì)一些突發(fā)日志的應(yīng)用程序比較有利,提高吞吐量。

AsyncAppender 顧名思義是個(gè)異步Appender,采用異步方式處理日志,在其內(nèi)部維護(hù)了一個(gè) BlockingQueue 阻塞隊(duì)列,每次處理日志時(shí),都先嘗試把 Log4jLogEvent 事件存入隊(duì)列中,然后交由后臺(tái)線程從隊(duì)列中取出事件并處理(把日志交由 AsyncAppender 所關(guān)聯(lián)的Appender處理),但隊(duì)列長度總是有限的,且隊(duì)列默認(rèn)大小是128,如果日志量過大或日志異步線程處理不及時(shí),就很可能導(dǎo)致日志隊(duì)列被打滿。

官網(wǎng)也為我們提供的對(duì)比圖

下圖比較了同步記錄器、異步追加器和異步記錄器的吞吐量。這是所有線程的總吞吐量。在具有 64 個(gè)線程的測(cè)試中,異步記錄器比異步追加器快 12 倍,比同步記錄器快 68 倍。

異步記錄器的吞吐量隨著線程數(shù)的增加而增加,而同步記錄器和異步追加器的吞吐量或多或少是恒定的,而不管執(zhí)行日志記錄的線程數(shù)如何。

cefb3db4-6238-11ed-8abf-dac502259ad0.png

(1)異步日志丟失行號(hào)、方法名等信息



false

cf2dda1c-6238-11ed-8abf-dac502259ad0.png

看到前面的?,就是我們丟失的行號(hào)、方法名等信息

主要是因?yàn)閕ncludeCallerData屬性設(shè)置為false的原因

includeCallerData作用:控制是否收集調(diào)用方數(shù)據(jù),設(shè)置為false目的是為了提高性能



true

cf556776-6238-11ed-8abf-dac502259ad0.png

(2) 異步日志出現(xiàn)丟失

在異步日志中有關(guān)鍵的三個(gè)屬性值,他決定了日志是否出現(xiàn)丟失的問題

模擬打印500次的INFO級(jí)別的日志信息

@GetMapping("/testPerformance")
publicvoidtestPerformance(){
StopWatchstopWatch=newStopWatch();
stopWatch.start();
IntStream.rangeClosed(1,500).forEach(val->{
log.info("{},{}",val,"writelog"+val);
});
stopWatch.stop();
log.info("result{}",stopWatch.prettyPrint());
}

執(zhí)行結(jié)果:

cf89bea4-6238-11ed-8abf-dac502259ad0.pngcfcc4c88-6238-11ed-8abf-dac502259ad0.png

根據(jù)圖片我們可以看到,我們進(jìn)行了500次循環(huán),但是打印結(jié)果的數(shù)據(jù)存在缺失。

分析源碼

AsyncAppender 類主要繼承了 AsyncAppenderBase類

下面我們來看一下源碼

publicclassAsyncAppenderBaseextendsUnsynchronizedAppenderBaseimplementsAppenderAttachable{
AppenderAttachableImplaai=newAppenderAttachableImpl();
BlockingQueueblockingQueue;
publicstaticfinalintDEFAULT_QUEUE_SIZE=256;
intqueueSize=256;
intappenderCount=0;
staticfinalintUNDEFINED=-1;
intdiscardingThreshold=-1;
booleanneverBlock=false;
AsyncAppenderBase.Workerworker=newAsyncAppenderBase.Worker();
publicstaticfinalintDEFAULT_MAX_FLUSH_TIME=1000;
intmaxFlushTime=1000;

publicAsyncAppenderBase(){
}

if(this.discardingThreshold==-1){
this.discardingThreshold=this.queueSize/5;
}

queueSize 屬性

作用:用于控制阻塞隊(duì)列大小,防止隊(duì)列塞滿后阻塞,默認(rèn)為256,內(nèi)存中最多保存256條日志。

discardingThreshold 屬性

作用:控制丟棄日志的閾值,默認(rèn)值-1,需要注意的是,這里的閾值是指行數(shù),不是百分比

privatevoidput(EeventObject){
if(this.neverBlock){
this.blockingQueue.offer(eventObject);
}else{
this.putUninterruptibly(eventObject);
}

}

問題分析:

因?yàn)?queueSize 的默認(rèn)為256,所以內(nèi)存中最多保存256條日志,當(dāng)?shù)竭_(dá) discardingThreshold 剩余總行數(shù)的五分之一時(shí),就會(huì)進(jìn)行日志丟棄的操作。

解決方案:

通過修改關(guān)鍵的屬性值,來進(jìn)行日志丟失問題的解決



false
0

我們只需要將 discardingThreshold 設(shè)置為0,他表示不進(jìn)行日志的丟棄操作,這樣就可以達(dá)到保證日志不丟失的目的。

(3)異步日志內(nèi)存撐爆

因?yàn)楫惒嚼锩嬷饕褂?BlockingQueue 阻塞隊(duì)列,隊(duì)列都會(huì)存在的一個(gè)問題就是過大的時(shí)候肯定就會(huì)造成OOM。

所以 queueSize 設(shè)置特別大,也會(huì)造成 OOM 異常。

(4) 異步日志出現(xiàn)阻塞

業(yè)務(wù)場(chǎng)景:

a. 調(diào)用后端RPC服務(wù)超時(shí),導(dǎo)致調(diào)用方大量線程阻塞

b. 輸出異常日志導(dǎo)致大量線程阻塞

關(guān)鍵屬性:neverBlock屬性

作用:隊(duì)列滿的時(shí)候,加入數(shù)據(jù)是否丟棄,默認(rèn)不丟棄

是的時(shí)候調(diào)用 offer() 方法不阻塞,否的時(shí)候調(diào)用 put() 方法阻塞。

我們來看一下這部分的源碼

privatevoidput(EeventObject){
if(this.neverBlock){
this.blockingQueue.offer(eventObject);
}else{
this.putUninterruptibly(eventObject);
}

}

privatevoidputUninterruptibly(EeventObject){
booleaninterrupted=false;

try{
while(true){
try{
this.blockingQueue.put(eventObject);
return;
}catch(InterruptedExceptionvar7){
interrupted=true;
}
}
}finally{
if(interrupted){
Thread.currentThread().interrupt();
}

}
}

在默認(rèn)情況下,就會(huì)調(diào)用putUninterruptibly阻塞方法,拋出 OOM 異常后,會(huì)一直阻塞著。

所以,當(dāng)我們?cè)O(shè)置過大的隊(duì)列或者不愿意犧牲日志的情況下可能會(huì)導(dǎo)致線程的阻塞問題。

我們需要做好取舍的工作,看一看是性能有限還是數(shù)據(jù)安全優(yōu)先

d0069f78-6238-11ed-8abf-dac502259ad0.png

4. DefaultErrorHandler存在線程安全問題

//org.apache.logging.log4j.core.appender.DefaultErrorHandler
privatestaticfinalLoggerLOGGER=StatusLogger.getLogger();
privatestaticfinalintMAX_EXCEPTIONS=3;
//5min時(shí)間間隔
privatestaticfinallongEXCEPTION_INTERVAL=TimeUnit.MINUTES.toNanos(5);
privateintexceptionCount=0;
privatelonglastException=System.nanoTime()-EXCEPTION_INTERVAL-1;
publicvoiderror(finalStringmsg){
finallongcurrent=System.nanoTime();
//當(dāng)前時(shí)間距離上次異常處理時(shí)間間隔超過5min或者異常處理數(shù)小于3次
if(current-lastException>EXCEPTION_INTERVAL
||exceptionCount++

DefaultErrorHandler 內(nèi)部在處理異常日志時(shí)增加了條件限制,只有下述兩個(gè)條件任一滿足 時(shí)才會(huì)處理,從而避免大量異常日志導(dǎo)致的性能問題。

兩條日志處理間隔超過5min。

異常日志數(shù)量不超過3次。

lastException 用于標(biāo)記上次異常的時(shí)間戳,該變量可能被多線程訪問,無法保證多線程情況下的線程安全。

exceptionCount用于統(tǒng)計(jì)異常日志次數(shù),該變量可能被多線程訪問,無法保證多線程情況下的線程安全。

5. 需要禁止的一些操作

【強(qiáng)制】ERROR 級(jí)別日志需打印堆棧,而非 ERROR 級(jí)別日志則不需要。

【強(qiáng)制】禁止日志打印內(nèi)容中僅打印特殊字符或數(shù)字的情況。

【強(qiáng)制】禁止使用 Logback/Log4j2 等的 API,應(yīng)使用 SLF4J 的 API。

【強(qiáng)制】禁止日志不能打印客戶敏感信息,如身份證號(hào),銀行卡號(hào)。

6. 需要注意的一些操作

【建議】日志內(nèi)容中應(yīng)包含關(guān)鍵特征類信息,例如:用戶標(biāo)識(shí)或流水號(hào)。

【建議】應(yīng)采用異步打印模式,且打印時(shí)建議關(guān)閉打印位置信息。

【建議】日志打印若出現(xiàn)堵塞,建議至少丟棄 INFO 級(jí)別以上的日志。

【建議】每條日志在語義上可獨(dú)立被理解,減少上下文關(guān)聯(lián)理解。

【建議】打印異常堆棧信息,調(diào)用方法log.error(Object message, Throwable e) ,正確寫法:log.error("訂單【"+orderId+"】詳情查詢異常:, e);

【強(qiáng)制】在接口/方法的入口/出口處,打印請(qǐng)求及響應(yīng)參數(shù)日志。

7. 系統(tǒng)異常處理原則和實(shí)踐

異常處理原則

(1)具體明確的原則。具體而不是泛化地拋出異常,明確捕獲哪種類型的異常,而不是泛化地捕獲Exception類型的異常??梢詫?duì)同一try塊定義多個(gè)catch塊。

(2)更泛化的異常類型放在最后的catch塊,如IOException。

(3)提早拋出的原則。提早拋出異常(又稱"快速失?。ⅲ?,異常得以清晰又準(zhǔn)確。

(4)延遲捕獲的原則,在合適的層面捕獲異常。如在web前端捕獲、處理異常,可以有效通知用戶并提供處理意見。

異常處理實(shí)踐

(1)在堆棧跟蹤中包含引起異常的原因 。

(2)檢查型異常轉(zhuǎn)為運(yùn)行時(shí)異常。如throw new RuntimeException("查詢失敗",sqlException)。

(3)禁止catch塊為空。

異常處理建議

(1)catch塊中,ex.printStacktrace()只是將異常輸出到控制臺(tái),沒有任何意義。而且這里出現(xiàn)了異常并沒有中斷程序,進(jìn)而調(diào)用代碼繼續(xù)執(zhí)行,導(dǎo)致更多的異常。

(2)不要在PC網(wǎng)頁、H5、APP界面上顯示異常類名或者打印異常堆棧信息,這種東西對(duì)用戶沒有幫助。

(3)不要將異常包含在循環(huán)語句塊中(特別注意循環(huán)代碼塊中調(diào)用方法中包含異常處理),異常處理占用系統(tǒng)資源。

(4)多層次打印異常,會(huì)導(dǎo)致異常日志重復(fù)累贅。在合適的處理層打印異常日志信息和處理異常。

(5)不要在 finally 內(nèi)部使用 return 語句。它不僅會(huì)影響函數(shù)的正確返回值,而且它可能還會(huì)導(dǎo)致一些異常處理過程的意外終止,最終導(dǎo)致某些異常的丟失。

總結(jié)

關(guān)于日志,確實(shí)是我們?nèi)粘V惺褂米疃啵珔s最容易忽視的地方,只要線上一出現(xiàn)問題我們只能通過日志來復(fù)現(xiàn)問題,

只有規(guī)范的輸出日志才能讓我們的排查做到事半功倍,主要從日志的兼容性、異步日志問題、需要禁止和注意的一些操作,

在之后的輸出日志的時(shí)候,保持著更嚴(yán)謹(jǐn)?shù)膽B(tài)度。

d1098804-6238-11ed-8abf-dac502259ad0.png

作者小郭的技術(shù)筆記,在此特別鳴謝!

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • JAVA
    +關(guān)注

    關(guān)注

    19

    文章

    2952

    瀏覽量

    104477
  • API
    API
    +關(guān)注

    關(guān)注

    2

    文章

    1472

    瀏覽量

    61749
  • 日志
    +關(guān)注

    關(guān)注

    0

    文章

    138

    瀏覽量

    10626

原文標(biāo)題:從阿里來個(gè)技術(shù)大佬,入職就給我們分享Java打日志的幾大神坑!

文章出處:【微信號(hào):芋道源碼,微信公眾號(hào):芋道源碼】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    日志框架簡(jiǎn)介-Slf4j+Logback入門實(shí)踐

    結(jié)果不受日志的有無影響,但沒有日志的應(yīng)用程序是不完整的,甚至可以說是有缺陷的。優(yōu)秀的日志系統(tǒng)可以 記錄操作軌跡 、 監(jiān)控系統(tǒng)運(yùn)行狀態(tài) 和 解決系統(tǒng)故障 。 Java
    的頭像 發(fā)表于 07-30 10:00 ?1035次閱讀
    <b class='flag-5'>日志</b><b class='flag-5'>框架</b>簡(jiǎn)介-Slf4j+Logback入門實(shí)踐

    Java入門需要學(xué)習(xí)什么?

    機(jī)制都內(nèi)置到基礎(chǔ)平臺(tái)當(dāng)中了。四、如何學(xué)好集合框架Java描述復(fù)雜數(shù)據(jù)結(jié)構(gòu)的主要方式是集合框架。Java沒有指針,而是通過強(qiáng)大的集合框架描述
    發(fā)表于 03-01 15:45

    JAVA字符集主要包括以下幾個(gè)方面

    1. 概述 本文主要包括以下幾個(gè)方面:編碼基本知識(shí),java,系統(tǒng)軟件,url,工具軟件。 在下面的描述中,將以"中文"兩個(gè)字為例,經(jīng)查表可以知道其GB2312編碼是"
    發(fā)表于 07-11 07:10

    Java學(xué)習(xí)過程中需要注意的25個(gè)要點(diǎn)

    想要精通Java,成為Java高手,需要不斷的學(xué)習(xí)和積累。本文給出了Java學(xué)習(xí)過程中需要注意的25個(gè)學(xué)習(xí)目標(biāo),希望可以給您帶來幫助。
    發(fā)表于 07-11 07:49

    java日志采集步驟

    十一、k8s收集 pod中 java日志
    發(fā)表于 11-06 09:26

    java 日志框架Spring Boot分析

    應(yīng)用程序中輸出相應(yīng)的日志。 在傳統(tǒng)Java應(yīng)用程序中,我們一般會(huì)使用類似Log4j這樣的日志框架來輸出日志,而不是直接在代碼中通過Syste
    發(fā)表于 09-28 14:58 ?0次下載

    對(duì)于大規(guī)模系統(tǒng)日志日志模式提煉算法的優(yōu)化

    LARGE框架是部署在中國科學(xué)院超級(jí)計(jì)算環(huán)境中的日志分析系統(tǒng),通過日志收集、集中分析、結(jié)果反饋步驟對(duì)環(huán)境中的各種日志文件進(jìn)行監(jiān)控和分析。在
    發(fā)表于 11-21 14:54 ?7次下載
    對(duì)于大規(guī)模系統(tǒng)<b class='flag-5'>日志</b>的<b class='flag-5'>日志</b>模式提煉算法的優(yōu)化

    基于時(shí)間卷積網(wǎng)絡(luò)的通用日志序列異常檢測(cè)框架

    基于循環(huán)神經(jīng)網(wǎng)絡(luò)的日志序列異常檢測(cè)模型對(duì)短序列有較好的檢測(cè)能力,但對(duì)長序列的檢測(cè)準(zhǔn)確性較差。為此,提出一種基于時(shí)間卷積網(wǎng)絡(luò)的通用日志序列異常檢測(cè)框架。將日志模板序列建模為自然語言序列,
    發(fā)表于 03-30 10:29 ?8次下載
    基于時(shí)間卷積網(wǎng)絡(luò)的通用<b class='flag-5'>日志</b>序列異常檢測(cè)<b class='flag-5'>框架</b>

    Java日志框架中的王者是誰

    來源丨juejin.cn/post/6945753017878577165 Logback 算是JAVA 里一個(gè)老牌的日志框架,從06年開始第一個(gè)版本,迭代至今也十幾年了。 不過logback最近一
    的頭像 發(fā)表于 10-13 09:12 ?1336次閱讀

    log4j日志框架分析

    og4j是Apache下的一款開源的日志框架,能夠滿足我們?cè)陧?xiàng)目中對(duì)于日志記錄的需求。log4j提供了簡(jiǎn)單的API調(diào)用,強(qiáng)大的日志格式定義以及靈活的擴(kuò)展性。使用者可以自己定義Appen
    的頭像 發(fā)表于 02-28 14:32 ?1073次閱讀
    log4j<b class='flag-5'>日志</b><b class='flag-5'>框架</b>分析

    Spring Boot的日志框架使用

    目前市面上常見的日志框架有:slf4j(Simple Logging Facade for Java)、logback、log4j、log4j2、commons-logging(Spring默認(rèn)
    的頭像 發(fā)表于 06-02 10:59 ?910次閱讀
    Spring Boot的<b class='flag-5'>日志</b><b class='flag-5'>框架</b>使用

    C++異步日志實(shí)踐

    一個(gè)高效可拓展的異步C++日志庫:RING LOG,本文分享了了其設(shè)計(jì)方案與技術(shù)原理內(nèi)容 導(dǎo)論 同步日志與缺點(diǎn) 傳統(tǒng)的日志也叫同步日志,每
    的頭像 發(fā)表于 11-09 10:29 ?620次閱讀
    C++異步<b class='flag-5'>日志</b>實(shí)踐

    Android開發(fā)中的日志接口介紹

    1、日志接口 日志接口內(nèi)容,共分為java層、native層、kernel層。下面就對(duì)每個(gè)層級(jí)的內(nèi)容分別進(jìn)行介紹。 1.1 java層調(diào)用
    的頭像 發(fā)表于 11-23 16:27 ?1011次閱讀
    Android開發(fā)中的<b class='flag-5'>日志</b>接口介紹

    日志篇:模組日志總體介紹

    ?今天我們學(xué)習(xí)合宙模組日志總體介紹,以下進(jìn)入正文。 一、本文討論的邊界 本文是對(duì)合宙 4G 模組, 以及 4G+GNSS 模組的日志功能的總體介紹。通過日志,可以對(duì)研發(fā)過程中,以及模組運(yùn)行過程中
    的頭像 發(fā)表于 10-24 07:16 ?121次閱讀
    <b class='flag-5'>日志</b>篇:模組<b class='flag-5'>日志</b>總體介紹

    nginx日志配置方法

    access_log用來定義日志級(jí)別,日志位置。
    的頭像 發(fā)表于 10-24 17:43 ?173次閱讀