天天看點

日志架構總結(一)一、日志概述二、Java日志架構三、實際應用及區分

日志架構總結(一)

  • 一、日志概述
  • 二、Java日志架構
    • 2.1、日志疊代過程
    • 2.2、Logback vs Log4j2
  • 三、實際應用及區分
    • 3.1、 Logback 實踐:
      • 3.1.1、快速入門
      • 3.1.2、logback.xml 配置檔案分析
    • 3.2、 Log4j2 實踐:
      • 3.2.1 使用
      • 3.2.2 log4j2.xml 配置及說明
      • 3.2.3 log4j2 最佳實踐
        • 3.2.3.1 日志分類
        • 3.2.3.2 日志模式-同步/異步
        • 3.2.3.3 日志滾動和清除政策
        • 3.2.3.4 其他注意事項和調優
      • 3.2.4 log4j-disruptor等待政策
      • 3.2.5 其他建議

一、日志概述

一個在生産環境裡運作的程式如果沒有日志是很讓維護者提心吊膽的,有太多雜亂又無意義的日志也是令人傷神。程式出現問題時候,從日志裡如果發現不了問題可能的原因是很令人受挫的。本文想讨論的是如何在Java程式裡寫好日志。

一般來說日志分為兩種:業務日志和異常日志,使用日志我們希望能達到以下目标:

1、對程式運作情況的記錄和監控;

2、在必要時可詳細了解程式内部的運作狀态;

3、對系統性能的影響盡量小;

二、Java日志架構

java裡常見的日志庫有java.util.logging(JDKlog)、Apache log4j、log4j2、logback、slf4j等等,這麼多的日志架構裡如何選擇。

2.1、日志疊代過程

首先需要梳理的是日志接口,以及日志接口對應的實作。然後再考慮如何選擇使用哪個日志架構。

下圖說明了幾個日志架構的關系:

日志架構總結(一)一、日志概述二、Java日志架構三、實際應用及區分

上述關系圖,大緻就是兩個 API 接口 + 4個實作架構。我們應用服務調用日志架構,隻需遵循 API 接口規範,配置調用即可。

現在了解下上面接口及架構疊代曆史程序,如下圖:

日志架構總結(一)一、日志概述二、Java日志架構三、實際應用及區分
  • Log4j

    Log4j : 是apache下一個功能非常豐富的java日志庫實作,Log4j應該是出現比較早而且最受歡迎的java日志元件,它是基于java的開源的日志元件。Log4j的功能非常強大,通過Log4j可以把日志輸出到控制台、檔案、使用者界面。也可以輸出到作業系統的事件記錄器和一些系統常駐程序。值得一提的是:Log4j可

    以允許你非常便捷地自定義日志格式和日志等級,可以幫助開發人員全方位的掌控自己的日志資訊是一個日志開源架構。

    這個是最先出現流行的日志架構。

  • Java.util.logging

    Log4j 流行之時,java自身也不忘在這方面發力,于是java 1.4 之後,java原生庫也寫了一套日志架構 即是 Java.util.logging。

    此時,也有很多廠家的日志架構在共同競争,各自為營,互相相容性很差。導緻應用軟體API 在互相調用受阻。于是,apache 就站出來,設計了一套公共接口 – commons-logging ,來相容各方面的日志架構,便于實際開發程序;

  • Commons-logging(jcl)

    1、 在log4j , java.util.logging 等問世之後,相對于日志輸出就簡化很多,但是由于沒有統一規範,開發者調用這些架構也是很麻煩,互相相容性也很差(不同廠家的方法各不一樣),同時對日志架構疊代更新維護帶來不确定性,于是 apache 為規範日志輸出,定義一套标準日志API接口 ---- commons-logging API 接口。開發者,隻需關注接口,而不需看重實作細節,極大提供效率。

    2、盡管 commons-logging(jcl) 也為衆多日志實作庫提供了統一的接口,作用和slf4j類似。它允許運作時綁定任意的日志庫。但commons-loggins對log4j和java.util.logging的配置問題相容性不太好,還會遇到類加載問題。是以當時log4j的作者CEKI又創作了 SLF4j API 接口。

  • SLF4j:

    1、它是基于API的java日志架構,slf4j提供了簡單統一的日志記錄的接口,開發者在配置部署時隻需要是吸納這個接口就能是實作日志功能。它自身并沒有提供具體的日志解決方案,它是負責服務于各種各樣的日志系統,允許使用者在部署應用上使用自己常用的日志架構。也就是說,SLF4j是一個抽象層,它提供了衆多的擴充卡能是配合其他所有開源日志架構。

    2、為了考慮其他項目會使用大量的第三方庫,而第三方庫使用的日志架構又各不相同,不同的日志架構又需要不同的配置,不同配置就會導緻日志輸出到不同的位置。是以我們就需要一個可以将日志level、日志輸出等統一管理,而slf4j的擴充卡又對各種日志都實作了接管,接管後就可以統一配置這些第三方庫中使用的日志。

  • logback

    對比 Log4j的優勢:

      1、更快的實作:Logback的核心重寫了,在一些關鍵執行路徑上性能提升10倍以上。而且logback不僅性能提升了,初始化記憶體加載也更小了。

      2、非常充分的測試:Logback經過了幾年,數不清小時的測試。Logback的測試完全不同級别的。

      3、Logback-classic非常自然實作了SLF4j:Logback-classic實作了SLF4j。在使用SLF4j中,你都感覺不到logback-classic。而且因為logback-classic非常自然地實作了slf4j , 所 以切換到log4j或者其他,非常容易,隻需要提供成另一個jar包就OK,根本不需要去動那些通過SLF4JAPI實作的代碼。

      4、非常充分的文檔 官方網站有兩百多頁的文檔。

      5、自動重新加載配置檔案,當配置檔案修改了,Logback-classic能自動重新加載配置檔案。掃描過程快且安全,它并不需要另外建立一個掃描線程。這個技術充分保證了應用程式能跑得很歡在JEE環境裡面。

      6、Lilith是log事件的觀察者,和log4j的chainsaw類似。而Lilith還能處理大數量的log資料 。

      7、謹慎的模式和非常友好的恢複,在謹慎模式下,多個FileAppender執行個體跑在多個JVM下,能夠安全地寫道同一個日志檔案。RollingFileAppender會有些限制。Logback的FileAppender和它的子類包括 RollingFileAppender能夠非常友好地從I/O異常中恢複。

      8、配置檔案可以處理不同的情況,開發人員經常需要判斷不同的Logback配置檔案在不同的環境下(開發,測試,生産)。而這些配置檔案僅僅隻有一些很小的不同,可以通過,和來實作,這樣一個配置檔案就可以适應多個環境。

      9、Filters(過濾器)有些時候,需要診斷一個問題,需要打出日志。在log4j,隻有降低日志級别,不過這樣會打出大量的日志,會影響應用性能。在Logback,你可以繼續 保持那個日志級别而除掉某種特殊情況,如alice這個使用者登入,她的日志将打在DEBUG級别而其他使用者可以繼續打在WARN級别。要實作這個功能隻需加4行XML配置。可以參考MDCFIlter 。

      10、SiftingAppender(一個非常多功能的Appender):它可以用來分割日志檔案根據任何一個給定的運作參數。如,SiftingAppender能夠差別日志事件跟進使用者的Session,然後每個使用者會有一個日志檔案。

      11、自動壓縮已經打出來的log:RollingFileAppender在産生新檔案的時候,會自動壓縮已經打出來的日志檔案。壓縮是個異步過程,是以甚至對于大的日志檔案,在壓縮過程中應用不會受任何影響。

      12、堆棧樹帶有包版本:Logback在打出堆棧樹日志時,會帶上包的資料。

      13、自動去除舊的日志檔案:通過設定TimeBasedRollingPolicy或者SizeAndTimeBasedFNATP的maxHistory屬性,你可以控制已經産生日志檔案的最大數量。如果設定maxHistory 12,那那些log檔案超過12個月的都會被自動移除。

  • Log4j2

    Log4j2 的特性:

      1、API分離:Log4j的API與實作是分開的,進而使應用程式開發人員可以清楚地了解他們可以使用哪些類和方法,同時確定向前的相容性。這允許Log4j團隊以相容的方式安全地改進實施。

      2、性能提升:Log4j 2包含基于LMAX Disruptor庫的下一代異步記錄器。在多線程方案中,與Log4j 1.x和Logback相比,異步Logger的吞吐量高18倍,延遲降低了幾個數量級。

      3、支援多種API:Log4j 2 API将提供最佳性能,而Log4j 2提供對Log4j 1.2,SLF4J,Commons Logging和java.util.logging(JUL)API的支援。

      4、避免鎖定:編碼為Log4j 2 API的應用程式始終可以選擇使用任何SLF4J相容庫作為其Log4j-to-slf4j擴充卡的記錄器實作。利用jdk1.5并發的特性,減少了死鎖的發生;

      5、自動重載配置:與Logback一樣的是,Log4j 2可以在修改後自動重新加載其配置。與Logback不同的是,它在進行重新配置時不會丢失日志事件。丢資料這種情況少,可以用來做審計功能。而且自身内部報的exception會被發現,但是logback和log4j不會。

      6、進階篩選:與Logback一樣,Log4j 2支援基于上下文資料,标記,正規表達式和Log事件中的其他元件進行過濾。可以指定過濾以将所有事件應用到所有事件,然後再傳遞給Logger或事件通過Appender。此外,過濾器還可以與Loggers關聯。與Logback不同,您可以在任何這些情況下使用通用的Filter類。

      7、插件架構:Log4j使用插件模式來配置元件。這樣,您無需編寫代碼即可建立和配置Appender,Layout,Pattern Converter等。Log4j自動識别插件,并在配置引用它們時使用它們。

      8、物業支援:您可以在配置中引用屬性,Log4j将直接替換它們,或者Log4j将它們傳遞給将動态解析它們的基礎元件。屬性來自配置檔案中定義的值,系統屬性,環境變量,ThreadContext映射以及事件中存在的資料。使用者可以通過添加自己的查找插件來進一步自定義屬性提供程式。

      9、Java 8 Lambda支援:以前,如果建構日志消息的成本很高,則通常會在建構消息之前顯式檢查是否啟用了請求的日志級别。在Java 8上運作的用戶端代碼可以受益于Log4j的lambda支援。如果未啟用請求的日志級别,由于Log4j不會評估lambda表達式,是以可以用更少的代碼獲得相同的效果。

      10、自定義日志級别:在Log4j 2中,可以通過代碼或配置輕松定義自定義日志級别。不需要子類。

      11、無垃圾:在穩态日志記錄期間,Log4j 2 在獨立應用程式中是無垃圾的,而在Web應用程式中是低垃圾的。這樣可以減少垃圾收集器上的壓力,并可以提供更好的響應時間性能。【擁有号稱能夠減少 JVM 垃圾回收停頓時間的 Garbage-free(無垃圾模式)】

      12、與應用伺服器內建:版本2.10.0引入了子產品log4j-appserver,以改善與Apache Tomcat和Eclipse Jetty的內建。

      13、啟用雲:2.12.0版引入了對通過Lookup通路Docker容器資訊以及通過Spring Cloud Configuration通路和更新Log4j配置的支援。

2.2、Logback vs Log4j2

日志架構大戰随着 SLF4j 的一統天下而落下帷幕,但 SLF4j 僅僅是接口,實作方面, logback 與 log4j2 仍然難分高下,接下來聊聊,日志架構實作到底是該選擇 Log4j2 還是 Logback。這篇文章我們将從功能、API 設計、可擴充性、性能四個方面展開讨論。

  • 生态
  1. 老牌的 Log4j2 憑借着入場早、背靠 Apache 兩大優勢有着不錯的使用者支援,官網文檔也很完善。
  2. 新生的 Logback 憑借着 SLF4j 的原生實作以及被 Spring Boot 欽點的日志架構(Spring 也提供了Log4j2 的 starter,切換依賴即可完成更換日志架構,前文講過,此處不再贅述),同樣也擁有着很好的前景。
  3. 社群支援方面,Log4j2 作為 Apache 頂級項目,支援度應該是很不錯的,Logback 作為Ceki創業後的産物,也能有很好的保證,二者生态可謂不相上下;
  • 功能:我們從使用者的角度可以分為:配置、使用、以及獨有特性。
  1. 配置檔案方面,Log4j 提供了更多的配置檔案配置方式,Log4j2 支援 properties、YAML、JSON、XML四種,Logback 則支援 XML 與 groovy 兩種方式;
  2. Appender 方面,兩者均支援自定義擴充 Appender ,Log4j2 并且提供了幾乎全部場景的 Appender,檔案、控制台、JDBC、NoSql、MQ、Syslog、Socket、SMTP等,Logback提供 Appender 略少于 Log4j2,提供了檔案、控制台、資料庫、Syslog、Socket、SMTP等,動态化輸出方面,Log4j2 提供了ScriptAppenderSelector,Logback 則提供了 Evaluator 與 SiftingAppender(兩者均可以用于判斷并選擇對應的 Appender);
  3. 獨有特性方面,Logback 支援 Receivers, 可以接收其他 Logback 的 Socket Appender 輸出,Logbak 還擁有 logback-access 子產品,可以用來與 Servlet容器(如 Tomcat 和 Jetty)內建,提供 http 通路日志功能;Log4j2 則擁有号稱能夠減少 JVM 垃圾回收停頓時間的 Garbage-free(無垃圾模式),Log4j2 API 支援使用 Java 8 lambda,SLF4j 則在 2.0 版本提供流式(Fluent)API 同時支援 lambda;
  • API 設計及可擴充性

    如前文所說,SLF4j 則在 2.0 版本提供流式(Fluent)API ,屆時Logback将會原生實作(理論上會比動态轉譯過去要好),而 Log4j2 并沒有提供支援。擴充方面,Logback 采用配置檔案中直接寫對應實作(class=“ch.qos.logback.core.rolling.RollingFileAppender”)來自定義實作擴充,Log4j2 采用插件機制,無需配置,但比較複雜,個人認為 Logback 反而清晰一些。

  • 性能

    對于高并發,大量日志輸出,Log4j2 确實有一定優勢。因為其内部的隊列使用的是disruptor。異步輸出性能提升不少,是實話!

    但是,對于日志架構應用,我們要求可能并不需要那麼高,即對于一般的日志輸出,Logback 與 Log4j2 性能方面差不多;

    具體參考: https://www.jianshu.com/p/359b14067b9e,http://www.cainiaoxueyuan.com/bc/17731.html

  • 總結

    Logback 使用更簡單、Log4j2 功能更強大,如果不是深度使用,兩者并不會有太大差别,并且在使用SLF4j的時候可以無縫切換。建議,不必糾結選型,按照偏好選擇即可。

三、實際應用及區分

3.1、 Logback 實踐:

3.1.1、快速入門

第1步: 對于一般 Maven 項目,引入依賴

<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-core</artifactId>
    <version>1.2.3</version>
</dependency>
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.3</version>
</dependency>
           

第2步:導入 logback.xml 即是logback日志輸出配置檔案,如下

<?xml version="1.0" encoding="UTF-8"?>  
<configuration debug="true">
	<!-- <property name="LOG_HOME" value="/home/core/project"/> -->
	<property name="LOG_HOME" value="D:/log/" />
	<property name="APP_NAME" value="lolo"/>
	<!-- 控制台輸出-->
	<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
		<layout class="ch.qos.logback.classic.PatternLayout">
			<Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{64}[%line] - %msg%n</Pattern>
		</layout>
	</appender>
	<!-- 按照每天生成日志檔案 -->  
	<appender name="DATELOG" class="ch.qos.logback.core.rolling.RollingFileAppender">  
		<File>${LOG_HOME}${APP_NAME}.log</File>
		<filter class="ch.qos.logback.classic.filter.LevelFilter"> 
            <level>info</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch> 
		</filter>
        <rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
          <FileNamePattern>${LOG_HOME}/${APP_NAME}.%i.bak</FileNamePattern>
            <MinIndex>1</MinIndex>
            <MaxIndex>30</MaxIndex>
        </rollingPolicy>
        <triggeringPolicy
            class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
            <MaxFileSize>100MB</MaxFileSize>
        </triggeringPolicy>
        <layout class="ch.qos.logback.classic.PatternLayout">
            <Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{64}[%line] - %msg%n</Pattern>
        </layout>
	</appender>
	<!-- 按照每天生成日志檔案   錯誤資訊 -->  
	<appender name="DATELOG_ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">  
		<File>${LOG_HOME}${APP_NAME}.error.log</File>
		<filter class="ch.qos.logback.classic.filter.LevelFilter"> 
			<level>ERROR</level> 
			<onMatch>ACCEPT</onMatch> 
			<onMismatch>DENY</onMismatch> 
		</filter>
        <rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy"><FileNamePattern>${LOG_HOME}/${APP_NAME}.error.%i.bak</FileNamePattern>
            <MinIndex>1</MinIndex>
            <MaxIndex>30</MaxIndex>
        </rollingPolicy>
        <triggeringPolicy
            class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
            <MaxFileSize>100MB</MaxFileSize>
        </triggeringPolicy>
        <layout class="ch.qos.logback.classic.PatternLayout">
            <Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{64}[%line] - %msg%n</Pattern>
        </layout> 
	</appender> 
	<logger name="com.jieshun.jscp.p" level="DEBUG" additivity="false" >
	    <appender-ref ref="CONSOLE" />
	    <appender-ref ref="DATELOG" />
		<appender-ref ref="DATELOG_ERROR" />
	</logger>
	<root level="INFO" >
	    <appender-ref ref="CONSOLE" />
	    <appender-ref ref="DATELOG" />
		<appender-ref ref="DATELOG_ERROR" />
	</root>
</configuration>
           

第3步:再代碼調用

1) 可以使用 lomback 的@SLf4j 注解,即可直接使用預設的 log 對象;

@Slf4j
public class TestDemo {
	@Test
    public void testDateStr2TimeStamp() throws Exception{
        String dateStr = "2020-07-13 11:16:00";
        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date date = formatter.parse(dateStr);
        log.info(" 2020-07-13 11:16:00 to times = {}",date.getTime());
//        System.out.println(date.getTime());
    }
}
           
注意調用 @Slf4j 注解時,需要引入 lomback 依賴!
<dependency>
    <groupId>org.projectlombok</groupId>
     <artifactId>lombok</artifactId>
     <version>1.18.12</version>
     <scope>provided</scope>
</dependency>
           

2)不使用 @Slf4j 注解,那麼在類的首行就要自定義一個日志輸出對象

public class TestDemo {
	// 使用 static 修飾,獨立類對象之外,為所有類的對象共用
    private static Logger logger = LoggerFactory.getLogger(TestDemo.class); // TestDemo => Person

	@Test
    public void testDateStr2TimeStamp() throws Exception{
        String dateStr = "2020-07-13 11:16:00";
        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date date = formatter.parse(dateStr);
        logger.info(" 2020-07-13 11:16:00 to times = {}",date.getTime());
//        System.out.println(date.getTime());
    }
}

========== 輸出的結果 ==========
14:03:09,386 |-INFO in ch.qos.logback.classic.joran.action.ConfigurationAction - End of configuration.
14:03:09,387 |-INFO in ch.qos.logback.classic.joran.JoranConfigurator@36d4b5c - Registering current configuration as safe fallback point
2020-07-24 14:03:09.394 [main] INFO  com.lolo.entity.Person[51] -  2020-07-13 11:16:00 to times = 1594610160000
           
【注意】
  1. Logger logger 對象對應的類為:

    org.slf4j.Logger

    ; 在我們輸入 Logger 可能有一堆相同類名對象,請注意使用

    org.slf4j.Logger

    。因為前面已講過,Slf4j 一統天下。使用此接口,友善後續任意日志架構移植和切換;
  2. LoggerFactory.getLogger(TestDemo.class); 中的 class 類,應為日志所在類的名稱,不要随意書寫。若像上面代碼寫成 Person.class ,那麼日志輸出就成了

    ... com.lolo.entity.Person[51] ...

    ,導緻我們來看日志定位不明朗不準确;

3.1.2、logback.xml 配置檔案分析

<?xml version="1.0" encoding="UTF-8"?>

<!-- 1. configuration:
    scan:當此屬性設定為true時,配置檔案如果發生改變,将會被重新加載,預設值為true。
    scanPeriod:設定監測配置檔案是否有修改的時間間隔,如果沒有給出時間機關,預設機關是毫秒。當scan為true時,此屬性生效。預設的時間間隔為1分鐘。
    debug: 當此屬性設定為true時,将列印出logback内部日志資訊,實時檢視logback運作狀态。預設值為false。 若true logback内部都是穩定,一般情況不會有其相關日志
    一般情況采用預設設定即可!
-->
<configuration scan="true" scanPeriod="60 seconds" debug="false">
    <!-- 2. contextName:
        用來設定上下文名稱,每個logger都關聯到logger上下文,預設上下文名稱為default。
        但可以使用<contextName>設定成其他名字,用于區分不同應用程式的記錄。一旦設定,不能修改。
        作用不大,真實有效的日志,沒有特别處理,不會帶有上下文的名稱。在項目啟動時,會看到些許日志帶上了上下文名稱,
        如 ch.qos.logback.classic.LoggerContext[myAppName]
    -->
<!--    <contextName>myAppName</contextName>-->

    <!-- 3. timestamp:
        擷取時間戳字元串,他有兩個屬性key和datePattern
    key: 辨別此<timestamp> 的名字;
    datePattern: 設定将目前時間(解析配置檔案的時間)轉換為字元串的模式,遵循java.txt.SimpleDateFormat的格式。
        一般也無需設定。 後面的 layout 下的 Pattern 可以自動比對時間戳
    -->
    <timestamp key="bySecond" datePattern="yyyyMMdd'T'HHmmss"/>
    <contextName>${bySecond}</contextName>
    <!-- 4. property:
        用來定義變量值,它有兩個屬性name和value,通過<property>定義的值會被插入到logger上下文中,可以使“${}”來使用變量。
    -->
    <!-- <property name="LOG_HOME" value="/home/core/project"/> -->
    <property name="LOG_HOME" value="D:/log/" />
    <property name="APP_NAME" value="lolo"/>

    <!-- 5. appender:
        負責寫日志的元件,它有兩個必要屬性name和class。name指定appender名稱,class指定appender的全限定名.
        ConsoleAppender: 負責将日志寫入控制台;(常用)
        RollingFileAppender: 負責将日志寫入指定檔案,持久化,并且是循環輸出檔案; (常用)
        FileAppender:把日志添加到檔案,有以下子節點:(少用)
            <file>:被寫入的檔案名,可以是相對目錄,也可以是絕對目錄,如果上級目錄不存在會自動建立,沒有預設值。
            <append>:如果是 true,日志被追加到檔案結尾,如果是 false,清空現存檔案,預設是true。
            <encoder>:對記錄事件進行格式化。
            <prudent>:如果是 true,日志會被安全的寫入檔案,即使其他的FileAppender也在向此檔案做寫入操作,效率低,預設是 false。
    -->
    <!-- 控制台輸出-->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <!-- 5.1 encoder,layout
            格式化輸出: encoder, layout 都可以
        -->
        <encoder>
            <!-- 規則:輸出日志的logger名,可有一個整形參數,功能是縮短logger名,設定為0表示隻輸入logger最右邊點符号之後的字元串。
            %logger	    mainPackage.sub.sample.Bar	mainPackage.sub.sample.Bar
            %logger{0}	mainPackage.sub.sample.Bar	Bar
            %logger{5}	mainPackage.sub.sample.Bar	m.s.s.Bar
            %logger{10}	mainPackage.sub.sample.Bar	m.s.s.Bar
            %logger{15}	mainPackage.sub.sample.Bar	m.s.sample.Bar
            %logger{16}	mainPackage.sub.sample.Bar	m.sub.sample.Bar
            %logger{26}	mainPackage.sub.sample.Bar	mainPackage.sub.sample.Bar
            -->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{16}[%line] - %msg%n</pattern>
        </encoder>
<!--        <layout class="ch.qos.logback.classic.PatternLayout">-->
<!--            <Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{64}[%line] - %msg%n</Pattern>-->
<!--        </layout>-->
    </appender>

    <!-- 按照每天生成日志檔案 -->
    <appender name="DATELOG" class="ch.qos.logback.core.rolling.RollingFileAppender">

        <!--
            <file>:被寫入的檔案名,可以是相對目錄,也可以是絕對目錄,如果上級目錄不存在會自動建立,沒有預設值。
        -->
        <File>${LOG_HOME}/${APP_NAME}.log</File>
        <!--
            <append>:如果是 true,日志被追加到檔案結尾,如果是 false,清空現存檔案,預設是true。
        -->
        <append>true</append>
        <!-- LevelFilter: 級别過濾器,根據日志級别進行過濾。如果日志級别等于配置級别,過濾器會根據onMath 和 onMismatch接收或拒絕日志。有以下子節點:
            <level>:設定過濾級别
            <onMatch>:用于配置符合過濾條件的操作
            <onMismatch>:用于配置不符合過濾條件的操作
        -->
        <!--
            日志過濾,隻輸出 info 的日志
        -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>info</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <!--
            <rollingPolicy>:當發生滾動時,決定 RollingFileAppender 的行為,涉及檔案移動和重命名。

             TimeBasedRollingPolicy: 最常用的滾動政策,它根據時間來制定滾動政策,既負責滾動也負責出發滾動。

                    <fileNamePattern>:必要節點,包含檔案名及“%d”轉換符, “%d”可以包含一個java.text.SimpleDateFormat指定的時間格式,
                    如:%d{yyyy-MM}。如果直接使用 %d,預設格式是 yyyy-MM-dd。RollingFileAppender 的file位元組點可有可無,通過設定file,
                    可以為活動檔案和歸檔檔案指定不同位置,目前日志總是記錄到file指定的檔案(活動檔案),活動檔案的名字不會改變;如果沒設定file,
                    活動檔案的名字會根據fileNamePattern 的值,每隔一段時間改變一次。“/”或者“\”會被當做目錄分隔符。

                    <maxHistory>:可選節點,控制保留的歸檔檔案的最大數量,超出數量就删除舊檔案。假設設定每個月滾動,且<maxHistory>是6,
                    則隻儲存最近6個月的檔案,删除之前的舊檔案。注意,删除舊檔案是,那些為了歸檔而建立的目錄也會被删除。
        -->

        <!-- 每天生成一個日志檔案,儲存30天的日志檔案。
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>logFile.%d{yyyy-MM-dd}.log</fileNamePattern>
            <maxHistory>30</maxHistory>
        </rollingPolicy>
        -->

        <!-- FixedWindowRollingPolicy: 根據固定視窗算法重命名檔案的滾動政策。有以下子節點:
            <minIndex>:視窗索引最小值
            <maxIndex>:視窗索引最大值,當使用者指定的視窗過大時,會自動将視窗設定為12。
            <fileNamePattern >:必須包含“%i”例如,假設最小值和最大值分别為1和2,命名模式為 mylog%i.log,會産生歸檔檔案mylog1.log和mylog2.log。
            還可以指定檔案壓縮選項,例如,mylog%i.log.gz 或者 沒有log%i.log.zip
        -->
        <rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
            <FileNamePattern>${LOG_HOME}/${APP_NAME}.%i.bak</FileNamePattern>
            <MinIndex>1</MinIndex>
            <MaxIndex>30</MaxIndex>
        </rollingPolicy>
        <!--
            <triggeringPolicy >: 告知 RollingFileAppender 合适激活滾動。
            SizeBasedTriggeringPolicy: 檢視目前活動檔案的大小,如果超過指定大小會告知 RollingFileAppender 觸發目前活動檔案滾動。隻有一個節點:
                <maxFileSize>:這是活動檔案的大小,預設值是10MB。
        -->
        <triggeringPolicy
                class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
            <MaxFileSize>100MB</MaxFileSize>
        </triggeringPolicy>

        <!--
            <prudent>:當為true時,不支援FixedWindowRollingPolicy。
            支援TimeBasedRollingPolicy,但是有兩個限制,1不支援也不允許檔案壓縮,2不能設定file屬性,必須留白。
            預設:false
        -->
        <prudent>false</prudent>

        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36}[%line] - %msg%n</pattern>
        </encoder>
<!--        <layout class="ch.qos.logback.classic.PatternLayout">-->
<!--            <Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{64}[%line] - %msg%n</Pattern>-->
<!--        </layout>-->

    </appender>

    <!-- 按照每天生成日志檔案   錯誤資訊 -->
    <appender name="DATELOG_ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">

        <File>${LOG_HOME}${APP_NAME}.error.log</File>
        <!--
            日志過濾,隻輸出 error 的日志
        -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
            <FileNamePattern>${LOG_HOME}/${APP_NAME}.error.%i.bak</FileNamePattern>
            <MinIndex>1</MinIndex>
            <MaxIndex>30</MaxIndex>
        </rollingPolicy>
        <triggeringPolicy
                class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
            <MaxFileSize>100MB</MaxFileSize>
        </triggeringPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36}[%line] - %msg%n</pattern>
        </encoder>
<!--        <layout class="ch.qos.logback.classic.PatternLayout">-->
<!--            <Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{64}[%line] - %msg%n</Pattern>-->
<!--        </layout>-->
    </appender>


    <!-- 6. logger:
        用來設定某一個包或者具體的某一個類的日志列印級别、以及指定<appender>。<logger>僅有一個name屬性,一個可選的level和一個可選的addtivity屬性。
        name:用來指定受此loger限制的某一個包或者具體的某一個類。
        level:用來設定列印級别,大小寫無關:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,還有一個特俗值INHERITED或者同義詞NULL,代表強制執行上級的級别。
        如果未設定此屬性,那麼目前loger将會繼承上級的級别。
        addtivity:是否向上級loger傳遞列印資訊。預設是true,向上級傳遞。參考:https://blog.csdn.net/qq_36850813/article/details/83092051
    -->
    <logger name="com.jieshun.jscp.p" level="DEBUG" additivity="false" >
        <!-- 6.1 appender-ref:
            可以包含零個或多個<appender-ref>元素,辨別這個appender将會添加到這個logger
        -->
        <appender-ref ref="CONSOLE" />
        <appender-ref ref="DATELOG" />
        <appender-ref ref="DATELOG_ERROR" />
    </logger>

    <!-- 7. root: 類同 logger, 代表根節點。有且僅有一個的
            屬性 level:用來設定列印級别,大小寫無關:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,不能設定為INHERITED或者同義詞NULL。預設是DEBUG。
    -->
    <root level="INFO" >
        <!--
        包含零個或多個<appender-ref>元素,辨別這個appender将會添加 root
        -->
        <appender-ref ref="CONSOLE" />
        <appender-ref ref="DATELOG" />
        <appender-ref ref="DATELOG_ERROR" />
    </root>
</configuration>
           

3.2、 Log4j2 實踐:

3.2.1 使用

第1步: 引入依賴。

<dependency>
   <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-slf4j-impl</artifactId>
    <version>2.12.1</version>
</dependency>

<dependency>
    <groupId>com.lmax</groupId>
    <artifactId>disruptor</artifactId>
    <version>3.4.2</version>
</dependency>
           
1、一般需要 log4j-core, log4j-api, slf4j-api jar包。下面的 log4j-slf4j-impl 涵蓋所有,因為依賴傳遞,包含了log4j-core, log4j-api, slf4j-api 。若jar 包引入又問題,将會報如下錯:
# 1. 錯誤如下
 SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
 SLF4J: Defaulting to no-operation (NOP) logger implementation
 SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
# 2. 因為sl4j和log4j的不相容導緻的,具體處理方案如下: 
 1)若能重新引入依賴,那參考上面引入一個高版本的 log4j-slf4j-impl即可。
 2)若不想上面對應的操作,那就試探引入 對應 slf4j-log4j 版本橋接包,盡讓版本高些即可;
           

2、disruptor 的版本不能太低,否則會報:java.lang.NoSuchMethodError: com.lmax.disruptor.dsl.Disruptor.

3.、在依賴包時,預設會到mvn中央倉庫去拿資料。但是那可能很慢,導緻更新失敗或拉不到jar。此時,把 repositories 配置到 aliyun 倉庫即可

<repositories>
     <repository>
          <id>aliyun-repo</id>
         <name>nexus aliyun</name>
           <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
       </repository>
  </repositories>
           

3.2.2 log4j2.xml 配置及說明

<?xml version="1.0" encoding="UTF-8"?>
<!--日志級别以及優先級排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL
    1) Configuration後面的 status ,這個用于設定log4j2自身内部的資訊輸出,可以不設定 預設 info,
        當設定成trace時,你會看到log4j2内部各種詳細輸出
    2) monitorInterval:Log4j能夠自動檢測修改配置 檔案和重新配置本身,設定間隔秒數-->
<Configuration status="info" monitorInterval="30">
    <Properties>
        <Property name="baseLogDir">logs</Property>
        <!-- logback: %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{64}[%line] - %msg%n<-->
        <!-- log4j2:%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %c{64}[%L] - %msg%n/> -->

        <!-- logback 與 log4j2 格式化: 稍有區分。 以下 log4j2 标準
            %d{HH:mm:ss.SSS} 表示輸出到毫秒的時間
 	        %t 輸出目前線程名稱 (%thread] 也行,同logback)
  	        %-5level 輸出日志級别,-5表示左對齊并且固定輸出5個字元,如果不足在右邊補空格; (%level 也行)
  	        %logger 輸出logger名稱,因為Root Logger沒有名稱,是以沒有輸出;(%c 也行)
 	        %msg 日志文本
  	        %n 換行
        其他常用的占位符有:
  	        %F 輸出所在的類檔案名,如Log4j2Test.java
  	        %L 輸出行号;(不能 %Line。同時,顯示行,必須<logger> 配置includeLocation="true")
  	        %M 輸出所在方法名
  	        %l 輸出語句所在的行數, 包括類名、方法名、檔案名、行數;(生效:必須<logger> 配置includeLocation="true"))
        -->
        <Property name="pattern">%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %level %c{64}[%L] - %msg%n</Property>
    </Properties>
    <!-- 先定義所有的appender -->
    <Appenders>
        <!-- 這個輸出控制台的配置 -->
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout charset="UTF-8">
                <Pattern>${pattern}</Pattern>
            </PatternLayout>
        </Console>

        <!-- 應用info日志 -->
        <RollingRandomAccessFile name="APPINFO_APPENDER" fileName="${baseLogDir}/appinfo.log"
                                 filePattern="${baseLogDir}/appinfo.log.%d{yyyyMMddHH}.%i.gz">
            <PatternLayout>
                <Pattern>${pattern}</Pattern>
            </PatternLayout>
            <Policies>
                <SizeBasedTriggeringPolicy size="500MB" />
                <TimeBasedTriggeringPolicy interval="1" modulate="true" />
            </Policies>
            <Filters>
                <ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY" />
            </Filters>
            <!-- max=20辨別一小時内最多産生20個日志檔案 -->
            <DefaultRolloverStrategy max="20">
                <!-- 對于指定的路徑下的指定字尾的檔案,隻保留3天的日志檔案,那麼最多會有3天*24小時*20個日志檔案 -->
                <!-- 注意應用需要根據業務需求和磁盤大小評估需要保留的日志個數,對于500M的日志檔案來說,要根據應用日志的情況,觀察單個日志壓縮後檔案大小,并計算總大小需要的空間 -->
                <Delete basePath="${baseLogDir}" maxDepth="1">
                    <IfFileName glob="*.gz" />
                    <IfLastModified age="3d" />
                </Delete>
            </DefaultRolloverStrategy>
        </RollingRandomAccessFile>
    </Appenders>
    <Loggers>
        <!-- root是預設的logger,也是所有logger的父級logger,如果需要,可以在這裡配置一個檔案appender以列印外部元件日志 -->
        <AsyncRoot level="info">
            <AppenderRef ref="Console" />
        </AsyncRoot>
        <!-- 應用日志,采用異步模式,name根據實際的包名修改;生産環境中additivity建議設定為false以避免在root logger中重複列印;
            includeLocation 設定 false, 避免精确到 行 %L, 類%C, 方法%method 的定位,影響性能;
         -->
        <AsyncLogger name="com.lolo" level="info" includeLocation="false" additivity="false">
            <AppenderRef ref="APPINFO_APPENDER" />
        </AsyncLogger>
    </Loggers>
</Configuration>
           

3.2.3 log4j2 最佳實踐

3.2.3.1 日志分類

簡易日志配置如上。若我們需要對日志分類管理及存儲: ThresholdFilter 配置作适當修改即可!

<?xml version="1.0" encoding="UTF-8"?>
<!-- monitorInterval配置成一個正整數,則每隔這麼久的時間(秒),log4j2會重新整理一次配置。如果不配置則不會動态重新整理 -->
<Configuration status="INFO" monitorInterval="30">
    <Properties>
        <!-- 應用需要修改為合适的log路徑 -->
        <Property name="baseLogDir">logs</Property>
        <Property name="pattern">%d{yyyyMMdd-HHmmss.SSS} [%level] %c{1} - %msg%n</Property>
    </Properties>
    <!-- 先定義所有的appender -->
    <Appenders>
        <!-- 這個輸出控制台的配置 -->
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout>
                <Pattern>${pattern}</Pattern>
            </PatternLayout>
        </Console>
        <!-- 系統日志,可以作為root logger的appender,供列印一些中間件的日志 -->
        <RollingRandomAccessFile name="SYS_APPENDER" fileName="${baseLogDir}/server.log"
            filePattern="${baseLogDir}/server.log.%d{yyyyMMddHH}.%i.gz">
            <PatternLayout>
                <Pattern>${pattern}</Pattern>
            </PatternLayout>
            <Policies>
                <SizeBasedTriggeringPolicy size="200MB" />
                <TimeBasedTriggeringPolicy interval="1" modulate="true" />
            </Policies>
            <Filters>
                <ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY" />
            </Filters>
            <!-- max=6辨別一小時内最多産生6個日志檔案 -->
            <DefaultRolloverStrategy max="6">
                <!-- 對于指定的路徑下的指定字尾的檔案,隻保留1天的日志檔案,那麼最多會有24小時*6個日志檔案 -->
                <Delete basePath="${baseLogDir}" maxDepth="1">
                    <IfFileName glob="*.gz" />
                    <IfLastModified age="1d" />
                </Delete>
            </DefaultRolloverStrategy>
        </RollingRandomAccessFile>
        <!-- 應用info日志 -->
        <RollingRandomAccessFile name="APPINFO_APPENDER" fileName="${baseLogDir}/appinfo.log"
            filePattern="${baseLogDir}/appinfo.log.%d{yyyyMMddHH}.%i.gz">
            <PatternLayout>
                <Pattern>${pattern}</Pattern>
            </PatternLayout>
            <Policies>
                <SizeBasedTriggeringPolicy size="500MB" />
                <TimeBasedTriggeringPolicy interval="1" modulate="true" />
            </Policies>
            <Filters>
                <!-- 目前appender隻列印info日志,warn及以上日志忽略,由後面的appender決定是否需要列印 -->
                <ThresholdFilter level="WARN" onMatch="DENY" onMismatch="NEUTRAL" />
                <ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY" />
            </Filters>
            <!-- max=20辨別一小時内最多産生20個日志檔案 -->
            <DefaultRolloverStrategy max="20">
                <!-- 對于指定的路徑下的指定字尾的檔案,隻保留3天的日志檔案,那麼最多會有3天*24小時*20個日志檔案 -->
                <!-- 注意應用需要根據業務需求和磁盤大小評估需要保留的日志個數,對于500M的日志檔案來說,要根據應用日志的情況,觀察單個日志壓縮後檔案大小,并計算總大小需要的空間 -->
                <Delete basePath="${baseLogDir}" maxDepth="1">
                    <IfFileName glob="*.gz" />
                    <IfLastModified age="3d" />
                </Delete>
            </DefaultRolloverStrategy>
        </RollingRandomAccessFile>
        <!-- 應用錯誤日志 -->
        <RollingRandomAccessFile name="APPERROR_APPENDER" fileName="${baseLogDir}/apperror.log"
            filePattern="${baseLogDir}/apperror.log.%d{yyyyMMddHH}.%i.gz">
            <PatternLayout>
                <Pattern>${pattern}</Pattern>
            </PatternLayout>
            <Policies>
                <SizeBasedTriggeringPolicy size="500MB" />
                <TimeBasedTriggeringPolicy interval="1" modulate="true" />
            </Policies>
            <Filters>
                <ThresholdFilter level="WARN" onMatch="ACCEPT" onMismatch="DENY" />
            </Filters>
            <!-- max=10辨別一小時内最多産生10個日志檔案 -->
            <DefaultRolloverStrategy max="10">
                <!-- 對于指定的路徑下的指定字尾的檔案,隻保留3天的日志檔案,那麼最多會有3*24小時*10個日志檔案 -->
                <Delete basePath="${baseLogDir}" maxDepth="1">
                    <IfFileName glob="*.gz" />
                    <IfLastModified age="3d" />
                </Delete>
            </DefaultRolloverStrategy>
        </RollingRandomAccessFile>
    </Appenders>

    <Loggers>
        <!-- root是預設的logger,也就是公共的logger,供記錄一些不常列印的系統參數或者其他元件參數 -->
        <AsyncRoot level="WARN">
            <AppenderRef ref="Console" />
            <AppenderRef ref="SYS_APPENDER" />
        </AsyncRoot>
        <!-- 常列印的應用日志,建議獨立配置,并采用異步模式。name根據實際的包名修改,生産環境中additivity建議設定為false以避免在root logger中重複列印 -->
        <AsyncLogger name="com.lolo" level="INFO" includeLocation="false" additivity="false">
            <AppenderRef ref="APPINFO_APPENDER" />
            <AppenderRef ref="APPERROR_APPENDER" />
        </AsyncLogger>
    </Loggers>
</Configuration>
           

3.2.3.2 日志模式-同步/異步

log4j2提供了AsyncAppender和AsyncLogger以及全局異步,開啟方式如下

  • 同步模式:預設配置即為同步模式,即沒有使用任何AsyncAppender和AsyncLogger;
  • 全局異步:配置按照同步方式配,通過添加jvm啟動參數即可開啟全局異步,無需修改配置和應用;
# 全局異步 jvm 參數配置
-Dlog4j2.contextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
# 修正disruptor等待政策  4個政策: Block - Timeout - Sleep - Yield
-Dlog4j2.asyncLoggerWaitStrategy=Block
           
  • 混合異步:使用異步Logger和同步Logger的混合配置,且不開啟全局異步,即Logger配置中部分AsyncLogger,部分Logger;
日志模式使用注意事項:
  1. 如果使用異步,建議使用AsyncLogger實作而不是AsyncAppender
  2. 如果使用同步,AsyncLogger、AsyncAppender和全局異步隻能使用一種,不可以同時配置AsyncAppender和AsyncLogger,或者配置了異步的情況下啟用了全局異步

3.2.3.3 日志滾動和清除政策

log4j2提供了基于檔案大小的滾動政策和基于時間的滾動政策,也可以二者并用,這裡給出基于大小的滾動政策配置和基于大小/時間雙滾動政策配置。

  • 基于大小的滾動政策
<!--此處舉例為每500M滾動一個檔案,且最多保留20個檔案,具體需要根據應用的日志量和希望保留日志大小以及磁盤空間進行評估-->
  <RollingRandomAccessFile name="APPINFO_APPENDER" fileName="${baseLogDir}/appinfo.log"
      filePattern="${baseLogDir}/appinfo.log.%i.gz">
      <PatternLayout>
          <Pattern>${pattern}</Pattern>
      </PatternLayout>
      <Policies>
          <SizeBasedTriggeringPolicy size="500MB" />
      </Policies>
      <Filters>
          <!-- 目前appender隻列印info日志,warn及以上日志忽略,由後面的錯誤日志記錄 -->
          <ThresholdFilter level="WARN" onMatch="DENY" onMismatch="NEUTRAL" />
          <ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY" />
      </Filters>
      <!-- max=20表示最多保留20個日志檔案 -->
      <DefaultRolloverStrategy max="20"/>
  </RollingRandomAccessFile>
           
  • 基于大小/時間雙滾動滾動政策
<!--此處舉例為每小時滾動一個檔案且每500M滾動一個檔案,控制每小時最多保留20個檔案,總的檔案保留3天-->
  <!--具體需要根據應用的日志量和希望保留日志大小以及磁盤空間進行評估-->
  <RollingRandomAccessFile name="APPINFO_APPENDER" fileName="${baseLogDir}/appinfo.log"
      filePattern="${baseLogDir}/appinfo.log.%d{yyyyMMddHH}.%i.gz">
      <PatternLayout>
          <Pattern>${pattern}</Pattern>
      </PatternLayout>
      <Policies>
          <SizeBasedTriggeringPolicy size="500MB" />
          <TimeBasedTriggeringPolicy interval="1" modulate="true" />
      </Policies>
      <Filters>
          <!-- 目前appender隻列印info日志,warn及以上日志忽略,由後面的錯誤日志記錄 -->
          <ThresholdFilter level="WARN" onMatch="DENY" onMismatch="NEUTRAL" />
          <ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY" />
      </Filters>
      <!-- max=20表示一小時内最多保留20個日志檔案 -->
      <DefaultRolloverStrategy max="20">
          <!-- 對于指定的路徑下的指定字尾的檔案,隻保留3天的日志檔案,那麼最多會有3天*24小時*20個日志檔案 -->
          <!-- 注意應用需要根據業務需求和磁盤大小評估需要保留的日志個數,對于500M的日志檔案來說,要根據應用日志的情況,觀察單個日志壓縮後檔案大小,并計算總大小需要的空間 -->
          <Delete basePath="${baseLogDir}" maxDepth="1">
              <IfFileName glob="*.gz" />
              <IfLastModified age="3d" />
          </Delete>
      </DefaultRolloverStrategy>
  </RollingRandomAccessFile>
           
注意:控制總的日志留存時間的機制,需要log4j-2.5及以上的版本支援,如上面的 2.12版本!

3.2.3.4 其他注意事項和調優

  • 推薦在Configuration中添加monitorInterval以支援動态重新整理;
  • 推薦使用異步日志而不是同步日志,可以是混合異步也可以是全局異步

    不推薦配置AsyncAppender,如果需要混合異步,使用AsyncLogger;

  • PatternLayout不要使用%L、%C、%method等含有“位置資訊”的配置項,非常影響性能。同時logger配置中需要加上inclueLocation=“false”,這樣即使配置了位置資訊也會被忽略;
  • 推薦使用RollingRandomAccessFile做為appender;
  • 推薦基于大小和時間的雙重檔案滾動政策,并配合壓縮;

3.2.4 log4j-disruptor等待政策

相關測試驗證: 同步性能最差(這裡就不多說了),異步全局異步的性能接近異步appender的10倍,同樣是異步實作的,為何性能有如此大的差距?

參考:disruptor 算法

log4j2的日志使用了disruptor,其内部使用了基于ringbuffer的環形隊列,并且也有生産者消費者的概念。在消費者等待消息事件(也就是日志消息)時,其内部有一處等待政策的配置,配置項可以是Block/Timeout/Sleep/Yield,預設Timeout,不同政策的含義如下:

  • Block,在I/O線程等待時使用鎖和條件觸發機制,當cpu的優先級高于吞吐量和延遲的時候推建議使用。官方推薦在資源受限/虛拟化環境中使用;
  • Timeout,是Block政策的變種,它将定期從等待中被喚醒,確定了如果錯過了通知,disruptor消費者不會被永久阻塞,但會有較小的延遲(10ms);
  • Sleep,等待IO事件先自旋,然後調用Thread.yield(),最後park,在性能和cpu之間取得一個折中;
  • Yield,等待IO事件先自旋,然後調用用Thread.yield()等待日志事件,相較于Sleep可能會消耗更多的cpu;

log4j2預設政策是Timeout,在實際測試中,我們嘗試測試出不同政策下的cpu占用和延遲時間情況,但測試結果并沒有明顯的資料對比,是以這裡僅供參考,應用如果修改,需要結合場景做全面的測試。例如如果發現cpu占用較高,可以嘗試修改為Block或者其他政策并測試觀察。

# 修改disruptor wait政策的方法為(以修改為Block為例)
-Dlog4j2.asyncLoggerWaitStrategy=Block
           

3.2.5 其他建議

// 不推薦方式
logger.debug("this is log4j2, current time:" + System.currentTimeMillis());
// 推薦使用占位符
logger.debug("this is log4j2, current time:{}", System.currentTimeMillis());
           

上面兩行的代碼功能相同,但是前一句在每次執行時,無論我們的日志級别是不是debug即以上,每次都會生成一個新的字元串,字元串的字面值是字首加上系統目前時間。即使我們的日志級别配置成warn,該句也會産生一個新的字元串

後一句,當我們日志級别是debug或者小于debug的時候,才會真的建立一個完整的字元串,否則記憶體中隻會有包含了占位符的唯一一個字元串

如果這種情況非常多,那麼直接拼接字元串的方式對于記憶體的浪費就非常明顯了。若這時候使用占位符的方式,可以明顯的改善字元串的生成數量。當然也不是說任何地方都要使用占位符,因為占位符拼接成字元串,也是有開銷的,起碼要周遊占位符後面的參數。是以一般建議:

  • 對于一定要列印的日志,使用字元串拼接的方式(必要時引入StringBuilder輔助而不是一直加);
  • 對于調整級别才需要列印的日志,使用占位符的方式而不是直接拼接;