摘要:深入學習系列三那些年我們用過的日志框架目前常見的日志框架和中文似乎不太好翻譯有一下幾種即其中,為同一個作者所寫。如前面所述,在才被引入,在這之前,并沒有官方的日志庫供開發(fā)者使用。
title: 【Java深入學習系列】三. 那些年我們用過的日志框架
date: 2016-10-16 15:32:50
目前常見的Java日志框架和facades(中文似乎不太好翻譯)有一下幾種:
① log4j
② logback
③ SLF4J
④ commons-logging
⑤ j.u.l (即java.util.logging)
其中,①-③為同一個作者(Ceki)所寫。④被很多開源項目所用,⑤是Java原生庫(以下用j.u.l簡寫來代替),但是在Java 1.4中才被引入。
這么多的日志庫,我們該如何選擇呢,我認為,這并非一道非此即彼的選擇題,但是在了解它們的歷史淵源和優(yōu)劣以及相互關系的基礎上才能更好地適配自己的項目。
下面我將上述這些框架串起來講一下,如有疏漏請見諒。
1. Logging frameworks的上古時期(Java 1.3及以前)在上古時期,Java打日志依賴System.out.println(), System.err.println()或者e.printStackTrace()。Debug日志被寫入STDOUT流,錯誤日志被寫入STDERR流。
這種方式目前小腳本中也依然使用廣泛。但是在生產(chǎn)環(huán)境或大的項目中,Debug日志通常被重定向到/dev/null中: >/dev/null, 錯誤日志被重定向到本地文件中: 2>stderr.log??雌饋砗芡昝?,是嗎?實則不然,這樣打日志有一個非常大的缺陷:無法可定制化。
具體來講,沒有一個類似開關的東東來切換是否打印Debug日志,當我們定位問題時需要輸出Debug日志到文件去查看,而不是到/dev/null里,是嗎?日志無法定制化,我們只能硬編碼到代碼里,不需要時再注釋掉相關代碼,重新編譯。
還有一些缺陷,比如:無法更細粒度地輸出日志,換句話說,缺少當前成熟的日志框架常見的LOG LEVEL控制。
而Java本身也沒有提供相應的Library,在這樣惡劣的境況下,Log4j勇敢地站了出來,拯救勞苦大眾。
Log4j可以說是一個里程碑式的框架,它提出的一些基本理念,深深地影響了后來者,直至今天,這些理念也依然在被廣泛使用:
Logger
我們來看下維基百科對Logger的定義:
A Logger is an object that allows the application to log without regard to where the output is sent/stored. The application logs a message by passing an object or an object and an exception with an optional severity level to the logger object under a given a name/identifier.
Logger是一個允許應用記錄日志的對象,開發(fā)者不必需考慮輸出位置。應用可將具體需要打印的信息通過一個Object傳遞。每個Logger互相獨立,通過名字或標識符來區(qū)分。
Appender
每個appender可獨立配置記錄日志的設備,可以是文件、數(shù)據(jù)庫、消息系統(tǒng)等。
Level
每個打印日志都可以多帶帶制定日志級別。外部通過配置文件來控制輸出級別,不同的輸出級別打印不同的日志信息。
2. J.U.L姍姍來遲后來,Sun公司開始意識到JDK需要一個記錄日志的特性。受Log4j的啟發(fā),Sun在Java1.4版本中引入了一個新的API, 叫java.util.logging, 但是,j.u.l功能遠不如Log4j完善,如果開發(fā)者要使用它,就意味著需要自己寫Appenders(Sun稱它為Handlers),而且,只有兩個Handlers可被使用:Console和File,這就意味著,開發(fā)者只能將日志寫入Console和文件。
如前面所述,j.u.l在Java 1.4才被引入,在這之前,并沒有官方的日志庫供開發(fā)者使用。于是便有了很多日志相關的"輪子"。我想這應該是當前會有如此多日志框架的一個很重要的原因。
回顧歷史,一方面,在Java 1.4之前,第三方日志庫已經(jīng)被廣泛使用了,占得了先機。另一方面,j.u.l在被引入時性能和可用性都很差,直到1.5甚至以后才有了顯著提升。
3-1. Logging facades出現(xiàn)及進化由于項目的日志打印必然依賴以上兩個框架中至少一個,無論是j.u.l還是log4j,開發(fā)者必須去兩個都配置。這時候,Apache的commons-logging出現(xiàn)了。本質(zhì)上來講,commons-logging并非一個日志打印框架,而是一個API bridge, 它起到一個連接和溝通的作用,開發(fā)者可以使用它來兼容logging frameworks(j.u.l和log4j)。有了它,第三方庫就可以使用commons-logging來做一個中間層,去靈活選擇j.u.l或者log4j,而不必強加依賴。
然而commons-logging對j.u.l和log4j的配置問題兼容得并不好,更糟糕的是,使用commons-logging可能會遇到類加載問題,導致NoClassDefFoundError的錯誤出現(xiàn)。
最終,log4j的創(chuàng)始人Ceki發(fā)起了另一個項目,這便是大名鼎鼎的SLF4j 日志框架,該框架可以看成是log4j的升級版。需要說明的是,log4j 2.0已經(jīng)被加入Apache基金會,過去幾年已經(jīng)被大幅改善,社區(qū)活躍度也非常高,借助開源社區(qū)的力量,log4j 2.0目前被加入越來越多得現(xiàn)代化特性,一定程度上,甚至超越了log4j的升級版logback(稍后介紹),關于log4j 2.0的新特性,請參見這篇文章:THE NEW LOG4J 2.0
據(jù)slf4j的作者Ceki說,首先,slf4j是不僅僅是一個logging framework, 而且一個logging facdes, 借助slf4j的log4j adapter, 開發(fā)者從slf4j切換到log4j不需要額外改動一行代碼,只需要從CLASS_PATH中排除掉slf4j-log4j12.jar。如果想從log4j遷移到logback, 在CLASS_PATH添加slf4j-log4j12.jar, 并將log4j.properties轉(zhuǎn)換為logback.xml即可,這里有一個在線工具可以自動完成轉(zhuǎn)換: logback.xml translator。
slf4j提供了很大的靈活度,開發(fā)者可以借助它去靈活選擇底層的日志框架。比如,當下更多的開發(fā)者比較傾向于使用log4j的升級版logback,因為它具有較log4j更多更好的特性:
配置文件支持xml和Groovy語法(版本號>= 0.9.22)
自動重載有變更的配置文件
自動壓縮歷史日志
打印異常信息時自動包含package名稱級版本號
Filters
其它一些很棒的特性
需要說明的是,logback是slf4j接口的一套具體實現(xiàn),又是同一個作者,因而保證了其和log4j相近的使用方式,也具有slf4j的全部特性。
此外,對于一些大型框架及服務的開發(fā)者,需要考慮客戶端用戶的體驗。比如jstorm, 你不能只考慮自己的喜好,或許有人偏好使用slf4j開發(fā)jstorm topology, 而另一些人喜歡用logback。這種情況下,你應該使用slf4j,把最終logging framework的選擇權(quán)留給用戶。
最后,除了slf4j比j.u.l或者log4j更好用,還有一個選擇slf4j的現(xiàn)實原因:Java圈的非常多開發(fā)者更鐘情于slf4j作為他們的logging API, 隨大流有時候能少很多不必要的麻煩。
3-2. 日志參數(shù)化打印的支持(parameterized logging)slf4j除了包含該log4j的全部特性外,還提供了parameterized logging特性。這個特性非常有用,它允許開發(fā)者在打印日志時借助{}來實現(xiàn)參數(shù)化打?。?/p>
logger.debug("The attribute value is {}", fooIns.getAttribute());
logback復用了slf4j的API,這意味著使用logback實際上是在使用slf4j的API,不難看出,logback同樣支持parameterized logging特性。
4. 各日志框架時間線以上日志框架,有些是為了解決現(xiàn)有框架的不足,有些是功能的擴展升級,有些是從頭到尾重新寫的,根據(jù)各自出現(xiàn)先后次序,可以將它們放在同一時間線上:
注意箭頭僅代表時間走向,分支不具有fork的含義。
5. SLF4J使用方法slf4j的使用有兩種方式,一種是混合綁定(concrete-bindings), 另一種是橋接遺產(chǎn)(bridging-legacy).
5.1 混合綁定(concrete-bindings)concrete-bindings模式指在新項目中 即開發(fā)者直接使用sl4j的api來打印日志, 而底層綁定任意一種日志框架,如logback, log4j, j.u.l等.
混合綁定根據(jù)實現(xiàn)原理,基本上有兩種形式, 分別為有適配器(adapter)的綁定和無適配器的綁定.
有適配器的混合綁定是指底層沒有實現(xiàn)slf4j的接口,而是通過適配器直接調(diào)用底層日志框架的Logger, 無適配器的綁定不需要調(diào)用其它日志框架的Logger, 其本身就實現(xiàn)了slf4j的全部接口.
幾個混合綁定的包分別是:
slf4j-log4j12-1.7.21.jar(適配器, 綁定log4j, Logger由log4j-1.2.17.jar提供)
slf4j-jdk14-1.7.21.jar(適配器, 綁定l.u.l, Logger由JVM runtime, 即j.u.l庫提供)
logback-classic-1.0.13.jar(無適配器, slf4j的一個native實現(xiàn))
slf4j-simple-1.7.21.jar(無適配器,slf4j的簡單實現(xiàn), 僅打印INFO及更高級別的消息, 所有輸出全部重定向到System.err, 適合小應用)
以上幾種綁定可以無縫切換, 不需要改動內(nèi)部代碼. 無論哪種綁定,均依賴slf4j-api.jar.
此外, 適配器綁定需要一種具體的日志框架, 如log4j綁定slf4j-log4j12-1.7.21.jar依賴log4j.jar, j.u.l綁定slf4j-jdk14-1.7.21.jar依賴j.u.l(java runtime提供); 無適配器的直接實現(xiàn), logback-classic依賴logback-core提供底層功能, slf4j-simple則不依賴其它庫.
以上四種綁定的示例圖如下:
下面來分析兩個典型綁定log4j和logback的用法.
①log4j適配器綁定(slf4j-log4j12)
配置:
org.slf4j slf4j-log4j12 1.7.21
注意: 添加上述適配器綁定配置后會自動拉下來兩個依賴庫, 分別是slf4j-api-1.7.21.jar和log4j-1.2.17.jar
基本邏輯: 用戶層 <- 中間層 <- 底層基礎日志框架層
org.slf4j.impl.Log4jLoggerFactory <- StaticLoggerBinder.getSingleton().getLoggerFactory()<- org.sl4j.impl.StaticLoggerBinder.getSingleton().getLoggerFactory() <- 具體的日志框庫的Logger
其中org.slf4j.impl.Log4jLoggerFactory在應用層調(diào)用, StaticLoggerBinder在中間層實現(xiàn), 獲取具體的日志框庫的Logger
綁定實例圖如下:
應用層(slf4j-api-1.7.21.jar)
用戶調(diào)用org.slf4j.impl.Log4jLoggerFactory.getLogger獲取底層具體的Logger:
// Foo.java import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class Foo { private final static Logger logger = LoggerFactory.getLogger(CommuteNaviInfoParser.class); public static void main() { logger.info("info:{}..", "hello, sl4j"); } }
適配層(slf4j-log4j12-1.7.21.jar)
由應用層org.slf4j.impl.Log4jLoggerFactory.getLogger內(nèi)部創(chuàng)建適配層的StaticLoggerBinder:
public static Logger getLogger(Class> clazz) { return StaticLoggerBinder.getSingleton().getLoggerFactory(); }
接下來直接由StaticLoggerBinder獲取具體的Logger:
private StaticLoggerBinder() { loggerFactory = new Log4jLoggerFactory(); } public Log4jLoggerFactory() { // force log4j to initialize org.apache.log4j.LogManager.getRootLogger(); }
注意, 各個StaticLoggerBinder均在適配層實現(xiàn), 放在org.slf4j.impl中.
② slf4j綁定到logback-classic上
配置:
ch.qos.logback logback-classic 1.1.7
注意: 添加上述適配器綁定配置后會自動拉下來兩個依賴庫, 分別是slf4j-api-1.7.21.jar和logback-core-1.0.13.jar
logback-classic沒有適配器層, 而是在logback-classic-1.0.13.jar的ch.qos.logback.classic.Logger直接實現(xiàn)了slf4j的org.slf4j.Logger, 并強依賴ch.qos.logback.core中的大量基礎類:
import org.slf4j.LoggerFactory; import org.slf4j.Marker; import org.slf4j.spi.LocationAwareLogger; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.classic.spi.LoggingEvent; import ch.qos.logback.classic.util.LoggerNameUtil; import ch.qos.logback.core.Appender; import ch.qos.logback.core.CoreConstants; import ch.qos.logback.core.spi.AppenderAttachable; import ch.qos.logback.core.spi.AppenderAttachableImpl; import ch.qos.logback.core.spi.FilterReply; public final class Logger implements org.slf4j.Logger, LocationAwareLogger, AppenderAttachable, Serializable {}
綁定示例圖:
5.2 橋接遺產(chǎn)(bridging-legacy)橋接遺產(chǎn)用法主要針對歷史遺留項目, 不論是用log4j寫的, j.c.l寫的,還是j.u.l寫的, 都可以在不改動代碼的情況下具有另外一種日志框架的能力.
比如,你的項目使用java提供的原生日志庫j.u.l寫的, 使用slf4j的bridging-legacy模式,便可在不改動一行代碼的情況下瞬間具有l(wèi)og4j的全部特性.
說得更直白一些,就是你的項目代碼可能是5年前寫的, 當時由于沒得選擇, 用了一個比較垃圾的日志框架, 有各種缺陷和問題, 如不能按天存儲, 不能控制大小, 支持的appender很少, 無法存入數(shù)據(jù)庫等. 你很想對這個已完工并在線上運行的項目進行改造, 顯然, 直接改代碼, 把舊的日志框架替換掉是不現(xiàn)實的, 因為很有可能引入不可預期的bug.
那么,如何在不修改代碼的前提下, 替換掉舊的日志框架,引入更優(yōu)秀且成熟的日志框架如如log4j和logback呢? slf4j的bridging-legacy模式便是為了解決這個痛點.
slf4j以slf4j-api為中間層, 將上層舊日志框架的消息轉(zhuǎn)發(fā)到底層綁定的新日志框架上.
基于不同的底層框架,以SLF4J作為中轉(zhuǎn)層,有如下幾種組合用法:
基于j.u.l的facade使用
上述facade將slf4j-api.jar綁定到底層基礎日志庫j.u.l(jvm runtime)上. slf4j-api和底層日志庫的Logger通過適配器連接.
基于logback-classic的facade使用
基于log4j的facade使用
舉例說明上述facade的使用, 以便于大家理解.
假如我有一個已完成的使用了舊日志框架commons-loggings的項目,現(xiàn)在想把它替換成log4j以獲得更多更好的特性.
項目的maven舊配置如下:
commons-logging commons-logging 1.2
項目代碼:
import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * Created by xialeizhou on 16/9/20. */ public class MainTest { private static Log logger = LogFactory.getLog(MainTest.class); public static void main(String[] args) throws InterruptedException { logger.info("hello,world"); } }
項目打印的基于commons-logging的日志顯示在console上,具體如下:
十月 23, 2016 6:52:00 下午 MainTest main 信息: hello,world
下面我們對項目改造, 將commongs-logging框架的日志轉(zhuǎn)發(fā)到log4j上. 改造很簡單, 我們將commongs-logging依賴刪除, 替換為相應的facade(此處為jcl-over-slf4j.jar), 并在facade下面掛一個5.1的混合綁定即可.
具體來講, 將commons-logging.jar替換成jcl-over-slf4j.jar, 并加入適配器slf4j-log412.jar(注意, 加入slf4j-log412.jar后會自動pull下來另外兩個jar包), 所以實際最終只需添加facadejcl-over-slf4j.jar和5.1節(jié)混合綁定中相同的jar包slf4j-log412.jar即可.
改造后的maven配置:
org.slf4j jcl-over-slf4j 1.7.21 org.slf4j slf4j-log4j12 1.7.21
現(xiàn)在, 我們的舊項目在沒有改一行代碼的情況下具有了log4j的全部特性, 下面進行測試.
在resources/下新建一個log4j.properties文件, 對commongs-logging庫的日志輸出進行定制化:
# Root logger option log4j.rootLogger=INFO, stdout, fout # Redirect log messages to console log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.Target=System.out log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.Threshold = INFO log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n # add a FileAppender to the logger fout log4j.appender.fout=org.apache.log4j.FileAppender # create a log file log4j.appender.fout.File=royce-testing.log log4j.appender.fout.layout=org.apache.log4j.PatternLayout # use a more detailed message pattern log4j.appender.fout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
重新編譯運行, console輸出變?yōu)?
2016-10-23 19:26:15 INFO MainTest:11 - hello,world
同時在當前目錄生成了一個日志文件:
% cat royce-testing.log INFO 2016-10-23 19:26:15,341 0 MainTest [main] hello,world
可見, 基于facade的日志框架橋接已經(jīng)生效, 我們再不改動代碼的前提下,讓commons-logging日志框架具有了log4j12的全部特性.
6. 參考文獻http://stackoverflow.com/ques...
http://logback.qos.ch/reasons...
http://stackoverflow.com/ques...
http://stackoverflow.com/ques...
http://stackoverflow.com/ques...
https://blog.frankel.ch/thoug...
https://en.wikipedia.org/wiki...
http://slf4j.org/faq.html
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/66034.html
摘要:用戶態(tài)不能干擾內(nèi)核態(tài)所以指令就有兩種特權(quán)指令和非特權(quán)指令不同的狀態(tài)對應不同的指令。非特權(quán)指令所有程序均可直接使用。用戶態(tài)常態(tài)目態(tài)執(zhí)行非特權(quán)指令。 這是我今年從三月份開始,主要的大廠面試經(jīng)過,有些企業(yè)面試的還沒來得及整理,可能有些沒有帶答案就發(fā)出來了,還請各位先思考如果是你怎么回答面試官?這篇文章會持續(xù)更新,請各位持續(xù)關注,希望對你有所幫助! 面試清單 平安產(chǎn)險 飛豬 上汽大通 浩鯨科...
摘要:開頭正式開啟我入職的里程,現(xiàn)在已是工作了一個星期了,這個星期算是我入職的過渡期,算是知道了學校生活和工作的差距了,總之,盡快習慣這種生活吧。當時是看的廖雪峰的博客自己也用做爬蟲寫過幾篇博客,不過有些是在前人的基礎上寫的。 showImg(https://segmentfault.com/img/remote/1460000010867984); 開頭 2017.08.21 正式開啟我...
摘要:前提好幾周沒更新博客了,對不斷支持我博客的童鞋們說聲抱歉了。熟悉我的人都知道我寫博客的時間比較早,而且堅持的時間也比較久,一直到現(xiàn)在也是一直保持著更新狀態(tài)。 showImg(https://segmentfault.com/img/remote/1460000014076586?w=1920&h=1080); 前提 好幾周沒更新博客了,對不斷支持我博客的童鞋們說聲:抱歉了!。自己這段時...
摘要:如果應用發(fā)生了內(nèi)存泄漏問題,就會進行檢測生成報告,并且提供切實可行的方案去掉這個問題。主要特性實時的內(nèi)存泄漏檢測和告警一份包含時間,內(nèi)存大小,速度以及泄漏事件的重要級別的報告。 在這篇文章中我們決定收集制作一個關于這類工具的簡略名單,他們中的大多數(shù)工具只是最近推出的。其中一些工具是為Java定制的,但也有一些是支持其他語言。但對于Java項目而言,他們都是非常好的,并且擁有同一個愿景:...
閱讀 1319·2021-11-24 09:38
閱讀 3256·2021-11-22 12:03
閱讀 4158·2021-11-11 10:59
閱讀 2317·2021-09-28 09:36
閱讀 1032·2021-09-09 09:32
閱讀 3411·2021-08-05 10:00
閱讀 2528·2021-07-23 15:30
閱讀 2973·2019-08-30 13:12