Usage

静态 Logger 与非静态 Logger

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

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

  • 创建 Logger 后,在删除与之关联的 LoggerContext 之前,不会将其删除。通常,仅在应用程序关闭或未部署时才会发生这种情况。对具有相同 Logger 名称的 getLogger 的每次调用都将返回相同的 Logger 实例。因此,静态或非静态 Logger 之间的差异很小。

  • 静态和非静态 Logger 之间没有行为上的区别。两者都将在创建时分配给 Logger 名称,通常是它们所关联的类的名称。有关更多信息,请参见下面有关 Logger 名称与类名称的讨论以及示例。

记录 Logger 名称与类名称

创建 Logger 时,将指定 Logger 的 Logger 名称。调用日志方法时,日志事件中的类名称值将反映调用该日志方法的类的名称,该名称不必与创建 Logger 的类相同。以下示例对此进行了说明。

Base Class 创建一个静态 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");
      }
  }

此类扩展了基础类。它提供了自己的 Logger,并具有三种方法,一种使用此类中的 Logger,一种使用 Base Class 中的静态 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 class Child extends Parent {

      // The name of this Logge 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");
      }
  }

该应用程序将练习所有记录方法四次。Base Class 中的前两次 Logger 设置为静态 Logger。第二次将 Base Class 中的 Logger 设置为使用子类中的 Logger。在每个方法的第一次和第三次调用中,将传递一个空标记。在第二个和第四个中,传递了一个名为“ 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,即 Logger 名。

<?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 名称之间的区别。所有奇数编号的项目均打印 Logger 的名称(%c),而所有偶数编号的项目均打印调用日志记录方法的类的名称(%C)。下表中结果描述中的数字与输出中显示的相应数字匹配。

  • 使用带有 Logger 名称模式的静态 Logger 在父类中执行记录。Logger 名称与父类的名称匹配。

  • 使用具有类名模式的静态 Logger 在父类中执行记录。尽管该方法是针对 Child 实例调用的,但它是在 Parent 中实现的,因此出现了。

  • 使用父级中的 Logger 在“子级”中执行记录,因此将父级名称打印为 Logger 名称。

  • 使用父级中的 Logger 在“子级”中执行记录。由于调用日志记录方法的方法位于 Child 中,因此它是显示的类名。

  • 使用父级中的静态 Logger 在“子级”中执行记录,因此将父级名称打印为 Logger 名称。

  • 使用父级中的静态 Logger 在“子级”中执行记录。由于调用日志记录方法的方法位于 Child 中,因此它是显示的类名。

  • 使用 Child 的 Logger 在父类中执行记录。Logger 名称与孩子的姓名匹配,因此将其打印出来。

  • 使用 Child 的 Logger 在父类中执行记录。尽管该方法是针对 Child 实例调用的,但它是在 Parent 中实现的,因此显示为类名。

  • 使用设置为子 Logger 的父级中的 Logger 在“子项”中执行记录,因此将子名称作为 Logger 名称打印。

  • 使用设置在子 Logger 中的父 Logger 在“子记录”中执行记录。由于调用日志记录方法的方法位于 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 的名称将始终源自创建 Logger 的类,并且每个日志事件中的类名称将始终反映调用记录方法的类。

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