目录
前言
Java Util Logging (JUL) 是Java平台自带的一个日志框架,无需额外依赖第三方库即可使用。由于其集成度高、配置简单的特点,JUL在一些小型项目或初学者入门阶段非常受欢迎。本文将详细介绍JUL的基本概念、常用组件以及如何通过示例代码来理解和使用这些组件。此外,我们还会探讨如何通过配置文件来自定义日志行为,这对于实现更加灵活的日志管理至关重要。
一、JUL简介
JUL全称Java Util Logging,它是java原生的日志框架,使用时不需要另外引用第三方的类库,相对其他的框架使用方便,学习简单,主要使用在小型应用中。
二、JUL组件介绍
- Logger:被称为记录器,应用程序通过获取Logger对象,调用其API来发布日志信息。Logger通常被认为是访问日志系统的入口程序。
- Handler:处理器,每个Logger都会关联一个或者是一组Handler,Logger会将日志交给关联的Handler去做处理,由Handler负责将日志做记录。Handler具体实现了日志的输出位置,比如可以输出到控制台或者文件中等等。
- Filter:过滤器,根据需要定制哪些信息会被记录,哪些信息会被略过。
- Formatter:格式化组件,它负责对日志中的数据和信息进行转换和格式化,所以它决定了我们输出日志最终的形式。
- Level:日志的输出级别,每条日志消息都有一个关联的级别。我们根据输出级别的设置,用来展现最终所呈现的日志信息。根据不同的需求,去设置不同的级别。
三、代码实例
1. 入门案例
// 入门案例
@Test
public void test01() {
// Logger创建方式,参数为当前类全路径字符串com.demo.logger.jul.JULTest
Logger logger = Logger.getLogger(JULTest.class.getCanonicalName());
/*
第一种方式:
直接调用日志级别相关的方法,方法中传递日志输出信息
假设现在我们要输出info级别的日志信息
*/
logger.info("输出info信息");
/*
第二种方式:
调用通用的log方法,然后在里面通过level类型来定义日志的级别参数,以及搭配日志输出信息的参数
*/
logger.log(Level.WARNING, "输出warning信息");
// 动态输出数据,生产日志,使用占位符的方式进行操作
String name = "zhangsan";
int age = 23;
// logger.log(Level.INFO, "姓名:" + name + "年龄:" + age);
logger.log(Level.INFO, "姓名:{0}年龄:{1}", new Object[]{name, age});
}
2. 日志级别
// 日志级别
@Test
public void test02() {
/*
日志的级别,总共七级
Level.SEVERE:(最高级)错误
Level.WARNING:警告
Level.INFO:(默认级别)消息
Level.CONFIG:配置级别
Level.FINE:详细信息(少)
Level.FINER:详细信息(中)
Level.FINEST:(最低级)详细信息(多)
两个特殊的级别:
Level.OFF;可用来关闭日志记录
Level.ALL:启用所有日志记录
对于日志的级别,我们重点关注的是new对象时第二个参数,是一个数值:
public static final Level OFF = new Level("OFF",Integer.MAX_VALUE, defaultBundle);
public static final Level SEVERE = new Level("SEVERE",1000, defaultBundle);
public static final Level WARNING = new Level("WARNING", 900, defaultBundle);
public static final Level INFO = new Level("INFO", 800, defaultBundle);
public static final Level CONFIG = new Level("CONFIG", 700, defaultBundle);
public static final Level FINE = new Level("FINE", 500, defaultBundle);
public static final Level FINER = new Level("FINER", 400, defaultBundle);
public static final Level FINEST = new Level("FINEST", 300, defaultBundle);
public static final Level ALL = new Level("ALL", Integer.MIN_VALUE, defaultBundle);
这个数值的意义在于:如果我们设置的日志级别是800,那么最终展现的日志信息,比如是数值大于800的所有日志信息。
*/
Logger logger = Logger.getLogger(JULTest.class.getCanonicalName());
logger.severe("severe信息");
logger.warning("warning信息");
logger.info("info信息");
logger.config("config信息");
logger.fine("fine信息");
logger.finer("finer信息");
logger.finest("finest信息");
}
3. 自定义日志级别
JUL对于日志级别的自定义有些麻烦,要先排除掉默认的Handler,然后替换为自定义的Handler。
// 自定义日志级别
@Test
public void test03(){
//日志记录器
Logger logger = Logger.getLogger(JULTest.class.getCanonicalName());
//将默认的日志打印方式关闭掉
//参数设置为false,我们打印日志的方式就不会按照父logger默认的方式去进行操作
logger.setUseParentHandlers(false);
//处理器Handler
//在此我们使用的是控制台日志处理器,取得处理器对象
ConsoleHandler handler = new ConsoleHandler();
//创建日志格式化组件对象
SimpleFormatter formatter = new SimpleFormatter();
//在处理器中设置输出格式
handler.setFormatter(formatter);
//在记录器中添加处理器
logger.addHandler(handler);
//设置日志的打印级别
//此处必须将日志记录器和处理器的级别进行统一的设置,才会达到日志显示相应级别的效果
//logger.setLevel(Level.CONFIG);
//handler.setLevel(Level.CONFIG);
logger.setLevel(Level.ALL);
handler.setLevel(Level.ALL);
logger.severe("severe信息");
logger.warning("warning信息");
logger.info("info信息");
logger.config("config信息");
logger.fine("fine信息");
logger.finer("finer信息");
logger.finest("finest信息");
}
3. 将日志输出到文件中
用户使用Logger来进行日志的记录,Logger可以持有多个处理器Handler(日志的记录使用的是Logger,日志的输出使用的是Handler)。添加了哪些handler对象,就相当于需要根据所添加的handler将日志输出到指定的位置上,例如控制台、文件等。
@Test
public void test04() throws IOException {
/*
将日志输出到具体的磁盘文件中
这样做相当于是做了日志的持久化操作
*/
Logger logger = Logger.getLogger(JULTest.class.getCanonicalName());
logger.setUseParentHandlers(false);
//文件日志处理器,输出到指定目录下
FileHandler handler = new FileHandler("D:\\test\\myLogTest.log");
SimpleFormatter formatter = new SimpleFormatter();
handler.setFormatter(formatter);
logger.addHandler(handler);
//也可以同时在控制台和文件中进行打印
ConsoleHandler handler2 = new ConsoleHandler();
handler2.setFormatter(formatter);
logger.addHandler(handler2); //可以在记录器中同时添加多个处理器
logger.setLevel(Level.ALL);
handler.setLevel(Level.ALL); // 文件中的日志级别为ALL
handler2.setLevel(Level.CONFIG); // 控制台的日志级别为CONFIG
logger.severe("severe信息");
logger.warning("warning信息");
logger.info("info信息");
logger.config("config信息");
logger.fine("fine信息");
logger.finer("finer信息");
logger.finest("finest信息");
}
4. Logger的父子关系
JUL中Logger之间是存在"父子"关系的,值得注意的是,这种父子关系不是我们普遍认为的类之间的继承关系,这种关系是通过树状结构存储的。
@Test
public void test05(){
/*
从下面创建的两个logger对象看来
我们可以认为logger1是logger2的父亲
*/
//父亲是RootLogger,名称默认是一个空的字符串
//RootLogger可以被称之为所有logger对象的顶层logger
Logger logger1 = Logger.getLogger("com.demo.test");
Logger logger2 = Logger.getLogger("com.demo.test.JULTest");
System.out.println(logger2.getParent()==logger1); //true
// logger1的父Logger引用为:java.util.logging.LogManager$RootLogger@31ef45e3; 名称为com.demo.test; 父亲的名称为
System.out.println("logger1的父Logger引用为:"
+logger1.getParent()+"; 名称为"+logger1.getName()+"; 父亲的名称为"+logger1.getParent().getName());
// logger2的父Logger引用为:java.util.logging.Logger@598067a5; 名称为com.demo.test.JULTest; 父亲的名称为com.demo.test
System.out.println("logger2的父Logger引用为:"
+logger2.getParent()+"; 名称为"+logger2.getName()+"; 父亲的名称为"+logger2.getParent().getName());
/*
父亲所做的设置,也能够同时作用于儿子
对logger1做日志打印相关的设置,然后我们使用logger2进行日志的打印
*/
//父亲做设置
logger1.setUseParentHandlers(false);
ConsoleHandler handler = new ConsoleHandler();
SimpleFormatter formatter = new SimpleFormatter();
handler.setFormatter(formatter);
logger1.addHandler(handler);
handler.setLevel(Level.ALL);
logger1.setLevel(Level.ALL);
//儿子做打印,会输出ALL
logger2.severe("severe信息");
logger2.warning("warning信息");
logger2.info("info信息");
logger2.config("config信息");
logger2.fine("fine信息");
logger2.finer("finer信息");
logger2.finest("finest信息");
}
5. 使用配置文件
5.1 默认配置文件位置
如果我们没有自己添加配置文件,则会使用系统默认的配置文件。也就是说,如果我们没有指定配置文件的话,JUL也是会读取默认的配置文件,对于不同的JDK版本来说,读取的文件路径是不一样的,一般我们会认为其默认配置文件在java.homo目录中,jre文件夹下,lib目录中的logging.properties文件。但是,以我的JDK17为例,其配置文件所在的位置如下图所示:
5.2 配置文件解析
# RootLogger使用的处理器,在获取RootLogger对象时进行的设置
# 默认情况下,配置的是控制台处理器,只能在控制台上进行输出操作
# 如果想要其他的处理器,在处理器类后面通过以逗号的形式进行分割
handlers= java.util.logging.ConsoleHandler
# 根节点RootLogger的日志级别
# 默认情况下,这是全局的日志级别,如果不手动配置其他的日志级别,则默认输出下述配置的级别及更高的级别
.level= INFO
# 文件处理器属性的设置
# 输出日志文件的路径
java.util.logging.FileHandler.pattern = %h/java%u.log
# 输出日志文件的限制(默认50000个字节)
java.util.logging.FileHandler.limit = 50000
# 设置日志文件的数量
java.util.logging.FileHandler.count = 1
# 输出日志的格式
# 默认是以XML的方式输出
java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter
# 控制台处理器的属性设置
# 控制台输出默认级别
java.util.logging.ConsoleHandler.level = INFO
# 控制台默认输出日志的格式
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
# 也可以将日志级别设定到具体的某个包下
#com.xyz.foo.level = SEVERE
5.3 自定义读取配置文件
可以将logging.properties文件稍作修改,验证有效性。
InputStream input = new FileInputStream("E:\\logging.properties");
//取得日志管理器对象
LogManager logManager = LogManager.getLogManager();
//读取自定义的配置文件
logManager.readConfiguration(input);
Logger logger = Logger.getLogger(JULTest.class.getCanonicalName());
logger.severe("severe信息");
logger.warning("warning信息");
logger.info("info信息");
logger.config("config信息");
logger.fine("fine信息");
logger.finer("finer信息");
logger.finest("finest信息");
5.4 自定义日志配置文件
以下配置可以自定义FileHandler的设置:
# 自定义Logger
com.demo.logger.jul.handlers=java.util.logging.FileHandler
# 自定义Logger日志等级
com.demo.logger.jul.level=CONFIG
# 屏蔽掉父Logger的日志设置
com.demo.logger.jul.useParentHandlers=false
默认使用的是XMLFormatter,我们可以改成容易读的:
java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter
文件默认保存在用户的home目录的java0.log文件。默认的日志文件是覆盖的方式,我们可以设置为追加的方式:
# 输出日志文件,是否追加
java.util.logging.FileHandler.append=true
总结
本文通过一系列示例代码详细介绍了Java Util Logging的基本使用方法和高级特性,包括如何创建和使用Logger对象、设置日志级别、配置日志输出目的地以及利用父子关系来简化日志配置。此外,还介绍了如何通过自定义配置文件来实现更复杂的日志管理需求。掌握了这些知识后,开发者可以更加高效地管理和调试他们的Java应用程序。尽管JUL相比其他第三方日志框架可能功能有限,但在某些场景下,尤其是对于小型项目或特定需求的应用而言,它仍然是一个非常实用的选择。