JDK日志框架解读

Posted LackMemory

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JDK日志框架解读相关的知识,希望对你有一定的参考价值。

日志是什么?说穿了不就是一堆System.out.println() 吗?记得刚学java的时候,还不会调试,于是就在关键位置使用System.out.println()打印变量的值。有了日志程序之后, 本质上还是一样的,你还得在需要的地方手动调用日志程序的API,比如常见的log.info()。但是使用日志程序的好处是,输出被格式化了,显得更加规范,以及可以方便的将日志导入到其他目的地比如文件中。
      相关的类和接口位于java/util/logging包中,这个框架记录日志的思路是这样的:主要包括Loggor和Appender(又叫Handler)两部分(其他的是细节先不管),前者嵌入到应用中供应用调用,接收日志,形式如log.info();然后把接收到的日志转发给Appender处理,后者将对日志进行格式化然后输出到对应的目的地。一个Logger可同时对应多个Appender,每个Appender对应一个具体的输出目的地。盗张图放下面:

在代码中的示例如下:
public static void main(String[] args) 
        ConsoleHandler handler = new ConsoleHandler();
        handler.setLevel(Level.FINE);
        FileHandler fileHandler = new FileHandler("/home/tt.txt", true);
        fileHandler.setLevel(Level.FINER);


        Logger logger =  Logger.getLogger("W");
        logger.setLevel(Level.ALL);
        logger.addHandler(handler);
        logger.addHandler(fileHandler);
        logger.log(Level.INFO, "hehe");
 

获取一个Logger对象的唯一方法就是使用Logger的静态方法getLogger,同时传入一个String类型的参数作为这个对象的名字。由于可能在各个具体类中使用Logger,所以对其起名一般是直接使用该类的类名,这样的好处是可以避免重复。因为生成Logger对象时,会首先根据名字去查找是否已经存在了同名的Logger,有的话直接返回它,否则才去生成新的对象。由于每个类使用Logger的需求不同,要进行个性化配置,所以尽量不使用同一个Logger。
Appender对象是单独定义的,然后作为入参传递给Logger对象的addHandler方法。这里分别创建了一个ConsoleHandler对象和一个FileHandler对象,从名字可以看出前者将日志输出到标准输出上,后者把日志输出到指定的文件中去。当然也可以定义多个ConsoleHandler或FileHandler对象,然后添加到这个Logger对象上(虽然对于ConsoleHandler没啥意义),毕竟一个Logger可同时对应多个Appender。
另外代码中还涉及到了日志级别,对日志进行分级是一个挺好的想法,目的是便于进行管理吧,这种思维方式很“西方化”。jdk中,默认有以下几个级别的日志:OFF,SEVERE,WARNING,INFO,CONFIG,FINE,FINER,FINEST,ALL,等级依次降低,对应的日志越来越详细,重要程度也越来越低。就是说,OFF将什么日志都不要,而ALL将输出所有日志。一条日志属于哪个级别是开发者在调用Logger的log方法时候自己指定的,该方法的第一个参数要求指定一个日志的级别,第二个参数才是具体的日志内容。
无论是Logger还是Appender都可以有自己的过滤器,用来过滤自己向下游输出的日志。过滤的原则就是只输出等级高于或等于配置级别的日志,比如配置的级别是INFO,那么级别低于INFO的日志将不会发送到下游。代码中,无论是Logger还是Appender都是调用setLevel方法来设置过滤级别的。

这么一圈下来,为了打一个"hehe",代码就过于臃肿了,远没有System.out.println("hehe")简洁高效。我随便debug了一下源代码,本质上最后还是一样的套路:

首先Logger.log()方法中的核心代码:

while (logger != null) 
            final Handler[] loggerHandlers = isSystemLogger
                ? logger.accessCheckedHandlers()
                : logger.getHandlers();
            for (Handler handler : loggerHandlers) 
                handler.publish(record);

其逻辑就是循环所有的Appender,并调用其publish方法。这个方法最终调用了StreamHandler类的publish方法,这个方法的核心就是:

try 
            if (!doneHeader) 
                writer.write(getFormatter().getHead(this));
                doneHeader = true;
            
            writer.write(msg);
而这个writer又是谁呢?对于ConsoleHandler而言:

public ConsoleHandler() 
        sealed = false;
        configure();
        setOutputStream(System.err);
        sealed = true;


于是jdk的日志框架为开发者提供了配置文件,以减少代码量。假如对Logger的个性化要求不高,或者说程序中的大部分Logger是一样的套路,那么可以采用配置文件来搞定日志;需要个性化配置的就自己撸代码,代码的优先级高于配置文件。配置文件名为logging.properties,位于$JAVA_HOME/jre/lib目录中,内容如下:

handlers= java.util.logging.ConsoleHandler
.level= INFO
# default file output is in user's home directory.
java.util.logging.FileHandler.pattern = %h/java%u.log
java.util.logging.FileHandler.limit = 50000
java.util.logging.FileHandler.count = 1
java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter
# Limit the message that are printed on the console to INFO and above.
java.util.logging.ConsoleHandler.level = INFO
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
# For example, set the com.xyz.foo logger to only log SEVERE messages:
com.xyz.foo.level = SEVERE
那么这个配置文件做了哪些事呢?第一:为每个非个性化的Logger指定了它将拥有哪类Appender(每类只能有一个),这通过handlers属性配置;第二:指定了各个Appender类的具体设置,比如日志级别,日志格式等;第三:指定了各个Logger的日志级别,这通过Logger的名字的.level属性指定的,就像例子中那样,不知道能不能指定其他的属性,源代码没看完。

最后有一个全局的日志级别配置:
.level= INFO
这个配置只有在某个Logger或Appender没有自己配置级别时才会起作用;然而若Logger或Appender有了自己的配置,那么将会覆盖全局配置,比如下面这句:
java.util.logging.ConsoleHandler.level = INFO

需要注意的是,这个配置文件是默认的,也就是你可以定义自己的个性化配置文件,然后在运行你的Java程序时,使用java.util.logging.config.file参数来指定文件的位置,logging.properties也给出了例子:
java -Djava.util.logging.config.file=myfile 

这也说明了一个问题,配置文件是针对整个Java程序的,比如Tomcat就是一个java程序,在其上运行的各个Web应用只是这个程序的一部分,所以各个Web应用也将共享这个日志的配置。但是如果一个Web应用想拥有自己独特的日志配置的话就很麻烦,这是JDK自带的日志框架满足不了的,所以Tomcat开发了自己的日志系统。
 
最后说一下Layouts,算了还是不说了。。

以上是关于JDK日志框架解读的主要内容,如果未能解决你的问题,请参考以下文章

API接口自动化测试框架搭建(二十二)-全局变量config.py完整代码及解读

日志技术-Java原生日志实现JUL

log4j2 配置文件解读

日志框架--JDK Logging

Gin框架源码解读(一)

一文解读JDK8中HashMap的源码