静态与非静态日志记录器
与Java中的任何变量一样,logger可以声明为静态变量或类成员变量。
然而,在选择将日志记录器声明为静态还是非静态时,需要考虑一些因素。一般来说,最好将logger声明为静态的。
-
当使用默认的ContextSelector ClassLoaderContextSelector时,实例化一个新的Logger是一个相当昂贵的操作。当Logger被创建时,ClassLoaderContextSelector将定位与Logger关联的类的ClassLoader,并将Logger添加到与该ClassLoader关联的LoggerContext中。
-
一旦创建了Logger,它将不会被删除,直到它所关联的LoggerContext被删除。通常,这只会在应用程序关闭或取消部署时发生。每次调用具有相同logger名称的getLogger将返回相同的logger实例。因此,静态或非静态Logger之间的区别非常小。
-
静态和非静态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