Java 日志框架

一、日志框架简介

        日志框架主要分为两大类,第一类是日志门面,第二类是日志实现。

1.1 日志门面和日志实现

        日志门面是日志实现的抽象层,日志实现是具体日志功能的实现。为什么不直接使用日志实现,而是又弄了一个叫日志门面的东西?因为日志实现可能会有一些代码的优化和改动,为了避免影响用户在项目中的使用,可以使用日志门面这些统一的接口,即便将来在实现层代码做了更改,用户在项目中使用日志调用接口也不会收到影响。

        在实际的项目中,一般都是一个日志门面搭配一个日志实现来使用。

1.2 日志门面分类

        日志门面目前比较主流的是 apache JCLJakarta Commons Logging) 和 slf4j。其中 JCL 已经停止更新,一般不考虑使用。目前推荐使用 slf4j

1.3 日志实现分类

        日志实现目前比较主流的是 jdk 自带的 JULjava util logging)、logbacklog4jlog4j2 等。

        其中 logback 推出以后,log4j 就停止更新了,它性能出现了问题,所以 apache 又将版本升级到了 log4j2 ,其中 log4j2 是性能最好的技术。

二、JUL 学习

        JUL全称 Java util Loggingjava 原生的日志框架,使用时不需要另外引用第三方类库,相对其他日志框架使用方便,学习简单,能够在小型应用中灵活使用。

2.1 架构介绍

        整体的架构如下图所示,用户使用 Logger 来进行日志记录,Logger 持有若干个 Handler,日志的输出操作是由 Handler 完成的。Handler 在输出日志前,会经过 Filter 的过滤,判断哪些日志级别过滤放行哪些拦截,Handler 会将日志内容输出到指定位置(日志文件、控制台等)。Handler 在输出日志时会使用 Layout,将输出内容进行排版。

        1、Loggers:被称为记录器,应用程序通过获取 Logger 对象,调用其 API 来来发布日志信息。Logger 通常是应用程序访问日志系统的入口程序。

        2、Handlers:每个 Logger 都会关联一组 HandlersLogger 会将日志交给关联 Handlers 处理,由 Handlers 负责将日志做记录。Handlers 在此是一个抽象,其具体的实现决定了日志记录的位置可以是控制台、文件、网络上的其他日志服务或操作系统日志等。

        3、Filters:过滤器,根据需要定制哪些信息会被记录,哪些信息会被放过。

2.2 入门案例

        创建一个 maven 工程,不要引入任何依赖,只需要指定 jdk 的版本即可,执行下面的代码:

package org.example;

import java.util.logging.Level;
import java.util.logging.Logger;

public class Main {
    public static void main(String[] args) {

        // 1.创建日志记录器对象
        Logger logger = Logger.getLogger("org.example.Main");

        // 2.日志记录输出的第一种方式,直接输出 info 级别的日志
        logger.info("hello jul");

        //3.日志输出的第二种方式,指定日志的输出级别
        logger.log(Level.INFO,"info msg");

        //4.日志输出的第三种方式,占位符输出
        String name="张三";
        int age = 18;
        logger.log(Level.INFO,"用户信息:{0},{1}",new Object[]{name,age});
    }
}

        输出结果如下所示:

2.3 日志的级别

2.3.1 默认级别

        在 JUL 中定义了 7 种日志的级别,分别为:SEVERE(最高值)> WARNING > INFO (默认级别) > CONFIG > FINE > FINER > FINEST(最低值)。如下代码:

public class Main {
    public static void main(String[] args) {

        // 1.创建日志记录器对象
        Logger logger = Logger.getLogger("org.example.Main");
        //2.日志输出的第二种方式,指定日志的输出级别
        logger.log(Level.SEVERE,"SEVERE msg");
        logger.log(Level.WARNING,"WARNING msg");
        logger.log(Level.INFO,"INFO msg");
        logger.log(Level.CONFIG,"CONFIG msg");
        logger.log(Level.FINE,"FINE msg");
        logger.log(Level.FINER,"FINER msg");
        logger.log(Level.FINEST,"FINEST msg");
    }
}

        可以看到由于默认级别是 INFO,它只会打印比 INFO 级别高的日志

2.3.2 自定义级别 

         还有两个特殊的级别,ALL 和 OFF,他们都属于自定义的级别,其中 OFF 可用来关闭日志记录,如下代码:

public class Main {
    public static void main(String[] args) {

        // 1.创建日志记录器对象
        Logger logger = Logger.getLogger("org.example.Main");
        // 2.重新配置日志具体级别
        logger.setLevel(Level.OFF);

        // 3.指定日志的输出级别
        logger.log(Level.SEVERE,"SEVERE msg");
        logger.log(Level.WARNING,"WARNING msg");
        logger.log(Level.INFO,"INFO msg");
        logger.log(Level.CONFIG,"CONFIG msg");
        logger.log(Level.FINE,"FINE msg");
        logger.log(Level.FINER,"FINER msg");
        logger.log(Level.FINEST,"FINEST msg");
    }
}

         ALL 可用来启用所有的日志消息,如下代码:

public class Main {
    public static void main(String[] args) {

        // 1.创建日志记录器对象
        Logger logger = Logger.getLogger("org.example.Main");

        // 2.关闭系统默认的级别
        logger.setUseParentHandlers(false);
        // 3.重新配置日志具体级别
        logger.setLevel(Level.ALL);
        // 4.创建 ConsoleHandler 控制台输出
        ConsoleHandler consoleHandler = new ConsoleHandler();
        // 5.创建简单格式转换对象
        SimpleFormatter simpleFormatter = new SimpleFormatter();
        // 6.将 Handler 和 Formatter 进行关联: logger ——关联—— Handler ——关联—— Formatter
        consoleHandler.setFormatter(simpleFormatter);
        logger.addHandler(consoleHandler);
        // 7.设consoleHandler 控制台输出的级别~
        consoleHandler.setLevel(Level.ALL);

        // 8.指定日志的输出级别
        logger.log(Level.SEVERE,"SEVERE msg");
        logger.log(Level.WARNING,"WARNING msg");
        logger.log(Level.INFO,"INFO msg");
        logger.log(Level.CONFIG,"CONFIG msg");
        logger.log(Level.FINE,"FINE msg");
        logger.log(Level.FINER,"FINER msg");
        logger.log(Level.FINEST,"FINEST msg");
    }
}

         输出结果如下所示:

2.3.3 输出指定目录

        可以将日志输出到指定的文件中,如下代码:

public class Main {
    public static void main(String[] args) throws IOException {

        // 1.创建日志记录器对象
        Logger logger = Logger.getLogger("org.example.Main");

        // 2.关闭系统默认的级别
        logger.setUseParentHandlers(false);
        // 3.重新配置日志具体级别
        logger.setLevel(Level.ALL);
        // 4.创建 ConsoleHandler 控制台输出
        ConsoleHandler consoleHandler = new ConsoleHandler();
        // 5.创建简单格式转换对象
        SimpleFormatter simpleFormatter = new SimpleFormatter();
        // 6.将 Handler 和 Formatter 进行关联: logger ——关联—— Handler ——关联—— Formatter
        consoleHandler.setFormatter(simpleFormatter);
        logger.addHandler(consoleHandler);
        // 7.设consoleHandler 控制台输出的级别~
        consoleHandler.setLevel(Level.ALL);
        // 8.指定日志输出的文件
        FileHandler fileHandler = new FileHandler("f:/jul.log");
        fileHandler.setFormatter(simpleFormatter);
        logger.addHandler(fileHandler);

        // 8.指定日志的输出级别
        logger.log(Level.SEVERE,"SEVERE msg");
        logger.log(Level.WARNING,"WARNING msg");
        logger.log(Level.INFO,"INFO msg");
        logger.log(Level.CONFIG,"CONFIG msg");
        logger.log(Level.FINE,"FINE msg");
        logger.log(Level.FINER,"FINER msg");
        logger.log(Level.FINEST,"FINEST msg");
    }
}

2.4 父子关系

        JUL Logger 之间存在父子关系,这种父子关系通过树状结构存储,JUL 在初始化时会创建一个顶层 RootLogger 作为所有 Logger Logger,存储上作为树状结构的根节点。并父子关系通过路径来关联。如下代码:

public class Main {
    public static void main(String[] args) throws IOException {

        // 日志记录器对象父子关系
        Logger logger1 = Logger.getLogger("org.example.Main");
        Logger logger2 = Logger.getLogger("org.example");
        System.out.println(logger1.getParent() == logger2);
        // 所有日志记录器对象的顶级父元素 class 为 java.util.logging.LogManager$RootLogger name为""
        System.out.println("logger2 parent:" + logger2.getParent() + ",name:" + logger2.getParent().getName());
        // 一、自定义日志级别
        // a.关闭系统默认配置
        logger2.setUseParentHandlers(false);
        // b.创建handler对象
        ConsoleHandler consoleHandler = new ConsoleHandler();
        // c.创建formatter对象
        SimpleFormatter simpleFormatter = new SimpleFormatter();
        // d.进行关联
        consoleHandler.setFormatter(simpleFormatter);
        logger2.addHandler(consoleHandler);
        // e.设置日志级别
        logger2.setLevel(Level.ALL);
        consoleHandler.setLevel(Level.ALL);
        // 测试日志记录器对象父子关系
        logger1.severe("severe");
        logger1.warning("warning");
        logger1.info("info");
        logger1.config("config");
        logger1.fine("fine");
        logger1.finer("finer");
        logger1.finest("finest");
    }
}

        输出结果如下所示,可以看到,打印的是 logger2 设置的日志级别。

2.5 日志的配置文件

        使用 JUL 的这种日志方式默认的配置文件的位置是在 $JAVAHOME\jre\lib\logging.properties,如下图:

        我们可以自己指定配置文件的位置,首先在 resources 下创建一个 logging.properties 文件,内容如下所示:

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
java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter


java.util.logging.ConsoleHandler.level = ALL
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter


com.xyz.foo.level = SEVERE

        然后在代码里面指定读取配置文件里面的内容即可,如下代码:

public class Main {
    public static void main(String[] args) throws IOException {

        // 通过类加载器读取自定义配置文件
        InputStream in =
                Main.class.getClassLoader().getResourceAsStream("logging.properties");
        // 获取日志管理器对象
        LogManager logManager = LogManager.getLogManager();
        // 通过日志管理器加载配置文件
        logManager.readConfiguration(in);

        Logger logger = Logger.getLogger("org.example.Main");
        logger.severe("severe");
        logger.warning("warning");
        logger.info("info");
        logger.config("config");
        logger.fine("fine");
        logger.finer("finer");
        logger.finest("finest");
    }
}

        输出结果如下:

        配置文件的参数详解如下所示:

## RootLogger使用的处理器(获取时设置),这个地方既可以指定控制台处理器也可以设置文件处理器
handlers= java.util.logging.ConsoleHandler
# RootLogger日志等级
.level= INFO


## 自定义Logger
com.itheima.handlers= java.util.logging.FileHandler
# 自定义Logger日志等级
com.itheima.level= INFO
# 忽略父日志设置
com.itheima.useParentHandlers=false


## 控制台处理器
# 输出日志级别
java.util.logging.ConsoleHandler.level = INFO
# 输出日志格式
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter


## 文件处理器
# 输出日志级别
java.util.logging.FileHandler.level=INFO
# 输出日志格式
java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter
# 输出日志文件路径
java.util.logging.FileHandler.pattern = /java%u.log
# 输出日志文件限制大小(50000字节)
java.util.logging.FileHandler.limit = 50000
# 输出日志文件限制个数
java.util.logging.FileHandler.count = 10
# 输出日志文件 是否是追加
java.util.logging.FileHandler.append=true

2.6 日志原理解析

        JUL 流程示意图如下所示:

        1、初始化 LogManagerLogManager 加载 logging.properties 配置,并添加 Logger LogManager

        2、从单例 LogManager 获取 Logger

        3、设置级别 Level,并指定日志记录 LogRecord

        4、Filter 提供了日志级别之外更细粒度的控制

        5、Handler 是用来处理日志输出位置

        6、Formatter 是用来格式化 LogRecord 的。

三、Log4j 学习

        Log4j Apache 下的一款开源的日志框架,通过在项目中使用 Log4J,我们可以控制日志信息输出到控制台、文件、甚至是数据库中。我们可以控制每一条日志的输出格式,通过定义日志的输出级别,可以更灵活的控制日志的输出过程。方便项目的调试。

3.1 Log4j 入门

        首先添加 maven 依赖,如下代码:

    <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>

        java 示例代码如下所示:

package org.example;

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

public class Log4jTest {

    public static void main(String[] args) {
        // 1、由于没有配置文件,所以需要初始化系统配置信息
        BasicConfigurator.configure();

        // 2、创建日志记录器对象
        Logger logger = Logger.getLogger(Log4jTest.class);
        // 3、日志记录输出的第一种方式
        logger.info("hello log4j");
        // 4、日志记录输出的第二种方式
        logger.fatal("fatal"); // 严重错误,一般会造成系统崩溃和终止运行
        logger.error("error"); // 错误信息,但不会影响系统运行
        logger.warn("warn"); // 警告信息,可能会发生问题
        logger.info("info"); // 程序运行信息,数据库的连接、网络、IO操作等
        logger.debug("debug"); // 调试信息,一般在开发阶段使用,记录程序的变量、参数等
        logger.trace("trace"); // 追踪信息,记录程序的所有流程信息
    }
}

        输出结果如下所示,没有输出 trace 日志信息是因为 log4j 默认的日志输出级别是 debug

        日志的级别从高到低分别为:fatal > error > warn > info > debug > trace ,在实际开发中一般只使用 errorwarninfo debug 就可以了。

3.2 Log4j 组件

        Log4J 主要由 Loggers (日志记录器)、Appenders(输出端)和 Layout(日志格式化器)组成。其中 Loggers 控制日志的输出级别与日志是否输出;Appenders 指定日志的输出方式(输出到控制台、文件等);Layout 控制日志信息的输出格式。

3.2.1 Loggers

        日志记录器,负责收集处理日志记录,实例的命名就是类 “XX” 的 full quailied name(类的全限定名),Logger 的名字大小写敏感,其命名有继承机制:例如:name org.apache.commons logger 会继承 name org.apache logger

        Log4J 中有一个特殊的 logger 叫做 “root”,他是所有 logger 的根,也就意味着其他所有的 logger 都会直接或者间接地继承自 rootroot logger 可以用 Logger.getRootLogger() 方法获取。

        但是,自 log4j 1.2 版以来, Logger 类已经取代了 Category 类。对于熟悉早期版本的 log4j 的人来说,Logger 类可以被视为 Category 类的别名。

3.2.2 Appenders

        Appender 用来指定日志输出到哪个地方,可以同时指定日志的输出目的地。Log4j 常用的输出目的地有以下几种:

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

3.2.3 Layouts

        布局器 Layouts 用于控制日志输出内容的格式,让我们可以使用各种需要的格式输出日志。 Log4j 常用的 Layouts 有以下几种:

格式化器类型作用
HTMLLayout格式化日志输出为HTML表格形式
SimpleLayout简单的日志输出格式化,打印的日志格式为(info - message)
PatternLayout最强大的格式化期,可以根据自定义格式输出日志,如果没有指定转换格式,就是用默认的转换格式

3.3 添加配置文件

        在 resources 目录下添加 log4j.properties 的配置文件,并配置相关的配置信息,如下所示:

# 指定 RootLogger 顶级父元素默认配置信息
# 指定日志级别为 trace,使用的 appdener 为 console
log4j.rootLogger = trace,console

# 指定控制台日志输出的 appender 对象
log4j.appender.console = org.apache.log4j.ConsoleAppender

# 指定消息格式 layout
log4j.appender.console.layout = org.apache.log4j.SimpleLayoutayout

        测试代码如下:

public class Log4jTest {

    public static void main(String[] args) {
        // 1、创建日志记录器对象
        Logger logger = Logger.getLogger(Log4jTest.class);
        // 2、日志记录输出的第一种方式
        logger.info("hello log4j");
        // 3、日志记录输出的第二种方式
        logger.fatal("fatal"); // 严重错误,一般会造成系统崩溃和终止运行
        logger.error("error"); // 错误信息,但不会影响系统运行
        logger.warn("warn"); // 警告信息,可能会发生问题
        logger.info("info"); // 程序运行信息,数据库的连接、网络、IO操作等
        logger.debug("debug"); // 调试信息,一般在开发阶段使用,记录程序的变量、参数等
        logger.trace("trace"); // 追踪信息,记录程序的所有流程信息
    }
}

3.4 Layout 的格式

        在上面 log4j.properties 的配置文件中,我们定义了日志的输出级别与输出端,接下来详细介绍下在输出端配置日志的输出格式。如下配置文件:

# 指定 RootLogger 顶级父元素默认配置信息
# 指定日志级别为 trace,使用的 appdener 为 console
log4j.rootLogger = trace,console
# 指定控制台日志输出的 appender 对象
log4j.appender.console = org.apache.log4j.ConsoleAppender
# 指定消息格式 layout
log4j.appender.console.layout = org.apache.log4j.PatternLayout
# 指定消息格式的内容
log4j.appender.console.layout.conversionPattern = [%p]%r %l %d{yyyy-MM-dd HH:mm:ss.SSS} %m%n

    # %m 输出代码中指定的日志信息
    # %p 输出优先级,及 DEBUG、INFO 等
    # %n 换行符(Windows平台的换行符为 "\n",Unix 平台为 "\n")
    # %r 输出自应用启动到输出该 log 信息耗费的毫秒数
    # %c 输出打印语句所属的类的全名
    # %t 输出产生该日志的线程全名
    # %d 输出服务器当前时间,默认为 ISO8601,也可以指定格式,如:%d{yyyy-MM-dd HH:mm:ss.SSS}
    # %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.4 Appender 的输出

        Appender 的输出位置可以是控制台、文件中或者是数据库中。

3.4.1 输出到文件

        首先将日志文件输出到文件之中,配置信息如下:

# 指定 RootLogger 顶级父元素默认配置信息
# 指定日志级别为 trace,使用的 appdener 为 console
log4j.rootLogger = trace,console,file


# 指定控制台日志输出的 appender 对象
log4j.appender.console = org.apache.log4j.ConsoleAppender
# 指定消息格式 layout
log4j.appender.console.layout = org.apache.log4j.PatternLayout
# 指定消息格式的内容
log4j.appender.console.layout.conversionPattern = [%p]%r %l %d{yyyy-MM-dd HH:mm:ss.SSS} %m%n



# 日志输出的 appender 对象
log4j.appender.file = org.apache.log4j.FileAppender
# 指定消息格式 layout
log4j.appender.file.layout = org.apache.log4j.PatternLayout
# 指定消息格式的内容
log4j.appender.file.layout.conversionPattern = [%p]%r %l %d{yyyy-MM-dd HH:mm:ss.SSS} %m%n
# 指定日志文件保存路径
log4j.appender.file.file = f:/log4j.log
# 指定日志文件的字符集
log4j.appender.file.encoding = UTF-8

        运行代码,可以看到,日志不光在控制台进行了输出,还在 f 盘进行了存储,如下:

3.4.2 按照大小拆分 

        上面我们将日志输出到了文件之中,我们还可以按照文件的大小进行拆分,拆分成不同的 appender,如下配置文件:

# 指定 RootLogger 顶级父元素默认配置信息
# 指定日志级别为 trace,使用的 appdener 为 console
log4j.rootLogger = trace,rollingFile


# 按照文件大小进行拆分的 appender 对象
# 日志文件输出的 appender 对象
log4j.appender.rollingFile = org.apache.log4j.RollingFileAppender
# 指定消息格式 layout
log4j.appender.rollingFile.layout = org.apache.log4j.PatternLayout
# 指定消息格式的内容
log4j.appender.rollingFile.layout.conversionPattern = [%p]%r %l %d{yyyy-MM-dd HH:mm:ss.SSS} %m%n
# 指定日志文件保存路径
log4j.appender.rollingFile.file = f:/log4j.log
# 指定日志文件的字符集
log4j.appender.rollingFile.encoding = UTF-8
# 指定日志文件内容的大小
log4j.appender.rollingFile.maxFileSize = 1MB
# 指定日志文件的数量,最多就是 10 个,超过了则覆盖最早的日志文件
log4j.appender.rollingFile.maxBackupIndex = 10

        执行下面的测试代码:

public class Log4jTest {
    public static void main(String[] args) {
        // 1、创建日志记录器对象
        Logger logger = Logger.getLogger(Log4jTest.class);
        // 2、日志记录输出的第一种方式
        logger.info("hello log4j");
        for (int i = 0; i < 10000; i++) {
            // 3、日志记录输出的第二种方式
            logger.fatal("fatal"); // 严重错误,一般会造成系统崩溃和终止运行
            logger.error("error"); // 错误信息,但不会影响系统运行
            logger.warn("warn"); // 警告信息,可能会发生问题
            logger.info("info"); // 程序运行信息,数据库的连接、网络、IO操作等
            logger.debug("debug"); // 调试信息,一般在开发阶段使用,记录程序的变量、参数等
            logger.trace("trace"); // 追踪信息,记录程序的所有流程信息
        }
    }
}

        输出的结果如下所示:

3.4.3 按照时间拆分

         按照时间拆分的配置文件内容如下所示:

# 指定 RootLogger 顶级父元素默认配置信息
# 指定日志级别为 trace,使用的 appdener 为 console
log4j.rootLogger = trace,dailyFile



# 按照时间规则进行拆分的 appender 对象
# 日志输出的 appender 对象
log4j.appender.dailyFile = org.apache.log4j.DailyRollingFileAppender
# 指定消息格式 layout
log4j.appender.dailyFile.layout = org.apache.log4j.PatternLayout
# 指定消息格式的内容
log4j.appender.dailyFile.layout.conversionPattern = [%p]%r %l %d{yyyy-MM-dd HH:mm:ss.SSS} %m%n
# 指定日志文件保存路径
log4j.appender.dailyFile.file = f:/log4j.log
# 指定日志文件的字符集
log4j.appender.dailyFile.encoding = UTF-8
# 按照日期拆分规则,下面这种格式是按照秒来拆分的
log4j.appender.dailyFile.datePattern = '.'yyyy-MM-dd-HH-mm-ss

        多执行几次上一节的测试代码,可以看到,生成了好几个日志文件,如下:

3.4.4 保存到数据库

        接下来介绍下如何将日志信息保存到数据库中,首先在数据库中创建一张表,建表语句如下:

CREATE TABLE `log` (
`log_id` int(11) NOT NULL AUTO_INCREMENT,
`project_name` varchar(255) DEFAULT NULL COMMENT '目项名',
`create_date` varchar(255) DEFAULT NULL COMMENT '创建时间',
`level` varchar(255) DEFAULT NULL COMMENT '优先级',
`category` varchar(255) DEFAULT NULL COMMENT '所在类的全名',
`file_name` varchar(255) DEFAULT NULL COMMENT '输出日志消息产生时所在的文件名称 ',
`thread_name` varchar(255) DEFAULT NULL COMMENT '日志事件的线程名',
`line` varchar(255) DEFAULT NULL COMMENT '号行',
`all_category` varchar(255) DEFAULT NULL COMMENT '日志事件的发生位置',
`message` varchar(4000) DEFAULT NULL COMMENT '输出代码中指定的消息',
PRIMARY KEY (`log_id`)
);

         然后在 log4j.properties 中添加相关的配置信息,如下:

# 指定 RootLogger 顶级父元素默认配置信息
# 指定日志级别为 trace,使用的 appdener 为 console
log4j.rootLogger = trace,logDB


log4j.appender.logDB=org.apache.log4j.jdbc.JDBCAppender
log4j.appender.logDB.layout=org.apache.log4j.PatternLayout
log4j.appender.logDB.Driver=com.mysql.jdbc.Driver
log4j.appender.logDB.URL=jdbc:mysql://localhost:3306/test
log4j.appender.logDB.User=root
log4j.appender.logDB.Password=123456
log4j.appender.logDB.Sql=INSERT INTO log(project_name,create_date,level,category,file_name,thread_name,line,all_category,message) values('itcast','%d{yyyy-MM-dd HH:mm:ss}','%p','%c','%F','%t','%L','%l','%m')

        引入 mysql maven 依赖,如下:

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>

        执行上一节的测试代码,可以看到,数据已经存储到了数据库的表中,如下:

3.5 自定义 Logger

        自定义 Logger 的作用是指定一些类的日志输出到指定目录,以下面的配置文件为例,org.example 包下面的日志输出级别为 info,并将日志输出到指定文件夹下,org.apache 包下面日志的输出级别为 error

        其他未指定的类则按照 rootLogger trace 级别进行打印,并将打印信息显示在控制台中。

# RootLogger 配置
log4j.rootLogger = trace,console


# 自定义Logger
log4j.logger.org.example = info,file
log4j.logger.org.apache = error

四、JCL 学习

        JCL 全称为 Jakarta Commons Logging,是 Apache 提供的一个通用日志 API

        它是为 "所有的 Java 日志实现" 提供一个统一的接口,它自身也提供一个日志的实现,但是功能非常常弱(SimpleLog)。所以一般不会单独使用它。他允许开发人员使用不同的具体日志实现工具:Log4jJdk 自带的日志(JUL)。

        JCL 有两个基本的抽象类:基本记录器 Log 和负责创建 Log 实例的 LogFactory。架构图如下:

4.1 JCL 入门

        首先创建一个 maven 工程,添加如下的 maven 依赖:

<dependency>
	<groupId>commons-logging</groupId>
	<artifactId>commons-logging</artifactId>
	<version>1.2</version>
</dependency>

        编写测试代码如下:

public class Main {
    public static void main(String[] args) {

        // 创建日志对象
        Log log = LogFactory.getLog(Main.class);
        // 日志记录输出
        log.fatal("fatal");
        log.error("error");
        log.warn("warn");
        log.info("info");
        log.debug("debug");
        System.out.println("Hello world!");
    }
}

        输出结果如下,可以看到 JCL 默认调用的就是 JUL 里面的 api 来打印日志的。

        接下来我们添加 log4j maven 依赖,并将 log4j.properties 配置文件复制到 resources 目录下,再次执行上面的代码,可以看到,这次调用就是 log4j 的底层 api 了。还是比较厉害的。

4.2 使用日志门面原因

        1、面向接口开发,不再依赖具体的实现类。减少代码的耦合

        2、项目通过导入不同的日志实现类,可以灵活的切换日志框架

        3、统一 API,方便开发者学习和使用

        4、统一配置便于项目日志的管理

4.3 JCL 原理

        JCL 底层实际上是通过 LogFactory 动态加载 Log 实现类来完成日志功能的,可以加载下面的四种实现类。

        JCL 这种日志门面支持的日志实现数组有下面的四个,首先判断是否存在 log4j ,如果存在则调用它的 api,不存在则调用 jdk 自带的 logger,如下:

private static final String[] classesToDiscover =
	new String[]{"org.apache.commons.logging.impl.Log4JLogger",
		"org.apache.commons.logging.impl.Jdk14Logger",
		"org.apache.commons.logging.impl.Jdk13LumberjackLogger",
		"org.apache.commons.logging.impl.SimpleLog"};

五、日志门面

5.1 日志门面简介

        在项目的初期时,我们可能只是使用 JUL 进行日志的单一输出,JUL 可以满足项目的需求。但是随着功能的增加,单一的日志文件会越来越臃肿,所以我们需要按照一定的规则进行滚动拆分,比如说按照时间以天为单位进行管理,但是 JUL 并不支持。

        此时我们就需要在项目中引入 log4j,但是项目之前的日志代码都是基于 JUL 来实现的,一旦换了日志框架,那么之前的代码就需要修改,工作量是非常大的。所以此时 apache 组织就站出来了,将这两个日志框架进行了统一的管理和维护,提供了一个日志门面 JCL ,并提供了一套全新的 api,以面向接口的方式来操作具体的实现。即日志框架变了,代码也不需要发生修改。这个就是日志门面技术。

5.2 日志门面实现

        常见的日志门面有 JCL slf4jJCL 仅支持 JUL log4j 框架,目前已经被淘汰。slf4j 是目前市场上比较主流的日志门面,它支持现在所有的日志实现框架。

        常见的日志实现有 JULlog4jlogbacklog4j2 等。

5.3 日志门面和日志实现关系

        最早的时候,用户要想进行日志记录是直接操作日志实现类的。这种方式意味着,如果后期日志实现发生了变化,代码也需要发生变化。

        但是现在有了日志门面之后,用户只需要操作统一的 api 就可以了,由这套接口的底层再去操作具体的日志实现进行日志的记录,这样就可以实现用户和日志实现的解耦的思想,方便进行统一的管理和维护。后期如果想替换日志实现,只需要替换相应的 jar 包即可。

5.4 日志框架历史顺序

log4j -->JUL-->JCL--> logback --> slf4j --> log4j2

        jdk 最早期的时候是没有提供日志记录功能的,而是通过 System.out 的方式进行输出,此时 apache 的一个哥们就设计出了一个框架 log4j,设计出来之后很受欢迎。所以 jdk 1.4 版本时也设计出了一个日志框架 JUL

        所以当时市场就比较混乱,有的公司使用 log4j,有的公司使用 JUL,为了解决这个问题,apache 公司就推出了 JCL 来统一管理 log4j JUL

        过了一段时间之后 log4j 的创始人和 apache 闹了一些矛盾就出去单飞了,然后单飞的这个哥们就基于 log4j 设计出了一个全新的日志框架 logback,功能和性能比 log4j 更强大,但是用的人不多,因为 JCL 不支持 logback

        基于这种情况,这个哥们又设计出了一个日志门面 slf4j,这个日志门面支持所有的日志实现框架,而且操作起来非常简单。等到 slf4j 出现之后,JCL 就完犊子了。

        apache 公司就着急了,市场上没有人用了,所以他们就蛰伏了两年,根据 logback 开放的源代码,设计出了全新的日志框架 log4j2,它从功能上而言几乎和 logback 一致,但是性能上比 logback 提升了很多。需要注意的是 log4j2 既是日志门面技术也是日志实现框架

六、slf4j 

6.1 简介

        简单日志门面(Simple Logging Facade For JavaSLF4J 主要是为了给 Java 日志访问提供一套标准、规范的 API 框架,其主要意义在于提供接口,具体的实现可以交由其他日志框架,例如 log4j logback 等。当然 slf4j 自己也提供了功能较为简单的实现,但是一般很少用到。对于一般的 Java 项目而言,日志框架会选择 slf4j 作为门面,配上具体的实现框架(log4jlogback等),中间使用桥接器完成桥接。

        SLF4J 是目前市面上最流行的日志门面。现在的项目中,基本上都是使用 SLF4J 作为我们的日志系统。SLF4J 日志门面主要提供两大功能:日志框架的绑定和日志框架的桥接。

6.2 快速入门

        首先创建一个 maven 工程,并添加下面的依赖:

        <!--slf4j core 使用slf4j必須添加-->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.27</version>
        </dependency>
        <!--slf4j 自带的简单日志实现 -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
            <version>1.7.27</version>
        </dependency>

        编写代码如下所示:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class SLF4JTestMain {

    // 声明日志对象
    private final static Logger LOGGER = LoggerFactory.getLogger(SLF4JTestMain.class);
    public static void main(String[] args) {
        //打印日志信息
        LOGGER.error("error");
        LOGGER.warn("warn");
        LOGGER.info("info"); // 默认输出级别为 info
        LOGGER.debug("debug");
        LOGGER.trace("trace");

        // 使用占位符输出日志信息
        String name = "jack";
        Integer age = 18;
        LOGGER.info("用户:{},{}", name, age);

        // 将系统异常信息写入日志
        try {
            int i = 1 / 0;
        } catch (Exception e) {
            // e.printStackTrace();
            LOGGER.info("出现异常:", e);
        }
    }
}

        输出结果如下所示:

6.3 绑定日志的实现

        在上一小节的快速入门时,使用 slf4j 绑定了一个内置的简单实现框架,接下来我们看下 slf4j 如何绑定其他主流的日志实现框架,在官网中有详细的介绍,如下图:

        在这个图片的上方绿颜色的部分表示的是我们项目的应用,如果我们的项目想要进行日志记录的话,首先需要关联 SLF4J,它就是面向的接口,统一所有日志的 api,就是 maven 依赖里面的 slf4j-api ,它的下方需要提供具体的实现,在这里一共有三种情况。

        第一种情况是如果项目中只引入了 slf4j-api 日志门面,并没有导入具体的日志实现,那么日志的这个功能就是关闭的,此时不会进行任何的日志输出。上图中左侧的第一个。

        第二种情况是蓝颜色部分,它里面有三种日志实现,一个是 logback,一个是 slf4j 内置的 simple 简单框架,还有一个是 nop,这三个框架的设计是要比 slf4j 要晚的,所以它们默认就遵从 slf4j 的开发规范了,即我们只需要导入它们的实现即可。上图中左侧的第二、五和六个。

        第三种情况是灰颜色部分,即中间那两个,一个是 log4j,一个是 JUL,它们两个设计的比较早,默认是并没有遵循 slf4j 的规范的,它们是无法进行直接绑定的,中间需要添加一个适配层 Adaptation 来适配我们具体的实现,就会简介的遵循我们的 slf4j api 规范。

6.3.1 绑定 logback

        添加 maven 依赖,如下代码:

<!--slf4j core 使用slf4j必須添加-->
<dependency>
	<groupId>org.slf4j</groupId>
	<artifactId>slf4j-api</artifactId>
	<version>1.7.27</version>
</dependency>
<!-- logback 日志实现-->
<dependency>
	<groupId>ch.qos.logback</groupId>
	<artifactId>logback-classic</artifactId>
	<version>1.2.3</version>
</dependency>

        执行快速入门的测试代码,输出结果如下图:

6.3.2 绑定 slf4j-nop

        slf4j-nop 是一个日志的开关,当我们导入这个实现之后,就表示 slf4j 就不会使用任何的日志实现框架,这个功能就关闭了,如下 maven 依赖:

<!--slf4j core 使用slf4j必須添加-->
<dependency>
	<groupId>org.slf4j</groupId>
	<artifactId>slf4j-api</artifactId>
	<version>1.7.27</version>
</dependency>
<!-- nop 日志的开关-->
<dependency>
	<groupId>org.slf4j</groupId>
	<artifactId>slf4j-nop</artifactId>
	<version>1.6.6</version>
</dependency>

        执行测试代码,输出结果如下,没有任何的日志输出。

6.3.3 绑定 log4j

        如果想要绑定 log4j,需要需要绑定一个 slf4j-log412 适配器,如下 maven 依赖:

<!--slf4j core 使用slf4j必須添加-->
<dependency>
	<groupId>org.slf4j</groupId>
	<artifactId>slf4j-api</artifactId>
	<version>1.7.27</version>
</dependency>
<!-- 绑定 log4j 日志实现,需要导入适配器-->
<dependency>
	<groupId>org.slf4j</groupId>
	<artifactId>slf4j-log4j12</artifactId>
	<version>1.7.25</version>
</dependency>
<dependency>
	<groupId>log4j</groupId>
	<artifactId>log4j</artifactId>
	<version>1.2.17</version>
</dependency>

        还需要把以前案例中使用的 log4j.properties 文件粘贴到 resources 目录下,然后执行测试代码,如下所示:

6.3.4 绑定 jul

        如果想要绑定 jul,需要需要绑定一个 slf4j-jdk14 适配器,如下 maven 依赖:

<!--slf4j core 使用slf4j必須添加-->
<dependency>
	<groupId>org.slf4j</groupId>
	<artifactId>slf4j-api</artifactId>
	<version>1.7.27</version>
</dependency>
<!-- 绑定 jul 日志实现,需要导入适配器-->
<dependency>
	<groupId>org.slf4j</groupId>
	<artifactId>slf4j-jdk14</artifactId>
	<version>1.7.25</version>
</dependency>

        输出结果如下所示:

6.3.5 绑定实现小结

        1、添加 slf4j-api 的依赖

        2、使用 slf4j API 在项目中进行统一的日志记录

        3、绑定具体的日志实现框架,针对于已经实现了 slf4j 的日志框架,直接添加对应依赖;针对于没有实现 slf4j 的日志框架,先添加日志的适配器,再添加实现类的依赖。

        4、slf4j 有且仅有一个日志实现框架的绑定(如果出现多个默认使用第一个依赖日志实现)

6.4 桥接旧的日志框架

6.4.1 示例展示

        假设以前项目用的是 log4j 日志框架,现在项目需要切换到 slf4j + logback 日志框架,由于这两个框架没啥关系,切换起来需要修改的代码也是很恐怖的。为了解决这种情况,slf4j 附带了几个桥接模块,可以解决这些问题。

        桥接解决的是项目中日志的遗留问题,当系统中存在之前的日志 API,可以通过桥接转换到 slf4j 的实现。

        现在的项目使用的是 log4j 框架,如下:

<dependency>
	<groupId>log4j</groupId>
	<artifactId>log4j</artifactId>
	<version>1.2.17</version>
</dependency>
import org.apache.log4j.Logger;

public class Log4jTest {

    public static final Logger LOGGER = Logger.getLogger(Log4jTest.class);
    public static void main(String[] args) {
        LOGGER.info("hello log4j");
    }
}

        可以看到此时没有任何问题。 

        此时不希望再使用 log4j 了,此时就需要把 log4j 的依赖注释掉,添加日志门面技术的依赖 slf4j 和 logback 的依赖,如下:

<!--slf4j core 使用slf4j必須添加-->
<dependency>
	<groupId>org.slf4j</groupId>
	<artifactId>slf4j-api</artifactId>
	<version>1.7.27</version>
</dependency>
<!-- logback 日志实现-->
<dependency>
	<groupId>ch.qos.logback</groupId>
	<artifactId>logback-classic</artifactId>
	<version>1.2.3</version>
</dependency>

        此时,项目里面的代码由于引入了 log4j 的类,此时就会报错,如下图:

        正常来说我们就需要修改里面的代码,但是我们不想改,此时就需要使用到 slf4j 里面的桥接器了,如下图:

         此时就需要配置 log4j 的桥接器,maven 依赖如下:

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>log4j-over-slf4j</artifactId>
            <version>1.7.25</version>
        </dependency>

        此时我们的工程就不再报错了,如下图:

        此时,执行的日志框架是 logback,如下图:

6.4.2 注意事项

        1、jcl-over-slf4j.jar slf4j-jcl.jar 不能同时部署。前一个 jar 文件将导致 JCL 将日志系统的选择委托给 SLF4J,后一个 jar 文件将导致 SLF4J 将日志系统的选择委托给 JCL,从而导致无限循环。

        2、log4j-over-slf4j.jar slf4j-log4j12.jar 不能同时出现,会出现栈内存溢出。

        3、jul-to-slf4j.jarslf4j-jdk14.jar 不能同时出现,会出现栈内存溢出。

        4、所有的桥接都只对 Logger 日志记录器对象有效,如果程序中调用了内部的配置类或者是 AppenderFilter 等对象,将无法产生效果。

6.5 slf4j 原理解析

        1、SLF4J 通过 LoggerFactory 加载日志具体的实现对象。

        2、 LoggerFactory 在初始化的过程中,会通过 performInitialization() 方法绑定具体的日志实现。

        3、在绑定具体实现的时候,通过类加载器,加载 org/slf4j/impl/StaticLoggerBinder.class

        4、所以,只要是一个日志实现框架,在 org.slf4j.impl 包中提供一个自己的 StaticLoggerBinder 类,在其中提供具体日志实现的 LoggerFactory 就可以被 SLF4J 所加载。

七、Logback

7.1 简介

        Logback 是由 log4j 创始人设计的另一个开源日志组件,性能比 log4j 要好。官网地址在这Logback 主要分为三个模块:

        1、logback-core:其它两个模块的基础模块

        2、logback-classic:它是 log4j 的一个改良版本,同时它完整实现了 slf4j API

        3、logback-access:访问模块与 Servlet 容器集成提供通过 Http 来访问日志的功能

7.2 入门案例

        首先创建一个 maven 工程,并添加如下的 maven 依赖:

<!--slf4j core 使用slf4j必須添加-->
<dependency>
	<groupId>org.slf4j</groupId>
	<artifactId>slf4j-api</artifactId>
	<version>1.7.27</version>
</dependency>
<!-- logback 日志实现-->
<dependency>
	<groupId>ch.qos.logback</groupId>
	<artifactId>logback-classic</artifactId>
	<version>1.2.3</version>
</dependency>

        编写 java 代码,如下:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class LogBackTest {

    //定义日志对象
    public final static Logger LOGGER =
            LoggerFactory.getLogger(LogBackTest.class);

    public static void main(String[] args) {
        //打印日志信息
        LOGGER.error("error");
        LOGGER.warn("warn");
        LOGGER.info("info");
        LOGGER.debug("debug"); // 默认输出级别
        LOGGER.trace("trace");
    }
}

         输出结果如下:

7.3 Logback 配置

        logback 会依次读取以下类型配置文件,如果均不存在会采用默认配置。

1、logback.groovy
2、logback-test.xml
3、logback.xml

        logback 一共有三个组件,分别为 LoggerAppender Layout

        Logger 是日志的记录器,把它关联到应用的对应的 context 上后,主要用于存放日志对象,也可以定义日志类型、级别。

        Appender 用于指定日志输出的目的地,目的地可以是控制台、文件、数据库等等。

        Layout 负责把事件转换成字符串,格式化的日志信息的输出。在 logback Layout 对象被封装在 encoder 中。

7.3.1 基本信息配置

        首先在 resources 目录下创建一个 logback.xml 文件,内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!-- 日志输出格式:
        %-5level                      :左对齐、占用五个字符、日志输出级别
        %d{yyyy-MM-dd HH:mm:ss.SSS}   :日期
        %c                            :类的完整名称
        %M                            :输出日志的 method
        %L                            :行号
        %thread                       :线程名称
        %m或者%msg                     :输出的信息
        %n                            :换行
    -->
    <!--格式化输出:%d 表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度 %msg:日志消息,%n是换行符-->
    <property name="pattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %c [%thread] %-5level %msg%n"/>

    <!--
        Appender: 设置日志信息的去向,常用的有以下几个
            ch.qos.logback.core.ConsoleAppender               :输出到控制台
            ch.qos.logback.core.rolling.RollingFileAppender   :文件大小到达指定尺寸的时候产生一个新文件
            ch.qos.logback.core.FileAppender                  :文件
    -->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <!--输出流对象 默认 System.out 改为 System.err 输出的日志将变成红色-->
        <target>System.err</target>
        <!--日志格式配置-->
        <encoder  class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${pattern}</pattern>
        </encoder>
    </appender>

    <!-- root logger 配置,指定了日志的输出级别和输出位置-->
    <root level="ALL">
        <appender-ref ref="console"></appender-ref>
    </root>
</configuration>

        执行测试代码,输出结果如下:

7.3.2 FileAppender 配置

        还可以将日志输出到指定的文件之中,如下配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!-- 日志输出格式:
        %-5level                      :左对齐、占用五个字符、日志输出级别
        %d{yyyy-MM-dd HH:mm:ss.SSS}   :日期
        %c                            :类的完整名称
        %M                            :输出日志的 method
        %L                            :行号
        %thread                       :线程名称
        %m或者%msg                     :输出的信息
        %n                            :换行
    -->
    <!--格式化输出:%d 表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度 %msg:日志消息,%n是换行符-->
    <property name="pattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %c [%thread] %-5level %msg%n"/>

    <!-- 添加一个标签用于指定日志存储的位置-->
    <property name="log_dir" value="e:/logs" />
     <!--
        Appender: 设置日志信息的去向,常用的有以下几个
            ch.qos.logback.core.ConsoleAppender               :输出到控制台
            ch.qos.logback.core.rolling.RollingFileAppender   :文件大小到达指定尺寸的时候产生一个新文件
            ch.qos.logback.core.FileAppender                  :文件
    -->
    <!--控制台输出的 appender-->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <!--输出流对象 默认 System.out 改为 System.err 输出的日志将变成红色-->
        <target>System.err</target>
        <!--日志格式配置-->
        <encoder  class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${pattern}</pattern>
        </encoder>
    </appender>

    <!-- 日志文件输出的 appender-->
    <appender name="file" class="ch.qos.logback.core.FileAppender">
      <file>${log_dir}/logback.log</file>
        <!--日志格式配置-->
        <encoder  class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${pattern}</pattern>
        </encoder>
    </appender>

    <!-- root logger 配置-->
    <root level="ALL">
        <appender-ref ref="console"></appender-ref>
        <appender-ref ref="file"></appender-ref>
    </root>
</configuration>

        执行测试代码,不光在控制台有日志输出,还在文件夹种生成了日志文件,如下:

7.3.3 RollingFileAppender 配置

        接下来介绍下日志拆分和归档压缩的 appender 对象,如下配置文件,既可以按照时间拆分,也可以按照文件的大小拆分。

<?xml version="1.0" encoding="UTF-8"?>
<configuration>

    <!--格式化输出:%d 表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度 %msg:日志消息,%n是换行符-->
    <property name="pattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %c [%thread] %-5level %msg%n"/>

    <!-- 添加一个标签用于指定日志存储的位置-->
    <property name="log_dir" value="e:/logs" />
    
    <!-- 日志文件拆分和归档的 appender 对象-->
    <appender name="rollFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!--日志格式配置-->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${pattern}</pattern>
        </encoder>
        <!--日志输出路径-->
        <file>${log_dir}/roll_logback.log</file>
        <!--指定日志文件拆分和压缩规则-->
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!--通过指定压缩文件名称,来确定分割文件方式-->
            <fileNamePattern>${log_dir}/rolling.%d{yyyy-MM-dd}.log%i.gz</fileNamePattern>
            <!--文件拆分大小-->
            <maxFileSize>1MB</maxFileSize>
        </rollingPolicy>
    </appender>

    <!-- root logger 配置-->
    <root level="ALL">
        <appender-ref ref="rollFile"></appender-ref>
    </root>
</configuration>

7.3.4 Filter 配置

        我们可以在 appender 中添加过滤器,只有当超过我们指定的日志级别才会存储,没超过则不存储,如下配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>

    <!--格式化输出:%d 表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度 %msg:日志消息,%n是换行符-->
    <property name="pattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %c [%thread] %-5level %msg%n"/>

    <!-- 添加一个标签用于指定日志存储的位置-->
    <property name="log_dir" value="e:/logs" />
    
    <!-- 日志文件拆分和归档的 appender 对象-->
    <appender name="rollFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!--日志格式配置-->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${pattern}</pattern>
        </encoder>
        <!--日志输出路径-->
        <file>${log_dir}/roll_logback.log</file>
        <!--指定日志文件拆分和压缩规则-->
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!--通过指定压缩文件名称,来确定分割文件方式-->
            <fileNamePattern>${log_dir}/rolling.%d{yyyy-MM-dd}.log%i.gz</fileNamePattern>
            <!--文件拆分大小-->
            <maxFileSize>1MB</maxFileSize>
        </rollingPolicy>
        <!--filter配置-->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <!--设置拦截日志级别-->
            <level>error</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!-- root logger 配置-->
    <root level="ALL">
        <appender-ref ref="rollFile"></appender-ref>
    </root>
</configuration>

7.3.5 异步日志配置

        我们还可以配置异步的日志存储,主线程在控制台打印日志,存储日志文件是另外一个线程来操作的,如下配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>

    <!--格式化输出:%d 表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度 %msg:日志消息,%n是换行符-->
    <property name="pattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %c [%thread] %-5level %msg%n"/>

    <!-- 添加一个标签用于指定日志存储的位置-->
    <property name="log_dir" value="e:/logs" />

    <!-- 日志文件拆分和归档的 appender 对象-->
    <appender name="rollFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!--日志格式配置-->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${pattern}</pattern>
        </encoder>
        <!--日志输出路径-->
        <file>${log_dir}/roll_logback.log</file>
        <!--指定日志文件拆分和压缩规则-->
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!--通过指定压缩文件名称,来确定分割文件方式-->
            <fileNamePattern>${log_dir}/rolling.%d{yyyy-MM-dd}.log%i.gz</fileNamePattern>
            <!--文件拆分大小-->
            <maxFileSize>1MB</maxFileSize>
        </rollingPolicy>
        <!--filter配置-->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <!--设置拦截日志级别-->
            <level>error</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!--异步日志-->
    <appender name="async" class="ch.qos.logback.classic.AsyncAppender">
        <!-- 需要指定具体的 appender -->
        <appender-ref ref="rollFile"/>
    </appender>

    <!-- root logger 配置-->
    <root level="ALL">
        <appender-ref ref="console"></appender-ref>
        <appender-ref ref="rollFile"></appender-ref>
    </root>
</configuration>

7.3.6 自定义 logger

        我们还可以自定义 logger 对象,并指定日志的输出级别,如下配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>

    <!--格式化输出:%d 表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度 %msg:日志消息,%n是换行符-->
    <property name="pattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %c [%thread] %-5level %msg%n"/>

    <!-- 添加一个标签用于指定日志存储的位置-->
    <property name="log_dir" value="e:/logs" />

    <!-- 日志文件拆分和归档的 appender 对象-->
    <appender name="rollFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!--日志格式配置-->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${pattern}</pattern>
        </encoder>
        <!--日志输出路径-->
        <file>${log_dir}/roll_logback.log</file>
        <!--指定日志文件拆分和压缩规则-->
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!--通过指定压缩文件名称,来确定分割文件方式-->
            <fileNamePattern>${log_dir}/rolling.%d{yyyy-MM-dd}.log%i.gz</fileNamePattern>
            <!--文件拆分大小-->
            <maxFileSize>1MB</maxFileSize>
        </rollingPolicy>
        <!--filter配置-->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <!--设置拦截日志级别-->
            <level>error</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!--异步日志-->
    <appender name="async" class="ch.qos.logback.classic.AsyncAppender">
        <!-- 需要指定具体的 appender -->
        <appender-ref ref="rollFile"/>
    </appender>

    <!-- root logger 配置-->
    <root level="ALL">
        <appender-ref ref="console"></appender-ref>
        <appender-ref ref="rollFile"></appender-ref>
    </root>

    <!--自定义 logger 
        additivity 表示是否从 rootLogger 继承配置-->
    <logger name="org.example" level="debug" additivity="false">
        <appender-ref ref="console"/>
    </logger>

</configuration>

7.4 logback-access 使用

        logback-access 模块主要用于与 Servlet 容器(如 Tomcat Jetty)集成,以提供 HTTP 访问日志功能。我们可以使用 logback-access 模块来替换 tomcat 的访问日志。

        正常来说,我们的 tomcat 的日志是存放在下面的目录下:

        如果我们想要使用 logback 来替换掉 tomcat 的访问日志,就需要下面的三个步骤:

        1、 logback-access.jar logback-core.jar 复制到 $TOMCAT_HOME/lib/ 目录下。

        2、修改 $TOMCAT_HOME/conf/server.xml 中的 Host 元素中添加

<Valve className="ch.qos.logback.access.tomcat.LogbackValve" />

         3、然后在 $TOMCAT_HOME/conf 下添加 logback-access.xml 配置文件,内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!-- always a good activate OnConsoleStatusListener -->
    <statusListener
            class="ch.qos.logback.core.status.OnConsoleStatusListener"/>
    <property name="LOG_DIR" value="${catalina.base}/logs"/>
    <appender name="FILE"
              class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_DIR}/access.log</file>
        <rollingPolicy
                class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>access.%d{yyyy-MM-dd}.log.zip</fileNamePattern>
        </rollingPolicy>
        <encoder>
            <!-- 访问日志的格式 -->
            <pattern>combined</pattern>
        </encoder>
    </appender>
    <appender-ref ref="FILE"/>
</configuration>

八、log4j2

8.1 简介

        Apache Log4j 2 是对 Log4j 的升级版,参考了 logback 的一些优秀的设计,并且修复了一些问题,因此带来了一些重大的提升,主要有:

        1、异常处理,在 logback 中,Appender 中的异常不会被应用感知到,但是在 log4j2 中,提供了一些异常处理机制。

        2、性能提升, log4j2 相较于 log4jlogback 都具有很明显的性能提升,后面会有官方测试的数据。

        3、自动重载配置,参考了 logback 的设计,当然会提供自动刷新参数配置,最实用的就是我们在生产上可以动态的修改日志的级别而不需要重启应用。

        4、无垃圾机制,log4j2 在大部分情况下,都可以使用其设计的一套无垃圾机制,避免频繁的日志收集导致的 jvm gc

8.2 log4j2 入门

        目前市面上最主流的日志门面就是 SLF4J,虽然 Log4j2 也是日志门面,因为它的日志实现功能非常强大,性能优越。所以大家一般还是将 Log4j2 看作是日志的实现,Slf4j + Log4j2 应该是未来的大势所趋。

        log4j2 既可以作为门面使用,又可以作为日志实现使用,我们来看下效果,首先创建一个 maven 工程,添加 maven 依赖,如下:

<!-- Log4j2 门面API-->
<dependency>
	<groupId>org.apache.logging.log4j</groupId>
	<artifactId>log4j-api</artifactId>
	<version>2.11.1</version>
</dependency>
<!-- Log4j2 日志实现 -->
<dependency>
	<groupId>org.apache.logging.log4j</groupId>
	<artifactId>log4j-core</artifactId>
	<version>2.11.1</version>
</dependency>

        编写测试代码如下所示:

public class Log4j2Test {

    // 定义日志记录器对象
   public static final Logger LOGGER =
            LogManager.getLogger(Log4j2Test.class);
    public static void main(String[] args) {
        LOGGER.fatal("fatal");
        LOGGER.error("error"); // 默认级别
        LOGGER.warn("warn");
        LOGGER.info("info");
        LOGGER.debug("debug");
        LOGGER.trace("trace");
    }
}

        输出结果如下所示:

        目前主流的用法是使用 slf4j 作为日志的门面,使用 log4j2 作为日志的实现,添加如下的 maven 依赖:

<!--使用slf4j作为日志的门面,使用log4j2来记录日志 -->
<dependency>
	<groupId>org.slf4j</groupId>
	<artifactId>slf4j-api</artifactId>
	<version>1.7.25</version>
</dependency>
<!--为slf4j绑定日志实现 log4j2的适配器 -->
<dependency>
	<groupId>org.apache.logging.log4j</groupId>
	<artifactId>log4j-slf4j-impl</artifactId>
	<version>2.10.0</version>
</dependency>

        执行上面的测试代码,输出结果如下:

8.3 log4j2 配置

        log4j2 默认加载 classpath 下的 log4j2.xml 文件中的配置。配置文件的详细信息,如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<!--
    status="warn" 日志框架本身的输出日志级别
    monitorInterval="5" 自动加载配置文件的间隔时间,不低于 5 秒,即支持热部署,修改配置信息不需要重启服务
-->
<Configuration status="debug" monitorInterval="5">

    <!--
        集中配置属性进行管理
        使用时通过:${name}
    -->
    <properties>
        <property name="LOG_HOME">e:/logs</property>
    </properties>

    <!--日志处理-->
    <Appenders>
        <!--控制台输出 appender-->
        <Console name="Console" target="SYSTEM_ERR">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] [%-5level] %c{36}:%L --- %m%n" />
        </Console>

        <!--日志文件输出 appender-->
        <File name="file" fileName="${LOG_HOME}/myfile.log">
            <PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l %c{36} - %m%n" />
        </File>

        <!--使用随机读写流的日志文件输出 appender,性能提高-->
        <RandomAccessFile name="accessFile" fileName="${LOG_HOME}/myAcclog.log">
            <PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l %c{36} - %m%n" />
        </RandomAccessFile>

        <!--按照一定规则拆分的日志文件的 appender-->
        <RollingFile name="rollingFile" fileName="${LOG_HOME}/myrollog.log"
                     filePattern="/logs/$${date:yyyy-MM-dd}/myrollog-%d{yyyy-MM-dd-HH-mm}-%i.log">
            <!--日志级别过滤器-->
            <ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY" />
            <!--日志消息格式-->
            <PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l %c{36} - %msg%n" />
            <Policies>
                <!--在系统启动时,出发拆分规则,生产一个新的日志文件-->
                <OnStartupTriggeringPolicy />
                <!--按照文件大小拆分,10MB -->
                <SizeBasedTriggeringPolicy size="10 MB" />
                <!--按照时间节点拆分,规则根据filePattern定义的-->
                <TimeBasedTriggeringPolicy />
            </Policies>
            <!--在同一个目录下,文件的个数限定为 30 个,超过进行覆盖-->
            <DefaultRolloverStrategy max="30" />
        </RollingFile>
    </Appenders>

    <!--logger 定义-->
    <Loggers>
        <!--使用 rootLogger 配置 日志级别 level="trace"-->
        <Root level="trace">
            <!--指定日志使用的处理器-->
            <AppenderRef ref="Console" />
            <AppenderRef ref="file" />
        </Root>
    </Loggers>
</Configuration>

        启动测试代码,输出结果如下:

8.4 log4j2 异步日志

        log4j2 最大的特点就是异步日志,其性能的提升主要也是从异步日志中受益,我们来看看如何使用。

        先来看下同步日志的整体运行流程,如下图所示,主线程如果想要输出一条日志消息,需要调用 logger 对象的 info() 方法,其内部首先进行日志级别的过滤,过滤完成之后生成一个日志消息的内容,然后创建一个 LogEvent 日志事件对象,然后调用给 appender 进行调用和处理,再根据拦截器去判断是否拦截还剩放行,然后调用 layout 日志消息格式化,最终调用输出流进行输出到指定位置。

        再来看下异步日志的整体运行流程,如下图所示,还是主线程调用 logger 对象的 info() 方法,也会创建一个 LogEvent 日志事件对象,然后将 LogEvent 传给了一个阻塞队列,传递成功之后主线程任务完成,此时 log4j2 就会开启一个新的线程让 LogEvent 对象调用具体的 appender 进行处理,最终输出到我们的指定位置。

        Log4j2 提供了两种实现异步日志的方式,一个是通过 AsyncAppender,一个是通过 AsyncLogger,分别对应前面我们说的 Appender 组件和 Logger 组件。

        开启异步日志需要添加 maven 依赖,如下:

<!--异步日志依赖-->
<dependency>
	<groupId>com.lmax</groupId>
	<artifactId>disruptor</artifactId>
	<version>3.3.4</version>
</dependency>

8.4.1 AsyncAppender 方式

        这种异步的 appender 的方式在开发中几乎没有人使用,因为它的性能并没有提升多少。它是在 log4j2.xml 中的 appenders 标签中配置一个 Async 标签,指定一个异步日志的 appender 名称和引用的具体对象即可,如下配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<!--
    status="warn" 日志框架本身的输出日志级别
    monitorInterval="5" 自动加载配置文件的间隔时间,不低于 5 秒
-->
<Configuration status="debug" monitorInterval="5">

    <!--
        集中配置属性进行管理
        使用时通过:${name}
    -->
    <properties>
        <property name="LOG_HOME">e:/logs</property>
    </properties>

    <!--日志处理-->
    <Appenders>
        <!--控制台输出 appender-->
        <Console name="Console" target="SYSTEM_ERR">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] [%-5level] %c{36}:%L --- %m%n" />
        </Console>

        <!--日志文件输出 appender-->
        <File name="file" fileName="${LOG_HOME}/myfile.log">
            <PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l %c{36} - %m%n" />
        </File>

        <Async name="Async">
            <AppenderRef ref="file"/>
        </Async>

        <!--使用随机读写流的日志文件输出 appender,性能提高-->
        <RandomAccessFile name="accessFile" fileName="${LOG_HOME}/myAcclog.log">
            <PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l %c{36} - %m%n" />
        </RandomAccessFile>

        <!--按照一定规则拆分的日志文件的 appender-->
        <RollingFile name="rollingFile" fileName="${LOG_HOME}/myrollog.log"
                     filePattern="/logs/$${date:yyyy-MM-dd}/myrollog-%d{yyyy-MM-dd-HH-mm}-%i.log">
            <!--日志级别过滤器-->
            <ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY" />
            <!--日志消息格式-->
            <PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l %c{36} - %msg%n" />
            <Policies>
                <!--在系统启动时,出发拆分规则,生产一个新的日志文件-->
                <OnStartupTriggeringPolicy />
                <!--按照文件大小拆分,10MB -->
                <SizeBasedTriggeringPolicy size="10 MB" />
                <!--按照时间节点拆分,规则根据filePattern定义的-->
                <TimeBasedTriggeringPolicy />
            </Policies>
            <!--在同一个目录下,文件的个数限定为 30 个,超过进行覆盖-->
            <DefaultRolloverStrategy max="30" />
        </RollingFile>
    </Appenders>

    <!--logger 定义-->
    <Loggers>
        <!--使用 rootLogger 配置 日志级别 level="trace"-->
        <Root level="trace">
            <!--指定日志使用的处理器-->
            <AppenderRef ref="Console" />
            <AppenderRef ref="Async" />
        </Root>
    </Loggers>
</Configuration>

8.4.2 AsyncLogger 方式

        AsyncLogger 才是 log4j2 的重头戏,也是官方推荐的异步方式。它可以使得调用 Logger.log 返回的更快。你可以有两种选择:全局异步和混合异步。

        全局异步就是所有的日志都异步的记录,在配置文件上不用做任何改动,只需要添加一个log4j2.component.properties 配置文件,并添加如下的内容:

Log4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector

        其内部就是采用异步的 Context 来实现日志的记录,如下图

        混合异步就是,你可以在应用中同时使用同步日志和异步日志,这使得日志的配置方式更加灵活。比如说下面的配置文件,其中 com.itheima 日志是异步的,root 日志是同步的。

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
    <properties>
        <property name="LOG_HOME">D:/logs</property>
    </properties>
    <Appenders>
        <File name="file" fileName="${LOG_HOME}/myfile.log">
            <PatternLayout>
                <Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
            </PatternLayout>
        </File>
        <Async name="Async">
            <AppenderRef ref="file"/>
        </Async>
    </Appenders>
    <Loggers>
        <AsyncLogger name="com.itheima" level="trace"
                     includeLocation="false" additivity="false">
            <AppenderRef ref="file"/>
        </AsyncLogger>
        <Root level="info" includeLocation="true">
            <AppenderRef ref="file"/>
        </Root>
    </Loggers>
</Configuration>

8.5 注意事项

        1、如果使用异步日志,AsyncAppenderAsyncLogger 和全局日志,不要同时出现。性能会和 AsyncAppender 一致,降至最低。

        2、如果设置了 includeLocation=false 属性,打印位置信息会急剧降低异步日志的性能,比同步日志还要慢。

8.6 log4j2 性能

        Log4j2 最牛的地方在于异步输出日志时的性能表现,Log4j2 在多线程的环境下吞吐量与 Log4j Logback 的比较如下图。下图比较中 Log4j2 有三种模式:

        1、全局使用异步模式;

        2、部分 Logger 采用异步模式;

        3、异步 Appender。可以看出在前两种模式下,Log4j2 的性能较之 Log4j Logback 有很大的优势。

8.7 无垃圾记录

        垃圾收集暂停是延迟峰值的常见原因,并且对于许多系统而言,花费大量精力来控制这些暂停。

        许多日志库(包括以前版本的 Log4j)在稳态日志记录期间分配临时对象,如日志事件对象,字符串,字符数组,字节数组等。这会对垃圾收集器造成压力并增加 GC 暂停发生的频率。

        从版本 2.6 开始,默认情况下 Log4j 以“无垃圾”模式运行,其中重用对象和缓冲区,并且尽可能不分配临时对象。还有一个“低垃圾”模式,它不是完全无垃圾,但不使用 ThreadLocal 字段。

        Log4j 2.6 中的无垃圾日志记录部分通过重用 ThreadLocal 字段中的对象来实现,部分通过在将文本转换为字节时重用缓冲区来实现。

九、SpringBoot 中的日志使用

        springboot 框架在企业中的使用越来越普遍,springboot 日志也是开发中常用的日志系统。springboot 默认就是使用 SLF4J 作为日志门面,logback 作为日志实现来记录日志。

9.1 日志设计

        springboot 中的日志是在如下的依赖中的,如下:

<dependency>
	<artifactId>spring-boot-starter-logging</artifactId>
	<groupId>org.springframework.boot</groupId>
</dependency>

         它的依赖关系图如下所示:

总结: 

        1、springboot 底层默认使用 logback 作为日志实现。

        2、使用了 SLF4J 作为日志门面

        3、JUL 也转换成 slf4j

        4、也可以使用 log4j2 作为日志门面,但是最终也是通过 slf4j 调用 logback

9.2 日志使用

        在 springboot 中测试打印日志,如下代码:

@SpringBootTest
class SpringBootTestApplicationTests {

    //记录器
    public static final Logger LOGGER =
            LoggerFactory.getLogger(SpringBootTestApplication.class);
    @Test
    void contextLoads() {
        // 打印日志信息
        LOGGER.error("error");
        LOGGER.warn("warn");
        LOGGER.info("info"); // 默认日志级别
        LOGGER.debug("debug");
        LOGGER.trace("trace");
    }
}

        输出结果如下所示:

        可以在 application.propesties 中设置一些日志的简单属性,如下所示:

# 指定某些类的日志输出级别
logging.level.com.log.springboottest=trace

# 在控制台输出的日志的格式 同logback
logging.pattern.console=%d{yyyy-MM-dd} [%thread] [%-5level] %logger{50} -%msg%n

# 指定文件中日志输出的格式
logging.file=D:/logs/springboot.log
logging.pattern.file=%d{yyyy-MM-dd} [%thread] %-5level %logger{50} - %msg%n

        这些简单的日志属性配置还不够满足企业的正常开发,我们还需要导入具体某个日志实现矿建的配置文件,如下,给类路径下放上每个日志框架自己的配置文件,SpringBoot 就不使用默认配置的了。

日志框架配置文件
Logbacklogback-spring.xml 或 logback.xml
Log4j2log4j2-spring.xml 或 log4j2.xml
JULlogging.properties

        接下来我们在 resources 目录下创建一个 logback.xml 文件,内容如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>


    <property name="pattern" value="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} %c %M %L [%thread] %m%n"></property>

    <!--控制台日志输出的 appender-->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <!--控制输出流对象 默认 System.out 改为 System.err-->
        <target>System.err</target>
        <!--日志消息格式配置-->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${pattern}</pattern>
        </encoder>
    </appender>

    <logger name="com.log.springboottest" level="info" additivity="false">
        <appender-ref ref="console"/>
    </logger>
</configuration>

        输出结果如下所示,可以看到 application.propesties 里面的配置失效了,生效的是 logback.xml 里面的配置。

        logback.xml 是直接就被日志框架识别了,而 logback-spring.xml 是由 SpringBoot 解析日志配置,所以我们可以通过配置文件动态的控制测试环境和生产环境的日志输出格式。

        首先创建一个  logback-spring.xml 文件,并放到 resources 目录下,内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>

    <property name="pattern" value="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} %c %M %L [%thread] %m%n"></property>

    <!--控制台日志输出的 appender-->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <!--控制输出流对象 默认 System.out 改为 System.err-->
        <target>System.err</target>
        <!--日志消息格式配置-->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <springProfile name="dev">
                <pattern>${pattern}</pattern>
            </springProfile>
            <springProfile name="pro">
                <pattern>%d{yyyyMMdd:HH:mm:ss.SSS} [%thread] %-5level
                    %msg%n</pattern>
            </springProfile>
        </encoder>
    </appender>

    <logger name="com.log.springboottest" level="info" additivity="false">
        <appender-ref ref="console"/>
    </logger>
</configuration>

        然后在 application.properties 中动态的控制日志的输出类型,是 dev 还是 pro,配置如下:

spring.profiles.active=dev

9.3 日志替换 

        我们还可以将默认的日志实现 logback 升级为 log4j2,需要去除依赖和添加依赖,如下:

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <!--排除 logback 日志实现-->
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <!--使用 log4j2 的日志启动器-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j2</artifactId>
        </dependency>
        <!--这个 maven 依赖的位置要在 spring-boot-starter-web 下面,否则会出问题-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

        启动测试代码,输出结果如下,没啥问题。

十、@SLF4J 注解

10.1 注解由来

        @Slf4j Lombok 提供的一种注解,用于在类中自动生成一个名为 log 的日志对象。通过使用 @Slf4j 注解,可以方便地在代码中使用日志功能,而无需手动创建和初始化日志对象。

10.2 注解作用

        一般情况下如果我们想要使用日志对象,就需要在类里面使用如下的写法:

private static final Logger logger = LoggerFactory.getLogger(类.class);

        现在有了 @Slf4j 之后,直接使用下面的写法即可:

@SpringBootTest
@Slf4j
class SpringBootTestApplicationTests {


    @Test
    void contextLoads() {
        // 打印日志信息
        log.error("error");
        log.warn("warn");
        log.info("info"); // 默认日志级别
        log.debug("debug");
        log.trace("trace");
    }
}

10.3 如何引入

        如果你使用的是 idea,需要确保你已经安装了 Lombok 插件,如下图,为什么要安装 lombok 插件?是因为该插件中会将 @Slf4j 注解进行编译。

        还需要在 pom.xml 引入相关的依赖,如下:

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.22</version>
        </dependency>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

快乐的小三菊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值