系列
簡(jiǎn)介
?Log4j2是apache在Log4j的基礎(chǔ)上,參考logback架構(gòu)實(shí)現(xiàn)的一套新的日志系統(tǒng)。本文基于Log4j-2.12.1的版本進(jìn)行源碼分析惩系。
?本文的目的在于梳理Log4j2的Logger對(duì)象生成過(guò)程讹挎,借此理順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é)點(diǎn)Configuration設(shè)置Log4j2本身的屬性躁锡。
- Appenders設(shè)置多個(gè)Appender對(duì)象午绳,Appender內(nèi)部包含Layout和Filters。
- Loggers設(shè)置Logger對(duì)象映之,包含Logger和Root兩個(gè)子節(jié)點(diǎn)拦焚。
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級(jí)別的信息
logger.trace("log4j2日志輸出:This is trace message.");
// 記錄debug級(jí)別的信息
logger.debug("log4j2日志輸出:This is debug message.");
// 記錄info級(jí)別的信息
logger.info("log4j2日志輸出:This is info message.");
// 記錄error級(jí)別的信息
logger.error("log4j2日志輸出:This is error message.");
}
}
}
- 通過(guò)LogManager.getLogger()來(lái)獲取Logger對(duì)象進(jìn)行日志輸出。
配置文件解析
- 根節(jié)點(diǎn)Configuration有兩個(gè)屬性:status和monitorinterval,有兩個(gè)子節(jié)點(diǎn):Appenders和Loggers(表明可以定義多個(gè)Appender和Logger)杠输。
- status:用來(lái)指定log4j本身的打印日志的級(jí)別赎败。
- monitorinterval:用于指定log4j自動(dòng)重新配置的監(jiān)測(cè)間隔時(shí)間,單位是s,最小是5s抬伺。
- Appenders節(jié)點(diǎn)螟够,常見(jiàn)的有三種子節(jié)點(diǎn):Console、RollingFile峡钓、File妓笙。
-
Console節(jié)點(diǎn)用來(lái)定義輸出到控制臺(tái)的Appender。
- name:指定Appender的名字能岩。
- target:SYSTEM_OUT 或 SYSTEM_ERR,一般只設(shè)置默認(rèn):SYSTEM_OUT寞宫。
- PatternLayout:輸出格式,不設(shè)置默認(rèn)為:%m%n拉鹃。
-
File節(jié)點(diǎn)用來(lái)定義輸出到指定位置的文件的Appender辈赋。
- name:指定Appender的名字膏燕。
- fileName:指定輸出日志的目的文件帶全路徑的文件名。
- PatternLayout:輸出格式坝辫,不設(shè)置默認(rèn)為:%m%n。
-
RollingFile節(jié)點(diǎn)用來(lái)定義超過(guò)指定大小自動(dòng)刪除舊的創(chuàng)建新的Appender竭业。
- name:指定Appender的名字。
- fileName:指定輸出日志的目的文件帶全路徑的文件名未辆。
- PatternLayout:輸出格式,不設(shè)置默認(rèn)為:%m%n咐柜。
- filePattern:指定新建日志文件的名稱(chēng)格式兼蜈。
- Policies:指定滾動(dòng)日志的策略,就是什么時(shí)候進(jìn)行新建日志文件輸出日志炕桨。
- TimeBasedTriggeringPolicy:Policies子節(jié)點(diǎn)饭尝,基于時(shí)間的滾動(dòng)策略,interval屬性用來(lái)指定多久滾動(dòng)一次献宫,默認(rèn)是1 hour钥平。modulate=true用來(lái)調(diào)整時(shí)間:比如現(xiàn)在是早上3am,interval是4姊途,那么第一次滾動(dòng)是在4am涉瘾,接著是8am,12am...而不是7am捷兰。
- SizeBasedTriggeringPolicy:Policies子節(jié)點(diǎn)立叛,基于指定文件大小的滾動(dòng)策略,size屬性用來(lái)定義每個(gè)日志文件的大小贡茅。
- DefaultRolloverStrategy:用來(lái)指定同一個(gè)文件夾下最多有幾個(gè)日志文件時(shí)開(kāi)始刪除最舊的秘蛇,創(chuàng)建新的(通過(guò)max屬性),默認(rèn)是7個(gè)文件。
-
- Loggers節(jié)點(diǎn)顶考,常見(jiàn)的有兩種:Root和Logger赁还。
- Root節(jié)點(diǎn)用來(lái)指定項(xiàng)目的根日志,如果沒(méi)有單獨(dú)指定Logger驹沿,那么就會(huì)默認(rèn)使用該Root日志輸出艘策。
- level:日志輸出級(jí)別,共有8個(gè)級(jí)別渊季,按照從低到高為:All < Trace < Debug < Info < Warn < Error < Fatal < OFF朋蔫。
- AppenderRef:Root的子節(jié)點(diǎn),用來(lái)指定該日志輸出到哪個(gè)Appender却汉。
- Logger節(jié)點(diǎn)用來(lái)單獨(dú)指定日志的形式驯妄,比如要為指定包下的class指定不同的日志級(jí)別等。
- level:日志輸出級(jí)別合砂,共有8個(gè)級(jí)別赎懦,按照從低到高為:All < Trace < Debug < Info < Warn < Error < Fatal < OFF励两。
- name:用來(lái)指定該Logger所適用的類(lèi)或者類(lèi)所在的包全路徑,繼承自Root節(jié)點(diǎn)当悔。
- AppenderRef:Logger的子節(jié)點(diǎn)盲憎,用來(lái)指定該日志輸出到哪個(gè)Appender,如果沒(méi)有指定饼疙,就會(huì)默認(rèn)繼承自Root.如果指定了,那么會(huì)在指定的這個(gè)Appender和Root的Appender中都會(huì)輸出慕爬,此時(shí)我們可以設(shè)置Logger的additivity="false"只在自定義的Appender中進(jìn)行輸出。
- Root節(jié)點(diǎn)用來(lái)指定項(xiàng)目的根日志,如果沒(méi)有單獨(dú)指定Logger驹沿,那么就會(huì)默認(rèn)使用該Root日志輸出艘策。
- 關(guān)于日志level.
- 共有8個(gè)級(jí)別磅甩,按照從低到高為:All < Trace < Debug < Info < Warn < Error < Fatal < OFF卷要。
- All:最低等級(jí)的僧叉,用于打開(kāi)所有日志記錄彪标。
- Trace:是追蹤捞烟,就是程序推進(jìn)以下题画,你就可以寫(xiě)個(gè)trace輸出苍息。
- Debug:指出細(xì)粒度信息事件對(duì)調(diào)試應(yīng)用程序是非常有幫助的表谊。
- Info:消息在粗粒度級(jí)別上突出強(qiáng)調(diào)應(yīng)用程序的運(yùn)行過(guò)程爆办。
- Warn:輸出警告及warn以下級(jí)別的日志距辆。
- Error:輸出錯(cuò)誤信息日志跨算。
- Fatal:輸出每個(gè)嚴(yán)重的錯(cuò)誤事件將會(huì)導(dǎo)致應(yīng)用程序的退出的日志.
- OFF:最高等級(jí)的诸蚕,用于關(guān)閉所有日志記錄挫望。
log4j2核心組件
public class LoggerContext extends AbstractLifeCycle
implements org.apache.logging.log4j.spi.LoggerContext,
AutoCloseable, Terminable, ConfigurationListener,
LoggerContextShutdownEnabled {
// 保存Logger對(duì)象的LoggerRegistry對(duì)象
private final LoggerRegistry<Logger> loggerRegistry =
new LoggerRegistry<>();
// 保存配置的Configuration對(duì)象
private volatile Configuration configuration = new DefaultConfiguration();
}
- LoggerContext通過(guò)LoggerRegistry保存Logger對(duì)象。
public class Logger extends AbstractLogger implements Supplier<LoggerConfig> {
// PrivateConfig包含LoggerConfig
protected volatile PrivateConfig privateConfig;
// LoggerContext對(duì)象
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通過(guò)PrivateConfig來(lái)保存LoggerConfig對(duì)象蛉幸。
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通過(guò)AppenderControlArraySet保存AppenderControl。
- AppenderControl保存Appender匹层。
- LoggerConfig父類(lèi)包含F(xiàn)ilter對(duì)象升筏。
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對(duì)象包含F(xiàn)ilter對(duì)象您访。
- Appender對(duì)象包含Layout對(duì)象檀训。
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對(duì)象峻凫。
- Configuration包含LoggerConfig對(duì)象蔚晨。
- Configuration包含F(xiàn)ilter對(duì)象。
Log4j2初始化流程
LogManager
- LogManager是Log4J啟動(dòng)的入口多糠,后續(xù)的LoggerContext以及Logger都是通過(guò)調(diào)用LogManager的getLogger靜態(tài)方法獲得夹孔。
- LogManager的static代碼塊初始化LoggerContextFactor對(duì)象 搭伤。
public class LogManager {
public static String FACTORY_PROPERTY_NAME = "log4j2.loggerContextFactory";
private static volatile LoggerContextFactory factory;
static {
// step1 通過(guò)特定配置文件的配置信息獲取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 默認(rèn)SimpleLoggerContextFactory
factory = new SimpleLoggerContextFactory();
}
}
}
}
- 1.首先通過(guò)PropertiesUtil.getProperties()根據(jù)特定配置文件的配置信息獲取loggerContextFactory的類(lèi)邓尤。
- 2.如果沒(méi)有找到對(duì)應(yīng)的Factory則通過(guò)ProviderUtil中的getProviders()方法載入providers季稳,通過(guò)provider的loadLoggerContextFactory方法載入LoggerContextFactory的實(shí)現(xiàn)類(lèi)澈魄。
- 3.如果provider中沒(méi)有獲取到LoggerContextFactory的實(shí)現(xiàn)類(lèi)或provider為空铛漓,則使用SimpleLoggerContextFactory作為L(zhǎng)oggerContextFactory票渠。
根據(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));
}
// 查找指定文件內(nèi)容并加載至Properties當(dāng)中
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()負(fù)責(zé)加載log4j2.component.properties文件。
- log4j2.component.properties內(nèi)包含log4j2.loggerContextFactory的配置肠骆。
通過(guò)ProviderUtil加載Factory
public final class ProviderUtil {
static String PROVIDER_RESOURCE = "META-INF/log4j-provider.properties";
private ProviderUtil() {
// 通過(guò)SPI機(jī)制進(jìn)行加載
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) {
// 通過(guò)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的通過(guò)ServiceLoader實(shí)現(xiàn)SPI的加載制定的Factory。
生成默認(rèn)Factory
factory = new SimpleLoggerContextFactory();
- 默認(rèn)的工廠(chǎng)為SimpleLoggerContextFactory莉钙。
LoggerContext
LoggerContext 包含了配置信息磁玉,并能創(chuàng)建log4j-api定義的Logger接口實(shí)例蚊伞。
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);
}
}
- 通過(guò)Log4jContextFactory的getContext來(lái)獲取LoggerContext對(duì)象时迫。
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) {
// 通過(guò)selector=ClassLoaderContextSelector來(lái)獲取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;
}
}
- 通過(guò)ClassLoaderContextSelector的getContext來(lái)獲取LoggerContext。
- ctx.start()負(fù)責(zé)配置的解析碳想,這部分待后續(xù)進(jìn)行分析胧奔。
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通過(guò)線(xiàn)程安全的putIfAbsent保存新建的LoggerContext返吻。
- ClassLoaderContextSelector負(fù)責(zé)LoggerContext的創(chuàng)建街佑。
配置解析過(guò)程
- LoggerContext的start過(guò)程就是配置文件的解析過(guò)程,這部分邏輯后續(xù)補(bǔ)充捍靠。