主流的Web框架spring boot默认使用slf4j + logback,slf4j 为日志处理提供了统一的接口,比较代表的实现org.apache.log4j、ch.qos.logback等。
主要内容 logback的一般加载过程:
- slf4j查找具体实现类的原理;
-
加载配置文件过程(logback是logback.xml);
logback load.jpg
1. slf4j + logback maven集成
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.7</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.1.7</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.7</version>
</dependency>
2. 基本使用
package com.bclz;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @Program: logback-test
* @Description: logback测试
* @Author: c
* @Date: 2021-08-17 18:36
*/
public class Test {
private static final Logger LOGGER= LoggerFactory.getLogger(Test.class);
public static void main(String[] args) {
LOGGER.info("测试普通日志...");
LOGGER.error("测试错误日志...");
}
}
3. 源码分析
关键方法分析
-
getILoggerFactory()
public static ILoggerFactory getILoggerFactory() { if (INITIALIZATION_STATE == UNINITIALIZED) { INITIALIZATION_STATE = ONGOING_INITIALIZATION; performInitialization(); } switch (INITIALIZATION_STATE) { case SUCCESSFUL_INITIALIZATION: //如果初始化成功,则调用StaticLoggerBinder单例的getLoggerFactory()方法获得LoggerFactory工厂对象 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://bugzilla.slf4j.org/show_bug.cgi?id=106 return TEMP_FACTORY; } throw new IllegalStateException("Unreachable code"); }
-
performInitialization()初始化
private final static void performInitialization() { bind(); if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) { versionSanityCheck(); } }
-
bind()
try { //从classpath获取所有slf4j的实现,并将它们的资源路径存放到staticLoggerBinderPathSet中 Set<URL> staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet(); //若从classpath中找到了多个slf4j的实现,则打印警告 reportMultipleBindingAmbiguity(staticLoggerBinderPathSet); // the next line does the binding 将具体的实现绑定到slf4j StaticLoggerBinder.getSingleton(); //修改初始化状态为初始化成功 INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION; reportActualBinding(staticLoggerBinderPathSet); fixSubstitutedLoggers(); } catch (NoClassDefFoundError ncde) { //若有多个实现,则会抛此异常 //jvm在编译时能找到合适的类,而在运行时不能找到合适的类导致的错误(在这里是找不到具体的StaticLoggerBinder) ...... }
-
findPossibleStaticLoggerBinderPathSet()
注意在方法中有个经典的资源路径使用:
ClassLoader.getSystemResources(...)
-
LoggerFactory.class.getClassLoader().getResources(...)
这两者由于类加载器的不同,可能会有不同的结果:
getSystemResources(...)是用AppClassLoader加载的;
而后者可能是自定义的类加载器,这两个方法可能会有不同的结果(加载jar资源一般推荐后者)。
ClassLoader loggerFactoryClassLoader = LoggerFactory.class .getClassLoader(); Enumeration<URL> paths; if (loggerFactoryClassLoader == null) { //STATIC_LOGGER_BINDER_PATH="org/slf4j/impl/StaticLoggerBinder.class" paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH); } else { paths = loggerFactoryClassLoader .getResources(STATIC_LOGGER_BINDER_PATH); }
-
至此,完成"org/slf4j/impl/StaticLoggerBinder.class"路径的查找,该实现类在logback-classic包中,slf4j的具体实现就是通过这种方式查找绑定的。
- StaticLoggerBinder.getSingleton()
该方法返回SINGLETON。
具体在类加载的静态代码块中初始化:
static {
SINGLETON.init();
}
/**
* Package access for testing purposes.
*/
void init() {
try {
try {
new ContextInitializer(defaultLoggerContext).autoConfig();
} catch (JoranException je) {
Util.report("Failed to auto configure default logger context", je);
}
// logback-292
if (!StatusUtil.contextHasStatusListener(defaultLoggerContext)) {
StatusPrinter.printInCaseOfErrorsOrWarnings(defaultLoggerContext);
}
contextSelectorBinder.init(defaultLoggerContext, KEY);
initialized = true;
} catch (Throwable t) {
// we should never get here
Util.report("Failed to instantiate [" + LoggerContext.class.getName() + "]", t);
}
}
关键方法上下文初始化器的 <font color='red'>new ContextInitializer(defaultLoggerContext).autoConfig();</font>
public void autoConfig() throws JoranException {
StatusListenerConfigHelper.installIfAsked(loggerContext);
//查找默认配置文件
URL url = findURLOfDefaultConfigurationFile(true);
if (url != null) {
configureByResource(url);
} else {
Configurator c = EnvUtil.loadFromServiceLoader(Configurator.class);
if (c != null) {
try {
c.setContext(loggerContext);
c.configure(loggerContext);
} catch (Exception e) {
throw new LogbackException(String.format("Failed to initialize Configurator: %s using ServiceLoader", c != null ? c.getClass()
.getCanonicalName() : "null"), e);
}
} else {
//没有配置文件,则使用打印控制台的默认配置
BasicConfigurator basicConfigurator = new BasicConfigurator();
basicConfigurator.setContext(loggerContext);
basicConfigurator.configure(loggerContext);
}
}
}
再看看findURLOfDefaultConfigurationFile方法,就是查找具体路径的实现了
public URL findURLOfDefaultConfigurationFile(boolean updateStatus) {
// 获取当前实例的类加载器
ClassLoader myClassLoader = Loader.getClassLoaderOfObject(this);
//logback.configurationFile
URL url = findConfigFileURLFromSystemProperties(myClassLoader, updateStatus);
if (url != null) {
return url;
}
//再找logback.groovy
url = getResource(GROOVY_AUTOCONFIG_FILE, myClassLoader, updateStatus);
if (url != null) {
return url;
}
//再找logback-test.xml
url = getResource(TEST_AUTOCONFIG_FILE, myClassLoader, updateStatus);
if (url != null) {
return url;
}
//最后找logback.xml
return getResource(AUTOCONFIG_FILE, myClassLoader, updateStatus);
}
至此xml配置文件加载完毕