久久精品五月,日韩不卡视频在线观看,国产精品videossex久久发布 ,久久av综合

站長資訊網
最全最豐富的資訊網站

搞懂Java日志級別,重復記錄、丟日志問題

java基礎教程欄目介紹如何解決Java日志級別等問題

搞懂Java日志級別,重復記錄、丟日志問題

相關免費學習推薦:java基礎教程

1 日志常見錯因

1.1 日志框架繁多

不同類庫可能使用不同日志框架,兼容是個難題

1.2 配置復雜且容易出錯

日志配置文件通常很繁雜,很多同學習慣從其他項目或網上博客直接復制份配置文件,但卻不仔細研究如何修改。常見錯誤發生于重復記錄日志、同步日志的性能、異步記錄的錯誤配置。

1.3 日志記錄本身就有些誤區

比如沒考慮到日志內容獲取的代價、胡亂使用日志級別等。

2 SLF4J

Logback、Log4j、Log4j2、commons-logging、JDK自帶的java.util.logging等,都是Java體系的日志框架,確實非常多。而不同的類庫,還可能選擇使用不同的日志框架。這樣一來,日志的統一管理就變得非常困難。

  • SLF4J(Simple Logging Facade For Java)就為解決該問題

搞懂Java日志級別,重復記錄、丟日志問題

  • 提供統一的日志門面API,即圖中紫色部分,實現中立的日志記錄API
  • 橋接功能,藍色部分,把各種日志框架API(綠色部分)橋接到SLF4J API。這樣即便你的程序中使用各種日志API記錄日志,最終都可橋接到SLF4J門面API。
  • 適配功能,紅色部分,可實現SLF4J API和實際日志框架(灰色部分)綁定。
    SLF4J只是日志標準,還是需要實際日志框架。日志框架本身未實現SLF4J API,所以需前置轉換。Logback就是按SLF4J API標準實現,所以才無需綁定模塊做轉換。

雖然可用log4j-over-slf4j實現Log4j橋接到SLF4J,也可使用slf4j-log4j12實現SLF4J適配到Log4j,也把它們畫到了一列,但是它不能同時使用它們,否則就會產生死循環。jcl和jul同理。

雖然圖中有4個灰色的日志實現框架,但日常業務使用最多的還是Logback和Log4j,都是同一人開發的。Logback可認為是Log4j改進版,更推薦使用,基本已是主流。

Spring Boot的日志框架也是Logback。那為什么我們沒有手動引入Logback包,就可直接使用Logback?

spring-boot-starter模塊依賴spring-boot-starter-logging模塊
spring-boot-starter-logging模塊自動引入logback-classic(包含SLF4J和Logback日志框架)和SLF4J的一些適配器。其中,log4j-to-slf4j用于實現Log4j2 API到SLF4J的橋接,jul-to-slf4j則是實現java.util.logging API到SLF4J的橋接。

3 日志重復記錄

日志重復記錄不但給查看日志和統計工作帶來不必要的麻煩,還會增加磁盤和日志收集系統的負擔。

logger配置繼承關系導致日志重復記錄

  • 定義一個方法實現debug、info、warn和error四種日志的記錄
    搞懂Java日志級別,重復記錄、丟日志問題

  • Logback配置
    搞懂Java日志級別,重復記錄、丟日志問題

  • 配置看沒啥問題,執行方法后出現日志重復記錄
    搞懂Java日志級別,重復記錄、丟日志問題

  • 分析
    CONSOLE這個Appender同時掛載到了倆Logger,定義的<logger><root>,由于定義的<logger>繼承自<root>,所以同一條日志既會通過logger記錄,也會發送到root記錄,因此應用package下日志出現重復記錄。

如此配置的初衷是啥呢?
內心是想實現自定義logger配置,讓應用內的日志暫時開啟DEBUG級別日志記錄。其實,這無需重復掛載Appender,去掉<logger>下掛載的Appender即可:

<logger name="org.javaedge.time.commonmistakes.logging" level="DEBUG"/>

若自定義<logger>需把日志輸出到不同Appender:
比如

  • 應用日志輸出到文件app.log
  • 其他框架日志輸出到控制臺

可設置<logger>additivity屬性為false,這就不會繼承<root>的Appender
搞懂Java日志級別,重復記錄、丟日志問題

錯誤配置LevelFilter造成日志重復

  • 在記錄日志到控制臺的同時,把日志記錄按照不同級別記錄到倆文件
    搞懂Java日志級別,重復記錄、丟日志問題

  • 執行結果

  • info.log 文件包含INFO、WARN和ERROR三級日志,不符預期
    搞懂Java日志級別,重復記錄、丟日志問題

  • error.log包含WARN和ERROR倆級別日志,導致日志重復收集搞懂Java日志級別,重復記錄、丟日志問題

  • 事故問責
    一些公司使用自動化ELK方案收集日志,日志會同時輸出到控制臺和文件,開發人員在本地測試不會關心文件中記錄的日志,而在測試和生產環境又因為開發人員沒有服務器訪問權限,所以原始日志文件中的重復問題難以發現。

日志到底為何重復呢?

ThresholdFilter源碼解析

  • 日志級別 ≥ 配置級別 返回NEUTRAL,繼續調用過濾器鏈上的下個過濾器
  • 否則返回DENY,直接拒絕記錄日志
    搞懂Java日志級別,重復記錄、丟日志問題

該案例我們將 ThresholdFilterWARN,因此可記錄WARNERROR級日志。

LevelFilter

用于比較日志級別,然后進行相應處理。

  • 若匹配就調用onMatch定義的處理方式:默認交給下一個過濾器處理(AbstractMatcherFilter基類中定義的默認值)
  • 否則調用onMismatch定義的處理方式:默認也是交給下一個過濾器

搞懂Java日志級別,重復記錄、丟日志問題
搞懂Java日志級別,重復記錄、丟日志問題

ThresholdFilter不同,LevelFilter僅配置level無法真正起作用

由于未配置onMatch和onMismatch屬性,所以該過濾器失效,導致INFO以上級別日志都記錄了。

修正

配置LevelFilter的onMatch屬性為ACCEPT,表示接收INFO級別的日志;配置onMismatch屬性為DENY,表示除了INFO級別都不記錄:
搞懂Java日志級別,重復記錄、丟日志問題

如此,_info.log文件只會有INFO級日志,不會再出現日志重復。

4 異步日志提高性能?

知道了到底如何正確將日志輸出到文件后,就該考慮如何避免日志記錄成為系統性能瓶頸。這可解決,磁盤(比如機械磁盤)IO性能較差、日志量又很大的情況下,如何記錄日志問題。

定義如下的日志配置,一共有兩個Appender:

FILE是一個FileAppender,用于記錄所有的日志;
CONSOLE是一個ConsoleAppender,用于記錄帶有time標記的日志。
搞懂Java日志級別,重復記錄、丟日志問題
把大量日志輸出到文件中,日志文件會非常大,如果性能測試結果也混在其中的話,就很難找到那條日志。所以,這里使用EvaluatorFilter對日志按照標記進行過濾,并將過濾出的日志單獨輸出到控制臺上。該案例中給輸出測試結果的那條日志上做了time標記。

配合使用標記和EvaluatorFilter,實現日志的按標簽過濾

  • 測試代碼:實現記錄指定次數的大日志,每條日志包含1MB字節的模擬數據,最后記錄一條以time為標記的方法執行耗時日志:搞懂Java日志級別,重復記錄、丟日志問題

執行程序后可以看到,記錄1000次日志和10000次日志的調用耗時,分別是5.1秒和39秒
搞懂Java日志級別,重復記錄、丟日志問題搞懂Java日志級別,重復記錄、丟日志問題

對只記錄文件日志的代碼,這耗時過長。

源碼解析

FileAppender繼承自OutputStreamAppender
搞懂Java日志級別,重復記錄、丟日志問題
在追加日志時,是直接把日志寫入OutputStream中,屬同步記錄日志
搞懂Java日志級別,重復記錄、丟日志問題

所以日志大量寫入才會曠日持久。如何才能實現大量日志寫入時,不會過多影響業務邏輯執行耗時而影響吞吐量呢?

AsyncAppender

使用Logback的AsyncAppender

即可實現異步日志記錄。AsyncAppender類似裝飾模式,在不改變類原有基本功能情況下為其增添新功能。這便可把AsyncAppender附加在其他Appender,將其變為異步。

定義一個異步Appender ASYNCFILE,包裝之前的同步文件日志記錄的FileAppender, 即可實現異步記錄日志到文件
搞懂Java日志級別,重復記錄、丟日志問題

  • 記錄1000次日志和10000次日志的調用耗時,分別是537毫秒和1019毫秒
    搞懂Java日志級別,重復記錄、丟日志問題搞懂Java日志級別,重復記錄、丟日志問題

異步日志真的如此高性能?并不,因為這并沒有記錄下所有日志。

AsyncAppender異步日志坑

  • 記錄異步日志撐爆內存
  • 記錄異步日志出現日志丟失
  • 記錄異步日志出現阻塞。

案例

模擬慢日志記錄場景:
首先,自定義一個繼承自ConsoleAppenderMySlowAppender,作為記錄到控制臺的輸出器,寫入日志時休眠1秒。
搞懂Java日志級別,重復記錄、丟日志問題

  • 配置文件中使用AsyncAppender,將MySlowAppender包裝為異步日志記錄
    搞懂Java日志級別,重復記錄、丟日志問題

  • 測試代碼
    搞懂Java日志級別,重復記錄、丟日志問題

  • 耗時很短但出現日志丟失:要記錄1000條日志,最終控制臺只能搜索到215條日志,而且日志行號變問號。
    搞懂Java日志級別,重復記錄、丟日志問題

  • 原因分析
    AsyncAppender提供了一些配置參數,而當前沒用對。

源碼解析

  • includeCallerData
    默認false:方法行號、方法名等信息不顯示
  • queueSize
    控制阻塞隊列大小,使用的ArrayBlockingQueue阻塞隊列,默認容量256:內存中最多保存256條日志
  • discardingThreshold
    丟棄日志的閾值,為防止隊列滿后發生阻塞。默認隊列剩余容量 < 隊列長度的20%,就會丟棄TRACE、DEBUG和INFO級日志
  • neverBlock
    控制隊列滿時,加入的數據是否直接丟棄,不會阻塞等待,默認是false
    • 隊列滿時:offer不阻塞,而put會阻塞
    • neverBlock為true時,使用offer
public class AsyncAppender extends AsyncAppenderBase<ILoggingEvent> { 	// 是否收集調用方數據     boolean includeCallerData = false;     protected boolean isDiscardable(ILoggingEvent event) {         Level level = event.getLevel();         // 丟棄 ≤ INFO級日志         return level.toInt() <= Level.INFO_INT;     }     protected void preprocess(ILoggingEvent eventObject) {         eventObject.prepareForDeferredProcessing();         if (includeCallerData)             eventObject.getCallerData();     }}public class AsyncAppenderBase<E> extends UnsynchronizedAppenderBase<E> implements AppenderAttachable<E> {  	// 阻塞隊列:實現異步日志的核心     BlockingQueue<E> blockingQueue;     // 默認隊列大小     public static final int DEFAULT_QUEUE_SIZE = 256;     int queueSize = DEFAULT_QUEUE_SIZE;     static final int UNDEFINED = -1;     int discardingThreshold = UNDEFINED;     // 當隊列滿時:加入數據時是否直接丟棄,不會阻塞等待     boolean neverBlock = false;      @Override     public void start() {        	...         blockingQueue = new ArrayBlockingQueue<E>(queueSize);         if (discardingThreshold == UNDEFINED)         //默認丟棄閾值是隊列剩余量低于隊列長度的20%,參見isQueueBelowDiscardingThreshold方法             discardingThreshold = queueSize / 5;         ...     }      @Override     protected void append(E eventObject) {         if (isQueueBelowDiscardingThreshold() && isDiscardable(eventObject)) { //判斷是否可以丟數據             return;         }         preprocess(eventObject);         put(eventObject);     }      private boolean isQueueBelowDiscardingThreshold() {         return (blockingQueue.remainingCapacity() < discardingThreshold);     }      private void put(E eventObject) {         if (neverBlock) { //根據neverBlock決定使用不阻塞的offer還是阻塞的put方法             blockingQueue.offer(eventObject);         } else {             putUninterruptibly(eventObject);         }     }     //以阻塞方式添加數據到隊列     private void putUninterruptibly(E eventObject) {         boolean interrupted = false;         try {             while (true) {                 try {                     blockingQueue.put(eventObject);                     break;                 } catch (InterruptedException e) {                     interrupted = true;                 }             }         } finally {             if (interrupted) {                 Thread.currentThread().interrupt();             }         }     }}

默認隊列大小256,達到80%后開始丟棄<=INFO級日志后,即可理解日志中為什么只有兩百多條INFO日志了。

queueSize 過大

可能導致OOM

queueSize 較小

默認值256就已經算很小了,且discardingThreshold設置為大于0(或為默認值),隊列剩余容量少于discardingThreshold的配置就會丟棄<=INFO日志。這里的坑點有兩個:

  1. 因為discardingThreshold,所以設置queueSize時容易踩坑。
    比如本案例最大日志并發1000,即便置queueSize為1000,同樣會導致日志丟失
  2. discardingThreshold參數容易有歧義,它不是百分比,而是日志條數。對于總容量10000隊列,若希望隊列剩余容量少于1000時丟棄,需配置為1000

neverBlock 默認false

意味總可能會出現阻塞。

  • discardingThreshold = 0,那么隊列滿時再有日志寫入就會阻塞
  • discardingThreshold != 0,也只丟棄≤INFO級日志,出現大量錯誤日志時,還是會阻塞

queueSize、discardingThreshold和neverBlock三參密不可分,務必按業務需求設置:

  • 若優先絕對性能,設置neverBlock = true,永不阻塞
  • 若優先絕不丟數據,設置discardingThreshold = 0,即使≤INFO級日志也不會丟。但最好把queueSize設置大一點,畢竟默認的queueSize顯然太小,太容易阻塞。
  • 若兼顧,可丟棄不重要日志,把queueSize設置大點,再設置合理的discardingThreshold

以上日志配置最常見兩個誤區

再看日志記錄本身的誤區。

使用日志占位符就無需判斷日志級別?

SLF4J的{}占位符語法,到真正記錄日志時才會獲取實際參數,因此解決了日志數據獲取的性能問題。
這說法對嗎?

  • 驗證代碼:返回結果耗時1秒
    搞懂Java日志級別,重復記錄、丟日志問題

若記錄DEBUG日志,并設置只記錄>=INFO級日志,程序是否也會耗時1秒?
三種方法測試:

  • 拼接字符串方式記錄slowString
  • 使用占位符方式記錄slowString
  • 先判斷日志級別是否啟用DEBUG。

搞懂Java日志級別,重復記錄、丟日志問題
搞懂Java日志級別,重復記錄、丟日志問題

前倆方式都調用slowString,所以都耗時1s。且方式二就是使用占位符記錄slowString,這種方式雖允許傳Object,不顯式拼接String,但也只是延遲(若日志不記錄那就是省去)日志參數對象.toString()字符串拼接的耗時。

本案例除非事先判斷日志級別,否則必調用slowString。
所以使用{}占位符不能通過延遲參數值獲取,來解決日志數據獲取的性能問題。

除事先判斷日志級別,還可通過lambda表達式延遲參數內容獲取。但SLF4J的API還不支持lambda,因此需使用Log4j2日志API,把Lombok的@Slf4j注解替換為**@Log4j2**注解,即可提供lambda表達式參數的方法:
搞懂Java日志級別,重復記錄、丟日志問題

這樣調用debug,簽名Supplier<?>,參數就會延遲到真正需要記錄日志時再獲取:
搞懂Java日志級別,重復記錄、丟日志問題
搞懂Java日志級別,重復記錄、丟日志問題
搞懂Java日志級別,重復記錄、丟日志問題
搞懂Java日志級別,重復記錄、丟日志問題

所以debug4并不會調用slowString方法
搞懂Java日志級別,重復記錄、丟日志問題

只是換成Log4j2 API,真正的日志記錄還是走的Logback,這就是SLF4J適配的好處。

總結

  • SLF4J統一了Java日志框架。在使用SLF4J時,要理清楚其橋接API和綁定。若程序啟動時出現SLF4J錯誤提示,那可能是配置問題,可使用Maven的dependency:tree命令梳理依賴關系。
  • 異步日志解決性能問題,是用空間換時間。但空間畢竟有限,當空間滿,要考慮阻塞等待or丟棄日志。如果更希望不丟棄重要日志,那么選擇阻塞等待;如果更希望程序不要因為日志記錄而阻塞,那么就需要丟棄日志。
  • 日志框架提供的參數化日志記錄方式不能完全取代日志級別判斷。若你的日志量很大,獲取日志參數代價也很大,就要判斷日志級別,避免不記錄日志也要耗時獲取日志參數。

贊(0)
分享到: 更多 (0)
?
網站地圖   滬ICP備18035694號-2    滬公網安備31011702889846號
久久精品五月,日韩不卡视频在线观看,国产精品videossex久久发布 ,久久av综合
亚洲欧美日韩国产一区二区| 麻豆精品在线| 天堂av在线| 四虎国产精品免费观看| 都市激情国产精品| 精品亚洲美女网站| 欧美日韩精品一本二本三本| 亚洲精品1区2区| 日韩在线一二三区| 天堂久久av| 国产精品久久久久久久久久妞妞| 国产精品videossex| 国产伦精品一区二区三区视频| 国产精品视频一区视频二区| 日韩中出av| 成人台湾亚洲精品一区二区| 色乱码一区二区三区网站| 99久久婷婷这里只有精品| 美女少妇全过程你懂的久久| 久久福利影视| 国产精品久久久久久久久免费高清 | 国产精品美女久久久浪潮软件| 亚洲无线观看| 国产精品一二| 日韩高清成人| 欧美va天堂| 少妇精品久久久一区二区| 精品美女久久| 99视频一区| 久久99久久久精品欧美| 久久精品国产68国产精品亚洲| 亚洲乱码一区| 理论片午夜视频在线观看| 久久久久久久久丰满| 亚洲欧洲日韩| 久久精品国产亚洲一区二区三区| 午夜日韩av| 国产精品久久久久av蜜臀| 成人免费电影网址| 日韩毛片一区| 国产一区二区三区探花| 91久久黄色| 国产专区精品| 亚洲精品免费观看| 欧美成人基地| 日本色综合中文字幕| 毛片在线网站| 欧美精品中文字幕亚洲专区| 91精品推荐| 国产精品亚洲产品| 免费日韩精品中文字幕视频在线| 精品视频自拍| 日韩高清不卡在线| 日韩亚洲国产欧美| 国产成人久久精品麻豆二区 | 天堂资源在线亚洲| 国产精品久久久一区二区| 国产一区91| 不卡福利视频| 免费在线亚洲| 日本国产亚洲| 国产精品免费看| sm久久捆绑调教精品一区| 亚洲精品九九| 狠狠干综合网| 99热精品久久| 久久国产精品色av免费看| 精品成人免费一区二区在线播放| 国产精品亲子伦av一区二区三区| 国产日韩专区| 精品免费av在线| 美女视频黄免费的久久| 日本成人在线视频网站| 欧美日韩黑人| 日本久久成人网| 精品久久久中文字幕| 国产日韩一区| 69堂免费精品视频在线播放| 一区二区电影在线观看| 夜久久久久久| 亚洲福利一区| 国产一区二区三区不卡视频网站| 日本高清久久| 亚洲精品乱码日韩| 亚洲欧美日韩专区| 国产婷婷精品| 亚洲少妇一区| 亚洲一区中文| 欧美午夜不卡| 欧美日韩激情| 欧美日韩第一| 一区在线免费观看| 91久久久精品国产| 自拍日韩欧美| 国产精品婷婷| 久久亚洲国产精品一区二区| 91精品一区二区三区综合| 日韩福利一区| 性欧美xxxx免费岛国不卡电影| 私拍精品福利视频在线一区| 日韩高清不卡| 婷婷综合网站| 亚洲欧美视频| 亚洲人亚洲人色久| 青青国产91久久久久久| 欧美日韩一区二区三区不卡视频| 欧美一级一区| 精品久久视频| 91精品国产成人观看| 偷拍欧美精品| 亚洲欧美一级| 少妇精品久久久一区二区| 日韩一区二区三区精品| 国产欧美高清视频在线| 久久不见久久见中文字幕免费| 麻豆精品蜜桃视频网站| 成人影视亚洲图片在线| 欧美天堂视频| 伊人久久亚洲影院| 亚洲精华国产欧美| 日韩有吗在线观看| 国产麻豆一区二区三区精品视频| 欧美激情久久久久久久久久久| 日韩.com| 久久久久99| 一区二区亚洲视频| 亚洲一级淫片| 国产精品玖玖玖在线资源| 国产精品不卡| 红桃视频国产精品| 欧美久久亚洲| 国产精品精品国产一区二区| 亚洲精品888| 欧美一区二区三区免费看| 欧美国产偷国产精品三区| 奶水喷射视频一区| 国产女人18毛片水真多18精品| 日产精品一区二区| 久久亚洲精品伦理| 欧美成人精品一级| 亚洲午夜黄色| 欧美在线精品一区| 成人影视亚洲图片在线| 99国产精品| 久久中文字幕导航| 蜜桃tv一区二区三区| 日韩国产欧美一区二区三区| 国产精品成人a在线观看| 亚洲综合丁香| 精品一区二区男人吃奶| 99国产精品免费视频观看| 在线观看一区| 久草免费在线视频| 日韩欧美2区| 久久精品免费一区二区三区| 香蕉久久久久久| 香蕉视频亚洲一级| 夜夜嗨一区二区| 久久精品三级| 99久久九九| 国产伦精品一区二区三区视频| 欧美日韩一二三四| 麻豆精品国产91久久久久久| 国产亚洲午夜| 日韩久久精品网| 日韩和欧美一区二区| 国产字幕视频一区二区| 国产精品三级| 亚洲一级大片| 99久久精品网| 精品美女视频 | 99久久九九| 麻豆国产欧美日韩综合精品二区| 亚洲一区二区三区四区五区午夜| 加勒比视频一区| 日韩高清电影一区| 免费精品视频| 91精品蜜臀一区二区三区在线| 国产精品任我爽爆在线播放| 蜜臀av在线播放一区二区三区| 精品视频免费| 欧美日本一区| 日本不卡一二三区黄网| 国产偷自视频区视频一区二区| 在线观看精品| 亚洲天堂资源| 成人午夜网址| 狂野欧美性猛交xxxx| 亚洲bt欧美bt精品777| 亚洲免费在线| 国产精品88久久久久久| 麻豆精品在线| 国产日韩一区二区三区在线| 三级欧美韩日大片在线看| 亚洲精品小说| 天堂√8在线中文| 国产伦久视频在线观看| 精品一区二区三区免费看| 国产精品宾馆| 国产精品成人国产|