在软件开发过程中日志记录是必不可少的一部分。作为一个合格的软件开发者(Java),有必要深入了解一下Java的日志体系。本文将详细的介绍我在学习JUL时的一些方法和心得。
一般来说日志分为两种:业务日志和异常日志,使用日志我们希望能达到以下目标:
- 对程序运行情况的记录和监控;
- 在必要时可详细了解程序内部的运行状态;
- 对系统性能的影响尽量小;
个人觉得记录日志最重要的目的在于记录程序运行的信息,以方便分析程序的运行结果和定位问题。
梳理了自己学习JUL的过程,并进行总结,以便在学习其他技术时也能使用这种学习方法。
软件包 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中可以看出:
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 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 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 isLoggable(Level level) {
if (level.intValue() < levelValue || levelValue == offValue) {
return false;
}
return true;
}
//------------------Handler相关-----------------------
public void addHandler(Handler handler) throws SecurityException {
// Check for null handler
handler.getClass();
checkPermission();
handlers.add(handler);
}
public void removeHandler(Handler handler) throws SecurityException {
checkPermission();
if (handler == null) {
return;
}
handlers.remove(handler);
}
public Handler[] getHandlers() {
return accessCheckedHandlers();
}
Handler[] accessCheckedHandlers() {
return handlers.toArray(emptyHandlers);
}
//------------------父级日志相关-----------------------
public Logger getParent() {
return parent;
}
public void setParent(Logger parent) {
if (parent == null) {
throw new NullPointerException();
}
// check permission for all loggers, including anonymous loggers
if (manager == null) {
manager = LogManager.getLogManager();
}
manager.checkPermission();
doSetParent(parent);
}
private void doSetParent(Logger newParent) {
synchronized (treeLock) {
// Remove ourself from any previous parent.
LogManager.LoggerWeakRef ref = null;
if (parent != null) {
// assert parent.kids != null;
for (Iterator iter = parent.kids.iterator(); iter.hasNext(); ) {
ref = iter.next();
Logger kid = ref.get();
if (kid == this) {
// ref is used down below to complete the reparenting
iter.remove();
break;
} else {
ref = null;
}
}
// We have now removed ourself from our parents' kids.
}
// Set our new parent.
parent = newParent;
if (parent.kids == null) {
parent.kids = new ArrayList<>(2);
}
if (ref == null) {
// we didn't have a previous parent
ref = manager.new LoggerWeakRef(this);
}
ref.setParentRef(new WeakReference<>(parent));
parent.kids.add(ref);
// As a result of the reparenting, the effective level
// may have changed for us and our children.
updateEffectiveLevel();
}
}
private void updateEffectiveLevel() {
// Figure out our current effective level.
int newLevelValue;
if (levelObject != null) {
newLevelValue = levelObject.intValue();
} else {
if (parent != null) {
newLevelValue = parent.levelValue;
} else {
// This may happen during initialization.
newLevelValue = Level.INFO.intValue();
}
}
// If our effective value hasn't changed, we're done.
if (levelValue == newLevelValue) {
return;
}
levelValue = newLevelValue;
// Recursively update the level on each of our kids.
if (kids != null) {
for (int i = 0; i < kids.size(); i++) {
LogManager.LoggerWeakRef ref = kids.get(i);
Logger kid = ref.get();
if (kid != null) {
kid.updateEffectiveLevel();
}
}
}
}
}
Logger中的核心属性:
String name;
Logger parent;
ArrayList kids;
LogManager manager;
CopyOnWriteArrayList handlers;
Filter filter;
Level levelObject;
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);
}
//-----------------------------记录日志的相关方法--------------------------------------
public void log(Level level, String msg) {
if (!isLoggable(level)) {
return;
}
LogRecord lr = new LogRecord(level, msg);
doLog(lr);
}
private void doLog(LogRecord lr) {
lr.setLoggerName(name);
//忽略LogBundle
log(lr);
}
// 核心的日志操作方法,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();
}
}
梳理代码中的核心部分(忽略了安全、线程、ResourceBundle等部分),限于篇幅以下只列出核心的属性和方法。
Logger类中是JUL体系的核心,主要包括几个重要的属性和两个核心的方法,阅读起来不太复杂。而LogManager内容较多,放在最后介绍。
2.Level类
Level中的核心属性:
String name;
int value;
String resourceBundleName;
Level OFF;
Level WARNING;
Level INFO;
Level ALL;
Level中的核心方法:通过name获取Level
//-----------------------------核心方法------------------------------
static Level findLevel(String name) {
if (name == null) {
throw new NullPointerException();
}
KnownLevel level;
// Look for a known Level with the given non-localized name.
level = KnownLevel.findByName(name);
if (level != null) {
return level.mirroredLevel;
}
try {
int x = Integer.parseInt(name);
level = KnownLevel.findByValue(x);
if (level == null) {
// add new Level
Level levelObject = new Level(name, x);
level = KnownLevel.findByValue(x);
}
return level.mirroredLevel;
} catch (NumberFormatException ex) {
// Not an integer.
// Drop through.
}
// level = KnownLevel.findByLocalizedLevelName(name);
// if (level != null) {
// return level.mirroredLevel;
// }
return null;
}
Level中的核心内部类:KnownLevel,日志级别的容器
static final class KnownLevel {
private static Map> nameToLevels = new HashMap<>();
private static Map> intToLevels = new HashMap<>();
final Level levelObject; // instance of Level class or Level subclass
final Level mirroredLevel; // mirror of the custom Level
KnownLevel(Level l) {
this.levelObject = l;
if (l.getClass() == Level.class) {
this.mirroredLevel = l;
} else {
// this mirrored level object is hidden
this.mirroredLevel = new Level(l.name, l.value, l.resourceBundleName, false);
}
}
static synchronized void add(Level l) {
// the mirroredLevel object is always added to the list
// before the custom Level instance
KnownLevel o = new KnownLevel(l);
List list = nameToLevels.get(l.name);
if (list == null) {
list = new ArrayList<>();
nameToLevels.put(l.name, list);
}
list.add(o);
list = intToLevels.get(l.value);
if (list == null) {
list = new ArrayList<>();
intToLevels.put(l.value, list);
}
list.add(o);
}
static synchronized KnownLevel findByName(String name) {
List list = nameToLevels.get(name);
if (list != null) {
return list.get(0);
}
return null;
}
static synchronized KnownLevel findByValue(int value) {
List list = intToLevels.get(value);
if (list != null) {
return list.get(0);
}
return null;
}
}
Level类中属性和方法不多,重点在KnownLevel这个内部容器类。
3.LogRecord类
LogRecord的核心属性 :
private static final AtomicLong globalSequenceNumber=new AtomicLong(0);
private long sequenceNumber;
private Level level;
private String message;
private String loggerName;
private transient Object parameters[];
private String sourceClassName;
private String sourceMethodName;
LogRecord的构造器:
public LogRecord(Level level, String message){
this.level = level;
this.message=message;
this.sequenceNumber=globalSequenceNumber.getAndIncrement();
}
LogRecord类相对简单,值得关注的是属性和构造器。
4.Filter接口(函数式接口)
public boolean isLoggable(LogRecord record);
主要是验证LogReocord是否能够继续传递。
5.Handler抽象类: 处理LogRecord并输出
Handler的核心属性:
private static final int offValue = Level.OFF.intValue();
private final LogManager manager = LogManager.getLogManager();
private volatile Filter filter;
private volatile Formatter formatter;
private volatile Level logLevel = Level.ALL;
Handler的核心方法:
//---------------------抽象方法------------------------
public abstract void publish(LogRecord record);
public abstract void flush();
public abstract void close() throws SecurityException;
//--------------------主要方法--------------------------
public boolean isLoggable(LogRecord record) {
final int levelValue = getLevel().intValue();
if (record.getLevel().intValue() < levelValue || levelValue == offValue) {
return false;
}
final Filter filter = getFilter();
if (filter == null) {
return true;
}
return filter.isLoggable(record);
}
Handler是抽象类,JUL中提供的子类有MemoryHandler, StreamHandler。
6.Formatter抽象类
Formatter的核心方法:
public abstract String format(LogRecord record);
public synchronized String formatMessage(LogRecord record) {
String format = record.getMessage();
// Do the formatting.
try {
Object parameters[] = record.getParameters();
if (parameters == null || parameters.length == 0) {
// No parameters. Just return format string.
return format;
}
// Is it a java.text style format?
// Ideally we could match with
// Pattern.compile("\\{\\d").matcher(format).find())
// However the cost is 14% higher, so we cheaply check for
// 1 of the first 4 parameters
if (format.indexOf("{0") >= 0 || format.indexOf("{1") >=0 ||
format.indexOf("{2") >=0|| format.indexOf("{3") >=0) {
return java.text.MessageFormat.format(format, parameters);
}
return format;
} catch (Exception ex) {
// Formatting failed: use localized format string.
return format;
}
}
Formatter抽象类JUL提供的子类有SimpleFormatter, XMLFormatter。
7.LogManager类
LogManager的核心属性:
//---------------------------核心属性--------------------------------------
private static final LogManager manager;// 全局的LogManager对象
private volatile Properties props = new Properties(); //配置
private final static Level defaultLevel = Level.INFO;//默认日志级别
private final Map
LogManager的静态代码块:
//---------------------------------静态代码块----------------------------------------
static {
manager = AccessController.doPrivileged(new PrivilegedAction() {
@Override
public LogManager run() {
LogManager mgr = null;
String cname = null;
try {
cname = System.getProperty("java.util.logging.manager");
if (cname != null) {
try {
Class> clz = ClassLoader.getSystemClassLoader()
.loadClass(cname);
mgr = (LogManager) clz.newInstance();
} catch (ClassNotFoundException ex) {
Class> clz = Thread.currentThread()
.getContextClassLoader().loadClass(cname);
mgr = (LogManager) clz.newInstance();
}
}
} catch (Exception ex) {
System.err.println("Could not load Logmanager \"" + cname + "\"");
ex.printStackTrace();
}
//确保初始化一个全局的LogManager
if (mgr == null) {
mgr = new LogManager();
}
return mgr;
}
});
}
LogManager的核心内部类:
//LoggerContext日志上下文信息包括:Hashtable namedLoggers和LogNode root;
class LoggerContext {
// Table of named Loggers that maps names to Loggers.
private final Hashtable namedLoggers = new Hashtable<>();
// Tree of named Loggers
private final LogNode root;
private LoggerContext() {
this.root = new LogNode(null, this);
}
final LogManager getOwner() {
return LogManager.this;
}
final Logger getRootLogger() {
return getOwner().rootLogger;
}
synchronized Logger findLogger(String name) {
// ensure that this context is properly initialized before looking for loggers.
LoggerWeakRef ref = namedLoggers.get(name);
if (ref == null) {
return null;
}
Logger logger = ref.get();
return logger;
}
synchronized void removeLoggerRef(String name, LoggerWeakRef ref) {
namedLoggers.remove(name, ref);
}
synchronized Enumeration getLoggerNames() {
return namedLoggers.keys();
}
LogNode getNode(String name) {
if (name == null || name.equals("")) {
return root;
}
LogNode node = root;
while (name.length() > 0) {
int ix = name.indexOf(".");
String head;
if (ix > 0) {
head = name.substring(0, ix);
name = name.substring(ix + 1);
} else {
head = name;
name = "";
}
if (node.children == null) {
node.children = new HashMap<>();
}
LogNode child = node.children.get(head);
if (child == null) {
child = new LogNode(node, this);
node.children.put(head, child);
}
node = child;
}
return node;
}
}
//LoggerContext日志上下文信息包括:Hashtable namedLoggers和LogNode root;
class LoggerContext {
// Table of named Loggers that maps names to Loggers.
private final Hashtable namedLoggers = new Hashtable<>();
// Tree of named Loggers
private final LogNode root;
private LoggerContext() {
this.root = new LogNode(null, this);
}
final LogManager getOwner() {
return LogManager.this;
}
final Logger getRootLogger() {
return getOwner().rootLogger;
}
synchronized Logger findLogger(String name) {
// ensure that this context is properly initialized before looking for loggers.
LoggerWeakRef ref = namedLoggers.get(name);
if (ref == null) {
return null;
}
Logger logger = ref.get();
return logger;
}
synchronized void removeLoggerRef(String name, LoggerWeakRef ref) {
namedLoggers.remove(name, ref);
}
synchronized Enumeration getLoggerNames() {
return namedLoggers.keys();
}
LogNode getNode(String name) {
if (name == null || name.equals("")) {
return root;
}
LogNode node = root;
while (name.length() > 0) {
int ix = name.indexOf(".");
String head;
if (ix > 0) {
head = name.substring(0, ix);
name = name.substring(ix + 1);
} else {
head = name;
name = "";
}
if (node.children == null) {
node.children = new HashMap<>();
}
LogNode child = node.children.get(head);
if (child == null) {
child = new LogNode(node, this);
node.children.put(head, child);
}
node = child;
}
return node;
}
}
//根日志类
private final class RootLogger extends Logger {
private RootLogger() {
super("");
}
@Override
public void log(LogRecord record) {
initializeGlobalHandlers();
super.log(record);
}
@Override
public void addHandler(Handler h) {
initializeGlobalHandlers();
super.addHandler(h);
}
@Override
public void removeHandler(Handler h) {
initializeGlobalHandlers();
super.removeHandler(h);
}
@Override
Handler[] accessCheckedHandlers() {
initializeGlobalHandlers();
return super.accessCheckedHandlers();
}
}
//配置改变监听和配置改变事件
private static class Beans {
private static final Class> propertyChangeListenerClass =
getClass("java.beans.PropertyChangeListener");
private static final Class> propertyChangeEventClass =
getClass("java.beans.PropertyChangeEvent");
private static final Method propertyChangeMethod =
getMethod(propertyChangeListenerClass,
"propertyChange",
propertyChangeEventClass);
private static final Constructor> propertyEventCtor =
getConstructor(propertyChangeEventClass,
Object.class,
String.class,
Object.class,
Object.class);
private static Class> getClass(String name) {
try {
return Class.forName(name, true, Beans.class.getClassLoader());
} catch (ClassNotFoundException e) {
return null;
}
}
private static Constructor> getConstructor(Class> c, Class>... types) {
try {
return (c == null) ? null : c.getDeclaredConstructor(types);
} catch (NoSuchMethodException x) {
throw new AssertionError(x);
}
}
private static Method getMethod(Class> c, String name, Class>... types) {
try {
return (c == null) ? null : c.getMethod(name, types);
} catch (NoSuchMethodException e) {
throw new AssertionError(e);
}
}
/**
* Returns {@code true} if java.beans is present.
*/
static boolean isBeansPresent() {
return propertyChangeListenerClass != null &&
propertyChangeEventClass != null;
}
static Object newPropertyChangeEvent(Object source, String prop,
Object oldValue, Object newValue)
{
try {
return propertyEventCtor.newInstance(source, prop, oldValue, newValue);
} catch (InstantiationException | IllegalAccessException x) {
throw new AssertionError(x);
} catch (InvocationTargetException x) {
Throwable cause = x.getCause();
if (cause instanceof Error)
throw (Error)cause;
if (cause instanceof RuntimeException)
throw (RuntimeException)cause;
throw new AssertionError(x);
}
}
static void invokePropertyChange(Object listener, Object ev) {
try {
propertyChangeMethod.invoke(listener, ev);
} catch (IllegalAccessException x) {
throw new AssertionError(x);
} catch (InvocationTargetException x) {
Throwable cause = x.getCause();
if (cause instanceof Error)
throw (Error)cause;
if (cause instanceof RuntimeException)
throw (RuntimeException)cause;
throw new AssertionError(x);
}
}
}
LogManager的核心方法:
public static LogManager getLogManager() {
if (manager != null) {
//确保初始化
manager.ensureLogManagerInitialized();
}
return manager;
}
Logger demandLogger(String name, String resourceBundleName, Class> caller) {
Logger result = getLogger(name);
if (result == null) {
// only allocate the new logger once
Logger newLogger = new Logger(name);
do {
if (addLogger(newLogger)) {
// We successfully added the new Logger that we
// created above so return it without refetching.
return newLogger;
}
result = getLogger(name);
} while (result == null);
}
return result;
}
//读取配置
public void readConfiguration() throws IOException, SecurityException {
checkPermission();
// if a configuration class is specified, load it and use it.
//加载日志配置类
String cname = System.getProperty("java.util.logging.config.class");
if (cname != null) {
try {
try {
Class> clz = ClassLoader.getSystemClassLoader().loadClass(cname);
clz.newInstance();
return;
} catch (ClassNotFoundException ex) {
Class> clz = Thread.currentThread().getContextClassLoader().loadClass(cname);
clz.newInstance();
return;
}
} catch (Exception ex) {
System.err.println("Logging configuration class \"" + cname + "\" failed");
System.err.println("" + ex);
// keep going and useful config file.
}
}
//加载日志配置文件
String fname = System.getProperty("java.util.logging.config.file");
if (fname == null) {
fname = System.getProperty("java.home");
if (fname == null) {
throw new Error("Can't find java.home ??");
}
File f = new File(fname, "lib");
f = new File(f, "logging.properties");
fname = f.getCanonicalPath();//获取规范路径
}
try (final InputStream in = new FileInputStream(fname)) {
final BufferedInputStream bin = new BufferedInputStream(in);
readConfiguration(bin);
}
}
//读取配置文件jre_home/logging.properties
public void readConfiguration(InputStream ins) throws IOException, SecurityException {
checkPermission();
reset();//关闭原有handlers
props.load(ins);
// Instantiate new configuration objects.
String names[] = parseClassNames("config");
for (int i = 0; i < names.length; i++) {
String word = names[i];
try {
Class> clz = ClassLoader.getSystemClassLoader().loadClass(word);
clz.newInstance();
} catch (Exception ex) {
System.err.println("Can't load config class \"" + word + "\"");
System.err.println("" + ex);
// ex.printStackTrace();
}
}
//给现有的日志设置最新的日志级别
setLevelsOnExistingLoggers();
Map
//------------------------------添加日志------------------------------
public boolean addLogger(Logger logger) {
final String name = logger.getName();
if (name == null) {
throw new NullPointerException();
}
loadLoggerHandlers(logger, name, name + ".handlers");
return true;
}
//添加日志处理器
private void loadLoggerHandlers(final Logger logger, final String name,
final String handlersPropertyName)
{
AccessController.doPrivileged(new PrivilegedAction() {
@Override
public Object run() {
String names[] = parseClassNames(handlersPropertyName);
for (int i = 0; i < names.length; i++) {
String word = names[i];
try {
Class> clz = ClassLoader.getSystemClassLoader().loadClass(word);
Handler hdl = (Handler) clz.newInstance();
String levs = getProperty(word + ".level");
if (levs != null) {
Level l = Level.findLevel(levs);
if (l != null) {
hdl.setLevel(l);
} else {
// Probably a bad level. Drop through.
System.err.println("Can't set level for " + word);
}
}
logger.addHandler(hdl);
} catch (Exception ex) {
System.err.println("Can't load log handler \"" + word + "\"");
System.err.println("" + ex);
ex.printStackTrace();
}
}
return null;
}
});
}
LogManager类内容比较多,梳理起来比较麻烦。核心有几点:LoggerContext,LoggerWeakRef,LogNode内部类记录、维护日志信息。
JUL中核心的类我们已经熟悉了,其中SimpleFormatter和StreamHandler相对简单不再赘述。
API,源码都已经看过了,代码中不太明白的一定有不少,看完真是让人头大。不要方,下面才是重点,通过一个简单的HelloWorld程序将流程给捋顺了。
public class Test {
public static void main(String[] args){
Logger logger=Logger.getLogger("com.blue.Test");
logger.log(Level.INFO,"MESSAGE:{0},{1}!",new Object[]{"HELLO","JUL"});
}
}
运行结果:二月 15, 2019 4:59:19 下午 com.blue.Test main 信息: MESSAGE:HELLO,JUL!
这样整个流程就很清晰了(更详细的代码追踪参考链接: https://pan.baidu.com/s/1Yxph2Y-yvwcs8D5p67kJAw 提取码: n9as)。
public ConsoleHandler() {
sealed = false;
configure();
setOutputStream(System.err);
sealed = true;
}
跑完程序,虽然逻辑会更加清晰,但还差点意思,我们需要更进一步——造个轮子。仿照JUL写一套日志实现(结合流程图和简化后的代码)。
限于篇幅原因代码不在此处展示,如感兴趣请访问(百度网盘链接: https://pan.baidu.com/s/1Vlqcs8mZxrbijCa1Hp-AIw 提取码: 4m7p)或JUL的简单日志实现。
注:①示例代码中配置文件读取部分可能需要调整
②如果封装成jar包时,读取jar内部的配置文件也有问题,建议将读取配置部分做适当修改。
本文的重点是介绍JUL的源码学习,其他JAVA日志的信息只做简要介绍。
- Jul (Java Util Logging),自Java1.4以来的官方日志实现。
- Log4j Apache Log4j是一个基于Java的日志记录工具。它是由Ceki Gülcü首创的,现在则是Apache软件基金会的一个项目。 Log4j是几种Java日志框架之一。
- Log4j 2 Apache Log4j 2是apache开发的一款Log4j的升级产品。
- Logback 一套日志组件的实现(Slf4j阵营)。
- Commons Logging Apache基金会所属的项目,是一套Java日志接口,之前叫Jakarta Commons Logging,后更名为Commons Logging。
- Slf4j 类似于Commons Logging,是一套简易Java日志门面,本身并无日志的实现。(Simple Logging Facade for Java,缩写Slf4j)。
接口类日志框架:JCL,Slf4j
日志实现框架:JUL,Log4j,Log4j2,Logback
以上是常用的日志框架,相关的桥接包不再赘述。
目前就性能而言Log4j2更胜一筹,其次是Logback。
JUL和Log4j不推荐使用。
目前本人对 Log4j2和Logback都只是限于简单配置使用,以后深入研究后会补充此部分的内容。
第一次研究源码花费的时间还是比较长的,这篇文章断断续续写了两周。一直想研究Java源码,却不知从何开始,这次在学习日志知识的时候选择分析JUL的源码,收获还是很大的。限于篇幅和精力问题后面部分介绍的过于简单,想要深入了解的可以浏览下面的参考资料。
整体来说JUL的核心是Logger类和LogManager类,其他都相对简单。
简单的概括就是七个类(接口),两条线(获取Logger读取配置,记录日志并输出)。
Spring Boot(十)Logback和Log4j2集成与日志发展史
Log4j、Log4j 2、Logback、SFL4J、JUL、JCL的比较
Asynchronous Loggers for Low-Latency Logging
Java日志
Java常用日志框架介绍
Log日志规范