▎日志原理解析
JUL的执行流程如下:
① 初始化LogManager
- LogManager加载logging.properties配置
- 添加Logger到LogManager (包括自定义logger对象和RootLogger对象)
② 从单例LogManager获取Logger
③ 设置级别Level,并制定日志记录LogRecord
④ Filter提供了日志级别之外更细粒度的控制
⑤ Handler是用来处理日志输出位置
⑥ Formatter是用来格式LogRecord的
➳ 结论
我们的应用要进行日志记录的话,需要拿到Logger对象,Logger对象是从LogManager日志管理器中获取,LogManager对象加载配置文件、创建RootLogger对象以及创建自定义的Logger对象
紧接着设置日志级别,设置日志记录,通过filter过滤器进行细粒度的控制,是否放行,是否拦截,通过handler确认输出的位置,通过formatter格式化日志消息,最终日志输出到指定位置
源码查看
我们通过以下测试用例来进行断点,完整展示logger对象的执行原理和流程
@Test
public void test06() throws IOException {
// 1.获取Logger对象
Logger logger = Logger.getLogger("com");
// 2.打印日志信息
logger.info("info 一般信息");
}
1. 在获取logger对象处进行断点,debug模式运行
2. 如下图可看到进入了Logger对象的构造函数,其内部调用了 demandLogger 方法
3. 进入 demandLogger 方法,关注两个要点:
- 第一处:LogManager.getLogManager() ,该方法主要作用是加载logging的配置文件、创建所有logger对象的默认顶级父对象:RootLogger对象
- 第二处:manager.demandLogger()方法,该方法主要作用是加载我们自定义的logger对象,即 Logger logger = Logger.getLogger("com");
▸ 需要了解
- LogManager的初始化,需要加载的是配置文件和创建我们的RootLogger对象
- 自定义的Logger对象由manager.demandLogger()方法加载
▎LogManager.getLogManager() 方法详解
主要作用:1. 加载logging配置文件 2. 创建默认父元素RootLogger
1. 继续进入LogManager.getLogManager() 方法内部
2. 其 manager.ensureLogManagerInitialized() 方法内部有两处方法,继续进入下面第1个方法
3. ①加载配置文件:在上述第1个方法 readPrimordialConfiguration() 方法内部调用了 readConfiguration(),它是真正加载配置文件的方法
4. ①加载配置文件:在 readConfiguration() 方法内同样需要关注两处地方
- getProperty("java.util.logging.config.class"):当前系统是否有配置类,如果有则直接加载
- getProperty("java.util.logging.config.file") :是否有自定义的配置文件,如果有则直接加载
在上篇博文《JUL配置文件》中,讲述了配置文件的使用和自定义,也就是上图方式2的加载方式,详情介绍可移步翻阅
5. ①加载配置文件:如果我们没有自定义的logging配置文件,以及相关配置类,则会去默认查找java.home目录,找到 lib/logging.properties 默认的配置文件,进行加载里面的配置
由于我们没有定义配置类和自定义配置文件,因此会加载默认的配置文件,我们可以复制该路径地址,打开访达,使用快捷键 Command + shift +G 进行搜索
进入lib 目录,找到logging.properties 配置文件
打开文件,内容如下
# RootLogger顶级父元素指定的默认处理器为:ConsoleHandler(多个处理器之间用逗号隔开)
handlers= java.util.logging.ConsoleHandler
# RootLogger顶级父元素默认的日志级别为:INFO
.level= INFO
# -------------------- 向文件输出的handler对象--------------------
# 指定日志文件路径:%h 当前用户目录,文件名以java开始,%u 是以数字取值
java.util.logging.FileHandler.pattern = %h/java%u.log
# 指定日志文件内容大小,最大为5w条日志
java.util.logging.FileHandler.limit = 50000
# 指定日志文件数量:如果设置为10,则pattern日志文件的命名中的%u取值是 0-9之间
java.util.logging.FileHandler.count = 1
# 指定日志消息格式对象:日志内容将以XML的形式输出到文件中
java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter
# -------------------- 向控制台输出的handler对象--------------------
# 指定 handler 对象的日志级别
java.util.logging.ConsoleHandler.level = ALL
# 指定日志消息格式对象:日志内容是简单的文字信息
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
☛ 注意:文件中更多配置参数可翻阅上篇博文《JUL配置文件》,有详细配置介绍
6. 至此①加载配置文件 方法介绍完毕,接下来查看创建父元素RootLogger对象方法。下图方法2
7. 如下图进入new RootLogger() 方法,我们可以看到其父类是Logger类,调用了父类的构造函数
8. 其super父类构造函数的具体实现如下, 设置了父元素RootLogger的一些相关属性
9. 创建完毕后,将其父对象放入LogManager日志管理器中
10. 查看addLogger() 具体方法实现,如下图所示
★ LoggerContext对象
该对象实际内部维护了一个map集合,key是logger对象名称, value是logger对象的引用
11. 查看步骤10中具体cx.addLocalLogger(logger)方法内部,实际就是将父对象put到map集合中
▎manager.demandLogger()方法详解
其主要作用是加载我们自定义的logger对象
1. 继续断点查看 manager.demandLogger ()方法
2. 方法内部实际上与加载父元素RootLogger是一样的,初始化完成后,放入map中,这里不再继续展示了,感兴趣的伙伴可以自己断点走下去看看