spring日志體系淺析(spring 5.x)


  日志是進行軟件開發必不可少的一項功能,目前流行着很多開源日志庫,比如log4j、log4j2、logback、JDK Logging、commons-logging、slf4j等。

  幾種日志產品的介紹

  JDK Logging:Java標准庫內置的日志包 java.util.logging,以下簡稱jul。

  log4j:一種非常流行的日志框架,最新版本是2.x。

  commons-logging:簡稱jcl,它是一個第三方的日志庫,由Apache創建的日志模塊。特點是可以掛接不同的日志系統,可以根據配置文件指定掛接的日志系統。默認情況下,jcl自動搜索並使用log4j,如果過沒找到log4j,再使用JDK Logging。

  前面介紹了Commons Logging和Log4j,它們一個負責充當日志API,一個負責實現日志底層,搭配使用非常便於開發。還有SLF4J和Logback,其實SLF4J類似於Commons Logging,也是一個日志接口,而Logback類似於Log4j,是一個日志的實現。

  為什么有了Commons Logging和Log4j,又會蹦出來SLF4J和Logback?這是因為Java有着非常悠久的開源歷史,不但OpenJDK本身是開源的,而且我們用到的第三方庫,幾乎全部都是開源的。開源生態豐富的一個特定就是,同一個功能,可以找到若干種互相競爭的開源庫。因為對Commons Logging的接口不滿意,有人就搞了SLF4J。因為對Log4j的性能不滿意,有人就搞了Logback。

  總結起來,幾個日志產品的關系如圖所示

  

  spring日志測試

  spring 4.x 及以前版本基本采用jcl,擴展機制根據用戶手動依賴的日志產品進行掛接,改變spring默認日志框架,有興趣的童鞋可以看下spring 4.x版本的源碼。本文基於spring 5.x 版本。

  書寫了一個小demo進行spring的默認日志測試,pom文件中只添加了spring上下文環境依賴

1 <dependency>
2     <groupId>org.springframework</groupId>
3     <artifactId>spring-context</artifactId>
4     <version>5.0.4.RELEASE</version>
5 </dependency>

  運行以下spring上下文初始化代碼,

1 public class SpringLogTest {
2     public static void main(String[] args) {
3         AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(BeanConfig.class);
4     }
5 }

  console打印以下日志

  十一月 27, 2019 4:38:09 下午 org.springframework.context.support.AbstractApplicationContext prepareRefresh
  信息: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@51081592: startup date [Wed Nov 27 16:37:30 CST 2019]; root of context hierarchy

  spring日志源碼分析

  當然,以上測試中,日志的輸出內容並不重要,重要的是spring是如何選擇logging類型,並進行打印的。

  打印上述日志的地方,是在AbstractApplicationContext類(AnnotationConfigApplicationContext繼承自它)的prepareRefresh方法,該方法在AnnotationConfigApplicationContext初始化的時候執行。

1 protected void prepareRefresh() {
2     this.startupDate = System.currentTimeMillis();
3     this.closed.set(false);
4     this.active.set(true);
5 
6     if (logger.isInfoEnabled()) {
7         logger.info("Refreshing " + this);
8     }

   這段代碼里面有個logger是關鍵,該logger是在AbstractApplicationContext中的定義的一個屬性:

1 protected final Log logger = LogFactory.getLog(getClass());

   那我們繼續看LogFactory類,會發現該類是在org.apache.commons.logging包下面,但是仔細查看spring包結構,發現該包位於spring-jcl目錄下(這是與4.x不同的地方,5.x系列spring開發團隊改寫了commons-logging的內部源碼)

  在LogFactory類中,getLog方法是一個門面方法,調用了重載的getFactory方法。

 1 public abstract class LogFactory {
 2     private static LogFactory.LogApi logApi;   //默認的
 3 
 4     public LogFactory() {
 5     }
 6 
 7     public static Log getLog(Class<?> clazz) {
 8         return getLog(clazz.getName());
 9     }
10   //根據logApi的值選擇logger類型
11     public static Log getLog(String name) {
12         switch(logApi) {
13         case LOG4J:
14             return LogFactory.Log4jDelegate.createLog(name);
15         case SLF4J_LAL:
16             return LogFactory.Slf4jDelegate.createLocationAwareLog(name);
17         case SLF4J:
18             return LogFactory.Slf4jDelegate.createLog(name);
19         default:
20             return LogFactory.JavaUtilDelegate.createLog(name);
21         }
22     }
23   ......部分不太重要的代碼,此處忽略42 
43     static {
       //logApi的默認值
44 logApi = LogFactory.LogApi.JUL; 45 ClassLoader cl = LogFactory.class.getClassLoader(); 46      //根據load的情況,動態更改logApi的值 47 try { 48 cl.loadClass("org.apache.logging.log4j.spi.ExtendedLogger"); 49 logApi = LogFactory.LogApi.LOG4J; 50 } catch (ClassNotFoundException var6) { 51 try { 52 cl.loadClass("org.slf4j.spi.LocationAwareLogger"); 53 logApi = LogFactory.LogApi.SLF4J_LAL; 54 } catch (ClassNotFoundException var5) { 55 try { 56 cl.loadClass("org.slf4j.Logger"); 57 logApi = LogFactory.LogApi.SLF4J; 58 } catch (ClassNotFoundException var4) { 59 ; 60 } 61 } 62 } 63 64 }

  在重載的getLog方法中,會根據一個變量logApi的值返回不同的日志對象,logApi的值初始化為jul;

  在目前的場景下,代碼會返回一個JDK Logging logger進行打印信息,也就是以上的getLog方法中的default情況。

  那么,現在問題來了,如果想改變spring的輸出日志類型,如何修改?

  從上述的代碼看,返回logger對象是根據logApi的值的,但是spring在此處並未提供可供用戶修改logApi值的方法或者接口。細看LogFactory中有一個static的靜態代碼塊,在該代碼塊執行的時候會去load相關的日志包產品,根據加載到的情況去動態的修改logApi的值,從而可以達到用戶自選擇logger類型的目的。

  那么我如果要改變spring的輸出是log4j打印的消息,那么可以考慮采用slf4j的橋接方式,在pom中加入如下依賴

 1 <dependency>
 2       <groupId>org.slf4j</groupId>
 3       <artifactId>slf4j-api</artifactId>
 4       <version>1.7.25</version>
 5 </dependency>
 6 <dependency>
 7       <groupId>org.slf4j</groupId>
 8       <artifactId>slf4j-log4j12</artifactId>
 9       <version>1.7.28</version>
10 </dependency>

   運行代碼會發現此處打印的日志和之前默認的不同

  INFO - Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@3e57cd70: startup date [Wed Nov 27 17:26:47 CST 2019]; root of context hierarchy

   spring 5.x 日志的基本功能和核心源碼基本說明完了,可能有不太准確的地方,希望各位大神給我留言討論交流。

 

 

 

 

 

 

 

 

 

 

 

 

     


免責聲明!

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



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