Log4J2源码系列(九) - 没有配置文件也能打印日志? 默认配置怎么创建的?

x33g5p2x  于2021-12-25 转载在 其他  
字(4.8k)|赞(0)|评价(0)|浏览(573)

之前分析过Log4J2是怎么加载我们应用程序中的配置文件的,参见Log4J2(五) - 配置文件是怎么获取的? . 但是当我们没有配置文件的时候,你会发现控制台会打印错误,这个时候你有没有想过一个问题:为什么没有配置好日志系统,它也能打印日志呢?
因为它在加载自定义的配置之前新建了一套默认配置。

ps:这里说的打印日志是指的应用程序打印日志,不是只Log4J2本身的日志,它本身的日志是由StatusLogger打印的,这个后续有时间再分析。

创建时机

跟NullConguration一样, 是在LoggerContext的构造方法执行之前创建的。

private volatile Configuration configuration = new DefaultConfiguration();

使用时机

当所有的配置方式都没有找到配置时,就会使用默认配置。

代码分析

  • 构造方法
public DefaultConfiguration() {
    	//调用父类AbstractConfiguration的构造方法
        super(null, ConfigurationSource.NULL_SOURCE);
        //调用父类AbstractConfiguration的setToDefault方法,给rootLogger做默认配置
        setToDefault();
    }
  • 来看看父类的构造方法
private final ConcurrentMap<String, String> properties = new ConcurrentHashMap<>();
  // 这就是上面提到的rootLogger,其实就是个空壳 
  private LoggerConfig root = new LoggerConfig();

  protected AbstractConfiguration(final LoggerContext loggerContext, final ConfigurationSource configurationSource) {
  		//保存loggerContext的一个弱引用
        this.loggerContext = new WeakReference<>(loggerContext);
        this.configurationSource = Objects.requireNonNull(configurationSource, "configurationSource is null");
        //如上面的代码,初始存放的是一个空map
        componentMap.put(Configuration.CONTEXT_PROPERTIES, properties);
        //新建一个插件管理器
        pluginManager = new PluginManager(Node.CATEGORY);
        //创建一个空的根节点
        rootNode = new Node();
        //设置配置状态开启初始化
        setState(State.INITIALIZING);
    }
  • AbstractConfiguration 的setToDefault方法, 也就是默认配置的具体创建过程
protected void setToDefault() {
        // LOG4J2-1176 facilitate memory leak investigation
        setName(DefaultConfiguration.DEFAULT_NAME + "@" + Integer.toHexString(hashCode()));
        //初始化输出的消息样式
        //默认格式:%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
        final Layout<? extends Serializable> layout = PatternLayout.newBuilder()
             .withPattern(DefaultConfiguration.DEFAULT_PATTERN)
             .withConfiguration(this)
             .build();
        //默认appender为控制台
        final Appender appender = ConsoleAppender.createDefaultAppenderForLayout(layout);
        appender.start();
        addAppender(appender);
        //初始化logger,即 new LoggerConfig()
        final LoggerConfig rootLoggerConfig = getRootLogger();
        rootLoggerConfig.addAppender(appender, null, null);

		//设置默认日志等级为ERROR
        final Level defaultLevel = Level.ERROR;
        final String levelName = PropertiesUtil.getProperties().getStringProperty(DefaultConfiguration.DEFAULT_LEVEL,
                defaultLevel.name());
        final Level level = Level.valueOf(levelName);
        rootLoggerConfig.setLevel(level != null ? level : defaultLevel);
    }

默认配置怎么样被自定义配置所取代呢?

通过reconfigure方法, 即重新配置

public void reconfigure() {
        reconfigure(configLocation);
    }
	
	private void reconfigure(final URI configURI) {
		//外部类加载器,正常启动不经过额外配置的话,这里是null
        final ClassLoader cl = ClassLoader.class.isInstance(externalContext) ? (ClassLoader) externalContext : null;
        LOGGER.debug("Reconfiguration started for context[name={}] at URI {} ({}) with optional ClassLoader: {}",
                contextName, configURI, this, cl);
        //通过配置工厂获取对应配置,这里就是获取自定义配置资源的入口了
        final Configuration instance = ConfigurationFactory.getInstance().getConfiguration(this, contextName, configURI, cl);
        if (instance == null) {
            LOGGER.error("Reconfiguration failed: No configuration found for '{}' at '{}' in '{}'", contextName, configURI, cl);
        } else {
        	//将获取到配置替换掉默认配置
            setConfiguration(instance);
            final String location = configuration == null ? "?" : String.valueOf(configuration.getConfigurationSource());
            LOGGER.debug("Reconfiguration complete for context[name={}] at URI {} ({}) with optional ClassLoader: {}",
                    contextName, location, this, cl);
        }
    }
  • setConfiguration方法:
private Configuration setConfiguration(final Configuration config) {
        if (config == null) {
            LOGGER.error("No configuration found for context '{}'.", contextName);
            // No change, return the current configuration.
            return this.configuration;
        }
        //修改配置时,要上锁
        configLock.lock();
        try {
            final Configuration prev = this.configuration;
            //设置监听器,用于热更新。
            //热更新的文章可以参考之前的文章https://blog.csdn.net/sweetyi/article/details/105038162
            config.addListener(this);
            final ConcurrentMap<String, String> map = config.getComponent(Configuration.CONTEXT_PROPERTIES);
			//设置属性
            try { // LOG4J2-719 network access may throw android.os.NetworkOnMainThreadException
                map.putIfAbsent("hostName", NetUtils.getLocalHostname());
            } catch (final Exception ex) {
                LOGGER.debug("Ignoring {}, setting hostName to 'unknown'", ex.toString());
                map.putIfAbsent("hostName", "unknown");
            }
            map.putIfAbsent("contextName", contextName);
            //启动配置
            config.start();
            this.configuration = config;
            
            updateLoggers();
            //停掉之前的config,移除对其变更的监控
            if (prev != null) {
                prev.removeListener(this);
                prev.stop();
            }
			//触发propertyChange事件,通知相关方做处理
            firePropertyChangeEvent(new PropertyChangeEvent(this, PROPERTY_CONFIG, prev, config));
            
			//注册mbean,供jmx监测使用
            try {
                Server.reregisterMBeansAfterReconfigure();
            } catch (final LinkageError | Exception e) {
                // LOG4J2-716: Android has no java.lang.management
                LOGGER.error("Could not reconfigure JMX", e);
            }
            // AsyncLoggers update their nanoClock when the configuration changes
            Log4jLogEvent.setNanoClock(configuration.getNanoClock());

            return prev;
        } finally {
        	//工作完成,释放锁
            configLock.unlock();
        }

看完setConfiguration的方法可能有点记不住,下面画个简单的流程图以供理解:

NYstart配置是否为nullend设置文件监听器设置上下文属性启动新配置停掉原来的配置并移除监听器注册mbean

小结

本文从源码的角度分析了默认配置创建的时机,作用 以及默认配置是如何配置的,分析了默认配置被自定义配置替换的时机。

相关文章