1 日志实现原理
Java 的日志框架有很多,比如:JUL(Java Util Logging)、Log4j、Logback、Log4j2、Tinylog 等,但其核心功能,实现原理基本一致。
2.1 核心功能
日志系统核心时记录日志,以方便排查问题或作为其他系统进行统计。其核心功能如下
-
1 支持多渠道输出
-
2 日志信息支持多等级
-
3 渠道,日志和等级做关联,以支持渠道过滤不必要的日志信息
2.2 架构设计
针对上述功能日志系统划分为如下模块:
![](https://img.haomeiwen.com/i9793627/da44d578a80ffc97.png)
Loggers:被称为记录器,应用程序通过获取Logger对象,调用其API来来发布日志信息。Logger通常时应用程序访问日志系统的入口程序。
Appenders:也被称为Handlers,每个Logger都会关联一组Handlers,Logger会将日志交给关联Handlers处理,由Handlers负责将日志做记录。Handlers在此是一个抽象,其具体的实现决定了日志记录的位置可以是控制台、文件、网络上的其他日志服务或操作系统日志等。
Layouts:也被称为Formatters,它负责对日志事件中的数据进行转换和格式化。Layouts决定了数据在一条日志记录中的最终形式。
Level:每条日志消息都有一个关联的日志级别。该级别粗略指导了日志消息的重要性和紧迫,我可以将Level和Loggers,Appenders做关联以便于我们过滤消息。
2.3 处理流程
当Logger记录一个日志时,它将日志信息转发给适当的Appender。然后Appender使用Layout来对日志记录进行格式化,并将其发送给控制台、文件或者其它目标位置。
2 JUL
2.1 概述
JUL全称Java util Logger是java原生的日志框架,使用时不需要另外引用类库,相对其他日志框架方便,学习简单,能够在小型应用中灵活使用。
2.2 核心功能
2.2.1 Logger
JUL中Logger之间存在父子关系,这种父子关系通过树状结构存储,JUL在初始化时会创建一个顶层RootLogger作为所有Logger父Logger,存储上作为树状结构的根节点。并父子关系通过路径来关联。
Logger logger = Logger.getLogger("com.wuhao.log");
Logger logger1 = Logger.getLogger("com.wuhao");
Logger logger2 = Logger.getLogger("com");
System.out.println(logger);
System.out.println(logger1.equals(logger.getParent()));
System.out.println(logger2.equals(logger1.getParent()));
从上诉代码可以发现父Loggers并不是表示父类,Loggers类内部存在一个类型为Loggers属性parent用来表示父Loggers.
父Loggers存在意义在于复用处理器Handler,在默认情况Logger记录日志时,会判断是否存在父Loggers,如果存在那么不仅仅自己关联Handlers会处理,其父Loggers关联Handlers也会处理。当然我们也可以通过配置忽略父Logger来处理。
2.2.2 日志级别
每条日志消息都有一个关联的日志级别。该级别粗略指导了日志消息的重要性和紧迫性,日志级别定义在java.util.logging.Level类中,用装整数值来表示,值越高表示优先级越高。
常用日志级别如下:
- SEVERE(最高值)
- WARNING
- INFO
- CONFIG
- FINE
- FINER
- FINEST(最低值)
还有两个特殊的级别
- OFF,可用来关闭日志记录。
- ALL,启用所有消息的日志记录。
logger默认的级别是INFO,比INFO更低的日志将不显示
2.2.3 Handler
JUL提供多种日志处理器。
- StreamHandler:用于将格式化记录写入OutputStream的简单处理程序。
- ConsoleHandler:用于将格式化记录写入System.err的简单处理程序
- FileHandler:将格式化日志记录写入单个文件或一组旋转日志文件的处理程序。
- SocketHandler:将格式化日志记录写入远程TCP端口的处理程序。
- MemoryHandler:缓冲内存中日志记录的处理程序。
2.2.4 Formatter
JUL提供了2种日志格式处理器
- SimpleFormatter:写简短的“人类可读”日志记录摘要。
- XMLFormatter:写入详细的XML结构信息。
2.2.5 多样化记录日志API
Logger类提供了一组用于生成日志消息的便捷方法,为方便起见,每个日志记录级别都有记录方法API,方法名称以日志记录级别名称命名。这样就不再需要调用“logger.log(Level.WARNING,......”方式记录日志
调用相应级别的API方法
public void warning(String msg) {
log(Level.WARNING, msg);
}
将级别作为参数传入来方法
public void log(Level level, String msg) {
if (!isLoggable(level)) {
return;
}
LogRecord lr = new LogRecord(level, msg);
doLog(lr);
}
同时记录日志时可以告知记录日志的源类名和源方法名
oid warning(String sourceClass,String sourceMethod,String msg);
2.2.6 LogManager
LogManager 被称为Logger的管理器,当我们获取Logger本质是从LogManager获取,LogManager内部通过LoggerContext类对象来存储logger,并维护logger之间的父子关系。实现原理是将每个Looger封装为LogNode节点,存在在一个树状结构中。
2.2.7 配置文件
JUL读取指定路径下logging.properties文件来初始化LogManager,默认情况下配置文件路径为$JAVAHOME\jre\lib\logging.properties
2.3 使用案例
Logger之间的父子关系
/**
* 日志之间存在父子关系,最顶层的日志类型为LogManager$RootLogger,命名为""
*/
@Test
public void test() throws IOException {
Logger logger = Logger.getLogger("com.wuhao.log");
Logger logger1 = Logger.getLogger("com.wuhao");
Logger logger2 = Logger.getLogger("com");
System.out.println(logger);
System.out.println(logger1.equals(logger.getParent()));
System.out.println(logger2.equals(logger1.getParent()));
System.out.println(logger2.getParent());
}
java.util.logging.Logger@7fbe847c
true
true
java.util.logging.LogManager$RootLogger@41975e01
读取配置文件打印日志
@Test
public void test2() throws IOException {
InputStream in = JULTest.class.getResourceAsStream("/logging.properties");//注意配置
logManager.readConfiguration(in);
Logger logger= Logger.getLogger("TestLog");
logger.info("INFO");
Logger otherLog = Logger.getLogger("otherLog");
otherLog.info("Other INFO");
}
配置文件
## RootLogger处理器(获取时设置)
handlers= java.util.logging.ConsoleHandler
## RootLogger处理器(打印日志时设置)
.handlers= RootLogger.java.util.logging.FileHandler
# RootLogger日志等级
.level= INFO
## TestLog日志处理器
TestLog.handlers= java.util.logging.FileHandler
# TestLog日志等级
TestLog.level= INFO
# 忽略父日志处理
TestLog.useParentHandlers=false
## 控制台处理器
# 输出日志级别
java.util.logging.ConsoleHandler.level = INFO
# 输出日志格式
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
## 文件处理器
# 输出日志级别
java.util.logging.FileHandler.level=INFO
# 输出日志格式
java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter
# 输出日志文件路径
java.util.logging.FileHandler.pattern = D:\\TestLog.log
# 输出日志文件限制大小(50000字节)
java.util.logging.FileHandler.limit = 50000
# 输出日志文件限制个数
java.util.logging.FileHandler.count = 10
# 输出日志文件 是否是追加
java.util.logging.FileHandler.append=true
## 文件处理器
# 输出日志级别
RootLogger.java.util.logging.FileHandler.level=INFO
# 输出日志格式
RootLogger.java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter
# 输出日志文件路径
RootLogger.java.util.logging.FileHandler.pattern = D:\\RootLogger.log
# 输出日志文件限制大小(50000字节)
RootLogger.java.util.logging.FileHandler.limit = 50000
# 输出日志文件限制个数
RootLogger.java.util.logging.FileHandler.count = 10
# 输出日志文件 是否是追加
RootLogger.java.util.logging.FileHandler.append=true
3 原理解析
3.1 获取Logger
getLogger获取Logger的本质是从全局单例LogManager获取Logger。
/** 单例LogManager **/
private static final LogManager manager;
/**
* 获取Logger
*/
@CallerSensitive
public static Logger getLogger(String name) {
return demandLogger(name, null, Reflection.getCallerClass());
}
/**
* 从单例LogManager获取Logger
*/
private static Logger demandLogger(String name, String resourceBundleName, Class<?> caller) {
/** 获取单例LogManager **/
LogManager manager = LogManager.getLogManager();
SecurityManager sm = System.getSecurityManager();
if (sm != null && !SystemLoggerHelper.disableCallerCheck) {
if (caller.getClassLoader() == null) {
return manager.demandSystemLogger(name, resourceBundleName);
}
}
/** 从单例LogManager中获取Logger **/
return manager.demandLogger(name, resourceBundleName, caller);
}
/**
* 获取单例LogManager
*/
public static LogManager getLogManager() {
if (manager != null) {
/** 单例LogManager初始化 **/
manager.ensureLogManagerInitialized();
}
return manager;
}
2.4.1 初始化LogManager
final void ensureLogManagerInitialized() {
final LogManager owner = this;
/** 如果当前对象不是单例LogManager或以被初始化直接返回 **/
if (initializationDone || owner != manager) {
return;
}
synchronized(this) {
...省略代码
try {
AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
assert rootLogger == null;
assert initializedCalled && !initializationDone;
/** 单例LogManager读取 $JAVAHOME\jre\lib\logging.properties 配置文件加载到Properties类型的属性props中**/
owner.readPrimordialConfiguration();
/** 给LogManager添加RootLogger **/
owner.rootLogger = owner.new RootLogger();
owner.addLogger(owner.rootLogger);
/** 是否设置rootLogger日志级别**/
if (!owner.rootLogger.isLevelInitialized()) {
/** 设置RootLogger日志等级 **/
owner.rootLogger.setLevel(defaultLevel);
}
/** 给LogManager添加global Logger. **/
final Logger global = Logger.global;
owner.addLogger(global);
return null;
}
});
} finally {
initializationDone = true;
}
}
}
LogManager加载logging.properties配置
private void readPrimordialConfiguration() {
/** 以读取配置文件跳过 **/
if (!readPrimordialConfiguration) {
synchronized (this) {
if (!readPrimordialConfiguration) {
if (System.out == null) {
return;
}
readPrimordialConfiguration = true;
try {
AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
@Override
public Void run() throws Exception {
/** 读取默认logging.properties配置 **/
readConfiguration();
sun.util.logging.PlatformLogger.redirectPlatformLoggers();
return null;
}
});
} catch (Exception ex) {
assert false : "Exception raised while reading logging configuration: " + ex;
}
}
}
}
}
/**
* 读取默认logging.properties配置
*/
public void readConfiguration() throws IOException, SecurityException {
checkPermission();
/** 尝试获取系统属性java.util.logging.config.class对应日志配置实现类,并实例化对象 **/
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);
}
}
/**
* 从系统属性java.util.logging.config.file中获取默认配置文件路径
* 如果不存在则读取默认配置文件路径$JAVAHOME\jre\lib\logging.properties
**/
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();
}
/** 获取配置文件FileInputStream **/
try (final InputStream in = new FileInputStream(fname)) {
final BufferedInputStream bin = new BufferedInputStream(in);
/** 读取InputStream配置信息 **/
readConfiguration(bin);
}
}
/**
* 读取InputStream配置信息
*/
public void readConfiguration(InputStream ins) throws IOException, SecurityException {
checkPermission();
/** 重置LogManager 内部调用resetLogger方法重置所有被LogManager管理的Logger**/
reset();
/** 将配置文件信息加载到props **/
props.load(ins);
/** 解析配置中config属性值,将","分隔字符串分隔成字符串数组**/
String names[] = parseClassNames("config");
/** 尝试实例化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();
}
}
/**
* 读取配置文件中 .level结尾的配置,设置对应Logger的日志等级
* test.level=INFO 标识test Loggr的日志等级为INFO
* **/
setLevelsOnExistingLoggers();
/** 为LogManager设置属性监听器 **/
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);
}
}
}
/** 标识还未设置全局Handlers **/
synchronized (this) {
initializedGlobalHandlers = false;
}
}
添加Logger到LogManager
LogManager内部通过LoggerContext类对象来存储logger,并维护logger之间的父子关系。实现原理是将每个Looger封装为LogNode节点,存在在一个树状结构中。
private static class LogNode {
/** 子节点 **/
HashMap<String,LogNode> children;
/** 关联Logger引用对象 **/
LoggerWeakRef loggerRef;
/** 父节点**/
LogNode parent;
/** 关联的LoggerContext **/
final LoggerContext context;
class LoggerContext {
/** 存储LoggerContext中保存的所有Logger引用对象 **/.
private final Hashtable<String,LoggerWeakRef> namedLoggers = new Hashtable<>();
// 树状结构根节点
private final LogNode root;
private LoggerContext() {
this.root = new LogNode(null, this);
}
final class LoggerWeakRef extends WeakReference<Logger> {
/** 日志名称 **/
private String name;
/** 关联的LogNode **/
private LogNode node;
/** 父节点引用对象 **/
private WeakReference<Logger> parentRef;
/** 是否处理 **/
private boolean disposed = false;
LoggerWeakRef(Logger logger) {
/** 将logger设置为弱引用 **/
super(logger, loggerRefQueue);
name = logger.getName(); cleanup
}
/**
* 添加logger
*/
public boolean addLogger(Logger logger) {
final String name = logger.getName();
if (name == null) {
throw new NullPointerException();
}
drainLoggerRefQueueBounded();
/** 获取用户 LoggerContext **/
LoggerContext cx = getUserContext();
/** 将logger 添加到 LoggerContext**/
if (cx.addLocalLogger(logger)) {
/** 读取配置文件中"日志名称.handlers"属性值,并实例化为Handler关联到logger **/
loadLoggerHandlers(logger, name, name + ".handlers");
return true;
} else {
return false;
}
}
boolean addLocalLogger(Logger logger) {
return addLocalLogger(logger, requiresDefaultLoggers());
}
synchronized boolean addLocalLogger(Logger logger, boolean addDefaultLoggersIfNeeded) {
if (addDefaultLoggersIfNeeded) {
ensureAllDefaultLoggers(logger);
}
/** 获取日志名称 **/
final String name = logger.getName();
if (name == null) {
throw new NullPointerException();
}
/**
* 获取对应LoggerWeakRef,如果存在说明Logger已经添加直接返回
* **/
LoggerWeakRef ref = namedLoggers.get(name);
if (ref != null) {
if (ref.get() == null) {
ref.dispose();
} else {
return false;
}
}
/** 为添加Logger实例化LoggerWeakRef 添加到Context.namedLoggers属性中 **/
final LogManager owner = getOwner();
logger.setLogManager(owner);
ref = owner.new LoggerWeakRef(logger);
namedLoggers.put(name, ref);
/** 读取配置文件中设置name + ".level" 日志等级 **/
Level level = owner.getLevelProperty(name + ".level", null);
if (level != null && !logger.isLevelInitialized()) {
doSetLevel(logger, level);
}
/**
* 读取配置文件中的 name + ".useParentHandlers" 设置当前Logger所对应父Logger是否忽略处理
* 如果配置文件中的存在pname + ".level" 或 pname + ".handlers" 配置,则创建名称pname对应的Logger
* **/
processParentHandlers(logger, name);
/** 获取并将当前logger存储到Context树状结构中 **/
LogNode node = getNode(name);
node.loggerRef = ref;
Logger parent = null;
/** 获取父节点 **/
LogNode nodep = node.parent;
while (nodep != null) {
LoggerWeakRef nodeRef = nodep.loggerRef;
if (nodeRef != null) {
parent = nodeRef.get();
if (parent != null) {
break;
}
}
nodep = nodep.parent;
}
/** 给当前添加logger建立父子关系 **/
if (parent != null) {
doSetParent(logger, parent);
}
node.walkAndSetParent(logger);
ref.setNode(node);
return true;
}
2.4.2 从单例LogManager获取Logger
Logger demandLogger(String name, String resourceBundleName, Class<?> caller) {
/** 从LoggerContext中获取Logger **/
Logger result = getLogger(name);
/** 如果不存在则新建Logger添加到LoggerManger **/
if (result == null) {
Logger newLogger = new Logger(name, resourceBundleName, caller, this, false);
do {
if (addLogger(newLogger)) {
return newLogger;
}
result = getLogger(name);
} while (result == null);
}
return result;
}
/**
*
* @param name 名称
* @param resourceBundleName 资源
* @param manager 关联的manager
* @param isSystemLogger 是否是系统Logger
*/
Logger(String name, String resourceBundleName, Class<?> caller, LogManager manager, boolean isSystemLogger) {
this.manager = manager;
this.isSystemLogger = isSystemLogger;
setupResourceInfo(resourceBundleName, caller);
this.name = name;
levelValue = Level.INFO.intValue();
}
3.2 打印日志
/**
* 记录INFO类型的消息
*/
public void info(String msg) {
log(Level.INFO, msg);
}
/**
* 记录INFO类型的日志
*/
public void log(Level level, String msg) {
if (!isLoggable(level)) {
return;
}
/** 将日志封装称LogRecord **/
LogRecord lr = new LogRecord(level, msg);
doLog(lr);
}
private void doLog(LogRecord lr) {
/** 设置日志名称 **/
lr.setLoggerName(name);
/** 设置LogRecord相关属性**/
final LoggerBundle lb = getEffectiveLoggerBundle();
final ResourceBundle bundle = lb.userBundle;
final String ebname = lb.resourceBundleName;
if (ebname != null && bundle != null) {
lr.setResourceBundleName(ebname);
lr.setResourceBundle(bundle);
}
/** 打印日志 **/
log(lr);
}
public void log(LogRecord record) {
/** 校验匹配日志级别 **/
if (!isLoggable(record.getLevel())) {
return;
}
/** 使用Filter 过滤消息**/
Filter theFilter = filter;
if (theFilter != null && !theFilter.isLoggable(record)) {
return;
}
Logger logger = this;
while (logger != null) {
/**
* 获取日志处理器
* 这里主要是针对RootLogger 获取配置文件中handlers属性加载设置到处理中
* **/
final Handler[] loggerHandlers = isSystemLogger
? logger.accessCheckedHandlers()
: logger.getHandlers();
/** 日志传递给关联handler 执行记录 **/
for (Handler handler : loggerHandlers) {
handler.publish(record);
}
/** 判断是否需要将日记记录传递给父logger处理 **/
final boolean useParentHdls = isSystemLogger
? logger.useParentHandlers
: logger.getUseParentHandlers();
/** 无需交给父logger处理退出 **/
if (!useParentHdls) {
break;
}
/** 获取父looger记录日志 **/
logger = isSystemLogger ? logger.parent : logger.getParent();
}
}
private final class RootLogger extends Logger {
private RootLogger() {
super("", null, null, LogManager.this, true);
}
@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();
}
}
/**
* 读取配置文件handlers加载设置handlers
*/
private synchronized void initializeGlobalHandlers() {
if (initializedGlobalHandlers) {
return;
}
initializedGlobalHandlers = true;
if (deathImminent) {
return;
}
loadLoggerHandlers(rootLogger, null, "handlers");
}
参考
https://docs.oracle.com/javase/8/docs/technotes/guides/logging/overview.html
网友评论