一、日志介绍
1、 常用的日志框架历史
Java常用日志框架历史:Log4j(reload4j) --> JUL --> JCL --> SLF4J --> Logback --> Log4j2
- 1996年早期,欧洲安全电子市场项目组决定编写它自己的程序跟踪API(Tracing API)。经过不断的完善,这个API终于成为一个十分受欢迎的Java日志软件包,即Log4j。后来Log4j成为Apache基金会项目中的一员。
- 期间Log4j近乎成了Java社区的日志标准。据说Apache基金会还曾经建议sun引入Log4j到java的标准库中,但Sun拒绝了。
- 2002年Java1.4发布,Sun推出了自己的日志库JUL(Java Util Logging),其实现基本模仿了Log4j的实现。在JUL出来以前,log4j就已经成为一项成熟的技术,使得log4j在选择上占据了一定的优势。
- 接着,Apache推出了Jakarta Commons Logging,JCL只是定义了一套日志接口(其内部也提供一个Simple Log的简单实现),支持运行时动态加载日志组件的实现,也就是说,在你应用代码里,只需调用Commons Logging的接口,底层实现可以是log4j,也可以是Java Util Logging。
- 后来(2006年),Ceki Gülcü不适应Apache的工作方式,离开了Apache。然后先后创建了slf4j(日志门面接口,类似于Commons Logging)和Logback(Slf4j的实现)两个项目,并回瑞典创建了QOS公司,QOS官网上是这样描述Logback的:The Generic,Reliable Fast&Flexible Logging Framework(一个通用,可靠,快速且灵活的日志框架)。
- 现今,Java日志领域被划分为两大阵营:Commons Logging阵营和SLF4J阵营。Commons Logging在Apache大树的笼罩下,有很大的用户基数。但有证据表明,形式正在发生变化。2013年底有人分析了GitHub上30000个项目,统计出了最流行的100个Libraries,可以看出slf4j的发展趋势更好。
- Apache眼看有被Logback反超的势头,于2012-07重写了log4j 1.x,成立了新的项目Log4j 2。Log4j 2具有logback的所有特性。
2、 日志门面和日志框架的区别
日志框架技术:JUL、Logback、Log4j、Log4j2
日志门面技术:JCL、SLF4j
日志门面(Logging Facade)是一种抽象层,用于将应用程序代码与底层的日志系统分离开来,使得应用程序可以在不修改代码的情况下切换不同的日志框架。通常情况下,应用程序都会使用某个日志门面来记录日志,例如 SLF4J、Log4j2 等。
日志框架(Logging Framework)则是具体的日志实现,用于将应用程序中的日志记录到指定的位置,例如文件、数据库、控制台等。日志框架通常包含了日志门面的实现,并且提供了更加丰富的功能和配置选项。例如,Log4j2 可以作为日志门面,同时也是一个完整的日志框架,提供了多种输出方式、日志级别、过滤器等功能。 因此,日志门面和日志框架的区别在于日志门面是一种抽象,用于提供统一的日志接口,而日志框架是一种具体的实现,用于将日志记录到指定的位置。通常情况下,开发人员会使用某个日志门面,同时选择一个合适的日志框架来实现具体的日志记录。
3、三者关系
日志框架分为三大部分,包括日志门面、日志适配器、日志库。利用门面设计模式进行解耦,使日志使用变得更加简单,如下图:
二、JUL 日志框架
1、简单介绍
JUL(Java Util Logging),它是 Java 原生的日志框架,位于 java.util.logging.Logger 包。使用时不需要另外引入第三方类库,相对于其他日志框架来说其特点是使用方便,能够在小型应用中灵活应用。
一个完整的日志记录过程如下:
用户使用Logger来进行日志记录的行为,Logger可以同时持有若干个Handler,日志输出操作是由Handler完成的;在Handler输出日志前,会经过Filter的过滤,判断哪些日志级别放行、哪些拦截,Handler会将日志内容输出到指定位置(日志文件、控制台等);Handler在输出日志时会使用Layout对日志内容进行排版,之后再输出到指定的位置。
2、入门案例
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
package com.jul.test;
import org.junit.Test;
import java.util.logging.Level;
import java.util.logging.Logger;
// 日志入口程序:java.util.logging.Logger
public class JULTest {
@Test
public void test01() {
// 引入当前类的全路径字符串获取日志记录器
Logger logger = Logger.getLogger("com.jul.JulTest");
// 对于日志的输出有两种方式
// 1、直接调用日志级别的相关方法,方法中传递日志输出信息
logger.info("info信息1");
// 2、调用log方法,通过Level类型定义日志级别参数,以及搭配日志输出信息的参数
logger.log(Level.INFO, "info信息2");
System.out.println("--------");
// 打印日志信息并传参
// 输出学生信息:姓名、年龄
String name = "张三";
int age = 23;
logger.log(Level.INFO, "方式一:学生姓名:" + name + ",学生年龄:" + age);
// 以上操作中,对于输出消息用字符串拼接弊端很多。拼接麻烦、程序效率低、可读性不强、维护成本高
// 应该使用动态生成数据的方式生产日志,就是占位符的方式来进行操作
logger.log(Level.INFO, "方式二:学生姓名:{0},学生年龄:{1}", new Object[]{name, age});
}
}
六月 14, 2021 10:14:37 下午 com.jul.JulTest test01
信息: info信息1
六月 14, 2021 10:14:37 下午 com.jul.JulTest test01
--------
信息: info信息2
六月 14, 2021 10:14:37 下午 com.jul.JulTest test01
信息: 方式一:学生姓名:张三,学生年龄:23
六月 14, 2021 10:14:37 下午 com.jul.JulTest test01
信息: 方式二:学生姓名:张三,学生年龄:23
3、日志级别
4、自定义日志级别
@Test
public void test03() {
// 获取日志记录器
Logger logger = Logger.getLogger("com.jul.JulTest");
// 将默认的日志打印方式关闭
// 参数设置为 false,打印日志的方式就不会按照父 logger 默认的方式去进行操作
logger.setUseParentHandlers(false);
// 控制台日志处理器
ConsoleHandler handler = new ConsoleHandler();
// 创建日志格式化组件对象
SimpleFormatter formatter = new SimpleFormatter();
// 在处理器中设置日志输出格式
handler.setFormatter(formatter);
// 在记录器中添加处理器
logger.addHandler(handler);
// 设置日志的打印级别
// 此处必须将日志记录器和处理器的级别进行统一的设置,才会达到日志显示相应级别的效果
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:详细信息(多)");
}
六月 14, 2021 10:19:26 下午 com.jul.JulTest test03
严重: severe:错误信息
六月 14, 2021 10:19:26 下午 com.jul.JulTest test03
警告: warning:警告信息
六月 14, 2021 10:19:26 下午 com.jul.JulTest test03
信息: info:默认信息
六月 14, 2021 10:19:26 下午 com.jul.JulTest test03
配置: config:配置信息
六月 14, 2021 10:19:26 下午 com.jul.JulTest test03
详细: fine:详细信息(少)
六月 14, 2021 10:19:26 下午 com.jul.JulTest test03
较详细: finer:详细信息(中)
六月 14, 2021 10:19:26 下午 com.jul.JulTest test03
非常详细: finest:详细信息(多)
5、日志打印到文件
@Test
public void test04() throws IOException {
// 获取日志记录器
Logger logger = Logger.getLogger("com.jul.JulTest");
// 关闭父记录器打印方式
logger.setUseParentHandlers(false);
// 文件日志处理器
FileHandler handler = new FileHandler("src\\jul.log"); // 指定输出的日志文件
SimpleFormatter formatter = new SimpleFormatter();
handler.setFormatter(formatter);
logger.addHandler(handler);
// 统一设置日志的打印级别
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:详细信息(多)");
}
运行结果: 此时控制台中并没有输出日志信息,打开 jul.log 文件,日志信息打印到文件中了。
六月 14, 2021 10:24:19 下午 com.jul.JulTest test04
严重: severe:错误信息
六月 14, 2021 10:24:19 下午 com.jul.JulTest test04
警告: warning:警告信息
六月 14, 2021 10:24:19 下午 com.jul.JulTest test04
信息: info:默认信息
六月 14, 2021 10:24:19 下午 com.jul.JulTest test04
配置: config:配置信息
六月 14, 2021 10:24:19 下午 com.jul.JulTest test04
详细: fine:详细信息(少)
六月 14, 2021 10:24:19 下午 com.jul.JulTest test04
较详细: finer:详细信息(中)
六月 14, 2021 10:24:19 下午 com.jul.JulTest test04
非常详细: finest:详细信息(多)
6、添加多个处理器
用户使用 Logger 来进行日志的记录,使用 Handler 来进行日志的输出,Logger 可以持有多个处理器 Handler,
添加了哪些 Handler 对象,就相当于根据所添加的 Handler 将日志输出到指定的位置上,例如控制台、文件中…
@Test
public void test05() throws IOException {
Logger logger = Logger.getLogger("com.jul.JulTest");
logger.setUseParentHandlers(false);
SimpleFormatter formatter = new SimpleFormatter();
// 文件日志处理器
FileHandler handler1 = new FileHandler("src\\Jul2.log"); // 指定输出的日志文件
handler1.setFormatter(formatter);
logger.addHandler(handler1); // 记录器中添加了一个文件日志处理器
// 控制台日志处理器
ConsoleHandler handler2 = new ConsoleHandler();
handler2.setFormatter(formatter);
logger.addHandler(handler2); // 记录器中又添加了一个控制台日志处理器
// 统一设置日志的打印级别
logger.setLevel(Level.ALL);
handler1.setLevel(Level.ALL);
handler2.setLevel(Level.ALL);
// 输出日志信息
logger.severe("severe:错误信息");
logger.warning("warning:警告信息");
logger.info("info:默认信息");
logger.config("config:配置信息");
logger.fine("fine:详细信息(少)");
logger.finer("finer:详细信息(中)");
logger.finest("finest:详细信息(多)");
}
运行结果:
六月 14, 2021 10:38:28 下午 com.jul.JulTest test05
严重: severe:错误信息
六月 14, 2021 10:38:28 下午 com.jul.JulTest test05
警告: warning:警告信息
六月 14, 2021 10:38:28 下午 com.jul.JulTest test05
信息: info:默认信息
六月 14, 2021 10:38:28 下午 com.jul.JulTest test05
配置: config:配置信息
六月 14, 2021 10:38:28 下午 com.jul.JulTest test05
详细: fine:详细信息(少)
六月 14, 2021 10:38:28 下午 com.jul.JulTest test05
较详细: finer:详细信息(中)
六月 14, 2021 10:38:28 下午 com.jul.JulTest test05
非常详细: finest:详细信息(多)
Jul2.log 文件中也打印了日志信息:
六月 14, 2021 10:38:28 下午 com.jul.JulTest test05
严重: severe:错误信息
六月 14, 2021 10:38:28 下午 com.jul.JulTest test05
警告: warning:警告信息
六月 14, 2021 10:38:28 下午 com.jul.JulTest test05
信息: info:默认信息
六月 14, 2021 10:38:28 下午 com.jul.JulTest test05
配置: config:配置信息
六月 14, 2021 10:38:28 下午 com.jul.JulTest test05
详细: fine:详细信息(少)
六月 14, 2021 10:38:28 下午 com.jul.JulTest test05
较详细: finer:详细信息(中)
六月 14, 2021 10:38:28 下午 com.jul.JulTest test05
非常详细: finest:详细信息(多)
7、记录器父子关系
JUL 中 Logger 记录器之间是存在 “父子” 关系的,这种父子关系不是我们普遍认为的类之间的继承关系,关系是通过树状结构存储的。
JUL 在初始化时会创建一个顶层 RootLogger 作为所有 Logger 的父 Logger,RootLogger 是 LogManager 的内部类,默认的名称为空串。
以上的 RootLogger 对象作为树状结构的根节点存在的,将来自定义的父子关系通过路径来进行关联,父子关系同时也是节点之间的挂载关系。
代码示例:
@Test
public void test06() {
// 创建两个 logger 对象,可以认为 logger1 是 logger2 的父亲
// RootLogger 是所有 logger 对象的顶层 logger,名称默认是一个空的字符串
Logger logger1 = Logger.getLogger("com.jul");
Logger logger2 = Logger.getLogger("com.jul.JulTest");
System.out.println(logger2.getParent() == logger1);
System.out.println("----");
System.out.println("logger1名称:" + logger1.getName() +
",\n父Logger名称:" + logger1.getParent().getName() +
",\n父Logger引用:" + logger1.getParent());
System.out.println("----");
System.out.println("logger2名称:" + logger2.getName() +
",\n父Logger名称:" + logger2.getParent().getName() +
",\n父Logger引用:" + lo