本文接著上一篇 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#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
大致過程如圖:

這里直接看 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 就是一個(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)系

看下 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)行分析,其他的屬性和邊邊角角的額外處理等碰到具體問題再來深究吧~
餓死,終于可以去煮碗面了。。。