在软件开发过程中日志记录是必不可少的一部分。作为一个合格的软件开发者(Java),有必要深入了解一下Java的日志体系。本文将详细的介绍我在学习JUL时的一些方法和心得。

一般来说日志分为两种:业务日志和异常日志,使用日志我们希望能达到以下目标:

  • 对程序运行情况的记录和监控;
  • 在必要时可详细了解程序内部的运行状态;
  • 对系统性能的影响尽量小;

个人觉得记录日志最重要的目的在于记录程序运行的信息,以方便分析程序的运行结果和定位问题。

概览(查、看、跑、造、比、买)

梳理了自己学习JUL的过程,并进行总结,以便在学习其他技术时也能使用这种学习方法。

  1. 查API
    查看官方API,对这个技术的整体以及核心类的介绍有所了解。
  2. 看源码
    根据API的介绍查看核心类的代码,去繁存简只研究与日志相关的部分
    (例如,本文将忽略安全、多线程、本地化相关的代码 )
  3. 跑程序
    从helloworld开始,将核心功能串联起来。
  4. 造轮子
    梳理清楚逻辑后,关闭浏览器(这时候应该放下资料),打开IDE按照自己的理解实现这个功能。
  5. 货比三家
    研究同种类型技术的实现方式。
  6. 买定离手
    对比不同技术的性能和适用场景,技术选型。

查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 valueprivate 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的namepublic 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) {//获取需要的Loggerreturn demandLogger(name, null, Reflection.getCallerClass());}private static Logger demandLogger(String name, String resourceBundleName, Class<?> caller) {//初始化LogManagerLogManager manager = LogManager.getLogManager();
//        SecurityManager sm = System.getSecurityManager();
//        if (sm != null) {
//            if (caller.getClassLoader() == null) {
//                return manager.demandSystemLogger(name, resourceBundleName);
//            }
//        }////通过LogManager获取需要的Loggerreturn manager.demandLogger(name, resourceBundleName, caller);}//--------------------Filter的方法----------------------public void setFilter(Filter newFilter) throws SecurityException {//校验权限和初始化LogManagercheckPermission();filter = newFilter;}public Filter getFilter() {return filter;}//--------------------记录日志的方法----------------------// 核心的日志操作方法,handler.publish(record);将LogRecord添加到handlers中public void log(LogRecord record) {//校验Levelif (!isLoggable(record.getLevel())) {return;}//校验FilterFilter 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);//忽略LogBundlelog(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;}//校验Levelpublic 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 handlerhandler.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 loggersif (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<LogManager.LoggerWeakRef> iter = parent.kids.iterator(); iter.hasNext(); ) {ref = iter.next();Logger kid =  ref.get();if (kid == this) {// ref is used down below to complete the reparentingiter.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 parentref = 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<LogManager.LoggerWeakRef> kids;LogManager manager;CopyOnWriteArrayList<Handler> handlers;Filter filter;Level levelObject;

Logger中的核心方法

//-----------------------------获取日志个工厂方法---------------------------------public static Logger getLogger(String name) {//获取需要的Loggerreturn demandLogger(name, null, Reflection.getCallerClass());}private static Logger demandLogger(String name, String resourceBundleName, Class<?> caller) {//初始化LogManagerLogManager manager = LogManager.getLogManager();
//        SecurityManager sm = System.getSecurityManager();
//        if (sm != null) {
//            if (caller.getClassLoader() == null) {
//                return manager.demandSystemLogger(name, resourceBundleName);
//            }
//        }////通过LogManager获取需要的Loggerreturn 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);//忽略LogBundlelog(lr);}// 核心的日志操作方法,handler.publish(record);将LogRecord添加到handlers中public void log(LogRecord record) {//校验Levelif (!isLoggable(record.getLevel())) {return;}//校验FilterFilter 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 LevelLevel 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<String, List<KnownLevel>> nameToLevels = new HashMap<>();private static Map<Integer, List<KnownLevel>> intToLevels = new HashMap<>();final Level levelObject;     // instance of Level class or Level subclassfinal Level mirroredLevel;   // mirror of the custom LevelKnownLevel(Level l) {this.levelObject = l;if (l.getClass() == Level.class) {this.mirroredLevel = l;} else {// this mirrored level object is hiddenthis.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 instanceKnownLevel o = new KnownLevel(l);List<KnownLevel> 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<KnownLevel> list = nameToLevels.get(name);if (list != null) {return list.get(0);}return null;}static synchronized KnownLevel findByValue(int value) {List<KnownLevel> 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 parametersif (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<Object,Integer> listenerMap = new HashMap<>();//负责存储监听// LoggerContext for system loggers and user loggersprivate final LoggerContext userContext = new LoggerContext();private volatile Logger rootLogger;//根日志private volatile boolean readPrimordialConfiguration;//读取原始配置// Have we initialized global (root) handlers yet?// This gets set to false in readConfigurationprivate boolean initializedGlobalHandlers = true;// True if JVM death is imminent and the exit hook has been called.private boolean deathImminent;

LogManager的静态代码块:

//---------------------------------静态代码块----------------------------------------static {manager = AccessController.doPrivileged(new PrivilegedAction<LogManager>() {@Overridepublic 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();}//确保初始化一个全局的LogManagerif (mgr == null) {mgr = new LogManager();}return mgr;}});}

LogManager的核心内部类:

 //LoggerContext日志上下文信息包括:Hashtable<String,LoggerWeakRef> namedLoggers和LogNode root;class LoggerContext {// Table of named Loggers that maps names to Loggers.private final Hashtable<String,LoggerWeakRef> namedLoggers = new Hashtable<>();// Tree of named Loggersprivate 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<String> 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<String,LoggerWeakRef> namedLoggers和LogNode root;class LoggerContext {// Table of named Loggers that maps names to Loggers.private final Hashtable<String,LoggerWeakRef> namedLoggers = new Hashtable<>();// Tree of named Loggersprivate 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<String> 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("");}@Overridepublic void log(LogRecord record) {initializeGlobalHandlers();super.log(record);}@Overridepublic void addHandler(Handler h) {initializeGlobalHandlers();super.addHandler(h);}@Overridepublic void removeHandler(Handler h) {initializeGlobalHandlers();super.removeHandler(h);}@OverrideHandler[] 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 onceLogger 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.propertiespublic void readConfiguration(InputStream ins) throws IOException, SecurityException {checkPermission();reset();//关闭原有handlersprops.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<Object,Integer> listeners = null;synchronized (listenerMap) {if (!listenerMap.isEmpty())listeners = new HashMap<>(listenerMap);}if (listeners != null) {assert Beans.isBeansPresent();//修改配置的监听事件Object ev = Beans.newPropertyChangeEvent(LogManager.class, null, null, null);for (Map.Entry<Object,Integer> entry : listeners.entrySet()) {Object listener = entry.getKey();int count = entry.getValue().intValue();for (int i = 0; i < count; i++) {Beans.invokePropertyChange(listener, ev);}}}// Note that we need to reinitialize global handles when they are first referenced.synchronized (this) {initializedGlobalHandlers = false;}}
 //------------------------------添加日志------------------------------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<Object>() {@Overridepublic 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)。

  • 示例中没有使用自定义的配置类和配置文件,采用JDK默认配置,路径jre/lib/logging.properties
    查看此文件:默认只使用java.util.logging.ConsoleHandler,
    而ConsoleHandler的默认级别为INFO,采用java.util.logging.SimpleFormatter,输出到System.err
   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)。

接口类日志框架JCLSlf4j
日志实现框架JULLog4jLog4j2Logback
以上是常用的日志框架,相关的桥接包不再赘述。

买定离手

目前就性能而言Log4j2更胜一筹,其次是Logback。
JUL和Log4j不推荐使用。
目前本人对 Log4j2和Logback都只是限于简单配置使用,以后深入研究后会补充此部分的内容。

总结

第一次研究源码花费的时间还是比较长的,这篇文章断断续续写了两周。一直想研究Java源码,却不知从何开始,这次在学习日志知识的时候选择分析JUL的源码,收获还是很大的。限于篇幅和精力问题后面部分介绍的过于简单,想要深入了解的可以浏览下面的参考资料。
整体来说JUL的核心是Logger类和LogManager类,其他都相对简单。

  • Logger包括Handler[],LogManager,Level,Filter,name
  • LogManager包括Properties,LogContext
  • LogRecord包括 Level,message,loggerName,sourceMethodName,sourceClassName
  • Handler包括Filter,Formatter,Handler相关实现类
  • Filter主要是验证LogRecord:isLoggable(LogRecord record)
  • Formatter和相关实现类
  • Level包括name,value,定义的几个默认级别

简单的概括就是七个类(接口),两条线(获取Logger读取配置,记录日志并输出)。

参考资料

Spring Boot(十)Logback和Log4j2集成与日志发展史
Log4j、Log4j 2、Logback、SFL4J、JUL、JCL的比较
Asynchronous Loggers for Low-Latency Logging
Java日志
Java常用日志框架介绍
Log日志规范

Java日志体系---JUL源码解析相关推荐

  1. Java集合之TreeMap源码解析上篇

    上期回顾 上期我从树型结构谈到了红黑树的概念以及自平衡的各种变化(指路上期←戳),本期我将会对TreeMap结合红黑树理论进行解读. 首先,我们先来回忆一下红黑树的5条基本规则. 1.结点是红色或者黑 ...

  2. java arraylist 赋值_ArrayList源码解析,老哥,来一起复习一哈?

    点击上方"码农沉思录",选择"设为星标" 优质文章,及时送达 前言 JDK源码解析系列文章,都是基于JDK8分析的,虽然JDK14已经出来,但是JDK8我还不会 ...

  3. Java并发之Semaphore源码解析

    Semaphore 前情提要 在学习本章前,需要先了解ReentrantLock源码解析,ReentrantLock源码解析里介绍的方法有很多是本章的铺垫.下面,我们进入本章正题Semaphore. ...

  4. Spring的日志模块-spring-jcl源码解析以及Java的日志框架

    1. spring-jcl模块结构 可以看到,这个模块的包名是apache的commons.logging,因为这是spring团队对common.logging 进行了重写,所以包名还保留之前的ap ...

  5. 爆火的Java面试题-kafka源码解析与实战豆瓣

    1 基础 为什么 Java 中只有值传递? int 范围?float 范围? hashCode 与 equals,什么关系? String StringBuffer 和 StringBuilder 的 ...

  6. java日志模块_Java源码初探_logging日志模块实现

    一.用途 程序中记录日志,打印到控制台.文件等方式,记录过程可根据日志级别做筛选,日志格式可以自定义. 大概结构如下所示: 简要说明各个模块: (1) LogManager:管理LoggerConte ...

  7. Java FileReader InputStreamReader类源码解析

    FileReader 前面介绍FileInputStream的时候提到过,它是从文件读取字节,如果要从文件读取字符的话可以使用FileReader.FileReader是可以便利读取字符文件的类,构造 ...

  8. 日志 note_深入源码解析日志框架Log4j2(二)

    异步 AsyncAppender ​ log4j2突出于其他日志的优势,异步日志实现.我们先从日志打印看进去.找到Logger,随便找一个log日志的方法. public void debug(fin ...

  9. 深入Java集合ArrayList的源码解析

    现在由大恶人付有杰来从增删改查几个角度轻度解析ArrayList的源码 首先ArrayList的底层数据结构非常简单,就是一个数组. 从源码第115行我们可以得出信息,他的默认数组长度是10. /** ...

  10. Java集合---Arrays类源码解析

    一.Arrays.sort()数组排序 Java Arrays中提供了对所有类型的排序.其中主要分为Primitive(8种基本类型)和Object两大类. 基本类型:采用调优的快速排序: 对象类型: ...

最新文章

  1. MyEclipse 中文乱码
  2. Python:安装dlib库
  3. java可视化模板——java可视化操作步骤
  4. 【模板】 最小生成树
  5. myysql 不能远程访问的解决办法
  6. (转载)windows server 2003的注意事项
  7. php修改隐藏域非法提交,PHP get、post变量放在表单隐藏域示例
  8. 南京理工大学计算机专业考研,2020南京理工大学计算机考研初试科目、参考书目、复试详情汇总...
  9. 官宣了!受疫情影响,程序员可免费领这些!
  10. 多级联动下拉java,下拉列表多级联动前端实现
  11. 利用计算机的图形能力来进行设计工作的是,计算机一级MSOffice考试巩固练习题...
  12. Web App开发入门
  13. windows如何安装pycharm2022版本?pycharm如何安装汉化语言包{www.423zy.com}
  14. python库itchat介绍(自动登录微信,自动回复消息)
  15. 【WebDriver】WebDriverWait 用法代码
  16. 使用ffmpeg进行音频采样率转换
  17. 本质矩阵E的内在性质证明
  18. The Thirty-fifth Of Word-Day
  19. vscode和sqlite3的环境配置
  20. php广告联盟,PHPCPS广告联盟系统

热门文章

  1. 开发用于异构环境的可生存云多机器人框架
  2. 22071班(8月16日作业)
  3. charles限制网速
  4. 手工脱壳之 ASPack压缩壳【随机基址】【重定位表加密】
  5. JPS(Jump Point Search)寻路及实现代码分析
  6. Windows XP SP3截至2011年4月更新补丁汇总(WinXP补丁包) 修正版
  7. 震惊了!鸿蒙又多了一种开发方式Ets,让更多的人赶上红利!
  8. 原光明中医函授大学教材
  9. html5 自动刷新,javascript – 每5分钟自动刷新一次
  10. 可微分神经计算机DNC