如果配置过logback的同学肯定都清楚,在resource下创建一个logback.xml文件即可,但是今天遇到了一个需求,需要在生产环境和开发环境进行区分,因为生产环境下的路径是work用户的而我们开发环境是dev用户,默认权限运维也只给到home目录下,如果配置文件只有一套的话,在开发时需要调整地址,而发布代码的时候又要改回去。容易出错也麻烦,那么以这个背景为前提,研究一下如何配置。
之前也没有详细的了解过slf4j和logback是如何实现的,整好跟下代码看一下。如果没兴趣了解细节的同学,可以直接拉到底部
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private static final Logger logger = LoggerFactory.getLogger(Test.class);
引用sl4j一般都是上面的办法,没有什么特别的,那么如何加载框架肯定是在LoggerFactory.getLogger(Test.class)
中了,LoggerFactory点进去看了一下没有静态块,明显的一个工厂方法,那么看看getLogger
public static Logger getLogger(Class<?> clazz) {
// 返回的就是这个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;
}
继续进入到getLogger
public static Logger getLogger(String name) {
ILoggerFactory iLoggerFactory = getILoggerFactory();
return iLoggerFactory.getLogger(name);
}
跟到getILoggerFactory
public static ILoggerFactory getILoggerFactory() {
if (INITIALIZATION_STATE == UNINITIALIZED) {
INITIALIZATION_STATE = ONGOING_INITIALIZATION;
//方法命很明显,执行初始化操作
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 TEMP_FACTORY;
}
throw new IllegalStateException("Unreachable code");
}
中间方法就不粘贴了performInitialization->bind()->StaticLoggerBinder.getSingleton()
下面是重点,看注释
private final static void bind() {
try {
Set<URL> staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
// the next line does the binding
/*
* 这个StaticLoggerBinder类是在ch.qos.logback-classic包了,我猜测log4j应该也是这么实现的。
* 那么然后在StaticLoggerBinder中的静态块初始化logbck的实现
* 下面是调用栈StaticLoggerBinder.init()->new ContextInitializer(defaultLoggerContext).autoConfig()->findURLOfDefaultConfigurationFile(true)
*/
StaticLoggerBinder.getSingleton();
INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
reportActualBinding(staticLoggerBinderPathSet);
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.contains("org.slf4j.impl.StaticLoggerBinder.getSingleton()")) {
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);
}
}
下面是findURLOfDefaultConfigurationFile(boolean updateStatus)
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 URL findURLOfDefaultConfigurationFile(boolean updateStatus) {
ClassLoader myClassLoader = Loader.getClassLoaderOfObject(this);
//这个方法里就是通过SystemProperties来改变logback配置文件的地方了
URL url = findConfigFileURLFromSystemProperties(myClassLoader, updateStatus);
if (url != null) {
return url;
}
//如果没有设置那么优先用TEST_AUTOCONFIG_FILE的配置
url = getResource(TEST_AUTOCONFIG_FILE, myClassLoader, updateStatus);
if (url != null) {
return url;
}
url = getResource(GROOVY_AUTOCONFIG_FILE, myClassLoader, updateStatus);
if (url != null) {
return url;
}
return getResource(AUTOCONFIG_FILE, myClassLoader, updateStatus);
}
下面是findConfigFileURLFromSystemProperties
private URL findConfigFileURLFromSystemProperties(ClassLoader classLoader, boolean updateStatus) {
//在这里获取配置
String logbackConfigFile = OptionHelper.getSystemProperty(CONFIG_FILE_PROPERTY);
if (logbackConfigFile != null) {
URL result = null;
try {
result = new URL(logbackConfigFile);
return result;
} catch (MalformedURLException e) {
// so, resource is not a URL:
// attempt to get the resource from the class path
result = Loader.getResource(logbackConfigFile, classLoader);
if (result != null) {
return result;
}
File f = new File(logbackConfigFile);
if (f.exists() && f.isFile()) {
try {
result = f.toURI().toURL();
return result;
} catch (MalformedURLException e1) {
}
}
} finally {
if (updateStatus) {
statusOnResourceSearch(logbackConfigFile, classLoader, result);
}
}
}
return null;
}
总结
- 在jvm参数中添加
-Dlogback.configurationFile=logback-product.xml
,logback-product.xml文件在编译后保证在classes下即可 - 综上分析得知logback配置文件的优先级是SystemProperties>logback-test.xml>logback.groovy>logback.xml