JDK Logging源码学习笔记

(1)Apache log4j-1.2.17源码学习笔记 http://blog.csdn.net/zilong_zilong/article/details/78715500
(2)Apache log4j-1.2.17问答式学习笔记 http://blog.csdn.net/zilong_zilong/article/details/78916626 
(3)JDK Logging源码学习笔记  http://aperise.iteye.com/blog/2411850

1.JDK Logging介绍

        断点调试和记录日志,是程序员排查问题的2个有效手段,断点调试需要对全盘代码熟门熟路,费时费力,如果代码不开源那么此种方法就不能使用,相对于断点调试,记录日志提供了另外一种更有效的排错方法,预先植入了有效的日志信息,后期只需通过配置文件即可管理日志,借助工具扫描日志文件内容可以有效的监测当前运行的系统是否运行正常。记录日志作为一个通用的重要的模块,所以开源组织分别推出了自己的日志框架,比如Apache Log4j,Apache Log4j 2、Apache Commons Logging、Slf4j、Logback和JDK Logging,今天我要分享的是JDK Logging日志框架

        JDK Logging的组成及其关系如下图所示:

        在上图中,Application代表我们的Java程序,Logger代表用户日志输出logger,每个用户logger上关联着handler,最终通过用户logger的handler将日志输出到外界Outside World(内存、操作系统文件、socket),同时每个handler上也可以配置filter进行过滤filter,特殊的对于MemoryHandler可以将应用程序的日志事件向下一个handler相传,所以MemoryHandler的处理流程如下:
        其它详细说明详见JDK官方说明文档,JDK Logging的官方介绍文档链接如下:

JDK1.6 https://docs.oracle.com/javase/6/docs/technotes/guides/logging/overview.html
JDK1.7 https://docs.oracle.com/javase/7/docs/technotes/guides/logging/overview.html
JDK1.8 https://docs.oracle.com/javase/8/docs/technotes/guides/logging/overview.html

        在项目中使用JDK Logging举例如下:

package com.wombat;
import java.util.logging.*;
public class Nose {
    private static Logger logger = Logger.getLogger("com.wombat.nose");
    private static FileHandler fh = new FileHandler("mylog.txt");
    public static void main(String argv[]) {
        // Send logger output to our FileHandler.
        logger.addHandler(fh);
        // Request that every detail gets logged.
        logger.setLevel(Level.ALL);
        // Log a simple INFO message.
        logger.info("doing stuff");
        try {
            Wombat.sneeze();
        } catch (Exception ex) {
            logger.log(Level.WARNING, "trouble sneezing", ex);
        }
        logger.fine("done");
    }
}

        接下来的环节就从这个代码入手,从源码的角度来逐步分析和学习JDK Logging。

 

2.LogManager的static代码块源码分析(RootLogger创建过程)

        之前在Apache log4j-1.2.17的源码分析中我们看到log4j-1.2.17也是通过类LogManager的静态代码块来进行的,这里对于JDK Logging也是一样,下面将LogManager的static代码块注释如下:


        上面代码中有2个JAVA的基础知识这里也顺便提一下:

  • AccessController.doPrivileged属于特权操作,意思是不管此方法由哪个用户发起,都无需对此操作涉及的资源(文件读写特权等等)进行检查
  • System.getProperty用户获取JVM系统变量的值或者由JDK的参数-Dproterty=value设置的属性的值

        这两个知识点在众多的开源框架中都被广泛使用,好了回到正题,上面代码中默认获取的java.util.logging.manager为空,所以cname=null,所以默认会通过构造方法new LogManager()创建LogManager对象,之后就是一系列上下文设置操作,但直至代码结束,我们注意到始终未看到在哪里读取过日志配置文件logging.properties(为什么是logging.properties这个接下来会讲到),实际上对于logging.properties的加载是采用延迟加载策略,只会在第一次调用LogManager.getLogManager()才开始解析logging.properties日志配置文件,这里注意和Apache log4j-1.2.17加载log4j.properties类比来增强学习,Apache log4j-1.2.17是直接在静态代码块中就加载解析了log4j.properties文件的,并未采用延迟加载。

 

3.JDK Logging读取日志配置文件logging.properties代码分析

        在此文的开头对于JDK Logging的举例使用中,我们看到对于JDK Logging的使用的第一行代码为:

        那么我们就从此作为入口,先看下JDK Logging中Logger.getLogger代码:

        上面代码中,当Logger.getLogger被调用时,接着会调用代码Logger.demandLogger,而Logger.demandLogger中正如章节2中提到的,调用了全局静态类LogManager的方法LogManager.getLogManager(),那么接下来就详细看下LogManager.getLogManager()相关代码:

        从上面的代码中,我们终于找到了JDK Logging读取日志配置文件的蛛丝马迹,继续跟踪代码Logger.readConfiguration:

        上面的代码中我们看到,JDK Logging加载日志配置文件是有先后顺序的:

  • 首先通过java.util.logging.config.class配置的类来初始化JDK Logging;
  • 如果java.util.logging.config.class找不到,接着通过java.util.logging.config.file配置的文件来初始化JDK Logging;
  • 最后如果java.util.logging.config.file找不到,就通过操作系统安装的JDK的jre/lib下的logging.properties来初始化JDK Logging;
  • 最后如果连JDK都为安装,直接抛出错误Error("Can't find java.home ??")

  

4.JDK Logging的用户logger创建过程

        在上面我们已经看到了RootLogger的创建过程,RootLogger是所有用户logger的父亲logger,这里继续查看我们的用户logger如何创建的,一般创建用户logger的代码如下:

Logger logger = Logger.getLogger("com.wombat.nose");

        那么我们就以此为入口查看logger是如何创建的,类Logger里getLogger方法代码如下:

        在上面的代码中,我们看到,最终都会执行如下这行代码:

        继续分析这段代码,如下:

        我们看到如果用户logger不存在就创建,创建logger的代码如下:
        如果用户logger存在就直接从UserContext上下文获取,那么我们继续关注下用户logger创建完毕后,代码addLogger(newLogger)干了点啥,代码如下:


        上面的代码告诉我们创建用户logger后,添加logger到usercontext,配置logger的handler以及其level属性,很关键的信息是用户logger只会创建一次

 

5.日志输出代码logger.info(String msg)代码分析

        上面我们已经看到创建了logger对象,接着就是在需要输出日志的地方调用logger.info(String msg)输出日志,那么我们看下这块的代码逻辑,代码分析如下:

 

6.JDK Logging的日志级别

        我们习惯了Apache log4j-1.2.17,那么这里就讲JDK Logging的日志级别与Apache log4j-1.2.17进行对别如下:


7.JDK Logging的Handlers及其属性配置

        JDK Logging的Handlers的类继承关系图如下:

        这里在之前的代码中,我们看到对于handler,每次创建,只设置了handler的level属性,那么对于其他属性,是哪里设置的呢?每个handler的具体实现类里面都有个privatevoid configure() 方法,这个方法值得我们特别关注。

    7.1 MemoryHandler

        MemoryHandler会将LogRecords存储到内存当中,存储到内存的同时清理掉之前的LogRecords,MemoryHandler的属性设置方法private void configure()在构造方法public MemoryHandler()中被调用,代码如下:

        从上面的代码我们看到MemoryHandler的配置属性列表如下:

  • java.util.logging.MemoryHandler.level 设置handler的level(defaults to Level.ALL).
  • java.util.logging.MemoryHandler.filter 设置handler的filter (defaults to no Filter).
  • java.util.logging.MemoryHandler.size 设置handler的内存缓冲区数组的大小 (defaults to 1000).
  • java.util.logging.MemoryHandler.push 设置push level (defaults to level.SEVERE).
  • java.util.logging.MemoryHandler.target 设置目标handler给这个MemoryHandler (no default).

 

    7.2 StreamHandler

        流处理handler的基础类,其实现类如下:

        StreamHandler的属性设置方法private void configure()在构造方法public StreamHandler()中被调用,代码如下:

        从上面可以看到StreamHandler的配置属性如下:

  • java.util.logging.StreamHandler.level 设置handler的level (defaults to Level.INFO).
  • java.util.logging.StreamHandler.filter 设置handler的filter的类 (defaults to no Filter).
  • java.util.logging.StreamHandler.formatter 设置handler的formatter类 (defaults to java.util.logging.SimpleFormatter).
  • java.util.logging.StreamHandler.encoding 设置编码格式 (defaults to the default platform encoding).

 

    7.3 ConsoleHandler

        ConsoleHandler默认将LogRecords输出到System.err,ConsoleHandler的属性设置方法private void configure()在构造方法public ConsoleHandler()中被调用,代码如下:

        从上面可知ConsoleHandler的配置属性如下:

  • java.util.logging.ConsoleHandler.level 设置handler的level (defaults to Level.INFO).
  • java.util.logging.ConsoleHandler.filter 设置handler使用的filter类 (defaults to no Filter).
  • java.util.logging.ConsoleHandler.formatter 设置formatter类 (defaults to java.util.logging.SimpleFormatter).
  • java.util.logging.ConsoleHandler.encoding 设置编码格式(defaults to the default platform encoding).

 

    7.4 FileHandler

        FileHandler负责将LogRecords输出到操作系统的文件,FileHandler的属性设置方法private void configure()在构造方法public FileHandler()中被调用,代码如下:

        从上面可以看出FileHandler的配置属性如下:

  • java.util.logging.FileHandler.level 设置handler的level (defaults to Level.ALL).
  • java.util.logging.FileHandler.filter 设置handler使用的filter类 (defaults to no Filter).
  • java.util.logging.FileHandler.formatter 设置formatter类 (defaults to java.util.logging.XMLFormatter)
  • java.util.logging.FileHandler.encoding 设置编码格式 (defaults to the default platform encoding).
  • java.util.logging.FileHandler.limit 设置一个日志文件最大文件大小,单位bytes 。设置0表示没有任何限制 (Defaults to no limit).
  • java.util.logging.FileHandler.count 设置最多保留几个日志文件 (defaults to 1).
  • java.util.logging.FileHandler.pattern 设置产生日志文件的名字的规则(Defaults to "%h/java%u.log").
  • java.util.logging.FileHandler.append 设置是否在已经存在的日志文件里最后追加日志,默认false (defaults to false).

 

    7.5 SocketHandler

        SocketHandler负责将LogRecords输出到socket接口,SocketHandler的属性设置方法private void configure()在构造方法public SocketHandler()中被调用,代码如下:

        从上面可以看到SocketHandler的配置属性如下:

  • java.util.logging.SocketHandler.level 设置handler的level(defaults to Level.ALL).
  • java.util.logging.SocketHandler.filter 设置handler使用的filter类 (defaults to no Filter).
  • java.util.logging.SocketHandler.formatter 设置编formatter类 (defaults to java.util.logging.XMLFormatter).
  • java.util.logging.SocketHandler.encoding 设置编码格式 (defaults to the default platform encoding).
  • java.util.logging.SocketHandler.host 设置socket接口的IP地址 (no default).
  • java.util.logging.SocketHandler.port 设置socket接口的port (no default).

 

8.JDK Logging的Formatters

        Formatters的继承关系图如下:


    

    8.1 SimpleFormatter

        SimpleFormatter按照Java使用Java的String.format方法按照格式"%1$tb %1$td, %1$tY %1$tl:%1$tM:%1$tS %1$Tp %2$s%n%4$s: %5$s%6$s%n"来格式化日志进行输出,代码如下:


    8.2 XMLFormatter

        XMLFormatter负责将LogRecord格式化为如下的XML格式:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE log SYSTEM "logger.dtd">
<log>
<record>
  <date>2000-08-23 19:21:05</date>
  <millis>967083665789</millis>
  <sequence>1256</sequence>
  <logger>kgh.test.fred</logger>
  <level>INFO</level>
  <class>kgh.test.XMLTest</class>
  <method>writeLog</method>
  <thread>10</thread>
  <message>Hello world!</message>
</record>
</log>

        XMLFormatter的代码部分注释如下:

public class XMLFormatter extends Formatter {
    private LogManager manager = LogManager.getLogManager();

    // 将小于10的数字前补充0
    private void a2(StringBuffer sb, int x) {
        if (x < 10) {
            sb.append('0');
        }
        sb.append(x);
    }

    // 时间按照ISO 8601标准输出
    private void appendISO8601(StringBuffer sb, long millis) {
        Date date = new Date(millis);
        sb.append(date.getYear() + 1900);
        sb.append('-');
        a2(sb, date.getMonth() + 1);
        sb.append('-');
        a2(sb, date.getDate());
        sb.append('T');
        a2(sb, date.getHours());
        sb.append(':');
        a2(sb, date.getMinutes());
        sb.append(':');
        a2(sb, date.getSeconds());
    }

    // 将于XML字符<>冲突的字符进行转换,如果字符串为null,则输出<null>
    private void escape(StringBuffer sb, String text) {
        if (text == null) {
            text = "<null>";
        }
        for (int i = 0; i < text.length(); i++) {
            char ch = text.charAt(i);
            if (ch == '<') {
                sb.append("&lt;");
            } else if (ch == '>') {
                sb.append("&gt;");
            } else if (ch == '&') {
                sb.append("&amp;");
            } else {
                sb.append(ch);
            }
        }
    }

    /**
     * 转换为如下XML格式的日志输出
     *<?xml version="1.0" encoding="UTF-8" standalone="no"?>
     *<!DOCTYPE log SYSTEM "logger.dtd">
     *<log>
     *<record>
     *  <date>2000-08-23 19:21:05</date>
     *  <millis>967083665789</millis>
     *  <sequence>1256</sequence>
     *  <logger>kgh.test.fred</logger>
     *  <level>INFO</level>
     *  <class>kgh.test.XMLTest</class>
     *  <method>writeLog</method>
     *  <thread>10</thread>
     *  <message>Hello world!</message>
     *</record>
     *</log>
     *
     * @param record 被格式化的日志消息
     * @return 返回被格式化后的XML
     */
    public String format(LogRecord record) {
        StringBuffer sb = new StringBuffer(500);
        sb.append("<record>\n");

        sb.append("  <date>");
        appendISO8601(sb, record.getMillis());
        sb.append("</date>\n");

        sb.append("  <millis>");
        sb.append(record.getMillis());
        sb.append("</millis>\n");

        sb.append("  <sequence>");
        sb.append(record.getSequenceNumber());
        sb.append("</sequence>\n");

        String name = record.getLoggerName();
        if (name != null) {
            sb.append("  <logger>");
            escape(sb, name);
            sb.append("</logger>\n");
        }

        sb.append("  <level>");
        escape(sb, record.getLevel().toString());
        sb.append("</level>\n");

        if (record.getSourceClassName() != null) {
            sb.append("  <class>");
            escape(sb, record.getSourceClassName());
            sb.append("</class>\n");
        }

        if (record.getSourceMethodName() != null) {
            sb.append("  <method>");
            escape(sb, record.getSourceMethodName());
            sb.append("</method>\n");
        }

        sb.append("  <thread>");
        sb.append(record.getThreadID());
        sb.append("</thread>\n");

        if (record.getMessage() != null) {
            // Format the message string and its accompanying parameters.
            String message = formatMessage(record);
            sb.append("  <message>");
            escape(sb, message);
            sb.append("</message>");
            sb.append("\n");
        }

        // 如果消息需要国际化来实现本地化,这里进行处理
        ResourceBundle bundle = record.getResourceBundle();
        try {
            if (bundle != null && bundle.getString(record.getMessage()) != null) {
                sb.append("  <key>");
                escape(sb, record.getMessage());
                sb.append("</key>\n");
                sb.append("  <catalog>");
                escape(sb, record.getResourceBundleName());
                sb.append("</catalog>\n");
            }
        } catch (Exception ex) {
            // The message is not in the catalog.  Drop through.
        }

        Object parameters[] = record.getParameters();
        //  Check to see if the parameter was not a messagetext format
        //  or was not null or empty
        if ( parameters != null && parameters.length != 0
                && record.getMessage().indexOf("{") == -1 ) {
            for (int i = 0; i < parameters.length; i++) {
                sb.append("  <param>");
                try {
                    escape(sb, parameters[i].toString());
                } catch (Exception ex) {
                    sb.append("???");
                }
                sb.append("</param>\n");
            }
        }

        if (record.getThrown() != null) {
            // Report on the state of the throwable.
            Throwable th = record.getThrown();
            sb.append("  <exception>\n");
            sb.append("    <message>");
            escape(sb, th.toString());
            sb.append("</message>\n");
            StackTraceElement trace[] = th.getStackTrace();
            for (int i = 0; i < trace.length; i++) {
                StackTraceElement frame = trace[i];
                sb.append("    <frame>\n");
                sb.append("      <class>");
                escape(sb, frame.getClassName());
                sb.append("</class>\n");
                sb.append("      <method>");
                escape(sb, frame.getMethodName());
                sb.append("</method>\n");
                // Check for a line number.
                if (frame.getLineNumber() >= 0) {
                    sb.append("      <line>");
                    sb.append(frame.getLineNumber());
                    sb.append("</line>\n");
                }
                sb.append("    </frame>\n");
            }
            sb.append("  </exception>\n");
        }

        sb.append("</record>\n");
        return sb.toString();
    }

    /**
     * 返回XML的消息头,也即返回如下格式
     *<?xml version="1.0" encoding="UTF-8" standalone="no"?>
     *<!DOCTYPE log SYSTEM "logger.dtd">
     *<log>
     *
     * @param   h  The target handler (can be null)
     * @return  a valid XML string
     */
    public String getHead(Handler h) {
        StringBuffer sb = new StringBuffer();
        String encoding;
        sb.append("<?xml version=\"1.0\"");

        if (h != null) {
            encoding = h.getEncoding();
        } else {
            encoding = null;
        }

        if (encoding == null) {
            // Figure out the default encoding.
            encoding = java.nio.charset.Charset.defaultCharset().name();
        }
        // Try to map the encoding name to a canonical name.
        try {
            Charset cs = Charset.forName(encoding);
            encoding = cs.name();
        } catch (Exception ex) {
            // We hit problems finding a canonical name.
            // Just use the raw encoding name.
        }

        sb.append(" encoding=\"");
        sb.append(encoding);
        sb.append("\"");
        sb.append(" standalone=\"no\"?>\n");
        sb.append("<!DOCTYPE log SYSTEM \"logger.dtd\">\n");
        sb.append("<log>\n");
        return sb.toString();
    }

    /**
     * 返回XML的尾部,也即返回</log>
     *
     * @param   h  The target handler (can be null)
     * @return  a valid XML string
     */
    public String getTail(Handler h) {
        return "</log>\n";
    }
}

        其中logger.dtd的定义如下:

<!-- DTD used by the java.util.logging.XMLFormatter -->
<!-- This provides an XML formatted log message. -->

<!-- The document type is "log" which consists of a sequence
of record elements -->
<!ELEMENT log (record*)>

<!-- Each logging call is described by a record element. -->
<!ELEMENT record (date, millis, sequence, logger?, level,
class?, method?, thread?, message, key?, catalog?, param*, exception?)>

<!-- Date and time when LogRecord was created in ISO 8601 format -->
<!ELEMENT date (#PCDATA)>

<!-- Time when LogRecord was created in milliseconds since
midnight January 1st, 1970, UTC. -->
<!ELEMENT millis (#PCDATA)>

<!-- Unique sequence number within source VM. -->
<!ELEMENT sequence (#PCDATA)>

<!-- Name of source Logger object. -->
<!ELEMENT logger (#PCDATA)>

<!-- Logging level, may be either one of the constant
names from java.util.logging.Level (such as "SEVERE"
or "WARNING") or an integer value such as "20". -->
<!ELEMENT level (#PCDATA)>

<!-- Fully qualified name of class that issued
logging call, e.g. "javax.marsupial.Wombat". -->
<!ELEMENT class (#PCDATA)>

<!-- Name of method that issued logging call.
It may be either an unqualified method name such as
"fred" or it may include argument type information
in parenthesis, for example "fred(int,String)". -->
<!ELEMENT method (#PCDATA)>

<!-- Integer thread ID. -->
<!ELEMENT thread (#PCDATA)>

<!-- The message element contains the text string of a log message. -->
<!ELEMENT message (#PCDATA)>

<!-- If the message string was localized, the key element provides
the original localization message key. -->
<!ELEMENT key (#PCDATA)>

<!-- If the message string was localized, the catalog element provides
the logger's localization resource bundle name. -->
<!ELEMENT catalog (#PCDATA)>

<!-- If the message string was localized, each of the param elements
provides the String value (obtained using Object.toString())
of the corresponding LogRecord parameter. -->
<!ELEMENT param (#PCDATA)>

<!-- An exception consists of an optional message string followed
by a series of StackFrames. Exception elements are used
for Java exceptions and other java Throwables. -->
<!ELEMENT exception (message?, frame+)>

<!-- A frame describes one line in a Throwable backtrace. -->
<!ELEMENT frame (class, method, line?)>

<!-- an integer line number within a class's source file. -->
<!ELEMENT line (#PCDATA)>

 

9.logging.properties

        logging.properties是JDK Logging的默认配置文件,其默认位置在JDK的安装根路径下的jre/lib下,最近我换成MAC PRO了,所以这里我截图我的logging.properties的配置文件位置如下:

        默认的logging.properties配置文件的内容如下:

 
############################################################
#  	Default Logging Configuration File
#
# You can use a different file by specifying a filename
# with the java.util.logging.config.file system property.  
# For example java -Djava.util.logging.config.file=myfile
############################################################

############################################################
#  	Global properties
############################################################

# "handlers" specifies a comma separated list of log Handler 
# classes.  These handlers will be installed during VM startup.
# Note that these classes must be on the system classpath.
# By default we only configure a ConsoleHandler, which will only
# show messages at the INFO and above levels.
handlers= java.util.logging.ConsoleHandler

# To also add the FileHandler, use the following line instead.
#handlers= java.util.logging.FileHandler, java.util.logging.ConsoleHandler

# Default global logging level.
# This specifies which kinds of events are logged across
# all loggers.  For any given facility this global level
# can be overriden by a facility specific level
# Note that the ConsoleHandler also has a separate level
# setting to limit messages printed to the console.
.level= INFO

############################################################
# Handler specific properties.
# Describes specific configuration info for Handlers.
############################################################

# 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

# Example to customize the SimpleFormatter output format 
# to print one-line log message like this:
#     <level>: <log message> [<date/time>]
#
# java.util.logging.SimpleFormatter.format=%4$s: %5$s [%1$tc]%n

############################################################
# Facility specific properties.
# Provides extra control for each logger.
############################################################

# For example, set the com.xyz.foo logger to only log SEVERE
# messages:
com.xyz.foo.level = SEVERE

 

猜你喜欢

转载自aperise.iteye.com/blog/2411850