主要內容 1·學習java日誌體系及日誌工具的演進 2·瞭解日誌採集、處理、分析等各階段的常用中間件 3·學會搭建完整的elk日誌平臺 4·學習日誌打點,切麵,日誌文件等輸出手段 5·項目實戰,完成一訪問日誌鏈路的跟蹤 1、Java日誌體系 1.1 體系概述 1.1.1 日誌介面 JCL:Apach ...
主要內容
1·學習java日誌體系及日誌工具的演進
2·瞭解日誌採集、處理、分析等各階段的常用中間件
3·學會搭建完整的elk日誌平臺
4·學習日誌打點,切麵,日誌文件等輸出手段
5·項目實戰,完成一訪問日誌鏈路的跟蹤
1、Java日誌體系
1.1 體系概述
1.1.1 日誌介面
- JCL:Apache基金會所屬的項目,是一套Java日誌介面,之前叫Jakarta Commons Logging,後更名為Commons Logging,簡稱JCL
- SLF4J:Simple Logging Facade for Java,縮寫Slf4j,是一套簡易Java日誌門面,只提供相關介面,和其他日誌工具之間需要橋接
1.1.2 日誌實現
- JUL:JDK中的日誌工具,也稱為jdklog、jdk-logging,自Java1.4以來sun的官方提供。
- Log4j:隸屬於Apache基金會的一套日誌框架,現已不再維護
- Log4j2:Log4j的升級版本,與Log4j變化很大,不相容
- Logback:一個具體的日誌實現框架,和Slf4j是同一個作者,性能很好
1.2 發展歷程
1.2.1 上古時代
在JDK 1.3及以前,Java打日誌依賴System.out.println(), System.err.println()或者e.printStackTrace(),Debug日誌被寫到STDOUT流,錯誤日誌被寫到STDERR流。這樣打日誌有一個非常大的缺陷,非常機械,無法定製,且日誌粒度不夠細分。
代碼:
System.out.println("123");
System.err.println("456");
1.2.2 開創先驅
於是,Ceki Gülcü 於2001年發佈了Log4j,並將其捐獻給了Apache軟體基金會,成為Apache 基金會的頂級項目。後來衍生支持C, C++, C#, Perl, Python, Ruby等語言。
Log4j 在設計上非常優秀,它定義的Logger、Appender、Level等概念對後續的 Java Log 框架有深遠的影響,如今的很多日誌框架基本沿用了這種思想。Log4j 的性能是個問題,在Logback 和 Log4j2 出來之後,2015年9月,Apache軟體基金會宣佈,Log4j不再維護,建議所有相關項目升級到Log4j2
pom:
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>apache-log4j-extras</artifactId>
<version>1.2.17</version>
</dependency>
配置:
log4j.rootLogger=debug
#console
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern= log4j:[%d{yyyy-MM-dd HH:mm:ss a}]:%p %l%m%n
#dailyfile
log4j.appender.dailyfile=org.apache.log4j.DailyRollingFileAppender
log4j.appender.dailyfile.DatePattern='_'yyyy-MM-dd'.log'
log4j.appender.dailyfile.File=./log4j.log
log4j.appender.dailyfile.Append=true
log4j.appender.dailyfile.Threshold=INFO
log4j.appender.dailyfile.layout=org.apache.log4j.PatternLayout
log4j.appender.dailyfile.layout.ConversionPattern=log4j:[%d{yyyy-MM-dd HH:mm:ss a}] [Thread: %t][ Class:%c >> Method: %l ]%n%p:%m%n
代碼:
import org.apache.log4j.Logger;
Logger logger = Logger.getLogger(Demo.class);
logger.info("xxxx");
1.2.3 搞事情的JUL
sun公司對於log4j的出現內心隱隱表示嫉妒。於是在jdk1.4版本後,開始搞事情,增加了一個包為java.util.logging,簡稱為JUL,用以對抗log4j ,但是卻給開發造成了麻煩。相互引用的項目之間可能使用了不同的日誌框架,經常將代碼搞得一片混亂。
代碼:
import java.util.logging.Logger;
Logger loggger = Logger.getLogger(Demo.class.getName());
logger.finest("xxxx");
配置路徑:
$JAVA_HOME/jre/lib/logging.properties
JUL功能遠不如log4j完善,自帶的Handlers有限,性能和可用性上也一般,JUL在Java1.5以後才有所提升。
1.2.4 JCL應運而生
從上面可以看出,JUL的api與log4j是完全不同的(參數只接受string)。由於日誌系統互相沒有關聯,彼此沒有約定,不同人的代碼使用不同日誌,替換和統一也就變成了比較棘手的一件事。假如你的應用使用log4j,然後項目引用了一個其他團隊的庫,他們使用了JUL,你的應用就得使用兩個日誌系統了,然後其他團隊又使用了simplelog……這個時候如果要調整日誌的輸出級別,用於跟蹤某個信息,簡直就是一場災難。
那這個狀況該如何解決呢?答案就是進行抽象,抽象出一個介面層,對每個日誌實現都適配或者轉接,這樣這些提供給別人的庫都直接使用抽象層即可 ,以後調用的時候,就調用這些介面。(面向介面思想)
於是,JCL(Jakarta Commons Logging)應運而生,也就是commons-logging-xx.jar組件。JCL 只提供 log 介面,具體的實現則在運行時動態尋找。這樣一來組件開發者只需要針對 JCL 介面開發,而調用組件的應用程式則可以在運行時搭配自己喜好的日誌實踐工具。
那介面下真實的日誌是誰呢?參考下圖:
JCL會在ClassLoader中進行查找,如果能找到Log4j 則預設使用log4j 實現,如果沒有則使用JUL(jdk自帶的) 實現,再沒有則使用JCL內部提供的SimpleLog 實現。(代碼驗證)
pom:
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
代碼:
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
Log log =LogFactory.getLog(Demo.class);
log.info('xxx');
JCL缺點也很明顯,一是效率較低,二是容易引發混亂,三是JCL的機制有很大的可能會引發記憶體泄露。
同時,JCL的書寫存在一個不太優雅的地方,典型的場景如下:
假如要輸出一條debug日誌,而一般情況下,生產環境 log 級別都會設到 info 或者以上,那這條 log 是不會被輸出的。於是,在代碼里就出現了
logger.debug("this is a debug info , message :" + msg);
這個有什麼問題呢?雖然生產不會打出日誌,但是這其中都會做一個字元串連接操作,然後生成一個新的字元串。如果這條語句在迴圈或者被調用很多次的函數中,就會多做很多無用的字元串連接,影響性能。
所以,JCL推薦的寫法如下:
if (logger.isDebugEnabled()) {
logger.debug("this is a debug info , message :" + msg);
}
雖然解決了問題,但是這個代碼實在看上去不怎麼舒服...
1.2.5 再起波瀾
於是,針對以上情況,log4j的作者再次出手,他覺得JCL不好用,自己又寫了一個新的介面api,就是slf4j,並且為了追求更極致的性能,新增了一套日誌的實現,就是logback,一時間烽煙又起……
坐標:
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.30</version>
</dependency>
<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>
配置:
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<property name="logPattern" value="logback:[ %-5level] [%date{yyyy-MM-dd HH:mm:ss.SSS}] %logger{96} [%line] [%thread]- %msg%n"></property>
<!-- 控制台的標準輸出 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<charset>UTF-8</charset>
<pattern>${logPattern}</pattern>
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="STDOUT"/>
</root>
</configuration>
logback-core 提供基礎抽象,logback-classic 提供日誌實現,並且直接就是基於Slf4j API。所以slf4j配合logback來完成日誌時,不需要像其他的日誌框架一樣提供適配器。
slf4j本身並沒有實際的日誌輸出能力,它底層還是需要去調用具體的日誌框架API,也就是它需要跟具體的日誌框架結合使用。由於具體日誌框架比較多,而且互相也大都不相容,日誌門面介面要想實現與任意日誌框架結合就需要額外對應的橋接器。
有了新的slf4j後,上面的字元串拼接問題,被以下代碼所取代,而logback也提供了更高級的特性,如非同步 logger,Filter等。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
final Logger logger = LoggerFactory.getLogger(Demo.class);
logger.debug("this is a debug info , message : {}", msg);
事情結束了嗎?沒有,log4j的粉絲們並不開心,於是下一代誕生了……
1.2.6 再度青春
前面提到,log4j由apache宣佈,2015年後,不再維護。推薦大家升級到log4j2,雖然log4j2沿襲了log4j的思想,然而log4j2和log4j完全是兩碼事,並不相容。
log4j2以性能著稱,它比其前身Log4j 1.x提供了重大改進,同時類比logback,它提供了Logback中可用的許多改進,同時修複了Logback架構中的一些固有問題。功能上,它有著和Logback相同的基本操作,同時又有自己獨特的部分,比如:插件式結構、配置文件優化、非同步日誌等。
pom:
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.13.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.13.0</version>
</dependency>
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>3.4.1</version>
</dependency>
代碼:
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
Logger logger = LogManager.getLogger(Demo.class);
logger.debug("debug Msg");
配置:
<?xml version="1.0" encoding="UTF-8"?>
<configuration status="info" monitorInterval="30">
<Properties>
<Property name="pattern">log4j2:[%-5p]:%d{YYYY-MM-dd HH:mm:ss} [%t] %c{1}:%L - %msg%n</Property>
</Properties>
<appenders>
<!--console :控制台輸出的配置-->
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="${pattern}"/>
</Console>
</appenders>
<loggers>
<logger name="org.springframework" level="INFO"></logger>
<root level="info">
<appender-ref ref="Console"/>
</root>
</loggers>
</configuration>
到log4j2,轟轟烈烈的java log戰役基本就結束了。下麵的章節,我們將進入日誌工具的詳細配置階段。
1.3 配置講解
1.3.1 概述
1)日誌級別
一個完整的日誌組件都要具備日誌級別的概念,每種日誌組件級別定義不同,日常編碼最經常用到的主流分級如下(由低到高):
-
trace:路徑跟蹤
-
debug:一般用於日常調式
-
info:列印重要信息
-
warn:給出警告
-
error:出現錯誤或問題
每個日誌組件的具體級別劃分稍有不同,參考下文各章節。
2)日誌組件
- appender:日誌輸出目的地,負責日誌的輸出 (輸出到什麼 地方)
- logger:日誌記錄器,負責收集處理日誌記錄 (如何處理日誌)
- layout:日誌格式化,負責對輸出的日誌格式化(以什麼形式展現)
1.3.2 jul
1)配置文件:
預設情況下配置文件路徑為$JAVAHOME\jre\lib\logging.properties
可以指定配置文件:
static {
System.setProperty("java.util.logging.config.file",
Demo.class.getClassLoader().getResource("logging.properties").getPath());
}
代碼實踐:配置文件位置,日誌輸出級別,如何輸出低於INFO級別的信息
2)級別:
-
SEVERE(最高值)
-
WARNING
-
INFO
-
CONFIG
-
FINE
-
FINER
-
FINEST(最低值)
-
OFF,關閉日誌。
-
ALL,啟用所有日誌。
3)處理器:
-
StreamHandler:日誌記錄寫入OutputStream。
-
ConsoleHandler:日誌記錄寫入System.err。
-
FileHandler:日誌記錄寫入單個文件或一組滾動日誌文件。
-
SocketHandler:日誌記錄寫入遠程TCP埠的處理程式。
-
MemoryHandler:緩衝記憶體中日誌記錄。
4)格式化:
-
SimpleFormatter:格式化為簡短的日誌記錄摘要。
-
XMLFormatter:格式化為詳細的XML結構信息。
-
可自定輸出格式,繼承抽象類java.util.logging.Formatter即可。
- 代碼實踐:
- Appender:Console,File
- Layout:Xml,Simple
1.3.3 log4j
1)配置文件:
- 啟動時,預設會尋找source folder下的log4j.xml
- 若沒有,會尋找log4j.properties
2)級別:
-
FATAL(最高)
-
ERROR
-
WARN
-
INFO
-
DEBUG (最低)
-
OFF,關閉日誌。
-
ALL,啟用所有日誌。
3)處理器:
- org.apache.log4j.ConsoleAppender(控制台)
- org.apache.log4j.FileAppender(文件)
- org.apache.log4j.DailyRollingFileAppender(每天產生一個日誌文件)
- org.apache.log4j.RollingFileAppender(文件大小到達指定尺寸的時候產生一個新的文件)
- org.apache.log4j.WriterAppender(將日誌信息以流格式發送到任意指定的地方)
4)格式化:
- org.apache.log4j.HTMLLayout(以HTML表格形式佈局)
- org.apache.log4j.PatternLayout(可以靈活地指定佈局模式)
- org.apache.log4j.SimpleLayout(包含日誌信息的級別和信息字元串)
- org.apache.log4j.TTCCLayout(包含日誌產生的時間、線程、類別等等信息)
5) 代碼實踐:
- Appender:Console,DailyRollingFile
- Layout:Pattern
log4j.rootLogger=debug,stdout,dailyfile
#console
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern= [%d{yyyy-MM-dd HH:mm:ss a}]:%p %l%m%n
#dailyfile
log4j.appender.dailyfile=org.apache.log4j.DailyRollingFileAppender
log4j.appender.dailyfile.DatePattern='_'yyyy-MM-dd'.log'
log4j.appender.dailyfile.File=./log4j.log
log4j.appender.dailyfile.Append=true
log4j.appender.dailyfile.Threshold=INFO
log4j.appender.dailyfile.layout=org.apache.log4j.PatternLayout
log4j.appender.dailyfile.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss a} [Thread: %t][ Class:%c >> Method: %l ]%n%p:%m%n
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration debug="true" xmlns:log4j='http://jakarta.apache.org/log4j/'>
<appender name="CONSOLE" class="org.apache.log4j.ConsoleAppender">
<param name="Target" value="System.out"/>
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern"
value=""/>
</layout>
<filter class="org.apache.log4j.varia.LevelRangeFilter">
<param name="LevelMin" value="DEBUG"/>
<param name="LevelMax" value="DEBUG"/>
</filter>
</appender>
<appender name="DAILYROLLINGFILE" class="org.apache.log4j.DailyRollingFileAppender">
<param name="File" value="log4j.log"/>
<param name="DatePattern" value="yyyy-MM-dd"/>
<param name="Append" value="true"/>
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern"
value="%d{yyyy-MM-dd HH:mm:ss a} [Thread: %t][ Class:%c Method: %l ]%n%p:%m%n"/>
</layout>
</appender>
<root>
<appender-ref ref="CONSOLE"/>
<appender-ref ref="DAILYROLLINGFILE"/>
</root>
</log4j:configuration>
語法 | 說明 |
---|---|
%c | a.b.c |
%c{2} | b.c |
%20c | (若名字空間長度小於20,則左邊用空格填充) |
%-20c | (若名字空間長度小於20,則右邊用空格填充) |
%.30c | (若名字空間長度超過30,截去多餘字元) |
%20.30c | (若名字空間長度小於20,則左邊用空格填充;若名字空間長度超過30,截去多餘字元) |
%-20.30c | (若名字空間長度小於20,則右邊用空格填充;若名字空間長度超過30,截去多餘字元) |
%C | org.apache.xyz.SomeClass |
%C{1} | SomeClass |
%d{yyyy/MM/dd HH:mm:ss,SSS} | 2000/10/12 11:22:33,117 |
%d{ABSOLUTE} | 11:22:33,117 |
%d{DATE} | 12 Oct 2000 11:22:33,117 |
%d{ISO8601} | 2000-10-12 11:22:33,117 |
%F | MyClass.java |
%l | MyClass.main(MyClass.java:123) |
%L | 123 |
%m | This is a message for debug. |
%M | main |
%n | Windows平臺下表示rn,UNIX平臺下表示n |
%p | INFO |
%r | 1215 |
%t | MyClass |
%% | % |
1.3.4 logback
http://logback.qos.ch/manual/index.html
1)配置文件:
-
Logback tries to find a file called
logback-test.xml
in the classpath. -
If no such file is found, logback tries to find a file called
logback.groovy
in the classpath. -
If no such file is found, it checks for the file
logback.xml
in the classpath.. -
If no such file is found, service-provider loading facility (introduced in JDK 1.6) is used to resolve the implementation of com.qos.logback.classic.spi.Configurator interface by looking up the file META-INF\services\ch.qos.logback.classic.spi.Configurator in the class path. Its contents should specify the fully qualified class name of the desired Configurator implementation.
-
If none of the above succeeds, logback configures itself automatically using the BasicConfigurator which will cause logging output to be directed to the console.
2)級別:
- 日誌列印級別 ALL > TRACE > FATAL > DEBUG > INFO > WARN > ERROR > OFF
- 日誌輸出級別 TRACE > DEBUG > INFO > WARN > ERROR
3)處理器:
http://logback.qos.ch/manual/appenders.html
4)格式化:
http://logback.qos.ch/manual/layouts.html
- 代碼實戰:
- Appender:Console,Rollingfile ,DB
- Layout:Xml,Pattern,Html , 自定義
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="3 seconds" debug="false">
<!-- lOGGER PATTERN 根據個人喜好選擇匹配 -->
<property name="logPattern" value="logback:[ %-5level] [%date{yyyy-MM-dd HH:mm:ss.SSS}] %logger{96} [%line] [%thread]- %msg%n"></property>
<!-- 動態日誌級別 -->
<jmxConfigurator/>
<!-- 控制台的標準輸出 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<charset>UTF-8</charset>
<pattern>${logPattern}</pattern>
</encoder>
</appender>
<!-- 滾動文件 -->
<appender name="ROLLING_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>DEBUG</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<file>./logback.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>./logback.log.%d{yyyy-MM-dd}.zip</fileNamePattern>
<!-- 最大保存時間 -->
<maxHistory>2</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${logPattern}</pattern>
</encoder>
</appender>
<!-- DB -->
<appender name="DB" class="ch.qos.logback.classic.db.DBAppender">
<connectionSource class="ch.qos.logback.core.db.DriverManagerConnectionSource">
<driverClass>com.mysql.jdbc.Driver</driverClass>
<url>jdbc:mysql://172.17.0.203:3306/log?useSSL=false</url>
<user>root</user>
<password>root</password>
</connectionSource>
</appender>
<!-- ASYNC_LOG -->
<appender name="ASYNC_LOG" class="ch.qos.logback.classic.AsyncAppender">
<!-- 不丟失日誌.預設的,如果隊列的80%已滿,則會丟棄TRACT、DEBUG、INFO級別的日誌 -->
<discardingThreshold>0</discardingThreshold>
<!-- 更改預設的隊列的深度,該值會影響性能.預設值為256 -->
<queueSize>3</queueSize>
<appender-ref ref="STDOUT"/>
</appender>
<!-- 日誌的記錄級別 -->
<!-- 在定義後引用APPENDER -->
<root level="DEBUG">
<!-- 控制台 -->
<appender-ref ref="STDOUT"/>
<!-- ROLLING_FILE -->
<appender-ref ref="ROLLING_FILE"/>
<!-- ASYNC_LOG -->
<appender-ref ref="ASYNC_LOG"/>
</root>
</configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<!--<charset>UTF-8</charset>-->
<!--<pattern>${logPattern}</pattern>-->
<!--<layout class="com.itheima.logback.MySampleLayout" />-->
<layout class="ch.qos.logback.classic.html.HTMLLayout">
<pattern>%relative%thread%mdc%level%logger%msg</pattern>
</layout>
<!--<layout class="ch.qos.logback.classic.log4j.XMLLayout">-->
<!--<locationInfo>false</locationInfo>-->
<!--</layout>-->
</encoder>
</appender>
1.3.5 jcl
1)配置文件:
- 首先在classpath下尋找commons-logging.properties文件。如果找到,則使用其中定義的Log實現類;如果找不到,則在查找是否已定義系統環境變數org.apache.commons.logging.Log,找到則使用其定義的Log實現類;
- 查看classpath中是否有Log4j的包,如果發現,則自動使用Log4j作為日誌實現類;
- 否則,使用JDK自身的日誌實現類(JDK1.4以後才有日誌實現類);
- 否則,使用commons-logging自己提供的一個簡單的日誌實現類SimpleLog;
2)級別:
- jcl有5個級別:trace < debug < info < warn < error
3)代碼實戰:
- 日誌查找順序: log4j-jul-slog
- commons-logging.properties配置
#指定日誌對象:
#org.apache.commons.logging.Log=org.apache.commons.logging.impl.SimpleLog
#org.apache.commons.logging.Log=org.apache.commons.logging.impl.Jdk14Logger
org.apache.commons.logging.Log=org.apache.commons.logging.impl.Log4JLogger
#指定日誌工廠:
org.apache.commons.logging.LogFactory = org.apache.commons.logging.impl.LogFactoryImpl
1.3.6 log4j2
1)配置文件:
-
Log4j will inspect the
log4j.configurationFile
system property and, if set, will attempt to load the configuration using theConfigurationFactory
that matches the file extension. -
If no system property is set the YAML ConfigurationFactory will look for
log4j2-test.yaml
orlog4j2-test.yml
in the classpath. -
If no such file is found the JSON ConfigurationFactory will look for
log4j2-test.json
orlog4j2-test.jsn
in the classpath. -
If no such file is found the XML ConfigurationFactory will look for
log4j2-test.xml
in the classpath. -
If a test file cannot be located the YAML ConfigurationFactory will look for
log4j2.yaml
orlog4j2.yml
on the classpath. -
If a YAML file cannot be located the JSON ConfigurationFactory will look for
log4j2.json
orlog4j2.jsn
on the classpath. -
If a JSON file cannot be located the XML ConfigurationFactory will try to locate
log4j2.xml
on the classpath. -
If no configuration file could be located the
DefaultConfiguration
will be used. This will cause logging output to go to the console.
2)級別:
- 從低到高為:ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < OFF
3)處理器:
http://logging.apache.org/log4j/2.x/manual/appenders.html
-
FileAppender 普通地輸出到本地文件
-
KafkaAppender 輸出到kafka隊列
-
FlumeAppender 將幾個不同源的日誌彙集、集中到一處。
-
JMSQueueAppender,JMSTopicAppender 與JMS相關的日誌輸出
-
RewriteAppender 對日誌事件進行掩碼或註入信息
-
RollingFileAppender 對日誌文件進行封存(詳細)
-
RoutingAppender 在輸出地之間進行篩選路由
-
SMTPAppender 將LogEvent發送到指定郵件列表
-
SocketAppender 將LogEvent以普通格式發送到遠程主機
-
SyslogAppender 將LogEvent以RFC 5424格式發送到遠程主機
-
AsynchAppender 將一個LogEvent非同步地寫入多個不同輸出地
-
ConsoleAppender 將LogEvent輸出到命令行
-
FailoverAppender 維護一個隊列,系統將嘗試向隊列中的Appender依次輸出LogEvent,直到有一個成功為止
4)格式化:
http://logging.apache.org/log4j/2.x/manual/layouts.html
5)代碼實戰:
- Appender:Console,File,RollingFile
- Layout:csv,json,Xml,Pattern,Html
<?xml version="1.0" encoding="UTF-8"?>
<!--日誌級別以及優先順序排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
<!--status="WARN" :用於設置log4j2自身內部日誌的信息輸出級別,預設是OFF-->
<!--monitorInterval="30" :間隔秒數,自動檢測配置文件的變更和重新配置本身-->
<configuration status="info" monitorInterval="30">
<Properties>
<!--自定義一些常量,之後使用${變數名}引用-->
<Property name="pattern">log4j2:[%-5p]:%d{YYYY-MM-dd HH:mm:ss} [%t] %c{1}:%L - %msg%n</Property>
</Properties>
<!--appenders:定義輸出內容,輸出格式,輸出方式,日誌保存策略等,常用其下三種標簽[console,File,RollingFile]-->
<appenders>
<!--console :控制台輸出的配置-->
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="${pattern}"/>
</Console>
<!--File :同步輸出日誌到本地文件-->
<!--append="false" :根據其下日誌策略,每次清空文件重新輸入日誌,可用於測試-->
<File name="File" fileName="./log4j2-file.log" append="false">
<PatternLayout pattern="${pattern}"/>
</File>
<RollingFile name="RollingFile" fileName="./log4j2-rollingfile.log"
filePattern="./$${date:yyyy-MM}/log4j2-%d{yyyy-MM-dd}-%i.log">
<!--ThresholdFilter :日誌輸出過濾-->
<!--level="info" :日誌級別,onMatch="ACCEPT" :級別在info之上則接受,onMismatch="DENY" :級別在info之下則拒絕-->
<ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="${pattern}"/>
<!-- Policies :日誌滾動策略-->
<Policies>
<!-- TimeBasedTriggeringPolicy :時間滾動策略,
預設0點產生新的文件,
interval="6" : 自定義文件滾動時間間隔,每隔6小時產生新文件,
modulate="true" : 產生文件是否以0點偏移時間,即6點,12點,18點,0點-->
<TimeBasedTriggeringPolicy interval="6" modulate="true"/>
<!-- SizeBasedTriggeringPolicy :文件大小滾動策略-->
<SizeBasedTriggeringPolicy size="1 MB"/>
</Policies>
<!-- DefaultRolloverStrategy屬性如不設置,則預設為最多同一文件夾下7個文件,這裡設置了20 -->
<DefaultRolloverStrategy max="20"/>
</RollingFile>
</appenders>
<!--然後定義logger,只有定義了logger並引入的appender,appender才會生效-->
<loggers>
<!--過濾掉spring和mybatis的一些無用的DEBUG信息-->
<!--Logger節點用來單獨指定日誌的形式,name為包路徑,比如要為org.springframework包下所有日誌指定為INFO級別等。 -->
<logger name="org.springframework" level="INFO"></logger>
<logger name="org.mybatis" level="INFO"></logger>
<!--AsyncLogger :非同步日誌,LOG4J有三種日誌模式,全非同步日誌,混合模式,同步日誌,性能從高到底,線程越多效率越高,也可以避免日誌卡死線程情況發生-->
<!--additivity="false" : additivity設置事件是否在root logger輸出,為了避免重覆輸出,可以在Logger 標簽下設置additivity為”false”-->
<AsyncLogger name="AsyncLogger" level="trace" includeLocation="true" additivity="true">
<appender-ref ref="Console"/>
</AsyncLogger>
<logger name="Kafka" additivity="false" level="debug">
<appender-ref ref="Kafka"/>
<appender-ref ref="Console"/>
</logger>
<!-- Root節點用來指定項目的根日誌,如果沒有單獨指定Logger,那麼就會預設使用該Root日誌輸出 -->
<root level="info">
<appender-ref ref="Console"/>
<!--<appender-ref ref="File"/>-->
<!--<appender-ref ref="RollingFile"/>-->
<!--<appender-ref ref="Kafka"/>-->
</root>
</loggers>
</configuration>
1.3.7 slf4j
1)配置文件:
具體日誌輸出內容取決於生效的具體日誌實現
2)級別:
slf4j日誌級別有五種:ERROR、WARN、INFO、DEBUG、TRACE,級別從高到底
3)slf日誌實現:
<!--slf轉其他日誌-->
<!--slf - jcl-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jcl</artifactId>
<version>1.7.30</version>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- slf - log4j -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.30</version>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- slf4j - log4j2 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.13.0</version>
<scope>runtime</scope>
<optional>true</optional>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--slf - jul-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<version>1.7.30</version>
</dependency>
<!--slf - simplelog-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.30</version>
</dependency>
<!--slf - logback-->
<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>
問題:多個橋接器的話,slf4j怎麼處理呢?
使用在class path中較早出現的那個,如在maven中,會使用在pom.xml中定義較靠前的橋接器(代碼驗證)
小知識:
橋接器會傳遞依賴到對應的下游日誌組件,比如slf4j-log4j12會附帶log4j的jar包依賴(代碼驗證)
4)其他日誌轉slf
<!--其他日誌轉slf-->
<!--jul - slf-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jul-to-slf4j</artifactId>
<version>1.7.30</version>
</dependency>
<!--jcl - slf-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.7.30</version>
</dependency>
<!--log4j - slf-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
<version>1.7.30</version>
</dependency>
<!--log4j2 - slf-->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-to-slf4j</artifactId>
<version>2.13.0</version>
</dependency>
5)slf4j日誌環
<!-- 演示實例:slf4j - log4j - slf4j -->
<!--log4j-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>apache-log4j-extras</artifactId>
<version>1.2.17</version>
</dependency>
<!--slf4j-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.30</version>
</dependency>
<!--log4j - slf-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
<version>1.7.30</version>
</dependency>
<!-- slf - log4j -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.30</version>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
依賴展示:
報錯結果:
SLF4J: Detected both log4j-over-slf4j.jar AND bound slf4j-log4j12.jar on the class path, preempting StackOverflowError.
SLF4J: See also http://www.slf4j.org/codes.html#log4jDelegationLoop for more details.
java.lang.ExceptionInInitializerError
at org.slf4j.impl.StaticLoggerBinder.<init>(StaticLoggerBinder.java:72)
at org.slf4j.impl.StaticLoggerBinder.<clinit>(StaticLoggerBinder.java:45)
at org.slf4j.LoggerFactory.bind(LoggerFactory.java:150)
at org.slf4j.LoggerFactory.performInitialization(LoggerFactory.java:124)
at org.slf4j.LoggerFactory.getILoggerFactory(LoggerFactory.java:417)
at org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:362)
at org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:388)
at com.itheima.slf4j.Demo.<clinit>(Demo.java:8)
Caused by: java.lang.IllegalStateException: Detected both log4j-over-slf4j.jar AND bound slf4j-log4j12.jar on the class path, preempting StackOverflowError. See also http://www.slf4j.org/codes.html#log4jDelegationLoop for more details.
at org.slf4j.impl.Log4jLoggerFactory.<clinit>(Log4jLoggerFactory.java:54)
... 8 more
Exception in thread "main"
Process finished with exit code 1
經驗:使用slf橋接和綁定的時候要留心,不要自相矛盾形成環
1.4日誌建議
1.4.1 門面約束
使用門面,而不是具體實現
使用 Log Facade 可以方便的切換具體的日誌實現。而且,如果依賴多個項目,使用了不同的Log Facade,還可以方便的通過 Adapter 轉接到同一個實現上。如果依賴項目直接使用了多個不同的日誌實現,會非常糟糕。
經驗之談:日誌門面,一般現在推薦使用 Log4j-API 或者 SLF4j,不推薦繼續使用 JCL。
1.4.2 單一原則
只添加一個日誌實現
項目中應該只使用一個具體的 Log Implementation,如果在依賴的項目中,使用的 Log Facade不支持當前 Log Implementation,就添加合適的橋接器。
經驗之談:jul性能一般,log4j性能也有問題而且不再維護,建議使用 Logback 或者Log4j2。
1.4.3 依賴約束
日誌實現坐標應該設置為optional並使用runtime scope
在項目中,Log Implementation的依賴強烈建議設置為runtime scope,並且設置為optional。例如項目中使用了 SLF4J 作為 Log Facade,然後想使用 Log4j2 作為 Implementation,那麼使用 maven 添加依賴的時候這樣設置:
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>${log4j.version}</version>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>${log4j.version}</version>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
設為optional,依賴不會傳遞,這樣如果你是個lib項目,然後別的項目使用了你這個lib,不會被引入不想要的Log Implementation 依賴;
Scope設置為runtime,是為了防止開發人員在項目中直接使用Log Implementation中的類,強制約束開發人員使用Facade介面。
1.4.4 避免傳遞
儘量用exclusion排除依賴的第三方庫中的日誌坐標
同上一個話題,第三方庫的開發者卻未必會把具體的日誌實現或者橋接器的依賴設置為optional,然後你的項目就會被迫傳遞引入這些依賴,而這些日誌實現未必是你想要的,比如他依賴了Log4j,你想使用Logback,這時就很尷尬。另外,如果不同的第三方依賴使用了不同的橋接器和Log實現,極有可能會形成環。
這種情況下,推薦的處理方法,是使用exclude來排除所有的這些Log實現和橋接器的依賴,只保留第三方庫裡面對Log Facade的依賴。
實例:依賴jstorm會引入Logback和log4j-over-slf4j,如果你在自己的項目中使用Log4j或其他Log實現的話,就需要加上exclusion:
<dependency>
<groupId>com.alibaba.jstorm</groupId>
<artifactId>jstorm-core</artifactId>
<version>2.1.1</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
</exclusion>
<exclusion>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</exclusion>
</exclusions>
</dependency>
1.4.5 註意寫法
避免為不會輸出的log買單
Log庫都可以靈活的設置輸出級別,所以每一條程式中的log,都是有可能不會被輸出的。這時候要註意不要額外的付出代價。實例如下:
logger.debug("this is debug: " + message);
logger.debug("this is json msg: {}", toJson(message));
前面講到,第一條的字元串拼接,即使日誌級別高於debug不會列印,依然會做字元串連接操作;第二條雖然用了SLF4J/Log4j2 中的懶求值方式,但是toJson()這個函數卻是總會被調用並且開銷更大。推薦的寫法如下:
// SLF4J/LOG4J2
logger.debug("this is debug:{}", message);
// LOG4J2
logger.debug("this is json msg: {}", () -> toJson(message));
// SLF4J/LOG4J2
if (logger.isDebugEnabled()) {
logger.debug("this is debug: " + message);
}
1.4.6 減少分析
輸出的日誌中儘量不要使用行號,函數名等信息
原因是,為了獲取語句所在的函數名,或者行號,log庫的實現都是獲取當前的stacktrace,然後分析取出這些信息,而獲取stacktrace的代價是很昂貴的。如果有很多的日誌輸出,就會占用大量的CPU。在沒有特殊需要的情況下,建議不要在日誌中輸出這些這些欄位。
1.4.7 精簡至上
log中儘量不要輸出稀奇古怪的字元,這是個習慣和約束問題。有的同學習慣用這種語句:
logger.debug("=====================================:{}",message);
輸出了大量無關字元,雖然自己一時痛快,但是如果所有人都這樣做的話,那log輸出就沒法看了!正確的做法是日誌只輸出必要信息,如果要過濾,後期使用grep來篩選,只查自己關心的日誌。
本文由傳智教育博學谷 - 狂野架構師教研團隊發佈
轉載請註明出處!