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引用:" + logger2.getParent());
    System.out.println("----");

    // 父亲所做的设置,也能够同时作用于儿子
    // 对 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);

    //儿子做打印
    logger2.severe("severe:错误信息");
    logger2.warning("warning:警告信息");
    logger2.info("info:默认信息");
    logger2.config("config:配置信息");
    logger2.fine("fine:详细信息(少)");
    logger2.finer("finer:详细信息(中)");
    logger2.finest("finest:详细信息(多)");
}

 运行结果:

true
----
logger1名称:com.jul,
父Logger名称:,
父Logger引用:java.util.logging.LogManager$RootLogger@3b764bce
----
logger2名称:com.jul.JulTest,
父Logger名称:com.jul,
父Logger引用:java.util.logging.Logger@759ebb3d
----
六月 14, 2021 10:41:40 下午 com.jul.JulTest test06
严重: severe:错误信息
六月 14, 2021 10:41:40 下午 com.jul.JulTest test06
警告: warning:警告信息
六月 14, 2021 10:41:40 下午 com.jul.JulTest test06
信息: info:默认信息
六月 14, 2021 10:41:40 下午 com.jul.JulTest test06
配置: config:配置信息
六月 14, 2021 10:41:40 下午 com.jul.JulTest test06
详细: fine:详细信息(少)
六月 14, 2021 10:41:40 下午 com.jul.JulTest test06
较详细: finer:详细信息(中)
六月 14, 2021 10:41:40 下午 com.jul.JulTest test06
非常详细: finest:详细信息(多)

8、日志的配置文件

以上所有配置的相关操作,都是以 java 硬编码的形式进行的,我们可以使用配置文件,若没有指定自定义日志配置文件,则使用系统默认的日志配置文件。

默认配置文件位置: jdk 安装目录下 \ jre \ lib \ logging.properties 文件 。

############################################################
#  	默认日志记录配置文件
#
# 您可以通过使用java.util.logging.config.file系统属性指定文件名来使用不同的文件
# 例如 java -Djava.util.logging.config.file=myfile
############################################################

############################################################
#  	全局性质
############################################################

# RootLogger使用的处理器,在获取RootLogger对象时进行的设置
# 可在当前处理器类后,通过指定的英文逗号分隔,添加多个日志处理器
# 这些处理程序将在VM启动期间安装,请注意:这些类必须位于系统类路径上
# 默认情况下,只配置控制台处理程序,默认打印INFO和高于INFO级别消息
handlers = java.util.logging.ConsoleHandler

# 要添加文件处理程序,请使用以下行(多个日志处理器)
#handlers= java.util.logging.FileHandler, java.util.logging.ConsoleHandler

# RootLogger 默认的全局日志记录级别
# 对于这种全局层面的任何特定配置,可以通过配置特定的水平来覆盖
# 如果不手动配置其它的日志级别,则默认输出下述配置的级别以及更高的级别
.level = INFO

############################################################
# 处理器指定属性,描述处理程序的特定配置信息
############################################################

# 文件处理器属性设置
# 默认输出的日志文件路径,位于用户的主目录中
# %h:当前用户系统的默认根路径,C:\用户\用户名\java0.log
# %u:指向默认输出的日志文件数量count,count=1,则:java0.log;count=2,则:java0.log,java1.log...
java.util.logging.FileHandler.pattern = %h/java%u.log
# 默认输出的日志文件大小(单位字节)
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
# 默认输出的日志格式(Simple)
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter

# 示例以自定义简单的格式化器输出格式,以打印这样的单行日志消息:
#     <level>: <log message> [<date/time>]
#
# java.util.logging.SimpleFormatter.format=%4$s: %5$s [%1$tc]%n

############################################################
# 配置特定属性,为每个记录器提供额外的控制
############################################################

# 例如:将日志级别设定到具体的某个包下
com.xyz.foo.level = SEVERE

9、自定义配置文件

创建配置: logging.properties 文件

############################################################
# 默认日志记录配置文件
############################################################
# 全局性质
############################################################

# 默认配置控制台处理程序,默认打印INFO和高于INFO级别信息
handlers=java.util.logging.ConsoleHandler
# 如果不手动配置其它的日志级别,则默认输出下述配置的级别以及更高的级别
.level = ALL

############################################################
# 处理器指定属性,描述处理程序的特定配置信息
############################################################

# 文件处理器属性设置
# 默认输出的日志文件路径,位于用户的主目录中
java.util.logging.FileHandler.pattern = %h/java%u.log
# 默认输出的日志文件大小(单位字节)
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 = ALL
# 默认输出的日志格式(Simple)
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter

############################################################
# 配置特定属性,为每个记录器提供额外的控制
############################################################
# 例如:将日志级别设定到具体的某个包下
com.xyz.foo.level = SEVERE

 代码示例:

// 自定义配置文件
@Test
public void test07() throws IOException {
    // 读取自定义日志配置文件
    InputStream input = new FileInputStream("src/logging.properties");
    // 获取日志管理器
    LogManager logManager = LogManager.getLogManager();
    // 日志管理器读取自定义配置文件
    logManager.readConfiguration(input);
    // 日志记录器
    Logger logger = Logger.getLogger("com.jul.JulTest");
    // 输出日志信息
    logger.severe("severe:错误信息");
    logger.warning("warning:警告信息");
    logger.info("info:默认信息");
    logger.config("config:配置信息");
    logger.fine("fine:详细信息(少)");
    logger.finer("finer:详细信息(中)");
    logger.finest("finest:详细信息(多)");
}

运行结果: 此时控制台输出的日志使用的是我们自定义的日志配置文件,打印了所有级别日志信息。

六月 14, 2021 11:00:47 下午 com.jul.JulTest test07
严重: severe:错误信息
六月 14, 2021 11:00:47 下午 com.jul.JulTest test07
警告: warning:警告信息
六月 14, 2021 11:00:47 下午 com.jul.JulTest test07
信息: info:默认信息	
六月 14, 2021 11:00:47 下午 com.jul.JulTest test07
配置: config:配置信息
六月 14, 2021 11:00:47 下午 com.jul.JulTest test07
详细: fine:详细信息(少)
六月 14, 2021 11:00:47 下午 com.jul.JulTest test07
较详细: finer:详细信息(中)
六月 14, 2021 11:00:47 下午 com.jul.JulTest test07
非常详细: finest:详细信息(多)

添加配置: 在 11 行处添加自定义文件日志处理器的配置信息。

# 自定义文件日志处理器
com.jul.handlers = java.util.logging.FileHandler
# 自定义输出的日志级别
com.jul.level = WARNING
# 屏蔽父记录器打印方式
com.jul.useParentHandlers = false

再次运行: 在 C:\用户\用户名 目录下会有一个 java0.log 文件,打开文件发现日志格式为 XML 格式,这是因为 java.util.logging.FileHandler.formatter 指定的格式为 XMLFormatter 。

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE log SYSTEM "logger.dtd">
<log>
<record>
  <date>2021-06-14T23:24:06</date>
  <millis>1623684246017</millis>
  <sequence>0</sequence>
  <logger>com.jul.JulTest</logger>
  <level>SEVERE</level>
  <class>com.jul.JulTest</class>
  <method>test07</method>
  <thread>1</thread>
  <message>severe:错误信息</message>
</record>
<record>
  <date>2021-06-14T23:24:06</date>
  <millis>1623684246022</millis>
  <sequence>1</sequence>
  <logger>com.jul.JulTest</logger>
  <level>WARNING</level>
  <class>com.jul.JulTest</class>
  <method>test07</method>
  <thread>1</thread>
  <message>warning:警告信息</message>
</record>
</log>

 修改配置:

# 指定输出日志内容的日志文件
java.util.logging.FileHandler.pattern = src\\jul3.log
# 将XML格式更改为Simple格式
java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter

再次运行: 查看 jul3.log 文件中的内容。

六月 14, 2021 11:28:58 下午 com.jul.JulTest test07
严重: severe:错误信息
六月 14, 2021 11:28:58 下午 com.jul.JulTest test07
警告: warning:警告信息

10、追加的日志信息

test07() 方法运行多次,会发现每次输出的日志内容会将上次的内容覆盖掉,这明显是不太好。

添加配置:

# 默认输出的日志内容会覆盖上次输出的内容, 设为true改为追加
java.util.logging.FileHandler.append=true

多次运行 test07() 方法: 发现 jul3.log 文件中内容并没有被覆盖,而是一直往后追加。

六月 14, 2021 11:47:17 下午 com.jul.JulTest test07
严重: severe:错误信息
六月 14, 2021 11:47:17 下午 com.jul.JulTest test07
警告: warning:警告信息
六月 14, 2021 11:47:22 下午 com.jul.JulTest test07
严重: severe:错误信息
六月 14, 2021 11:47:22 下午 com.jul.JulTest test07
警告: warning:警告信息
六月 14, 2021 11:47:33 下午 com.jul.JulTest test07
严重: severe:错误信息
六月 14, 2021 11:47:33 下午 com.jul.JulTest test07
警告: warning:警告信息

三、Log4j 日志框架

1、简单介绍

Log4j(Log for java)是 Apache 的一个开源项目,通过使用 Log4j,可以控制日志信息输送的目的地是控制台或文件等,也可以控制每一条日志的输出格式。通过定义每一条日志信息的级别,能够更加细致地控制日志的生成过程。我们使用log4j技术,主要使用的是其配置文件

2、三大组件介绍

Log4j主要由 Loggers (日志记录器)、Appenders(日志输出器)和 Layout(日志格式化器)组成。

1、 Loggers

Loggers(记录器)控制日志的输出级别,以及引用 Appenders(输出器)。

Log4j输出日志的规则是:只输出级别不低于设定级别的日志信息,假设Loggers级别设定为INFO,则INFO、WARN、ERROR级别的日志信息都会输出,而级别比INFO低的DEBUG则不会输出。

2、 Appenders

Appender(输出器) 通常只负责将日志信息写入目标目的地。将格式化日志信息的责任委托给 Layout(格式器)。定义一个名字以便被 Loggers(记录器)引用。
记录日志以及定义日志的级别仅仅是Log4j的基本功能,Log4j日志系统还提供许多强大的功能,比如允许把日志输出到不同的地方,如控制台(Console)、文件(Files)等,可以根据天数或者文件大小产生新的文件,可以以流的形式发送到其它地方等等。

常用Appenders:

  1. ConsoleAppender :将日志输出到控制台
  2. FileAppender :将日志输出到文件中
  3. DailyRollingFileAppender :将日志输出到一个日志文件,并且每天输出到一个新的文件
  4. RollingFileAppender :将日志信息输出到一个日志文件,并且指定文件的尺寸,当文件大小达到指定尺寸时,会自动把文件改名,同时产生一个新的文件
  5. JDBCAppender :把日志信息保存到数据库中

3、Layouts

Layout(格式器)接收 Appender(输出器)的日志信息将其格式化为满足任何消费日志事件需求的样式。

有时用户希望根据自己的喜好格式化自己的日志输出,Log4j可以在Appenders的后面附加Layouts来完成这个功能。Layouts提供四种日志输出样式,如根据HTML样式、自由指定样式、包含日志级别与信息的样式和包含日志时间、线程、类别等信息的样式。

常用Layouts:

  1. HTMLLayout :格式化日志输出为HTML表格形式
  2. SimpleLayout :简单的日志输出格式化,打印的日志格式如默认INFO级别的消息
  3. PatternLayout :最强大的格式化组件,可以根据自定义格式输出日志,如果没有指定转换格式,就是用默认的转换格式

PatternLayout中的格式化规则:

log4j采用类似C语言的printf函数的打印格式格式化日志信息,具体的占位符及其含义如下:

%m 输出代码中指定的日志信息

%p 输出优先级,及DEBUG、INFO等

%n 换行符(Windows平台的换行符为"\n",Unix平台为"\n")

%r 输出自应用启动到输出该 log 信息耗费的毫秒数

%c 输出打印语句所属的类的全名

%t 输出产生该日志的线程全名

%d 输出服务器当前时间,默认为ISO8601,也可以指定格式,如:%d{yyyy年MM月dd日 HH:mm:ss}

%l 输出日志时间发生的位置,包括类名、线程、及在代码中的行数。如:Test.main(Test.java:10)

%F 输出日志消息产生时所在的文件名称

%L 输出代码中的行号

%% 输出一个"%"字符

可以在%与字符之间加上修饰符来控制最小宽度、最大宽度和文本的对其方式。如:

%5c 输出category名称,最小宽度是5,category<5,默认的情况下右对齐

%-5c 输出category名称,最小宽度是5,category<5,"-"号指定左对齐,会有空格

%.5c 输出category名称,最大宽度是5,category>5,就会将左边多出的字符截掉,<5不会有空格

%20.30c category名称<20补空格,并且右对齐,>30字符,就从左边交远销出的字符截掉

3、入门案例

导入依赖:

<dependencies>
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
</dependencies>

代码示例: 

import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Logger;
import org.junit.Test;

public class Log4jTest {
    @Test
    public void test01() {
        BasicConfigurator.configure(); //加载初始化配置,此时没有配置文件
        Logger logger = Logger.getLogger(Log4jTest.class);
        logger.trace("trace信息");
        logger.debug("debug信息"); //默认级别
        logger.info("info信息");
        logger.warn("warn信息");
        logger.error("error信息");
        logger.fatal("fatal信息");
    }
}

 运行结果:

0 [main] DEBUG com.Log4jTest  - debug信息
1 [main] INFO com.Log4jTest  - info信息
1 [main] WARN com.Log4jTest  - warn信息
1 [main] ERROR com.Log4jTest  - error信息
1 [main] FATAL com.Log4jTest  - fatal信息

4、关于配置文件解析

源码解析:

  • 观察源码BasicConfigurator.configure();
public class BasicConfigurator {
    protected BasicConfigurator() {
    }

    public static void configure() {
        //创建了根节点的对象
        Logger root = Logger.getRootLogger();
        //根节点添加了ConsoleAppender对象,表示默认打印到控制台并自定义格式化输出
        root.addAppender(new ConsoleAppender(new PatternLayout("%r [%t] %p %c %x - %m%n")));
    }

    public static void configure(Appender appender) {
        Logger root = Logger.getRootLogger();
        root.addAppender(appender);
    }

    public static void resetConfiguration() {
        LogManager.resetConfiguration();
    }
}
  •  这次,我们不使用BasicConfigurator.configure();使用配置文件来实现功能,通过第一点的分析,我们需要提供logger、appender、layout三个组件信息,来代替BasicConfigurator.configure();

        a. 分析Logger logger = Logger.getLogger(Log4jTest.class)

        b. 进到LogManager(日志管理器),看到很多常量信息 

         c. 看到static,问题:log4j.properties的加载时机?立马去找静态代码块     继续观察LogManager,找到其中的静态代码块static,并找到url = Loader.getResource("log4j.properties"); 

        d.  拿到URL之后,传入了这里OptionConverter.selectAndConfigure(url, configuratorClassName, getLoggerRepository());

        e. 进到selectAndConfigure,找到了configurator = new PropertyConfigurator();

        f. 进到PropertyConfigurator

        g. 搜得死内,new了对应的配置文件实例 new PropertyConfigurator(),通过stream流的方式把配置文件读进来,load装载进去;

实际操作:

通过配置文件实现功能,需要提供logger、appender、layout三组信息,来代替BasicConfigurator.configure();

配置说明:

log4j.rootLogger=日志级别,appendName1,appendName2,appendName3,...
log4j.appender.appendName1(自定义输出控制器名称)=输出方式
log4j.appender.appendName1(自定义输出控制器名称).layout=输出格式

 修改配置:

log4j.rootLogger=INFO,console

########## 控制台输出器 ##########
#配置控制台输出器
log4j.appender.console=org.apache.log4j.ConsoleAppender
#配置自定义格式器
log4j.appender.console.layout=org.apache.log4j.PatternLayout
#配置自定义转换模式
log4j.appender.console.layout.conversionPattern=[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5p] [%t] [%-4rms] [%c] %M %L %m%n

 代码示例:

@Test
public void test02() {
    Logger logger = Logger.getLogger(Log4jTest.class);
    logger.trace("trace信息");
    logger.debug("debug信息");
    logger.info("info信息");
    logger.warn("warn信息");
    logger.error("error信息");
    logger.fatal("fatal信息");
}

运行结果: 

[2022-06-30 21:19:45.421] [INFO ] [main] [0   ms] [com.log4j.Log4jTest] test02 26 info信息
[2022-06-30 21:19:45.406] [WARN ] [main] [985 ms] [com.log4j.Log4jTest] test02 27 warn信息
[2022-06-30 21:19:45.410] [ERROR] [main] [989 ms] [com.log4j.Log4jTest] test02 28 error信息
[2022-06-30 21:19:45.416] [FATAL] [main] [995 ms] [com.log4j.Log4jTest] test02 29 fatal信息

5、将日志输出到文件(到这了)

保存到一个文件中,相关配置:

#配置日志级别,输出控制器
log4j.rootLogger=INFO,file

########## 文件输出器 ##########
#配置文件输出器
log4j.appender.file=org.apache.log4j.FileAppender
#配置自定义格式器
log4j.appender.file.layout=org.apache.log4j.PatternLayout
#配置自定义转换模式
log4j.appender.file.layout.conversionPattern=[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5p] [%t] [%-4rms] [%c] %M %L %m%n
#配置文件路径及名称
log4j.appender.file.file=../logDir/file.log
#配置字符编码
log4j.appender.file.encoding=UTF-8

按照文件大小拆分,相关配置:

#配置日志级别,输出控制器
log4j.rootLogger=INFO,rollingFile

########## 拆分文件输出器 ##########
#配置拆分文件输出器
log4j.appender.rollingFile=org.apache.log4j.RollingFileAppender
#配置自定义格式器
log4j.appender.rollingFile.layout=org.apache.log4j.PatternLayout
#配置自定义转换模式
log4j.appender.rollingFile.layout.conversionPattern=[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5p] [%t] [%-4rms] [%c] %M %L %m%n
#配置文件路径及名称
log4j.appender.rollingFile.file=../logDir/rollingFile.log
#配置字符编码
log4j.appender.rollingFile.encoding=UTF-8
#配置拆分文件大小
log4j.appender.rollingFile.maxFileSize=1KB
#配置拆分文件数量
log4j.appender.rollingFile.maxBackupIndex=2

#若文件大小超过1KB,则生成另外一个文件,数量最多2个。
#若2个文件不够用,则按照时间来进行覆盖,保留新的覆盖旧的。

按照时间进行拆分,相关配置:

#配置日志级别,输出控制器
log4j.rootLogger=INFO,dailyRollingFile

########## 按天拆分文件输出器 ##########
#配置每天拆分文件输出器
log4j.appender.dailyRollingFile=org.apache.log4j.DailyRollingFileAppender
#配置自定义格式器
log4j.appender.dailyRollingFile.layout=org.apache.log4j.PatternLayout
#配置自定义转换模式
log4j.appender.dailyRollingFile.layout.conversionPattern=[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5p] [%t] [%-4rms] [%c] %M %L %m%n
#配置文件路径及名称
log4j.appender.dailyRollingFile.file=../logDir/dailyRollingFile.log
#配置字符编码
log4j.appender.dailyRollingFile.encoding=UTF-8
#配置日期格式
log4j.appender.dailyRollingFile.datePattern='.'yyyy-MM-dd

保存日志到数据库,相关配置:

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.26</version>
</dependency>
create table log4j
(
    id         int    (11)    not null auto_increment comment '自增ID',
    name       varchar(30)    default null comment '项目名称',
    createTime varchar(30)    default null comment '创建时间',
    level      varchar(10)    default null comment '日志级别',
    thread     varchar(30)    default null comment '线程名称',
    className  varchar(255)   default null comment '全限定名',
    method     varchar(50)    default null comment '方法名称',
    lineNumber int    (5)     default null comment '代码行号',
    message    varchar(10000) default null comment '日志信息',
    primary key (id)
)
#配置日志级别,输出控制器
log4j.rootLogger=INFO,db

########## 数据库输出器 ##########
#配置数据库输出器
log4j.appender.db=org.apache.log4j.jdbc.JDBCAppender
#配置自定义格式器
log4j.appender.db.layout=org.apache.log4j.PatternLayout
#配置自定义转换模式
log4j.appender.db.layout.conversionPattern=[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5p] [%t] [%-4rms] [%c] %M %L %m%n
#配置数据库
log4j.appender.db.URL=jdbc:mysql://localhost:3306/test
log4j.appender.db.Driver=com.mysql.cj.jdbc.Driver
log4j.appender.db.User=root
log4j.appender.db.Password=root
log4j.appender.db.Sql=insert into log4j (name, createTime, level, thread, className, method, lineNumber, message) \
  values ('log', '%d{yyyy-MM-dd HH:mm:ss.SSS}', '%p', '%t', '%c', '%M', '%L', '%m');

四、JCL 日志门面框架

1、简单介绍

JCL(Jakarta Commons Logging)是Apache提供的一个通用日志API(日志门面)。用户可以自由选择第三方的日志组件作为具体实现,像log4j或者JDK自带日志框架(JUL),选择Log4j或JUL的第三方日志框架后,common-logging会通过动态查找的机制,在程序运行时自动找出真正使用的日志库。当然common-logging内部有一个Simple logger 的简单实现,但是功能很弱。所以使用common-logging,通常都是配合着log4j以及其它日志框架来使用。使用它的好处就是我们代码依赖common-logging的API而非log4j的API,避免了和具体的日志API直接耦合,在有必要时,可以更改日志实现的第三方库。

JCL有两个基本抽象类:Log(基本记录器)和LogFactory(负责创建Log实例)

2、入门案例

<!--JCL门面坐标依赖-->
    <dependency>
      <groupId>commons-logging</groupId>
      <artifactId>commons-logging</artifactId>
      <version>1.2</version>
    </dependency>


// 入门代码
public class JCLClient {
    @Test
    public void demoA() {
        //创建日志对象;LogFactory是一个抽象类,LogFactoryImpl才是具体实现
        Log logger = LogFactory.getLog(JCLClient.class);
        //日志记录输出
        logger.fatal("严重错误,一般造成系统崩溃并终止运行");
        logger.error("错误信息,不会影响系统运行");
        logger.warn("警告信息,可能会发生问题");
        logger.info("运行信息,数据连接,网络连接,IO操作等"); // 默认级别INFO(和JUL默认级别一样)
        logger.debug("调试信息,一般在开发中使用,记录程序变量传递信息等等");
        logger.trace("追踪信息,记录程序所有的流程信息");
    }
}
  

//打印
二月 09, 2022 9:49:11 下午 cn.xw.JCLClient demoA  
严重: 严重错误,一般造成系统崩溃并终止运行  
二月 09, 2022 9:49:11 下午 cn.xw.JCLClient demoA  
严重: 错误信息,不会影响系统运行  
二月 09, 2022 9:49:11 下午 cn.xw.JCLClient demoA  
警告: 警告信息,可能会发生问题  
二月 09, 2022 9:49:11 下午 cn.xw.JCLClient demoA  
信息: 运行信息,数据连接,网络连接,IO操作等
   

通过上面的入门案例我们可以发现,打印出来的日志和JUL的日志样式是一样的,那么我猜测它底层的日志实现使用的是JUL,因为我并未导入Log4j包,那导入了Log4j坐标是什么样的呢?测试一下看看

<!--Log4j日志框架坐标-->
    <dependency>
      <groupId>log4j</groupId>
      <artifactId>log4j</artifactId>
      <version>1.2.17</version>
    </dependency>

//导入后我们编写的代码不用改变,直接可以运行,日志打印出
log4j:WARN No appenders could be found for logger (cn.xw.JCLClient).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
这就证明了,导入Log4j日志框架坐标后JCL会动态查找到Log4j并切换Log4j作为底层日志打印的第三方框架

这里在resources目录下编写一个log4j.properties配置文件即可总结:我们为什么要使用日志门面:
    ①:面向接口开发,不再依赖具体的实现类。减少代码的耦合
    ②:项目通过导入不同的日志实现类,可以灵活的切换日志框架
    ③:统一API,方便开发者学习和使用
    ④:统一配置便于项目日志的管理

五、SLF4J 日志门面框架(重点)

SLF4J即简单日志门面(Simple Logging Facade for Java),它主要是为了给Java日志访问提供一套标准、规范的API框架,其主要意义在于提供接口,具体的实现可以交由其它日志框架,例如log4j和logback等;当然slf4j自己也提供了功能较为简单的实现,但是一般很少用到;对于一般的Java项目而言,日志框架会选择slf4j-api作为门面,配上具体的实现框架(log4j、logback等),中间使用桥接器完成桥接。所以我们可以得出SLF4J最重要的两个功能就是对于日志框架的绑定以及日志框架的桥接。SLF4J和JCL这两个门面框架差不多。

核心功能:日志框架的绑定、日志框架的桥接

1、SLF4J入门案例及说明

<!--SLF4J基本的API坐标依赖;注单独导入这个是没有日志效果-->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.35</version>
</dependency>
    
<!--基本的simple,就是刚才说的SLF4J提供的基本日志实现,-->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-simple</artifactId>
    <version>1.7.35</version>
</dependency>
@Test
    public void demoA() {
        //通过Logger工厂对象动态获取我们具体导入的日志实现框架(Log4j、Logback、JUL、slf4j-simple)
        Logger logger = LoggerFactory.getLogger(Slf4jDemo.class);
        //日志打印方式1
        logger.error("[error  ] 最高级别程序错误");
        logger.warn("[warning] 程序警告不会出现错误");
        logger.info("[info   ] 程序info数据连接,网络连接,IO操作等"); // 默认级别
        logger.debug("[debug  ] 一般在开发中使用,记录程序变量传递信息等等");
        logger.trace("[trace  ] 记录程序所有的流程信息");
        //日志打印方式2
        String name = "蚂蚁小哥", age = "23";
        logger.info("info日志记录:我的姓名是:{} 年龄:{}", name, age);
        //日志打印方式3
        try {
            int a = 1 / 0;
        } catch (Exception e) {
            logger.error("程序发生严重错误!", e);
        }
    }

[main] ERROR cn.xw.Slf4jDemo - [error  ] 最高级别程序错误
[main] WARN cn.xw.Slf4jDemo - [warning] 程序警告不会出现错误
[main] INFO cn.xw.Slf4jDemo - [info   ] 程序info数据连接,网络连接,IO操作等
[main] INFO cn.xw.Slf4jDemo - info日志记录:我的姓名是:蚂蚁小哥 年龄:23
[main] ERROR cn.xw.Slf4jDemo - 程序发生严重错误!
java.lang.ArithmeticException: / by zero
    at cn.xw.Slf4jDemo.demoA(Slf4jDemo.java:30)
    ....

2、常见日志框架的绑定集成

通过前面介绍知道SLF4J它只是个门面框架,只提供API接口而不提供底层实现,所以我们就要绑定具体的日志实现框架;在入门案例中我们已经绑定了SLF4J官方的自己编写的入门日志框架,这里就不在过多介绍,因为功能单调;

针对上面的编号进行介绍:

①:SLF4J

单独导入slf4j-api是没有日志打印的效果,只会打印几句提示信息,提示未绑定日志实现,因为底层没有绑定具体的日志框架

②:SLF4J+logback

底层绑定logback日志实现框架

③:SLF4J+reload4j(Log4j)

底层绑定Log4j日志实现框架(reload4j是Log4j的升级版,因为之前Log4j出现了重大漏洞)

④:SLF4J+JUL

底层绑定Java自带的JUL日志实现框架

⑤:SLF4J+Simple

底层绑定SLF4J官方推出的基本日志实现框架

⑥:SLF4J+nop

关闭一切日志输出信息

下面按照顺序依次介绍及使用:

①:SLF4J

<!--SLF4J基本的API坐标依赖;注单独导入这个是没有日志效果-->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.35</version>
</dependency>

//测试代码(这个测试代码是通用的,因为使用的是门面API,后面的案例可以不要改变测试代码)
@Test
public void demoA() {
    //通过Logger工厂对象动态获取我们具体导入的日志实现框架(Log4j、Logback、JUL、slf4j-simple)
    Logger logger = LoggerFactory.getLogger(Slf4jDemo.class);
    //日志打印方式
    logger.error("[error  ] 最高级别程序错误");
    logger.warn("[warning] 程序警告不会出现错误");
    logger.info("[info   ] 程序info数据连接,网络连接,IO操作等");
    logger.debug("[debug  ] 一般在开发中使用,记录程序变量传递信息等等");
    logger.trace("[trace  ] 记录程序所有的流程信息");
}

<!--具体打印-->
<!--提示我们说没有绑定具体的日志实现-->
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.

②:SLF4J+LogBack

<!--集成logback 在maven中只要导入logback-classic即可,code会依赖传递-->
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.10</version>
</dependency>
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-core</artifactId>
    <version>1.2.10</version>
</dependency>

<!--定义一个logback.xml文件放到resources目录下,没有当前文件则走默认配置,后面章节会详细讲解logback-->
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false" scan="false" scanPeriod="30 seconds">
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>
    <root level="DEBUG">
        <appender-ref ref="STDOUT"/>
    </root>
</configuration>

<!--打印效果-->
17:35:04.120 [main] ERROR cn.xw.Slf4jDemo - [error  ] 最高级别程序错误
17:35:04.124 [main] WARN  cn.xw.Slf4jDemo - [warning] 程序警告不会出现错误
17:35:04.125 [main] INFO  cn.xw.Slf4jDemo - [info   ] 程序info数据连接,网络连接,IO操作等
17:35:04.126 [main] DEBUG cn.xw.Slf4jDemo - [debug  ] 一般在开发中使用,记录程序变量传递信息等等

③:SLF4J+reload4j(Log4j)

<!--reload4j适配器  替代Log4j-->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-reload4j</artifactId>
    <version>1.7.35</version>
</dependency>
<!--reload4j(原名Log4j)-->
<dependency>
    <groupId>ch.qos.reload4j</groupId>
    <artifactId>reload4j</artifactId>
    <version>1.2.18.5</version>
</dependency>

<!--创建log4j.properties放在resources目录下(必须有这个文件,否则无法使用)-->
log4j.rootLogger=DEBUG,console
#输出到控制台
log4j.appender.console=org.apache.log4j.ConsoleAppender
#设置输出样式
log4j.appender.console.layout=org.apache.log4j.PatternLayout
#日志输出信息格式为
log4j.appender.console.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n

<!--日志打印-->
2022-02-12 17:59:30,428 [main] ERROR [cn.xw.Slf4jDemo] - [error  ] 最高级别程序错误
2022-02-12 17:59:30,429 [main] WARN  [cn.xw.Slf4jDemo] - [warning] 程序警告不会出现错误
2022-02-12 17:59:30,429 [main] INFO  [cn.xw.Slf4jDemo] - [info   ] 程序info数据连接,网络连接,IO操作等
2022-02-12 17:59:30,429 [main] DEBUG [cn.xw.Slf4jDemo] - [debug  ] 一般在开发中使用,记录程序变量传递信息等等

④:SLF4J+JUL

<!--JUL适配器-->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-jdk14</artifactId>
    <version>1.7.35</version>
</dependency>

// 打印日志
二月 12, 2022 6:01:45 下午 cn.xw.Slf4jDemo demoA
严重: [error  ] 最高级别程序错误
二月 12, 2022 6:01:45 下午 cn.xw.Slf4jDemo demoA
警告: [warning] 程序警告不会出现错误
二月 12, 2022 6:01:45 下午 cn.xw.Slf4jDemo demoA
信息: [info   ] 程序info数据连接,网络连接,IO操作等

⑤:SLF4J+Simple

这个可以参考入门案例

⑥:SLF4J+nop

<!--slf4j-nop禁用日志,导入这个坐标即可-->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-nop</artifactId>
    <version>1.7.35</version>
</dependency>

注意事项:

坐标导入不能出现冲突情况,比如既导入了Log4j作底层实现也导入了LogBack作底层实现,这样就会产生冲突,会选择其中一个,选择规律是先导入的被选择

这时日志打印出多个实现框架,并在后面选择出当前正在使用的日志打印框架是logback

SLF4J: Class path contains multiple SLF4J bindings.

SLF4J: Found binding in [jar:file:/I:/maven_repository/ch/qos/logback/logback-classic/1.2.10/logback-classic-1.2.10.jar!/org/slf4j/impl/StaticLoggerBinder.class]

SLF4J: Found binding in [jar:file:/I:/maven_repository/org/slf4j/slf4j-reload4j/1.7.35/slf4j-reload4j-1.7.35.jar!/org/slf4j/impl/StaticLoggerBinder.class]

SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.

SLF4J: Actual binding is of type [ch.qos.logback.classic.util.ContextSelectorStaticBinder]

3、slf4j 的桥接器和适配器

桥接器

        A. log4j --> slf4j
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>log4j-over-slf4j</artifactId>
            <version>2.0.0-alpha1</version>
        </dependency>

       B. log4j2 --->slf4j
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-to-slf4j</artifactId>
            <version>2.14.1</version>
        </dependency>

       C. jul--->slf4j
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>jul-to-slf4j</artifactId>
            <version>2.0.0-alpha1</version>
        </dependency>
      
       D. jcl --->slf4j
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>jcl-over-slf4j</artifactId>
            <version>2.0.0-alpha1</version>
        </dependency>

适配器(也就是具体的日志记录策略)

     A. jul
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-jdk14</artifactId>
            <version>2.0.0-alpha1</version>
        </dependency>

    B. log4j
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>2.0.0-alpha1</version>
        </dependency>
      
    C.log4j2
         <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-slf4j-impl</artifactId>
            <version>2.14.1</version>
        </dependency>

    D. logback
         <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.3.0-alpha5</version>
        </dependency>

六、Logback 日志框架

Logback是由log4j创始人设计的又一个开源日志组件。

Logback当前分成三个模块:logback-core、logback- classic和logback-access。

  • logback-core 是其它两个模块的基础模块。
  • logback-classic 是log4j的一个改良版本。此外logback-classic完整实现SLF4J API。使你可以很方便地更换成其它日志系统如log4j或JDK14 Logging。
  • logback-access 访问模块与Servlet容器集成提供通过Http来访问日志的功能。

Logback取代Log4j的几个理由:

  1. 更快的实现:Logback的内核重写了,在一些关键执行路径上性能提升10倍以上。而且logback不仅性能提升了,初始化内存加载也更小了。
  2. 非常充分的测试:Logback经过了几年,数不清小时的测试。Logback的测试完全不同级别的。
  3. Logback-classic 非常自然实现了SLF4J,切换到log4j或者其他都非常容易,只需要提供成另一个jar包就OK,根本不需要去动那些通过SLF4J API实现的代码。
  4. 自动重新加载配置文件,当配置文件修改了,Logback-classic能自动重新加载配置文件。扫描过程快且安全,它并不需要另外创建一个扫描线程。这个技术充分保证了应用程序能跑得很欢在JAVA EE环境里面。

1、Logback入门案例

<!--Logback基础模块-->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
            <version>1.2.10</version>
        </dependency>
    
<!--logback-classic是log4j改良版本,它完整实现SLF4J API,这样只要导入此坐标就可以和SLF4J API契合-->
<!--导入此坐标会自带依赖导入一个core基本包-->
<!--这个依赖直接包含了 logback-core 以及 slf4j-api的依赖-->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.10</version>
        </dependency>


@Test
public void demoA() {
    //通过Logger工厂对象动态获取我们具体导入的日志实现框架Logback
    //因为导入logback-classic坐标后会自动依赖传递SLF4J-api门面
    Logger logger = LoggerFactory.getLogger(LogbackDemo.class);
    //日志打印方式
    logger.error("[error  ] 最高级别程序错误");
    logger.warn(" [warning] 程序警告不会出现错误");
    logger.info(" [info   ] 程序info数据连接,网络连接,IO操作等");
    logger.debug("[debug  ] 一般在开发中使用,记录程序变量传递信息等等"); // 默认级别
    logger.trace("[trace  ] 记录程序所有的流程信息");
}
注:Logback因为和SLF4J API紧密结合,所以日志级别Level和SLF4J一样;
    不指定配置文件则有默认配置文件日志级别:error > warning > info > debug > trace

2、配置文件加载顺序

logback在启动的时候,会按照下面的顺序加载配置文件:

①:如果java程序启动时指定了logback.configurationFile属性,就用该属性指定的配置文件。

如java -Dlogback.configurationFile=/path/to/mylogback.xml Test ,

这样执行Test类的时候就会加载/path/to/mylogback.xml配置

②:在classpath中查找 logback.groovy 文件

③:在classpath中查找 logback-test.xml 文件

④:在classpath中查找 logback.xml 文件

⑤:如果是jdk6+,那么会调用ServiceLoader 查找com.qos.logback.classic.spi.Configurator接口的第一个实现类

⑥:自动使用ch.qos.logback.classic.BasicConfigurator,在控制台输出日志

注:上面的顺序表示优先级,使用java -D配置的优先级最高,只要获取到配置后就不会再执行下面的流程。相关代码可以看ContextInitializer#autoConfig()方法。

3、配置文件详解

Logback.xml控制台输出配置

<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
    <!--定义日志格式参数,后面可以通过${myPattern}使用-->
    <property name="myPattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %c [%thread] %-5level %msg%n"/>

    <!--定义ConsoleAppender 用于在屏幕上输出日志-->
    <appender name="consoleAppend" class="ch.qos.logback.core.ConsoleAppender">
        <!--显示控制台日志颜色 System.err【红色】 System.out【默认白色】-->
        <target>System.err</target>
        <!--配置日志输出格式,并引用 myPattern 自定的日志格式-->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${myPattern}</pattern>
        </encoder>
    </appender>

    <!--配置日志记录器并设置日志记录器的打印级别-->
    <root level="ALL">
        <!--引入appender....-->
        <appender-ref ref="consoleAppend"/>
    </root>
</configuration>

//日志打印
2022-02-15 16:10:34.466 cn.xw.LogbackDemo [main] ERROR [error  ] 最高级别程序错误
2022-02-15 16:10:34.470 cn.xw.LogbackDemo [main] WARN   [warning] 程序警告不会出现错误
2022-02-15 16:10:34.471 cn.xw.LogbackDemo [main] INFO   [info   ] 程序info数据连接,网络连接,IO操作等
2022-02-15 16:10:34.472 cn.xw.LogbackDemo [main] DEBUG [debug  ] 一般在开发中使用,记录程序变量传递信息等等
2022-02-15 16:10:34.472 cn.xw.LogbackDemo [main] TRACE [trace  ] 记录程序所有的流程信息

Logback.xml文件和HTML格式输出

<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
    <!--定义日志格式参数,后面可以通过${myPattern}使用-->
    <property name="myPattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %c [%thread] %-5level %msg%n"/>
    <!--定义日志路径参数,后面可以通过${myPattern}使用-->
    <property name="file_dir" value="D:/logs"/>

    <!--定义FileAppender 用于在文件上输出日志-->
    <appender name="fileAppend" class="ch.qos.logback.core.FileAppender">
        <!--日志文件名称-->
        <file>${file_dir}/fileLogback.log</file>
        <!--配置日志输出格式,并引用 myPattern 自定的日志格式-->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${myPattern}</pattern>
        </encoder>
    </appender>

    <!--定义FileAppender 用于在文件上输出日志-->
    <appender name="fileHtmlAppend" class="ch.qos.logback.core.FileAppender">
        <file>${file_dir}/fileLogback.html</file>
        <!--因为是要输出HTML格式,所以需要布局包装器一下-->
        <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
            <!--设置最终的输出样式-->
            <layout class="ch.qos.logback.classic.html.HTMLLayout">
                <pattern>%level%d{yyyy-MM-dd HH:mm:ss}%c%M%L%thread%m</pattern>
            </layout>
        </encoder>
    </appender>
    
    <!--配置日志记录器并设置日志记录器的打印级别-->
    <root level="ALL">
        <!--引入appender....-->
        <appender-ref ref="fileAppend"/>
        <appender-ref ref="fileHtmlAppend"/>
    </root>
</configuration>

Logback.xml文件输出之拆分归档

<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
    <!--定义日志格式参数,后面可以通过${myPattern}使用-->
    <property name="myPattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %c [%thread] %-5level %msg%n"/>
    <!--定义日志路径参数,后面可以通过${myPattern}使用-->
    <property name="file_dir" value="D:/logs"/>

    <!--定义RollingFileAppender 用于在文件上输出日志并拆分归档-->
    <appender name="rollFileAppend" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!--定义日志输出的路径-->
        <!--这里的scheduler.manager.server.home 没有在上面的配置中设定,所以会使用java启动时配置的值-->
        <!--比如通过 java -Dscheduler.manager.server.home=/path/to XXXX 配置该属性-->
        <file>${scheduler.manager.server.home}/fileRollLogback.log</file>
        <!--配置日志输出格式,并引用 myPattern 自定的日志格式-->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${myPattern}</pattern>
        </encoder>
        <!--基于大小和时间滚动策略-->
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!--按照时间和压缩格式声明文件名 压缩文件为.gz-->
            <fileNamePattern>${scheduler.manager.server.home}/roll.%d{yyyy-MM-dd}.log%i.gz</fileNamePattern>
            <!--按照文件大小拆分 每个文件达到500MB会自动压缩归档-->
            <maxFileSize>500MB</maxFileSize>
        </rollingPolicy>
    </appender>

    <!--配置日志记录器并设置日志记录器的打印级别-->
    <root level="ALL">
        <!--引入appender....-->
        <appender-ref ref="rollFileAppend"/>
    </root>
</configuration>

Logback过滤器及异步打印

<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
    <!--定义日志格式参数,后面可以通过${myPattern}使用-->
    <property name="myPattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %c [%thread] %-5level %msg%n"/>

    <!--定义ConsoleAppender 用于在屏幕上输出日志-->
    <appender name="consoleAppend" class="ch.qos.logback.core.ConsoleAppender">
        <!--显示控制台日志颜色 System.err【红色】 System.out【默认白色】-->
        <target>System.err</target>
        <!--配置日志输出格式,并引用 myPattern 自定的日志格式-->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${myPattern}</pattern>
        </encoder>

        <!--LevelFilter: 级别过滤器,根据日志级别进行过滤。如果日志级别等于配置级别,
        过滤器会根据onMath 和 onMismatch接收或拒绝日志。-->
        <!--例如:将过滤器的日志级别配置为INFO,所有INFO级别的日志交给appender处理,非INFO级别的日志,被过滤掉。-->
<!--        <filter class="ch.qos.logback.classic.filter.LevelFilter">-->
<!--        <level>INFO</level>-->
<!--        <onMatch>ACCEPT</onMatch>-->
<!--        <onMismatch>DENY</onMismatch>-->
<!--        </filter>-->
        <!--ThresholdFilter: 临界值过滤器,过滤掉低于指定临界值的日志。当日志级别等于或高于临界值时,
        过滤器返回NEUTRAL;当日志级别低于临界值时,日志会被拒绝。-->
        <!-- 过滤掉所有低于 DEBUG 级别的日志,留下DEBUG及以上级别的日志 -->
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>DEBUG</level>
        </filter>
    </appender>
    
    <!--配置异步日志-->
    <appender name="asyncAppend" class="ch.qos.logback.classic.AsyncAppender">
        <!--要使用到异步的Appender-->
        <appender-ref ref="consoleAppend"/>
    </appender>
    
    <!--配置日志记录器并设置日志记录器的打印级别-->
    <root level="ALL">
        <!--引入appender....-->
        <appender-ref ref="asyncAppend"/>
    </root>
</configuration>

自定义Logger

<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
    <!--定义日志格式参数,后面可以通过${myPattern}使用-->
    <property name="myPattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %c [%thread] %-5level %msg%n"/>
    <!--定义ConsoleAppender 用于在屏幕上输出日志-->
    <appender name="consoleAppend" class="ch.qos.logback.core.ConsoleAppender">
        <!--显示控制台日志颜色 System.err【红色】 System.out【默认白色】-->
        <target>System.err</target>
        <!--配置日志输出格式,并引用 myPattern 自定的日志格式-->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${myPattern}</pattern>
        </encoder>
    </appender>

    <!--additvity="false" 不继承父元素-->
    <logger name="cn.xw" level="INFO" additvity="false">
        <!--自定义logger中配置appender-->
        <appender-ref ref="consoleAppend"/>
    </logger>
</configuration>

七、Log4j2 日志框架

Apache Log4j 2是对Log4j的升级,它比其前身Log4j 1.x提供了重大改进,并提供了Logback中可用的许多改进,同时修复了Logback架构中的一些问题。被誉为是目前最优秀的Java日志框架。

性能提升

Log4j2包含基于LMAX Disruptor库的下一代异步记录器。在多线程场景中,异步记录器的吞吐量比Log4j 1.x和Logback高18倍,延迟低。

自动重新加载配置

与Logback一样,Log4j2可以在修改时自动重新加载其配置。与Logback不同,它会在重新配置发生时不会丢失日志事件。

高级过滤

与Logback一样,Log4j2支持基于Log事件中的上下文数据,标记,正则表达式和其他组件进行过滤。此外,过滤器还可以与记录器关联。与Logback不同,Log4j2可以在任何这些情况下使用通用的Filter类。

插件架构

Log4j使用插件模式配置组件。因此,您无需编写代码来创建和配置Appender,Layout,Pattern Converter等。在配置了的情况下,Log4j自动识别插件并使用它们。

无垃圾机制

在稳态日志记录期间,Log4j2 在独立应用程序中是无垃圾的,在Web应用程序中是低垃圾。这减少了垃圾收集器的压力,并且可以提供更好的响应性能。

目前市面上最主流的日志门面就是SLF4J,虽然Log4j2 也是日志门面,因为它的日志实现功能非常强大,性能优越。所以我们一般情况下还是将 Log4j2 看作是日志的实现SLF4j + Log4j2 的组合,是市场上最强大的日志功能实现方式,绝对是未来的主流趋势。

  • 32
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值