问题
使用slf4j和logback记录日志,有这样一个需求,如果指定了一个特定logback.xml,就用指定的配置,如果没有,则系统提供一个默认的配置。
解决
classpath下放一个配置文件logback.xml,使用环境变量logback.configurationFile指定另一个配置。logback会首先使用环境变量指定的文件,如果没有指定或文件不存在,logback会使用classpath下的文件。
原理 - logback配置文件查找过程
1. 使用如下代码获取日志器
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Logger logger = LoggerFactory.getLogger(LogTest.class);
2. slf4j首先会判断有没有初始化过
org.slf4j.LoggerFactory
public static Logger getLogger(Class> clazz) {
Logger logger = getLogger(clazz.getName());
...
return logger;
}
public static Logger getLogger(String name) {
ILoggerFactory iLoggerFactory = getILoggerFactory();
return iLoggerFactory.getLogger(name);
}
public static ILoggerFactory getILoggerFactory() {
if (INITIALIZATION_STATE == UNINITIALIZED) {
synchronized (LoggerFactory.class) {
if (INITIALIZATION_STATE == UNINITIALIZED) {
INITIALIZATION_STATE = ONGOING_INITIALIZATION;
performInitialization();
}
}
}
...
}
3. 如果没有初始化过,则执行初始化,初始化的过程是加载org.slf4j.impl.StaticLoggerBinder类
private final static void performInitialization() {
bind();
...
}
private final static void bind() {
...
staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
...
}
private static String STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class";
static Set findPossibleStaticLoggerBinderPathSet() {
// use Set instead of list in order to deal with bug #138
// LinkedHashSet appropriate here because it preserves insertion order during iteration
Set staticLoggerBinderPathSet = new LinkedHashSet();
try {
ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader();
Enumeration paths;
if (loggerFactoryClassLoader == null) {
paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);
} else {
paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH);
}
while (paths.hasMoreElements()) {
URL path = paths.nextElement();
staticLoggerBinderPathSet.add(path);
}
} catch (IOException ioe) {
Util.report("Error getting resources from path", ioe);
}
return staticLoggerBinderPathSet;
}
4. 加载StaticLoggerBinder类的初始化过程
org.slf4j.impl.StaticLoggerBinder
static {
SINGLETON.init();
}
void init() {
new ContextInitializer(defaultLoggerContext).autoConfig();
}
ch.qos.logback.classic.util.ContextInitializer
final public static String GROOVY_AUTOCONFIG_FILE = "logback.groovy";
final public static String AUTOCONFIG_FILE = "logback.xml";
final public static String TEST_AUTOCONFIG_FILE = "logback-test.xml";
final public static String CONFIG_FILE_PROPERTY = "logback.configurationFile";
public void autoConfig() throws JoranException {
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);
}
}
}
public URL findURLOfDefaultConfigurationFile(boolean updateStatus) {
ClassLoader myClassLoader = Loader.getClassLoaderOfObject(this);
URL url = findConfigFileURLFromSystemProperties(myClassLoader, updateStatus);
if (url != null) {
return url;
}
url = getResource(GROOVY_AUTOCONFIG_FILE, myClassLoader, updateStatus);
if (url != null) {
return url;
}
url = getResource(TEST_AUTOCONFIG_FILE, myClassLoader, updateStatus);
if (url != null) {
return url;
}
return getResource(AUTOCONFIG_FILE, myClassLoader, updateStatus);
}
private URL findConfigFileURLFromSystemProperties(ClassLoader classLoader, boolean updateStatus) {
String logbackConfigFile = OptionHelper.getSystemProperty(CONFIG_FILE_PROPERTY);
...
}
可见,logback加载的过程是
(1)使用logback.configurationFile环境变量的设置
(2)使用classpath中的logback.groovy
(3)使用classpath中的logback-test.xml
(4)使用classpath中的logback.xml
(5)查找com.qos.logback.classic.spi.Configurator接口实现类,调用实现类的configure方法设置
(6)使用BasicConfigurator类的configure方法设置