Log4j2的应用和原理
2021-02-20 本文已影响0人
晴天哥_王志
系列
简介
Log4j2是apache在Log4j的基础上,参考logback架构实现的一套新的日志系统。本文基于Log4j-2.12.1的版本进行源码分析。
本文的目的在于梳理Log4j2的Logger对象生成过程,借此理顺Log4j2各组件之间的联系。
Log4j2的用法
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="debug" strict="true">
<Properties>
<Property name="filename">target/test.log</Property>
</Properties>
<Filter type="ThresholdFilter" level="trace"/>
<Appenders>
<Appender type="Console" name="STDOUT">
<Layout type="PatternLayout" pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n"/>
<Filters>
<Filter type="MarkerFilter" marker="FLOW" onMatch="DENY" onMismatch="NEUTRAL"/>
<Filter type="MarkerFilter" marker="EXCEPTION" onMatch="DENY" onMismatch="ACCEPT"/>
</Filters>
</Appender>
<Appender type="Console" name="FLOW">
<Layout type="PatternLayout" pattern="%C{1}.%M %m %ex%n"/><!-- class and line number -->
<Filters>
<Filter type="MarkerFilter" marker="FLOW" onMatch="ACCEPT" onMismatch="NEUTRAL"/>
<Filter type="MarkerFilter" marker="EXCEPTION" onMatch="ACCEPT" onMismatch="DENY"/>
</Filters>
</Appender>
<Appender type="File" name="File" fileName="${filename}">
<Layout type="PatternLayout">
<Pattern>%d %p %C{1.} [%t] %m%n</Pattern>
</Layout>
</Appender>
</Appenders>
<Loggers>
<Logger name="test1" level="debug" additivity="false">
<Filter type="ThreadContextMapFilter">
<KeyValuePair key="test" value="123"/>
</Filter>
<AppenderRef ref="STDOUT"/>
</Logger>
<Logger name="test2" level="debug" additivity="false">
<AppenderRef ref="File"/>
</Logger>
<Root level="trace">
<AppenderRef ref="STDOUT"/>
</Root>
</Loggers>
</Configuration>
- 根节点Configuration设置Log4j2本身的属性。
- Appenders设置多个Appender对象,Appender内部包含Layout和Filters。
- Loggers设置Logger对象,包含Logger和Root两个子节点。
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Log4jDemo {
private static Logger logger=
LogManager.getLogger(LogManager.ROOT_LOGGER_NAME);
public static void main(String[] args) {
for(int i=0;i<3;i++){
// 记录trace级别的信息
logger.trace("log4j2日志输出:This is trace message.");
// 记录debug级别的信息
logger.debug("log4j2日志输出:This is debug message.");
// 记录info级别的信息
logger.info("log4j2日志输出:This is info message.");
// 记录error级别的信息
logger.error("log4j2日志输出:This is error message.");
}
}
}
- 通过LogManager.getLogger()来获取Logger对象进行日志输出。
配置文件解析
- 根节点Configuration有两个属性:status和monitorinterval,有两个子节点:Appenders和Loggers(表明可以定义多个Appender和Logger)。
- status:用来指定log4j本身的打印日志的级别。
- monitorinterval:用于指定log4j自动重新配置的监测间隔时间,单位是s,最小是5s。
- Appenders节点,常见的有三种子节点:Console、RollingFile、File。
-
Console节点用来定义输出到控制台的Appender。
- name:指定Appender的名字。
- target:SYSTEM_OUT 或 SYSTEM_ERR,一般只设置默认:SYSTEM_OUT。
- PatternLayout:输出格式,不设置默认为:%m%n。
-
File节点用来定义输出到指定位置的文件的Appender。
- name:指定Appender的名字。
- fileName:指定输出日志的目的文件带全路径的文件名。
- PatternLayout:输出格式,不设置默认为:%m%n。
-
RollingFile节点用来定义超过指定大小自动删除旧的创建新的Appender。
- name:指定Appender的名字。
- fileName:指定输出日志的目的文件带全路径的文件名。
- PatternLayout:输出格式,不设置默认为:%m%n。
- filePattern:指定新建日志文件的名称格式。
- Policies:指定滚动日志的策略,就是什么时候进行新建日志文件输出日志。
- TimeBasedTriggeringPolicy:Policies子节点,基于时间的滚动策略,interval属性用来指定多久滚动一次,默认是1 hour。modulate=true用来调整时间:比如现在是早上3am,interval是4,那么第一次滚动是在4am,接着是8am,12am...而不是7am。
- SizeBasedTriggeringPolicy:Policies子节点,基于指定文件大小的滚动策略,size属性用来定义每个日志文件的大小。
- DefaultRolloverStrategy:用来指定同一个文件夹下最多有几个日志文件时开始删除最旧的,创建新的(通过max属性),默认是7个文件。
-
- Loggers节点,常见的有两种:Root和Logger。
- Root节点用来指定项目的根日志,如果没有单独指定Logger,那么就会默认使用该Root日志输出。
- level:日志输出级别,共有8个级别,按照从低到高为:All < Trace < Debug < Info < Warn < Error < Fatal < OFF。
- AppenderRef:Root的子节点,用来指定该日志输出到哪个Appender。
- Logger节点用来单独指定日志的形式,比如要为指定包下的class指定不同的日志级别等。
- level:日志输出级别,共有8个级别,按照从低到高为:All < Trace < Debug < Info < Warn < Error < Fatal < OFF。
- name:用来指定该Logger所适用的类或者类所在的包全路径,继承自Root节点。
- AppenderRef:Logger的子节点,用来指定该日志输出到哪个Appender,如果没有指定,就会默认继承自Root.如果指定了,那么会在指定的这个Appender和Root的Appender中都会输出,此时我们可以设置Logger的additivity="false"只在自定义的Appender中进行输出。
- Root节点用来指定项目的根日志,如果没有单独指定Logger,那么就会默认使用该Root日志输出。
- 关于日志level.
- 共有8个级别,按照从低到高为:All < Trace < Debug < Info < Warn < Error < Fatal < OFF。
- All:最低等级的,用于打开所有日志记录。
- Trace:是追踪,就是程序推进以下,你就可以写个trace输出。
- Debug:指出细粒度信息事件对调试应用程序是非常有帮助的。
- Info:消息在粗粒度级别上突出强调应用程序的运行过程。
- Warn:输出警告及warn以下级别的日志。
- Error:输出错误信息日志。
- Fatal:输出每个严重的错误事件将会导致应用程序的退出的日志.
- OFF:最高等级的,用于关闭所有日志记录。
log4j2核心组件
public class LoggerContext extends AbstractLifeCycle
implements org.apache.logging.log4j.spi.LoggerContext,
AutoCloseable, Terminable, ConfigurationListener,
LoggerContextShutdownEnabled {
// 保存Logger对象的LoggerRegistry对象
private final LoggerRegistry<Logger> loggerRegistry =
new LoggerRegistry<>();
// 保存配置的Configuration对象
private volatile Configuration configuration = new DefaultConfiguration();
}
- LoggerContext通过LoggerRegistry保存Logger对象。
public class Logger extends AbstractLogger implements Supplier<LoggerConfig> {
// PrivateConfig包含LoggerConfig
protected volatile PrivateConfig privateConfig;
// LoggerContext对象
private final LoggerContext context;
protected class PrivateConfig {
// config fields are public to make them visible to Logger subclasses
/** LoggerConfig to delegate the actual logging to. */
public final LoggerConfig loggerConfig; // SUPPRESS CHECKSTYLE
/** The current Configuration associated with the LoggerConfig. */
public final Configuration config; // SUPPRESS CHECKSTYLE
private final Level loggerConfigLevel;
private final int intLevel;
private final Logger logger;
private final boolean requiresLocation;
}
}
- Logger通过PrivateConfig来保存LoggerConfig对象。
public abstract class AbstractFilterable
extends AbstractLifeCycle implements Filterable {
private volatile Filter filter;
}
public class LoggerConfig extends AbstractFilterable implements LocationAware {
private List<AppenderRef> appenderRefs = new ArrayList<>();
private final AppenderControlArraySet appenders =
new AppenderControlArraySet();
private LoggerConfig parent;
private Map<Property, Boolean> propertiesMap;
private final List<Property> properties;
private final Configuration config;
}
public class AppenderControl extends AbstractFilterable {
private final ThreadLocal<AppenderControl> recursive = new ThreadLocal<>();
private final Appender appender;
private final Level level;
private final int intLevel;
private final String appenderName;
}
- LoggerConfig通过AppenderControlArraySet保存AppenderControl。
- AppenderControl保存Appender。
- LoggerConfig父类包含Filter对象。
public abstract class AbstractFilterable
extends AbstractLifeCycle implements Filterable {
private volatile Filter filter;
}
public abstract class AbstractAppender extends AbstractFilterable
implements Appender, LocationAware {
private final String name;
private final boolean ignoreExceptions;
private final Layout<? extends Serializable> layout;
}
- Appender对象包含Filter对象。
- Appender对象包含Layout对象。
public abstract class AbstractFilterable
extends AbstractLifeCycle implements Filterable {
private volatile Filter filter;
}
public abstract class AbstractConfiguration
extends AbstractFilterable implements Configuration {
protected final List<String> pluginPackages = new ArrayList<>();
protected PluginManager pluginManager;
private String name;
private ConcurrentMap<String, Appender> appenders
= new ConcurrentHashMap<>();
private ConcurrentMap<String, LoggerConfig> loggerConfigs
= new ConcurrentHashMap<>();
private LoggerConfig root = new LoggerConfig();
private final WeakReference<LoggerContext> loggerContext;
}
- Configuration包含Appender对象。
- Configuration包含LoggerConfig对象。
- Configuration包含Filter对象。
Log4j2初始化流程
LogManager
- LogManager是Log4J启动的入口,后续的LoggerContext以及Logger都是通过调用LogManager的getLogger静态方法获得。
- LogManager的static代码块初始化LoggerContextFactor对象 。
public class LogManager {
public static String FACTORY_PROPERTY_NAME = "log4j2.loggerContextFactory";
private static volatile LoggerContextFactory factory;
static {
// step1 通过特定配置文件的配置信息获取loggerContextFactory
final PropertiesUtil managerProps = PropertiesUtil.getProperties();
final String factoryClassName = managerProps.getStringProperty(
FACTORY_PROPERTY_NAME);
if (factoryClassName != null) {
try {
factory = LoaderUtil.newCheckedInstanceOf(
factoryClassName, LoggerContextFactory.class);
} catch (final ClassNotFoundException cnfe) {
} catch (final Exception ex) {
}
}
if (factory == null) {
final SortedMap<Integer, LoggerContextFactory> factories =
new TreeMap<>();
// step2 ProviderUtil中的getProviders()方法载入providers
if (ProviderUtil.hasProviders()) {
for (final Provider provider : ProviderUtil.getProviders()) {
final Class<? extends LoggerContextFactory> factoryClass
= provider.loadLoggerContextFactory();
if (factoryClass != null) {
try {
factories.put(provider.getPriority(),
factoryClass.newInstance());
} catch (final Exception e) {
}
}
}
if (factories.isEmpty()) {
factory = new SimpleLoggerContextFactory();
} else if (factories.size() == 1) {
factory = factories.get(factories.lastKey());
} else {
factory = factories.get(factories.lastKey());
}
} else {
// step3 默认SimpleLoggerContextFactory
factory = new SimpleLoggerContextFactory();
}
}
}
}
- 1.首先通过PropertiesUtil.getProperties()根据特定配置文件的配置信息获取loggerContextFactory的类。
- 2.如果没有找到对应的Factory则通过ProviderUtil中的getProviders()方法载入providers,通过provider的loadLoggerContextFactory方法载入LoggerContextFactory的实现类。
- 3.如果provider中没有获取到LoggerContextFactory的实现类或provider为空,则使用SimpleLoggerContextFactory作为LoggerContextFactory。
根据配置文件加载Factory
public final class PropertiesUtil {
private static String LOG4J_PROPERTIES_FILE_NAME =
"log4j2.component.properties";
private static final PropertiesUtil LOG4J_PROPERTIES =
new PropertiesUtil(LOG4J_PROPERTIES_FILE_NAME);
public static PropertiesUtil getProperties() {
return LOG4J_PROPERTIES;
}
public PropertiesUtil(final String propertiesFileName) {
this.environment = new Environment(
new PropertyFilePropertySource(propertiesFileName));
}
}
public class PropertyFilePropertySource extends PropertiesPropertySource {
public PropertyFilePropertySource(final String fileName) {
super(loadPropertiesFile(fileName));
}
// 查找指定文件内容并加载至Properties当中
private static Properties loadPropertiesFile(final String fileName) {
final Properties props = new Properties();
for (final URL url : LoaderUtil.findResources(fileName)) {
try (final InputStream in = url.openStream()) {
props.load(in);
} catch (final IOException e) {
}
}
return props;
}
}
- PropertiesUtil.getProperties()负责加载log4j2.component.properties文件。
- log4j2.component.properties内包含log4j2.loggerContextFactory的配置。
通过ProviderUtil加载Factory
public final class ProviderUtil {
static String PROVIDER_RESOURCE = "META-INF/log4j-provider.properties";
private ProviderUtil() {
// 通过SPI机制进行加载
for (final ClassLoader classLoader : LoaderUtil.getClassLoaders()) {
try {
loadProviders(classLoader);
} catch (final Throwable ex) {
}
}
// 查找log4j-provider.properties文件
for (final LoaderUtil.UrlResource resource :
LoaderUtil.findUrlResources(PROVIDER_RESOURCE)) {
loadProvider(resource.getUrl(), resource.getClassLoader());
}
}
protected static void loadProviders(final ClassLoader classLoader) {
// 通过SPI方式加载Provider
final ServiceLoader<Provider> serviceLoader =
ServiceLoader.load(Provider.class, classLoader);
for (final Provider provider : serviceLoader) {
if (validVersion(provider.getVersions())
&& !PROVIDERS.contains(provider)) {
PROVIDERS.add(provider);
}
}
}
}
- loadProviders的通过ServiceLoader实现SPI的加载制定的Factory。
生成默认Factory
factory = new SimpleLoggerContextFactory();
- 默认的工厂为SimpleLoggerContextFactory。
LoggerContext
LoggerContext 包含了配置信息,并能创建log4j-api定义的Logger接口实例。
public class LogManager {
public static LoggerContext getContext(final boolean currentContext) {
try {
return factory.getContext(FQCN, null, null,
currentContext, null, null);
} catch (final IllegalStateException ex) {
return new SimpleLoggerContextFactory().
getContext(FQCN, null, null, currentContext, null, null);
}
}
- 通过Log4jContextFactory的getContext来获取LoggerContext对象。
public class Log4jContextFactory implements LoggerContextFactory {
private static ContextSelector createContextSelector() {
// 省略其他代码
return new ClassLoaderContextSelector();
}
public LoggerContext getContext(final String fqcn,
final ClassLoader loader,
final Object externalContext,
final boolean currentContext,
final URI configLocation,
final String name) {
// 通过selector=ClassLoaderContextSelector来获取ctx
final LoggerContext ctx =
selector.getContext(fqcn, loader, currentContext, configLocation);
if (externalContext != null && ctx.getExternalContext() == null) {
ctx.setExternalContext(externalContext);
}
if (name != null) {
ctx.setName(name);
}
if (ctx.getState() == LifeCycle.State.INITIALIZED) {
if (configLocation != null || name != null) {
ContextAnchor.THREAD_CONTEXT.set(ctx);
final Configuration config = ConfigurationFactory.getInstance()
.getConfiguration(ctx, name, configLocation);
ctx.start(config);
ContextAnchor.THREAD_CONTEXT.remove();
} else {
ctx.start();
}
}
return ctx;
}
}
- 通过ClassLoaderContextSelector的getContext来获取LoggerContext。
- ctx.start()负责配置的解析,这部分待后续进行分析。
public class ClassLoaderContextSelector
implements ContextSelector, LoggerContextShutdownAware {
public LoggerContext getContext(final String fqcn,
final ClassLoader loader,
final boolean currentContext,
final URI configLocation) {
if (currentContext) {
final LoggerContext ctx = ContextAnchor.THREAD_CONTEXT.get();
if (ctx != null) {
return ctx;
}
return getDefault();
} else if (loader != null) {
return locateContext(loader, configLocation);
} else {
final Class<?> clazz = StackLocatorUtil.getCallerClass(fqcn);
if (clazz != null) {
return locateContext(clazz.getClassLoader(), configLocation);
}
final LoggerContext lc = ContextAnchor.THREAD_CONTEXT.get();
if (lc != null) {
return lc;
}
return getDefault();
}
}
private LoggerContext locateContext(final ClassLoader loaderOrNull,
final URI configLocation) {
final ClassLoader loader = loaderOrNull != null ?
loaderOrNull : ClassLoader.getSystemClassLoader();
final String name = toContextMapKey(loader);
AtomicReference<WeakReference<LoggerContext>> ref =
CONTEXT_MAP.get(name);
if (ref == null) {
if (configLocation == null) {
ClassLoader parent = loader.getParent();
while (parent != null) {
ref = CONTEXT_MAP.get(toContextMapKey(parent));
if (ref != null) {
final WeakReference<LoggerContext> r = ref.get();
final LoggerContext ctx = r.get();
if (ctx != null) {
return ctx;
}
}
parent = parent.getParent();
}
}
LoggerContext ctx = createContext(name, configLocation);
final AtomicReference<WeakReference<LoggerContext>> r =
new AtomicReference<>();
r.set(new WeakReference<>(ctx));
CONTEXT_MAP.putIfAbsent(name, r);
ctx = CONTEXT_MAP.get(name).get().get();
return ctx;
}
final WeakReference<LoggerContext> weakRef = ref.get();
LoggerContext ctx = weakRef.get();
if (ctx != null) {
if (ctx.getConfigLocation() == null && configLocation != null) {
ctx.setConfigLocation(configLocation);
}
return ctx;
}
ctx = createContext(name, configLocation);
ref.compareAndSet(weakRef, new WeakReference<>(ctx));
return ctx;
}
protected LoggerContext createContext(final String name,
final URI configLocation) {
return new LoggerContext(name, null, configLocation);
}
}
- 按照getContext、locateContext、createContext的逻辑创建LoggerContext。
- CONTEXT_MAP通过线程安全的putIfAbsent保存新建的LoggerContext。
- ClassLoaderContextSelector负责LoggerContext的创建。
配置解析过程
- LoggerContext的start过程就是配置文件的解析过程,这部分逻辑后续补充。