清单 3
// 只纪录有异常并且高于 pushLevel 的 logRecord
finalLevel level = record.getLevel();
finalThrowable thrown = record.getThrown();
If(level >= pushLevel){
push();
}
MemoryHandler.push 方法的触发条件
Push 方法会导致 MemoryHandler 转储日志到下一 handler,清空 buffer。触发条件可以是但不局限于以下几种,实例中使用的是默认的第一种:
日志条目的 Level 大于或等于当前 MemoryHandler 中默认定义或用户配置的 pushLevel;
外部程序调用 MemoryHandler 的 push 方法;
MemoryHandler 子类可以重载 log 方法或自定义触发方法,在方法中逐一扫描日志条目,满足自定义规则则触发转储日志和清空 buffer 的操作。MemoryHanadler 的可配置属性
表 1.MemoryHandler 可配置属性
属性名
描述
缺省值
继承属性
MemoryHandler.level
MemoryHandler 接受的输入到 buffer 的日志等级
Level.INFO
MemoryHandler.filter
在输入到 buffer 之前,可在 filter 中自定义除日志等级外的其他过滤条件
(Undefined)
MemoryHandler.formatter
指定输入至 buffer 的日志格式
(Undefined)
MemoryHandler.encoding
指定输入至 buffer 的日志编码,在 MemoryHandler 中应用甚少
(Undefined)
私有属性
MemoryHandler.size
以日志条目为单位定义循环 buffer 的大小
1,000
MemoryHandler.push
定义将 buffer 中的日志条目发送至下一个 Handler 的最低 Level(包含)
Level.SEVERE
MemoryHandler.target
在构造函数中指定下一步承接日志的 Handler
(Undefined)
使用方式:
以上是记录产品 Exception 错误日志,以及如何转储的 MemoryHandler 处理的内部细节;接下来给出 MemoryHandler 的一些使用方式。
1. 直接使用 java.util.logging 中的 MemoryHandler
清单4
// 在 buffer 中维护 5 条日志信息
// 仅记录 Level 大于等于 Warning 的日志条目并
// 刷新 buffer 中的日志条目到 fileHandler 中处理
intbufferSize =5;
f = newFileHandler("testMemoryHandler.log");
m = newMemoryHandler(f, bufferSize, Level.WARNING);
…
myLogger = Logger.getLogger("com.ibm.test");
myLogger.addHandler(m);
myLogger.log(Level.WARNING, “thisis a WARNING log”);
. 自定义
1)反射
思考自定义 MyHandler 继承自 MemoryHandler 的场景,由于无法直接使用作为父类私有属性的 size、buffer 及 buffer 中的 cursor,如果在 MyHandler 中有获取和改变这些属性的需求,一个途径是使用反射。清单 5 展示了使用反射读取用户配置并设置私有属性。
清单5
intm_size;
String sizeString = manager.getProperty(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,newLogRecord[m_size]);
} catch(Exception e) {
}
}
2)重写
直接使用反射方便快捷,适用于对父类私有属性无频繁访问的场景。思考这样一种场景,默认环形队列无法满足我们存储需求,此时不妨令自定义的 MyMemoryHandler 直接继承 Handler,直接对存储结构进行操作,可以通过清单 6 实现。
清单 6
publicclassMyMemoryHandlerextendsHandler{
// 默认存储 LogRecord 的缓冲区容量
privatestaticfinalintDEFAULT_SIZE =1000;
// 设置缓冲区大小
privateintsize = DEFAULT_SIZE;
// 设置缓冲区
privateLogRecord[] buffer;
// 参考 java.util.logging.MemoryHandler 实现其它部分
...
}
使用 MemoryHandler 时需关注的几个问题
了解了使用 MemoryHandler 实现的 Java 日志缓冲机制的内部细节和外部应用之后,来着眼于两处具体实现过程中遇到的问题:Logger/Handler/LogRecord Level 的传递影响,以及如何在开发 MemoryHandler 过程中处理错误日志。
1. Level 的传递影响
Java.util.logging 中有三种类型的 Level,分别是 Logger 的 Level,Handler 的 Level 和 LogRecord 的 Level. 前两者可以通过配置文件设置。之后将日志的 Level 分别与 Logger 和 Handler 的 Level 进行比较,过滤无须记录的日志。在使用 Java Log 时需关注 Level 之间相互影响的问题,尤其在遍历 Logger 绑定了多个 Handlers 时。如图 3 所示:
图 3. Java Log 中 Level 的传递影响
Java.util.logging.Logger 提供的 setUseParentHandlers 方法,也可能会影响到最终输出终端的日志显示。这个方法允许用户将自身的日志条目打印一份到 Parent Logger 的输出终端中。缺省会打印到 Parent Logger 终端。此时,如果 Parent Logger Level 相关的设置与自身 Logger 不同,则打印到 Parent Logger 和自身中的日志条目也会有所不同。如图 4 所示:
图 4. 子类日志需打印到父类输出终端
2. 开发 log 接口过程中处理错误日志
在开发 log 相关接口中调用自身接口打印 log,可能会陷入无限循环。Java.util.logging 中考虑到这类问题,提供了一个 ErrorManager 接口,供 Handler 在记录日志期间报告任何错误,而非直接抛出异常或调用自身的 log 相关接口记录错误或异常。Handler 需实现 setErrorManager() 方法,该方法为此应用程序构造 java.util.logging.ErrorManager 对象,并在错误发生时,通过 reportError 方法调用 ErrorManager 的 error 方法,缺省将错误输出到标准错误流,或依据 Handler 中自定义的实现处理错误流。关闭错误流时,使用 Logger.removeHandler 移除此 Handler 实例。
两种经典使用场景,一种是自定义 MyErrorManager,实现父类相关接口,在记录日志的程序中调用 MyHandler.setErrorManager(new MyEroorManager()); 另一种是在 Handler 中自定义 ErrorManager 相关方法,示例如清单 7:
内容导航
第 1 页:清单1-3 第 2 页:清单4-6