1. 添加依赖
在一般项目中使用Log4j2至少需要引用log4j-api-2.x和log4j-core-2.x这两个jar包(老版本),现在只需引入以下一个依赖即可,但是如果想桥接至slf4j,需要额外依赖(两者关系参考另一文章Java日志框架:slf4j作用及其实现原理)
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.12.0</version>
</dependency>
<!-- slf4j与log4j之间的桥接包-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.25</version>
</dependency>
在spring boot项目中使用Log4j2
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<!-- 排除spring boot默认日志logback -->
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<!--同时引入这两个依赖时,因为该jar依赖于前者,所以只需在前者中排除掉自带的logging依赖即可 -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 引入log4j2依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
依赖添加完成后,我们看一下都有什么jar包
第一个jar的作用是将log4j2与slf4j之间的桥接包,起绑定作用
第二个以及第三个是log4j2的核心包
第四个是java.util.logging.Logger与slf4j之间的桥接包
2. 添加配置文件
配置文件的加载主要逻辑在ConfigurationFactory这个类中,类容较多,能力有限不做过多分析,如深究,建议从
org.apache.logging.log4j.core.LoggerContext中start方法开始debug跟。
package org.apache.logging.log4j.core.config;
// **************略****************
public abstract class ConfigurationFactory extends ConfigurationBuilderFactory {
public ConfigurationFactory() {
super();
// TEMP For breakpoints
}
/**
* Allows the ConfigurationFactory class to be specified as a system property.
*/
public static final String CONFIGURATION_FACTORY_PROPERTY = "log4j.configurationFactory";
/**
* Allows the location of the configuration file to be specified as a system property.
*/
public static final String CONFIGURATION_FILE_PROPERTY = "log4j.configurationFile";
配置文件可以手动指定,但我们通常的选择是只提供文件,让log4j自己去找,log4j有默认的4个格式工厂,但它支持扩充(这里我不知道该怎么描述,建议往下看),可以加载其他自定义的工厂实现,如springBoot jar包中的工厂实现(第5个)
static List<ConfigurationFactory> getFactories() {
return factories;
}
每个工厂支持一个或多个后缀,分别是{.properties},{.yml以及.yaml},{.jsn以及.json},{.xml以及*},{.springBoot}
如果什么都没配置,springBoot项目中会自动生成一个默认配置。
3. 源码简单阅读
想了想,既然看了,还是写点啥吧,org.apache.logging.log4j.core.config.ConfigurationFactory
这里是不提供配置文件的springBoot项目的debug跟踪阅读源码放式,所以解读有限
图片来自https://blog.csdn.net/sweetyi/article/details/104942310
private void reconfigure(final URI configURI) {
final ClassLoader cl = ClassLoader.class.isInstance(externalContext) ? (ClassLoader) externalContext : null;
LOGGER.debug("Reconfiguration started for context[name={}] at URI {} ({}) with optional ClassLoader: {}",
contextName, configURI, this, cl);
// 贴上这段代码主要是学习一下ConfigurationFactory.getInstance()这个方法
final Configuration instance = ConfigurationFactory.getInstance().getConfiguration(this, contextName, configURI, cl);
// ********略*******
}
public static ConfigurationFactory getInstance() {
// volatile works in Java 1.6+, so double-checked locking also works properly
// noinspection DoubleCheckedLocking
if (factories == null) {
LOCK.lock();
try {
if (factories == null) {
final List<ConfigurationFactory> list = new ArrayList<>();
// 检查一下,有没有手动指定额外的配置工厂
final String factoryClass = PropertiesUtil.getProperties().getStringProperty(CONFIGURATION_FACTORY_PROPERTY);
if (factoryClass != null) {
addFactory(list, factoryClass);
}
final PluginManager manager = new PluginManager(CATEGORY);
// 这里就是加载配置工厂的逻辑了,springBoot自己实现的工厂就是在这里被加载进来的,有兴趣的可以自己去看一下这个方法的源码
manager.collectPlugins();
final Map<String, PluginType<?>> plugins = manager.getPlugins();
final List<Class<? extends ConfigurationFactory>> ordered = new ArrayList<>(plugins.size());
for (final PluginType<?> type : plugins.values()) {
try {
ordered.add(type.getPluginClass().asSubclass(ConfigurationFactory.class));
} catch (final Exception ex) {
LOGGER.warn("Unable to add class {}", type.getPluginClass(), ex);
}
}
Collections.sort(ordered, OrderComparator.getInstance());
for (final Class<? extends ConfigurationFactory> clazz : ordered) {
addFactory(list, clazz);
}
// see above comments about double-checked locking
//noinspection NonThreadSafeLazyInitialization
factories = Collections.unmodifiableList(list);
}
} finally {
LOCK.unlock();
}
}
LOGGER.debug("Using configurationFactory {}", configFactory);
/**
* private static volatile List<ConfigurationFactory> factories = null;
* private static ConfigurationFactory configFactory = new Factory();
* 上面的步骤是初始化factories ,实际返回的是ConfigurationFactory的内部实现类Factory
*/
return configFactory;
}
factories 最后包含了Factory以外的5个配置工厂实现类,这5个工厂是又排序的,决定了配置文件的查找顺序,这里截图不太合适,正确的顺序是Properties,Yaml,Json,Xml,SpringBoot
从包名可以看出,log4j包中是不含SpirngBoot配置工厂的,所以非springBoot项目,如果不提供配置文件的话,它是不会有默认配置的。
public Configuration getConfiguration(final LoggerContext loggerContext, final String name, final URI configLocation, final ClassLoader loader) {
// ********略*******
// 先判断类加载器是否为null
if (loader == null) {
return getConfiguration(loggerContext, name, configLocation);
}
// 如果加载器不为null,且URL资源有效,则尝试解析并返回配置
if (isClassLoaderUri(configLocation)) {
// 解析路径
final String path = extractClassLoaderUriPath(configLocation);
final ConfigurationSource source = ConfigurationSource.fromResource(path, loader);
if (source != null) {
final Configuration configuration = getConfiguration(loggerContext, source);
if (configuration != null) {
return configuration;
}
}
}
return getConfiguration(loggerContext, name, configLocation);
}
// 后面这些就是解析URI的步骤了,可能需要一些URI知识点
static boolean isClassLoaderUri(final URI uri) {
if (uri == null) {
return false;
}
final String scheme = uri.getScheme();
return scheme == null || scheme.equals(CLASS_LOADER_SCHEME) || scheme.equals(CLASS_PATH_SCHEME);
}
static String extractClassLoaderUriPath(final URI uri) {
return uri.getScheme() == null ? uri.getPath() : uri.getSchemeSpecificPart();
}
// 这个方法比较重要,配置文件的查找
public static ConfigurationSource fromResource(final String resource, final ClassLoader loader) {
// 通过类加载器查找配置文件的URL,这里我其实也想去了解一下,就不写在这里了
final URL url = Loader.getResource(resource, loader);
if (url == null) {
return null;
}
InputStream is = null;
try {
is = url.openStream();
} catch (final IOException ioe) {
ConfigurationFactory.LOGGER.catching(Level.DEBUG, ioe);
return null;
}
if (is == null) {
return null;
}
if (FileUtils.isFile(url)) {
try {
return new ConfigurationSource(is, FileUtils.fileFromUri(url.toURI()));
} catch (final URISyntaxException ex) {
// Just ignore the exception.
ConfigurationFactory.LOGGER.catching(Level.DEBUG, ex);
}
}
return new ConfigurationSource(is, url);
}
private static final String PROTOCOL_FILE = "file";
private static final String JBOSS_FILE = "vfsfile";
public static boolean isFile(final URL url) {
return url != null && (url.getProtocol().equals(PROTOCOL_FILE) || url.getProtocol().equals(JBOSS_FILE));
}
// 这是ConfigurationFactory的默认方法,仅发现某个工厂假如支持"*"格式时会被调用,实在有点绕,没读明白,不能理解意图,跳过
public Configuration getConfiguration(final LoggerContext loggerContext, final String name, final URI configLocation) {
if (!isActive()) {
return null;
}
if (configLocation != null) {
final ConfigurationSource source = ConfigurationSource.fromUri(configLocation);
if (source != null) {
return getConfiguration(loggerContext, source);
}
}
return null;
}
// 这是Factory对上面方法的覆盖,也解释过了 ConfigurationFactory.getInstance()返回的是Factory实例,所以debug跟下来调用的也是这个
public Configuration getConfiguration(final LoggerContext loggerContext, final String name, final URI configLocation) {
if (configLocation == null) {
// 又是查环境变量,直接跳过
final String configLocationStr = this.substitutor.replace(PropertiesUtil.getProperties()
.getStringProperty(CONFIGURATION_FILE_PROPERTY));
if (configLocationStr != null) {
final String[] sources = configLocationStr.split(",");
if (sources.length > 1) {
final List<AbstractConfiguration> configs = new ArrayList<>();
for (final String sourceLocation : sources) {
final Configuration config = getConfiguration(loggerContext, sourceLocation.trim());
if (config != null && config instanceof AbstractConfiguration) {
configs.add((AbstractConfiguration) config);
} else {
LOGGER.error("Failed to created configuration at {}", sourceLocation);
return null;
}
}
return new CompositeConfiguration(configs);
}
return getConfiguration(loggerContext, configLocationStr);
}
// 这里就是去匹配*格式,debug跟完没有什么作用,跳过算了
for (final ConfigurationFactory factory : getFactories()) {
final String[] types = factory.getSupportedTypes();
if (types != null) {
for (final String type : types) {
if (type.equals(ALL_TYPES)) {
final Configuration config = factory.getConfiguration(loggerContext, name, configLocation);
if (config != null) {
return config;
}
}
}
}
}
} else {
// configLocation != null
final String configLocationStr = configLocation.toString();
for (final ConfigurationFactory factory : getFactories()) {
final String[] types = factory.getSupportedTypes();
if (types != null) {
for (final String type : types) {
if (type.equals(ALL_TYPES) || configLocationStr.endsWith(type)) {
final Configuration config = factory.getConfiguration(loggerContext, name, configLocation);
if (config != null) {
return config;
}
}
}
}
}
}
// 重点就是在这个方法了,以各种不同组合的参数去查找配置
Configuration config = getConfiguration(loggerContext, true, name);
if (config == null) {
config = getConfiguration(loggerContext, true, null);
if (config == null) {
config = getConfiguration(loggerContext, false, name);
if (config == null) {
config = getConfiguration(loggerContext, false, null);
}
}
}
if (config != null) {
return config;
}
LOGGER.error("No Log4j 2 configuration file found. " +
"Using default configuration (logging only errors to the console), " +
"or user programmatically provided configurations. " +
"Set system property 'log4j2.debug' " +
"to show Log4j 2 internal initialization logging. " +
"See https://logging.apache.org/log4j/2.x/manual/configuration.html for instructions on how to configure Log4j 2");
return new DefaultConfiguration();
}
private Configuration getConfiguration(final LoggerContext loggerContext, final boolean isTest, final String name) {
final boolean named = Strings.isNotEmpty(name);
final ClassLoader loader = LoaderUtil.getThreadContextClassLoader();
for (final ConfigurationFactory factory : getFactories()) {
String configName;
final String prefix = isTest ? TEST_PREFIX : DEFAULT_PREFIX;
final String [] types = factory.getSupportedTypes();
if (types == null) {
continue;
}
for (final String suffix : types) {
if (suffix.equals(ALL_TYPES)) {
continue;
}
configName = named ? prefix + name + suffix : prefix + suffix;
// 逻辑很简单,生成配置名称,根据类加载器查找指定配置
final ConfigurationSource source = ConfigurationSource.fromResource(configName, loader);
if (source != null) {
if (!factory.isActive()) {
LOGGER.warn("Found configuration file {} for inactive ConfigurationFactory {}", configName, factory.getClass().getName());
}
return factory.getConfiguration(loggerContext, source);
}
}
}
return null;
}