在软件开发过程中日志记录是必不可少的一部分。作为一个合格的软件开发者(Java),有必要深入了解一下Java的日志体系。本文将详细的介绍我在学习JUL时的一些方法和心得。
一般来说日志分为两种:业务日志和异常日志,使用日志我们希望能达到以下目标:
- 对程序运行情况的记录和监控;
- 在必要时可详细了解程序内部的运行状态;
- 对系统性能的影响尽量小;
个人觉得记录日志最重要的目的在于记录程序运行的信息,以方便分析程序的运行结果和定位问题。
概览(查、看、跑、造、比、买)
梳理了自己学习JUL的过程,并进行总结,以便在学习其他技术时也能使用这种学习方法。
- 查API
查看官方API,对这个技术的整体以及核心类的介绍有所了解。 - 看源码
根据API的介绍查看核心类的代码,去繁存简只研究与日志相关的部分
(例如,本文将忽略安全、多线程、本地化相关的代码 ) - 跑程序
从helloworld开始,将核心功能串联起来。 - 造轮子
梳理清楚逻辑后,关闭浏览器(这时候应该放下资料),打开IDE按照自己的理解实现这个功能。 - 货比三家
研究同种类型技术的实现方式。 - 买定离手
对比不同技术的性能和适用场景,技术选型。
查API
软件包 java.util.logging 的描述
提供 JavaTM 2 平台核心日志工具的类和接口。Logging API 的中心目标是支持在客户站点进行软件的维护和服务。
使用日志有 4 个主要目标:
1.由最终用户和系统管理员进行问题诊断。这由简单的常见问题日志组成,可在本地解决或跟踪这些问题,如资源不足、安全失败和简单的配置错误。
2.由现场服务工程师进行问题诊断。现场服务工程师使用的日志信息可以相当复杂和冗长,远超过系统管理员的要求。通常,这样的信息需要特定子系统中的额外日志记录。
3.由开发组织进行问题诊断。在现场出现问题时,必须将捕获的日志信息返回到原开发团队以供诊断。此日志信息可能非常详细,并且相当费解。这样的信息可能包括对特定子系统进行内部执行的详细跟踪。
4.由开发人员进行问题诊断。Logging API 还可以用来帮助调试正在开发的应用程序。这可能包括由目标应用程序产生的日志信息,以及由低级别的库产生的日志信息。但是要注意,虽然这样使用非常合理,但是 Logging API 并不用于代替开发环境中已经存在的调试和解析工具。
此包的关键元素包括:
Logger:应用程序进行 logging 调用的主要实体。Logger 对象用来记录特定系统或应用程序组件的日志消息。
LogRecord:用于在 logging 框架和单独的日志处理程序之间传递 logging 请求。
Handler:将 LogRecord 对象导出到各种目的地,包括内存、输出流、控制台、文件和套接字。为此有各种的 Handler 子类。其他 Handler 可能由第三方开发并在核心平台的顶层实现。
Level:定义一组可以用来控制 logging 输出的标准 logging 级别。可以配置程序为某些级别输出 logging,而同时忽略其他输出。
Filter:为所记录的日志提供日志级别控制以外的细粒度控制。Logging API 支持通用的过滤器机制,该机制允许应用程序代码附加任意的过滤器以控制 logging 输出。
Formatter:为格式化 LogRecord 对象提供支持。此包包括的两个格式器 SimpleFormatter 和 XMLFormatter 分别用于格式化纯文本或 XML 中的日志记录。与 Handler 一样,其他 Formatter 可能由第三方开发。
Logging API 提供静态和动态的配置控制。静态控制使现场服务人员可以建立特定的配置,然后重新启动带有新 logging 设置的应用程序。动态控制允许对当前正在运行的系统内的 logging 配置进行更新。API 也允许对不同的系统功能领域启用或禁用 logging。例如,现场服务工程师可能对跟踪所有 AWT 事件感兴趣,但是不会对套接字事件或内存管理感兴趣。
从JDK(java.util.logging)的API中可以看出:
- Logger是用来记录日志的,
- LogRecord是用来传递日志的,
- Handler是用来导出(处理)日志的,
- Level是用来定义日志级别的(控制日志输出),
- Filter提供了日志级别之外更细粒度的控制,
- Formatter是用来格式化LogRecord的,
- 此外还有一个单一的全局的LogManager维护Logger和管理日志。
JUL主要由这七个核心类或接口组成,再有就是一些子类或者实现类。API中关于这几个类或接口的类和方法的详细介绍这里不再复述。
现在我们已经对JUL有了一个整体的了解,并且对核心类或接口的功能职责也有了初步了解,接下就该结合代码看看JUL的真面目。
看源码
找到rt.jar,打开java.util.logging包查看每个类的源代码,结合JUL流程示意图逐个介绍。
1.Logger类
package java.util.logging;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.concurrent.CopyOnWriteArrayList;
import sun.reflect.Reflection;
public class Logger {
//------------------------------属性------------------------------------------
private String name;//日志名称
private static final int offValue = Level.OFF.intValue();
// We keep weak references from parents to children, but strong
// references from children to parents.
private volatile Logger parent; // our nearest parent.
private ArrayList<LogManager.LoggerWeakRef> kids; // WeakReferences to loggers that have us as parent
//内部类LoggerBundle相关部分忽略
private volatile LogManager manager;
private static final Handler emptyHandlers[] = new Handler[0];
private final CopyOnWriteArrayList<Handler> handlers =
new CopyOnWriteArrayList<>();
private volatile boolean useParentHandlers = true;
private volatile Filter filter;
private volatile Level levelObject;
private volatile int levelValue; // current effective level value
private static final Object treeLock = new Object();
private final boolean isSystemLogger;
//------------------------------构造器------------------------------------------
//只保留一个简单的私有构造器
private Logger(String name) {
// The manager field is not initialized here.
this.name = name;
this.isSystemLogger = true;
levelValue = Level.INFO.intValue();//初始化级别INFO
}
//------------------------------方法------------------------------------------
//获取Logger的name
public String getName() {
return name;
}
// It is called from LoggerContext.addLocalLogger() when the logger
// is actually added to a LogManager.
//--------------设置和初始化LogManager,包权限的方法-------------
void setLogManager(LogManager manager) {
this.manager = manager;
}
private void checkPermission() throws SecurityException {
if (manager == null) {
// Complete initialization of the global Logger.
manager = LogManager.getLogManager();
}
manager.checkPermission();
}
//-------------------获取Logger的工厂方法----------------------
public static Logger getLogger(String name) {
//获取需要的Logger
return demandLogger(name, null, Reflection.getCallerClass());
}
private static Logger demandLogger(String name, String resourceBundleName, Class<?> caller) {
//初始化LogManager
LogManager manager = LogManager.getLogManager();
// SecurityManager sm = System.getSecurityManager();
// if (sm != null) {
// if (caller.getClassLoader() == null) {
// return manager.demandSystemLogger(name, resourceBundleName);
// }
// }
//
//通过LogManager获取需要的Logger
return manager.demandLogger(name, resourceBundleName, caller);
}
//--------------------Filter的方法----------------------
public void setFilter(Filter newFilter) throws SecurityException {
//校验权限和初始化LogManager
checkPermission();
filter = newFilter;
}
public Filter getFilter() {
return filter;
}
//--------------------记录日志的方法----------------------
// 核心的日志操作方法,handler.publish(record);将LogRecord添加到handlers中
public void log(LogRecord record) {
//校验Level
if (!isLoggable(record.getLevel())) {
return;
}
//校验Filter
Filter theFilter = filter;
if (theFilter != null && !theFilter.isLoggable(record)) {
return;
}
Logger logger = this;
while (logger != null) {
final Handler[] loggerHandlers = isSystemLogger
? logger.accessCheckedHandlers()
: logger.getHandlers();
for (Handler handler : loggerHandlers) {
handler.publish(record);
}
final boolean useParentHdls = isSystemLogger
? logger.useParentHandlers
: logger.getUseParentHandlers();
if (!useParentHdls) {
break;
}
logger = isSystemLogger ? logger.parent : logger.getParent();
}
}
private void doLog(LogRecord lr) {
lr.setLoggerName(name);
//忽略LogBundle
log(lr);
}
public void log(Level level, String msg) {
if (!isLoggable(level)) {
return;
}
LogRecord lr = new LogRecord(level, msg);
doLog(lr);
}
public void log(Level level, String msg, Object param1) {
if (!isLoggable(level)) {
return;
}
LogRecord lr = new LogRecord(level, msg);
Object params[] = { param1 };
lr.setParameters(params);
doLog(lr);
}
public void log(Level level, String msg, Object params[]) {
if (!isLoggable(level)) {
return;
}
LogRecord lr = new LogRecord(level, msg);
lr.setParameters(params);
doLog(lr);
}
//------------------日志级别相关-----------------------
public void setLevel(Level newLevel) throws SecurityException {
checkPermission();
synchronized (treeLock) {
levelObject = newLevel;
updateEffectiveLevel();
}
}
final boolean isLevelInitialized() {
return levelObject != null;
}
public Level getLevel() {
return levelObject;
}
//校验Level
public boolean isL