java日志學習(持續更新)


1.Java實現日志

java日志體系大體可以分為三個部分:日志門面接口、橋接器、日志框架具體實現。
原生日志實現
(http://www.importnew.com/16331.html)

Java日志API由以下三個核心組件組成:

Loggers:Logger負責捕捉事件並將其發送給合適的Appender。
Appenders:也被稱為Handlers,負責將日志事件記錄到目標位置。在將日志事件輸出之前,Appenders使用Layouts來對事件進行格式化處理。
Layouts:也被稱為Formatters,它負責對日志事件中的數據進行轉換和格式化。Layouts決定了數據在一條日志記錄中的最終形式。

當Logger記錄一個事件時,它將事件轉發給適當的Appender。然后Appender使用Layout來對日志記錄進行格式化,並將其發送給控制台、文件或者其它目標位置。另外,Filters可以讓你進一步指定一個Appender是否可以應用在一條特定的日志記錄上。在日志配置中,Filters並不是必需的,但可以讓你更靈活地控制日志消息的流動。

上面是java utils logging(jul)的組件。

2.其他框架

參考:

  • (https://my.oschina.net/xianggao?tab=newest&catalogId=3413124)
  • (https://blog.csdn.net/nowuseeme/article/category/6745678)
  • (https://www.cnblogs.com/yudar/p/5113655.html)
  • (https://www.cnblogs.com/chenhongliang/p/5312517.html)


對於一個應用程序來說日志記錄是必不可少的一部分。線上問題追蹤,基於日志的業務邏輯統計分析等都離不日志。java領域存在多種日志框架,目前常用的日志框架包括Log4j,Log4j 2,Commons Logging,Slf4j,Logback,Jul。

java常用日志框架類別介紹:

1、Log4j

   Apache Log4j是一個基於Java的日志記錄工具。它是由Ceki Gülcü首創的,現在則是Apache軟件基金會的一個項目。 Log4j是幾種Java日志框架之一。all<debug<info<warn<error<fatal<off,logger以名字為標識(類似於樹結構),存在父子關系,
Threshold門檻優先級>Level優先級,如果子Logger設置了自己的Threshold,則會使用自己的Threshold,如果沒有設置,則繼續向上查詢,直到找到一個父類的Threshold或者rootLogger的level
Appender具有累加繼承性,可以通過setAdditivity管理累加性,level和Treshold具有繼承性,
  參考:(https://www.cnblogs.com/crazyacking/p/5456347.html#_label00)
    (https://my.oschina.net/xianggao/blog/515216)
     架構(https://my.oschina.net/xianggao/blog/518059)
2、Log4j 2

   Apache Log4j 2是apache開發的一款Log4j的升級產品。
  日志級別:TRACE, DEBUG,INFO, WARN, ERROR 以及FATAL。
參考:

  (https://github.com/apache/logging-log4j2)
  (http://logging.apache.org/log4j/2.x/)
  架構(https://my.oschina.net/xianggao/blog/523020)
  配置文件(https://my.oschina.net/xianggao/blog/523401)
3、Commons

   Logging Apache基金會所屬的項目,是一套Java日志接口,之前叫Jakarta Commons Logging,后更名為Commons Logging。
參考:

  (https://github.com/apache/commons-logging)
  (http://commons.apache.org/proper/commons-logging/)
  (https://blog.csdn.net/u011794238/article/details/50749260)
  (https://blog.csdn.net/SakuraInLuoJia/article/details/53534949?utm_source=blogxgwz0#二-commons-logging簡單日志實現)
4、Slf4j

  類似於Commons Logging,是一套簡易Java日志門面,本身並無日志的實現。(Simple Logging Facade for Java,縮寫Slf4j)。
參考:

  (https://github.com/qos-ch/slf4j)
  (https://www.slf4j.org/)
  代碼更改(https://www.slf4j.org/migrator.html)
5、Logback

  一套日志組件的實現(slf4j陣營)。日志級別TRACE < DEBUG < INFO < WARN < ERROR,

參考:

  (https://github.com/qos-ch/logback)

  (https://logback.qos.ch/documentation.html)
  分析(https://my.oschina.net/xianggao/blog/522590)
  配置(https://www.cnblogs.com/warking/p/5710303.html)

  (https://www.cnblogs.com/cjsblog/p/9113131.html)
6、Jul

   (Java Util Logging),自Java1.4以來的官方日志實現。日志級別OFF(2^31-1)—>SEVERE(1000)—>WARNING(900)—>INFO(800)—>CONFIG(700)—>FINE(500)—>FINER(400)—>FINEST(300)—>ALL(-2^31),配置文件位置$JAVA_HOME/jre/lib/logging.properties。

參考:

  (https://docs.oracle.com/javase/8/docs/technotes/guides/logging/overview.html)
  分析(https://my.oschina.net/xianggao/blog/520776)
7、tinylog

  輕量級的java和Android框架
  (https://tinylog.org/)
  (https://github.com/pmwmedia/tinylog)


3.java常用日志框架之間的關系

Log4j2與Log4j1發生了很大的變化,log4j2不兼容log4j1。

Commons Logging和Slf4j是日志門面(門面模式是軟件工程中常用的一種軟件設計模式,也被稱為正面模式、外觀模式。它為子系統中的一組接口提供一個統一的高層接口,使得子系統更容易使用)。log4j和Logback則是具體的日志實現方案。可以簡單的理解為接口與接口的實現,調用這只需要關注接口而無需關注具體的實現,做到解耦。

比較常用的組合使用方式是Slf4j與Logback組合使用,Commons Logging與Log4j組合使用。

Logback必須配合Slf4j使用。由於Logback和Slf4j是同一個作者,其兼容性不言而喻。

Commons Logging與Slf4j實現機制對比:
Commons logging實現機制
Commons logging是通過動態查找機制,在程序運行時,使用自己的ClassLoader尋找和載入本地具體的實現。詳細策略可以查看commons-logging-*.jar包中的org.apache.commons.logging.impl.LogFactoryImpl.java文件。由於OSGi不同的插件使用獨立的ClassLoader,OSGI的這種機制保證了插件互相獨立, 其機制限制了commons logging在OSGi中的正常使用。

Slf4j實現機制
Slf4j在編譯期間,靜態綁定本地的LOG庫,因此可以在OSGi中正常使用。它是通過查找類路徑下org.slf4j.impl.StaticLoggerBinder,然后綁定工作都在這類里面進。

如果在項目中如果選擇日志框架:
如果是在一個新的項目中建議使用Slf4j與Logback組合,這樣有如下的幾個優點。

Slf4j實現機制決定Slf4j限制較少,使用范圍更廣。由於Slf4j在編譯期間,靜態綁定本地的LOG庫使得通用性要比Commons logging要好。

Logback擁有更好的性能。Logback聲稱:某些關鍵操作,比如判定是否記錄一條日志語句的操作,其性能得到了顯著的提高。這個操作在Logback中需要3納秒,而在Log4J中則需要30納秒。LogBack創建記錄器(logger)的速度也更快:13毫秒,而在Log4J中需要23毫秒。更重要的是,它獲取已存在的記錄器只需94納秒,而Log4J需要2234納秒,時間減少到了1/23。跟JUL相比的性能提高也是顯著的。

Commons Logging開銷更高 在使Commons Logging時為了減少構建日志信息的開銷,通常的做法是:
if(log.isDebugEnabled()){
log.debug("User name: " +
user.getName() + " buy goods id :" + good.getId());
}
在Slf4j陣營,你只需這么做:
log.debug("User name:{} ,buy goods id :{}", user.getName(),good.getId());
也就是說,slf4j把構建日志的開銷放在了它確認需要顯示這條日志之后,減少內存和cup的開銷,使用占位符號,代碼也更為簡潔

Logback文檔免費。Logback的所有文檔是全面免費提供的,不象Log4J那樣只提供部分免費文檔而需要用戶去購買付費文檔。

 


#1 日志架構總結#
SLF4j框架包括:
1、門面接口(slf4j-api包),負責用戶使用時統一調用接口,不負責日志的具體實現,當不存在橋接器時,將會報錯,存在多個橋接器也會報錯,但會選擇一個進行使用,
使用,形如Logger logger=LoggerFactory.getLogger(Log4jSlf4JTest.class);
調用Logger實際上是先在slf4j-api中LoggerFactory類試圖初始化ILoggerFactory並使用LoggerFactory生成Logger。
2、橋接器(slf4j-log4j12包等等)是連接門面接口和日志框架(slf4j連接底層日志框架時),每個日志框架都有具體實現LoggerFactory和Logger等,在Logger里調用具體的日志框架,統一在StaticLoggerBinder類中返回ILoggerFactory類型,Log4jLoggerAdapter就是實現了slf4j定義的Logger接口。
3、日志框架(jdk logging、log4j、log4j2、logback等實現了對應橋接器的日志框架),
每個日志框架的具體實現不同,效率也就不同,如今比較好的就是log4j2>logback>log4j>jdk logging.
4、逆向橋接器(jul-to-slf4j、log4j-over-slf4j、jcl-over-slf4j等)是其他框架連接底層的slf4j時使用。通常是多模塊使用不同日志框架引起沖突時或統一代碼時使用。
slf4j使用預編譯方式連接橋接器和日志框架,降低運行時的工作量,提高速率,比Commons logging更優。

 

參考:

  (https://my.oschina.net/xianggao/blog/517188)
#2 各種jar包總結#

log4j1:

log4j:log4j1的全部內容

log4j2:

log4j-api:log4j2定義的API

log4j-core:log4j2上述API的實現

logback:

logback-core:logback的核心包

logback-classic:logback實現了slf4j的API

commons-logging:

commons-logging:commons-logging的原生全部內容

log4j-jcl:commons-logging到log4j2的橋梁

jcl-over-slf4j:commons-logging到slf4j的橋梁

slf4j轉向某個實際的日志框架:場景介紹:如 使用slf4j的API進行編程,底層想使用log4j1來進行實際的日志輸出,這就是slf4j-log4j12干的事。

slf4j-jdk14:slf4j到jdk-logging的橋梁

slf4j-log4j12:slf4j到log4j1的橋梁

log4j-slf4j-impl:slf4j到log4j2的橋梁

logback-classic:slf4j到logback的橋梁

slf4j-jcl:slf4j到commons-logging的橋梁

某個實際的日志框架轉向slf4j:場景介紹:如 使用log4j1的API進行編程,但是想最終通過logback來進行輸出,所以就需要先將log4j1的日志輸出轉交給slf4j來輸出,slf4j再交給logback來輸出。將log4j1的輸出轉給slf4j,這就是log4j-over-slf4j做的事。這一部分主要用來進行實際的日志框架之間的切換(下文會詳細講解)。

jul-to-slf4j:jdk-logging到slf4j的橋梁

log4j-over-slf4j:log4j1到slf4j的橋梁

jcl-over-slf4j:commons-logging到slf4j的橋梁

#3 集成總結#
##3.1 commons-logging與其他日志框架集成##

1 commons-logging與jdk-logging集成,需要的jar包:

commons-logging

2 commons-logging與log4j1集成,需要的jar包:

commons-logging

log4j

3 commons-logging與log4j2集成,需要的jar包:

commons-logging

log4j-api

log4j-core

log4j-jcl(集成包)

4 commons-logging與logback集成,需要的jar包:

logback-core

logback-classic

slf4j-api、jcl-over-slf4j(2個集成包,可以不再需要commons-logging)

5 commons-logging與slf4j集成,需要的jar包:

jcl-over-slf4j(集成包,不再需要commons-logging)

slf4j-api

##3.2 slf4j與其他日志框架集成##

slf4j與jdk-logging集成,需要的jar包:

slf4j-api

slf4j-jdk14(集成包)

slf4j與log4j1集成,需要的jar包:

slf4j-api

log4j

slf4j-log4j12(集成包)

slf4j與log4j2集成,需要的jar包:

slf4j-api

log4j-api

log4j-core

log4j-slf4j-impl(集成包)

slf4j與logback集成,需要的jar包:

slf4j-api

logback-core

logback-classic(集成包)

slf4j與commons-logging集成,需要的jar包:

slf4j-api

commons-logging

slf4j-jcl(集成包)

#4 日志系統之間的切換#
##4.1 log4j無縫切換到logback##
###4.1.1 案例###
我們已經在代碼中使用了log4j1的API來進行日志的輸出,現在想不更改已有代碼的前提下,使之通過logback來進行實際的日志輸出。已使用的jar包:

log4j
使用案例:

private static final Logger logger=Logger.getLogger(Log4jTest.class);
public static void main(String[] args){
if(logger.isInfoEnabled()){
logger.info("log4j info message");
}
}
上述的Logger是log4j1自己的org.apache.log4j.Logger,在上述代碼中,我們在使用log4j1的API進行編程。現在如何能讓上述的日志輸出通過logback來進行輸出呢?只需要更換一下jar包就可以:

第一步:去掉log4j jar包

第二步:加入以下jar包

log4j-over-slf4j(實現log4j1切換到slf4j)

slf4j-api

logback-core

logback-classic

第三步:在類路徑下加入logback的配置文件

###4.1.2 切換原理###
看下log4j-over-slf4j(簡化更改版的log4j)就一目了然了:

輸入圖片說明

我們可以看到,這里面其實是簡化更改版的log4j。去掉log4j1的原生jar包,換成該簡化更改版的jar包(可以實現無縫遷移)。但是簡化更改版中的Logger和原生版中的實現就不同了,簡化版中的Logger實現如下(繼承了Category):

public class Category {
private String name;
protected org.slf4j.Logger slf4jLogger;
private org.slf4j.spi.LocationAwareLogger locationAwareLogger;

Category(String name) {
this.name = name;
slf4jLogger = LoggerFactory.getLogger(name);
if (slf4jLogger instanceof LocationAwareLogger) {
locationAwareLogger = (LocationAwareLogger) slf4jLogger;
}
}
}
從上面可以看到簡化版中的Logger內部是使用slf4j的API來生成的,所以我們使用的簡化版的Logger會委托給slf4j來進行輸出,由於當前類路徑下有logback-classic,所以slf4j會選擇logback進行輸出。從而實現了log4j到logback的日志切換。

##4.2 jdk-logging無縫切換到logback##
###4.2.1 案例###

private static final Logger logger=Logger.getLogger(JulSlf4jLog4jTest.class.getName());

public static void main(String[] args){
logger.log(Level.INFO,"jul info a msg");
logger.log(Level.WARNING,"jul waring a msg");
}
可以看到上述是使用jdk-logging自帶的API來進行編程的,現在我們想這些日志交給logback來輸出,解決辦法如下:

第一步:加入以下jar包:

jul-to-slf4j (實現jdk-logging切換到slf4j)

slf4j-api

logback-core

logback-classic

第二步:在類路徑下加入logback的配置文件

第三步:在代碼中加入如下代碼:

static {
SLF4JBridgeHandler.install();
}
###4.2.2 切換原理###
先來看下jul-to-slf4j jar包中的內容:

輸入圖片說明

我們看到只有一個類:SLF4JBridgeHandler。

它繼承了jdk-logging中定義的java.util.logging.Handler,Handler是jdk-logging處理日志過程中的一個處理器(具體我也沒仔細研究過),在使用之前,必須要提前注冊這個處理器,即上述的SLF4JBridgeHandler.install()操作,install后我們就可以通過這個handler實現日志的切換工作,如下:

protected Logger getSLF4JLogger(LogRecord record) {
String name = record.getLoggerName();
if (name == null) {
name = UNKNOWN_LOGGER_NAME;
}
return LoggerFactory.getLogger(name);
}
在處理日志的過程中,使用了slf4j的原生方式LoggerFactory來獲取一個slf4j定義的Logger來進行日志的輸出,而slf4j則又會選擇logback來進行實際的日志輸出。

##4.3 commons-logging切換到logback##
###4.3.1 使用案例###
使用的jar包

commons-logging
案例如下:

private static Log logger=LogFactory.getLog(JulJclTest.class);

public static void main(String[] args){
if(logger.isTraceEnabled()){
logger.trace("commons-logging-jcl trace message");
}
}
可以看到我們使用commons-logging的API來進行日志的編程操作,現在想切換成logback來進行日志的輸出(這其實就是commons-logging與logback的集成),解決辦法如下:

第一步:去掉commons-logging jar包(其實去不去都無所謂)

第二步:加入以下jar包:

jcl-over-slf4j(實現commons-logging切換到slf4j)

slf4j-api

logback-core

logback-classic

第三步:在類路徑下加入logback的配置文件

###4.3.2 切換原理###
這個原理之前都已經說過了,可以看下commons-logging與logback的集成

就是commons-logging通過jcl-over-slf4j 來選擇slf4j作為底層的日志輸出對象,而slf4j又選擇logback來作為底層的日志輸出對象。

##4.4 常用的日志場景切換解釋##
上面把日志的切換原理說清楚了,下面就針對具體的例子來進行應用,先來看下slf4j官方的一張圖:

輸入圖片說明

下面分別詳細說明這三個案例

###4.4.1###

現狀:目前的應用程序中已經使用了如下混雜方式的API來進行日志的編程

commons-logging

log4j1

jdk-logging

改進:現在想統一將日志的輸出交給logback

第一步:將上述日志系統全部無縫先切換到slf4j

(1)去掉commons-logging(其實去不去都可以),使用jcl-over-slf4j將commons-logging的底層日志輸出切換到slf4j

(2)去掉log4j1(必須去掉),使用log4j-over-slf4j,將log4j1的日志輸出切換到slf4j

(3)使用jul-to-slf4j,將jul的日志輸出切換到slf4j

第二步:使slf4j選擇logback來作為底層日志輸出,加入以下jar包:
(1)slf4j-api

(2)logback-core

(3)logback-classic

###4.4.2###

現狀:目前的應用程序中已經使用了如下混雜方式的API來進行日志的編程:

commons-logging

jdk-logging

改進:現在想統一將日志的輸出交給log4j1

第一步:將上述日志系統全部無縫先切換到slf4j

(1)去掉commons-logging(其實去不去都可以),使用jcl-over-slf4j將commons-logging的底層日志輸出切換到slf4j

(2)使用jul-to-slf4j,將jul的日志輸出切換到slf4j

第二步:使slf4j選擇log4j1來作為底層日志輸出,加入以下jar包:
(1)slf4j-api

(2)log4j

(3)slf4j-log4j12(集成包)

###4.4.3###

現狀:目前的應用程序中已經使用了如下混雜方式的API來進行日志的編程

commons-logging
log4j
改進:現在想統一將日志的輸出交給jdk-logging

第一步:將上述日志系統全部無縫先切換到slf4j

(1)去掉commons-logging(其實去不去都可以),使用jcl-over-slf4j將commons-logging的底層日志輸出切換到slf4j

(2)去掉log4j1(必須去掉),使用log4j-over-slf4j,將log4j1的日志輸出切換到slf4j

第二步:使slf4j選擇jdk-logging來作為底層日志輸出,加入以下jar包:
(1)slf4j-api

(2)slf4j-jdk14(集成包)

#5 沖突說明# ##5.1 jcl-over-slf4j 與 slf4j-jcl 沖突##

jcl-over-slf4j: commons-logging切換到slf4j
slf4j-jcl : slf4j切換到commons-logging
如果這兩者共存的話,必然造成相互委托,造成內存溢出。

##5.2 log4j-over-slf4j 與 slf4j-log4j12 沖突##

log4j-over-slf4j : log4j1切換到slf4j
slf4j-log4j12 : slf4j切換到log4j1
如果這兩者共存的話,必然造成相互委托,造成內存溢出。但是log4j-over-slf4內部做了一個判斷,可以防止造成內存溢出:即判斷slf4j-log4j12 jar包中的org.slf4j.impl.Log4jLoggerFactory是否存在,如果存在則表示沖突了,拋出異常提示用戶要去掉對應的jar包,代碼在slf4j-log4j12 jar包的org.apache.log4j.Log4jLoggerFactory。

##5.3 jul-to-slf4j 與 slf4j-jdk14 沖突##
jul-to-slf4j : jdk-logging切換到slf4j
slf4j-jdk14 : slf4j切換到jdk-logging
如果這兩者共存的話,必然造成相互委托,造成內存溢出。

 

    參考文檔鏈接:https://pan.baidu.com/s/1riGTe9e1kY6qSXsrGfkOAQ  提取碼:h2l8


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM