使用

静态与非静态记录器

与 Java 中的任何变量一样,记录器可以声明为静态变量或类成员变量。但是,在选择将记录器声明为静态还是非静态时,需要考虑一些因素。通常,最好将记录器声明为静态。

  1. 当使用默认的 ContextSelector 时,实例化新的记录器是一个相当昂贵的操作,ClassLoaderContextSelector。当创建记录器时,ClassLoaderContextSelector 将定位与记录器关联的类的类加载器,并将记录器添加到与该类加载器关联的 LoggerContext 中。
  2. 一旦创建了记录器,它将不会被删除,直到与它关联的 LoggerContext 被删除。通常,这只会发生在应用程序关闭或取消部署时。每次使用相同的记录器名称调用 getLogger 时,都会返回相同的记录器实例。因此,静态记录器和非静态记录器之间几乎没有区别。
  3. 静态记录器和非静态记录器之间没有行为差异。两者在创建时都会分配记录器名称,该名称通常是与它们关联的类的名称。有关更多信息,请参阅下面关于记录器名称与类名称的讨论以及示例。

记录记录器名称与类名称

记录器的记录器名称是在创建记录器时指定的。当调用日志方法时,日志事件中的类名称值将反映调用日志方法的类的名称,这并不一定与创建记录器的类相同。以下示例说明了这一点。

基类创建了一个静态记录器和一个初始化为相同记录器的记录器变量。它有一个执行日志记录的方法,但提供了对两个记录器的访问权限,一个是静态的,另一个可以被覆盖。

  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");
      }
  }

应用程序四次执行所有日志记录方法。前两次,基类中的记录器设置为静态记录器。后两次,基类中的记录器设置为使用子类中的记录器。在每次方法的第一次和第三次调用中,传递了一个空标记。在第二次和第四次调用中,传递了一个名为“CLASS”的标记。

  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>

下面的输出说明了在模式中使用记录器名称和类名称之间的区别。所有奇数项打印记录器名称(%c),而所有偶数项打印调用日志记录方法的类的名称(%C)。以下列表中结果描述中的数字与输出中显示的相应数字匹配。

  1. 使用静态记录器和记录器名称模式在父类中执行日志记录。记录器名称与父类的名称匹配。
  2. 使用静态记录器和类名称模式在父类中执行日志记录。虽然方法是在 Child 实例上调用的,但它是在 Parent 中实现的,因此显示的是 Parent。
  3. 在 Child 中使用父记录器执行日志记录,因此打印父记录器的名称作为记录器名称。
  4. 在 Child 中使用父记录器执行日志记录。由于调用日志记录方法的方法在 Child 中,因此显示的是 Child 的类名称。
  5. 在 Child 中使用父记录器中的静态记录器执行日志记录,因此打印父记录器的名称作为记录器名称。
  6. 在 Child 中使用父记录器中的静态记录器执行日志记录。由于调用日志记录方法的方法在 Child 中,因此显示的是 Child 的类名称。
  7. 在父类中使用 Child 的记录器执行日志记录。记录器名称与子类的名称匹配,因此打印出来。
  8. 在父类中使用 Child 的记录器执行日志记录。虽然方法是在 Child 实例上调用的,但它是在 Parent 中实现的,因此显示的是 Parent 的类名称。
  9. 在 Child 中使用父记录器执行日志记录,该父记录器设置为子记录器,因此打印子记录器的名称作为记录器名称。
  10. 在 Child 中使用父记录器执行日志记录,该父记录器设置为子记录器。由于调用日志记录方法的方法在 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

在上面的示例中,声明了两个记录器。一个是静态的,另一个是非静态的。查看结果可以清楚地看到,无论记录器是如何声明的,结果都是完全相同的。记录器的名称始终源于创建它的类,而每个日志事件中的类名称始终反映调用日志记录方法的类。

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