系列
簡介
?Log4j2是apache在Log4j的基礎上,參考logback架構實現(xiàn)的一套新的日志系統(tǒng)。本文基于Log4j-2.12.1的版本進行源碼分析。
?本文的目的在于梳理Log4j2的Logger對象生成過程,借此理順Log4j2各組件之間的聯(lián)系。
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>
- 根節(jié)點Configuration設置Log4j2本身的屬性。
- Appenders設置多個Appender對象,Appender內部包含Layout和Filters。
- Loggers設置Logger對象,包含Logger和Root兩個子節(jié)點。
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對象進行日志輸出。
配置文件解析
- 根節(jié)點Configuration有兩個屬性:status和monitorinterval,有兩個子節(jié)點:Appenders和Loggers(表明可以定義多個Appender和Logger)。
- status:用來指定log4j本身的打印日志的級別。
- monitorinterval:用于指定log4j自動重新配置的監(jiān)測間隔時間,單位是s,最小是5s。
- Appenders節(jié)點,常見的有三種子節(jié)點:Console、RollingFile、File。
-
Console節(jié)點用來定義輸出到控制臺的Appender。
- name:指定Appender的名字。
- target:SYSTEM_OUT 或 SYSTEM_ERR,一般只設置默認:SYSTEM_OUT。
- PatternLayout:輸出格式,不設置默認為:%m%n。
-
File節(jié)點用來定義輸出到指定位置的文件的Appender。
- name:指定Appender的名字。
- fileName:指定輸出日志的目的文件帶全路徑的文件名。
- PatternLayout:輸出格式,不設置默認為:%m%n。
-
RollingFile節(jié)點用來定義超過指定大小自動刪除舊的創(chuàng)建新的Appender。
- name:指定Appender的名字。
- fileName:指定輸出日志的目的文件帶全路徑的文件名。
- PatternLayout:輸出格式,不設置默認為:%m%n。
- filePattern:指定新建日志文件的名稱格式。
- Policies:指定滾動日志的策略,就是什么時候進行新建日志文件輸出日志。
- TimeBasedTriggeringPolicy:Policies子節(jié)點,基于時間的滾動策略,interval屬性用來指定多久滾動一次,默認是1 hour。modulate=true用來調整時間:比如現(xiàn)在是早上3am,interval是4,那么第一次滾動是在4am,接著是8am,12am...而不是7am。
- SizeBasedTriggeringPolicy:Policies子節(jié)點,基于指定文件大小的滾動策略,size屬性用來定義每個日志文件的大小。
- DefaultRolloverStrategy:用來指定同一個文件夾下最多有幾個日志文件時開始刪除最舊的,創(chuàng)建新的(通過max屬性),默認是7個文件。
-
- Loggers節(jié)點,常見的有兩種:Root和Logger。
- Root節(jié)點用來指定項目的根日志,如果沒有單獨指定Logger,那么就會默認使用該Root日志輸出。
- level:日志輸出級別,共有8個級別,按照從低到高為:All < Trace < Debug < Info < Warn < Error < Fatal < OFF。
- AppenderRef:Root的子節(jié)點,用來指定該日志輸出到哪個Appender。
- Logger節(jié)點用來單獨指定日志的形式,比如要為指定包下的class指定不同的日志級別等。
- level:日志輸出級別,共有8個級別,按照從低到高為:All < Trace < Debug < Info < Warn < Error < Fatal < OFF。
- name:用來指定該Logger所適用的類或者類所在的包全路徑,繼承自Root節(jié)點。
- AppenderRef:Logger的子節(jié)點,用來指定該日志輸出到哪個Appender,如果沒有指定,就會默認繼承自Root.如果指定了,那么會在指定的這個Appender和Root的Appender中都會輸出,此時我們可以設置Logger的additivity="false"只在自定義的Appender中進行輸出。
- Root節(jié)點用來指定項目的根日志,如果沒有單獨指定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父類包含F(xiàn)ilter對象。
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對象包含F(xiàn)ilter對象。
- 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包含F(xiàn)ilter對象。
Log4j2初始化流程

LogManager
- LogManager是Log4J啟動的入口,后續(xù)的LoggerContext以及Logger都是通過調用LogManager的getLogger靜態(tài)方法獲得。
- 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()根據(jù)特定配置文件的配置信息獲取loggerContextFactory的類。
- 2.如果沒有找到對應的Factory則通過ProviderUtil中的getProviders()方法載入providers,通過provider的loadLoggerContextFactory方法載入LoggerContextFactory的實現(xiàn)類。
- 3.如果provider中沒有獲取到LoggerContextFactory的實現(xiàn)類或provider為空,則使用SimpleLoggerContextFactory作為LoggerContextFactory。
根據(jù)配置文件加載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實現(xiàn)SPI的加載制定的Factory。
生成默認Factory
factory = new SimpleLoggerContextFactory();
- 默認的工廠為SimpleLoggerContextFactory。
LoggerContext
LoggerContext 包含了配置信息,并能創(chuàng)建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()負責配置的解析,這部分待后續(xù)進行分析。
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的邏輯創(chuàng)建LoggerContext。
- CONTEXT_MAP通過線程安全的putIfAbsent保存新建的LoggerContext。
- ClassLoaderContextSelector負責LoggerContext的創(chuàng)建。
配置解析過程
- LoggerContext的start過程就是配置文件的解析過程,這部分邏輯后續(xù)補充。