0. 概述
在项目开发中,为了跟踪代码的运行情况,常常要使用日志来记录信息。
在Java世界中,有很多的日志工具库来实现日志功能,避免我们重复造轮子,下面我们就来逐一了解日志工具。
1. 最原始的日志记录方式
最早期的Java程序中,使用System.out.println()
把需要显示的内容打印到屏幕,这种方式使用起来非常简单,但是缺点却是非常多的:
- 输出内容不可控
- 可读性差
- 大量的IO操作使程序变慢
public class SystemOutPrintln {
public static boolean debug = false;
public static void main(String[] args) {
for(int count = 0; count < 4; count++) {
if(count % 2 == 0) {
debug = true;
} else {
debug = false;
}
if(debug) {
System.out.println("系统信息:第" + count + "次打印。");
}
}
}
}
2. JDK的Logging
从JDK1.4开始,JDK自带了一套日至系统,其最大的优点是不需要任何其他类库的支持,只要有JDK就可以运行,但是其易用性、功能和扩展性很差,因此在商业上很少使用。
JDK Logging把日志分为9个级别,分别为:ALL、FINEST、FINER、FINE、CONFIG、INFO、WARNING、SERVER、OFF,等级依次升高,较高等级屏蔽较低等级。
下面来看一个例子:
import java.util.logging.ConsoleHandler;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* @JDK 1.8
* @author weegee
*/
public class JDKLogging {
public static final Logger logger = Logger.getLogger(JDKLogging.class.toString());
//初始化日志
static {
//添加一个控制台输出
Handler console = new ConsoleHandler();
//添加到logger中
logger.addHandler(console);
}
public static void main(String[] args) {
//设置日志级别为CONFIG
logger.setLevel(Level.CONFIG);
logger.fine("FINE");
logger.config("CONFIG");
logger.info("INFO");
logger.warning("WARNING");
logger.log(Level.SEVERE,"SERVER");
}
}
输出结果为:
十月 07, 2016 6:34:16 下午 com.weegee.log.Logging.JDKLogging main
信息: INFO
十月 07, 2016 6:34:16 下午 com.weegee.log.Logging.JDKLogging main
信息: INFO
十月 07, 2016 6:34:16 下午 com.weegee.log.Logging.JDKLogging main
警告: WARNING
十月 07, 2016 6:34:16 下午 com.weegee.log.Logging.JDKLogging main
警告: WARNING
十月 07, 2016 6:34:16 下午 com.weegee.log.Logging.JDKLogging main
严重: SERVER
十月 07, 2016 6:34:16 下午 com.weegee.log.Logging.JDKLogging main
严重: SERVER
这里我们会碰到几个问题:
1. 我们会惊奇为什么明明设置Level为CONFIG
,为什么CONFIG
等级的信息没有输出?
2. 为什么信息输出了两次?
3. 输出的日志信息的格式、时间和方法名是怎么来的?
我们依次解答:
1. 对于JDK自带的Logging,会有一个对应的配置文件logging.properties
,位置在$JAVA_HOME/jre/lib/logging.properties
,我们首先看一个原始的配置文件。可以看到里面设置了默认的等级为INFO
,因此INFO
等级以下的信息就不会输出,要想输出其他等级的信息需要修改这里的level信息。
############################################################
# 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
- 信息输出两次的原因是每个Logger都有一个默认的Handler,在配置文件中有这么一行
handlers= java.util.logging.ConsoleHandler
因此我们不需要再给logger添加Handle,在程序中去掉static
代码块的内容就会使日志只打印一次。 在配置文件中
java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
指定了获取得方法名和输出格式。
这里我们就需要了解一下Logging的工作流程。
- Logger
1. 代码需要输入日志的地方都会用到Logger,这几乎是一个JDK logging模块的代言人,用Logger.getLogger(XXX)
获得一个logger,然后使用logger做日志的输出;
2. Logger其实只是一个逻辑管理单元,其多数操作都只是作为一个中继者传递别的<角色>,比如说:Logger.getLogger(“xxx”)
的调用将会依赖于LogManager类,使用Logger输入日志信息的时候会调用Logger中的所有Handler进行日志的输入;
3. Logger是有层次关系的,我们可一般性的理解为包名之间的父子继承关系。每个Logger通常以java包名为其名称。子Logger通常会从父Logger继承Logger级别、Handler、ResourceBundle名(与国际化信息有关)等,例如:
```
public static final Logger logger = Logger.getLogger(JDKLogging.class.toString());
```
和
```
public static final Logger logger = Logger.getLogger("com.weegee.log.Logging");
```
JDKLogging
是包com.weegee.log.Logging
下的java程序,那么通过java程序获得的Logger就继承通过包名获得的Logger,子类会继承父类的输出格式等信息;
4. 整个JVM会存在一个名称为空的root logger,所有匿名的logger都会把root logger作为其父。
- LogManager:整个JVM内部所有logger的管理,logger的生成、获取等操作都依赖于它,也包括配置文件的读取。LogManager中会有一个Hashtable
[private Hashtable<String,WeakReference<Logger>> 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)
,每个级别分别对应一个数字,输出日志时级别的比较就依