31

log4j2源码解析(2)--LoggerContext

 5 years ago
source link: https://bryantchang.github.io/2018/09/15/log4j2-code-2/?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

LoggerContext作用及初始化流程

根据我们在Log4j初识中的实例可以看出,在不适用日志门面插件slf4j的情况下,获取logger的方式一般为

Logger logger = logManager.getLogger(xx.class)

可以看到,通常情况下,我们会使用LogManager的静态方法getLogger方法获取logger,下面我们就简要看一下这个方法的流程。

public static Logger getLogger(final Class<?> clazz) {
    final Class<?> cls = callerClass(clazz);
    return getContext(cls.getClassLoader(), false).getLogger(toLoggerName(cls));
}

这个方法返回了一个链式调用后的结果,而首先是先调用了getContext,这个方法返回的是一个LoggerContext对象,该方法定义如下:

public static LoggerContext getContext(final ClassLoader loader, final boolean currentContext) {
    try {
        return factory.getContext(FQCN, loader, null, currentContext);
    } catch (final IllegalStateException ex) {
        LOGGER.warn(ex.getMessage() + " Using SimpleLogger");
        return new SimpleLoggerContextFactory().getContext(FQCN, loader, null,currentContext);
    }
}

这个方法中的LoggerContext是通过LoggerFactory的getContext方法,默认情况下,对应的子类是Log4jLoggerFactory。对应的方法如下:

public LoggerContext getContext(final String fqcn, final ClassLoader loader, final Object externalContext, final boolean currentContext) {
     //获得LoggerContex实例
    final LoggerContext ctx = selector.getContext(fqcn, loader, currentContext);
    if (externalContext != null && ctx.getExternalContext() == null) {
        ctx.setExternalContext(externalContext);
    }
    //启动LoggerContext
    if (ctx.getState() == LifeCycle.State.INITIALIZED) {
        ctx.start();
    }
    return ctx;
}

其中里面的主要逻辑是返回一个启动的LoggerContext实例,其中LoggerContext实例是通过Log4jLoggerFactory中的ContextSelector获取的,默认情况下使用的是ClassLoaderContextSelector,在Log4jLoggerFactory的构造方法中进行初始化。

完成初始化后,就是LoggerContext的启动,如果当前的context实例的状态为初始化状态,则调用start函数,其中的流程是

LogContext.start()—>LogContext.reconfigure()—>LogContext.reconfigure(URI)

其中主要做的事情就是根据配置文件的位置,读取相应的log4j2配置文件,解析配置文件,最终解析为各种Appender以及Logger的Java对象,下面我们来看大致的流程

首先会获取ConfigurationFactory的实例,然后获取Configuration,分别看这两步

1、获取ConfigurationFactory实例

public static ConfigurationFactory getInstance() {
    // volatile works in Java 1.6+, so double-checked locking also works properly
    //noinspection DoubleCheckedLocking
    if (factories == null) {
        LOCK.lock();
        try {
            if (factories == null) {
                final List<ConfigurationFactory> list = new ArrayList<>();
                final String factoryClass = PropertiesUtil.getProperties().getStringProperty(CONFIGURATION_FACTORY_PROPERTY);
                if (factoryClass != null) {
                    addFactory(list, factoryClass);
                }
                final PluginManager manager = new PluginManager(CATEGORY);
                manager.collectPlugins();
                final Map<String, PluginType<?>> plugins = manager.getPlugins();
                final List<Class<? extends ConfigurationFactory>> ordered = new ArrayList<>(plugins.size());
                for (final PluginType<?> type : plugins.values()) {
                    try {
                        ordered.add(type.getPluginClass().asSubclass(ConfigurationFactory.class));
                    } catch (final Exception ex) {
                        LOGGER.warn("Unable to add class {}", type.getPluginClass(), ex);
                    }
                }
                Collections.sort(ordered, OrderComparator.getInstance());
                for (final Class<? extends ConfigurationFactory> clazz : ordered) {
                    addFactory(list, clazz);
                }
                // see above comments about double-checked locking
                //noinspection NonThreadSafeLazyInitialization
                factories = Collections.unmodifiableList(list);
            }
        } finally {
            LOCK.unlock();
        }
    }

    LOGGER.debug("Using configurationFactory {}", configFactory);
    return configFactory;

这是一个典型的单例模式,使用了双重检查锁的方式获取实例,这个方法里面有一个操作步骤我们要重点介绍,就是PluginManager实例的创建,PluginManager主要用于管理Log4J中的插件,这些Plugin主要是一些对应不同格式的配置文件的解析器,PluginManager中使用category对不同类型的插件进行缓存。

获取到ConfigurationFactory的实例之后,就是获取Configuration的过程,主要流程是根据配置的格式,从遍历上面提到列表中的factory,加载到对应的ConfigurationFactory,随后通过这个Factory去解析配置文件最终获取到Configuration。代码流程如下:

public Configuration getConfiguration(final LoggerContext loggerContext, final String name, final URI configLocation, final ClassLoader loader) {
    if (!isActive()) {
        return null;
    }
    if (loader == null) {
        return getConfiguration(loggerContext, name, configLocation);
    }
    if (isClassLoaderUri(configLocation)) {
        final String path = extractClassLoaderUriPath(configLocation);
        final ConfigurationSource source = ConfigurationSource.fromResource(path,loader);
        if (source != null) {
            final Configuration configuration = getConfiguration(loggerContext,source);
            if (configuration != null) {
                return configuration;
            }
        }
    }
    return getConfiguration(loggerContext, name, configLocation);
}

注意这里的getConfiguration(loggerContext,source);是一个多态方法,会根据不同类型的配置发文件,调用相应的ConfigureFactory的方法,将配置文件中的tag变为java对象。下一篇文章中,我会以xml类型的文件详细介绍将tag变化为Java对象的过程。

谢谢你请我吃糖果


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK