目录
日志框架出现的历史顺序:Log4j → JUL → JCL → slf4j → logback → log4j2
JUL日志框架介绍
JUL 全称 Java Util Logging,是Java原生的日志框架,JDK自带的,使用时不需要另外引用第三方类库,相对其他日志框架使用方便,学习简单,能够在小型应用中灵活使用
通过Logger日志记录器进行日志的记录和输出,logger主要可以做两件事:
- 设置要输出的日志级别
- 设置日志的具体内容
流程如下图所示
日志记录器底层调用了Handler处理器,它的主要功能是将日志进行记录、日志格式化的转换、以及输出到指定位置,比如说控制台、文件等等
☁ 那么Handler 处理器是如何进行格式化转换的呢?
其实它底层也调用了一个对象:layouts,也称为formatters,日志内容转换完毕后通过Handler进行日志的输出,最终到达指定的路径
▎相关组件说明
Loggers
被称为记录器,应用程序通过获取logger对象,调用其他API来发布日志信息。logger通常是应用程序访问日志系统的入口程序
Appenders
也被称为handlers,每个logger都会关联一组handlers,logger会将日志交给关联的handlers处理,handlers在此是一个抽象,其具体的实现决定了日志记录的位置可以是控制台、文件、网络上的其他日志服务或操作系统日志等
Layouts
也被称为formatters,它负责对日志事件中的数据进行转换和格式化。layouts决定了数据在一条日志记录中的最终形式。
Level
每条日志消息都有一个关联的日志级别。该级别粗略指导了日志消息的重要性和紧迫,可以将level和loggers,appenders做关联以便于我们过滤消息
Filters
过滤器,根据需要定制哪些信息会被记录,哪些信息会被放过。
创建使用Logger对象
要使用J2SE的日志功能,首先要取得java.util.logging.Logger实例,这可以通过Logger类的两个静态getLogger()方法来取得:
// 查找或创建一个logger
static Logger getLogger(String name)
// 为指定子系统查找或创建一个logger
static Logger getLogger(String name, String resourceBundleName)
!! 注意:name是Logger的名称,当名称相同时候,同一个名称的Logger只创建一个,名称一般建议使用当前类的权限定名称
代码示例
public class LogDemo {
@Test
public void test01(){
// 1.获取日志记录器对象
Logger logger = Logger.getLogger("com.test.LogDemo");//当前类权限定名称
// 2.普通日志记录输出
logger.info("hello log");
// 又或者如下打印,参数一是日志级别,同上述logger.info() ,参数二是内容
logger.log(Level.INFO,"hello log2");
// 3.通过占位符,输出内容
String name="wpf";
int age=18;
/** @param1 : 日志级别
* @param2 : 日志具体内容 {}是占位符,括号里面是数组下标
* @param3 : 参数数组,是{}占位符里的具体内容
*/
logger.log(Level.INFO,"hi {0},{1}",new Object[]{name,age});
}
}
执行结果
日志级别
在进行信息的记录时,依信息程序的不同,会设定不同等级的信息输出。Java log比log4j的级别详细,全部定义在 java.util.logging.Level 里面,除去off 和 all 两个特殊级别,可分为7种
日志级别(降序) | 对应的整数 | 使用 |
---|---|---|
OFF | 最大整数( Integer. MAX_VALUE) | 关闭所有级别的日志记录(不捕获任何内容) |
SEVERE | 1000 | 严重故障 |
WARNING | 900 | 警告消息,潜在问题 |
INFO (默认) | 800 | 常规运行时信息 |
CONFIG | 700 | 配置信息 |
FINE | 500 | 普通的开发人员信息(跟踪消息) |
FINER | 400 | 详细的开发人员信息(跟踪消息) |
FINEST | 300 | 高度详细的开发人员信息(跟踪消息) |
ALL | 最小整数(Integer. MIN_VALUE) | 打开所有级别的日志记录(捕获所有内容) |
➳ 结论:级别依次从高到低,其中OFF可用来关闭日志记录,ALL启用所有消息的日志记录
★ 默认日志级别
可以翻阅源码,获取Logger实例时,初始化logger对象赋值的默认日志级别为Info
▎拓展:自定义日志级别
每个日志级别都有一个整数值,用来确定它们的严重性,除两个特殊的日志级别OFF和ALL之外。如果将日志级别设置在某一个级别上,低于该级别的都会被忽略,不会打印
你也可以定义自己的日志级别,通过继承Level的方式,譬如:
// 自定义日志级别
public class AlertLevel extends Level {
protected AlertLevel(String name, int value) {
super(name, value);
}
public static void main(String[] args){
Logger logger = Logger.getLogger("AlertLevel");
//低于INFO(800),显示不出来,因为默认日志级别是:INFO
logger.log(new AlertLevel("我是ALERT",950), "自定义 lever!");
logger.severe("severe 严重信息"); // 1000
logger.warning("warning 警示信息"); // 900
logger.info("info 一般信息"); // 800 -------
logger.config("config 配置方面的信息"); // 700
logger.fine("fine 细微的信息"); // 500
logger.finer("finer 更细微的信息"); // 400
logger.finest("finest 最细微的信息"); // 300
}
}
执行结果:
➳ 结论:由执行结果得知,logger默认的级别是INFO,比INFO更低的级别的日志将不显示。
设置日志级别
@Test
public void test04() throws IOException {
// 1 获取日志记录器对象
Logger logger = Logger.getLogger("com.test.LogDemo");
// 2 关闭系统默认配置的Handler处理器
logger.setUseParentHandlers(false);
// ----------设置日志级别---------------
// 3 创建Handler处理器类型为:ConsoleHandler控制台输出
ConsoleHandler consoleHandler = new ConsoleHandler();
// 4 创建Formatter格式化转换组件:将日志内容格式化为标准日志格式
SimpleFormatter formatter = new SimpleFormatter();
// 5 进行关联1:设置Handler处理器的格式化组件为SimpleFormatter
consoleHandler.setFormatter(formatter);
// 5.1 进行关联2:设置Logger的Handler处理器为ConsoleHandler
logger.addHandler(consoleHandler);
// 6.配置日志具体级别:logger日志记录器和Handler处理器都需要设置!!
logger.setLevel(Level.ALL);
consoleHandler.setLevel(Level.ALL);
// 3.1 创建Handler处理器类型为FileHandler文件输出
// 一个Logger可以拥有多个handler,每个handler可以有自己的日志级别
FileHandler fileHandler = new FileHandler("/Users/wpf0113/logs/jul.log");
fileHandler.setFormatter(formatter);
logger.addHandler(fileHandler);
// 打印日志信息
logger.severe("severe 严重信息");
logger.warning("warning 警示信息");
logger.info("info 一般信息");
logger.config("config 配置方面的信息");
logger.fine("fine 细微的信息");
logger.finer("finer 更细微的信息");
logger.finest("finest 最细微的信息");
}
Logger的相关组件说明,以及工作原理:
- Logger主要作用是设置日志级别和日志具体内容
- handlers处理器:logger会将日志交给关联的handlers处理,handlers决定了日志记录的位置可以是控制台、文件、网络上的其他日志服务或操作系统日志等
- formatters格式化组件:handlers底层调用了该组件,主要负责对日志数据进行转换和格式化
所以自定义日志级别,需要设置对应的handlers处理器,也就是代码中的第3步和3.1步骤,其中Formatter 对象可以不用设置,因为我们并没有设置格式化的具体方式,如果用到了再加!
执行结果:
代码中设置的日志级别是ALL,是最低级别,相当于打开所有级别的日志记录
我们设置了两个handler处理器,第一个是控制台输出,如上图结果,全部打印出来了,第二个是文件输出,找到该目录,可以查看日志记录形式
!! 注意:代码中设置的目录 /Users/wpf0113/logs 必须存在否则报错,jul.log是自动创建的
fileHandler默认的输出格式是XML格式。输出格式由java.util.logging.Formatter来控制
Handler处理器类型
输出端类型 | 作用 |
---|---|
StreamHandler | 写入OutputStream |
ConsoleHandler | 以System.err 方式将日志输出到控制台 |
FileHandler | 将信息输出到文件 |
SocketHandler | 写入到远程TCP端口 |
MemoryHandler | 写入内存 |
一个记录器可以有多个处理程序。要获取所有处理程序,我们使用以下代码:
Handler[] handlers = logger.getHandlers();
★ 自定义Handler
通过继承Handler,可定制输出媒介控制器,通常只需实现Handler中三个未定义的抽象方法
- publish:主要方法,把日志记录写入你需要的媒介。
- flush:清除缓冲区并保存数据。
- close:关闭控制器。
通过重写以上三个方法可以很容易实现一个新的输出媒介控制器。
格式化程序(Formatter)
Java SE具有两个内置的Formatter:
格式化器类型 | 作用 |
---|---|
SimpleFormatter | 将LogRecord格式化为字符串 |
XMLFormatter | 将LogRecord格式化为XML格式 |
▸ 我们可以使用以下代码来格式化处理程序
// 格式化成字符串形式
handler.setFormatter(new SimpleFormatter());
// 格式化成XML格式
handler.setFormatter(new XMLFormatter());
▎ SimpleFormatter
ConsolerHandler的默认格式。标准日志格式,输出的日志文件内容就是简单的文字信息
就是我们通常在启动一些诸如Tomcat、JBoss之类的服务器的时候经常能在控制台下看到的那种形式,就像这样:
2004-12-20 23:08:52 org.apache.coyote.http11.Http11Protocol init
信息: Initializing Coyote HTTP/1.1 on http-8080
▎ XMLFormatter
FileHandler的默认格式。以XML形式输出的日志格式,
如果为Logger添加了一个new XMLFormatter(),那么就会以XML形式输出,例如:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE log SYSTEM "logger.dtd">
<log>
<record>
<date>2022-03-22T17:32:24</date>
<millis>1647941544848</millis>
<sequence>0</sequence>
<logger>com.test.LogDemo</logger>
<level>SEVERE</level>
<class>com.test.LogDemo</class>
<method>test04</method>
<thread>1</thread>
<message>severe 严重信息</message>
</record>
★ 自定义Handler
除了XMLFormatter 与 SimpleFormatter之外,也可以自定义日志的输出格式
继承抽象类Formatter,并重新定义其format()方法即可。format()方法会传入一个LogRecord对象作为参数,可以使用它来取得一些与程序执行有关的信息。
Logger对象的父子关系
Logger的名称决定父子关系
在使用Logger的静态getLogger()方法取得Logger实例时,给getLogger()方法的名称是有意义的。
// logger的父子关系,Logger里面的命名只要包含父级的名称,就自动属于父子关系
@Test
public void test05() throws IOException {
// 1.获取日志记录器对象
Logger logger1 = Logger.getLogger("com");
Logger logger2 = Logger.getLogger("test");
Logger logger3 = Logger.getLogger("com.test.LogDemo");
System.out.println(logger3.getParent() == logger1); // true
System.out.println(logger3.getParent() == logger2); // false
System.out.println("logger1 parent:"+logger1.getParent());
System.out.println("logger2 parent:"+logger2.getParent());
System.out.println("logger3 parent:"+logger3.getParent());
}
执行结果
➳ 由上述结果可知
- 例如 logger3命名 com.test.LogDemo ,则它将继承 logger1 ,该实例的命名包含了logger1,因此默认称为父子关系
- 例如 logger1命名 com ,则它的父类是RootManager,即根Root,其name是"" ,该root是所有未设置父类的Logger实例的默认父亲类(logger2同理)
或者你也可以理解为包结构,当前LogDemo类的顶级父包为com,上述的 logger1 和 logger3的命名也是如此,存在顶级包含关系,默认为父子,如下:
▎继承特性
子类会默认从父类身上继承一些特性,例如 level日志级别、handler输出媒介控制器等
@Test
public void test06() throws IOException {
// 1.获取日志记录器对象
Logger logger1 = Logger.getLogger("com");
Logger logger2 = Logger.getLogger("com.test.LogDemo");
// 2.设置logger1 不继承父类的Handler
logger1.setUseParentHandlers(false);
// 3 设置logger1自己的Handler处理器:定义日志输出格式为普通字符串
ConsoleHandler consoleHandler = new ConsoleHandler();
logger1.addHandler(consoleHandler);
// 4.配置logger1的具体日志级别:logger日志记录器和Handler处理器都需要设置!!
logger1.setLevel(Level.WARNING);
consoleHandler.setLevel(Level.WARNING);
// 5.打印logger2的日志:观察logger2继承状态(level日志级别、Handler输出媒介)
logger2.severe("severe 严重信息");
logger2.warning("warning 警示信息");
logger2.info("info 一般信息");
logger2.config("config 配置方面的信息");
logger2.fine("fine 细微的信息");
logger2.finer("finer 更细微的信息");
logger2.finest("finest 最细微的信息");
}
执行结果:
➳ 结论:默认日志级别是Info ,而logger2继承了logger1,我们设置了父类logger1的日志级别为warning,因此最终logger2的打印,只要低于warning的日志将不会打印出来。