本文接着上一篇 log4j2(一) 获取 ILoggerFactory 继续讲。
2. 获取 Logger
public static Logger getLogger(String name) {
ILoggerFactory iLoggerFactory = getILoggerFactory();
// 这里
// 这里
// 这里
return iLoggerFactory.getLogger(name);
}
我们这里继续以 log4j2 为例探究 Logger 的获取,这里返回的 ILoggerFactory 是 Log4jLoggerFactory
Log4jLoggerFactory 的继承结构

Log4jLoggerFactory#getLogger(String)方法直接使用其父类AbstractLoggerAdapter的实现
@Override
public L getLogger(final String name) {
// 1. 获取并启动 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 -
- 启动 LoggerContext
- 获取ConfigurationFactory(DCL + volatile)
- 获取Configuration,对Configuration的初始化与一系列参数设置,比如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 方法会调用 locateContext 方法(精简修改版)
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. 创建 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. 仅创建 LoggerContext
ctx = createContext(name, configLocation);
ref.compareAndSet(weakRef, new WeakReference<>(ctx));
return ctx;
}
- ClassLoaderContextSelector 内部维护了一个 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);
}
还有一个 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 的构造
public LoggerContext(final String name, final Object externalContext, final URI configLocn) {
this.contextName = name;
this.externalContext = externalContext;
this.configLocation = configLocn;
}
设置了三个属性,这里就把 LoggerContext 创建好了。
2. 启动 LoggerContext
LoggerContext 的 start 方法中最重要的就是要初始化一个 Configuration 出来并放到 LoggerContext 自身中

前面我们说过 LoggerContext 就是一个持有 Configuration 的上下文对象
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 后有一个反塞操作,后面就不多提了。
2.1 获取 ConfigurationFactory
先看下继承关系

看下 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. 返回一个 ConfigurationFactory的 内部类 Factory 的实例对象
LOGGER.debug("Using configurationFactory {}", configFactory);
return configFactory;
}
- 对象 factories 用到了DCL + volatile,第一次在源码中看到这个用法,开心一下
- 这个方法主要就是按顺序填充 factories ,其中在springboot 项目中有五种可用
Configurationfactory | @Order |
---|---|
Propertiesconfigurationfactory | 8 |
Yamlconfigurationfactory | 7 |
Jsonconfigurationfactory | 6 |
Xmlconfigurationfactory | 5 |
Springbootconfigurationfactory | 0 |
- 最后返回的是一个 ConfigurationFactory 的内部类 Factory 的实例对象 configFactory
2.2 获取 Configuration
就是 ConfigurationFactory.Factory#getConfiguration 方法了,分段看下这个方法
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 能通过此轮校验,当configLocation为null时进入下一步,否则进行加载
2、config == null
config = getConfiguration(loggerContext, true, null);
if (config == null) {
config = getConfiguration(loggerContext, false, name);
if (config == null) {
config = getConfiguration(loggerContext, false, null);
}
}
可以看到这步调了三次getConfiguration,只是参数不断调整
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;
}
- 就是在不断调整配置文件名,最后总能调整到 log4j2.yml 然后
factory.getConfiguration(loggerContext, source);
返回了YamlConfiguration
再看看 YamlConfiguration 的初始化,而具体实现是在其父类 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 内部维护了一个Map类型的registry维护LoggerContext和Logger,其中 value 是一个 ConcurrentMap,这个 ConcurrentMap 的 key 为LoggerName(一般是包名),value 就是对应的 Logger 了
由于我们是第一次来取,这里就是将一个空的 ConcurrentHashMap 与 当前 LoggerContext 存入 registry 中,返回 ConcurrentHashMap 的引用
既然没有找到 Logger,当然就要创建了,看下 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);
}
然后就到创建一个 Logger
protected Logger(final LoggerContext context, final String name, final MessageFactory messageFactory) {
super(name, messageFactory);
this.context = context;
privateConfig = new PrivateConfig(context.getConfiguration(), this);
}
继续追下 PrivateConfig 的构造
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 }
- 第四行设置了当前 Logger 日志级别,可以看到是用的第三行获取到的 LoggerConfig
- 第三行根据 name 获取 LoggerConfig,看看是如何实现的
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;
}
一句话概括就是,以“.”对 Logger 名字进行分割,搜索匹配 LoggerConfig 中的 LoggerName,匹配规则为子串从最长到最短,否则返回Root节点。
然后将这个新创建的 Logger 维护到 AbstractLoggerAdapter.registry 属性中,返回此 Logger。
到这里 Logger 就构建好,本文只抓了一条主线进行分析,其他的属性和边边角角的额外处理等碰到具体问题再来深究吧~
饿死,终于可以去煮碗面了。。。
网友评论