Java原⽣⽇志java.util.logging
简介
Java 中的 Logging API 让 Java 应⽤可以记录不同级别的信息,它在debug过程中⾮常有⽤,如果系统因为各种各样的原因⽽崩溃,崩溃原因可以在⽇志中清晰地追溯,下⾯让我们来看看 Java 原⽣的 Logging 功能。
从1.4.2开始,Java 通过 Java.util.logging 包为应⽤程序提供了记录消息的可能,在 API 中的核⼼类为 Logger 类。理解在记录消息中的⽇志的不同级别是⾮常重要的。Java 为此定时了8个级别,它们是分别SEVERE, WARNING, INFO, CONFIG, FINE, FINER, FINEST 以及 ALL.它们按照优先级降序排列,在应⽤运⾏的任何时间点,⽇志级别可以被更改。
通常来说,当为 Logger 指定了⼀个 Level, 该 Logger 会包含当前指定级别以及更⾼级别的⽇志。举例⽽⾔,如果 Level 被设置成了WARNING, 所有的 warning 消息以及 SERVER 消息会被记录。应⽤可以⽤下列⽅法记录⽇志:Logger.warning(), Logger.info(), fig() ...
⼯作原理和⽇志处理流程
⼏个重要类的说明
Logger 对外发布的⽇志记录器,应⽤系统可以通过该对象完成⽇志记录的功能
Level ⽇志的记录级别
LoggingMXBean 接⼝对象,对外发布的⽇志管理器
LogRecord ⽇志信息描述对象
LoggerManager ⽇志管理器
Filter ⽇志过滤器,接⼝对象,在⽇志被 Handler 处理之前,起过滤作⽤
Handler ⽇志处理器,接⼝对象,决定⽇志的输出⽅式
Formatter ⽇志格式化转换器,接⼝对象,决定⽇志的输出格式
⼯作原理
⾸先通过LoggerManager进⾏⽇志框架的初始化,⽣成Logger的根节点RootLogger. 这⾥需要注意的是LoggerManager的初始化⼯作,并没有将构建配置⽂件中所有的⽇志对象,⽽仅仅是构建了根节点,这种⽅式就是我们多例模式中经常⽤到的懒加载,对象只有在真正被时候的时候,再进⾏构建。
通过Logger(String name) 获取⼀个已有的Logger对象或者是新建⼀个Logger对象。Logger,⽇志记录器,这就是在应⽤程序中需要调⽤的对象了,通过Logger对象的⼀系列log⽅法,
Logger的⼤致处理流程
收到应⽤程序的记录请求,将参数中的⽇志信息和运⾏时的信息构建出LogRecord对象,⽽后通过Logger对象本⾝设置的记录级别和调⽤者传递进来的⽇志级别,如果传递进来的⽇志级别低于Logger对象本⾝设置的记录级别(从语义上的理解,⽽实际上语义级别越⾼的级别其内部⽤数字表⽰的标志的数值越⼩),那么Logger对象将直接返回,因为他认为这条⽇志信息,在当前运⾏环境中,没有必要记录。
⽽满⾜以上条件的⽇志信息,将会通过Logger对象的filter元素的过滤校验,filter是动态的,在运⾏时是
可以随意设置的,如果有filter对象,那么将调⽤filter对象,对⽇志对象LogRecord进⾏校验,只有校验通过的LogRecord对象,才会继续往下执⾏。
通过filter校验后,Logger对象将依次调⽤其配置的处理器,通过处理器来真正实现⽇志的记录功能,⼀个Logger对象可以配置多个处理器handler,所以⼀条⽇志记录可以被多个处理器处理,同时Logger对象的实现是树形结构,如果Logger对象设置其可以继承其⽗节点的处理器(默认),⼀条⽇志记录还会被其⽗节点的Logger对象处理。⽽handler的处理⽅式就会是形形⾊⾊了,但是归根节点,会有以下⼏个⼤的步骤:
1. 级别的判定和⽐较,决定某条具体的⽇志记录是否应该继续处理
2. 将⽇志记录做格式化处理,以达到输出的⽇志在格式上统⼀,美观,可读性⾼。
3. 资源的释放,不管是以何种⽅式记录⽇志,总是会消耗⼀些⽅⾯的资源,所以
会涉及到资源的释放问题。⽐如以⽂件⽅式记录的⽇志的,在⼀定的时候需要做⽂件关闭操作,以报⽂⽅式发送⽇志的,在和远程通话的过程中,也需要涉及到⽹络IO的关闭操作,或者是存储在数据库等等,资源释放在程序开发过程中,是个不变的主题。
从⼀个⽰例讲起
public class TestLogger {
public static void main(String[] args) {
Logger log = Logger("lavasoft");
log.info("aaa");
}
}
console output:
>>> aaa
以上简单的代码背后发⽣那些事
1. LoggerManager 将会返回⼀个新的或者已经存在的同名的 Logger , ⾸先会查是否有同名 Logger 被 namedLoggers 维护有则返回, 但
是在我们这个⽰例中⼤多是重新⽣成⼀个 Logger,⾸先 LoggerManager 会读取系统配置,设定⼀个默认的的 INFO 级别的 Logger, 然后也许跟其他线程抢到⼀个 Logger 后返回
tips:
默认的Java⽇志框架将其配置存储到⼀个名为 logging.properties 的⽂件中。
在这个⽂件中,每⾏是⼀个配置项,配置项使⽤点标记(dot notation)的形式。
Java在其安装⽬录的lib⽂件夹下⾯安装了⼀个全局配置⽂件,但在启动⼀个Java程序时,
你可以通过指定 java.fig.file 属性的⽅式来使⽤⼀个单独的⽇志配置⽂件,
同样也可以在个⼈项⽬中创建和存储 logging.properties ⽂件。
Logger 中召唤 LoggerManager ⽚段
---------------------------
public static Logger getLogger(String name) {
LogManager manager = LogManager();
return manager.demandLogger(name);
}
LoggerManager 中产⽣ Logger 的⽚段
-----------------------------
Logger demandLogger(String name) {
Logger result = getLogger(name);
if (result == null) {
Logger newLogger = new Logger(name, null);
do {
if (addLogger(newLogger)) {
return newLogger;
}
result = getLogger(name);
} while (result == null);
}
return result;
}
LoggerManager 中维护了⼀个有继承关系的含有弱引⽤的 LoggerWeakRef
-------------------------------
private Hashtable<String,LoggerWeakRef> namedLoggers = new Hashtable<>();
LoggerWeakRef 类结构
-----------------
final class LoggerWeakRef extends WeakReference<Logger> {
private String                name;      // for namedLoggers cleanup
private LogNode              node;      // for loggerRef cleanup
private WeakReference<Logger> parentRef;  // for kids cleanup
以上两者维护了JVM中弱引⽤的 Loggers ⽗⼦结构
2. log.info()
Logger 中的 info(String msg) ⽅法
-----------------------------
public void info(String msg) {
if (Level.INFO.intValue() < levelValue) {
return;
}
log(Level.INFO, msg);
}
上⾯说过默认 LoggerManager 产⽣的 Logger ⽇志级别默认为 INFO ,所以这⾥默认的
levelValue 为 Level.INFO.intValue()
如果这⾥ Level.INFO.intValue() 低于 levelValue 的 , 将do nothing
调⽤ log(Level level, String msg) ⽅法
----------------------------------
public void log(Level level, String msg) {
if (level.intValue() < levelValue || levelValue == offValue) {
return;
}
LogRecord lr = new LogRecord(level, msg);
doLog(lr);
}
上⾯的 log.info ⽅法只是 log(Level level, String msg) ⽅法简单封装,在这⾥⽇志级别
为 Level.OFF.intValue() 也do nothing  了,否则创建真正的 LogRecord 对象
java反射的作用及应用场景调⽤ doLog(LogRecord lr) ⽅法
-------------------------
private void doLog(LogRecord lr) {
lr.setLoggerName(name);
String ebname = getEffectiveResourceBundleName();
if (ebname != null) {
lr.setResourceBundleName(ebname);
lr.setResourceBundle(findResourceBundle(ebname));
}
log(lr);
}
getEffectiveResourceBundleName() 将⼀直上溯查有效的 resourceBundleName , 有可能返回null 调⽤ log(LogRecord lr) ⽅法
-----------------------
public void log(LogRecord record) {
if (Level().intValue() < levelValue || levelValue == offValue) {
return;
}
Filter theFilter = filter;
if (theFilter != null && !theFilter.isLoggable(record)) {
return;
}
// Post the LogRecord to all our Handlers, and then to
// our parents' handlers, all the way up the tree.
Logger logger = this;
while (logger != null) {
for (Handler handler : Handlers()) {
handler.publish(record);
}
if (!UseParentHandlers()) {
break;
}
logger = Parent();
}
}
在这⾥我们可以看到了 Filter 与 Handler 的出现,我们可以使⽤ setFilter(Filter newFilter)
与 addHandler(Handler handler) 来为 Logger 添加 Filter 与 Handler
这⾥我们可以看出在while循环中会先对当前所有 handler 输出,在上溯所有⽗ Logger 所有 Handler 输出,⾄此两句代码解析结束。
log.info()
话说 Filter
作为⼀个接⼝, Filter:为所记录的⽇志提供⽇志级别控制以外的细粒度控制。
filter
话说 Handler
先上⼀张 java.util.logging 包中有关 Handler 的类图
Handler负责从Logger中取出⽇志消息并将消息发送出去,⽐如发送到控制台、⽂件、⽹络上的其他⽇志服务或操作系统⽇志等。Handler也具有级别概念,⽤于判断当前Logger中的消息是否应该被发送出去,可以使⽤定义好的各种⽇志级别(如Level.OFF表⽰关闭等)。
除了级别概念,⼀个Handler还可以具有⾃⼰的过滤器(Filter)、格式化器(Formatter)、错误管理器(ErrorManager)以及编码字符集等,这些属性借助LogManager中的配置信息进⾏设置。
Handler是⼀个抽象类,需要根据实际情况创建真正使⽤的具体Handler(如ConsoleHandler、FileHandler等),实现各⾃的publish、flush 以及close等⽅法。
对⼏种具体实现 Handler 类的类做简单说明
1. MemoryHandler,将当前⽇志信息写⼊内存缓冲区中同时丢弃缓存中以前的内容。将内存缓冲区中的信息转发⾄另⼀个Handler
2. StreamHandler所有基于I/O流的Handler的基类,将⽇志信息发送⾄给定的java.io.OutputStream中
3. ConsoleHandler,将消息发送⾄(⽽⾮System.out),默认配置与其⽗类StreamHandler相同。
4. FileHandler,将消息发送⾄单个⼀般⽂件或⼀个可回滚的⽂件集合。可回滚⽂件集中的⽂件依据⽂件⼤⼩进⾏回滚,久⽂件名称通过
当前⽂件名附加编号0、1、2等⽅式依次进⾏标⽰。默认情况下⽇志信息都存放在I/O缓冲中,但如果⼀条完整的⽇志信息会触发清空缓冲的动作。与其⽗类StramHandler不同的是,FileHandler的默认格式器是java.util.logging.XMLFormatter:
5. SocketHandler,负责将⽇志信息发送⾄⽹络,默认情况下也采⽤java.util.logging.XMLFormatter格式。
关于 MemoryHandler
MemoryHandler 使⽤了典型的“注册 - 通知”的观察者模式。MemoryHandler 先注册到对⾃⼰感兴趣的 Logger 中
(logger.addHandler(handler)),在这些 Logger 调⽤发布⽇志的 API:log()、logp()、logrb() 等,遍历这些 Logger 下绑定的所有Handlers 时,通知触发⾃⾝ publish(LogRecord)⽅法的调⽤,将⽇志写⼊ buffer,当转储到下⼀个⽇志发布平台的条件成⽴,转储⽇志并清空 buffer。
这⾥的 buffer 是 MemoryHandler ⾃⾝维护⼀个可⾃定义⼤⼩的循环缓冲队列,来保存所有运⾏时触发的 Exception ⽇志条⽬。同时在构造函数中要求指定⼀个 Target Handler,⽤于承接输出;在满⾜特定 flush buffer 的条件下,如⽇志条⽬等级⾼于 MemoryHandler 设定的push level 等级(实例中定义为 SEVERE)等,将⽇志移交⾄下⼀步输出平台。从⽽形成如下⽇志转储输出链:
MemoryHandler 使⽤⽅式
以上是记录产品 Exception 错误⽇志,以及如何转储的 MemoryHandler 处理的内部细节;接下来给出 MemoryHandler 的⼀些使⽤⽅式。
1. 直接使⽤ java.util.logging 中的 MemoryHandler
// 在 buffer 中维护 5 条⽇志信息
// 仅记录 Level ⼤于等于 Warning 的⽇志条⽬并
// 刷新 buffer 中的⽇志条⽬到 fileHandler 中处理
int bufferSize = 5;
f = new FileHandler("testMemoryHandler.log");
m = new MemoryHandler(f, bufferSize, Level.WARNING);
myLogger = Logger("st");
myLogger.addHandler(m);
myLogger.log(Level.WARNING, “this is a WARNING log”);
2. ⾃定义(反射)
思考⾃定义 MyHandler 继承⾃ MemoryHandler 的场景,由于⽆法直接使⽤作为⽗类私有属性的 size、buffer 及 buffer 中的 cursor,如果在 MyHandler 中有获取和改变这些属性的需求,⼀个途径是使⽤反射。清单 5 展⽰了使⽤反射读取⽤户配置并设置私有属性。
int m_size;
String sizeString = Property(loggerName + ".size");
if (null != sizeString) {
try {
m_size = Integer.parseInt(sizeString);
if (m_size <= 0) {
m_size = BUFFER_SIZE; // default 1000
}
// 通过 java 反射机制获取私有属性
Field f;
f = getClass().getSuperclass().getDeclaredField("size");
f.setAccessible(true);
f.setInt(this, m_size);
f = getClass().getSuperclass().getDeclaredField("buffer");
f.setAccessible(true);
f.set(this, new LogRecord[m_size]);
} catch (Exception e) {
}
}
3. ⾃定义(重写)
直接使⽤反射⽅便快捷,适⽤于对⽗类私有属性⽆频繁访问的场景。思考这样⼀种场景,默认环形队列⽆法满⾜我们存储需求,此时不妨令⾃定义的 MyMemoryHandler 直接继承 Handler,直接对存储结构进⾏操作,可以通过清单 6 实现。
public class MyMemoryHandler extends Handler{
// 默认存储 LogRecord 的缓冲区容量
private static final int DEFAULT_SIZE = 1000;
// 设置缓冲区⼤⼩
private int size = DEFAULT_SIZE;
// 设置缓冲区
private LogRecord[] buffer;
// 参考 java.util.logging.MemoryHandler 实现其它部分
...
}
logging.properties ⽂件
默认的 logging.properties 存放在 jre/lib/logging.properties,截取有效的配置项
handlers= java.util.logging.ConsoleHandler
.level= INFO
java.util.logging.FileHandler.pattern = %h/java%u.log
java.util.logging.FileHandler.limit = 50000
java.util.unt = 1
java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter
java.util.logging.ConsoleHandler.level = INFO
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
转载⾃:segmentfault/a/1190000004227150