静态与非静态日志记录器

与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的访问,一个是静态的,另一个可以被覆盖。

  [java]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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"); } }

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

  [java]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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。

  [java]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?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中,因此出现的就是类名。

  [plaintext]
1
2
3
4
5
6
7
8
9
10
11
12
------- 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