介紹
最近發(fā)現(xiàn)項(xiàng)目中使用log4j和SLF4J,打算研究下SLF4J。SLF4J用作各種日志框架的簡(jiǎn)單外觀或抽象弛矛,例如java.util.logging,logback和log4j比然。SLF4J允許最終用戶在部署時(shí)插入所需的日志記錄框架丈氓。
簡(jiǎn)單來說就是slf4j本身只是提供日志的外觀,而slf4j并不提供具體的日志實(shí)現(xiàn)。
slf4j用戶手冊(cè):
https://www.slf4j.org/manual.html
SLF4J不提供具體實(shí)現(xiàn)
導(dǎo)入jar包: slf4j-api-1.7.7.jar
public class Slf4jTest {
public static void main(String[] args) {
Logger logger = LoggerFactory.getLogger(Slf4jTest.class);
logger.info("HelloWorld");
}
}
SLF本身不提供日志具體實(shí)現(xiàn)万俗,本身也是無法打印具體日志的湾笛,需要配合其他具體的日志框架實(shí)現(xiàn)。通過上面代碼可以看出如果我們不指定具體的日志框架實(shí)現(xiàn)闰歪,那么SLF4J就無法調(diào)用具體日志框架嚎研。
完整例子
導(dǎo)包: slf4j-log4j12-1.6.2.jar 、slf4j-api-1.7.7.jar 库倘、log4j-1.2.17.jar
log4j.properties
############# 日志輸出到控制臺(tái) #############
# 通過根元素指定日志輸出的級(jí)別临扮、目的地
# 日志輸出的優(yōu)先級(jí): debug < info < warn < error
log4j.rootLogger=INFO,Console,File
# 日志輸出到控制臺(tái)使用的api類
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.Target=System.out
# 指定日志輸出的格式:靈活的格式
log4j.appender.Console.layout = org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=%d %p %c.%M()-%m%n
############# 日志輸出到文件 #############
log4j.appender.dailyRollingFile = org.apache.log4j.DailyRollingFileAppender
# 當(dāng)前日志信息追加到文件的末尾
log4j.appender.File = org.apache.log4j.RollingFileAppender
# 文件參數(shù),指定日志文件的路徑(此處是輸出到E:/log/myLog.log)
log4j.appender.File.File = E:/log/myLog.log
# 文件參數(shù)教翩,指定日志文件的最大大小
log4j.appender.File.MaxFileSize = 10MB
log4j.appender.File.Threshold = ALL
log4j.appender.File.layout = org.apache.log4j.PatternLayout
log4j.appender.File.layout.ConversionPattern =[%p] [%d{yyyy-MM-dd HH\:mm\:ss}][%c]%m%n
public class LoggerTest {
public static void main(String[] args) throws Exception {
Logger logger = LoggerFactory.getLogger(Object.class);
logger.info("sunpy");
}
}
SLF4J源碼分析
public static Logger getLogger(Class<?> clazz) {
// 獲取對(duì)應(yīng)的Logger日志器
Logger logger = getLogger(clazz.getName());
// 檢查日志器的名稱是否匹配
if (DETECT_LOGGER_NAME_MISMATCH) {
Class<?> autoComputedCallingClass = Util.getCallingClass();
if (autoComputedCallingClass != null && nonMatchingClasses(clazz, autoComputedCallingClass)) {
Util.report(String.format("Detected logger name mismatch. Given name: \"%s\"; computed name: \"%s\".", logger.getName(),
autoComputedCallingClass.getName()));
Util.report("See " + LOGGER_NAME_MISMATCH_URL + " for an explanation");
}
}
return logger;
}
public static Logger getLogger(String name) {
ILoggerFactory iLoggerFactory = getILoggerFactory();
return iLoggerFactory.getLogger(name);
}
getILoggerFactory方法
public static ILoggerFactory getILoggerFactory() {
// INITIALIZATION_STATE初始化值為UNINITIALIZED
if (INITIALIZATION_STATE == UNINITIALIZED) {
// 將LoggerFactory上鎖
synchronized (LoggerFactory.class) {
if (INITIALIZATION_STATE == UNINITIALIZED) {
// 將初始化狀態(tài)設(shè)置為進(jìn)行中初始化狀態(tài)
INITIALIZATION_STATE = ONGOING_INITIALIZATION;
// 執(zhí)行初始化
performInitialization();
}
}
}
switch (INITIALIZATION_STATE) {
case SUCCESSFUL_INITIALIZATION:
return StaticLoggerBinder.getSingleton().getLoggerFactory();
case NOP_FALLBACK_INITIALIZATION:
return NOP_FALLBACK_FACTORY;
case FAILED_INITIALIZATION:
throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);
case ONGOING_INITIALIZATION:
// support re-entrant behavior.
// See also http://jira.qos.ch/browse/SLF4J-97
return SUBST_FACTORY;
}
throw new IllegalStateException("Unreachable code");
}
說明:判斷當(dāng)前初始化狀態(tài)是否為未初始化狀態(tài)杆勇,如果未初始化就將狀態(tài)修改為當(dāng)前正在初始化中,執(zhí)行初始化方法performInitialization()迂曲。
// 執(zhí)行初始化操作
private final static void performInitialization() {
// 綁定
bind();
// 如果初始化狀態(tài)等于成功初始化狀態(tài)
if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) {
// 執(zhí)行版本安全檢查
versionSanityCheck();
}
}
versionSanityCheck方法
// 版本號(hào)檢查靶橱,如果不滿足1.6.x或者1.7.x的版本,將打印版本不匹配的錯(cuò)誤流信息
private final static void versionSanityCheck() {
try {
// public static String REQUESTED_API_VERSION = "1.6.99";
// requested請(qǐng)求版本默認(rèn)為1.6.99
String requested = StaticLoggerBinder.REQUESTED_API_VERSION;
boolean match = false;
// static private final String[] API_COMPATIBILITY_LIST = new String[] { "1.6", "1.7" };
// 遍歷API_COMPATIBILITY_LIST
for (String aAPI_COMPATIBILITY_LIST : API_COMPATIBILITY_LIST) {
// 如果requested請(qǐng)求版本為1.6.x或者1.7.x路捧,那么就將是否匹配的標(biāo)識(shí)設(shè)置為true
if (requested.startsWith(aAPI_COMPATIBILITY_LIST)) {
match = true;
}
}
// 如果標(biāo)識(shí)不匹配关霸,那么就打印SLF4J錯(cuò)誤:版本不匹配請(qǐng)滿足1.6.x或者1.7.x的版本
if (!match) {
Util.report("The requested version " + requested + " by your slf4j binding is not compatible with "
+ Arrays.asList(API_COMPATIBILITY_LIST).toString());
Util.report("See " + VERSION_MISMATCH + " for further details.");
}
} catch (java.lang.NoSuchFieldError nsfe) {
// given our large user base and SLF4J's commitment to backward
// compatibility, we cannot cry here. Only for implementations
// which willingly declare a REQUESTED_API_VERSION field do we
// emit compatibility warnings.
} catch (Throwable e) {
// we should never reach here
Util.report("Unexpected problem occured during version sanity check", e);
}
}
說明:主要就是版本號(hào)安全檢查,如果版本號(hào)不滿足1.6.x或者1.7.x版本杰扫,那么就打印版本不匹配錯(cuò)誤流信息队寇。
關(guān)鍵源碼設(shè)計(jì)
bind方法:綁定日志工廠
// 綁定
private final static void bind() {
try {
// 查找多個(gè)可能出現(xiàn)的StaticLoggerBinder類。
Set<URL> staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
// 校驗(yàn)設(shè)置的多個(gè)StaticLoggerBinder類
reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
// 創(chuàng)建一個(gè)默認(rèn)的Log4jLoggerFactory工廠
StaticLoggerBinder.getSingleton();
// 更新初始化狀態(tài)為成功初始化狀態(tài)
INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
// 報(bào)告實(shí)際上加入的多個(gè)StaticLoggerBinder的日志類型章姓。
reportActualBinding(staticLoggerBinderPathSet);
// 使用SLF4j提供的SubstituteLoggerFactory工廠
fixSubstitutedLoggers();
} catch (NoClassDefFoundError ncde) {
String msg = ncde.getMessage();
if (messageContainsOrgSlf4jImplStaticLoggerBinder(msg)) {
INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION;
Util.report("Failed to load class \"org.slf4j.impl.StaticLoggerBinder\".");
Util.report("Defaulting to no-operation (NOP) logger implementation");
Util.report("See " + NO_STATICLOGGERBINDER_URL
+ " for further details.");
} else {
failedBinding(ncde);
throw ncde;
}
} catch (java.lang.NoSuchMethodError nsme) {
String msg = nsme.getMessage();
if (msg != null && msg.indexOf("org.slf4j.impl.StaticLoggerBinder.getSingleton()") != -1) {
INITIALIZATION_STATE = FAILED_INITIALIZATION;
Util.report("slf4j-api 1.6.x (or later) is incompatible with this binding.");
Util.report("Your binding is version 1.5.5 or earlier.");
Util.report("Upgrade your binding to version 1.6.x.");
}
throw nsme;
} catch (Exception e) {
failedBinding(e);
throw new IllegalStateException("Unexpected initialization failure", e);
}
}
findPossibleStaticLoggerBinderPathSet方法:查找多個(gè)可能出現(xiàn)的StaticLoggerBinder類佳遣。
static Set<URL> findPossibleStaticLoggerBinderPathSet() {
Set<URL> staticLoggerBinderPathSet = new LinkedHashSet<URL>();
try {
// 獲取類加載器
ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader();
Enumeration<URL> paths;
// 當(dāng)前類加載器為啟動(dòng)類加載器
if (loggerFactoryClassLoader == null) {
// 查找路徑"org/slf4j/impl/StaticLoggerBinder.class"上的資源URL
paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);
} else { // 當(dāng)前類加載器為非啟動(dòng)類加載器
// 查找路徑"org/slf4j/impl/StaticLoggerBinder.class"上的資源URL
paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH);
}
// 遍歷枚舉集合
while (paths.hasMoreElements()) {
// 獲取對(duì)應(yīng)的URL資源
URL path = paths.nextElement();
// 將URL放入集合LinkedHashSet中
staticLoggerBinderPathSet.add(path);
}
} catch (IOException ioe) {
Util.report("Error getting resources from path", ioe);
}
return staticLoggerBinderPathSet;
}
說明:使用ClassLoader類加載器獲取類org.slf4j.impl.StaticLoggerBinder.class,將返回可能多個(gè)對(duì)應(yīng)的URL類型的Set集合凡伊。
reportMultipleBindingAmbiguity方法:校驗(yàn)設(shè)置的多個(gè)StaticLoggerBinder類
private static void reportMultipleBindingAmbiguity(Set<URL> staticLoggerBinderPathSet) {
// Set集合staticLoggerBinderPathSet中找到大于1個(gè)StaticLoggerBinder對(duì)應(yīng)的URL類
if (isAmbiguousStaticLoggerBinderPathSet(staticLoggerBinderPathSet)) {
// 打印錯(cuò)誤流信息:類路徑中包含多個(gè)SLF4J框架綁定
Util.report("Class path contains multiple SLF4J bindings.");
Iterator<URL> iterator = staticLoggerBinderPathSet.iterator();
// 遍歷Set集合
while (iterator.hasNext()) {
// 獲取遍歷到的資源URL
URL path = (URL) iterator.next();
// 打印錯(cuò)誤流信息:發(fā)現(xiàn)綁定路徑有零渐。。系忙。
Util.report("Found binding in [" + path + "]");
}
Util.report("See " + MULTIPLE_BINDINGS_URL + " for an explanation.");
}
}
說明:校驗(yàn)Set集合中出現(xiàn)多個(gè)StaticLoggerBinder類對(duì)應(yīng)的訪問資源URL類時(shí)诵盼,打印錯(cuò)誤信息流。
StaticLoggerBinder.getSingleton():?jiǎn)卫J皆O(shè)計(jì)
針對(duì)上面的StaticLoggerBinder出現(xiàn)多個(gè)的情況银还,那么SLF4J中是不希望出現(xiàn)多個(gè)StaticLoggerBinder风宁,使用單例設(shè)計(jì)模式來防止產(chǎn)生多個(gè)對(duì)象的問題。
public class StaticLoggerBinder implements LoggerFactoryBinder {
// 創(chuàng)建唯一StaticLoggerBinder實(shí)例SINGLETON
private static final StaticLoggerBinder SINGLETON = new StaticLoggerBinder();
// 對(duì)外提供獲取單個(gè)實(shí)例的靜態(tài)方法
public static final StaticLoggerBinder getSingleton() {
return SINGLETON;
}
// 私有構(gòu)造器蛹疯,防止外部new創(chuàng)建StaticLoggerBinder實(shí)例
private StaticLoggerBinder() {
// 創(chuàng)建一個(gè)Log4jLoggerFactory工廠類
loggerFactory = new Log4jLoggerFactory();
try {
Level level = Level.TRACE;
} catch (NoSuchFieldError nsfe) {
Util
.report("This version of SLF4J requires log4j version 1.2.12 or later. See also http://www.slf4j.org/codes.html#log4j_version");
}
}
戒财。。捺弦。
}
說明:對(duì)于SLF4J日志門面對(duì)應(yīng)多個(gè)日志實(shí)現(xiàn)類中的StaticLoggerBinder 饮寞,SLF4J采用單例設(shè)計(jì)模式來實(shí)現(xiàn)的孝扛,采用餓漢式實(shí)現(xiàn)的。 --> 我早期博客:
創(chuàng)建型模式之單例設(shè)計(jì)模式(java版)
reportActualBinding方法:報(bào)告實(shí)際上加入的多個(gè)StaticLoggerBinder的日志類型骂际。
// 提示實(shí)際綁定的StaticLoggerBinder
private static void reportActualBinding(Set<URL> staticLoggerBinderPathSet) {
// staticLoggerBinderPathSet集合中元素個(gè)數(shù)大于1
if (isAmbiguousStaticLoggerBinderPathSet(staticLoggerBinderPathSet)) {
// 打印錯(cuò)誤信息流: 實(shí)際綁定日志類型為L(zhǎng)og4j
Util.report("Actual binding is of type ["+StaticLoggerBinder.getSingleton().getLoggerFactoryClassStr()+"]");
}
}
SubstituteLoggerFactory工廠方法模式設(shè)計(jì)
public class SubstituteLoggerFactory implements ILoggerFactory {
final ConcurrentMap<String, SubstituteLogger> loggers = new ConcurrentHashMap<String, SubstituteLogger>();
public Logger getLogger(String name) {
// 獲取代理Logger
SubstituteLogger logger = loggers.get(name);
// 如果logger為null
if (logger == null) {
// 直接創(chuàng)建SubstituteLogger
logger = new SubstituteLogger(name);
// 如果集合loggers包含name鍵疗琉,那么直接獲取對(duì)應(yīng)的logger冈欢,返回對(duì)應(yīng)的logger
// 如果集合loggers不包含name鍵歉铝,那么設(shè)置到loggers中,返回oldLogger為null
SubstituteLogger oldLogger = loggers.putIfAbsent(name, logger);
// 如果返回oldLogger不為null
if (oldLogger != null)
logger = oldLogger;
}
return logger;
}
public List<String> getLoggerNames() {
return new ArrayList<String>(loggers.keySet());
}
public List<SubstituteLogger> getLoggers() {
return new ArrayList<SubstituteLogger>(loggers.values());
}
public void clear() {
loggers.clear();
}
}
說明:實(shí)際上SubstituteLoggerFactory工廠實(shí)現(xiàn)了日志抽象工廠ILoggerFactory凑耻,實(shí)現(xiàn)了getLogger方法來獲取創(chuàng)建的SubstituteLogger太示。思考:如果我們想創(chuàng)建一個(gè)自定義的日志類XXXLogger,那么我們直接就可以創(chuàng)建一個(gè)XXXLoggerFactory工廠實(shí)現(xiàn)ILoggerFactory接口香浩,加入我們需要的邏輯到實(shí)現(xiàn)的getLogger方法中类缤。
getLogger方法:如果我們沒有找到默認(rèn)的Log4j,那么就使用SubstituteLogger來代替邻吭。
fixSubstitutedLoggers方法:使用SLF4J提供的日志SubstitutedLogger(代理Logger)實(shí)現(xiàn)
private final static void fixSubstitutedLoggers() {
// 獲取所有的SubstituteLogger
List<SubstituteLogger> loggers = TEMP_FACTORY.getLoggers();
// 如果ArrayList集合loggers為空餐弱,直接退出方法
if(loggers.isEmpty()){
return;
}
Util.report("The following set of substitute loggers may have been accessed");
Util.report("during the initialization phase. Logging calls during this");
Util.report("phase were not honored. However, subsequent logging calls to these");
Util.report("loggers will work as normally expected.");
Util.report("See also " + SUBSTITUTE_LOGGER_URL);
// 遍歷loggers集合
for(SubstituteLogger subLogger : loggers){
// 通過名稱獲取對(duì)應(yīng)的logger俘枫,然后設(shè)置委托類
subLogger.setDelegate(getLogger(subLogger.getName()));
Util.report(subLogger.getName());
}
TEMP_FACTORY.clear();
}
Log4jLoggerFactory工廠解析
前面大概說了下SLF4J中獲取Logger的流程瞄摊,但是其中的一些細(xì)節(jié)需要再深入思考下,譬如我們前面提到了loggerFactory首先是使用Log4j的工廠Log4jLoggerFactory來進(jìn)行初始化的兼呵。下面主要講下Log4j的工廠Log4jLoggerFactory畸写。
總結(jié)
前面大概分析了下SLF4J中獲取Logger的流程驮瞧;
1. 如果SLF4J為未初始化將進(jìn)行初始化,其初始化默認(rèn)使用Log4j的工廠Log4jLoggerFactory枯芬。如果找不到將使用SLF4J的SubstituteLoggerFactory工廠论笔。
2. SLF4J使用ClassLoader類加載器來加載指定路徑資源。
3. 對(duì)于多個(gè)StaticLoggerBinder千所,其獲取采用餓漢式-單例設(shè)計(jì)模式來設(shè)計(jì)的狂魔。
4. SubstituteLoggerFactory工廠類設(shè)計(jì),采用工廠方法模式來設(shè)計(jì)的淫痰。
思考
我們前面提到了loggerFactory首先是使用Log4j的工廠Log4jLoggerFactory來進(jìn)行初始化的最楷。那么Log4j工廠是如何進(jìn)行初始化使用的?下篇文章再說黑界。管嬉。。Log4j的一些實(shí)現(xiàn)朗鸠。