2021-03-30

log4j2工作原理源码剖析

前言

本文建立在log4j-core 2.12.1版本为基础,在此基础上进行的源码剖析
Log4j2的配置可以通过以下四种方式之一完成

  1. 通过以
  2. 通过创建ConfigurationFactory和Configuration以编程方式实现
  3. 通过调用配置界面中公开的API,以编程方式将组建添加到默认配置
  4. 通过调用内部Logger类上的方法以编程实现。

本文的执行过程建立在由于本人技术尚浅,故有错误的地方,希望各位不吝赐教。

1.基础名词解释

Named Hierarchy

在Log4j1.x中,通过Logger之间的关系维护Logger层次结构。在Log4j2中,此关系不再存在。而是在LoggerConfig对象之间的关系中维护层次结构。
Logger和LoggerConfigs是命名实体。Logger名称区分大小写,并且遵循分层命名规则 ->Named Hierarchy

如果LoggerConfig的名称后跟一个点,则该LoggerConfig被称为另一个 LoggerConfig 的祖先。
如果LoggerConfig与子LoggerConfig之间没有祖先,则称该LoggerConfig为子LoggerConfig的父级。

例如,名为" com.foo"的 LoggerConfig 是名为" com.foo.Bar"的 LoggerConfig 的父级。
同样," java"是" java.util"的父代,也是" java.util.Vector"的祖先。

LoggerContextFactory

LoggerContextFactory将Log4jAPI绑定到其实现。

LoggerContext

LoggerContext充当Logging系统的定位点,一个应用程序中可能有多个活动LoggerContext.

Configuration

每个LoggerContext都有一个活动的Configuration。由Configuration管理配置文件结构转化的Java对象。

Logger

通过调用LogManager.getLogger来创建。Logger本身不执行任何直接操作。Logger本身不执行任何直接操作。Logger本身不执行任何直接操作。
它仅具有一个名称,并与LoggerConfig关联。它扩展了AbstractLogger并实现了所需的方法。

Appender

可以理解为log4j2输出目标,决定日志的输出方式。包含日志的输出格式、输出路径等一系列配置信息。

2.Log4j2的初始化过程(LogManager的启动过程)

LogManager类中存在以下代码块,作为LogManager的启动入口:

	static {  PropertiesUtil managerProps = PropertiesUtil.getProperties();  String factoryClassName = managerProps.getStringProperty("log4j2.loggerContextFactory");  if (factoryClassName != null) {   try {    factory = (LoggerContextFactory)LoaderUtil.newCheckedInstanceOf(factoryClassName, LoggerContextFactory.class);   } catch (ClassNotFoundException var8) {    LOGGER.error("Unable to locate configured LoggerContextFactory {}", factoryClassName);   } catch (Exception var9) {    LOGGER.error("Unable to create configured LoggerContextFactory {}", factoryClassName, var9);   }  }  if (factory == null) {   SortedMap<Integer, LoggerContextFactory> factories = new TreeMap();   if (ProviderUtil.hasProviders()) {    Iterator i$ = ProviderUtil.getProviders().iterator();    while(i$.hasNext()) {     Provider provider = (Provider)i$.next();     Class<? extends LoggerContextFactory> factoryClass = provider.loadLoggerContextFactory();     if (factoryClass != null) {      try {       factories.put(provider.getPriority(), factoryClass.newInstance());      } catch (Exception var7) {       LOGGER.error("Unable to create class {} specified in provider URL {}", factoryClass.getName(), provider.getUrl(), var7);      }     }    }    if (factories.isEmpty()) {     LOGGER.error("Log4j2 could not find a logging implementation. Please add log4j-core to the classpath. Using SimpleLogger to log to the console...");     factory = new SimpleLoggerContextFactory();    } else if (factories.size() == 1) {     factory = (LoggerContextFactory)factories.get(factories.lastKey());    } else {     StringBuilder sb = new StringBuilder("Multiple logging implementations found: \n");     Iterator i$ = factories.entrySet().iterator();     while(i$.hasNext()) {      Entry<Integer, LoggerContextFactory> entry = (Entry)i$.next();      sb.append("Factory: ").append(((LoggerContextFactory)entry.getValue()).getClass().getName());      sb.append(", Weighting: ").append(entry.getKey()).append('\n');     }     factory = (LoggerContextFactory)factories.get(factories.lastKey());     sb.append("Using factory: ").append(factory.getClass().getName());     LOGGER.warn(sb.toString());    }   } else {    LOGGER.error("Log4j2 could not find a logging implementation. Please add log4j-core to the classpath. Using SimpleLogger to log to the console...");    factory = new SimpleLoggerContextFactory();   }  }

该静态代码块主要有以下几个步骤:

  1. 根据配置文件的配置信息获取LoggerContextFactory。
    在这段逻辑中,LogManager优先通过配置文件"log4j2.component.properties"通过配置项"log4j2.loggerContextFactory"来获取LoggerContextFactory,
    如果用户做了对应的配置,通过newCheckedInstanceOf方法实例化LoggerContextFactory的对象。

    默认情况下,不存在初始的默认配置文件log4j2.component.properties。

  2. 若LoggerContextFactory获取失败则通过ProviderUtil中的getProviders()方法载入providers,随后通过provider的loadLoggerContextFactory方法载入LoggerContextFactory的实现类。
    代码如下:

   final SortedMap<Integer, LoggerContextFactory> factories = new TreeMap<>();   // note that the following initial call to ProviderUtil may block until a Provider has been installed when   // running in an OSGi environment   if (ProviderUtil.hasProviders()) {    for (final Provider provider : ProviderUtil.getProviders()) {     final Class<? extends LoggerContextFactory> factoryClass = provider.loadLoggerContextFactory();     if (factoryClass != null) {      try {       factories.put(provider.getPriority(), factoryClass.newInstance());      } catch (final Exception e) {       LOGGER.error("Unable to create class {} specified in provider URL {}", factoryClass.getName(), provider         .getUrl(), e);      }     }    }

其中有两个方法的设计比较有新意分别为ProviderUtil.hasProviders()以及ProviderUtil.getProviders()这两个方法首先都会调用lazyInit(),这个方法使用了线程安全的机制懒加载ProviderUtil类对象的实例。

	protected static void lazyInit() {  // noinspection DoubleCheckedLocking  if (instance == null) {   try {    STARTUP_LOCK.lockInterruptibly();    try {     if (instance == null) {      instance = new ProviderUtil();     }    } finally {     STARTUP_LOCK.unlock();    }   } catch (final InterruptedException e) {    LOGGER.fatal("Interrupted before Log4j Providers could be loaded.", e);    Thread.currentThread().interrupt();   }  } }

其中对于可重入锁的lockInterruptibly和lock的区别在于:

	lock 与 lockInterruptibly比较区别在于:	lock 优先考虑获取锁,待获取锁成功后,才响应中断。	lockInterruptibly 优先考虑响应中断,而不是响应锁的普通获取或重入获取。	ReentrantLock.lockInterruptibly允许在等待时由其它线程调用等待线程的Thread.interrupt方法来中断等待线程的等待而直接返回,这时不用获取锁,而会抛出一个InterruptedException。 	ReentrantLock.lock方法不允许Thread.interrupt中断,即使检测到Thread.isInterrupted,一样会继续尝试获取锁,失败则继续休眠。	只是在最后获取锁成功后再把当前线程置为interrupted状态,然后再中断线程。

在创建新的providerUtil实例的过程中就会直接实例化provider对象,其过程是先通过getClassLoaders方法获取provider的类加载器,然后通过loadProviders(classLoader)加载类。
在providerUtil实例化的最后,会统一查找"META-INF/log4j-provider.properties"文件中对应的provider的url,会考虑从远程加载provider。

	private ProviderUtil() {  for (final ClassLoader classLoader : LoaderUtil.getClassLoaders()) {   try {    loadProviders(classLoader);   } catch (final Throwable ex) {    LOGGER.debug("Unable to retrieve provider from ClassLoader {}", classLoader, ex);   }  }  for (final LoaderUtil.UrlResource resource : LoaderUtil.findUrlResources(PROVIDER_RESOURCE)) {   loadProvider(resource.getUrl(), resource.getClassLoader());  } }

在2.12.1版本中,不再通过log4j-provider.properties配置文件获取具体的LoggerContextFactory(从jar包中找不到log4j-provider.properties配置),而是通过实例化Log4jProvider类添加Provider,
再通过provider.loadLoggerContextFactory()获取对应的LoggerContextFactory->Log4jContextFactory。

  1. 如果provider中没有获取到LoggerContextFactory的实现类或provider为空,则使用SimpleLoggerContextFactory作为LoggerContextFactory。
    factory = new SimpleLoggerContextFactory();

3.LogManager.getLogger()方法的执行过程。

静态方法LogManager.getLogger()用于检索logger。
该方法链式调用LoggerContextFactory.getContext返回一个启动的LoggerContext实例。默认情况下,LoggerContextFactory的子类是log4jContextFactory。
log4jLoggerFactory.getContext方法如下:

@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; }

该方法的主要逻辑是从ContextSelector中获取一个LoggerContext并启动。
ContextSelector:
由Log4jLoggerContext工厂调用。他们执行查找或创建LoggerContext的实际工作,这是Logger及其配置的基础。
ContextSelector可以自由地实现他们希望ManagementLoggerContext的任何机制。
默认的Log4jContextFactory检查是否存在名为"Log4jContextSelector"的系统属性。如果找到,则该属性应包含实现要使用的ContextSelector的Class的名称。
默认使用ClassLoaderContextSelector,该ContextSelector将LoggerContexts与创建getLogger调用的调用程序的ClassLoader关联。

LoggerContext.start用于启动LoggerContext,方法如下

public void start() {  LOGGER.debug("Starting LoggerContext[name={}, {}]...", getName(), this);  if (PropertiesUtil.getProperties().getBooleanProperty("log4j.LoggerContext.stacktrace.on.start", false)) {   LOGGER.debug("Stack trace to locate invoker",     new Exception("Not a real error, showing stack trace to locate invoker"));  }  if (configLock.tryLock()) {   try {    if (this.isInitialized() || this.isStopped()) {     this.setStarting();     reconfigure();     if (this.configuration.isShutdownHookEnabled()) {      setUpShutdownHook();     }     this.setStarted();    }   } finally {    configLock.unlock();   }  }  LOGGER.debug("LoggerContext[name={}, {}] started OK.", getName(), this); }

该方法主要有以下几个步骤:

  1. 使用ReentrantLock加锁
  2. this.setStarting->将LoggetContext的状态设置为正在启动
  3. reconfigure(核心)根据配置文件的位置,读取相应的log4j2配置文件,解析配置文件,最终解析为各种Appender以及Logger的Java对象并启动。
  4. this.setStareded->将LoggetContext的状态设置为已启动
  5. ReentrantLock解锁

LoggerContext.reconfigure方法如下

	/**  * Reconfigures the context.  */ private void reconfigure(final URI configURI) {  final ClassLoader cl = ClassLoader.class.isInstance(externalContext) ? (ClassLoader) externalContext : null;  LOGGER.debug("Reconfiguration started for context[name={}] at URI {} ({}) with optional ClassLoader: {}",    contextName, configURI, this, cl);  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);   /*    * instance.start(); Configuration old = setConfiguration(instance); updateLoggers(); if (old != null) {    * old.stop(); }    */   final String location = configuration == null ? "?" : String.valueOf(configuration.getConfigurationSource());   LOGGER.debug("Reconfiguration complete for context[name={}] at URI {} ({}) with optional ClassLoader: {}",     contextName, location, this, cl);  } }

该方法主要有以下几个步骤:

  1. 双重检查锁定获取ConfigurationFactory
    双重检查锁定:首先测试锁定标准而不实际获取锁定来减少获取锁定的开销。仅当锁定标准检查指示需要锁定时,实际锁定逻辑才会继续。
  2. 获取Configuration,根据各个ConfigurationFactory中的后缀匹配对应的文件,返回Configuration实例。
  3. 若获取Configuration实例成功,则调用setConfiguration方法

setConfiguration方法的核心是调用config.start方法启动Configuration,该方法首先会调用AbstractConfiguraion的start方法将配置文件中所有元素转化为对应的logger或Appender对象并启动。
AbstractConfiguration.start方法如下:

	@Override public void start() {  // Preserve the prior behavior of initializing during start if not initialized.  if (getState().equals(State.INITIALIZING)) {   initialize();  }  LOGGER.debug("Starting configuration {}", this);  this.setStarting();  if (watchManager.getIntervalSeconds() >= 0) {   watchManager.start();  }  if (hasAsyncLoggers()) {   asyncLoggerConfigDisruptor.start();  }  final Set<LoggerConfig> alreadyStarted = new HashSet<>();  for (final LoggerConfig logger : loggerConfigs.values()) {   logger.start();   alreadyStarted.add(logger);  }  for (final Appender appender : appenders.values()) {   appender.start();  }  if (!alreadyStarted.contains(root)) { // LOG4J2-392   root.start(); // LOG4J2-336  }  super.start();  LOGGER.debug("Started configuration {} OK.", this); }

该方法主要有以下几个步骤:

  1. 初始化,主要执行两个操作,在
  2. 设置Configuration为正在启动状态
  3. asyncLoggerConfigDisruptor(Disruptor会在另外一文中详细描述,先挖坑)
  4. 启动所有的logger
  5. 启动所有的appender
  6. 设置Configuration为已启动状态

	private void constructHierarchy(final Node node, final Element element) {  processAttributes(node, element);  final StringBuilder buffer = new StringBuilder();  final NodeList list = element.getChildNodes();  final List<Node> children = node.getChildren();  for (int i = 0; i < list.getLength(); i++) {   final org.w3c.dom.Node w3cNode = list.item(i);   if (w3cNode instanceof Element) {    final Element child = (Element) w3cNode;    final String name = getType(child);    final PluginType<?> type = pluginManager.getPluginType(name);    final Node childNode = new Node(node, name, type);    constructHierarchy(childNode, child);    if (type == null) {     final String value = childNode.getValue();     if (!childNode.hasChildren() && value != null) {      node.getAttributes().put(name, value);     } else {      status.add(new Status(name, element, ErrorType.CLASS_NOT_FOUND));     }    } else {     children.add(childNode);    }   } else if (w3cNode instanceof Text) {    final Text data = (Text) w3cNode;    buffer.append(data.getData());   }  }  final String text = buffer.toString().trim();  if (text.length() > 0 || (!node.hasChildren() && !node.isRoot())) {   node.setValue(text);  } }

该方法是标准的树的深度优先遍历,通过遍历

参考资料

https://bryantchang.github.io/categories/Log4j/
log4j2中文文档









原文转载:http://www.shaoqun.com/a/654736.html

跨境电商:https://www.ikjzd.com/

跨境通:https://www.ikjzd.com/w/1329

usps:https://www.ikjzd.com/w/513


前言本文建立在log4j-core2.12.1版本为基础,在此基础上进行的源码剖析Log4j2的配置可以通过以下四种方式之一完成通过以通过创建ConfigurationFactory和Configuration以编程方式实现通过调用配置界面中公开的API,以编程方式将组建添加到默认配置通过调用内部Logger类上的方法以编程实现。本文的执行过程建立在由于本人技术尚浅,故有错误的地方,希望各位不吝赐
prime:https://www.ikjzd.com/w/129
菜鸟网:https://www.ikjzd.com/w/1547
extra:https://www.ikjzd.com/w/1736
德国站被封该如何解决,2种解封方案看这里!:https://www.ikjzd.com/home/22546
男技师帮我推油经历 口述在SPA会所的那些趣事:http://www.30bags.com/m/a/250475.html
SKU不同,排名却一模一样,这又是什么黑科技?:https://www.ikjzd.com/home/180

No comments:

Post a Comment