整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          Logbook:Python 快速日志記錄實踐

          Logbook:Python 快速日志記錄實踐

          者:東東 yasking

          來源:https://blog.yasking.org/a/python-logbook.html

          Python 本身有logging日志記錄模塊,之前發現了logbook這個包,介紹說是替代logging,索性整理一下,方便之后使用

          1. >>> from logbook import Logger, StreamHandler

          2. >>> import sys

          3. >>> StreamHandler(sys.stdout).push_application

          4. >>> log=Logger('Logbook')

          5. >>> log.info('Hello, World!')

          6. [2015-10-05 18:55:56.937141] INFO: Logbook: Hello, World!

          上邊這是文檔中給出的例子,它定義了許多的Handler,可以把日志記錄到標準輸出,文件,E-MAIL,甚至Twitter

          StreamHandler

          使用 StreamHandler記錄的日志會以流輸出,這里指定sys.stdout也就是記錄到標準輸出,與print一樣

          (一)可以使用 with來在一定作用域內記錄日志

          1. # -*- coding: utf-8 -*-

          2. from logbook import Logger, StreamHandler

          3. import logbook

          4. import sys

          5. handler=StreamHandler(sys.stdout)

          6. log=Logger('test')

          7. def main:

          8. log.info('something logging')

          9. if __name__=='__main__':

          10. with handler.applicationbound:

          11. main

          (二)也可以指定作用于整個應用

          1. # -*- coding: utf-8 -*-

          2. from logbook import Logger, StreamHandler

          3. import logbook

          4. import sys

          5. handler=StreamHandler(sys.stdout)

          6. handler.push_application

          7. log=Logger('test')

          8. def main:

          9. log.info('something logging')

          10. if __name__=='__main__':

          11. main

          FileHandler

          使用 FileHandler可以把日志記錄到文件,這也是最常見的方式

          1. # -*- coding: utf-8 -*-

          2. from logbook import Logger, FileHandler

          3. import logbook

          4. import sys

          5. handler=FileHandler('app.log')

          6. handler.push_application

          7. log=Logger('test')

          8. def main:

          9. log.info('something logging')

          10. if __name__=='__main__':

          11. main

          日志就寫到了 app.log文件

          同時輸出到文件與STDOUT

          同時把記錄輸出到多個地方可以方便查閱和記錄,初始化 Handler的時候設置bubble參數就可以使得其它Handler也可以接收到記錄

          1. from logbook import Logger, StreamHandler, FileHandler

          2. import logbook

          3. import sys

          4. '''

          5. 記錄日志到文件和STDOUT

          6. '''

          7. StreamHandler(sys.stdout, level='DEBUG').push_application

          8. FileHandler('app.log', bubble=True, level='INFO').push_application

          9. log=Logger('test')

          10. def main:

          11. log.info('hello world')

          12. if __name__=='__main__':

          13. main

          另外,通過 level可以設置日志級別,級別如下,從下到上級別越來越高,如level設置為INFO, 則除了DEBUG外都會記錄,設置不同的級別,搭配各種Handler可以讓日志的記錄更加靈活,上邊使用的log.info可以使用不同的記錄級別

          級別說明
          critical嚴重錯誤,需要退出程序
          error錯誤,但在可控范圍內
          warning警告
          notice大多數情況下希望看到的記錄
          info大多數情況不希望看到的記錄
          debug調試程序的時候詳細輸出的記錄

          MailHandler

          和日志文件同樣重要的就是 MailHandler了,當出現了比較嚴重錯誤的時候就要發送郵寄進行通知

          詳細的文檔見:

          分別使用了 163qq郵箱發送郵件測試,使用的郵箱需要開啟SMTP權限,代碼如下(163和qq發送參數稍有不同)

          163 Mail

          1. # -*- coding: utf-8 -*-

          2. from logbook import Logger, MailHandler

          3. import logbook

          4. import sys

          5. sender='Logger'

          6. recipients=['dongdong@qq.com']

          7. email_user='dongdong@163.com'

          8. email_pass='password'

          9. mail_handler=MailHandler(sender, recipients,

          10. server_addr='smtp.163.com',

          11. starttls=True,

          12. secure=False,

          13. credentials=(email_user, email_pass),

          14. format_string=u'''

          15. Subject: {record.level_name} on My Application

          16. Message type: {record.level_name}

          17. Location: {record.filename}:{record.lineno}

          18. Module: {record.module}

          19. Function: {record.func_name}

          20. Time: {record.time:%Y-%m-%d %H:%M:%S}

          21. Remote IP: {record.extra[ip]}

          22. Request: {record.extra[url]} [{record.extra[method]}]

          23. Message: {record.message}

          24. ''',

          25. bubble=True)

          26. log=Logger('test')

          27. def main:

          28. log.info('something logging')

          29. if __name__=='__main__':

          30. with mail_handler.threadbound:

          31. main

          QQ Mail

          1. # -*- coding: utf-8 -*-

          2. from logbook import Logger, MailHandler

          3. import logbook

          4. import sys

          5. sender='Logger'

          6. recipients=['dongdong@163.com']

          7. email_user='dongdong@qq.com'

          8. email_pass='password'

          9. mail_handler=MailHandler(sender, recipients,

          10. server_addr='smtp.qq.com',

          11. starttls=False,

          12. secure=True,

          13. credentials=(email_user, email_pass),

          14. format_string=u'''

          15. Subject: {record.level_name} on My Application

          16. Message type: {record.level_name}

          17. Location: {record.filename}:{record.lineno}

          18. Module: {record.module}

          19. Function: {record.func_name}

          20. Time: {record.time:%Y-%m-%d %H:%M:%S}

          21. Remote IP: {record.extra[ip]}

          22. Request: {record.extra[url]} [{record.extra[method]}]

          23. Message: {record.message}

          24. ''',

          25. bubble=True)

          26. log=Logger('test')

          27. def main:

          28. log.info('something logging')

          29. if __name__=='__main__':

          30. with mail_handler.threadbound:

          31. main

          內容 format_string中的用大括號的會進行數值替換,Subject字段上邊的``和下邊需要空一行,這樣解析參數收到的郵件才會正確的顯示標題

          Record Processors

          上邊發送郵件的例子,參數里面有一個 record.extra[url],這個參數是可以自己指定的,比如編寫WSGI的程序,處理URL,每一條記錄都希望記錄到訪問者的IP,可以這樣做:

          1. # -*- coding: utf-8 -*-

          2. from logbook import Logger, StreamHandler, Processor

          3. import logbook

          4. import sys

          5. handler=StreamHandler(sys.stdout)

          6. handler.format_string='[{{record.time:%Y-%m-%d %H:%M:%S}}] IP:{record.extra[ip]} {record.level_name}: {record.channel}: {record.message}'

          7. handler.formatter

          8. log=Logger('test')

          9. def inject_ip(record):

          10. record.extra['ip']='127.0.0.1'

          11. with handler.applicationbound:

          12. with Processor(inject_ip).applicationbound:

          13. log.error('something error')

          使用自定義參數,需要重新設置 format_string,才能進行記錄,record類可以在這里找到,詳細參數見logbook.LogRecord

          Output:

          1. [2016-12-13 12:20:46] IP:127.0.0.1 ERROR: test: something error

          日期格式

          上邊在介紹的自定義日志格式的時候使用的時間是雖然指定了格式但是是 UTC格式,跟北京時間是差了8個小時的。所以需要設置讓它記錄本地的時間

          在剛才的例子前面加上如下代碼即可

          1. logbook.set_datetime_format('local') # <=新加入行

          2. handler=StreamHandler(sys.stdout)

          3. handler.format_string='[{record.time:%Y-%m-%d %H:%M:%S}] IP:{record.extra[ip]} {record.level_name}: {record.channel}: {record.message}'

          4. handler.formatter

          更過日期格式化的設置參看:api/utilities - logbook.set_datetime_format(datetime_format)

          threadbound與applicationbound

          從最開始的例子來看,可以使用兩種方式來記錄日志,一種是在最開始使用 push_*來激活,另一種是使用的時候用with構造上下文,現在進行一些補充

          pushwithpop
          push_applicationapplicationboundpop_application
          push_threadthreadboundpop_threadbound
          push_greenletgreenletboundpop_greenlet
          • 使用 pop_*可以取消記錄的上下文

          • application作用于整個應用,thread只針對當前線程

          1. handler=MyHandler

          2. handler.push_application

          3. # all here goes to that handler

          4. handler.pop_application

          消除嵌套

          使用多個Handler的時候,使用 push_方式啟動的上下文不會形成嵌套,但是使用with啟動的上下文會形成嵌套,可以使用nested handler

          1. import os

          2. from logbook import NestedSetup, Handler, FileHandler,

          3. MailHandler, Processor

          4. def inject_information(record):

          5. record.extra['cwd']=os.getcwd

          6. setup=NestedSetup([

          7. # Handler避免stderr接受消息

          8. Handler(),

          9. FileHandler('application.log', level='WARNING'),

          10. MailHandler('servererrors@example.com', ['admin@example.com'],

          11. level='ERROR', bubble=True),

          12. Processor(inject_information)

          13. ])

          使用的時候就只需要一個 with來啟動

          1. with setup.threadbound:

          2. log.info('something logging')

          logbook是個不錯的包,記錄日志靈活方便,比自己包裝發送郵件方便了不少,整理了一些基本用法,還有不少值得學習的功能,暫時能用到的基本上就這么多,之后用到高級的功能再繼續研究補充。

          題圖:pexels,CC0 授權。


          作者日常在與其他同學合作時,經常發現不合理的日志配置以及五花八門的日志記錄方式,后續作者打算在團隊內做一次Java日志的分享,本文是整理出的系列文章第一篇。

          寫這篇文章的初衷,是想在團隊內做一次Java日志的分享,因為日常在與其他同學合作時,經常發現不合理的日志配置以及五花八門的日志記錄方式。但在準備分享、補充細節的過程中,我又進一步發現目前日志相關的文章,都只是專注于某一個方面,或者講歷史和原理,或者解決包沖突,卻都沒有把整個Java日志知識串聯起來。最終這篇文章超越了之前的定位,越寫越豐富,為了讓大家看得不累,我的文章將以系列的形式展示。


          一、前言


          日志發展到今天,被抽象成了三層:接口層、實現層、適配層:

          • 接口層:或者叫日志門面(facade),就是interface,只定義接口,等著別人實現。
          • 實現層:真正干活的、能夠把日志內容記錄下來的工具。但請注意它不是上邊接口實現,因為它不感知也不直接實現接口,僅僅是獨立的實現。
          • 適配層:一般稱為Adapter,它才是上邊接口的implements。因為接口層和適配層并非都出自一家之手,它們之間無法直接匹配。而魯迅曾經說過:「計算機科學領域的任何問題都可以通過增加一個中間層來解決」(All problems in computer science can be solved by another level of indirection. -- David Wheeler[1]),所以就有了適配層。

          適配層又可以分為綁定(Binding)和橋接(Bridging)兩種能力:

          • 綁定(Binding):將接口層綁定到某個實現層(實現一個接口層,并調用實現層的方法)
          • 橋接(Bridging):將接口層橋接到另一個接口層(實現一個接口層,并調用另一個接口層的接口),主要作用是方便用戶低成本的在各接口層和適配層之間遷移

          如果你覺得上面的描述比較抽象生硬,可以先跳過,等把本篇看完自然就明白了。
          接下來我們就以時間順序,回顧一下Java日志的發展史,這有助于指導我們后續的實踐,真正做到知其所以然。

          二、歷史演進



          2.1 標準輸出 (<1999)

          Java最開始并沒有專門記錄日志的工具,大家都是用System.outSystem.err輸出日志。但它們只是簡單的信息輸出,無法區分錯誤級別、無法控制輸出粒度,也沒有什么管理、過濾能力。隨著Java工程化的深入,它們的能力就有些捉襟見肘了。

          雖然System.outSystem.err默認輸出到控制臺,但它們是有能力將輸出保存到文件的:

          System.setOut(new PrintStream(new FileOutputStream("log.txt", true)));
          System.out.println("這句將輸出到 log.txt 文件中");
          
          
          System.setErr(new PrintStream(new FileOutputStream("error.txt", true)));
          System.err.println("這句將輸出到 error.txt 文件中");


          2.2 Log4j (1999)


          在1996年,一家名為SEMPER的歐洲公司決定開發一款用于記錄日志的工具。經過多次迭代,最終發展成為Log4j。這款工具的主要作者是一位名叫Ceki Gülcü[2]的俄羅斯程序員,請記住他的名字:Ceki,后面還會多次提到他。

          到了1999年,Log4j已經被廣泛使用,隨著用戶規模的增長,用戶訴求也開始多樣化。于是Ceki在2001年選擇將Log4j開源,希望借助社區的力量將Log4j發展壯大。不久之后Apache基金會向Log4j拋出了橄欖枝,自然Ceki也加入Apache繼續從事 Log4j的開發,從此Log4j改名Apache Log4j[3]并進入發展的快車道。

          Log4j相比于System.out提供了更強大的能力,甚至很多思想到現在仍被廣泛接受,比如:

          • 日志可以輸出到控制臺、文件、數據庫,甚至遠程服務器和電子郵件(被稱做 Appender);
          • 日志輸出格式(被稱做 Layout)允許定制,比如錯誤日志和普通日志使用不同的展現形式;
          • 日志被分為5個級別(被稱作Level),從低到高依次是debug, info, warn, error, fatal,輸出前會校驗配置的允許級別,小于此級別的日志將被忽略。除此之外還有all, off兩個特殊級別,表示完全放開和完全關閉日志輸出;
          • 可以在工程中隨時指定不同的記錄器(被稱做Logger),可以為之配置獨立的記錄位置、日志級別;
          • 支持通過properties或者xml文件進行配置;

          隨著Log4j的成功,Apache又孵化了Log4Net[4]、Log4cxx[5]、Log4php[6]產品,開源社區也模仿推出了如Log4c[7]、Log4cpp[8]、Log4perl[9]等眾多項目。從中也可以印證Log4j在日志處理領域的江湖影響力。

          不過Log4j有比較明顯的性能短板,在Logback和Log4j 2推出后逐漸式微,最終Apache在2015年宣布終止開發Log4j并全面遷移至Log4j 2[10](可參考【2.7 Log4j 2 (2012)】)。


          2.3 JUL (2002.2)


          隨著Java工程的發展,Sun也意識到日志記錄非常重要,認為這個能力應該由JRE原生支持。所以在1999年Sun提交了JSR 047[11]提案,標題就叫「Logging API Specification」。不過直到2年后的2002年,Java官方的日志系統才隨Java 1.4發布。這套系統稱做Java Logging API,包路徑是java.util.logging,簡稱JUL。

          在某些追溯歷史的文章中提到,「Apache曾希望將 Log4j加入到JRE中作為默認日志實現,但傲慢的Sun沒有答應,反而很快推出了自己的日志系統」。對于這個說法我并沒有找到出處,無法確認其真實性。

          不過從實際推出的產品來看,更晚面世的JUL無論是功能還是性能都落后于Log4j,頗有因被寄予厚望而倉促發布的味道,也許那個八卦并非空穴來風,哈哈。雖然在2004年推出的Java 5.0 (1.5) [12]上JUL進步不小,但它在Log4j面前仍無太多亮點,廣大開發者并沒有遷移的動力,導致JUL始終未成氣候。

          我們在后文沒有推薦JUL的計劃,所以這里也不多介紹了(主要是我也不會)。


          2.4 JCL (2002.8)


          在Log4j和JUL之外,當時市面上還有像Apache Avalon[13](一套服務端開發框架)、 Lumberjack[14](一套跑在JDK 1.2/1.3上的開源日志工具)等日志工具。
          對于獨立且輕量的項目來說,開發者可以根據喜好使用某個日志方案即可。但更多情況是一套業務系統依賴了大量的三方工具,而眾多三方工具會各自使用不同的日志實現,當它們被集成在一起時,必然導致日志記錄混亂。

          為此Apache在2002年推出了一套接口Jakarta Commons Logging[15],簡稱 JCL,它的主要作者仍然是Ceki。這套接口主動支持了Log4j、JUL、Apache Avalon、Lumberjack等眾多日志工具。開發者如果想打印日志,只需調用JCL的接口即可,至于最終使用的日志實現則由最上層的業務系統決定。我們可以看到,這其實就是典型的接口與實現分離設計。

          但因為是先有的實現(Log4j、JUL)后有的接口(JCL),所以JCL配套提供了接口與實現的適配層(沒有使用它的最新版,原因會在【1.2.7 Log4j2 (2012)】提到):

          簡單介紹一下JCL自帶的幾個適配層/實現層:

          • AvalonLogger/LogKitLogger:用于綁定Apache Avalon的適配層,因為Avalon 不同時期的日志包名不同,適配層也對應有兩個
          • Jdk13LumberjackLogger:用于綁定Lumberjack的適配層
          • Jdk14Logger:用于綁定JUL(因為JUL從JDK 1.4開始提供)的適配層
          • Log4JLogger:用于綁定Log4j的適配層
          • NoOpLog:JCL自帶的日志實現,但它是空實現,不做任何事情
          • SimpleLog:JCL自帶的日志實現 ,讓用戶哪怕不依賴其他工具也能打印出日志來,只是功能非常簡單

          當時項目前綴取名Jakarta,是因為它屬于Apache與Sun共同推出的Jakarta Project[16]項目(郵件[17])。現在JCL作為Apache Commons[18]的子項目,叫 Apache Commons Logging,與我們常用的Commons Lang[19]、Commons Collections [20]等是師兄弟。但JCL的簡寫命名被保留了下來,并沒有改為ACL。


          2.5 Slf4j (2005)


          Log4j的作者Ceki看到了很多Log4j和JCL的不足,但又無力推動項目快速迭代,加上對Apache的管理不滿,認為自己失去了對Log4j項目的控制權(博客[21]、郵件[22]),于是在2005年選擇自立門戶,并很快推出了一款新作品Simple Logging Facade for Java[23],簡稱Slf4j。

          Slf4j也是一個接口層,接口設計與JCL非常接近(畢竟有師承關系)。相比JCL有一個重要的區別是日志實現層的綁定方式:JCL是動態綁定,即在運行時執行日志記錄時判定合適的日志實現;而Slf4j選擇的是靜態綁定,應用編譯時已經確定日志實現,性能自然更好。這就是常被提到的classloader問題,更詳細地討論可以參考What is the issue with the runtime discovery algorithm of Apache Commons Logging[24]以及Ceki自己寫的文章Taxonomy of class loader problems encountered when using Jakarta Commons Logging[25]。

          在推出Slf4j的時候,市面上已經有了另一套接口層JCL,為了將選擇權交給用戶(我猜也為了挖JCL的墻角),Slf4j推出了兩個橋接層:

          • jcl-over-slf4j:作用是讓已經在使用JCL的用戶方便的遷移到Slf4j 上來,你以為調的是JCL接口,背后卻又轉到了Slf4j接口。我說這是在挖JCL的墻角不過分吧?
          • slf4j-jcl:讓在使用Slf4j的用戶方便的遷移到JCL上,自己的墻角也挖,主打的就是一個公平公正公開。

          Slf4j通過推出各種適配層,基本滿足了用戶的所有場景,我們來看一下它的全家桶:

          網上介紹Slf4j的文章,經常會引用它官網上的兩張圖:

          感興趣的同學也可以參考。

          這里解釋一下slf4j-log4j12這個名字,它表示Slf4j + Log4j 1.2(Log4j的最后一個版本) 的適配層。類似的,slf4j-jdk14表示Slf4j + JDK 1.4(就是 JUL)的適配層。


          2.6 Logback (2006)


          然而Ceki的目標并不止于Slf4j,面對自己一手創造的Log4j,作為原作者自然是知道它存在哪些問題的。于是在2006年Ceki又推出了一款日志記錄實現方案:Logback[26]。無論是易用度、功能、還是性能,Logback 都要優于Log4j,再加上天然支持Slf4j而不需要額外的適配層,自然擁躉者眾。目前Logback已經成為Java社區最被廣泛接受的日志實現層(Logback自己在2021年的統計是48%的市占率[27])。

          相比于Log4j,Logback提供了很多我們現在看起來理所當然的新特性:

          • 支持日志文件切割滾動記錄、支持異步寫入
          • 針對歷史日志,既支持按時間或按硬盤占用自動清理,也支持自動壓縮以節省硬盤空間
          • 支持分支語法,通過<if>, <then>, <else>可以按條件配置不同的日志輸出邏輯,比如判斷僅在開發環境輸出更詳細的日志信息
          • 大量的日志過濾器,甚至可以做到通過登錄用戶Session識別每一位用戶并輸出獨立的日志文件
          • 異常堆棧支持打印jar包信息,讓我們不但知道調用出自哪個文件哪一行,還可以知道這個文件來自哪個jar包

          Logback主要由三部分組成(網上各種文章在介紹classic和access時都描述的語焉不詳,我不得不直接翻官網文檔找更明確的解釋):

          • logback-core:記錄/輸出日志的核心實現
          • logback-classic:適配層,完整實現了Slf4j接口
          • logback-access[28]:用于將Logback集成到Servlet容器(Tomcat、Jetty)中,讓這些容器的HTTP訪問日志也可以經由強大的Logback輸出


          2.7 Log4j 2 (2012)


          看著Slf4j + Logback搞的風生水起,Apache自然不會坐視不理,終于在2012年憋出一記大招:Apache Log4j 2[29],它自然也有不少亮點:

          • 插件化結構[30],用戶可以自己開發插件,實現Appender、Logger、Filter完成擴展
          • 基于LMAX Disruptor的異步化輸出[31],在多線程場景下相比Logback有10倍左右的性能提升,Apache官方也把這部分作為主要賣點加以宣傳,詳細可以看Log4j 2 Performance[32]。

          Log4j 2主要由兩部分組成:

          • log4j-core:核心實現,功能類似于logback-core
          • log4j-api:接口層,功能類似于Slf4j,里面只包含Log4j 2的接口定義

          你會發現Log4j 2的設計別具一格,提供JCL和Slf4j之外的第三個接口層(log4j-api,雖然只是自己的接口),它在官網API Separation[33]一節中解釋說,這樣設計可以允許用戶在一個項目中同時使用不同的接口層與實現層。

          不過目前大家一般把Log4j 2作為實現層看待,并引入JCL或Slf4j作為接口層。特別是JCL,在時隔近十年后,于2023年底推出了1.3.0 版[34],增加了針對Log4j 2的適配。還記得我們在【1.2.4 JCL (2002.8)】中沒有用最新版的JCL做介紹嗎,就是因為這個十年之后的版本把那些已經「作古」的日志適配層@Deprecated掉了。

          多說一句,其實Logback和Slf4j就像log4j-core和log4j-api的關系一下,目前如果你想用Logback也只能借助Slf4j。但誰讓它們生逢其時呢,大家就會分別討論認為是兩個產品。

          雖然Log4j 2發布至今已有十年(本文寫于2024年),但它仍然無法撼動Logback的江湖地位,我個人總結下來主要有兩點:

          • Log4j 2雖然頂著Log4j的名號,但卻是一套完全重寫的日志系統,無法只通過修改Log4j版本號完成升級,歷史用戶升級意愿低
          • Log4j 2比Logback晚面世6年,卻沒有提供足夠亮眼及差異化的能力(前邊介紹的兩個亮點對普通用戶并沒有足夠吸引力),而Slf4j+Logback這套組合已經非常優秀,先發優勢明顯

          比如,曾有人建議Spring Boot將日志系統從Logback切換到Log4j2[35],但被Phil Webb[36](Spring Boot核心貢獻者)否決。他在回復中給出的原因包括:Spring Boot需要保證向前兼容以方便用戶升級,而切換Log4j 2是破壞性的;目前絕大部分用戶并未面臨日志性能問題,Log4j 2所推崇的性能優勢并非框架與用戶的核心關切;以及如果用戶想在Spring Boot中切換到Log4j 2也很方便(如需切換可參考 官方文檔[37])。


          2.8 spring-jcl (2017)


          因為目前大部分應用都基于Spring/Spring Boot搭建,所以我額外介紹一下spring-jcl [38]這個包,目前Spring Boot用的就是spring-jcl + Logback這套方案。

          Spring曾在它的官方Blog《Logging Dependencies in Spring》[39]中提到,如果可以重來,Spring會選擇李白Slf4j而不是JCL作為默認日志接口。

          現在Spring又想支持Slf4j,又要保證向前兼容以支持JCL,于是從5.0(Spring Boot 2.0)開始提供了spring-jcl這個包。它頂著Spring的名號,代碼中包名卻與JCL 一致(org.apache.commons.logging),作用自然也與JCL一致,但它額外適配了Slf4j,并將Slf4j放在查找的第一順位,從而做到了「既要又要」(你可以回到【1.2.4 JCL (2002.8)】節做一下對比)。

          如果你是基于Spring Initialize [40]新創建的應用,可以不必管這個包,它已經在背后默默工作了;如果你在項目開發過程中遇到包沖突,或者需要自己選擇日志接口和實現,則可以把spring-jcl當作JCL對待,大膽排除即可。


          2.9 其他


          除了我們上邊提到的日志解決方案,還有一些不那么常見的,比如:

          • Flogger[41]:由Google在2018年推出的日志接口層。首字母F的含義是Fluent,這也正是它的最大特點:鏈式調用(或者叫流式API,Slf4j 2.0也支持Fluent API 了,我們會在后續系列文章中介紹)
          • JBoss Logging[42]:由RedHat在約2010年推出,包含完整的接口層、實現層、適配層
          • slf4j-reload4j[43]:Ceki基于Log4j 1.2.7 fork出的版本,旨在解決Log4j的安全問題,如果你的項目還在使用Log4j且不想遷移,建議平替為此版本。(但也不是所有安全問題都能解決,具體可以參考上邊的鏈接)

          因為這些日志框架我們在實際開發中用的很少,此文也不再贅述了(主要是我也不會)。

          三、總結


          歷史介紹完了,但故事并沒有結束。兩個接口(JCL、Slf4j)四個實現(Log4j、JUL、Logback、Log4j2),再加上無數的適配層,它們之間串聯成了一個網,我專門畫了一張圖:

          解釋/補充一下這張圖:

          1. 相同顏色的模塊擁有相同的groupId,可以參考圖例中給出的具體值。
          2. JCL的適配層是直接在它自己的包中提供的,詳情我們在前邊已經介紹過,可以回【1.2.4 JCL (2002.8)】查看。
          3. 要想使用Logback,就一定繞不開Slf4j(引用它的適配層也算);同樣的,要想使用 Log4j 2,那它的log4j-api也繞不開。

          如果你之前在看「1.1 前言」時覺得過于抽象,那么此時建議你再回頭看一下,相信會有更多體會。

          從這段歷史,我也發現了幾個有趣的細節:

          • 在Log4j 2面世前后的很長一段時間,Slf4j及Logback因為沒有競爭對手而更新緩慢。英雄沒有對手只能慢慢垂暮,只有棋逢對手才能笑傲江湖。
          • 技術人的善良與倔強:面世晚的產品都針對前輩產品提供支持;面世早的產品都不搭理它的「后輩」。
          • 計算機科學領域的任何問題都可以通過增加一個中間層來解決,如果不行就兩個(橋接層干的事兒)。
          • Ceki一人肩挑Java日志半壁江山25年(還在增長ing),真神人也。(當然在代碼界有很多這樣的神人,比如Linus Torvalds[44]維護Linux至今已有33年,雖然后期主要作為產品經理參與,再比如已故的Bram Moolenaar[45]老爺子持續維護 Vim 32年之久)。

          參考鏈接:

          [1]https://codedocs.org/what-is/david-wheeler-computer-scientist
          [2]https://github.com/ceki
          [3]https://logging.apache.org/log4j/1.2/
          [4]https://logging.apache.org/log4net/
          [5]https://logging.apache.org/log4cxx/
          [6]https://logging.apache.org/log4php/
          [7]https://log4c.sourceforge.net/
          [8]https://log4cpp.sourceforge.net/
          [9]https://mschilli.github.io/log4perl/
          [10]https://news.apache.org/foundation/entry/apache_logging_services_project_announces
          [11]https://jcp.org/en/jsr/detail
          [12]https://www.java.com/releases/
          [13]https://avalon.apache.org/
          [14]https://javalogging.sourceforge.net/
          [15]https://commons.apache.org/proper/commons-logging/
          [16]https://jakarta.apache.org/
          [17]https://lists.apache.org/thread/53otcqljjfnvjs3hv8m4ldzlgz59yk6k
          [18]https://commons.apache.org/
          [19]https://commons.apache.org/proper/commons-lang/
          [20]https://commons.apache.org/proper/commons-collections/
          [21]http://ceki.blogspot.com/2010/05/forces-and-vulnerabilites-of-apache.html
          [22]https://lists.apache.org/thread/dyzmtholjdlf3h32vvl85so8sbj3v0qz
          [23]https://www.slf4j.org/
          [24]https://stackoverflow.com/questions/3222895/what-is-the-issue-with-the-runtime-discovery-algorithm-of-apache-commons-logging
          [25]https://articles.qos.ch/classloader.html
          [26]https://logback.qos.ch/
          [27]https://qos.ch/
          [28]https://logback.qos.ch/access.html
          [29]https://logging.apache.org/log4j/2.x/
          [30]https://logging.apache.org/log4j/2.x/manual/extending.html
          [31]https://logging.apache.org/log4j/2.x/manual/async.html
          [32]https://logging.apache.org/log4j/2.x/performance.html
          [33]https://logging.apache.org/log4j/2.x/manual/api-separation.html
          [34]https://commons.apache.org/proper/commons-logging/changes-report.html
          [35]https://github.com/spring-projects/spring-boot/issues/16864
          [36]https://spring.io/team/philwebb
          [37]https://docs.spring.io/spring-boot/docs/3.2.x/reference/html/howto.html
          [38]https://docs.spring.io/spring-framework/reference/core/spring-jcl.html
          [39]https://spring.io/blog/2009/12/04/logging-dependencies-in-spring
          [40]https://start.spring.io/
          [41]https://google.github.io/flogger/
          [42]https://github.com/jboss-logging
          [43]https://reload4j.qos.ch/
          [44]https://github.com/torvalds
          [45]https://moolenaar.net/



          作者:尚左

          來源-微信公眾號:阿里云開發者

          出處:https://mp.weixin.qq.com/s/eIiu08fVk194E0BgGL5gow


          019年10月24日,也就是“1024程序員節”的時候,騰訊突然發布了Linux系統版本的QQ,官方也宣稱“全新回歸”。

          這是因為在2008年的時候,騰訊曾經發布過一次Linux QQ,但如同浪花一般隨即消失得無影無蹤,如今隨著Linux生態日益完善,QQ回歸也是順應大勢。

          不過,在第一個2.0.0 Beta版本放出之后,一切又回歸沉寂,此后騰訊官方再也沒有更新過Linux QQ,直到160天后,就在昨天4月1日愚人節,Linux QQ 2.0.0 Beta2終于出現了。。。

          算下來,這也是Linux QQ 12年來第二次更新。

          新版變化不大,根據更新日志只是優化了穩定性,并新增多人聊天會話支持(群聊),而且依然是Beta測試狀態。

          但這至少說明,騰訊這次是打算持續做下去了,只是升級力度和Windows、Android、iOS版本不可同日而語,希望下次更新不要再等到程序員節就好了。

          Linux QQ目前支持x64(x86_64/amd64)、ARM64(aarch64)、MIPSs64(mips64el)三種架構,每種架構均支持Debian系、紅帽系、Arch Linux系、其它發行版中的一種或幾種,未來可能繼續擴充,每一次發布均會提供架構和發行版的若干種組合支持的安裝包。

          安裝的時候需要注意,當前版本的Linux QQ依賴于gtk2.0,必須首先確保系統已安裝gtk2.0,而卸載盡量使用安裝時使用的對應方式,并需要root權限。

          Linux QQ官方下載:

          https://im.qq.com/linuxqq/download.html

          附更新日志:

          Linux QQ 2.0.0 Beta2:

          - 優化穩定性;

          - 增加多人聊天會話支持。

          Linux QQ 2.0.0 Beta:

          - Linux QQ全新回歸;

          - 支持x64、ARM64、MIPS64三種架構;

          - 優化消息體驗,完善消息收發能力;

          - 性能優化。

          原文來自:http://985.so/mfZ4

          Linux命令大全:https://www.linuxcool.com/


          主站蜘蛛池模板: 无码毛片一区二区三区中文字幕| 一区二区不卡视频在线观看| 免费一区二区三区在线视频| 高清一区二区三区日本久| 2021国产精品视频一区| 中文无码AV一区二区三区| 国产一区二区好的精华液 | 中文字幕一区二区三区永久| 亚洲日韩国产欧美一区二区三区| 国产精品视频一区二区三区无码| 一区二区三区在线看| 国产精品第一区揄拍无码| 亚洲欧洲无码一区二区三区| 久久久久人妻一区二区三区| 国产精品日本一区二区在线播放 | 麻豆国产一区二区在线观看 | 欧美激情一区二区三区成人| 亚洲香蕉久久一区二区三区四区 | 日韩精品一区二区三区色欲AV| 在线精品国产一区二区三区| asmr国产一区在线| 风流老熟女一区二区三区| 日本一区高清视频| 天天爽夜夜爽人人爽一区二区| 国产成人无码精品一区不卡| 精品福利一区3d动漫| 变态调教一区二区三区| 国产精品一区在线观看你懂的| 香蕉久久AⅤ一区二区三区| 久久精品国产第一区二区| 国产成人综合精品一区| 国产福利视频一区二区 | 日韩精品一区二区三区国语自制| 最新中文字幕一区二区乱码 | 亚洲国产AV一区二区三区四区| 国产乱码精品一区二区三区麻豆| 精品人妻无码一区二区色欲产成人| 欧美日韩精品一区二区在线观看 | 亚州国产AV一区二区三区伊在| 亚洲国产专区一区| 国产午夜福利精品一区二区三区|