最近的一個項目采用Spring Boot構(gòu)建,使用了log4j2記錄日志泻肯;按照之前的習(xí)慣,通過-Dlog4j.configurationFile指定log4j2.xml路徑,啟動應(yīng)用疲吸,但意外的發(fā)現(xiàn)日志配置竟然沒有生效;
初步估計是Spring Boot對日志部分進(jìn)行了某種修改前鹅,導(dǎo)致了這個問題摘悴,查看Spring Boot的jar包,發(fā)現(xiàn)spring.factories中有這么一段配置:
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener,\
org.springframework.boot.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.logging.LoggingApplicationListener
可以看到有個LoggingApplicationListener:
public class LoggingApplicationListener implements GenericApplicationListener{}
可以看到它會監(jiān)聽Spring的事件舰绘,關(guān)鍵邏輯如下:
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationStartedEvent) {
onApplicationStartedEvent((ApplicationStartedEvent) event);
}
else if (event instanceof ApplicationEnvironmentPreparedEvent) {
onApplicationEnvironmentPreparedEvent(
(ApplicationEnvironmentPreparedEvent) event);
}
else if (event instanceof ApplicationPreparedEvent) {
onApplicationPreparedEvent((ApplicationPreparedEvent) event);
}
else if (event instanceof ContextClosedEvent && ((ContextClosedEvent) event)
.getApplicationContext().getParent() == null) {
onContextClosedEvent();
}
else if (event instanceof ApplicationFailedEvent) {
onApplicationFailedEvent();
}
}
首先看看它的** onApplicationStartedEvent**方法:
private void onApplicationStartedEvent(ApplicationStartedEvent event) {
this.loggingSystem = LoggingSystem
.get(event.getSpringApplication().getClassLoader());
this.loggingSystem.beforeInitialize();
}
public void beforeInitialize() {
LoggerContext loggerContext = getLoggerContext();
if (isAlreadyInitialized(loggerContext)) {
return;
}
super.beforeInitialize();
loggerContext.getConfiguration().addFilter(FILTER);
}
private LoggerContext getLoggerContext() {
return (LoggerContext) LogManager.getContext(false);
}
LogManager.getContext(false)實際上會調(diào)用Log4jContextFactory的getContext方法:
public LoggerContext getContext(final String fqcn, final ClassLoader loader, final Object externalContext,
final boolean currentContext, final URI configLocation, final String name) {
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(name, configLocation);
LOGGER.debug("Starting LoggerContext[name={}] from configuration at {}", ctx.getName(), configLocation);
ctx.start(config);
ContextAnchor.THREAD_CONTEXT.remove();
} else {
ctx.start();
}
}
return ctx;
}
而ConfigurationFactory就會從log4j.configurationFile獲取配置文件路徑執(zhí)行初始化蹂喻;既然已經(jīng)加載了-Dlog4j.configurationFile指定的配置文件,那為什么沒最終生效呢捂寿?繼續(xù)看下去:
private void onApplicationEnvironmentPreparedEvent(
ApplicationEnvironmentPreparedEvent event) {
if (this.loggingSystem == null) {
this.loggingSystem = LoggingSystem
.get(event.getSpringApplication().getClassLoader());
}
initialize(event.getEnvironment(), event.getSpringApplication().getClassLoader());
}
protected void initialize(ConfigurableEnvironment environment,
ClassLoader classLoader) {
new LoggingSystemProperties(environment).apply();
LogFile logFile = LogFile.get(environment);//讀取logging.file和logging.path參數(shù)
if (logFile != null) {
logFile.applyToSystemProperties();//添加系統(tǒng)屬性LOG_PATH口四、LOG_FILE
}
initializeEarlyLoggingLevel(environment); //如果在application.yml或application.properties中定義了debug或trace,則設(shè)置日志級別為DEBUG或TRACE
//關(guān)鍵方法秦陋,調(diào)用LoggingSystem的initialize方法下面單獨(dú)說明
initializeSystem(environment, this.loggingSystem, logFile);
initializeFinalLoggingLevels(environment, this.loggingSystem);
registerShutdownHookIfNecessary(environment, this.loggingSystem);
}
Log4J2LoggingSystem的initialize方法邏輯如下:
public void initialize(LoggingInitializationContext initializationContext,
String configLocation, LogFile logFile) {
LoggerContext loggerContext = getLoggerContext();
if (isAlreadyInitialized(loggerContext)) {
return;
}
loggerContext.getConfiguration().removeFilter(FILTER);
super.initialize(initializationContext, configLocation, logFile);
markAsInitialized(loggerContext);
}
繼續(xù)看super.initialize(initializationContext, configLocation, logFile);實現(xiàn):
public void initialize(LoggingInitializationContext initializationContext,
String configLocation, LogFile logFile) {
if (StringUtils.hasLength(configLocation)) {//如果通過logging.config指定了log4j2配置文件蔓彩,則采用該配置文件
initializeWithSpecificConfig(initializationContext, configLocation, logFile);
return;
}
//采用默認(rèn)配置
initializeWithConventions(initializationContext, logFile);
}
如果配置了logging.config屬性,最終會通過如下代碼重新配置log4j2:
protected void loadConfiguration(String location, LogFile logFile) {
Assert.notNull(location, "Location must not be null");
try {
LoggerContext ctx = getLoggerContext();
URL url = ResourceUtils.getURL(location);
ConfigurationSource source = getConfigurationSource(url);
//關(guān)鍵方法驳概,采用新的配置文件重新配置log4j2
ctx.start(ConfigurationFactory.getInstance().getConfiguration(source));
}
catch (Exception ex) {
throw new IllegalStateException(
"Could not initialize Log4J2 logging from " + location, ex);
}
}
如果沒有指定赤嚼,會調(diào)用initializeWithConventions方法:
private void initializeWithConventions(
LoggingInitializationContext initializationContext, LogFile logFile) {
//根據(jù)支持的格式,在classpath下查找log4j2.yaml顺又、log4j2.yml更卒、log4j2.json、log4j2.jsn和log4j2.xml文件待榔,如果找到則調(diào)用getLoggerContext().reconfigure()重現(xiàn)初始化log4j2,防止某些屬性文件發(fā)生變化
String config = getSelfInitializationConfig();
if (config != null && logFile == null) {
// self initialization has occurred, reinitialize in case of property changes
reinitialize(initializationContext);
return;
}
if (config == null) {//如果找不到上述配置文件逞壁,則在classpath下查找log4j2-spring.yaml、log4j2-spring.yml锐锣、log4j2-spring.json腌闯、log4j2-spring.jsn和log4j2-spring.xml文件
config = getSpringInitializationConfig();
}
if (config != null) {//如果找到以spring結(jié)尾的配置文件,調(diào)用ctx.start重新配置log4j2
loadConfiguration(initializationContext, config, logFile);
return;
}
//如果前面的查找都失敗了雕憔,則加載默認(rèn)配置姿骏;如果定義了log日志文件路徑,則加載log4j2-file.xml斤彼,否則加載log4j2.xml
loadDefaults(initializationContext, logFile);
}
在Spring boot jar包的org.springframework.boot.logging.log4j2包下面分瘦,有兩個配置文件log4j2.xml和log4j2-file.xml,這是默認(rèn)的log4j2配置文件蘸泻,文件內(nèi)容如下:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Properties>
<Property name="PID">????</Property>
<Property name="LOG_EXCEPTION_CONVERSION_WORD">%xwEx</Property>
<Property name="LOG_LEVEL_PATTERN">%5p</Property>
<Property name="LOG_PATTERN">%clr{%d{yyyy-MM-dd HH:mm:ss.SSS}}{faint} %clr{${LOG_LEVEL_PATTERN}} %clr{${sys:PID}}{magenta} %clr{---}{faint} %clr{[%15.15t]}{faint} %clr{%-40.40c{1.}}{cyan} %clr{:}{faint} %m%n${sys:LOG_EXCEPTION_CONVERSION_WORD}</Property>
</Properties>
<Appenders>
<Console name="Console" target="SYSTEM_OUT" follow="true">
<PatternLayout pattern="${LOG_PATTERN}" />
</Console>
</Appenders>
<Loggers>
<Logger name="org.apache.catalina.startup.DigesterFactory" level="error" />
<Logger name="org.apache.catalina.util.LifecycleBase" level="error" />
<Logger name="org.apache.coyote.http11.Http11NioProtocol" level="warn" />
<logger name="org.apache.sshd.common.util.SecurityUtils" level="warn"/>
<Logger name="org.apache.tomcat.util.net.NioSelectorPool" level="warn" />
<Logger name="org.crsh.plugin" level="warn" />
<logger name="org.crsh.ssh" level="warn"/>
<Logger name="org.eclipse.jetty.util.component.AbstractLifeCycle" level="error" />
<Logger name="org.hibernate.validator.internal.util.Version" level="warn" />
<logger name="org.springframework.boot.actuate.autoconfigure.CrshAutoConfiguration" level="warn"/>
<logger name="org.springframework.boot.actuate.endpoint.jmx" level="warn"/>
<logger name="org.thymeleaf" level="warn"/>
<Root level="info">
<AppenderRef ref="Console" />
</Root>
</Loggers>
</Configuration>
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Properties>
<Property name="PID">????</Property>
<Property name="LOG_EXCEPTION_CONVERSION_WORD">%xwEx</Property>
<Property name="LOG_LEVEL_PATTERN">%5p</Property>
<Property name="LOG_PATTERN">%d{yyyy-MM-dd HH:mm:ss.SSS} ${LOG_LEVEL_PATTERN} ${sys:PID} --- [%t] %-40.40c{1.} : %m%n${sys:LOG_EXCEPTION_CONVERSION_WORD}</Property>
</Properties>
<Appenders>
<Console name="Console" target="SYSTEM_OUT" follow="true">
<PatternLayout pattern="${LOG_PATTERN}" />
</Console>
<RollingFile name="File" fileName="${sys:LOG_FILE}" filePattern="logs/$${date:yyyy-MM}/app-%d{yyyy-MM-dd-HH}-%i.log.gz">
<PatternLayout>
<Pattern>${LOG_PATTERN}</Pattern>
</PatternLayout>
<Policies>
<SizeBasedTriggeringPolicy size="10 MB" />
</Policies>
</RollingFile>
</Appenders>
<Loggers>
<Logger name="org.apache.catalina.startup.DigesterFactory" level="error" />
<Logger name="org.apache.catalina.util.LifecycleBase" level="error" />
<Logger name="org.apache.coyote.http11.Http11NioProtocol" level="warn" />
<logger name="org.apache.sshd.common.util.SecurityUtils" level="warn"/>
<Logger name="org.apache.tomcat.util.net.NioSelectorPool" level="warn" />
<Logger name="org.crsh.plugin" level="warn" />
<logger name="org.crsh.ssh" level="warn"/>
<Logger name="org.eclipse.jetty.util.component.AbstractLifeCycle" level="error" />
<Logger name="org.hibernate.validator.internal.util.Version" level="warn" />
<logger name="org.springframework.boot.actuate.autoconfigure.CrshAutoConfiguration" level="warn"/>
<logger name="org.springframework.boot.actuate.endpoint.jmx" level="warn"/>
<logger name="org.thymeleaf" level="warn"/>
<Root level="info">
<AppenderRef ref="Console" />
<AppenderRef ref="File" />
</Root>
</Loggers>
</Configuration>