美文网首页
spring boot log4j2配置不生效

spring boot log4j2配置不生效

作者: allanYan | 来源:发表于2017-03-28 22:13 被阅读0次

    最近的一个项目采用Spring Boot构建,使用了log4j2记录日志;按照之前的习惯,通过-Dlog4j.configurationFile指定log4j2.xml路径,启动应用,但意外的发现日志配置竟然没有生效;

    初步估计是Spring Boot对日志部分进行了某种修改,导致了这个问题,查看Spring Boot的jar包,发现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{}
    

    可以看到它会监听Spring的事件,关键逻辑如下:

    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)实际上会调用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获取配置文件路径执行初始化;既然已经加载了-Dlog4j.configurationFile指定的配置文件,那为什么没最终生效呢?继续看下去:

    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参数
        if (logFile != null) {
            logFile.applyToSystemProperties();//添加系统属性LOG_PATH、LOG_FILE
        }
        initializeEarlyLoggingLevel(environment); //如果在application.yml或application.properties中定义了debug或trace,则设置日志级别为DEBUG或TRACE
           //关键方法,调用LoggingSystem的initialize方法下面单独说明
        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);
    }
    

    继续看super.initialize(initializationContext, configLocation, logFile);实现:

    public void initialize(LoggingInitializationContext initializationContext,
            String configLocation, LogFile logFile) {
        if (StringUtils.hasLength(configLocation)) {//如果通过logging.config指定了log4j2配置文件,则采用该配置文件
            initializeWithSpecificConfig(initializationContext, configLocation, logFile);
            return;
        }
           //采用默认配置
        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);
    
              //关键方法,采用新的配置文件重新配置log4j2
                ctx.start(ConfigurationFactory.getInstance().getConfiguration(source));
            }
            catch (Exception ex) {
                throw new IllegalStateException(
                        "Could not initialize Log4J2 logging from " + location, ex);
            }
        }
    

    如果没有指定,会调用initializeWithConventions方法:

    private void initializeWithConventions(
            LoggingInitializationContext initializationContext, LogFile logFile) {
           //根据支持的格式,在classpath下查找log4j2.yaml、log4j2.yml、log4j2.json、log4j2.jsn和log4j2.xml文件,如果找到则调用getLoggerContext().reconfigure()重现初始化log4j2,防止某些属性文件发生变化
        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结尾的配置文件,调用ctx.start重新配置log4j2
            loadConfiguration(initializationContext, config, logFile);
            return;
        }
           //如果前面的查找都失败了,则加载默认配置;如果定义了log日志文件路径,则加载log4j2-file.xml,否则加载log4j2.xml
        loadDefaults(initializationContext, logFile);
    }
    

    在Spring boot jar包的org.springframework.boot.logging.log4j2包下面,有两个配置文件log4j2.xml和log4j2-file.xml,这是默认的log4j2配置文件,文件内容如下:

    <?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>
    
    

    相关文章

      网友评论

          本文标题:spring boot log4j2配置不生效

          本文链接:https://www.haomeiwen.com/subject/gmssottx.html