美文网首页
log4j2(二) 获取 Logger

log4j2(二) 获取 Logger

作者: sunyelw | 来源:发表于2020-04-19 15:15 被阅读0次

本文接着上一篇 log4j2(一) 获取 ILoggerFactory 继续讲。


2. 获取 Logger

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

我们这里继续以 log4j2 为例探究 Logger 的获取,这里返回的 ILoggerFactory 是 Log4jLoggerFactory

Log4jLoggerFactory 的继承结构


Log4jLoggerFactory - inherit

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

大致过程如图:

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 方法会调用 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 - fields

前面我们说过 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

先看下继承关系

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. 返回一个 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 就构建好,本文只抓了一条主线进行分析,其他的属性和边边角角的额外处理等碰到具体问题再来深究吧~


饿死,终于可以去煮碗面了。。。

相关文章

网友评论

      本文标题:log4j2(二) 获取 Logger

      本文链接:https://www.haomeiwen.com/subject/wsivvhtx.html