Java日誌那點兒事兒 原

##前言

日誌這東西在語言裏算基礎組件了吧,可惜Java界第三方框架向來比原生組件好用也是事實,缺點是框架太多混戰江湖,今天我們就理一理這些日誌框架。Java的日誌框架分爲門面(Facade),或者叫通用日誌接口,還有日誌實現。日誌接口不用說,就是定下的日誌方法規範,需要具體日誌組件去實現的(爲啥Sun當年沒有定義這東西,看看JPA、JDBC、JMS這些規範定義的多好,或者定義了被拋棄了?)。日誌實現就是具體的日誌組件了,可以實現日誌打印到控制檯、文件、數據庫等等。下面咱們就具體說說這些東西。

Java日誌框架分類

日誌門面(Facade)

  • Slf4j

全稱Simple Logging Facade for JAVA,真正的日誌門面,只提供接口方法,當配合特定的日誌實現時,需要引入相應的橋接包

  • Common-logging

Apache提供的一個通用的日誌接口,common-logging會通過動態查找的機制,在程序運行時自動找出真正使用的日誌庫,自己也自帶一個功能很弱的日誌實現。

差別:

  1. Common-logging動態查找日誌實現(程序運行時找出日誌實現),Slf4j則是靜態綁定(編譯時找到實現),動態綁定因爲依賴ClassLoader尋找和載入日誌實現,因此類似於OSGI那種使用獨立ClassLoader就會造成無法使用的情況。(呵呵,我一個插件用一個日誌框架不行啊,土豪多任性,不過說實話,沒用過OSGI,這個我還真沒有概念)
  2. Slf4j支持參數化的log字符串,避免了之前爲了減少字符串拼接的性能損耗而不得不寫的if(logger.isDebugEnable()),現在你可以直接寫:logger.debug(“current user is: {}”, user)。

日誌實現

  • Log4j

Log4j可能是Java世界裏最出名的日誌框架了,支持各種目的地各種級別的日誌輸出,從我剛接觸日誌就知道這個框架(呵呵,我一直不知道還有JDK Logging這個東西)。最近(也不近了……)Log4j2發佈正式版了,沒看到誰用,聽說也很不錯。

  • LogBack

Log4j作者的又一力作(聽說是受不了收費文檔搞了個開源的,不需要橋接包完美適配Slf4j),個人感覺迄今爲止最棒的日誌框架了,一直都在用,配置文件夠簡潔,性能足夠好(估計是看自己的Log4j代碼差勁了,更新不能解決問題,直接重構了)。

  • JDK Logging 從JDK1.4開始引入,不得不說,你去Google下這個JDK自帶的日誌組件,並不如Log4j和LogBack之類好用,木有配置文件,日誌級別不好理解,想順心的用估計還得自己封裝下,總之大家已經被Log4j慣壞了,JDK的設計並不能被大家認同,唯一的優點我想就是不用引入新額jar包了。

爲什麼會有門面

看了以上介紹,如果你不是混跡(深陷)Java多年的老手,估計會蒙圈兒了吧,那你肯定會問,要門面幹嘛。有了手機就有手機殼、手機膜,框架也一樣,門面的作用更多的還是三個字:解耦合。說白了,加入一個項目用了一個日誌框架,想換咋整啊?那就一行一行的找日誌改唄,想想都是噩夢。於是,門面出來了,門面說啦, 你用我的格式寫日誌,把日誌想寫哪兒寫哪兒,例如Slf4j-api加上後,想換日誌框架,直接把橋接包一換就行。方便極了。

說實話,現在Slf4j基本可以是Java日誌的一個標準了,按照它寫基本可以實現所有日誌實現通吃,但是就有人不服,還寫了門面的門面(沒錯,這個人就是我)。

門面的門面

如果你看過Netty的源碼,推薦你看下io.netty.util.internal.logging這個包裏內容,會發現Netty又對日誌封裝了一層,於是靈感來源於此,我也對各大日誌框架和門面做了封裝。

Hutool-log模塊

無論是Netty的日誌模塊還是我的Hutool-log模塊,思想類似於Common Logging,做動態日誌實現查找,然後找到相應的日誌實現來寫入日誌,核心代碼如下:

/**

 * 決定日誌實現

 * @return 日誌實現類

 */
public static Class<? extends AbstractLog> detectLog(){
	List<Class<? extends AbstractLog>> logClassList = Arrays.asList(
			Slf4jLog.class,
			Log4jLog.class, 
			Log4j2Log.class, 
			ApacheCommonsLog.class, 
			JdkLog.class
	);
	
	for (Class<? extends AbstractLog> clazz : logClassList) {
		try {
			clazz.getConstructor(Class.class).newInstance(LogFactory.class).info("Use Log Framework: [{}]", clazz.getSimpleName());
			return clazz;
		} catch (Error | Exception e) {
			continue;
		}
	}
	return JdkLog.class;
}

詳細代碼可以看這裏

說白了非常簡單,按順序實例化相應的日誌實現,如果實例化失敗(一般是ClassNotFoundException),說明jar不存在,那實例化下一個,通過不停的嘗試,最終如果沒有引入日誌框架,那使用JDK Logging(這個肯定會有的),當然這種方式也和Common-logging存在類似問題,不過不用到跨ClassLoader還是很好用的。

對於JDK Logging,我也做了一些適配,使之可以與Slf4j的日誌級別做對應,這樣就將各個日誌框架差異化降到最小。另一方面,如果你看過我的這篇日誌,那你一定了解了我的類名自動識別功能,這樣大家在複製類名的時候,就不用修改日誌的那一行代碼了,在所有類中,日誌的初始化只有這一句:

Log log = LogFactory.get();

是不是簡潔簡潔又簡潔?實現方式也很簡單:

/**
 * @return 獲得調用者的日誌
 */
public static Log get() {
	return getLog(new Exception().getStackTrace()[1].getClassName());
}

通過堆棧引用獲得當前類名。

作爲一個強迫症患者,日誌接口我也會定義的非常處女座:

/**
 * 日誌統一接口
 * 
 * @author Looly
 *
 */
public interface Log extends TraceLog, DebugLog, InfoLog, WarnLog, ErrorLog

這樣就實現了單一使用,各個日誌框架靈活引用的作用了。好了,今天就到這裏了,有問題或者吐槽都已給我留言~~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章