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的类图吧: