日志在工作中起到關鍵作用,我們經常使用它來打印關鍵信息,方便分析,或者是輸出錯誤信息,用於bug排查,spring中同樣使用了日志進行信息的輸出,但是spring4和spring5之間的日志又有些不同,接下來我們就進行一些分析。
1. 各種日志技術簡述:
log4j,jul,jcl,log4j2,slf4j
我們先把他們展示出來,以免引用錯誤。
1.1 log4j
使用log4j需要引入log4j的配置文件log4j.properties,內容簡配一下:
log4j.rootLogger = info,stdout
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target = System.out
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern = [%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n
pom文件中只引入log4j所需要的jar包
程序也寫的簡單一點,輸出日志就行:
結果:
日志是輸出的。
1.2JUL
JUL 是jdk自帶的,所以pom文件中不需要引入任何包,直接擼代碼:
結果:
日志輸出格式我們不再做改變,記住log4j和jul的日志輸出格式,后面有用到。
1.3:JCL
pom中引入所需要的包:
擼代碼:
結果:
嗚呼,這個日志輸出是不是很眼熟呀,長得很像上面JUL打印出來的日志,難道他們之間有什么不可描述的事情?所以最帥的我准備打開JCL的源碼一探究竟。
從上面代碼中的LogFactory的getLog方法下手,進去:
木得意思,就一行代碼,那就再深一點,進入getInstance()中,
第一行代碼看着像是拿到了log,不過老哥我已經幫大家踩過坑了,debug走了一遍,第一行代碼執行后,Log 依然是null,所以我們繼續往下走,進入到
this.newInstance()方法中,有點長,就醬紫:
protected Log newInstance(String name) throws LogConfigurationException { Log instance = null; try { Object[] params; if (this.logConstructor == null) { instance = this.discoverLogImplementation(name); } else { params = new Object[]{name}; instance = (Log)this.logConstructor.newInstance(params); } if (this.logMethod != null) { params = new Object[]{this}; this.logMethod.invoke(instance, params); } return instance; } catch (LogConfigurationException var5) { throw var5; } catch (InvocationTargetException var6) { Throwable c = var6.getTargetException(); if (c != null) { throw new LogConfigurationException(c); } else { throw new LogConfigurationException(var6); } } catch (Throwable var7) { throw new LogConfigurationException(var7); } }
這段代碼差一點就比我的長處還長呢,所以我又幫大家debug了一遍,在第一個if判斷 之后執行的這個方法 this.discoverLogImplementation(name);就是我們要找的,再深入,在
discoverLogImplementation()這個方法中,我找到了這段代碼:
result屬性就是我們需要返回的Log,循環條件中有一個classesToDiscover,並且還把他的值傳給了this.createLogFromClass()方法,點擊看看他是什么,
String[] classesToDiscover = new String[]{"org.apache.commons.logging.impl.Log4JLogger",
"org.apache.commons.logging.impl.Jdk14Logger",
"org.apache.commons.logging.impl.Jdk13LumberjackLogger",
"org.apache.commons.logging.impl.SimpleLog"};
這個數組里面找到了 org.apache.commons.logging.impl.Jdk14Logger,這不就是我們的JUL 嗎?再想到剛剛的for循環,我們很容易理解,JCL就是循環這個數組,一次獲取Log,知道Log 不為null為止,為了驗證我們的猜想,
我們可以引入log4j的jar包和配置文件,其他代碼不做修改,這樣按照數組順序,應該打印出來log4j的日志。
果然,在有log4j的情況下,JCL按照剛剛那個數組,順序加載log,知道Log不為null。
由此可以看出,JCL起到的是一個中間商賺差價的作用,在有log4j的時候使用log4j,否則使用JUL
2 Sring4 的日志體系
上面說了那么多,只是介紹了一部分日志技術,接下來我們開始分別分析spring4和spring5的日志體系。
2.1 構建spring4項目
采用java+注解的方式快速構建,pom中只引入spring-context包
運行下面的代碼,可以看到有日志輸出
找到打印日志的地方,debug模式下,查看輸出日志的Log是什么log
可以看出是jdk14Logger,這個在JCL中說過,這個指的是JUL,也就是說在默認spring日志體系下,采用的是JUL,
接下來,我們按照之前的方法引入log4j,debug運行上面的程序,再次查看日志類型
額,這次在增加log4j jar包和配置文件的情況下,spring4有使用了log4j,這么像JCL呢,木錯,讓我們在idea中打開spring4的日志依賴結構:
common-logging 這不就是JCL使用到的包嗎,可以看出,Spring4使用的是原生的JCL,所以在有log4j的時候使用log4j打印日志,沒有的時候使用JUL打印日志。
3.Spring5日志體系
線上依賴結構圖:
答題結構沒變,只是原來common-logging ,換成了spring-jcl,看名字就知道是spring自造的包,jcl,更是標注了,它使用的是JCL日志體系。
所以還是看源碼吧。
按照之前的經驗,我們只用debug找到spring內部一個Log,看看他的產生方式和類型。這次我給大家找了AbstractApplicationContext里面找到產生Log的地方
進入這個方法的getLog()中,一直深入,不要憐惜spring,找到LogAdapter中的createLog()方法
可以看出來spring5中對日志的生產,不在像原生JCL中那樣使用一個數組,然后進行循環產生,這里用到的是Switch case,這個關鍵字段LogApi又是在哪一部分賦值的呢?看圖
Duang ,沒錯是在靜態代碼塊中賦的值,為了驗證,我們准備用其中提到的log4j2驗證(注意:log4j不行,因為這里的switch沒有log4j選項),首先我們准備log4j2的配置文件
<Configuration status="WARN"> <Appenders> <Console name="Console" target="SYSTEM_OUT"> <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/> </Console> </Appenders> <Loggers> <Root level="debug"> <AppenderRef ref="Console"/> </Root> </Loggers> </Configuration>
然后准備pom
代碼還是這一行,直接運行:
結果有日志打印出來了
所以,在spring5中,依然使用的是JCL,但是不是原生的,是經過改造的JCL,默認使用的是JUL,而原生JCL中默認使用的是log4j.
好了,就醬紫,求輕拍,大家中秋快樂。