JUL全称Java util Logging是java原生的日志框架,使用时不需要另外引用第三方类库,相对其他日志框架使用方便,学习简单,能够在小型应用中灵活使用。
在JUL中有以下组件:
总结一下就是:
用户使用Logger来进行日志记录,Logger持有若干个Handler,日志的输出操作是由Handler完成的。 在Handler在输出日志前,会经过Filter的过滤,判断哪些日志级别过滤放行哪些拦截,Handler会将日志内容输出到指定位置(日志文件、控制台等)。Handler在输出日志时会使用Layout,将输出内容进行排版。
日志的级别
java.util.log.Level中定义了日志的级别,severe,warning,info,config,fine,finer,finest,off,all
@Test
public void testLogger() {
Logger logger = Logger.getLogger(LoggerTest.class.getName());
logger.severe("severe");
logger.warning("warning");
logger.info("info");
logger.config("config");
logger.fine("fine");
logger.finer("finer");
logger.finest("finest");
}
发现只能打印出info及以上的日志,为什么呢?
我们调用是jdk提供的Logger,jdk会默认读取一个对应的配置文件logging.properties,在jdk1.8中这文件的位置是在jdk目录下/jre/lib/loggingproperties下,删除注释后我们可以看到配置文件是
handlers= java.util.logging.ConsoleHandler
.level= INFO
java.util.logging.FileHandler.pattern = %h/java%u.log
java.util.logging.FileHandler.limit = 50000
java.util.logging.FileHandler.count = 1
java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter
java.util.logging.ConsoleHandler.level = INFO
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
这里.level和consoleHandler的日志级别都是info,这导致上面的logger只能打印info及以上的信息。那么为什么是这样呢?
日志的继承关系
和装饰模式类似,logger实例上可以装载handler,filter,logger和handler都可以调整可打印的日志级别,打印日志时从logger再到handler再到filter。而logger类也存在继承关系,始祖类是根looger是.
,不同包名代表不同的层次关系。如图,com下的logger继承根logger,study和learn继承com的logger,但是两者又相互独立。
有了以上知识,可以回头看配置文件,以空行作为分隔,第一部分是根logger的配置,配置handler是consoleHandler,和对应的日志级别,第二部分是filehandler的配置,现在没有被使用,第三部分是consoleHandler的配置,配置了日志级别和打印字符串模板类。consoleHandler是控制台打印的,因此我们看到的日志是在控制台上的,又由于.和consolerhandler的日志级别都是info,而使用的logger默认会继承. ,因此控制台只会打印info之上的信息。
logger的创建
logger是由LogManager实例调用不同的方法实现,源码如:
manager又是静态创建的,从这个方法的源码,我们可以看出是懒加载的单例
manager可以确保同一个类对应的logger只会被创建一次,使得logger也是单例,可以通过Logger.getLogger获取两次logger比较两者哈希值判断是否为同一个。
从上述知识明白manager负责管理logger,我们可以进入manager的初始化方法,观察到manager调用了
很明显这是一个读取配置的方法,其中又调用了readConfiguration(),该方法中如下部分读取了logging.properties,此部分完成操作是找到配置文件并且获取一个该文件的输入流,调用readConfigurataion读取流中的数据
读取流中的数据后,再通过反射创建所需要的配置类
自定义配置文件
知悉上述过程,我们便可以通过配置类自定义我们的根logger和其他logger,创建一份我们需要的配置文件,使用输入流读取该数据,再调用LogManager的readConfigurataion方法读取,再调用静态方法Logger.getLogger方法创建logger。
@Test
public void testConfig() throws IOException {
LogManager manager = LogManager.getLogManager();
InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream("logging.properties");
manager.readConfiguration(in);
Logger logger = Logger.getLogger(TestJUL.class.getName());
logger.info("info");
logger.fine("fine");
logger.finer("finer");
}
/*
四月 07, 2022 12:49:46 下午 learn.log.TestJUL testConfig
信息: info
四月 07, 2022 12:49:46 下午 learn.log.TestJUL testConfig
详细: fine
*/
通过自定义方式创建自己所需要的logger
除去自定义配置文件我们也可以直接不使用默认流程而去使用,比如第一个例子,默认流程如下
@Test
public void testLogger() {
Logger logger = Logger.getLogger("TestJUL.class.getName()");
//注意,不设置这个值的化,默认会继承根logger,默认配置中就又一个consoleHandler,
// 加下下面那个就有两个所以会打印日志两次
logger.setUseParentHandlers(false);
ConsoleHandler consoleHandler = new ConsoleHandler();
SimpleFormatter simpleFormatter = new SimpleFormatter();
consoleHandler.setFormatter(simpleFormatter);
consoleHandler.setLevel(Level.INFO);
logger.setLevel(Level.INFO);
logger.addHandler(consoleHandler);
logger.severe("severe");
logger.warning("warning");
logger.info("info");
logger.config("config");
logger.fine("fine");
logger.finer("finer");
logger.finest("finest");
}
同样的我们可以像配置文件修改对应的值,效果也一样。这里不再赘述。
此外,文本的格式话涉及一些format的转换符,这里也不赘述。需要使用时上网查询即可。
参考资料:https://www.bilibili.com/video/BV1434y1o73n