目录
- 从例子开始
- 术语解答
- 总结对应关系
- Logging配置:
- Logging执行原理
- 总结
日志输出是所有系统必备的,很多开发人员可能因为常常使用log4j而忽视了JDK logging模块,两者之间是否有联系?是怎样的联系?JDK logging处理细节是怎么样的?本周抛砖引玉,先分析JDK logging机制。
从例子开始
JDK Logging的使用很简单,如下代码所示,先使用Logger类的静态方法getLogger就可以获取到一个logger,然后在任何地方都可以通过获取到的logger进行日志输入。
比如类似logger.info("Main running.")的调用。
package com.bes.logging;
import java.util.logging.Level;
import java.util.logging.Logger;
public class LoggerTest {private static Loggerlogger = Logger.getLogger("com.bes.logging");
public static void main(String argv[]) {// Log a FINEtracing messagelogger.info("Main running.");
logger.fine("doingstuff");
try {Thread.currentThread().sleep(1000);
// do some work} catch(Exception ex) {logger.log(Level.WARNING,"trouble sneezing", ex);
}logger.fine("done");
}}
不做任何代码修改和JDK配置修改的话,运行上面的例子,你会发现,控制台只会出现【Main running.】这一句日志。如下问题应该呈现在你的大脑里…
1,【Main running.】以外的日志为什么没有输出?怎么让它们也能够出现?
2,日志中出现的时间、类名、方法名等是从哪里输出的?
3,为什么日志就会出现在控制台?
4,大型的系统可能有很多子模块(可简单理解为有很多包名),如何对这些子模块进行单独的日志级别控制?
5,扩充:apache那个流行的log4j项目和JDK的logging有联系吗,怎么实现自己的LoggerManager?
带着这些问题,可能你更有兴趣了解一下JDK的logging机制,本章为你分析这个简单模块的机制。
术语解答
在深入分析之前,需要掌握以下术语
logger: 对于logger,需要知道其下几个方面
1,代码需要输入日志的地方都会用到Logger,这几乎是一个JDK logging模块的代言人,我们常常用Logger.getLogger("com.aaa.bbb");
获得一个logger,然后使用logger做日志的输出。
2,logger其实只是一个逻辑管理单元,其多数操作都只是作为一个中继者传递别的<角色>,比如说:Logger.getLogger(“xxx”)的调用将会依赖于LogManager类,使用logger输入日志信息的时候会调用logger中的所有handler进行日志的输入。
3,logger是有层次关系的,我们可一般性的理解为包名之间的父子继承关系。每个logger通常以java包名为其名称。子logger通常会从父logger继承logger级别、handler、ResourceBundle名(与国际化信息有关)等。
4,整个JVM会存在一个名称为空的root logger,所有匿名的logger都会把root logger作为其父
LogManager
:整个JVM内部所有logger的管理,logger的生成、获取等操作都依赖于它,也包括配置文件的读取。LogManager中会有一个Hashtable
private Hashtable> loggers
用于存储目前所有的logger,如果需要获取logger的时候,Hashtable已经有存在logger的话就直接返回Hashtable中的,如果hashtable中没有logger,则新建一个同时放入Hashtable进行保存。
Handler
:用来控制日志输出的,比如JDK自带的ConsoleHanlder把输出流重定向到System.err输出,每次调用Logger的方法进行输出时都会调用Handler的publish方法,每个logger有多个handler。我们可以利用handler来把日志输入到不同的地方(比如文件系统或者是远程Socket连接).
Formatter
:日志在真正输出前需要进行一定的格式话:比如是否输出时间?时间格式?是否输入线程名?是否使用国际化信息等都依赖于Formatter。
Log Level
:不必说,这是做容易理解的一个,也是logging为什么能帮助我们适应从开发调试到部署上线等不同阶段对日志输出粒度的不同需求。
JDK Log级别从高到低为
OFF(231-1)—>SEVERE(1000)—>WARNING(900)—>INFO(800)—>CONFIG(700)—>FINE(500)—>FINER(400)—>FINEST(300)—>ALL(-231)
每个级别分别对应一个数字,输出日志时级别的比较就依赖于数字大小的比较。但是需要注意的是:不仅是logger具有级别,handler也是有级别,也就是说如果某个logger级别是FINE,客户希望输入FINE级别的日志,如果此时logger对应的handler级别为INFO,那么FINE级别日志仍然是不能输出的。
总结对应关系
LogManager与logger是1对多关系,整个JVM运行时只有一个LogManager,且所有的logger均在LogManager中
logger与handler是多对多关系,logger在进行日志输出的时候会调用所有的hanlder进行日志的处理
handler与formatter是一对一关系,一个handler有一个formatter进行日志的格式化处理
很明显:logger与level是一对一关系,hanlder与level也是一对一关系
Logging配置:
JDK默认的logging配置文件为:$JAVA_HOME/jre/lib/logging.properties,可以使用系统属性java.util.logging.config.file指定相应的配置文件对默认的配置文件进行覆盖,配置文件中通常包含以下几部分定义:
1,
handlers
:用逗号分隔每个Handler,这些handler将会被加到root logger中。也就是说即使我们不给其他logger配置handler属性,在输出日志的时候logger会一直找到root logger,从而找到handler进行日志的输入。
2,
.level
是root logger的日志级别
3,.xxx是配置具体某个handler的属性,比如java.util.logging.ConsoleHandler.formatter便是为ConsoleHandler配置相应的日志Formatter.
4,logger的配置,所有以[.level]结尾的属性皆被认为是对某个logger的级别的定义
如com.bes.server.level=FINE是给名为[com.bes.server]的logger定义级别为FINE。
顺便说下,前边提到过logger的继承关系,如果还有com.bes.server.webcontainer这个logger,且在配置文件中没有定义该logger的任何属性,那么其将会从[com.bes.server]这个logger进行属性继承。除了级别之外,还可以为logger定义handler和useParentHandlers(默认是为true)属性,
如com.bes.server.handler=com.bes.test.ServerFileHandler(需要是一个extends java.util.logging.Handler的类),
com.bes.server.useParentHandlers=false
(意味着com.bes.server这个logger进行日志输出时,日志仅仅被处理一次,用自己的handler输出,不会传递到父logger的handler)。
以下是JDK配置文件示例
handlers= java.util.logging.FileHandler,java.util.logging.ConsoleHandler.level= INFOjava.util.logging.FileHandler.pattern = %h/java%u.logjava.util.logging.FileHandler.limit = 50000java.util.logging.FileHandler.count = 1java.util.logging.FileHandler.formatter =java.util.logging.XMLFormatterjava.util.logging.ConsoleHandler.level = INFOjava.util.logging.ConsoleHandler.formatter =java.util.logging.SimpleFormattercom.xyz.foo.level = SEVEREsun.rmi.transport.tcp.logLevel = FINE
Logging执行原理
Logger的获取
A,首先是调用Logger的如下方法获得一个logger
public static synchronized Logger getLogger(String name) {LogManager manager =LogManager.getLogManager();
returnmanager.demandLogger(name);
}
B,上面的调用会触发java.util.logging.LoggerManager的类初始化工作,LoggerManager有一个静态化初始化块(这是会先于LoggerManager的构造函数调用的):
static {AccessController.doPrivileged(newPrivilegedAction