乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      探索Java日志的奧秘: 底層日志系統(tǒng)

       蘇心閣 2019-10-07

      log4j2是apache在log4j的基礎(chǔ)上,參考logback架構(gòu)實(shí)現(xiàn)的一套新的日志系統(tǒng)(我感覺(jué)是apache害怕logback了)。

      log4j2的官方文檔上寫(xiě)著一些它的優(yōu)點(diǎn):

      在擁有全部logback特性的情況下,還修復(fù)了一些隱藏問(wèn)題

      API 分離:現(xiàn)在log4j2也是門(mén)面模式使用日志,默認(rèn)的日志實(shí)現(xiàn)是log4j2,當(dāng)然你也可以用logback(應(yīng)該沒(méi)有人會(huì)這么做)

      性能提升:log4j2包含下一代基于LMAX Disruptor library的異步logger,在多線(xiàn)程場(chǎng)景下,擁有18倍于log4j和logback的性能

      多API支持:log4j2提供Log4j 1.2, SLF4J, Commons Logging and java.util.logging (JUL) 的API支持

      避免鎖定:使用Log4j2 API的應(yīng)用程序始終可以選擇使用任何符合SLF4J的庫(kù)作為log4j-to-slf4j適配器的記錄器實(shí)現(xiàn)

      自動(dòng)重新加載配置:與Logback一樣,Log4j 2可以在修改時(shí)自動(dòng)重新加載其配置。與Logback不同,它會(huì)在重新配置發(fā)生時(shí)不會(huì)丟失日志事件。

      高級(jí)過(guò)濾: 與Logback一樣,Log4j 2支持基于Log事件中的上下文數(shù)據(jù),標(biāo)記,正則表達(dá)式和其他組件進(jìn)行過(guò)濾。

      插件架構(gòu): Log4j使用插件模式配置組件。因此,您無(wú)需編寫(xiě)代碼來(lái)創(chuàng)建和配置Appender,Layout,Pattern Converter等。Log4j自動(dòng)識(shí)別插件并在配置引用它們時(shí)使用它們。

      屬性支持:您可以在配置中引用屬性,Log4j將直接替換它們,或者Log4j將它們傳遞給將動(dòng)態(tài)解析它們的底層組件。

      Java 8 Lambda支持

      自定義日志級(jí)別

      產(chǎn)生垃圾少:在穩(wěn)態(tài)日志記錄期間,Log4j 2 在獨(dú)立應(yīng)用程序中是無(wú)垃圾的,在Web應(yīng)用程序中是低垃圾。這減少了垃圾收集器的壓力,并且可以提供更好的響應(yīng)時(shí)間性能。

      和應(yīng)用server集成:版本2.10.0引入了一個(gè)模塊log4j-appserver,以改進(jìn)與Apache Tomcat和Eclipse Jetty的集成。

      Log4j2類(lèi)圖:

      這次從四個(gè)地方去探索源碼:?jiǎn)?dòng),配置,異步,插件化

      源碼探索

      啟動(dòng)

      log4j2的關(guān)鍵組件

      LogManager

      根據(jù)配置指定LogContexFactory,初始化對(duì)應(yīng)的LoggerContext

      LoggerContext

      1、解析配置文件,解析為對(duì)應(yīng)的java對(duì)象。

      2、通過(guò)LoggerRegisty緩存Logger配置

      3、Configuration配置信息

      4、start方法解析配置文件,轉(zhuǎn)化為對(duì)應(yīng)的java對(duì)象

      5、通過(guò)getLogger獲取logger對(duì)象

      Logger

      LogManaer

      該組件是Log4J啟動(dòng)的入口,后續(xù)的LoggerContext以及Logger都是通過(guò)調(diào)用LogManager的靜態(tài)方法獲得。我們可以使用下面的代碼獲取Logger

      Logger logger = LogManager.getLogger;

      可以看出LogManager是十分關(guān)鍵的組件,因此在這個(gè)小節(jié)中我們?cè)敿?xì)分析LogManager的啟動(dòng)流程。

      LogManager啟動(dòng)的入口是下面的static代碼塊:

      這段靜態(tài)代碼段主要分為下面的幾個(gè)步驟:

      首先根據(jù)特定配置文件的配置信息獲取loggerContextFactory

      如果沒(méi)有找到對(duì)應(yīng)的Factory的實(shí)現(xiàn)類(lèi)則通過(guò)ProviderUtil中的getProviders方法載入providers,隨后通過(guò)provider的loadLoggerContextFactory方法載入LoggerContextFactory的實(shí)現(xiàn)類(lèi)

      如果provider中沒(méi)有獲取到LoggerContextFactory的實(shí)現(xiàn)類(lèi)或provider為空,則使用SimpleLoggerContextFactory作為L(zhǎng)oggerContextFactory。

      根據(jù)配置文件載入LoggerContextFactory

      在這段邏輯中,LogManager優(yōu)先通過(guò)配置文件”log4j2.component.properties”通過(guò)配置項(xiàng)”log4j2.loggerContextFactory”來(lái)獲取LoggerContextFactory,如果用戶(hù)做了對(duì)應(yīng)的配置,通過(guò)newCheckedInstanceOf方法實(shí)例化LoggerContextFactory的對(duì)象,最終的實(shí)現(xiàn)方式為:

      在默認(rèn)情況下,不存在初始的默認(rèn)配置文件log4j2.component.properties,因此需要從其他途徑獲取LoggerContextFactory。

      通過(guò)Provider實(shí)例化LoggerContextFactory對(duì)象

      代碼:

      這里比較有意思的是hasProviders和getProviders都會(huì)通過(guò)線(xiàn)程安全的方式去懶加載ProviderUtil這個(gè)對(duì)象。跟進(jìn)lazyInit方法:

      再看構(gòu)造方法:

      這里的懶加載其實(shí)就是懶加載Provider對(duì)象。在創(chuàng)建新的providerUtil實(shí)例的過(guò)程中就會(huì)直接實(shí)例化provider對(duì)象,其過(guò)程是先通過(guò)getClassLoaders方法獲取provider的類(lèi)加載器,然后通過(guò)loadProviders(classLoader);加載類(lèi)。在providerUtil實(shí)例化的最后,會(huì)統(tǒng)一查找”META-INF/log4j-provider.properties”文件中對(duì)應(yīng)的provider的url,會(huì)考慮從遠(yuǎn)程加載provider。而loadProviders方法就是在ProviderUtil的PROVIDERS列表中添加對(duì)一個(gè)的provider。可以看到默認(rèn)的provider是org.apache.logging.log4j.core.impl.Log4jContextFactory

      LoggerContextFactory = org.apache.logging.log4j.core.impl.Log4jContextFactory

      Log4jAPIVersion = 2.1.0

      FactoryPriority= 10

      很有意思的是這里懶加載加上了鎖,而且使用的是

      lockInterruptibly這個(gè)方法。lockInterruptibly和lock的區(qū)別如下:

      lock 與 lockInterruptibly比較區(qū)別在于:

      lock 優(yōu)先考慮獲取鎖,待獲取鎖成功后,才響應(yīng)中斷。

      lockInterruptibly 優(yōu)先考慮響應(yīng)中斷,而不是響應(yīng)鎖的普通獲取或重入獲取。

      ReentrantLock.lockInterruptibly允許在等待時(shí)由其它線(xiàn)程調(diào)用等待線(xiàn)程的

      Thread.interrupt 方法來(lái)中斷等待線(xiàn)程的等待而直接返回,這時(shí)不用獲取鎖,而會(huì)拋出一個(gè)InterruptedException。 ReentrantLock.lock方法不允許Thread.interrupt中斷,即使檢測(cè)到Thread.isInterrupted,一樣會(huì)繼續(xù)嘗試獲取鎖,失敗則繼續(xù)休眠。只是在最后獲取鎖成功后再把當(dāng)前線(xiàn)程置為interrupted狀態(tài),然后再中斷線(xiàn)程。

      上面有一句注釋值得注意:

      原來(lái)這里是為了讓osgi可以阻止啟動(dòng)。

      再回到logManager:

      可以看到在加載完P(guān)rovider之后,會(huì)做factory的綁定:

      到這里,logmanager的啟動(dòng)流程就結(jié)束了。

      配置

      在不使用slf4j的情況下,我們獲取logger的方式是這樣的:

      Logger logger = logManager.getLogger(xx.class)

      跟進(jìn)getLogger方法:

      這里有一個(gè)getContext方法,跟進(jìn),

      上文提到factory的具體實(shí)現(xiàn)是Log4jContextFactory,跟進(jìn)getContext

      方法:

      直接看start:

      發(fā)現(xiàn)其中的核心方法是reconfigure方法,繼續(xù)跟進(jìn):

      可以看到每一個(gè)configuration都是從ConfigurationFactory拿出來(lái)的,我們先看看這個(gè)類(lèi)的getInstance看看:

      這里可以看到ConfigurationFactory中利用了PluginManager來(lái)進(jìn)行初始化,PluginManager會(huì)將ConfigurationFactory的子類(lèi)加載進(jìn)來(lái),默認(rèn)使用的XmlConfigurationFactory,JsonConfigurationFactory,YamlConfigurationFactory這三個(gè)子類(lèi),這里插件化加載暫時(shí)按下不表。

      回到reconfigure這個(gè)方法,我們看到獲取ConfigurationFactory實(shí)例之后會(huì)去調(diào)用getConfiguration方法:

      跟進(jìn)getConfiguration,這里值得注意的是有很多個(gè)getConfiguration,注意甄別,如果不確定的話(huà)可以通過(guò)debug的方式來(lái)確定。

      這里就會(huì)根據(jù)之前加載進(jìn)來(lái)的factory進(jìn)行配置的獲取,具體的不再解析。

      回到reconfigure,之后的步驟就是setConfiguration,入?yún)⒕褪莿偛奴@取的config

      這個(gè)方法最重要的步驟就是config.start,這才是真正做配置解析的

      這里面有如下步驟:

      獲取日志等級(jí)的插件

      初始化

      初始化Advertiser

      配置

      先看一下初始化,也就是setup這個(gè)方法,setup是一個(gè)需要被復(fù)寫(xiě)的方法,我們以XMLConfiguration作為例子,

      發(fā)現(xiàn)這里面有一個(gè)比較重要的方法constructHierarchy,跟進(jìn):

      發(fā)現(xiàn)這個(gè)就是一個(gè)樹(shù)遍歷的過(guò)程。誠(chéng)然,配置文件是以xml的形式給出的,xml的結(jié)構(gòu)就是一個(gè)樹(shù)形結(jié)構(gòu)?;氐絪tart方法,跟進(jìn)doConfiguration:

      發(fā)現(xiàn)就是對(duì)剛剛獲取的configuration進(jìn)行解析,然后塞進(jìn)正確的地方?;氐絪tart方法,可以看到昨晚配置之后就是開(kāi)啟logger和appender了。

      異步

      AsyncAppender

      log4j2突出于其他日志的優(yōu)勢(shì),異步日志實(shí)現(xiàn)。我們先從日志打印看進(jìn)去。找到Logger,隨便找一個(gè)log日志的方法。

      一路跟進(jìn)

      可以看出這個(gè)在打日志之前做了調(diào)用次數(shù)的記錄。跟進(jìn)tryLogMessage,

      繼續(xù)跟進(jìn):

      這里可以看到在實(shí)際打日志的時(shí)候,會(huì)從config中獲取打日志的策略,跟蹤ReliabilityStrategy的創(chuàng)建,發(fā)現(xiàn)默認(rèn)的實(shí)現(xiàn)類(lèi)為DefaultReliabilityStrategy,跟進(jìn)看實(shí)際打日志的方法

      這里實(shí)際打日志的方法居然是交給一個(gè)config去實(shí)現(xiàn)的。。。感覺(jué)有點(diǎn)奇怪。。跟進(jìn)看看

      可以清楚的看到try之前是在創(chuàng)建LogEvent,try里面做的才是真正的log(好tm累),一路跟進(jìn)。

      接下來(lái)就是callAppender了,我們直接開(kāi)始看AsyncAppender的append方法:

      這里主要的步驟就是:

      生成logEvent

      將logEvent放入BlockingQueue,就是transfer方法

      如果BlockingQueue滿(mǎn)了則啟用相應(yīng)的策略

      同樣的,這里也有一個(gè)線(xiàn)程用來(lái)做異步消費(fèi)的事情

      直接看run方法:

      阻塞獲取logEvent

      將logEvent分發(fā)出去

      如果線(xiàn)程要退出了,將blockingQueue里面的event消費(fèi)完在退出。

      AsyncLogger

      直接從AsyncLogger的logMessage看進(jìn)去:

      跟進(jìn)logWithThreadLocalTranslator,

      這里的邏輯很簡(jiǎn)單,就是將日志相關(guān)的信息轉(zhuǎn)換成RingBufferLogEvent(RingBuffer是Disruptor的無(wú)所隊(duì)列),然后將其發(fā)布到RingBuffer中。發(fā)布到RingBuffer中,那肯定也有消費(fèi)邏輯。這時(shí)候有兩種方式可以找到這個(gè)消費(fèi)的邏輯。

      找disruptor被使用的地方,然后查看,但是這樣做會(huì)很容易迷惑

      按照Log4j2的尿性,這種Logger都有對(duì)應(yīng)的start方法,我們可以從start方法入手尋找

      在start方法中,我們找到了一段代碼:

      final RingBufferLogEventHandler[] handlers = {new RingBufferLogEventHandler};

      disruptor.handleEventsWith(handlers);

      直接看看這個(gè)RingBufferLogEventHandler的實(shí)現(xiàn):

      順著接口找上去,發(fā)現(xiàn)一個(gè)接口:

      通過(guò)注釋可以發(fā)現(xiàn),這個(gè)onEvent就是處理邏輯,回到RingBufferLogEventHandler的onEvent方法,發(fā)現(xiàn)里面有一個(gè)execute方法,跟進(jìn):

      這個(gè)方法就是實(shí)際打日志了,AsyncLogger看起來(lái)還是比較簡(jiǎn)單的,只是使用了一個(gè)Disruptor。

      插件化

      之前在很多代碼里面都可以看到

      final PluginManager manager = new PluginManager(CATEGORY);

      manager.collectPlugins(pluginPackages);

      其實(shí)整個(gè)log4j2為了獲得更好的擴(kuò)展性,將自己的很多組件都做成了插件,然后在配置的時(shí)候去加載plugin。

      跟進(jìn)collectPlugins。

      處理邏輯如下:

      從Log4j2Plugin.dat中加載所有的內(nèi)置的plugin

      然后將OSGi Bundles中的Log4j2Plugin.dat中的plugin加載進(jìn)來(lái)

      再加載傳入的package路徑中的plugin

      最后加載配置中的plugin

      邏輯還是比較簡(jiǎn)單的,但是我在看源碼的時(shí)候發(fā)現(xiàn)了一個(gè)很有意思的東西,就是在加載log4j2 core插件的時(shí)候,也就是

      PluginRegistry.getInstance.loadFromMainClassLoader

      這個(gè)方法,跟進(jìn)到decodeCacheFiles:

      可以發(fā)現(xiàn)加載時(shí)候是從一個(gè)文件(PLUGIN_CACHE_FILE)獲取所有要獲取的plugin。看到這里的時(shí)候我有一個(gè)疑惑就是,為什么不用反射的方式直接去掃描,而是要從文件中加載進(jìn)來(lái),而且文件是寫(xiě)死的,很不容易擴(kuò)展啊。然后我找了一下PLUGIN_CACHE_FILE這個(gè)靜態(tài)變量的用處,發(fā)現(xiàn)了PluginProcessor這個(gè)類(lèi),這里用到了注解處理器。

      /**

      * Annotation processor for pre-scanning Log4j 2 plugins.

      */@SupportedAnnotationTypes('org.apache.logging.log4j.core.config.plugins.*')public class PluginProcessor extends AbstractProcessor { // TODO: this could be made more abstract to allow for compile-time and run-time plugin processing

      (不太重要的方法省略)

      我們可以看到在process方法中,PluginProcessor會(huì)先收集所有的Plugin,然后在寫(xiě)入文件。這樣做的好處就是可以省去反射時(shí)候的開(kāi)銷(xiāo)。

      然后我又看了一下Plugin這個(gè)注解,發(fā)現(xiàn)它的RetentionPolicy是RUNTIME,一般來(lái)說(shuō)PluginProcessor是搭配RetentionPolicy.SOURCE,CLASS使用的,而且既然你把自己的Plugin掃描之后寫(xiě)在文件中了,RetentionPolicy就沒(méi)有必要是RUNTIME了吧,這個(gè)是一個(gè)很奇怪的地方。

      小結(jié)

      總算是把Log4j2的代碼看完了,發(fā)現(xiàn)它的設(shè)計(jì)理念很值得借鑒,為了靈活性,所有的東西都設(shè)計(jì)成插件式?;ヂ?lián)網(wǎng)技術(shù)日益發(fā)展,各種中間件層出不窮,而作為工程師的我們更需要做的是去思考代碼與代碼之間的關(guān)系,毫無(wú)疑問(wèn)的是,解耦是最具有美感的關(guān)系。

        本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶(hù)發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買(mǎi)等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
        轉(zhuǎn)藏 分享 獻(xiàn)花(0

        0條評(píng)論

        發(fā)表

        請(qǐng)遵守用戶(hù) 評(píng)論公約

        類(lèi)似文章 更多