本来是打算研究下Tomcat中的Logger实现方式的,不过在跟踪的过程中发现了不少JDK中Logger的影子。于是本着磨刀不误砍柴工的理念,便有了这篇文章。
1. 使用
JDK自带的Log使用起来还是很简单的。
@Test
public void info() throws Exception {
Logger logger = Logger.getLogger("LQ");
logger.info("this is a info log - LQ");
}
2. 指定配置文件
JDK默认提供的关于Log的配置文件位于JAVA_HOME/jre/lib/logging.properties
。但如果我们想要指定自定义的配置文件,则需要额外作一些配置。
1. 注意这里指定的路径不能是相对于Classpath的路径,而必须是文件系统路径。
2. 这里不能通过在代码中使用System.setProperty("java.util.logging.config.file", logFileFullPath);
来完成相同的效果。原因是日志管理器在VM启动过程中被初始化,它会在你的代码之前执行。
3. 配置文件解析
############################################################
# 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.
# 多个Handler使用 , 分割
#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.
# 日志默认是输出到用户目录下, 而默认的起始索引是从0开始, 所以这里生成的第一个文件为java0.log
java.util.logging.FileHandler.pattern = %h/java%u.log
java.util.logging.FileHandler.limit = 50000
java.util.logging.FileHandler.count = 1
# 日志内容的默认格式是XML
java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter
# Limit the message that are printed on the console to INFO and above.
# 配置处理器的级别. 注意Logger并不会将信息发送到控制台, 真正做这件事的是处理器(Handler)
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:
# 这里是指定自己的日志记录级别, 我们一般是采用`Logger.getLogger(this.getClass().getName())`这种方式来获取Logger实例, 此时我们就可以采用下面这种方式来配置专门的日志级别
com.xyz.foo.level = SEVERE
com.yourcompany.project.controller.level=FINE
LQ = FINE
4. 相关设计
JDK中的日志功能由java.util.logging
package中的类来完成。
非常清晰的结构,看下这些类的命名基本就能大概猜出处理流程和其中应用的设计模式。这里我们挑几个关键接口稍微深入下:
4.1 Filter
接口
- 在默认情况下,过滤器根据日志记录的级别进行过滤。
- 但是在某些情况下我们需要加入自定义的过滤逻辑。此时我们就需要借助这个接口(在实现的方法中返回true来表示这些记录应该包含在日志中)。
- 注入自定义
Filter
的逻辑是通过Logger
或Handler
提供的setFilter
方法来完成的。
// --------------------------------- 自定义Filter
public class CustomFilter implements java.util.logging.Filter {
@Override
public boolean isLoggable(LogRecord record) {
String message = record.getMessage();
System.out.println(message);
// 返回true来表示这些记录应该包含在日志中
return false;
}
}
// --------------------------------- 测试
@Test
public void filter() throws Exception {
Logger logger = Logger.getLogger("LQ");
// 通过setFilter方法嵌入自定义逻辑
logger.setFilter(new CustomFilter());
logger.fine("this is a fine log");
}
// 输出结果; 可以看到标准的日志格式内容没有输出
this is a fine log
4.2 Formatter
抽象类
JDK默认提供了简单文本格式(SimpleFormatter
)和XML格式(XMLFormatter
)的日志内容。但是如果有自定义的需求,JDK也是提供了可能性。
public class CustomFormatter extends Formatter {
@Override
public String format(LogRecord record) {
return "LQ - " + record.getMessage();
}
}
// --------------------------------- 测试
@Test
public void formatter() throws Exception {
Logger logger = Logger.getLogger("LQ");
logger.getParent().getHandlers()[0].setFormatter(new CustomFormatter());
logger.fine("this is a fine log");
}
// 输出结果; 可以看到默认的日志格式被修改
LQ - this is a fine log
4.3 Handler
抽象类
关于日志内容的处理,并不是Logger自身完成的, 它只是简单地将这项工作交给了处理器(Handler)。
public class CustomHandler extends StreamHandler {
public CustomHandler() {
super();
setOutputStream(System.out);
}
}
// --------------------------------- 测试
@Test
public void handler() throws Exception {
// 关于这个的测试, 就得去配置文件里注册相应的Handler了
Logger logger = Logger.getLogger("LQ");
//logger.getParent().getHandlers()[0] = new CustomHandler();
logger.fine("this is a fine log");
}
// 输出结果可以看出, 由原本输出到System.err变成了我们自定义指定的System.out
4.4 LogRecord
类
这个类在JDK的日志框架中的作用类似于 会话域, 或者说 《程序员修炼之道–从小工到专家》中的“黑板”概念。
4.5 LogManager
类
作为一个全局的单例对象,本类大概完成以下两个任务:
1. 维护一组层次结构的Logger对象。保存在 内部类LogManager.LoggerContext
中的namedLoggers
字段中。
2. 管理着一组有关Logger的属性值。存储在LogManager
中的props
字段中。
3. 这里还请关注一下本类的静态构造块。在静态构造块的逻辑里,会查找系统属性java.util.logging.manager
对应的值,如果发现不为空,则将其实例化作为LogManager
的实现者。这样就达到了允许外界自定义扩展的需求。而我们将要研究的Tomcat日志正是利用了这个特性。
5. Links
- 《Java核心技术 卷1 基础知识(第9版)》 P512
- Tomcat的Logging(1)