深入源码之JDK Logging

JDK从1.4开始提供Logging实现,据说当初JDK打算采用Log4J的,后来因为某些原因谈判没谈拢,然后就自己开发了一套,不知道是为了报复而故意不沿用Log4J的命名方式和抽象方式,还是开发这个模块的人水平不够,或没用心,亦或是我用Commons Logging和Log4J习惯了,看JDK的Logging实现怎么看怎么不爽~~~吐个槽额~~~~

JDK Logging将日志打印抽象成以下几个类之间的交互:

1.    Level,定义日志的级别,类似Log4J中的Level类。

JDK Logging采用了完全不同于Log4J中对级别的抽象。在JDK Logging中,默认定义了以下几个级别:SEVERE(对应Log4J中的ERROR或FATAL)、WARNING(对应Log4J中的WARN)、INFO(对应Log4J中的INFO)、CONFIG(对应Log4J中的DEBUG)、FINE(对应Log4J中的TRACE)、FINER(对应Log4J中的TRACE)、FINEST(对应Log4J中的TRACE)。另外,类似Log4J,JDK Logging也定义了两个特殊的级别:ALL和OFF,分别对应打印所有级别的日志和关闭日志打印。

Level中包含三个字段:name、value、resourceBundleName。其中name指定级别名称,value指定该级别对应的一个int值,其值从SEVERE开始依次递减,resourceBundleName定义本地化后的级别名称,默认是sun.util.logging.resources.logging,即我们在日志中看到警告、信息等级别字段就是通过调用Level的getLocalizedName()方法,读取resourceBundleName对应的resource值来获得的,这也是Log4J中没有听过的。

Level还定义了一个parse()方法,它可以支持解析name字符串、代表级别的int值(以字符串的形式)、以及对应的Localized名称,如果所有的都不满足需求,则抛出IllegalArgumentException。

最后,Level还实现了readResolve()方法,从而确保反序列化后的Level只是Level类中定义的几个实例(出了自定义的Level实例)。

2.    LogRecord,封装了打印一条一直所包含的所有数据,类似Log4J中的LoggingEvent。

它包含了以下信息:

a.    日志级别(level)

b.    全局标识号(sequenceNumber,即没创建一个LogRecord,sequenceNumber都会自增1)

c.    打印这条日志语句所在的类的名称(sourceClassName),可以通过调用inferCaller()方法解析出来。解析实现则是通过实例化一个Throwable实例,通过解析该实例的Call Stack即可得出打印这条日志所在的类的名称和方法名称,这里貌似不支持行号、文件名等信息。并且通过设置needToInferCaller字段(不可序列化)来判断sourceClassName和sourceMethodName是否已经取得,从而不用每次调用的时候都去解析而提升性能。

d.    打印这条日志语句所在调用方法的名称(sourceMethodName),它也是通过调用inferCaller()方法解析出来。

e.    打印消息(message)

f.     线程号(threadID),对LogRecord本身而言,从0开始对每个线程自增1

g.    创建LogRecord的时间(millis)

h.    打印日志中的异常(thrown)

i.      日志名称(loggerName)

j.     资源名称(resourceBundleName)

k.    参数列表(parameters数组),在Formatter中通过MessageFormat使用这些参数列表,并在序列化是手动调用其toString()方法序列化,而不是采用默认的序列化方式。

l.      ResourceBundle实例,获取本地消息,不可序列化。

3.    Formatter,根据配置格式化一条日志记录,类似Log4J中的Layout。

JDK Logging支持两种类型的Formatter:SimpleFormatter和XMLFormatter,默认采用SimpleFormatter,它先打印日期和时间、LoggerName或source ClassName、方法名称,然后换行,在打印日志级别、本地化后的消息,然后换行,打印异常信息。而XMLFormatter实现getHead()、getTail()方法,并且将每条记录写成一条<record></record>记录。吐槽一下,它的实现是在是太不专业了~~。

4.    Handler,实现将日志写入指定目的地,如ConsoleHandler、FileHandler、SocketHandler即对应将日志写入控制台、文件、Socket端口。它类似Log4J中的Appender。

Handler包含对Formatter、Level、Filter的引用,其中Formatter将LogRecord格式化成String字符串;Level定义当前Handler支持的日志级别;而Filter则提供一个用户自定义的过滤一些日志的扩展点。

public interface Filter {

        public boolean isLoggable(LogRecord record);

}

用户可以定义自己的Filter类以实现用户自定义的过滤逻辑。Handler还定义了encoding属性,以配置底层日志的输出格式。

Handler中最终要的方法是publish(),它实现了真正打印日志消息的逻辑。

public abstract void close() throws SecurityException;

 

默认JDK Logging实现了StreamHandler,而StreamHandler有三个子类:ConsoleHandler、FileHandler、SocketHandler。StreamHandler支持的配置有:

java.util.logging.StreamHandler.level 设置当前Handler支持的级别,默认为FINE

java.util.logging.StreamHandler.filter 设置当前Handler的Filter,默认为null

java.util.logging.StreamHandler.formatter 设置当前Handler的Formatter类,默认为SimpleFormatter

java.util.logging.StreamHandler.encoding 设置当前Handler的编码方式,默认为null

 

ConsoleHandler只是将OutputStream设置为System.err,其他实现和StreamHandler类似。

 

而SocketHandler将OutputStream绑定到对应的端口号中,其他也和StreamHandler类似。另外它还增加了两个配置:java.util.logging.SocketHandler.port和java.util.logging.SocketHandler.host分别对应端口号和主机。

 

FileHandler支持指定文件名模板(java.util.logging.FileHandler.pattern),文件最大支持大小(java.util.logging.FileHandler.limit,字节为单位,0为没有限制),循环日志文件数(java.util.logging.FileHandler.count)、对已存在的日志文件是否往后添加(java.util.logging.FileHandler.append)。

FileHandler支持的文件模板参数有:

/     目录分隔符

%t   系统临时目录

%h 系统当前用户目录

%g 生成的以区别循环日志文件名

%u 一个唯一的数字以处理冲突问题

%% 一个%

5.    LogManager类,读取配置文件和管理Logger实例,类似Log4J的LogRepository。

在LogManager类初始化时,用户可以通过指定java.util.logging.manager系统属性以自定义LogManager,默认使用LogManager类本身。初始化完成后创建RootLogger,并将新创建的RootLogger实例加入到LogManager中。LogManager中将所有创建的Logger缓存在loggers字段中(Hashtable,name作为key,WeakReference<Logger>作为value)。RootLogger的name为空,因而所有对RootLogger的配置都从”.”开始。

 

对Logger的配置支持一下几种方式:

<name>.level=INFO|CONFIG….

handers=<handlerName1>,<handlerName2>…. (以”,”分隔或以空格、TAB等字符分隔,全局Handler)

config=<configClassName1>,<configClassName2>….(自定义类,实现在其构造函数中实现自定义配置)

<name>.userParentHandlers=true|false|1|0

<name>.handlers=<handlerName1>,<handlerName2>…(以”,”分隔或以空格、TAB等字符分隔)

<handlerName>.level=INFO|CONFIG….

以及各自Handler本身支持的配置,具体各自的Handler。

 

用户可以通过以下方式自定义配置文件:

a.    设置系统属性java.util.logging.config.class,由自定义类的构造函数实现自定义配置,如调用LogManager、Logger中的一些静态方法。

b.    设置系统属性java.util.logging.config.file,自定义配置文件路径。读取该文件中的内容作为配置信息。

c.    默认使用${java.home}/lib/logging.properties文件作为配置文件(JDK已经提供了一些默认配置,一般是${JRE_HOME}/lib/logging.properties文件)

 

类似Log4J,LogManager也将Logger构建成树状结构,并且对那些暂时没有真正Logger实例的节点,使用LogNode,同样构建成树,这个实现也类似Log4J的ProvisionNode。

6.    Logger类,用户打印log接口,类似Log4J中的Logger。

Logger包含name、Handlers、resourceBundleName、useParentHandlers、filter、anonymous、levelObject、parent、kids等字段。其中其他字段都比较容易理解,anonymous比较难理解,按注释,它是用来表达当前Logger是一个匿名Logger,即不会被加入到LogManager中,因而也不需要安全检查,匿名Logger一般在Applet中使用,对Applet不了解,因而也无法做更详细的解释。在构建树时,该Logger同时保持了父节点和子节点的所有引用,对JDK这个Logging的实现一直无力吐槽。

Logger提供getLogger()接口,这个也是一般用户直接打交道的接口,传入name,返回缓存的Logger实例或新创建一个Logger实例。

对匿名Logger,Logger提供getAnonymousLogger()接口。log(LogRecord)方法是对打印日志的真正实现:

public void log(LogRecord record) {

    if (record.getLevel().intValue() < levelValue || levelValue == offValue) {

        return;

    }

    synchronized (this) {

        if (filter != null && !filter.isLoggable(record)) {

            return;

        }

    }

    Logger logger = this;

    while (logger != null) {

        Handler targets[] = logger.getHandlers();

        if (targets != null) {

            for (int i = 0; i < targets.length; i++) {

                targets[i].publish(record);

            }

        }

        if (!logger.getUseParentHandlers()) {

            break;

        }

        logger = logger.getParent();

    }

}

其他log方法只是对该方法中LogRecord中不同字段参数的组合。其他logp()系类方法只是提供给用户自定log所在的方法名和类名;而entering、existing、thrown等几个方法只是几个傻逼的命名而已。

 

最后给张JDK Logging的类图吧:

时间: 2024-09-19 06:41:46

深入源码之JDK Logging的相关文章

深入源码之Commons Logging

自从七月份去走川藏后,已经好几个月没有更新博客了.其实八月底从拉萨回来后一直在Spring的代码,也想写几篇关于Spring源码的解读,可惜Spring实在是太复杂了,花了我一个多月的时间,框架大体流程是有头绪了,但是具体实现和各个模块的具体细节还都不是很清楚,迟迟不敢动笔.其实原本我不想回到Logging这一块,我知道光看完Log4J的代码还不够,也感觉Log在系统中其实占据了蛮重要的位置(虽然很多人都没有意识到),不过一般Log框架使用简单,遇到问题也比较少,即使看完源码对实际工作也帮助不大

java源码-为什么jdk中的类不能被重写啊?

问题描述 为什么jdk中的类不能被重写啊? 是jvm有什么保护措施吗?我的意思就是,假设:我自己写一个String类会和jdk中的冲突,也就报错,写不了,为什么呢?跪求 解决方案 这是Java的类加载机制决定的,Java使用的是委托父类加载,所有的Java类库都是由指定的类加载器加载的,即使你定义一个一模一样的String类,最终引用加载的仍然是Java类库的类.这就是Java防止篡改的原理. 解决方案二: 当然是这样,否则很可怕了.比如说你的程序中有一个加密的逻辑,有人编写一个恶意的插件程序,

深入理解Tomcat系列之二:源码调试环境搭建

前言 最近对Tomcat的源码比较感兴趣,于是折腾了一番.要调试源码首先需要搭建环境,由于参考了几篇帖子发现都不怎么靠谱,最后还是折腾出来了,然而却花了足足一天的时间去搭建这个环境.发现都不是帖子的问题,主要是自己在搭建过程中忽略了一些细节,最后构建工程的时候一直失败,我也是醉了.所以本着共享的原则,把一些关键的步骤以及一些需要注意的细节写在博客中以飨读者. 下载Tomcat7源码 下载源码有多种方式,可以通过SVN直接拷贝到本地,svn地址在这里 下载之后源码的目录是这样的: 注意:要把bui

代码-JAVA源码应该怎么分模块进行解析与学习?

问题描述 JAVA源码应该怎么分模块进行解析与学习? 最近看过了JAVA虚拟机这本书,对JAVA源码也产生了兴趣,可是面对这么多的代码,无从下手,所以请大家给一下比较好的建议 解决方案 先找个工作,慢慢学习,在工作中遇到问题的时候,或者有空闲的时候,就开始看源码.可以从一个包一个包开始看.推荐从java.util包开始. 解决方案二: 可以先找几个常用的包看看,比如math,util什么的. 解决方案三: 一般来说,你应该知道源码的功能,按功能来划分模块. 解决方案四: 建议楼主不要单纯的为了看

请问jdk源码该如何研究

问题描述 请问jdk源码该如何研究 各位大神,小弟最近想看看jdk源码,想看看里面各个功能是怎么实现的,源码在myeclipse里面可以看到,但是不知道该如何开始,应该从哪里开始,希望各位大神指点迷津,谢谢. 解决方案 1.知道这个API是干嘛的:(可以通过注释知道,英文水平好的可以看原版API文档,也可以下个中文版的API文档)2.Ctrl+O键,看里面有什么方法,先看构造方法,再看其他功能方法3.你喜欢研究那个方法就看那个方法吧.希望采纳,谢谢 解决方案二: 去oracle官网下载jdk使用

从JDK源码角度看Float

关于IEEE 754 在看Float前需要先了解IEEE 754标准,该标准定义了浮点数的格式还有一些特殊值,它规定了计算机中二进制与十进制浮点数转换的格式及方法.规定了四种表示浮点数值的方法,单精确度(32位).双精确度(64位).延伸单精确度(43位以上)与延伸双精确度(79位以上).多数编程语言支持单精确度和双精确度,这里讨论的Float就是Java的单精确度的实现. 浮点数的表示 浮点数由三部分组成,如下图,符号位s.指数e和尾数f. 对于求值我们是有一个公式对应的,根据该公式来看会更简

Android源码浅析(二)——Ubuntu Root,Git,VMware Tools,安装输入法,主题美化,Dock,安装JDK和配置环境

Android源码浅析(二)--Ubuntu Root,Git,VMware Tools,安装输入法,主题美化,Dock,安装JDK和配置环境 接着上篇,上片主要是介绍了一些安装工具的小知识点Android源码浅析(一)--VMware Workstation Pro和Ubuntu Kylin 16.04 LTS安装配置,其实Ubuntu Kylin 16.04 LTS也只是为了体验,我们为了追求稳定,还是使用了Ubuntu14.04 这里提供一个国内镜像的下载链接,可以用迅雷,下载下来之后后缀

从JDK源码角度看java并发的公平性

        JAVA为简化开发者开发提供了很多并发的工具,包括各种同步器,有了JDK我们只要学会简单使用类API即可.但这并不意味着不需要探索其具体的实现机制,本文从JDK源码角度简单讲讲并发时线程竞争的公平性.         所谓公平性指所有线程对临界资源申请访问权限的成功率都一样,不会让某些线程拥有优先权.我们知道CLH Node FIFO等待队列是一个先进先出的队列,那么是否就可以说每条线程获取锁时就是公平的呢?关于公平性这里分拆成三个点分别阐述:         ① 准备入队列的节

Eclipse 如何查看jdk源码

作者:jiankunking 出处:http://blog.csdn.net/jiankunking 1.首先,在安装jdk的时候要安装源码,或许你没注意到,其实源码就在我们的jdk安装目录下面.(安装的时候是可以选择源码安装路径的,如果选择了其他路径就不会在jdk下面了,默认是在jdk下面的.) 2.打开eclipse,点 "window"-> "Preferences" -> "Java" -> "Installe