Java日志框架+日志门面详解,史上最全!

一、日志介绍

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
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值