在日常的开发中,想必大家听过也接触过很多的日志框架,比如log4j、Logback,JCL、slf4j …,相信很多人在使用这些框架的过程中,不关注也没有想过为什么会有这么五花八门的日志框架以及他们背后的实现异同点,只是盲目的调用相关的API将相关的日志打印出来就可以了,当然这应付一些日常开发自然是可以的,但是当你需要去整合一些历史遗留项目,并且这些项目与你当前系统使用的日志框架不同的时候,如果你对这些框架不够了解,自然会出现一些奇奇怪怪的问题。所以接下来,我们先梳理一下java日志框架的发展历史,并介绍一JDK自带的日志框架。
日志的发展历史
1.从最早期开始,大家都是使用System.out和System.err来打印日志;不灵活也不可以配置;要么全部打印,要么全部不打印;没有一个统一的日志级别
2.后来Log4j就出现了,是由一个java大佬Ceki开发的,后来Log4j成为了Apache基金会项目中的一员
3.后来Java官方也推出了自己的日志框架JUL(Java Util Logging),在package java.util.logging下
4.Apache又推出了日志接口Jakarta Commons Logging,也就是日志抽象层,你就可以很方便的在Log4j和JUL之间做切换
5.Ceki 觉得JCL不好,开发了一套新的日志门面Slf4j(Simple Logging Facade for Java)、它的实现Logback以及一些桥接包:
jcl-over-slf4j.jar :jcl ——> slf4j
slf4j-jcl.jar :slf4j ——> jcl
log4j-over-slf4j :log4j ——> slf4j
slf4j-log4j12.jar :slf4j ——> log4j
jul-to-slf4j :jul ——> slf4j
slf4j-jdk14.jar :slf4j ——> jul
6.后来Apache直接推出新项目,不是Log4j1.x升级,而是新项目Log4j2,因为Log4j2是完全不兼容Log4j1.x的,它也搞了接口与实现分离的设计,分化成log4j-api和log4j-core,这个log4j-api也是日志接口,log4j-core是日志实现,它也出了很多桥接包:
log4j-jcl :jcl ——> log4j2
log4j-1.2-api :log4j ——> log4j2
log4j-slf4j-impl :slf4j ——> log4j2
log4j-jul :jul ——> log4j2
log4j-to-slf4j :log4j2 ——> slf4j
Java.Util.logging.Logger
java.util.logging.Logger是JDK自带的日志工具类,从1.4版本开始就已经有了。由于log4j等开源的日志组件,这个Logger并没有太多展现机会。但在一些测试性的代码中,JDK自带的Logger比log4j等更方便。
简单使用
示例代码
private static final Logger logger=Logger.getLogger(JdkLoggingTest.class.getName());
public static void main(String[] args){
logger.info("jdk logging info: a msg");
}
过程分析
创建一个LogManager,默认是java.util.logging.LogManager,但是也可以自定义,修改系统属性"java.util.logging.manager"即可,源码如下(manager就是LogManager)
try {
cname = System.getProperty("java.util.logging.manager");
if (cname != null) {
try {
Class clz = ClassLoader.getSystemClassLoader().loadClass(cname);
manager = (LogManager) clz.newInstance();
} catch (ClassNotFoundException ex) {
Class clz = Thread.currentThread().getContextClassLoader().loadClass(cname);
manager = (LogManager) clz.newInstance();
}
}
} catch (Exception ex) {
System.err.println("Could not load Logmanager \"" + cname + "\"");
ex.printStackTrace();
}
if (manager == null) {
manager = new LogManager();
}
加载配置文件,默认是jre目录下的lib/logging.properties文件,也可以自定义修改系统属性"java.util.logging.config.file”,源码如下:
String fname = System.getProperty("java.util.logging.config.file");
if (fname == null) {
fname = System.getProperty("java.home");
if (fname == null) {
throw new Error("Can't find java.home ??");
}
File f = new File(fname, "lib");
f = new File(f, "logging.properties");
fname = f.getCanonicalPath();
}
InputStream in = new FileInputStream(fname);
BufferedInputStream bin = new BufferedInputStream(in);
try {
readConfiguration(bin);
}
创建Logger,并缓存起来,放置到一个Hashtable中,并把LogManager设置进新创建的logger中:
修改属性"java.util.logging.manager”,自定义LogManager
修改属性"java.util.logging.config.file”,自定义配置文件
JDK Logging相关对象
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作为其父。
Logger Manager
1.LogManager:整个JVM内部所有logger的管理,logger的生成、获取等操作都依赖于它,也包括配置文件的读取。
2.LogManager中会有一个Hashtable【private Hashtable<String,WeakReference> loggers】用于存储目前所有的logger,如果需要获取logger的时候,Hashtable已经有存在logger的话就直接返回Hashtable中的,如果hashtable中没有logger,则新建一个同时放入Hashtable进行保存。
Handler
1.Handler:用来控制日志输出的,比如JDK自带的ConsoleHanlder把输出流重定向到System.err输出,每次调用Logger的方法进行输出时都会调用Handler的publish方法,每个logger有多个handler。
2.我们可以利用handler来把日志输入到不同的地方(比如文件系统或者是远程Socket连接)。
Formatter
Formatter:日志在真正输出前需要进行一定的格式化,比如是否输出时间?时间格式?是否输入线程名?是否使用国际化信息等都依赖于Formatter。
Log Level
Log Level:不必说,这是做容易理解的一个,也是logging为什么能帮助我们适应从开发调试到部署上线等不同阶段对日志输出粒度的不同需求。JDK Log级别从高到低为:
OFF(2^31-1) —>SEVERE(1000)—>WARNING(900)—>INFO(800)—>CONFIG(700)—>FINE(500)—>FINER(400)—>FINEST(300)—>ALL(-2^31)
每个级别分别对应一个数字,输出日志时级别的比较就依赖于数字大小的比较。
但是需要注意的是:不仅是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也是一对一关系。