log4j2(二) 獲取 Logger

本文接著上一篇 log4j2(一) 獲取 ILoggerFactory 繼續(xù)講。


2. 獲取 Logger

public static Logger getLogger(String name) {
    ILoggerFactory iLoggerFactory = getILoggerFactory();
    // 這里
    // 這里
    // 這里    
    return iLoggerFactory.getLogger(name);
}

我們這里繼續(xù)以 log4j2 為例探究 Logger 的獲取,這里返回的 ILoggerFactory 是 Log4jLoggerFactory

Log4jLoggerFactory 的繼承結(jié)構(gòu)


Log4jLoggerFactory - inherit

Log4jLoggerFactory#getLogger(String)方法直接使用其父類AbstractLoggerAdapter的實(shí)現(xiàn)

@Override
public L getLogger(final String name) {
    // 1. 獲取并啟動(dòng) LoggerContext 
    final LoggerContext context = getContext();
    // 2. 獲取 Logger
    final ConcurrentMap<String, L> loggers = getLoggersInContext(context);
    final L logger = loggers.get(name);
    if (logger != null) {
        return logger;
    }
    loggers.putIfAbsent(name, newLogger(name, context));
    return loggers.get(name);
}

Logger的第一次 獲取大致分以下幾步

  • 獲取 LoggerContext
    • LogManage - LoggerContextFactory - ClassLoaderContextSelector -
  • 啟動(dòng) LoggerContext
    • 獲取ConfigurationFactory(DCL + volatile)
    • 獲取Configuration,對(duì)Configuration的初始化與一系列參數(shù)設(shè)置,比如Logger、Appender、Filter等
  • 獲取 Logger

下面分別一步步來過下

1. 獲取 LoggerContext

大致過程如圖:

getContext - flow

這里直接看 Log4jContextFactory#getContext方法

private final ContextSelector selector; // ClassLoaderContextSelector

@Override
public LoggerContext getContext(final String fqcn, final ClassLoader loader, final Object externalContext,
                                final boolean currentContext) {
    final LoggerContext ctx = selector.getContext(fqcn, loader, currentContext);
    if (externalContext != null && ctx.getExternalContext() == null) {
        ctx.setExternalContext(externalContext);
    }
    if (ctx.getState() == LifeCycle.State.INITIALIZED) {
        ctx.start();
    }
    return ctx;
}
  • selector 為 ClassLoaderContextSelector
    ClassLoaderContextSelector#getContext 方法會(huì)調(diào)用 locateContext 方法(精簡(jiǎn)修改版)
protected static final ConcurrentMap<String, AtomicReference<WeakReference<LoggerContext>>> CONTEXT_MAP = new ConcurrentHashMap<>();

private LoggerContext locateContext(final ClassLoader loaderOrNull, final URI configLocation) {
    // 0. 存在直接獲取
    AtomicReference<WeakReference<LoggerContext>> ref = CONTEXT_MAP.get(name);
    final WeakReference<LoggerContext> weakRef = ref.get();
    LoggerContext ctx = weakRef.get();
    ...
    // 1. 創(chuàng)建 AtomicReference + WeakReference + LoggerContext
    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;
    ...
    // 2. 僅創(chuàng)建 LoggerContext
    ctx = createContext(name, configLocation);
    ref.compareAndSet(weakRef, new WeakReference<>(ctx));
    return ctx;
}
  • ClassLoaderContextSelector 內(nèi)部維護(hù)了一個(gè) ConcurrentMap 類型的 CONTEXT_MAP
    • key: toContextMapKey(loader); 類加載器的 identityHashCode
    • value: LoggerContext 的弱引用+原子引用(CAS)
    • 可見 LoggerContext 與類加載器直接掛鉤
  • 需要注意的是,取 LoggerContext 的過程中還涉及到 ContextAnchor 的
    ThreadLocal 屬性 THREAD_CONTEXT 的取/塞

再看下 ClassLoaderContextSelector#createContext 方法

protected LoggerContext createContext(final String name, final URI configLocation) {
    return new LoggerContext(name, null, configLocation);
}

還有一個(gè) default 方法

private static final AtomicReference<LoggerContext> DEFAULT_CONTEXT = new AtomicReference<>();

protected LoggerContext getDefault() {
    final LoggerContext ctx = DEFAULT_CONTEXT.get();
    if (ctx != null) {
        return ctx;
    }
    DEFAULT_CONTEXT.compareAndSet(null, createContext(defaultContextName(), null));
    return DEFAULT_CONTEXT.get();
}

LoggerContext 的構(gòu)造

public LoggerContext(final String name, final Object externalContext, final URI configLocn) {
    this.contextName = name;
    this.externalContext = externalContext;
    this.configLocation = configLocn;
}

設(shè)置了三個(gè)屬性,這里就把 LoggerContext 創(chuàng)建好了。

2. 啟動(dòng) LoggerContext

LoggerContext 的 start 方法中最重要的就是要初始化一個(gè) Configuration 出來并放到 LoggerContext 自身中

LoggerContext - fields

前面我們說過 LoggerContext 就是一個(gè)持有 Configuration 的上下文對(duì)象

public void start() {
    if (configLock.tryLock()) {
        try {
            reconfigure();
        } finally {
            configLock.unlock();
        }
    }
}

public void reconfigure() {
    reconfigure(configLocation);
}

private void reconfigure(final URI configURI) {
    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);
    }
}

這里獲取到 Configuration 后有一個(gè)反塞操作,后面就不多提了。

2.1 獲取 ConfigurationFactory

先看下繼承關(guān)系

org.apache.logging.log4j.core.config.ConfigurationFactory - inherit

看下 ConfigurationFactory.getInstance() 方法

private static ConfigurationFactory configFactory = new Factory();
private static volatile List<ConfigurationFactory> factories = null;

public static ConfigurationFactory getInstance() {
    if (factories == null) {
        LOCK.lock();
        try {
            if (factories == null) {
                final List<ConfigurationFactory> list = new ArrayList<>();
                ...
                // 1. 拿到支持的插件類型列表 plugins, 并按 @Order 注解排序
                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);
                }
                factories = Collections.unmodifiableList(list);
            }
        } finally {
            LOCK.unlock();
        }
    }
    // 2. 返回一個(gè) ConfigurationFactory的 內(nèi)部類 Factory 的實(shí)例對(duì)象
    LOGGER.debug("Using configurationFactory {}", configFactory);
    return configFactory;
}
  • 對(duì)象 factories 用到了DCL + volatile,第一次在源碼中看到這個(gè)用法,開心一下
  • 這個(gè)方法主要就是按順序填充 factories ,其中在springboot 項(xiàng)目中有五種可用
Configurationfactory @Order
Propertiesconfigurationfactory 8
Yamlconfigurationfactory 7
Jsonconfigurationfactory 6
Xmlconfigurationfactory 5
Springbootconfigurationfactory 0
  • 最后返回的是一個(gè) ConfigurationFactory 的內(nèi)部類 Factory 的實(shí)例對(duì)象 configFactory
2.2 獲取 Configuration

就是 ConfigurationFactory.Factory#getConfiguration 方法了,分段看下這個(gè)方法
1、configLocation == null && configLocationStr == null

private static final String ALL_TYPES = "*";

for (final ConfigurationFactory factory : getFactories()) {
    final String[] types = factory.getSupportedTypes();
    if (types != null) {
        for (final String type : types) {
            if (type.equals(ALL_TYPES)) {
                final Configuration config = factory.getConfiguration(loggerContext, name, configLocation);
                if (config != null) {
                    return config;
                }
            }
        }
    }
}

按順序遍歷factories,getSupportedTypes方法用于取指定ConfigurationFactory指定后綴,比如
YamlConfigurationFactory: {".yml", ".yaml"}
XmlConfigurationFactory: {".xml", "*"}

而ALL_TYPES = "*",故只有 XmlConfigurationFactory 能通過此輪校驗(yàn),當(dāng)configLocation為null時(shí)進(jìn)入下一步,否則進(jìn)行加載

2、config == null

config = getConfiguration(loggerContext, true, null);
if (config == null) {
    config = getConfiguration(loggerContext, false, name);
    if (config == null) {
        config = getConfiguration(loggerContext, false, null);
    }
}

可以看到這步調(diào)了三次getConfiguration,只是參數(shù)不斷調(diào)整

private Configuration getConfiguration(final LoggerContext loggerContext, final boolean isTest, final String name) {
    final boolean named = Strings.isNotEmpty(name);
    final ClassLoader loader = LoaderUtil.getThreadContextClassLoader();
    for (final ConfigurationFactory factory : getFactories()) {
        String configName;
        final String prefix = isTest ? TEST_PREFIX : DEFAULT_PREFIX;
        final String [] types = factory.getSupportedTypes();
        if (types == null) {
            continue;
        }

        for (final String suffix : types) {
            if (suffix.equals(ALL_TYPES)) {
                continue;
            }
            configName = named ? prefix + name + suffix : prefix + suffix;

            final ConfigurationSource source = getInputFromResource(configName, loader);
            if (source != null) {
                return factory.getConfiguration(loggerContext, source);
            }
        }
    }
    return null;
}
  • 就是在不斷調(diào)整配置文件名,最后總能調(diào)整到 log4j2.yml 然后factory.getConfiguration(loggerContext, source);返回了 YamlConfiguration

再看看 YamlConfiguration 的初始化,而具體實(shí)現(xiàn)是在其父類 JsonConfiguration 中

public JsonConfiguration(final LoggerContext loggerContext, final ConfigurationSource configSource) {
    super(loggerContext, configSource);
    final File configFile = configSource.getFile();
    byte[] buffer;
    try {
        try (final InputStream configStream = configSource.getInputStream()) {
            buffer = toByteArray(configStream);
        }
        final InputStream is = new ByteArrayInputStream(buffer);
        root = getObjectMapper().readTree(is);
        if (root.size() == 1) {
            for (final JsonNode node : root) {
                root = node;
            }
        }
        processAttributes(rootNode, root);
        final StatusConfiguration statusConfig = new StatusConfiguration().withVerboseClasses(VERBOSE_CLASSES)
                .withStatus(getDefaultStatus());
        for (final Map.Entry<String, String> entry : rootNode.getAttributes().entrySet()) {
            final String key = entry.getKey();
            final String value = getStrSubstitutor().replace(entry.getValue());
            // TODO: this duplicates a lot of the XmlConfiguration constructor
            if ("status".equalsIgnoreCase(key)) {
                statusConfig.withStatus(value);
            } else if ("dest".equalsIgnoreCase(key)) {
                statusConfig.withDestination(value);
            } else if ("shutdownHook".equalsIgnoreCase(key)) {
                isShutdownHookEnabled = !"disable".equalsIgnoreCase(value);
            } else if ("verbose".equalsIgnoreCase(entry.getKey())) {
                statusConfig.withVerbosity(value);
            } else if ("packages".equalsIgnoreCase(key)) {
                pluginPackages.addAll(Arrays.asList(value.split(Patterns.COMMA_SEPARATOR)));
            } else if ("name".equalsIgnoreCase(key)) {
                setName(value);
            } else if ("monitorInterval".equalsIgnoreCase(key)) {
                final int intervalSeconds = Integer.parseInt(value);
                if (intervalSeconds > 0) {
                    getWatchManager().setIntervalSeconds(intervalSeconds);
                    if (configFile != null) {
                        final FileWatcher watcher = new ConfiguratonFileWatcher(this, listeners);
                        getWatchManager().watchFile(configFile, watcher);
                    }
                }
            } else if ("advertiser".equalsIgnoreCase(key)) {
                createAdvertiser(value, configSource, buffer, "application/json");
            }
        }
        statusConfig.initialize();
        if (getName() == null) {
            setName(configSource.getLocation());
        }
    } catch (final Exception ex) {
        LOGGER.error("Error parsing " + configSource.getLocation(), ex);
    }
}
  • 大致過一眼,都是 Configuration 的配置屬性解析,這里就不展開了

3. 獲取 Logger

在看下之前的代碼

@Override
public L getLogger(final String name) {
    final LoggerContext context = getContext();
    final ConcurrentMap<String, L> loggers = getLoggersInContext(context);
    final L logger = loggers.get(name);
    if (logger != null) {
        return logger;
    }
    loggers.putIfAbsent(name, newLogger(name, context));
    return loggers.get(name);
}
  • AbstractLoggerAdapter#getLoggersInContext 方法
protected final Map<LoggerContext, ConcurrentMap<String, L>> registry = new WeakHashMap<>();

public ConcurrentMap<String, L> getLoggersInContext(final LoggerContext context) {
    synchronized (registry) {
        ConcurrentMap<String, L> loggers = registry.get(context);
        if (loggers == null) {
            loggers = new ConcurrentHashMap<>();
            registry.put(context, loggers);
        }
        return loggers;
    }
}
  • AbstractLoggerAdapter 內(nèi)部維護(hù)了一個(gè)Map類型的registry維護(hù)LoggerContext和Logger,其中 value 是一個(gè) ConcurrentMap,這個(gè) ConcurrentMap 的 key 為L(zhǎng)oggerName(一般是包名),value 就是對(duì)應(yīng)的 Logger 了

由于我們是第一次來取,這里就是將一個(gè)空的 ConcurrentHashMap 與 當(dāng)前 LoggerContext 存入 registry 中,返回 ConcurrentHashMap 的引用

既然沒有找到 Logger,當(dāng)然就要?jiǎng)?chuàng)建了,看下 AbstractLoggerAdapter#newLogger 方法

@Override
protected Logger newLogger(final String name, final LoggerContext context) {
    final String key = Logger.ROOT_LOGGER_NAME.equals(name) ? LogManager.ROOT_LOGGER_NAME : name;
    return new Log4jLogger(context.getLogger(key), name);
}

然后就到創(chuàng)建一個(gè) Logger

protected Logger(final LoggerContext context, final String name, final MessageFactory messageFactory) {
    super(name, messageFactory);
    this.context = context;
    privateConfig = new PrivateConfig(context.getConfiguration(), this);
}

繼續(xù)追下 PrivateConfig 的構(gòu)造

1 public PrivateConfig(final Configuration config, final Logger logger) {
2    this.config = config;
3    this.loggerConfig = config.getLoggerConfig(getName());
4    this.loggerConfigLevel = this.loggerConfig.getLevel();
5    this.intLevel = this.loggerConfigLevel.intLevel();
6    this.logger = logger;
7 }
  • 第四行設(shè)置了當(dāng)前 Logger 日志級(jí)別,可以看到是用的第三行獲取到的 LoggerConfig
  • 第三行根據(jù) name 獲取 LoggerConfig,看看是如何實(shí)現(xiàn)的
public LoggerConfig getLoggerConfig(final String loggerName) {
    LoggerConfig loggerConfig = loggerConfigs.get(loggerName);
    if (loggerConfig != null) {
        return loggerConfig;
    }
    String substr = loggerName;
    while ((substr = NameUtil.getSubName(substr)) != null) {
        loggerConfig = loggerConfigs.get(substr);
        if (loggerConfig != null) {
            return loggerConfig;
        }
    }
    return root;
}

public static String getSubName(final String name) {
    if (Strings.isEmpty(name)) {
        return null;
    }
    final int i = name.lastIndexOf('.');
    return i > 0 ? name.substring(0, i) : Strings.EMPTY;
}

一句話概括就是,以“.”對(duì) Logger 名字進(jìn)行分割,搜索匹配 LoggerConfig 中的 LoggerName,匹配規(guī)則為子串從最長(zhǎng)到最短,否則返回Root節(jié)點(diǎn)。

然后將這個(gè)新創(chuàng)建的 Logger 維護(hù)到 AbstractLoggerAdapter.registry 屬性中,返回此 Logger。

到這里 Logger 就構(gòu)建好,本文只抓了一條主線進(jìn)行分析,其他的屬性和邊邊角角的額外處理等碰到具體問題再來深究吧~


餓死,終于可以去煮碗面了。。。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 關(guān)于log4j2的初始化流程,現(xiàn)在項(xiàng)目基本都是springboot項(xiàng)目,就需要結(jié)合 springboot 源碼來解...
    sunyelw閱讀 3,114評(píng)論 0 0
  • 一、log4j2簡(jiǎn)介 log4j2是log4j 1.x和logback的改進(jìn)版,據(jù)說采用了一些新技術(shù)(無(wú)鎖異步、等...
    菜鳥楊閱讀 561評(píng)論 0 0
  • 官網(wǎng)http://logging.apache.org/log4j/2.x/index.html 理論篇 參考:l...
    HansenGuan閱讀 1,100評(píng)論 0 1
  • 架構(gòu) 架構(gòu) 主要組件 Log4J類結(jié)構(gòu)如下圖所示: 使用Log4j 2 API的應(yīng)用程序?qū)腖ogManager請(qǐng)...
    在下喵星人閱讀 2,304評(píng)論 0 0
  • 人總是感性動(dòng)物,會(huì)根據(jù)心情看待事物。昨天一天里,對(duì)女兒發(fā)了幾次脾氣,到了晚上才后悔了,不應(yīng)該讓女兒承受這一切。 雖...
    蓮子育孩閱讀 153評(píng)論 2 4

友情鏈接更多精彩內(nèi)容