日志是什么?说穿了不就是一堆System.out.println() 吗?记得刚学java的时候,还不会调试,于是就在关键位置使用System.out.println()打印变量的值。有了日志程序之后, 本质上还是一样的,你还得在需要的地方手动调用日志程序的API,比如常见的log.info()。但是使用日志程序的好处是,输出被格式化了,显得更加规范,以及可以方便的将日志导入到其他目的地比如文件中。

      相关的类和接口位于java/util/logging包中,这个框架记录日志的思路是这样的:主要包括Loggor和Appender(又叫Handler)两部分(其他的是细节先不管),前者嵌入到应用中供应用调用,接收日志,形式如log.info();然后把接收到的日志转发给Appender处理,后者将对日志进行格式化然后输出到对应的目的地。一个Logger可同时对应多个Appender,每个Appender对应一个具体的输出目的地。盗张图放下面:

java log丢失 log.info java_java


在代码中的示例如下:


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,算了还是不说了。。