静态与非静态日志记录器

与Java中的任何变量一样,logger可以声明为静态变量或类成员变量。

然而,在选择将日志记录器声明为静态还是非静态时,需要考虑一些因素。一般来说,最好将logger声明为静态的

  1. 当使用默认的ContextSelector ClassLoaderContextSelector时,实例化一个新的Logger是一个相当昂贵的操作。当Logger被创建时,ClassLoaderContextSelector将定位与Logger关联的类的ClassLoader,并将Logger添加到与该ClassLoader关联的LoggerContext中。

  2. 一旦创建了Logger,它将不会被删除,直到它所关联的LoggerContext被删除。通常,这只会在应用程序关闭或取消部署时发生。每次调用具有相同logger名称的getLogger将返回相同的logger实例。因此,静态或非静态Logger之间的区别非常小。

  3. 静态和非静态Logger之间没有行为上的区别。两者在创建时都会分配Logger名称,这通常是与它们相关联的类的名称。有关更多信息,请参阅下面关于记录器名称与类名称的讨论和示例。

记录记录器名称与类名称

在创建logger时指定logger的名称。当调用日志方法时,日志事件中的类名值将反映从其中调用日志方法的类的名称,这不一定与创建Logger的类相同。

下面的例子说明了这一点。

基类创建一个静态Logger和一个初始化为同一个Logger的Logger变量。

它有一个执行日志记录的方法,但提供对两个logger的访问,一个是静态的,另一个可以被覆盖。

  package org.apache.logging;

  import org.apache.logging.log4j.LogManager;
  import org.apache.logging.log4j.Logger;
  import org.apache.logging.log4j.Marker;

  /**
  *
  */
  public abstract class Parent {

      // The name of this Logger will be "org.apache.logging.Parent"
      protected static final Logger parentLogger = LogManager.getLogger();

      private Logger logger = parentLogger;

      protected Logger getLogger() {
          return logger;
      }

      protected void setLogger(Logger logger) {
          this.logger = logger;
      }


      public void log(Marker marker) {
          logger.debug(marker,"Parent log message");
      }
  }

该类扩展基类。它提供了自己的记录器,并有三个方法,一个使用该类中的记录器,一个使用基类中的静态记录器,另一个可以将记录器设置为父类或子类。

  package org.apache.logging;

  import org.apache.logging.log4j.LogManager;
  import org.apache.logging.log4j.Logger;
  import org.apache.logging.log4j.Marker;

  /**
  *
  */
  public class Child extends Parent {

      // The name of this Logger will be "org.apache.logging.Child"
      public Logger childLogger = LogManager.getLogger();

      public void childLog(Marker marker) {
          childLogger.debug(marker,"Child logger message");
      }

      public void logFromChild(Marker marker) {
          getLogger().debug(marker,"Log message from Child");
      }

      public void parentLog(Marker marker) {
          parentLogger.debug(marker,"Parent logger, message from Child");
      }
  }

应用程序对所有的日志方法进行了四次练习。前两次,基类中的Logger被设置为静态Logger。第二次将基类中的Logger设置为使用子类中的Logger。在每个方法的第一次和第三次调用中,传递一个空标记。在第二个和第四个中,传递一个名为“CLASS”的Marker。

package org.apache.logging;

  import org.apache.logging.log4j.Marker;
  import org.apache.logging.log4j.MarkerManager;

  public class App {

      public static void main( String[] args ) {
          Marker marker = MarkerManager.getMarker("CLASS");
          Child child = new Child();

          System.out.println("------- Parent Logger ----------");
          child.log(null);
          child.log(marker);
          child.logFromChild(null);
          child.logFromChild(marker);
          child.parentLog(null);
          child.parentLog(marker);

          child.setLogger(child.childLogger);

          System.out.println("------- Parent Logger set to Child Logger ----------");
          child.log(null);
          child.log(marker);
          child.logFromChild(null);
          child.logFromChild(marker);
      }
  }

该配置利用了Log4j根据日志事件的属性选择模式的能力。在这种情况下,当class标记存在时使用%C(类名模式),而当class标记不存在时使用%C(记录器名称)。

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="error">
  <Appenders>
    <Console name="Console" target="SYSTEM_OUT">
      <PatternLayout>
        <MarkerPatternSelector defaultPattern="%sn. %msg: Logger=%logger%n">
          <PatternMatch key="CLASS" pattern="%sn. %msg: Class=%class%n"/>
        </MarkerPatternSelector>
      </PatternLayout>
    </Console>
  </Appenders>
  <Loggers>
    <Root level="TRACE">
      <AppenderRef ref="Console" />
    </Root>
  </Loggers>
</Configuration>

下面的输出说明了在模式中使用Logger名称和Class名称之间的区别。所有奇数项打印记录器的名称(%c),而所有偶数项打印调用日志方法的类的名称(%c)。

下表中结果描述中的数字与输出中显示的相应数字相匹配。

  • 日志记录在父类中使用具有logger名称模式的静态日志记录器执行。记录器名称与父类的名称匹配。

  • 日志记录是在父类中使用带有类名模式的静态日志记录器执行的。尽管该方法是针对Child实例调用的,但它是在Parent实例中实现的,因此显示的就是这个。

  • 使用父进程中的记录器在子进程中执行日志记录,因此父进程的名称被打印为记录器名称。

  • 使用父进程中的记录器在子进程中执行日志记录。由于调用日志方法的方法在Child中,因此出现的就是类名。

  • 使用父进程中的静态记录器在子进程中执行日志记录,因此父进程的名称被打印为记录器名称。

  • 在子进程中使用父进程中的静态日志记录器执行日志记录。由于调用日志方法的方法在Child中,因此出现的就是类名。

  • 日志记录是在父类中使用子类的记录器执行的。记录器的名称与子进程的名称匹配,因此将其打印出来。

  • 日志记录是在父类中使用子类的记录器执行的。尽管该方法是针对Child实例调用的,但它是在Parent中实现的,因此它是作为类名出现的。

  • 在Child中使用父级记录器执行日志记录,父级记录器被设置为子记录器,因此子记录器的名称被打印为记录器名称。

  • 使用父日志记录器在子日志记录器中执行日志记录,父日志记录器被设置为子日志记录器。由于调用日志方法的方法在Child中,因此出现的就是类名。

  ------- Parent Logger ----------
  1. Parent log message: Logger=org.apache.logging.Parent
  2. Parent log message: Class=org.apache.logging.Parent
  3. Log message from Child: Logger=org.apache.logging.Parent
  4. Log message from Child: Class=org.apache.logging.Child
  5. Parent logger, message from Child: Logger=org.apache.logging.Parent
  6. Parent logger, message from Child: Class=org.apache.logging.Child
  ------- Parent Logger set to Child Logger ----------
  7. Parent log message: Logger=org.apache.logging.Child
  8. Parent log message: Class=org.apache.logging.Parent
  9. Log message from Child: Logger=org.apache.logging.Child
  10. Log message from Child: Class=org.apache.logging.Child

在上面的例子中,声明了两个logger。一个是静态的,一个是非静态的。当查看结果时,很明显,无论如何声明记录器,结果都是完全相同的。

日志记录器的名称将始终源自创建它的类,并且每个日志事件中的类名称将始终反映从其中调用日志方法的类。

应该注意的是,打印位置信息(类名、方法名和行号)会造成很大的性能损失。如果方法名称和行号不重要,通常最好确保每个类都有自己的Logger,以便Logger名称准确地反映执行日志记录的类。

参考资料

https://logging.apache.org/log4j/2.x/manual/usage.html